/// <summary> /// Givent a position, verifies that the position is within the given file, and /// returns the effective indentation (i.e. the indentation when ignoring excess brackets throughout the file) /// at that position (i.e. not including the char at the given position). /// </summary> internal static int IndentationAt(this FileContentManager file, Position pos) { if (!file.ContainsPosition(pos)) { throw new ArgumentException("given position is not within file"); } var line = file.GetLine(pos.Line); var index = pos.Column; // check if the given position is within a string or a comment, or denotes an excess bracket, // and find the next closest position that isn't if (index >= line.WithoutEnding.Length) { return(line.FinalIndentation()); // not necessary, but saves doing the rest of the computation } index = line.FindInCode(trimmed => trimmed.Length - 1, 0, pos.Column); // if the given position is within a string, then this is the most convenient way to get the closest position that isn't.. if (index < 0) { return(line.Indentation); // perfectly valid scenario (if there is no relevant code before the given position) } // check how much the indentation changes in all valid (i.e. non-comment, non-string, non-excess-brackets) code up to that point // NOTE: per specification, this routine returns the indentation not incuding the char at the given position, // but if this char was within non-code text, then we need to include this char ... see (*) var indexInCode = IndexInRelevantCode(index, line); QsCompilerError.Verify(indexInCode >= 0, "index in code should be positive"); var code = RelevantCode(line).Substring(0, index < pos.Column ? indexInCode + 1 : indexInCode); // (*) - yep, that's a bit awkward, but the cleanest I can come up with right now var indentation = line.Indentation + NrIndents(code) - NrUnindents(code); QsCompilerError.Verify(indentation >= 0, "computed indentation at any position in the file should be positive"); return(indentation); }
/// <summary> /// Returns the Position after the last character in the file (including comments). /// Throws an ArgumentException is file does not have any content. /// </summary> public static Position End(this FileContentManager file) { if (file.NrLines() == 0) { throw new ArgumentException("file is empty", nameof(file)); } return(Position.Create(file.NrLines() - 1, file.GetLine(file.NrLines() - 1).Text.Length)); }
/// <summary> /// Returns all namespaces in which a callable with the name of the symbol at the given position in the given /// file belongs to. /// /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that /// location, or if the position is not part of a namespace. /// /// Returns the name of the identifier as an out parameter if an unqualified symbol exists at that location. /// </summary> private static IEnumerable <string> IdNamespaceSuggestions( this FileContentManager file, Position pos, CompilationUnit compilation, out string?idName) { var variables = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedVariables; idName = variables != null && variables.Any() ? variables.Single().Symbol.AsDeclarationName(null) : null; return(idName != null && compilation != null ? compilation.GlobalSymbols.NamespacesContainingCallable(idName) : ImmutableArray <string> .Empty); }
/// <summary> /// Computes excess bracket errors for the given range of lines in file based on the corresponding CodeLine. /// Throws an ArgumentOutOfRangeException if the range [start, start + count) is not within file. /// </summary> private static IEnumerable <Diagnostic> ComputeScopeDiagnostics(this FileContentManager file, int start, int count) { foreach (var line in file.GetLines(start, count)) { foreach (var pos in line.ExcessBracketPositions) { yield return(Errors.ExcessBracketError(file.FileName, Position.Create(start, pos))); } ++start; } }
/// <summary> /// Returns the position right after where the fragment containing the given position ends. /// If the closest previous ending was on the last character in a line, then the returned position is on the same line after the last character. /// Updates the given position to point to the first character in the fragment that contains code. /// Throws an ArgumentException if the given position is not smaller than the position after the last piece of code in the file (given by LastInFile). /// Throws an ArgumentException if the given position is not within file. /// </summary> internal static Position FragmentEnd(this FileContentManager file, ref Position current) { if (!file.ContainsPosition(current)) { throw new ArgumentException("given position is not within file"); } var lastInFile = LastInFile(file); if (lastInFile <= current) { throw new ArgumentException("no fragment exists at the given position"); }
/// <summary> /// Returns names that match an alternative casing of the identifier at the given position in the file, or the /// empty enumerable if there is no valid identifier at the given position. /// </summary> private static IEnumerable <string> IdCaseSuggestions( this FileContentManager file, Position pos, CompilationUnit compilation) { IEnumerable <string> AlternateNames(string name) => from callable in compilation.GlobalSymbols.AccessibleCallables() let otherName = callable.QualifiedName.Name where otherName != name && otherName.Equals(name, StringComparison.OrdinalIgnoreCase) select otherName; var symbolKind = file.TryGetQsSymbolInfo(pos, true, out _)?.UsedVariables?.SingleOrDefault()?.Symbol; return(symbolKind.AsDeclarationName(null)?.Apply(AlternateNames) ?? Enumerable.Empty <string>()); }
/// <summary> /// Returns all namespaces in which a type with the name of the symbol at the given position in the given file /// belongs to. /// /// Returns an empty collection if any of the arguments is null, if no unqualified symbol exists at that /// location, or if the position is not part of a namespace. /// /// Returns the name of the type as an out parameter if an unqualified symbol exists at that location. /// </summary> private static IEnumerable <string> TypeNamespaceSuggestions( this FileContentManager file, Position pos, CompilationUnit compilation, out string?typeName) { var types = file?.TryGetQsSymbolInfo(pos, true, out CodeFragment _)?.UsedTypes; typeName = types != null && types.Any() && types.Single().Type is QsTypeKind <QsType, QsSymbol, QsSymbol, Characteristics> .UserDefinedType udt ? udt.Item.Symbol.AsDeclarationName(null) : null; return(typeName != null && compilation != null ? compilation.GlobalSymbols.NamespacesContainingType(typeName) : ImmutableArray <string> .Empty); }
/// <summary> /// Returns the Position right after where the last relevant (i.e. non-comment) code in the file ends, /// or the position (0,0) if no such line exists. /// Throws an ArgumentException if file does not contain any lines. /// </summary> private static Position LastInFile(FileContentManager file) { if (file.NrLines() == 0) { throw new ArgumentException("file is null or missing content", nameof(file)); } var endIndex = file.NrLines(); while (endIndex-- > 0 && file.GetLine(endIndex).WithoutEnding.Trim().Length == 0) { } return(endIndex < 0 ? Position.Zero : Position.Create(endIndex, file.GetLine(endIndex).WithoutEnding.Length)); }
/// <summary> /// Computes excess bracket errors for the given range of lines in file based on the corresponding CodeLine. /// </summary> /// <exception cref="ArgumentOutOfRangeException">The range [<paramref name="start"/>, <paramref name="start"/> + <paramref name="count"/>) is not within <paramref name="file"/>.</exception> private static IEnumerable <Diagnostic> ComputeScopeDiagnostics(this FileContentManager file, int start, int count) { foreach (var line in file.GetLines(start, count)) { foreach (var pos in line.ExcessBracketPositions) { yield return(Errors.ExcessBracketError(file.FileName, Position.Create(start, pos))); } foreach (var pos in line.ErrorDelimiterPositions) { yield return(Errors.InvalidCharacterInInterpolatedArgument(file.FileName, Position.Create(start, pos), file.GetLine(start).Text[pos])); } ++start; } }
// routines used to compute scope diagnostics updates /// <summary> /// Given the total number of excess closings in the file /// checks for both an unclosed scope and a missing string ending on lastLine, and adds the corresponding error(s) to updatedScopeErrors. /// Throws an ArgumentException if the number of lines in the file is zero. /// </summary> private static IEnumerable <Diagnostic> CheckForMissingClosings(this FileContentManager file) { if (file.NrLines() == 0) { throw new ArgumentException("the number of lines in a file can never be zero"); } var lastLine = file.GetLine(file.NrLines() - 1); if (lastLine.FinalIndentation() > 0) { yield return(Errors.MissingClosingBracketError(file.FileName, Position.Create(file.NrLines() - 1, lastLine.Text.Length))); } if (ContinueString(lastLine)) { yield return(Errors.MissingStringDelimiterError(file.FileName, Position.Create(file.NrLines() - 1, lastLine.Text.Length))); } }
static int MoveNextLine(ref Position position) { position = Position.Create(position.Line + 1, position.Column); return(position.Line); }
// utils for getting the necessary information for editor commands internal static Location AsLocation(string source, Position offset, Range relRange) =>