Beispiel #1
0
        /// <summary>
        /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed,
        /// given the file for which those diagnostics were generated and the corresponding compilation.
        /// The given line number is used to determine the containing namespace.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUnknownIdentifiers
            (this FileContentManager file, CompilationUnit compilation, int lineNr, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var unknownCallables = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownIdentifier));
            var unknownTypes     = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UnknownType));

            if (!unknownCallables.Any() && !unknownTypes.Any())
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }

            var suggestionsForIds = unknownCallables.Select(d => d.Range.Start)
                                    .SelectMany(pos => file.NamespaceSuggestionsForIdAtPosition(pos, compilation, out var _));
            var suggestionsForTypes = unknownTypes.Select(d => d.Range.Start)
                                      .SelectMany(pos => file.NamespaceSuggestionsForTypeAtPosition(pos, compilation, out var _));

            return(file.OpenDirectiveSuggestions(lineNr, suggestionsForIds.Concat(suggestionsForTypes).ToArray())
                   .Select(edit => (edit.NewText.Trim().Trim(';'), file.GetWorkspaceEdit(edit))));
        }
Beispiel #2
0
        // the actual update routine

        /// <summary>
        /// Attempts to perform the necessary updates when replacing the range [start, start + count) by newText for the given file
        /// wrapping each step in a QsCompilerError.RaiseOnFailure.
        /// </summary>
        private static void Update(this FileContentManager file, int start, int count, IEnumerable <string> newText)
        {
            CodeLine[] replacements = QsCompilerError.RaiseOnFailure(
                () => ComputeCodeLines(newText, start > 0 ? file.GetLine(start - 1) : null).ToArray(),
                "scope tracking update failed during computing the replacements");

            IEnumerable <CodeLine>?updateRemaining = QsCompilerError.RaiseOnFailure(
                () => ComputeUpdates(file, start, count, replacements),
                "scope tracking update failed during computing the updates");

            QsCompilerError.RaiseOnFailure(
                () =>
            {
                if (updateRemaining == null)
                {
                    file.ContentUpdate(start, count, replacements);
                }
                else
                {
                    file.ContentUpdate(start, file.NrLines() - start, replacements.Concat(updateRemaining).ToArray());
                }
            }, "the proposed ContentUpdate failed");

            QsCompilerError.RaiseOnFailure(
                () =>
            {
                if (updateRemaining == null)
                {
                    file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start, replacements.Length));
                }
                else
                {
                    file.AddScopeDiagnostics(file.ComputeScopeDiagnostics(start));
                }
                file.AddScopeDiagnostics(file.CheckForMissingClosings());
            }, "updating the scope diagnostics failed");
        }
Beispiel #3
0
        /// <summary>
        /// Returns the workspace edit that describes the changes to be done if the symbol at the given position - if any - is renamed to the given name.
        /// Returns null if no symbol exists at the specified position,
        /// or if some parameters are unspecified (null),
        /// or if the specified position is not a valid position within the file.
        /// </summary>
        public static WorkspaceEdit Rename(this FileContentManager file, CompilationUnit compilation, Position position, string newName)
        {
            if (newName == null || file == null)
            {
                return(null);
            }
            var found = file.TryGetReferences(compilation, position, out var declLocation, out var locations);

            if (!found)
            {
                return(null);
            }

            if (declLocation != null)
            {
                locations = new[] { declLocation }.Concat(locations);
            }
            var changes = locations.ToLookup(loc => loc.Uri, loc => new TextEdit {
                Range = loc.Range, NewText = newName
            });

            return(new WorkspaceEdit
            {
                DocumentChanges = changes
                                  .Select(change => new TextDocumentEdit
                {
                    TextDocument = new VersionedTextDocumentIdentifier {
                        Uri = change.Key, Version = 1
                    },                                                                                        // setting version to null here won't work in VS Code ...
                    Edits = change.ToArray()
                })
                                  .ToArray(),

                Changes = changes.ToDictionary(
                    items => CompilationUnitManager.TryGetFileId(items.Key, out var name) ? name.Value : null,
                    items => items.ToArray())
            });
Beispiel #4
0
        /// <summary>
        /// Returns true if the given file contains any tokens overlapping with the given fragment.
        /// The range of the tokens in the file is assumed to be relative to their start line (the index at which they are listed),
        /// whereas the range of the given fragment is assumed to be the absolute range.
        /// Throws an ArgumentNullException if the given file or range is null.
        /// Throws an ArgumentOutOfRangeException if the given range is not a valid range within file.
        /// </summary>
        internal static bool ContainsTokensOverlappingWith(this FileContentManager file, Range range)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (!Utils.IsValidRange(range, file))
            {
                throw new ArgumentOutOfRangeException(nameof(range));
            }

            var(start, end) = (range.Start.Line, range.End.Line);
            if (start != end && file.GetTokenizedLines(start + 1, end - start - 1).SelectMany(x => x).Any())
            {
                return(true);
            }
            ;

            var inRange = file.GetTokenizedLine(start).Where(TokensAfter(new Position(0, range.Start.Character))); // checking tokens overlapping with range.Start below

            inRange = start == end
                ? inRange.Where(TokensStartingBefore(new Position(0, range.End.Character)))
                : inRange.Concat(file.GetTokenizedLine(end).Where(TokensStartingBefore(new Position(0, range.End.Character))));

            if (inRange.Any())
            {
                QsCompilerError.Raise($"{range.DiagnosticString()} overlaps for start = {start}, end = {end}, \n\n" +
                                      $"{String.Join("\n", file.GetTokenizedLine(start).Select(x => $"{x.GetRange().DiagnosticString()}"))},\n\n " +
                                      $"{String.Join("\n", file.GetTokenizedLine(end).Select(x => $"{x.GetRange().DiagnosticString()}"))},");
                return(true);
            }

            var overlapsWithStart = file.TryGetFragmentAt(range.Start, out var _);

            return(overlapsWithStart != null);
        }
