public override bool TryMapToProjectedDocumentRange(RazorCodeDocument codeDocument, Range originalRange, out Range projectedRange) { if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (originalRange is null) { throw new ArgumentNullException(nameof(originalRange)); } projectedRange = default; if ((originalRange.End.Line < originalRange.Start.Line) || (originalRange.End.Line == originalRange.Start.Line && originalRange.End.Character < originalRange.Start.Character)) { Debug.Fail($"DefaultRazorDocumentMappingService:TryMapToProjectedDocumentRange original range end < start '{originalRange}'"); return(false); } var sourceText = codeDocument.GetSourceText(); var range = originalRange; var startIndex = range.Start.GetAbsoluteIndex(sourceText); if (!TryMapToProjectedDocumentPosition(codeDocument, startIndex, out var projectedStart, out var _)) { return(false); } var endIndex = range.End.GetAbsoluteIndex(sourceText); if (!TryMapToProjectedDocumentPosition(codeDocument, endIndex, out var projectedEnd, out var _)) { return(false); } // Ensures a valid range is returned. // As we're doing two seperate TryMapToProjectedDocumentPosition calls, // it's possible the projectedStart and projectedEnd positions are in completely // different places in the document, including the possibility that the // projectedEnd position occurs before the projectedStart position. // We explicitly disallow such ranges where the end < start. if ((projectedEnd.Line < projectedStart.Line) || (projectedEnd.Line == projectedStart.Line && projectedEnd.Character < projectedStart.Character)) { return(false); } projectedRange = new Range( projectedStart, projectedEnd); return(true); }
/** * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: * - at index `5*i` - `deltaLine`: token line number, relative to the previous token * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes` * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` **/ private static IEnumerable <uint> GetData( SyntaxResult currentNode, SyntaxResult?previousNode, RazorCodeDocument razorCodeDocument) { var previousRange = previousNode?.Range; var currentRange = currentNode.Range; // deltaLine var previousLineIndex = previousNode == null ? 0 : previousRange.Start.Line; yield return((uint)(currentRange.Start.Line - previousLineIndex)); // deltaStart if (previousRange != null && previousRange?.Start.Line == currentRange.Start.Line) { yield return((uint)(currentRange.Start.Character - previousRange.Start.Character)); } else { yield return((uint)currentRange.Start.Character); } // length var endIndex = currentNode.Range.End.GetAbsoluteIndex(razorCodeDocument.GetSourceText()); var startIndex = currentNode.Range.Start.GetAbsoluteIndex(razorCodeDocument.GetSourceText()); var length = endIndex - startIndex; Debug.Assert(length > 0); yield return((uint)length); // tokenType yield return(GetTokenTypeData(currentNode.Kind)); // tokenModifiers // We don't currently have any need for tokenModifiers yield return(0); }
public override RazorLanguageKind GetLanguageKind(RazorCodeDocument codeDocument, int originalIndex) { if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } var syntaxTree = codeDocument.GetSyntaxTree(); var classifiedSpans = syntaxTree.GetClassifiedSpans(); var tagHelperSpans = syntaxTree.GetTagHelperSpans(); var documentLength = codeDocument.GetSourceText().Length; var languageKind = GetLanguageKindCore(classifiedSpans, tagHelperSpans, originalIndex, documentLength); return(languageKind); }
public static IReadOnlyList <SemanticRange> VisitAllNodes(RazorCodeDocument razorCodeDocument, Range?range = null) { TextSpan?rangeAsTextSpan = null; if (range is not null) { var sourceText = razorCodeDocument.GetSourceText(); rangeAsTextSpan = range.AsRazorTextSpan(sourceText); } var visitor = new TagHelperSemanticRangeVisitor(razorCodeDocument, rangeAsTextSpan); visitor.Visit(razorCodeDocument.GetSyntaxTree().Root); return(visitor._semanticRanges); }
// Internal for testing only internal static bool TryGetMinimalCSharpRange(RazorCodeDocument codeDocument, Range razorRange, [NotNullWhen(true)] out Range?csharpRange) { SourceSpan?minGeneratedSpan = null; SourceSpan?maxGeneratedSpan = null; var sourceText = codeDocument.GetSourceText(); var textSpan = razorRange.AsTextSpan(sourceText); var csharpDoc = codeDocument.GetCSharpDocument(); // We want to find the min and max C# source mapping that corresponds with our Razor range. foreach (var mapping in csharpDoc.SourceMappings) { var mappedTextSpan = mapping.OriginalSpan.AsTextSpan(); if (textSpan.OverlapsWith(mappedTextSpan)) { if (minGeneratedSpan is null || mapping.GeneratedSpan.AbsoluteIndex < minGeneratedSpan.Value.AbsoluteIndex) { minGeneratedSpan = mapping.GeneratedSpan; } var mappingEndIndex = mapping.GeneratedSpan.AbsoluteIndex + mapping.GeneratedSpan.Length; if (maxGeneratedSpan is null || mappingEndIndex > maxGeneratedSpan.Value.AbsoluteIndex + maxGeneratedSpan.Value.Length) { maxGeneratedSpan = mapping.GeneratedSpan; } } } // Create a new projected range based on our calculated min/max source spans. if (minGeneratedSpan is not null && maxGeneratedSpan is not null) { var csharpSourceText = codeDocument.GetCSharpSourceText(); var startRange = minGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); var endRange = maxGeneratedSpan.Value.AsTextSpan().AsRange(csharpSourceText); csharpRange = new Range { Start = startRange.Start, End = endRange.End }; Debug.Assert(csharpRange.Start <= csharpRange.End, "Range.Start should not be larger than Range.End"); return(true); } csharpRange = null; return(false); }
/** * In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following array indices: * - at index `5*i` - `deltaLine`: token line number, relative to the previous token * - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line) * - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline. * - at index `5*i+3` - `tokenType`: will be looked up in `SemanticTokensLegend.tokenTypes` * - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers` **/ private static IEnumerable <uint> GetData( SemanticRange currentRange, SemanticRange previousRange, RazorCodeDocument razorCodeDocument) { // var previousRange = previousRange?.Range; // var currentRange = currentRange.Range; // deltaLine var previousLineIndex = previousRange?.Range == null ? 0 : previousRange.Range.Start.Line; yield return((uint)(currentRange.Range.Start.Line - previousLineIndex)); // deltaStart if (previousRange != null && previousRange?.Range.Start.Line == currentRange.Range.Start.Line) { yield return((uint)(currentRange.Range.Start.Character - previousRange.Range.Start.Character)); } else { yield return((uint)currentRange.Range.Start.Character); } // length var textSpan = currentRange.Range.AsTextSpan(razorCodeDocument.GetSourceText()); var length = textSpan.Length; Debug.Assert(length > 0); yield return((uint)length); // tokenType yield return(currentRange.Kind); // tokenModifiers // We don't currently have any need for tokenModifiers yield return(0); }
public override bool TryMapToProjectedDocumentRange(RazorCodeDocument codeDocument, Range originalRange, out Range projectedRange) { if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (originalRange is null) { throw new ArgumentNullException(nameof(originalRange)); } projectedRange = default; var sourceText = codeDocument.GetSourceText(); var range = originalRange; var startIndex = range.Start.GetAbsoluteIndex(sourceText); if (!TryMapToProjectedDocumentPosition(codeDocument, startIndex, out var projectedStart, out var _)) { return(false); } var endIndex = range.End.GetAbsoluteIndex(sourceText); if (!TryMapToProjectedDocumentPosition(codeDocument, endIndex, out var projectedEnd, out var _)) { return(false); } projectedRange = new Range( projectedStart, projectedEnd); return(true); }
public static FormattingContext Create( DocumentUri uri, DocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, AdhocWorkspaceFactory workspaceFactory, bool isFormatOnType = false) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (originalSnapshot is null) { throw new ArgumentNullException(nameof(originalSnapshot)); } if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } if (workspaceFactory is null) { throw new ArgumentNullException(nameof(workspaceFactory)); } var syntaxTree = codeDocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var result = new FormattingContext(workspaceFactory) { Uri = uri, OriginalSnapshot = originalSnapshot, CodeDocument = codeDocument, Options = options, IsFormatOnType = isFormatOnType, FormattingSpans = formattingSpans }; var sourceText = codeDocument.GetSourceText(); var indentations = new Dictionary <int, IndentationContext>(); var previousIndentationLevel = 0; for (var i = 0; i < sourceText.Lines.Count; i++) { // Get first non-whitespace character position var nonWsPos = sourceText.Lines[i].GetFirstNonWhitespacePosition(); var existingIndentation = (nonWsPos ?? sourceText.Lines[i].End) - sourceText.Lines[i].Start; // The existingIndentation above is measured in characters, and is used to create text edits // The below is measured in columns, so takes into account tab size. This is useful for creating // new indentation strings var existingIndentationSize = sourceText.Lines[i].GetIndentationSize(options.TabSize); var emptyOrWhitespaceLine = false; if (nonWsPos == null) { emptyOrWhitespaceLine = true; nonWsPos = sourceText.Lines[i].Start; } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (result.TryGetFormattingSpan(nonWsPos.Value, out var span)) { indentations[i] = new IndentationContext(firstSpan: span) { Line = i, RazorIndentationLevel = span.RazorIndentationLevel, HtmlIndentationLevel = span.HtmlIndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = existingIndentation, ExistingIndentationSize = existingIndentationSize, EmptyOrWhitespaceLine = emptyOrWhitespaceLine, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line. // Let's create a 0 length span to represent this and default it to HTML. var placeholderSpan = new FormattingSpan( new Language.Syntax.TextSpan(nonWsPos.Value, 0), new Language.Syntax.TextSpan(nonWsPos.Value, 0), FormattingSpanKind.Markup, FormattingBlockKind.Markup, razorIndentationLevel: 0, htmlIndentationLevel: 0, isInClassBody: false, componentLambdaNestingLevel: 0); indentations[i] = new IndentationContext(firstSpan: placeholderSpan) { Line = i, RazorIndentationLevel = 0, HtmlIndentationLevel = 0, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = existingIndentation, ExistingIndentationSize = existingIndentation, EmptyOrWhitespaceLine = emptyOrWhitespaceLine, }; } } result.Indentations = indentations; return(result); }
public static FormattingContext Create( DocumentUri uri, DocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, Range range = null, bool isFormatOnType = false) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (originalSnapshot is null) { throw new ArgumentNullException(nameof(originalSnapshot)); } if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var text = codeDocument.GetSourceText(); range ??= TextSpan.FromBounds(0, text.Length).AsRange(text); var result = new FormattingContext() { Uri = uri, OriginalSnapshot = originalSnapshot, CodeDocument = codeDocument, Range = range, Options = options, IsFormatOnType = isFormatOnType }; var sourceText = codeDocument.GetSourceText(); var syntaxTree = codeDocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var indentations = new Dictionary <int, IndentationContext>(); var previousIndentationLevel = 0; for (var i = 0; i < sourceText.Lines.Count; i++) { // Get first non-whitespace character position var nonWsPos = sourceText.Lines[i].GetFirstNonWhitespacePosition(); var existingIndentation = (nonWsPos ?? sourceText.Lines[i].End) - sourceText.Lines[i].Start; var emptyOrWhitespaceLine = false; if (nonWsPos == null) { emptyOrWhitespaceLine = true; nonWsPos = sourceText.Lines[i].Start; } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (TryGetFormattingSpan(nonWsPos.Value, formattingSpans, out var span)) { indentations[i] = new IndentationContext { Line = i, RazorIndentationLevel = span.RazorIndentationLevel, HtmlIndentationLevel = span.HtmlIndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = existingIndentation, FirstSpan = span, EmptyOrWhitespaceLine = emptyOrWhitespaceLine, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line. // Let's create a 0 length span to represent this and default it to HTML. var placeholderSpan = new FormattingSpan( new Language.Syntax.TextSpan(nonWsPos.Value, 0), new Language.Syntax.TextSpan(nonWsPos.Value, 0), FormattingSpanKind.Markup, FormattingBlockKind.Markup, razorIndentationLevel: 0, htmlIndentationLevel: 0, isInClassBody: false); indentations[i] = new IndentationContext { Line = i, RazorIndentationLevel = 0, HtmlIndentationLevel = 0, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = existingIndentation, FirstSpan = placeholderSpan, EmptyOrWhitespaceLine = emptyOrWhitespaceLine, }; } } result.Indentations = indentations; return(result); }
public static FormattingContext Create( Uri uri, DocumentSnapshot originalSnapshot, RazorCodeDocument codedocument, FormattingOptions options, Range range = null, bool isFormatOnType = false) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (originalSnapshot is null) { throw new ArgumentNullException(nameof(originalSnapshot)); } if (codedocument is null) { throw new ArgumentNullException(nameof(codedocument)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var text = codedocument.GetSourceText(); range ??= TextSpan.FromBounds(0, text.Length).AsRange(text); var result = new FormattingContext() { Uri = uri, OriginalSnapshot = originalSnapshot, CodeDocument = codedocument, Range = range, Options = options, IsFormatOnType = isFormatOnType }; var source = codedocument.Source; var syntaxTree = codedocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var total = 0; var previousIndentationLevel = 0; for (var i = 0; i < source.Lines.Count; i++) { // Get first non-whitespace character position var lineLength = source.Lines.GetLineLength(i); var nonWsChar = 0; for (var j = 0; j < lineLength; j++) { var ch = source[total + j]; if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch)) { nonWsChar = j; break; } } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span)) { result.Indentations[i] = new IndentationContext { Line = i, IndentationLevel = span.IndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = span, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. result.Indentations[i] = new IndentationContext { Line = i, IndentationLevel = -1, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = nonWsChar, }; } total += lineLength; } return(result); }
public static FormattingContext Create( DocumentUri uri, DocumentSnapshot originalSnapshot, RazorCodeDocument codeDocument, FormattingOptions options, Range range = null, bool isFormatOnType = false) { if (uri is null) { throw new ArgumentNullException(nameof(uri)); } if (originalSnapshot is null) { throw new ArgumentNullException(nameof(originalSnapshot)); } if (codeDocument is null) { throw new ArgumentNullException(nameof(codeDocument)); } if (options is null) { throw new ArgumentNullException(nameof(options)); } var text = codeDocument.GetSourceText(); range ??= TextSpan.FromBounds(0, text.Length).AsRange(text); var result = new FormattingContext() { Uri = uri, OriginalSnapshot = originalSnapshot, CodeDocument = codeDocument, Range = range, Options = options, IsFormatOnType = isFormatOnType }; var source = codeDocument.Source; var syntaxTree = codeDocument.GetSyntaxTree(); var formattingSpans = syntaxTree.GetFormattingSpans(); var indentations = new Dictionary <int, IndentationContext>(); var total = 0; var previousIndentationLevel = 0; for (var i = 0; i < source.Lines.Count; i++) { // Get first non-whitespace character position var lineLength = source.Lines.GetLineLength(i); var nonWsChar = 0; for (var j = 0; j < lineLength; j++) { var ch = source[total + j]; if (!char.IsWhiteSpace(ch) && !ParserHelpers.IsNewLine(ch)) { nonWsChar = j; break; } } // position now contains the first non-whitespace character or 0. Get the corresponding FormattingSpan. if (TryGetFormattingSpan(total + nonWsChar, formattingSpans, out var span)) { indentations[i] = new IndentationContext { Line = i, IndentationLevel = span.IndentationLevel, RelativeIndentationLevel = span.IndentationLevel - previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = span, }; previousIndentationLevel = span.IndentationLevel; } else { // Couldn't find a corresponding FormattingSpan. Happens if it is a 0 length line. // Let's create a 0 length span to represent this and default it to HTML. var placeholderSpan = new FormattingSpan( new Language.Syntax.TextSpan(total + nonWsChar, 0), new Language.Syntax.TextSpan(total + nonWsChar, 0), FormattingSpanKind.Markup, FormattingBlockKind.Markup, indentationLevel: 0, isInClassBody: false); indentations[i] = new IndentationContext { Line = i, IndentationLevel = 0, RelativeIndentationLevel = previousIndentationLevel, ExistingIndentation = nonWsChar, FirstSpan = placeholderSpan, }; } total += lineLength; } result.Indentations = indentations; return(result); }