From eedfee6864352e88a10cae57700263480a9978db Mon Sep 17 00:00:00 2001 From: Gustaf Carefall <106698658+Gustaf-C@users.noreply.github.com> Date: Thu, 2 Apr 2026 17:15:55 +0200 Subject: [PATCH 1/2] Basic support for semantic highlighting --- .../semanticTokens/SemanticTokensProvider.ts | 83 +++++++++++++++++++ src/server.ts | 18 +++- 2 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 src/providers/semanticTokens/SemanticTokensProvider.ts diff --git a/src/providers/semanticTokens/SemanticTokensProvider.ts b/src/providers/semanticTokens/SemanticTokensProvider.ts new file mode 100644 index 0000000..0282d2b --- /dev/null +++ b/src/providers/semanticTokens/SemanticTokensProvider.ts @@ -0,0 +1,83 @@ +import { SemanticTokens, SemanticTokensParams, TextDocuments, Range } from 'vscode-languageserver' +import { TextDocument } from 'vscode-languageserver-textdocument' +import FileInfoIndex, { MatlabFunctionScopeInfo, MatlabGlobalScopeInfo } from '../../indexing/FileInfoIndex' +import DocumentIndexer from '../../indexing/DocumentIndexer' + +interface VariableToken { + range: Range + typeIndex: number +} + +class SemanticTokensProvider { + constructor( + protected readonly documentIndexer: DocumentIndexer, + protected readonly fileInfoIndex: FileInfoIndex + ) { } + + async handleSemanticTokensRequest( + params: SemanticTokensParams, + documentManager: TextDocuments + ): Promise { + + const textDocument = documentManager.get(params.textDocument.uri) + if (!textDocument) return null + + await this.documentIndexer.ensureDocumentIndexIsUpdated(textDocument) + + const codeInfo = this.fileInfoIndex.codeInfoCache.get(params.textDocument.uri) + if (!codeInfo) { + return { data: [] } + } + + const tokens: VariableToken[] = [] + this.collectVariableTokens(codeInfo.globalScopeInfo, tokens) + + tokens.sort((a, b) => + (a.range.start.line - b.range.start.line) || + (a.range.start.character - b.range.start.character) + ) + + const data: number[] = [] + let prevLine = 0 + let prevStart = 0 + + for (const token of tokens) { + const line = token.range.start.line + const start = token.range.start.character + const length = token.range.end.character - token.range.start.character + + const deltaLine = line - prevLine + const deltaStart = deltaLine === 0 ? start - prevStart : start + + data.push(deltaLine, deltaStart, length, token.typeIndex, 0) + prevLine = line + prevStart = start + } + + return { data } + } + + private collectVariableTokens( + scope: MatlabGlobalScopeInfo | MatlabFunctionScopeInfo, + tokens: VariableToken[] + ): void { + for (const variableInfo of scope.variables.values()) { + for (const ref of variableInfo.references) { + if (ref.components.length > 0) { + const typeIndex = 0 + tokens.push({ range: ref.components[0].range, typeIndex }) + } + } + } + + for (const nestedFunc of scope.functionScopes.values()) { + if (nestedFunc.functionScopeInfo) { + this.collectVariableTokens(nestedFunc.functionScopeInfo, tokens) + } + } + } +} + +export const SEMANTIC_TOKEN_TYPES = ['variable'] +export const SEMANTIC_TOKEN_MODIFIERS: string[] = [] +export default SemanticTokensProvider \ No newline at end of file diff --git a/src/server.ts b/src/server.ts index 2e985ca..5486821 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,7 +1,7 @@ // Copyright 2022 - 2025 The MathWorks, Inc. import { TextDocument } from 'vscode-languageserver-textdocument' -import { ClientCapabilities, InitializeParams, InitializeResult, TextDocuments } from 'vscode-languageserver/node' +import { ClientCapabilities, InitializeParams, InitializeResult, TextDocuments, SemanticTokensRequest, SemanticTokensParams } from 'vscode-languageserver/node' import DocumentIndexer from './indexing/DocumentIndexer' import WorkspaceIndexer from './indexing/WorkspaceIndexer' import ConfigurationManager, { ConnectionTiming } from './lifecycle/ConfigurationManager' @@ -22,6 +22,7 @@ import PathResolver from './providers/navigation/PathResolver' import Indexer from './indexing/Indexer' import RenameSymbolProvider from './providers/rename/RenameSymbolProvider' import HighlightSymbolProvider from './providers/highlighting/HighlightSymbolProvider' +import SemanticTokensProvider, { SEMANTIC_TOKEN_TYPES, SEMANTIC_TOKEN_MODIFIERS } from './providers/semanticTokens/SemanticTokensProvider' import { RequestType } from './indexing/SymbolSearchService' import { cacheAndClearProxyEnvironmentVariables } from './utils/ProxyUtils' import MatlabDebugAdaptorServer from './debug/MatlabDebugAdaptorServer' @@ -73,6 +74,7 @@ export async function startServer (): Promise { const navigationSupportProvider = new NavigationSupportProvider(matlabLifecycleManager, fileInfoIndex, indexer, documentIndexer, pathResolver) const renameSymbolProvider = new RenameSymbolProvider(matlabLifecycleManager, documentIndexer, fileInfoIndex) const highlightSymbolProvider = new HighlightSymbolProvider(matlabLifecycleManager, documentIndexer, indexer, fileInfoIndex) + const semanticTokensProvider = new SemanticTokensProvider(documentIndexer, fileInfoIndex) let pathSynchronizer: PathSynchronizer | null @@ -142,7 +144,14 @@ export async function startServer (): Promise { renameProvider: { prepareProvider: true }, - documentHighlightProvider: true + documentHighlightProvider: true, + semanticTokensProvider: { + legend: { + tokenTypes: SEMANTIC_TOKEN_TYPES, + tokenModifiers: SEMANTIC_TOKEN_MODIFIERS + }, + full: true + } } } @@ -361,6 +370,11 @@ export async function startServer (): Promise { connection.onDocumentHighlight(async params => { return await highlightSymbolProvider.handleDocumentHighlightRequest(params, documentManager) }) + + /** -------------- SEMANTIC TOKENS SUPPORT --------------- **/ + connection.onRequest(SemanticTokensRequest.method, async (params: SemanticTokensParams) => { + return await semanticTokensProvider.handleSemanticTokensRequest(params, documentManager) + }) } /** -------------------- Helper Functions -------------------- **/ From be7d2064d0415b0264f295bcb4c5ef06481d0f4b Mon Sep 17 00:00:00 2001 From: Gustaf Carefall <106698658+Gustaf-C@users.noreply.github.com> Date: Thu, 2 Apr 2026 22:34:15 +0200 Subject: [PATCH 2/2] Variable highlighting inside classes --- .../semanticTokens/SemanticTokensProvider.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/providers/semanticTokens/SemanticTokensProvider.ts b/src/providers/semanticTokens/SemanticTokensProvider.ts index 0282d2b..074fbe1 100644 --- a/src/providers/semanticTokens/SemanticTokensProvider.ts +++ b/src/providers/semanticTokens/SemanticTokensProvider.ts @@ -61,6 +61,8 @@ class SemanticTokensProvider { scope: MatlabGlobalScopeInfo | MatlabFunctionScopeInfo, tokens: VariableToken[] ): void { + + // Global scope, e.g. for scripts for (const variableInfo of scope.variables.values()) { for (const ref of variableInfo.references) { if (ref.components.length > 0) { @@ -70,6 +72,17 @@ class SemanticTokensProvider { } } + // Class scope, for class definitions and methods + const classScope = (scope as MatlabGlobalScopeInfo).classScope; + if (classScope) { + for (const nestedFunc of classScope.functionScopes.values()) { + if (nestedFunc.functionScopeInfo) { + this.collectVariableTokens(nestedFunc.functionScopeInfo, tokens); + } + } + } + + // Function scopes, for nested functions for (const nestedFunc of scope.functionScopes.values()) { if (nestedFunc.functionScopeInfo) { this.collectVariableTokens(nestedFunc.functionScopeInfo, tokens)