Beispiel #5
0
        /// <summary>
        /// Returns a sequence of suggestions for update-and-reassign statements based on the generated diagnostics,
        /// and given the file for which those diagnostics were generated.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForUpdateAndReassignStatements
            (this FileContentManager file, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var updateOfArrayItemExprs = diagnostics.Where(DiagnosticTools.ErrorType(ErrorCode.UpdateOfArrayItemExpr));

            (string, WorkspaceEdit) SuggestedCopyAndUpdateExpr(CodeFragment fragment)
            {
                var exprInfo = Parsing.ProcessUpdateOfArrayItemExpr.Invoke(fragment.Text);

                // Skip if the statement did not match a pattern for which we can give a code action
                if (exprInfo == null || (exprInfo.Item1.Line == 1 && exprInfo.Item1.Column == 1))
                {
                    return("", null);
                }

                // Convert set <identifier>[<index>] = <rhs> to set <identifier> w/= <index> <- <rhs>
                var rhs           = $"{exprInfo.Item3} {Keywords.qsCopyAndUpdateOp.cont} {exprInfo.Item4}";
                var outputStr     = $"{Keywords.qsValueUpdate.id} {exprInfo.Item2} {Keywords.qsCopyAndUpdateOp.op}= {rhs}";
                var fragmentRange = fragment.GetRange();
                var edit          = new TextEdit {
                    Range = fragmentRange.Copy(), NewText = outputStr
                };

                return("Replace with an update-and-reassign statement.", file.GetWorkspaceEdit(edit));
            }

            return(updateOfArrayItemExprs
                   .Select(d => file?.TryGetFragmentAt(d.Range.Start, out var _, includeEnd: true))
                   .Where(frag => frag != null)
                   .Select(frag => SuggestedCopyAndUpdateExpr(frag))
                   .Where(s => s.Item2 != null));
        }
        /// <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);
        }
