diff --git a/src/.vscode/launch.json b/src/.vscode/launch.json index 20ef8d1..5f1e49d 100644 --- a/src/.vscode/launch.json +++ b/src/.vscode/launch.json @@ -28,6 +28,31 @@ "name": ".NET Core Attach", "type": "coreclr", "request": "attach" + }, + { + "name": "Run Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ], + "outFiles": [ + "${workspaceFolder}/NetStackBeautifier.VSCExt/out/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" + }, + { + "name": "Extension Tests", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}", + "--extensionTestsPath=${workspaceFolder}/NetStackBeautifier.VSCExt/out/test/suite/index" + ], + "outFiles": [ + "${workspaceFolder}/out/test/**/*.js" + ], + "preLaunchTask": "${defaultBuildTask}" } ] } \ No newline at end of file diff --git a/src/NetStackBeautifier.Core/Models/FrameFullClass.cs b/src/NetStackBeautifier.Core/Models/FrameFullClass.cs index 6cd92a6..a00dee7 100644 --- a/src/NetStackBeautifier.Core/Models/FrameFullClass.cs +++ b/src/NetStackBeautifier.Core/Models/FrameFullClass.cs @@ -6,6 +6,7 @@ public record FrameFullClass { public IEnumerable NameSections { get; init; } = Enumerable.Empty(); public IEnumerable GenericParameterTypes { get; init; } = Enumerable.Empty(); + public IEnumerable RawGenericParameterTypes { get; init; } = Enumerable.Empty(); /// /// Gets the full class name. diff --git a/src/NetStackBeautifier.Core/Models/FrameMethod.cs b/src/NetStackBeautifier.Core/Models/FrameMethod.cs index dbb347e..c891124 100644 --- a/src/NetStackBeautifier.Core/Models/FrameMethod.cs +++ b/src/NetStackBeautifier.Core/Models/FrameMethod.cs @@ -14,5 +14,11 @@ public record FrameMethod /// /// public IEnumerable GenericParameterTypes{get; init;} = Enumerable.Empty(); + + /// + /// Generic types. + /// + /// + public IEnumerable RawGenericParameterTypes{get; init;} = Enumerable.Empty(); } } \ No newline at end of file diff --git a/src/NetStackBeautifier.Core/Models/FrameParameter.cs b/src/NetStackBeautifier.Core/Models/FrameParameter.cs index 8cfc17f..386d14e 100644 --- a/src/NetStackBeautifier.Core/Models/FrameParameter.cs +++ b/src/NetStackBeautifier.Core/Models/FrameParameter.cs @@ -3,5 +3,5 @@ namespace NetStackBeautifier.Core /// /// A parameter. /// - public record FrameParameter(string ParameterType, string ParameterName); + public record FrameParameter(string ParameterType, string ParameterName, string RawParameterType, string FullParameterName); } \ No newline at end of file diff --git a/src/NetStackBeautifier.Services/LineBeautifiers/ClassGenericTypeSystemCanonicalBeautifier.cs b/src/NetStackBeautifier.Services/LineBeautifiers/ClassGenericTypeSystemCanonicalBeautifier.cs index cbf0fe1..3f42b77 100644 --- a/src/NetStackBeautifier.Services/LineBeautifiers/ClassGenericTypeSystemCanonicalBeautifier.cs +++ b/src/NetStackBeautifier.Services/LineBeautifiers/ClassGenericTypeSystemCanonicalBeautifier.cs @@ -26,8 +26,10 @@ protected override Task BeautifyImpAsync(FrameItem line, Cancellation int tCount = 0; List result = new List(); + List rawResult = new List(); foreach (string item in line.FullClass.GenericParameterTypes) { + rawResult.Add(item); if (string.Equals(item, "System.__Canon", StringComparison.OrdinalIgnoreCase)) { result.Add(GetT(++tCount)); @@ -43,6 +45,7 @@ protected override Task BeautifyImpAsync(FrameItem line, Cancellation FullClass = line.FullClass with { GenericParameterTypes = result, + RawGenericParameterTypes = rawResult }, }; diff --git a/src/NetStackBeautifier.Services/LineBeautifiers/Int32ParameterFiller.cs b/src/NetStackBeautifier.Services/LineBeautifiers/Int32ParameterFiller.cs index 5706c8c..f1daf91 100644 --- a/src/NetStackBeautifier.Services/LineBeautifiers/Int32ParameterFiller.cs +++ b/src/NetStackBeautifier.Services/LineBeautifiers/Int32ParameterFiller.cs @@ -29,7 +29,7 @@ protected override Task BeautifyImpAsync(FrameItem line, Cancellation { paramName = "n" + order++; } - newParamList.Add(new FrameParameter("int", paramName)); + newParamList.Add(new FrameParameter("int", paramName, parameter.ParameterType, parameter.ParameterName)); continue; } diff --git a/src/NetStackBeautifier.Services/LineBeautifiers/MoveNextBeautifier.cs b/src/NetStackBeautifier.Services/LineBeautifiers/MoveNextBeautifier.cs index 1f93c3a..5b4a995 100644 --- a/src/NetStackBeautifier.Services/LineBeautifiers/MoveNextBeautifier.cs +++ b/src/NetStackBeautifier.Services/LineBeautifiers/MoveNextBeautifier.cs @@ -64,6 +64,7 @@ protected override Task BeautifyImpAsync(FrameItem line, Cancellation { Name = newMethodName, GenericParameterTypes = line.Method.GenericParameterTypes.NullAsEmpty().Any() ? line.Method.GenericParameterTypes : GenerateTypeParameter(genericMethodCount), + RawGenericParameterTypes = line.Method.GenericParameterTypes }, }; diff --git a/src/NetStackBeautifier.Services/Parsers/AIProfilerStackBeautifier.cs b/src/NetStackBeautifier.Services/Parsers/AIProfilerStackBeautifier.cs index 8f3e436..896c769 100644 --- a/src/NetStackBeautifier.Services/Parsers/AIProfilerStackBeautifier.cs +++ b/src/NetStackBeautifier.Services/Parsers/AIProfilerStackBeautifier.cs @@ -130,7 +130,7 @@ protected override IFrameLine CreateFrameItem(string line) string? parameterType = parameterDescriptor.Split(' ', StringSplitOptions.RemoveEmptyEntries).LastOrDefault(); if (!string.IsNullOrEmpty(parameterType)) { - methodParameters.Add(new FrameParameter(parameterType.Trim(), string.Empty)); + methodParameters.Add(new FrameParameter(parameterType.Trim(), string.Empty, parameterType, String.Empty)); } } diff --git a/src/NetStackBeautifier.Services/Parsers/SimpleExceptionStackBeautifier.cs b/src/NetStackBeautifier.Services/Parsers/SimpleExceptionStackBeautifier.cs index 51ab44e..07397c2 100644 --- a/src/NetStackBeautifier.Services/Parsers/SimpleExceptionStackBeautifier.cs +++ b/src/NetStackBeautifier.Services/Parsers/SimpleExceptionStackBeautifier.cs @@ -105,6 +105,7 @@ private FrameItem CreateFrame(string line) Name = methodName, Parameters = ParseParameters(mainPartsMatch.Groups[6].Value), GenericParameterTypes = methodTypeParameters, + RawGenericParameterTypes = methodTypeParameters }, FileInfo = frameFileInfo, AssemblySignature = assemblyInfo, @@ -132,7 +133,7 @@ private IEnumerable ParseParameters(string input) { throw new InvalidCastException($"Unexpected parameter pair: {pair}, input string: {input}"); } - yield return new FrameParameter(typeAndNameTokens[0], typeAndNameTokens[1]); + yield return new FrameParameter(typeAndNameTokens[0], typeAndNameTokens[1], typeAndNameTokens[0], typeAndNameTokens[1]); } } diff --git a/src/NetStackBeautifier.VSCExt/src/Actions.ts b/src/NetStackBeautifier.VSCExt/src/Actions.ts index 7a0ddcd..f4a4235 100644 --- a/src/NetStackBeautifier.VSCExt/src/Actions.ts +++ b/src/NetStackBeautifier.VSCExt/src/Actions.ts @@ -1,6 +1,6 @@ import * as vscode from 'vscode'; import { getStackJson } from './BackendService'; -import { generateColorizer, parseJsonStack } from './callStackLanguageService'; +import { generateColorizer, parseJsonStack, StackType } from './callStackLanguageService'; export const SHOW_BEAUTIFIED_DOCUMENT = 'stackbeauty.showBeautified'; @@ -24,11 +24,14 @@ export async function createBeautifiedDocument(context: vscode.ExtensionContext) const stackJson = await getStackJson(selectedText); if (stackJson && stackJson.length) { - generateColorizer(stackJson); const parsedStack = parseJsonStack(stackJson); + vscode.languages.setTextDocumentLanguage(vscode.window.activeTextEditor.document, 'callstack'); + generateColorizer(stackJson, vscode.window.activeTextEditor.document.uri.fsPath, StackType.full); const uri = vscode.Uri.parse(`callstack: ${parsedStack}`); const doc = await vscode.workspace.openTextDocument(uri); vscode.languages.setTextDocumentLanguage(doc, 'callstack'); + generateColorizer(stackJson, uri.fsPath); + // vscode.languages.setTextDocumentLanguage(doc, 'callstack'); await vscode.window.showTextDocument(doc, { preview: false, viewColumn: vscode.ViewColumn.Two diff --git a/src/NetStackBeautifier.VSCExt/src/CallStackLanguageService.ts b/src/NetStackBeautifier.VSCExt/src/CallStackLanguageService.ts index 30bfe9f..0bff75d 100644 --- a/src/NetStackBeautifier.VSCExt/src/CallStackLanguageService.ts +++ b/src/NetStackBeautifier.VSCExt/src/CallStackLanguageService.ts @@ -1,181 +1,149 @@ import { - CancellationToken, - DocumentSemanticTokensProvider, - languages, - Position, - Range, - SemanticTokensBuilder, - SemanticTokensLegend, - TextDocument, + CancellationToken, + DocumentSemanticTokensProvider, + languages, + Position, + Range, + SemanticTokensBuilder, + SemanticTokensLegend, + TextDocument, } from "vscode"; import { IStackTree } from "./StackDocumentProvider"; +import * as SemanticFilters from './SemanticFilters'; -export const generateColorizer = (json: IStackTree[]) => { - const tokenTypes = ['class', 'parameter', 'function', 'interface', 'label', 'typeParameter', 'comment']; - const tokenModifiers = ['declaration', 'documentation', 'definition', 'defaultLibrary']; - const legend = new SemanticTokensLegend(tokenTypes, tokenModifiers); - const buildRange = (line: number, startingPoint: number, endingPoint: number) => new Range(new Position(line, startingPoint), new Position(line, endingPoint)); - - const genericParamaterTypeBuilder = (genericParameterTypes: string[], line: string, lineNumber: number, builder: SemanticTokensBuilder) => { - genericParameterTypes.forEach((typeParam, typeIndex) => { - let typeParamStartingPoint; - let typeParamEndingPoint; - if (typeIndex === 0) { - typeParamStartingPoint = line.indexOf('<' + typeParam) + 1; - typeParamEndingPoint = typeParamStartingPoint + typeParam.length; - - } else if (typeIndex + 1 === genericParameterTypes.length) { - typeParamStartingPoint = line.indexOf(typeParam + '>'); - typeParamEndingPoint = typeParamStartingPoint + typeParam.length; - } else { - typeParamStartingPoint = line.indexOf(', ' + typeParam) + 2; - typeParamEndingPoint = typeParamStartingPoint + typeParam.length; - } - - builder.push( - buildRange(lineNumber, typeParamStartingPoint, typeParamEndingPoint), - 'interface', - ['declaration'] - ); - }); - }; - - // Maybe move this into where we parse the json to begin with. Only problem is it feels like we'd be abandoning the spirit of the semantic tokenizer - const provider: DocumentSemanticTokensProvider = { - provideDocumentSemanticTokens(document: TextDocument, _token: CancellationToken) { - const tokensBuilder = new SemanticTokensBuilder(legend); - document.getText().split('\n').forEach((line, i) => { - const jsonLine = json[i]; - const { fullClass, method } = jsonLine.typeValue; - - if (jsonLine.typeDiscriminator === 2) { - if (fullClass) { - if (fullClass.shortClassNameOrDefault) { - // Somewhat redundant since grammar regex generally matches correctly - const startingPoint = line.indexOf(fullClass.shortClassNameOrDefault); - const endingPoint = fullClass.shortClassNameOrDefault.length; - tokensBuilder.push( - buildRange(i, startingPoint, endingPoint), - 'class', - ['declaration'] - ); - } - - if (fullClass.genericParameterTypes) { - genericParamaterTypeBuilder(fullClass.genericParameterTypes, line, i, tokensBuilder); - } - } - if (method) { - let methodStartingPoint; - let methodEndingPoint; - if (fullClass && fullClass.shortClassNameOrDefault) { - const genericParameterTypeString = fullClass.genericParameterTypes.length ? `<${fullClass.genericParameterTypes.join(', ')}>` : ''; - const fullMethodString = fullClass.shortClassNameOrDefault + genericParameterTypeString + CLASS_DELIMITER + method.name; - methodStartingPoint = fullMethodString.length - method.name.length; - methodEndingPoint = fullMethodString.length; - } else { - methodStartingPoint = line.indexOf(method.name); - methodEndingPoint = method.name.length; - } - tokensBuilder.push( - buildRange(i, methodStartingPoint, methodEndingPoint), - 'function', - ['declaration'] - ); - - if (method.genericParameterTypes.length) { - genericParamaterTypeBuilder(method.genericParameterTypes, line, i, tokensBuilder); - } - - if (method.parameters.length) { - method.parameters.forEach((param) => { - if (param.parameterName && param.parameterType) { - const fullParamString = param.parameterType + STRING_SPACE + param.parameterName; - const paramTypeStartingPoint = line.indexOf(fullParamString); - const paramNameStartingPoint = paramTypeStartingPoint + param.parameterType.length + STRING_SPACE.length; - const paramTypeEndingPoint = paramTypeStartingPoint + param.parameterType.length; - const paramNameEndingPoint = paramNameStartingPoint + param.parameterName.length; - tokensBuilder.push( - buildRange(i, paramTypeStartingPoint, paramTypeEndingPoint), - 'interface', - ['defaultLibrary'] - ); - tokensBuilder.push( - buildRange(i, paramNameStartingPoint, paramNameEndingPoint), - 'parameter', - ['declaration'] - ); - } else if (param.parameterName) { - const paramNameStartingPoint = line.indexOf(param.parameterName); - const paramNameEndingPoint = paramNameStartingPoint + param.parameterName.length; - tokensBuilder.push( - buildRange(i, paramNameStartingPoint, paramNameEndingPoint), - 'parameter', - ['declaration'] - ); - } else { - const paramTypeStartingPoint = line.indexOf(param.parameterType); - const paramTypeEndingPoint = paramTypeStartingPoint + param.parameterType.length; - tokensBuilder.push( - buildRange(i, paramTypeStartingPoint, paramTypeEndingPoint), - 'interface', - ['defaultLibrary'] - ); - } - }); - } - } - } else { - tokensBuilder.push( - buildRange(i, 0, line.length), - 'comment', - ['definition'] - ); - } - }); - - return tokensBuilder.build(); - } - }; - languages.registerDocumentSemanticTokensProvider({ - language: 'callstack' - }, provider, legend); +export interface IDocumentLineRemovals { + removedAtIndex: number; + removedLength: number; +} + +export interface IDocumentLine { + fullLine: string; + lineNumber: number; + remainingLine: string; + removals: IDocumentLineRemovals[]; +} + +export enum StackType { + simplified, + full +} + +const buildRange = (line: number, startingPoint: number, endingPoint: number) => new Range(new Position(line, startingPoint), new Position(line, endingPoint)); + +const runSemanticFilters = (semanticFilters: (() => void)[]) => semanticFilters.forEach((filter) => filter()); + +const semanticStackBuilder = (docLine: IDocumentLine, json: IStackTree[], stackType: StackType, tokensBuilder: SemanticTokensBuilder) => { + const { lineNumber, fullLine } = docLine; + const jsonLine = json[lineNumber]; + if (jsonLine.typeDiscriminator === 1) { + tokensBuilder.push( + buildRange(lineNumber, 0, fullLine.length), + 'comment', + ['definition'] + ); + return; + } + + const { fullClass, method, assemblySignature } = jsonLine.typeValue; + const filters: (() => void)[] = []; + + if (stackType === StackType.full) { + // Assembly signature + // Full class name sections + // Full class generic paramater types + // Method name + // Method generic param types + // Method params + filters.push(...[ + () => SemanticFilters.assemblySignature(assemblySignature, docLine, tokensBuilder), + () => fullClass && SemanticFilters.classNameSections(fullClass.nameSections, docLine, tokensBuilder), + () => fullClass && SemanticFilters.rawGenericParameterType(fullClass.rawGenericParameterTypes, docLine, tokensBuilder), + () => SemanticFilters.methodName(method, docLine, tokensBuilder), + () => SemanticFilters.rawGenericParameterType(method.rawGenericParameterTypes, docLine, tokensBuilder), + () => SemanticFilters.methodParameterType(method, docLine, tokensBuilder, true) + ]); + } else if (stackType === StackType.simplified) { + // Short class name + // Full class generic paramater types + // Method name + // Method generic param types + // Method params + filters.push(...[ + () => fullClass && SemanticFilters.classNameFilter(fullClass.shortClassNameOrDefault, docLine, tokensBuilder), + () => fullClass && SemanticFilters.genericParamaterTypeBuilder(fullClass.genericParameterTypes, docLine, tokensBuilder), + () => SemanticFilters.methodName(method, docLine, tokensBuilder), + () => SemanticFilters.genericParamaterTypeBuilder(method.genericParameterTypes, docLine, tokensBuilder), + () => SemanticFilters.methodParameterType(method, docLine, tokensBuilder) + ]); + } + + runSemanticFilters(filters); +}; + +export const generateColorizer = (json: IStackTree[], fileUri: string, stackType: StackType = StackType.simplified) => { + const tokenTypes = ['class', 'parameter', 'function', 'interface', 'label', 'typeParameter', 'comment']; + const tokenModifiers = ['declaration', 'documentation', 'definition', 'defaultLibrary']; + const legend = new SemanticTokensLegend(tokenTypes, tokenModifiers); + + // Maybe move this into where we parse the json to begin with. Only problem is it feels like we'd be abandoning the spirit of the semantic tokenizer + const provider: DocumentSemanticTokensProvider = { + provideDocumentSemanticTokens(document: TextDocument, _token: CancellationToken) { + const tokensBuilder = new SemanticTokensBuilder(legend); + document.getText().split('\n').forEach((line, i) => { + const docLine: IDocumentLine = { + fullLine: line, + lineNumber: i, + remainingLine: line, + removals: [] + }; + + semanticStackBuilder(docLine, json, stackType, tokensBuilder); + }); + + return tokensBuilder.build(); + } + }; + + languages.registerDocumentSemanticTokensProvider({ + language: 'callstack', + pattern: fileUri + }, provider, legend); }; export const CLASS_DELIMITER = '.'; export const STRING_SPACE = ' '; export const METHOD_BODY = '{ ... }'; export const parseJsonStack = (jsonStack: IStackTree[]) => jsonStack.map((line) => { - const { method, fullClass } = line.typeValue; - const endLineStringStack = []; - if (line.typeDiscriminator === 1) { - return line.typeValue.value; - } - - if (fullClass) { - const { genericParameterTypes } = fullClass; - endLineStringStack.push(fullClass.shortClassNameOrDefault); - if (genericParameterTypes && genericParameterTypes.length) { - endLineStringStack.push(`<${genericParameterTypes.join(', ')}>${CLASS_DELIMITER}`); - } else { - endLineStringStack.push(CLASS_DELIMITER); - } - } - if (method) { - const { parameters, genericParameterTypes } = method; - endLineStringStack.push(method.name); - - // Push type params if they exist - if (genericParameterTypes && genericParameterTypes.length) { - endLineStringStack.push(`<${genericParameterTypes.join(', ')}>`); - } - // Push method params - const paramString = parameters.length ? parameters.map((param) => [ - param.parameterType && param.parameterType, - param.parameterType && param.parameterName && STRING_SPACE, - param.parameterName && param.parameterName - ].join('')).join(', ') : ''; - endLineStringStack.push(`(${paramString}) ${METHOD_BODY}`); - } - return endLineStringStack.join(''); - }).join('\n'); \ No newline at end of file + const { method, fullClass } = line.typeValue; + const endLineStringStack = []; + if (line.typeDiscriminator === 1) { + return line.typeValue.value; + } + + if (fullClass) { + const { genericParameterTypes } = fullClass; + endLineStringStack.push(fullClass.shortClassNameOrDefault); + if (genericParameterTypes && genericParameterTypes.length) { + endLineStringStack.push(`<${genericParameterTypes.join(', ')}>${CLASS_DELIMITER}`); + } else { + endLineStringStack.push(CLASS_DELIMITER); + } + } + if (method) { + const { parameters, genericParameterTypes } = method; + endLineStringStack.push(method.name); + + // Push type params if they exist + if (genericParameterTypes && genericParameterTypes.length) { + endLineStringStack.push(`<${genericParameterTypes.join(', ')}>`); + } + // Push method params + const paramString = parameters.length ? parameters.map((param) => [ + param.parameterType && param.parameterType, + param.parameterType && param.parameterName && STRING_SPACE, + param.parameterName && param.parameterName + ].join('')).join(', ') : ''; + endLineStringStack.push(`(${paramString}) ${METHOD_BODY}`); + } + return endLineStringStack.join(''); +}).join('\n'); \ No newline at end of file diff --git a/src/NetStackBeautifier.VSCExt/src/SemanticFilters.ts b/src/NetStackBeautifier.VSCExt/src/SemanticFilters.ts new file mode 100644 index 0000000..22de454 --- /dev/null +++ b/src/NetStackBeautifier.VSCExt/src/SemanticFilters.ts @@ -0,0 +1,109 @@ +import { Position, Range, SemanticTokensBuilder } from "vscode"; +import { IDocumentLine } from "./callStackLanguageService"; +import { IStackTreeMethod } from "./StackDocumentProvider"; + +const buildRange = (line: number, startingPoint: number, endingPoint: number) => new Range(new Position(line, startingPoint), new Position(line, endingPoint)); + +const getRemainingLine = (docLine: IDocumentLine, startingPoint: number, endingPoint: number) => docLine.remainingLine.slice(0, startingPoint) + docLine.remainingLine.slice(endingPoint, docLine.remainingLine.length) + +const updateDocumentLine = (docLine: IDocumentLine, startingPoint: number, endingPoint: number, matchLength: number) => { + docLine.removals.push({ + removedAtIndex: startingPoint, + removedLength: matchLength + }); + docLine.remainingLine = getRemainingLine(docLine, startingPoint, endingPoint); +}; + +const getTrueStartingPoint = (docLine: IDocumentLine, startingPoint: number) => + startingPoint + docLine.removals + .filter((removal) => removal.removedAtIndex <= startingPoint) + .reduce((acc, curr) => (curr.removedLength + acc), 0); + +const getFilterRange = (matchString: string, docLine: IDocumentLine, offSet: number = 0) => { + const { remainingLine, lineNumber } = docLine; + const startingPoint = remainingLine.indexOf(matchString); + const endingPoint = startingPoint + matchString.length; + + const realStartingPoint = getTrueStartingPoint(docLine, startingPoint) + offSet; + const realEndingPoint = matchString.length + realStartingPoint; + updateDocumentLine(docLine, startingPoint, endingPoint, matchString.length); + + return buildRange(lineNumber, realStartingPoint, realEndingPoint); +}; + +const highlightLine = (builder: SemanticTokensBuilder, matchString: string, docLine: IDocumentLine, tokenType: string, tokenModifiers?: string[], offSet: number = 0) => { + if (docLine.remainingLine.indexOf(matchString) !== -1) { + builder.push( + getFilterRange(matchString, docLine), + tokenType, + tokenModifiers + ); + } +}; + +export function assemblySignature(signature: string, docLine: IDocumentLine, builder: SemanticTokensBuilder) { + if (signature) { + highlightLine(builder, signature, docLine, 'class', ['declaration']); + } +} + +export function classNameFilter(className: string, docLine: IDocumentLine, builder: SemanticTokensBuilder) { + if (className) { + highlightLine(builder, className, docLine, 'class', ['declaration']); + } +} + +export function classNameSections(nameSections: string[], docLine: IDocumentLine, builder: SemanticTokensBuilder) { + if (nameSections && nameSections.length) { + nameSections.map((nameSection) => { + highlightLine(builder, nameSection, docLine, 'class', ['declaration']); + }); + } +} + +export function genericParamaterTypeBuilder(genericParameterTypes: string[], docLine: IDocumentLine, builder: SemanticTokensBuilder) { + genericParameterTypes.forEach((typeParam, typeIndex) => { + let matchString; + let offSet; + if (typeIndex === 0) { + matchString = '<' + typeParam; + offSet = 1; + } else if (typeIndex + 1 === genericParameterTypes.length) { + matchString = typeParam + '>'; + } else { + matchString = typeParam + ', ' + '>'; + offSet = 2; + } + + highlightLine(builder, matchString, docLine, 'interface', ['declaration']); + }); +}; + +export function methodName(method: IStackTreeMethod, docLine: IDocumentLine, builder: SemanticTokensBuilder) { + if (method && method.name) { + highlightLine(builder, method.name, docLine, 'function', ['declaration']); + } +} +export const STRING_SPACE = ' '; +export function methodParameterType(method: IStackTreeMethod, docLine: IDocumentLine, builder: SemanticTokensBuilder, rawParam: boolean = false) { + if (method && method.parameters && method.parameters.length) { + method.parameters.forEach((param) => { + const parameterType = rawParam ? param.rawParameterType : param.parameterType; + const parameterName = rawParam ? param.fullParameterName : param.parameterName; + if (parameterType && parameterType.length) { + highlightLine(builder, parameterType, docLine, 'interface', ['defaultLibrary']); + } + if (parameterName && parameterName.length) { + highlightLine(builder, parameterType, docLine, 'parameter', ['declaration']); + } + }); + } +} + +export function rawGenericParameterType(rawGenericParameterTypes: string[], docLine: IDocumentLine, builder: SemanticTokensBuilder) { + if (rawGenericParameterTypes && rawGenericParameterTypes.length) { + rawGenericParameterTypes.forEach((typeParam) => { + highlightLine(builder, typeParam, docLine, 'interface', ['declaration']); + }); + } +} diff --git a/src/NetStackBeautifier.VSCExt/src/stackDocumentProvider.ts b/src/NetStackBeautifier.VSCExt/src/stackDocumentProvider.ts index 902dcde..e6eaea2 100644 --- a/src/NetStackBeautifier.VSCExt/src/stackDocumentProvider.ts +++ b/src/NetStackBeautifier.VSCExt/src/stackDocumentProvider.ts @@ -8,28 +8,34 @@ import { interface IStackTreeParams { parameterName: string; parameterType: string; + rawParameterType: string; + fullParameterName: string; } -interface IStackTreeTypeValue { +export interface IStackTreeMethod { + name: string; + parameters: IStackTreeParams[]; + genericParameterTypes: string[]; + rawGenericParameterTypes: string[]; +} +export interface IStackTreeFullClass { + nameSections: string[]; + genericParameterTypes: string[]; + rawGenericParameterTypes: string[]; + fullClassNameOrDefault: string; + shortClassNameOrDefault: string; +} +export interface IStackTreeTypeValue { value?: string; assemblySignature: string; fileInfo: string; - fullClass?: { - nameSections: string[]; - genericParameterTypes: string[]; - fullClassNameOrDefault: string; - shortClassNameOrDefault: string; - }; + fullClass?: IStackTreeFullClass; id: string; - method: { - name: string; - parameters: IStackTreeParams[]; - genericParameterTypes: string[]; - }; + method: IStackTreeMethod; tags: any; } export interface IStackTree { typeDiscriminator: number; - typeValue: IStackTreeTypeValue; + typeValue: IStackTreeTypeValue; } export class StackDocumentProvider implements TextDocumentContentProvider {