Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions src/providers/semanticTokens/SemanticTokensProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
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<TextDocument>
): Promise<SemanticTokens | null> {

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 {

// Global scope, e.g. for scripts
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 })
}
}
}

// 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)
}
}
}
}

export const SEMANTIC_TOKEN_TYPES = ['variable']
export const SEMANTIC_TOKEN_MODIFIERS: string[] = []
export default SemanticTokensProvider
18 changes: 16 additions & 2 deletions src/server.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -73,6 +74,7 @@ export async function startServer (): Promise<void> {
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

Expand Down Expand Up @@ -142,7 +144,14 @@ export async function startServer (): Promise<void> {
renameProvider: {
prepareProvider: true
},
documentHighlightProvider: true
documentHighlightProvider: true,
semanticTokensProvider: {
legend: {
tokenTypes: SEMANTIC_TOKEN_TYPES,
tokenModifiers: SEMANTIC_TOKEN_MODIFIERS
},
full: true
}
}
}

Expand Down Expand Up @@ -361,6 +370,11 @@ export async function startServer (): Promise<void> {
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 -------------------- **/
Expand Down