Beispiel #7
0
        /// <summary>
        /// Returns a sequence of suggestions for replacing ranges over array indices with the corresponding library call,
        /// provided the corresponding library is referenced.
        /// Returns an empty enumerable if this is not the case or any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForIndexRange
            (this FileContentManager file, CompilationUnit compilation, LSP.Range range)
        {
            if (file == null || compilation == null || range?.Start == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var indexRangeNamespaces = compilation.GlobalSymbols.NamespacesContainingCallable(BuiltIn.IndexRange.Name);

            if (!indexRangeNamespaces.Contains(BuiltIn.IndexRange.Namespace))
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var suggestedOpenDir = file.OpenDirectiveSuggestions(range.Start.Line, BuiltIn.IndexRange.Namespace);

            /// Returns true the given expression is of the form "0 .. Length(args) - 1",
            /// as well as the range of the entire expression and the argument tuple "(args)" as out parameters.
            bool IsIndexRange(QsExpression iterExpr, Position offset, out LSP.Range exprRange, out LSP.Range argRange)
            {
                if (iterExpr.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .RangeLiteral rangeExpression && iterExpr.Range.IsValue &&                               // iterable expression is a valid range literal
                    rangeExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral intLiteralExpression && intLiteralExpression.Item == 0L &&      // .. starting at 0 ..
                    rangeExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .SUB SUBExpression &&                                                       // .. and ending in subracting ..
                    SUBExpression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .IntLiteral subIntLiteralExpression && subIntLiteralExpression.Item == 1L &&  // .. 1 from ..
                    SUBExpression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .CallLikeExpression callLikeExression &&                                      // .. a call ..
                    callLikeExression.Item1.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .Identifier identifier &&                                                 // .. to and identifier ..
                    identifier.Item1.Symbol is QsSymbolKind <QsSymbol> .Symbol symName && symName.Item.Value == BuiltIn.Length.Name.Value &&                                          // .. "Length" called with ..
                    callLikeExression.Item2.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .ValueTuple valueTuple && callLikeExression.Item2.Range.IsValue)          // .. a valid argument tuple
                {
                    exprRange = DiagnosticTools.GetAbsoluteRange(offset, iterExpr.Range.Item);
                    argRange  = DiagnosticTools.GetAbsoluteRange(offset, callLikeExression.Item2.Range.Item);
                    return(true);
                }

                (exprRange, argRange) = (null, null);
                return(false);
            }

            /// Returns the text edits for replacing an range over the indices with the corresponding library call if the given code fragment is a suitable for-loop intro.
            /// The returned edits do *not* include an edit for adding the corresponding open-directive if necessary.
            IEnumerable <TextEdit> IndexRangeEdits(CodeFragment fragment)
            {
                if (fragment.Kind is QsFragmentKind.ForLoopIntro forLoopIntro && // todo: in principle we could give these suggestions for any index range
                    IsIndexRange(forLoopIntro.Item2, fragment.GetRange().Start, out var iterExprRange, out var argTupleRange))
                {
                    yield return(new TextEdit()
                    {
                        Range = new LSP.Range()
                        {
                            Start = iterExprRange.Start, End = argTupleRange.Start
                        },
                        NewText = BuiltIn.IndexRange.Name.Value
                    });

                    yield return(new TextEdit()
                    {
                        Range = new LSP.Range()
                        {
                            Start = argTupleRange.End, End = iterExprRange.End
                        },
                        NewText = ""
                    });
                }
            }

            var fragments = file.FragmentsOverlappingWithRange(range);
            var edits     = fragments.SelectMany(IndexRangeEdits);

            return(edits.Any()
                ? new[] { ("Use IndexRange to iterate over indices.", file.GetWorkspaceEdit(suggestedOpenDir.Concat(edits).ToArray())) }
Beispiel #8
0
        /// <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(iter.Current >= 0 && 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 LSP.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

                    // length = 0 can occur e.g. if the last piece of code in the file does not terminate with a statement ending
                    if (extractedPiece.Length > 0)
                    {
                        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 LSP.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;
                }
            }
        }
Beispiel #9
0
        /// <summary>
        /// Returns a sequence of suggestions on how deprecated syntax can be updated based on the generated diagnostics,
        /// and given the file for which those diagnostics were generated.
        /// Returns an empty enumerable if any of the given arguments is null.
        /// </summary>
        internal static IEnumerable <(string, WorkspaceEdit)> SuggestionsForDeprecatedSyntax
            (this FileContentManager file, IEnumerable <Diagnostic> diagnostics)
        {
            if (file == null || diagnostics == null)
            {
                return(Enumerable.Empty <(string, WorkspaceEdit)>());
            }
            var deprecatedUnitTypes         = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedUnitType));
            var deprecatedNOToperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedNOToperator));
            var deprecatedANDoperators      = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedANDoperator));
            var deprecatedORoperators       = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedORoperator));
            var deprecatedOpCharacteristics = diagnostics.Where(DiagnosticTools.WarningType(WarningCode.DeprecatedOpCharacteristics));

            (string, WorkspaceEdit) ReplaceWith(string text, LSP.Range range)
            {
                bool NeedsWs(Char ch) => Char.IsLetterOrDigit(ch) || ch == '_';

                if (range?.Start != null && range.End != null)
                {
                    var beforeEdit = file.GetLine(range.Start.Line).Text.Substring(0, range.Start.Character);
                    var afterEdit  = file.GetLine(range.End.Line).Text.Substring(range.End.Character);
                    if (beforeEdit.Any() && NeedsWs(beforeEdit.Last()))
                    {
                        text = $" {text}";
                    }
                    if (afterEdit.Any() && NeedsWs(afterEdit.First()))
                    {
                        text = $"{text} ";
                    }
                }
                var edit = new TextEdit {
                    Range = range?.Copy(), NewText = text
                };

                return($"Replace with \"{text.Trim()}\".", file.GetWorkspaceEdit(edit));
            }

            // update deprecated keywords and operators

            var suggestionsForUnitType = deprecatedUnitTypes.Select(d => ReplaceWith(Keywords.qsUnit.id, d.Range));
            var suggestionsForNOT      = deprecatedNOToperators.Select(d => ReplaceWith(Keywords.qsNOTop.op, d.Range));
            var suggestionsForAND      = deprecatedANDoperators.Select(d => ReplaceWith(Keywords.qsANDop.op, d.Range));
            var suggestionsForOR       = deprecatedORoperators.Select(d => ReplaceWith(Keywords.qsORop.op, d.Range));

            // update deprecated operation characteristics syntax

            var typeToQs = new ExpressionTypeToQs(new ExpressionToQs());

            string CharacteristicsAnnotation(Characteristics c)
            {
                typeToQs.onCharacteristicsExpression(SymbolResolution.ResolveCharacteristics(c));
                return($"{Keywords.qsCharacteristics.id} {typeToQs.Output}");
            }

            var suggestionsForOpCharacteristics = deprecatedOpCharacteristics.SelectMany(d =>
            {
                // TODO: TryGetQsSymbolInfo currently only returns information about the inner most leafs rather than all types etc.
                // Once it returns indeed all types in the fragment, the following code block should be replaced by the commented out code below.
                var fragment = file.TryGetFragmentAt(d.Range.Start, out var _);
                IEnumerable <Characteristics> GetCharacteristics(QsTuple <Tuple <QsSymbol, QsType> > argTuple) =>
                SyntaxGenerator.ExtractItems(argTuple).SelectMany(item => item.Item2.ExtractCharacteristics()).Distinct();
                var characteristicsInFragment =
                    fragment?.Kind is QsFragmentKind.FunctionDeclaration function ? GetCharacteristics(function.Item2.Argument) :
                    fragment?.Kind is QsFragmentKind.OperationDeclaration operation ? GetCharacteristics(operation.Item2.Argument) :
                    fragment?.Kind is QsFragmentKind.TypeDefinition type ? GetCharacteristics(type.Item2) :
                    Enumerable.Empty <Characteristics>();

                //var symbolInfo = file.TryGetQsSymbolInfo(d.Range.Start, false, out var fragment);
                //var characteristicsInFragment = (symbolInfo?.UsedTypes ?? Enumerable.Empty<QsType>())
                //    .SelectMany(t => t.ExtractCharacteristics()).Distinct();
                var fragmentStart = fragment?.GetRange()?.Start;
                return(characteristicsInFragment
                       .Where(c => c.Range.IsValue && DiagnosticTools.GetAbsoluteRange(fragmentStart, c.Range.Item).Overlaps(d.Range))
                       .Select(c => ReplaceWith(CharacteristicsAnnotation(c), d.Range)));
            });

            return(suggestionsForOpCharacteristics.ToArray()
                   .Concat(suggestionsForUnitType)
                   .Concat(suggestionsForNOT)
                   .Concat(suggestionsForAND)
                   .Concat(suggestionsForOR));
        }
        // external routines for context verification

        /// <summary>
        /// Given the line number of the lines that contain tokens that (possibly) have been modified,
        /// checks which callable declaration they can potentially belong to and returns the fully qualified name of those callables.
        /// </summary>
        internal static IEnumerable <(Range, QsQualifiedName)> CallablesWithContentModifications(this FileContentManager file, IEnumerable <int> changedLines)
        {
            var lastInFile = file.LastToken()?.GetFragment()?.Range?.End ?? file.End();
            var callables  = file.GetCallableDeclarations().Select(tuple => // these are sorted according to their line number
            {
                var ns = file.TryGetNamespaceAt(tuple.Item2.Start);
                QsCompilerError.Verify(ns != null, "namespace for callable declaration should not be null"); // invalid namespace names default to an unknown namespace name, but remain included in the compilation
                return(tuple.Item2.Start, new QsQualifiedName(ns, tuple.Item1));
            }).ToList();

            // NOTE: The range of modifications that has to trigger an update of the syntax tree for a callable
            // does need to go up to and include modifications to the line containing the next callable!
            // Otherwise inserting a callable declaration in the middle of an existing callable does not trigger the right behavior!
            (Range, QsQualifiedName) TypeCheckingRange((Position, QsQualifiedName) lastPreceding, IEnumerable <(Position, QsQualifiedName)> next)
            {
                var callableStart = lastPreceding.Item1;
                var callableEnd   = next.Any() ? next.First().Item1 : lastInFile;

                return(Range.Create(callableStart, callableEnd), lastPreceding.Item2);
            }

            foreach (var lineNr in changedLines)
            {
                bool Precedes((Position, QsQualifiedName) tuple) => tuple.Item1.Line < lineNr;

                var preceding = callables.TakeWhile(Precedes);
                var following = callables.SkipWhile(Precedes);

                if (preceding.Any())
                {
                    yield return(TypeCheckingRange(preceding.Last(), following));
                }
                if (following.Any() && following.First().Start.Line == lineNr)
                {
                    yield return(TypeCheckingRange(following.First(), following.Skip(1)));
                }
            }
        }
