/// <summary> /// Extracts the code fragments based on the current file content that need to be re-processed due to content changes on the given lines. /// Ignores any whitespace or comments at the beginning of the file (whether they have changed or not). /// Ignores any whitespace or comments that occur after the last piece of code in the file. /// Throws an ArgumentNullException if any of the arguments is null. /// </summary> private static IEnumerable <CodeFragment> FragmentsToProcess(this FileContentManager file, SortedSet <int> changedLines) { // NOTE: I suggest not to touch this routine unless absolutely necessary...(things *will* break) if (file == null) { throw new ArgumentNullException(nameof(file)); } if (changedLines == null) { throw new ArgumentNullException(nameof(changedLines)); } var iter = changedLines.GetEnumerator(); var lastInFile = LastInFile(file); Position processed = new Position(0, 0); while (iter.MoveNext()) { QsCompilerError.Verify(0 <= iter.Current && iter.Current < file.NrLines(), "index out of range for changed line"); if (processed.Line < iter.Current) { var statementStart = file.PositionAfterPrevious(new Position(iter.Current, 0)); if (processed.IsSmallerThan(statementStart)) { processed = statementStart; } } while (processed.Line <= iter.Current && processed.IsSmallerThan(lastInFile)) { processed = processed.Copy(); // because we don't want to modify the ending of the previous code fragment ... var nextEnding = file.FragmentEnd(ref processed); var extractedPiece = file.GetCodeSnippet(new Range { Start = processed, End = nextEnding }); // constructing the CodeFragment - // NOTE: its Range.End is the position of the delimiting char (if such a char exists), i.e. the position right after Code ends if (extractedPiece.Length > 0) // length = 0 can occur e.g. if the last piece of code in the file does not terminate with a statement ending { var code = file.GetLine(nextEnding.Line).ExcessBracketPositions.Contains(nextEnding.Character - 1) ? extractedPiece.Substring(0, extractedPiece.Length - 1) : extractedPiece; if (code.Length == 0 || !CodeFragment.DelimitingChars.Contains(code.Last())) { code = $"{code}{CodeFragment.MissingDelimiter}"; } var endChar = nextEnding.Character - (extractedPiece.Length - code.Length) - 1; var codeRange = new Range { Start = processed, End = new Position(nextEnding.Line, endChar) }; yield return(new CodeFragment(file.IndentationAt(codeRange.Start), codeRange, code.Substring(0, code.Length - 1), code.Last())); } processed = nextEnding; } } }
/// <summary> /// Returns the completion environment at the given position in the file or null if the environment cannot be /// determined. Stores the code fragment found at or before the given position into an out parameter. /// </summary> private static (CompletionScope?, QsFragmentKind?) GetCompletionEnvironment( FileContentManager file, Position position, out CodeFragment?fragment) { if (!file.ContainsPosition(position)) { fragment = null; return(null, null); } var token = GetTokenAtOrBefore(file, position); if (token is null) { fragment = null; return(null, null); } fragment = token.GetFragment(); var relativeIndentation = fragment.Indentation - file.IndentationAt(position); QsCompilerError.Verify(Math.Abs(relativeIndentation) <= 1); var parents = new[] { token } .Concat(token.GetNonEmptyParents()) .Skip(relativeIndentation + 1) .Select(index => index.GetFragment()) .ToImmutableList(); var scope = parents.IsEmpty ? CompletionScope.TopLevel : parents.FirstOrDefault()?.Kind?.IsNamespaceDeclaration ?? false ? CompletionScope.NamespaceTopLevel : parents.Any(parent => parent.Kind?.IsFunctionDeclaration ?? false) ? CompletionScope.Function : parents.FirstOrDefault()?.Kind?.IsOperationDeclaration ?? false ? CompletionScope.OperationTopLevel : parents.Any(parent => parent.Kind?.IsOperationDeclaration ?? false) ? CompletionScope.Operation : null; var previous = relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position) ? fragment.Kind : relativeIndentation == 0 ? token.PreviousOnScope()?.GetFragment().Kind : relativeIndentation == 1 ? token.GetNonEmptyParent()?.GetFragment().Kind : null; return(scope, previous); }
/// <summary> /// Returns the completion environment at the given position in the file or null if the environment cannot be /// determined. Stores the code fragment found at or before the given position into an out parameter. /// </summary> /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception> /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception> private static (CompletionScope, QsFragmentKind) GetCompletionEnvironment( FileContentManager file, Position position, out CodeFragment fragment) { if (file == null) { throw new ArgumentNullException(nameof(file)); } if (!Utils.IsValidPosition(position)) { throw new ArgumentException(nameof(position)); } if (!Utils.IsValidPosition(position, file)) { // FileContentManager.IndentationAt will fail if the position is not within the file. fragment = null; return(null, null); } var token = GetTokenAtOrBefore(file, position); if (token == null) { fragment = null; return(null, null); } fragment = token.GetFragment(); var relativeIndentation = fragment.Indentation - file.IndentationAt(position); QsCompilerError.Verify(Math.Abs(relativeIndentation) <= 1); var parents = new[] { token }.Concat(token.GetNonEmptyParents()) .Skip(relativeIndentation + 1) .Select(t => t.GetFragment()); CompletionScope scope = null; if (!parents.Any()) { scope = CompletionScope.TopLevel; } else if (parents.Any() && parents.First().Kind.IsNamespaceDeclaration) { scope = CompletionScope.NamespaceTopLevel; } else if (parents.Where(parent => parent.Kind.IsFunctionDeclaration).Any()) { scope = CompletionScope.Function; } else if (parents.Any() && parents.First().Kind.IsOperationDeclaration) { scope = CompletionScope.OperationTopLevel; } else if (parents.Where(parent => parent.Kind.IsOperationDeclaration).Any()) { scope = CompletionScope.Operation; } QsFragmentKind previous = null; if (relativeIndentation == 0 && IsPositionAfterDelimiter(file, fragment, position)) { previous = fragment.Kind; } else if (relativeIndentation == 0) { previous = token.PreviousOnScope()?.GetFragment().Kind; } else if (relativeIndentation == 1) { previous = token.GetNonEmptyParent()?.GetFragment().Kind; } return(scope, previous); }