Beispiel #11
0
 /// <summary>
 /// Returns a sequence of suggestions on how errors for unknown types and callable in the given diagnostics can be fixed,
 /// given the file for which those diagnostics were generated and the corresponding compilation.
 /// The given line number is used to determine the containing namespace.
 /// Returns an empty enumerable if any of the given arguments is null.
 /// </summary>
 internal static IEnumerable <(string, WorkspaceEdit)> UnknownIdSuggestions(
     this FileContentManager file,
     CompilationUnit compilation,
     int lineNr,
     IReadOnlyCollection <Diagnostic> diagnostics) =>
Beispiel #12
0
        /// <summary>
        /// Returns the position and the kind of the closest specialization preceding the given position,
        /// and the name of the callable it belongs to as well as its position as Nullable.
        /// Returns null if the given file or position is null, or if no preceding callable can be found (e.g. because the callable name is invalid).
        /// If a callable name but no specializations (preceding or otherwise) within that callable can be found,
        /// assumes that the correct specialization is an auto-inserted default body,
        /// and returns QsBody as well as the start position of the callable declaration along with the callable name and its position.
        /// If a callable name as well as existing specializations can be found, but no specialization precedes the given position,
        /// returns null for the specialization kind as well as for its position.
        /// </summary>
        public static ((NonNullable <string>, Position), (QsSpecializationKind, Position))? TryGetClosestSpecialization(this FileContentManager file, Position pos)
        {
            QsSpecializationKind GetSpecializationKind(CodeFragment fragment)
            {
                var specDecl = fragment.Kind.DeclaredSpecialization();

                if (specDecl.IsNull)
                {
                    return(null);
                }
                var((kind, gen), typeArgs) = specDecl.Item;  // note: if we want to support type specializations we need to compute the signature of the spec to find the right one
                return(kind);
            }

            if (file == null || pos == null || !Utils.IsValidPosition(pos, file))
            {
                return(null);
            }
            file.SyncRoot.EnterReadLock();
            try {
                var declarations  = file.CallableDeclarationTokens();
                var precedingDecl = declarations.TakeWhile(tIndex => tIndex.GetFragment().GetRange().Start.IsSmallerThan(pos));
                if (!precedingDecl.Any())
                {
                    return(null);
                }

                var closestCallable  = precedingDecl.Last();
                var callablePosition = closestCallable.GetFragment().GetRange().Start;
                var callableName     = closestCallable.GetFragment().Kind.DeclaredCallableName(null);
                if (callableName == null)
                {
                    return(null);
                }

                var specializations = FileHeader.FilterCallableSpecializations(closestCallable.GetChildren(deep: false).Select(tIndex => tIndex.GetFragment()));
                var precedingSpec   = specializations.TakeWhile(fragment => fragment.GetRange().Start.IsSmallerThan(pos));
                var lastPreceding   = precedingSpec.Any() ? precedingSpec.Last() : null;

                if (specializations.Any() && lastPreceding == null) // the given position is within a callable declaration
                {
                    return((NonNullable <string> .New(callableName), callablePosition), (null, null));
                }
                return(lastPreceding == null
                    ? ((NonNullable <string> .New(callableName), callablePosition), (QsSpecializationKind.QsBody, callablePosition))
                    : ((NonNullable <string> .New(callableName), callablePosition), (GetSpecializationKind(lastPreceding), lastPreceding.GetRange().Start)));
            }
            finally { file.SyncRoot.ExitReadLock(); }
        }
        /// <summary>
        /// Returns the signature help information for a call expression if there is such an expression at the specified position.
        /// Returns null if some parameters are unspecified (null),
        /// or if the specified position is not a valid position within the currently processed file content,
        /// or if no call expression exists at the specified position at this time,
        /// or if no signature help information can be provided for the call expression at the specified position.
        /// </summary>
        public static SignatureHelp?SignatureHelp(
            this FileContentManager file,
            CompilationUnit compilation,
            Position?position,
            MarkupKind format = MarkupKind.PlainText)
        {
            // getting the relevant token (if any)

            var fragment = file?.TryGetFragmentAt(position, out var _, includeEnd: true);

            if (file is null || position is null || fragment?.Kind == null || compilation == null)
            {
                return(null);
            }
            var fragmentStart = fragment.Range.Start;

            // getting the overlapping call expressions (if any), and determine the header of the called callable

            bool OverlapsWithPosition(Range symRange) => (fragmentStart + symRange).ContainsEnd(position);

            var overlappingEx = fragment.Kind.CallExpressions().Where(ex => ex.Range.IsValue && OverlapsWithPosition(ex.Range.Item)).ToList();

            if (!overlappingEx.Any())
            {
                return(null);
            }
            overlappingEx.Sort((ex1, ex2) => // for nested call expressions, the last expressions (by range) is always the closest one
            {
                var(x, y)  = (ex1.Range.Item, ex2.Range.Item);
                int result = x.Start.CompareTo(y.Start);
                return(result == 0 ? x.End.CompareTo(y.End) : result);
            });

            var nsName = file.TryGetNamespaceAt(position);

            var(method, args) = overlappingEx.Last().Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .CallLikeExpression c ? (c.Item1, c.Item2) : (null, null);
            if (nsName == null || method == null || args == null)
            {
                return(null);
            }

            // getting the called identifier as well as what functors have been applied to it

            List <QsFunctor> FunctorApplications(ref QsExpression ex)
            {
                var(next, inner) =
                    ex.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .AdjointApplication adj ? (QsFunctor.Adjoint, adj.Item) :
                    ex.Expression is QsExpressionKind <QsExpression, QsSymbol, QsType> .ControlledApplication ctl ? (QsFunctor.Controlled, ctl.Item) :
                    (null, null);
                var fs = inner == null ? new List <QsFunctor>() : FunctorApplications(ref inner);

                if (next != null)
                {
                    fs.Add(next);
                }
                ex = inner ?? ex;
                return(fs);
            }

            var functors = FunctorApplications(ref method);
            var id       = method.Expression as QsExpressionKind <QsExpression, QsSymbol, QsType> .Identifier;

            if (id == null)
            {
                return(null);
            }

            // extracting and adapting the relevant information for the called callable

            ResolutionResult <CallableDeclarationHeader> .Found?methodDecl = null;
            if (id.Item1.Symbol is QsSymbolKind <QsSymbol> .Symbol sym)
            {
                methodDecl =
                    compilation.GlobalSymbols.TryResolveAndGetCallable(
                        sym.Item,
                        nsName,
                        file.FileName)
                    as ResolutionResult <CallableDeclarationHeader> .Found;
            }
            else if (id.Item1.Symbol is QsSymbolKind <QsSymbol> .QualifiedSymbol qualSym)
            {
                methodDecl =
                    compilation.GlobalSymbols.TryGetCallable(
                        new QsQualifiedName(qualSym.Item1, qualSym.Item2),
                        nsName,
                        file.FileName)
                    as ResolutionResult <CallableDeclarationHeader> .Found;
            }

            if (methodDecl == null)
            {
                return(null);
            }

            var(documentation, argTuple) = (methodDecl.Item.Documentation, methodDecl.Item.ArgumentTuple);
            var nrCtlApplications = functors.Where(f => f.Equals(QsFunctor.Controlled)).Count();

            while (nrCtlApplications-- > 0)
            {
                var ctlQsName = QsLocalSymbol.NewValidName(nrCtlApplications == 0 ? "cs" : $"cs{nrCtlApplications}");
                argTuple = SyntaxGenerator.WithControlQubits(argTuple, QsNullable <Position> .Null, ctlQsName, QsNullable <Range> .Null);
            }

            // now that we now what callable is called we need to check which argument should come next

            bool BeforePosition(Range symRange) => fragmentStart + symRange.End < position;

            IEnumerable <(Range?, string?)> ExtractParameterRanges(
                QsExpression?ex, QsTuple <LocalVariableDeclaration <QsLocalSymbol> > decl)
            {
                var @null = ((Range?)null, (string?)null);

                IEnumerable <(Range?, string?)> SingleItem(string paramName)
                {
                    var arg = ex?.Range == null ? ((Range?)null, paramName)
                        : ex.Range.IsValue ? (ex.Range.Item, paramName)
                        : @null; // no signature help if there are invalid expressions

                    return(new[] { arg });
                }

                if (decl is QsTuple <LocalVariableDeclaration <QsLocalSymbol> > .QsTupleItem dItem)
                {
                    return(SingleItem(dItem.Item.VariableName is QsLocalSymbol.ValidName n ? n.Item : "__argName__"));
                }

                var declItems = decl as QsTuple <LocalVariableDeclaration <QsLocalSymbol> > .QsTuple;
                var exItems   = ex?.Expression as QsExpressionKind <QsExpression, QsSymbol, QsType> .ValueTuple;

                if (declItems == null)
                {
                    return(new[] { @null });
                }
                if (exItems == null && declItems.Item.Length > 1)
                {
                    return(SingleItem(decl.PrintArgumentTuple()));
                }

                var argItems = exItems != null
                    ? exItems.Item.ToImmutableArray <QsExpression?>()
                    : ex == null
                    ? ImmutableArray <QsExpression?> .Empty
                    : ImmutableArray.Create <QsExpression?>(ex);

                return(argItems.AddRange(Enumerable.Repeat <QsExpression?>(null, declItems.Item.Length - argItems.Length))
                       .Zip(declItems.Item, (e, d) => (e, d))
                       .SelectMany(arg => ExtractParameterRanges(arg.Item1, arg.Item2)));
            }

            var callArgs = ExtractParameterRanges(args, argTuple).ToArray();

            if (id == null || callArgs == null || callArgs.Any(item => item.Item2 == null))
            {
                return(null); // no signature help if there are invalid expressions
            }

            // finally we can build the signature help information

            MarkupContent AsMarkupContent(string str) => new MarkupContent
            {
                Kind = format, Value = str
            };
            ParameterInformation AsParameterInfo(string?paramName) => new ParameterInformation
            {
                Label         = paramName,
                Documentation = AsMarkupContent(documentation.ParameterDescription(paramName))
            };

            var signatureLabel = $"{methodDecl.Item.QualifiedName.Name} {argTuple.PrintArgumentTuple()}";

            foreach (var f in functors)
            {
                if (f.IsAdjoint)
                {
                    signatureLabel = $"{Keywords.qsAdjointFunctor.id} {signatureLabel}";
                }
                if (f.IsControlled)
                {
                    signatureLabel = $"{Keywords.qsControlledFunctor.id} {signatureLabel}";
                }
            }

            var doc  = documentation.PrintSummary(format == MarkupKind.Markdown).Trim();
            var info = new SignatureInformation
            {
                Documentation = AsMarkupContent(doc),
                Label         = signatureLabel, // Note: the label needs to be expressed in a way that the active parameter is detectable
                Parameters    = callArgs.Select(d => d.Item2).Select(AsParameterInfo).ToArray()
            };
            var precedingArgs = callArgs
                                .TakeWhile(item => item.Item1 == null || BeforePosition(item.Item1)) // skip args that have already been typed or - in the case of inner items - are missing
                                .Reverse().SkipWhile(item => item.Item1 == null);                    // don't count missing, i.e. not yet typed items, of the relevant inner argument tuple

            return(new SignatureHelp
            {
                Signatures = new[] { info }, // since we don't support overloading there is just one signature here
                ActiveSignature = 0,
                ActiveParameter = precedingArgs.Count()
            });
        }
 /// <nodoc />
 public SourceChangeAffectedContents(PathTable pathTable, FileContentManager fileContentManager)
 {
     m_pathTable          = pathTable;
     m_fileContentManager = fileContentManager;
 }
Beispiel #15
0
 public FileContentManagerSemanticPathExpander(FileContentManager fileContentManager, SemanticPathExpander innerExpander)
 {
     m_innerExpander      = innerExpander;
     m_fileContentManager = fileContentManager;
 }
Beispiel #16
0
 /// <summary>
 /// Returns true if the given range is valid,
 /// i.e. if both start and end are valid positions within the given file, and start is smaller than or equal to end.
 /// Throws an ArgumentNullException if an argument is null.
 /// </summary>
 internal static bool IsValidRange(LSP.Range range, FileContentManager file) =>
 IsValidPosition(range?.Start, file) && IsValidPosition(range.End, file) && range.Start.IsSmallerThanOrEqualTo(range.End);
Beispiel #17
0
        /// <summary>
        /// Loads configured symlink definitions (if not already loaded)
        /// Stores to cache for use by workers in distributed build
        /// Eagerly creates symlinks if lazy symlink creation is disabled
        /// </summary>
        public static async Task <Possible <SymlinkDefinitions> > TryPrepareSymlinkDefinitionsAsync(
            LoggingContext loggingContext,
            GraphReuseResult reuseResult,
            IConfiguration configuration,
            MasterService masterService,
            CacheInitializationTask cacheInitializerTask,
            PipExecutionContext context,
            ITempDirectoryCleaner tempDirectoryCleaner = null)
        {
            var  pathTable           = context.PathTable;
            bool isDistributedMaster = configuration.Distribution.BuildRole == DistributedBuildRoles.Master;
            Possible <SymlinkDefinitions> maybeSymlinkDefinitions = new Possible <SymlinkDefinitions>((SymlinkDefinitions)null);

            if (reuseResult?.IsFullReuse == true)
            {
                maybeSymlinkDefinitions = reuseResult.EngineSchedule.Scheduler.SymlinkDefinitions;
            }
            else if (configuration.Layout.SymlinkDefinitionFile.IsValid)
            {
                var symlinkFilePath = configuration.Layout.SymlinkDefinitionFile.ToString(pathTable);
                Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Loading symlink file from location '{symlinkFilePath}'."));
                maybeSymlinkDefinitions = await SymlinkDefinitions.TryLoadAsync(
                    loggingContext,
                    pathTable,
                    symlinkFilePath,
                    symlinksDebugPath : configuration.Logging.LogsDirectory.Combine(pathTable, "DebugSymlinksDefinitions.log").ToString(pathTable),
                    tempDirectoryCleaner : tempDirectoryCleaner);
            }

            if (!maybeSymlinkDefinitions.Succeeded || maybeSymlinkDefinitions.Result == null)
            {
                return(maybeSymlinkDefinitions);
            }

            // Need to store symlinks to cache for workers
            if (configuration.Distribution.BuildRole == DistributedBuildRoles.Master)
            {
                var possibleCacheInitializer = await cacheInitializerTask;
                if (!possibleCacheInitializer.Succeeded)
                {
                    return(possibleCacheInitializer.Failure);
                }

                Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Storing symlink file for use by workers."));

                var symlinkFile = configuration.Layout.SymlinkDefinitionFile.Expand(pathTable);

                var possibleStore = await TryStoreToCacheAsync(
                    loggingContext,
                    cache : possibleCacheInitializer.Result.CreateCacheForContext(context).ArtifactContentCache,
                    symlinkFile : symlinkFile);

                if (!possibleStore.Succeeded)
                {
                    return(possibleStore.Failure);
                }

                masterService.SymlinkFileContentHash = possibleStore.Result;
                Logger.Log.SymlinkFileTraceMessage(loggingContext, I($"Stored symlink file for use by workers."));
            }

            if (!configuration.Schedule.UnsafeLazySymlinkCreation || configuration.Engine.PopulateSymlinkDirectories.Count != 0)
            {
                // Symlink definition file is defined, and BuildXL intends to create it eagerly.
                // At this point master and worker should have had its symlink definition file, if specified.
                if (!FileContentManager.CreateSymlinkEagerly(loggingContext, configuration, pathTable, maybeSymlinkDefinitions.Result, context.CancellationToken))
                {
                    return(new Failure <string>("Failed eagerly creating symlinks"));
                }
            }

            return(maybeSymlinkDefinitions);
        }
Beispiel #18
0
 /// <nodoc />
 public SourceChangeAffectedInputs(FileContentManager fileContentManager)
 {
     m_fileContentManager = fileContentManager;
 }
        /// <summary>
        /// Returns completion items that match the given kind.
        /// </summary>
        /// <exception cref="ArgumentNullException">Thrown when any argument is null.</exception>
        /// <exception cref="ArgumentException">Thrown when the position is invalid.</exception>
        private static IEnumerable <CompletionItem> GetCompletionsForKind(
            FileContentManager file,
            CompilationUnit compilation,
            Position position,
            CompletionKind kind,
            string namespacePrefix = "")
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (compilation == null)
            {
                throw new ArgumentNullException(nameof(compilation));
            }
            if (!Utils.IsValidPosition(position))
            {
                throw new ArgumentException(nameof(position));
            }
            if (kind == null)
            {
                throw new ArgumentNullException(nameof(kind));
            }
            if (namespacePrefix == null)
            {
                throw new ArgumentNullException(nameof(namespacePrefix));
            }

            switch (kind)
            {
            case CompletionKind.Member member:
                return(GetCompletionsForKind(file, compilation, position, member.Item2,
                                             ResolveNamespaceAlias(file, compilation, position, member.Item1)));

            case CompletionKind.Keyword keyword:
                return(new[] { new CompletionItem {
                                   Label = keyword.Item, Kind = CompletionItemKind.Keyword
                               } });
            }
            var currentNamespace = file.TryGetNamespaceAt(position);
            var openNamespaces   =
                namespacePrefix == "" ? GetOpenNamespaces(file, compilation, position) : new[] { namespacePrefix };

            switch (kind.Tag)
            {
            case CompletionKind.Tags.UserDefinedType:
                return
                    (GetTypeCompletions(file, compilation, currentNamespace, openNamespaces)
                     .Concat(GetGlobalNamespaceCompletions(compilation, namespacePrefix))
                     .Concat(GetNamespaceAliasCompletions(file, compilation, position, namespacePrefix)));

            case CompletionKind.Tags.NamedItem:
                return(GetNamedItemCompletions(compilation));

            case CompletionKind.Tags.Namespace:
                return
                    (GetGlobalNamespaceCompletions(compilation, namespacePrefix)
                     .Concat(GetNamespaceAliasCompletions(file, compilation, position, namespacePrefix)));

            case CompletionKind.Tags.Variable:
                return(GetLocalCompletions(file, compilation, position));

            case CompletionKind.Tags.MutableVariable:
                return(GetLocalCompletions(file, compilation, position, mutableOnly: true));

            case CompletionKind.Tags.Callable:
                return
                    (GetCallableCompletions(file, compilation, currentNamespace, openNamespaces)
                     .Concat(GetGlobalNamespaceCompletions(compilation, namespacePrefix))
                     .Concat(GetNamespaceAliasCompletions(file, compilation, position, namespacePrefix)));
            }
            return(Enumerable.Empty <CompletionItem>());
        }
Beispiel #20
0
        /// <summary>
        /// Given an SortedSet of changed lines, verifies the context for each token on one of these lines,
        /// and adds the computed diagnostics to the ones returned as out parameter.
        /// Marks the token indices which are to be excluded from compilation due to context errors.
        /// Returns the line numbers for which the context diagnostics have been recomputed.
        /// Throws an ArgumentNullException if any of the arguments is null.
        /// </summary>
        private static HashSet <int> VerifyContext(this FileContentManager file, SortedSet <int> changedLines, out List <Diagnostic> diagnostics)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }
            if (changedLines == null)
            {
                throw new ArgumentNullException(nameof(changedLines));
            }

            IEnumerable <CodeFragment.TokenIndex> TokenIndices(int lineNr) =>
            Enumerable.Range(0, file.GetTokenizedLine(lineNr).Count()).Select(index => new CodeFragment.TokenIndex(file, lineNr, index));

            var tokensToVerify = changedLines.SelectMany(TokenIndices);
            var verifiedLines  = new HashSet <int>();

            List <Diagnostic> Verify(CodeFragment.TokenIndex tokenIndex)
            {
                var messages = new List <Diagnostic>();
                var fragment = tokenIndex.GetFragment();
                var context  = tokenIndex.GetContext();

                var fragmentStart = fragment.GetRange().Start;

                var(include, verifications) = Context.VerifySyntaxTokenContext(context);
                foreach (var msg in verifications)
                {
                    messages.Add(Diagnostics.Generate(file.FileName.Value, msg, fragmentStart));
                }

                if (include)
                {
                    tokenIndex.MarkAsIncluded();
                }
                else
                {
                    tokenIndex.MarkAsExcluded();
                }
                verifiedLines.Add(tokenIndex.Line);

                // if a token is newly included in or excluded from the compilation,
                // then this may impact context information for all following tokens
                var changedStatus = include ^ fragment.IncludeInCompilation;
                var next          = tokenIndex.NextOnScope();

                if (changedStatus && next != null && !changedLines.Contains(next.Line))
                {
                    // NOTE: since we invalidate context diagnostics on a per-line basis, we need to verify the whole line!
                    var tokens = TokenIndices(next.Line);
                    foreach (var token in tokens)
                    {
                        messages.AddRange(Verify(token));
                    }
                }
                return(messages);
            }

            diagnostics = tokensToVerify.SelectMany(tIndex => Verify(tIndex)).ToList();
            return(verifiedLines);
        }
 /// <summary>
 /// Returns true if the fragment has a delimiting character and the given position occurs after it.
 /// </summary>
 private static bool IsPositionAfterDelimiter(FileContentManager file,
                                              CodeFragment fragment,
                                              Position position) =>
 fragment.FollowedBy != CodeFragment.MissingDelimiter &&
 GetDelimiterPosition(file, fragment).IsSmallerThan(position);
Beispiel #22
0
 /// <summary>
 /// Computes excess bracket errors for the given range of lines in file based on the corresponding CodeLine.
 /// Throws an ArgumentOutOfRangeException if start is not within file.
 /// </summary>
 private static IEnumerable <Diagnostic> ComputeScopeDiagnostics(this FileContentManager file, int start) =>
 ComputeScopeDiagnostics(file, start, file == null ? 0 : file.NrLines() - start);
        // routine(s) called by the FileContentManager upon updating a file

        /// <summary>
        /// Attempts to compute an incremental update for the change specified by start, count and newText, and updates file accordingly.
        /// The given argument newText replaces the entire lines from start to (but not including) start + count.
        /// If the given change is null, then (only) the currently queued unprocessed changes are processed.
        /// Throws an ArgumentNullException if file is null.
        /// Any other exceptions should be throws (and caught, and possibly re-thrown) during the updating.
        /// </summary>
        internal static void UpdateScopeTacking(this FileContentManager file, TextDocumentContentChangeEvent change)
        {
            if (file == null)
            {
                throw new ArgumentNullException(nameof(file));
            }

            /// <summary>
            /// Replaces the lines in the range [start, end] with those for the given text.
            /// </summary>
            void ComputeUpdate(int start, int end, string text)
            {
                QsCompilerError.Verify(start >= 0 && end >= start && end < file.NrLines(), "invalid range for update");

                // since both LF and CR in VS cause a line break on their own,
                // we need to check if the change causes subequent CR LF to merge into a single line break
                if (text.StartsWith(Utils.LF) && start > 0 && file.GetLine(start - 1).Text.EndsWith(Utils.CR))
                {
                    text = file.GetLine(--start).Text + text;
                }

                // we need to check if the change causes the next line to merge with the (last) changed line
                if (end + 1 < file.NrLines() && !Utils.EndOfLine.Match(text).Success)
                {
                    text = text + file.GetLine(++end).Text;
                }

                var newLines = Utils.SplitLines(text);
                var count    = end - start + 1;

                // note that the last line in the file won't end with a line break,
                // and is hence only captured by SplitLines if it is not empty
                // -> we therefore manually add the last line in the file if it is empty
                if (newLines.Length == 0 || // the case if the file will be empty after the update
                    (start + count == file.NrLines() && Utils.EndOfLine.Match(newLines.Last()).Success))
                {
                    newLines = newLines.Concat(new string[] { string.Empty }).ToArray();
                }

                QsCompilerError.Verify(newLines.Any(), "should have at least one line to replace");
                file.Update(start, count, newLines);
            }

            file.SyncRoot.EnterUpgradeableReadLock();
            try
            {
                // process the currently queued changes if necessary
                if (file.DequeueUnprocessedChanges(out int start, out string text))
                {
                    ComputeUpdate(start, start, text);
                }

                // process the given change if necessary
                if (change != null)
                {
                    ComputeUpdate(change.Range.Start.Line, change.Range.End.Line, Utils.GetTextChangedLines(file, change));
                }
            }
            finally
            {
                file.SyncRoot.ExitUpgradeableReadLock();
            }
        }
Beispiel #24
0
 /// <summary>
 /// Computes excess bracket errors for the given range of lines in file based on the corresponding CodeLine.
 /// Throws an ArgumentNullException if file is null.
 /// Throws an ArgumentOutOfRangeException if start is not within file.
 /// </summary>
 private static IEnumerable <Diagnostic> ComputeScopeDiagnostics(this FileContentManager file, int start)
 {
     return(ComputeScopeDiagnostics(file, start, file == null ? 0 : file.NrLines() - start));
 }                                                                                           // will raise an exception if file is null