From 53d9c4da5e6dfdf49eedaa7faf4cd3439e1ded9a Mon Sep 17 00:00:00 2001 From: Marios Ntoulas Date: Wed, 4 Mar 2026 09:58:56 +0200 Subject: [PATCH 1/3] feat: implement syntax highlighting for `.jref` files - Added grammar contribution to client package.json - Configured `source.jref` to inherit from `source.json` to mimic JSON syntax highlights --- package.json | 7 +++++++ syntaxes/jref.tmlanguage.json | 9 +++++++++ 2 files changed, 16 insertions(+) create mode 100644 syntaxes/jref.tmlanguage.json diff --git a/package.json b/package.json index 3952ad2..80d25bd 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,13 @@ ], "configuration": "./language-configuration.json" } + ], + "grammars": [ + { + "language": "jref", + "scopeName": "source.jref", + "path": "./syntaxes/jref.tmLanguage.json" + } ] }, "workspaces": [ diff --git a/syntaxes/jref.tmlanguage.json b/syntaxes/jref.tmlanguage.json new file mode 100644 index 0000000..33298e7 --- /dev/null +++ b/syntaxes/jref.tmlanguage.json @@ -0,0 +1,9 @@ +{ + "scopeName": "source.jref", + "injectionSelector": "L:source.json", + "patterns": [ + { + "include": "source.json" + } + ] +} From afa3e30119f15d71370fd3d05531445718fef38f Mon Sep 17 00:00:00 2001 From: Marios Ntoulas Date: Wed, 4 Mar 2026 10:58:17 +0200 Subject: [PATCH 2/3] feat: implement semantic tokens provider - Now ref values are highlighted as functions --- server/src/server.ts | 38 +++++++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/server/src/server.ts b/server/src/server.ts index b0e3559..f68d955 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -6,6 +6,9 @@ import { TextDocumentSyncKind, InitializeResult, TextDocumentChangeEvent, + SemanticTokensBuilder, + SemanticTokens, + SemanticTokensParams, } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -14,7 +17,7 @@ import { Node, ParseError, parseTree } from 'jsonc-parser'; import { createParseErrorDiagnostic } from './diagnostics'; import { onDefinition } from './definition'; -import { JRefSymbol, SymbolTable, visit } from './visitor'; +import { SymbolTable, visit } from './visitor'; // Create a connection for the server, using Node's IPC as a transport. // Also include all preview / proposed LSP features. @@ -28,6 +31,13 @@ connection.onInitialize((params: InitializeParams) => { capabilities: { textDocumentSync: TextDocumentSyncKind.Incremental, definitionProvider: true, + semanticTokensProvider: { + legend: { + tokenTypes: ['function'], + tokenModifiers: [], + }, + full: true, + }, }, }; return result; @@ -53,6 +63,32 @@ function sendDiagnostics(document: TextDocument, parseErrors: ParseError[]) { connection.onDefinition((params) => onDefinition(params, { documents, documentSymbols })); +connection.languages.semanticTokens.on((params: SemanticTokensParams): SemanticTokens => { + const document = documents.get(params.textDocument.uri); + if (!document) return { data: [] }; + const tokensBuilder = new SemanticTokensBuilder(); + + const symbols = documentSymbols.get(document); + if (!symbols || symbols.size === 0) return { data: [] }; + + const refs = Array.from(symbols.values()).filter((symbol) => symbol.isReference); + for (const ref of refs) { + const valueNode = ref.node; + const valuePosition = document.positionAt(valueNode.offset); + + // Highlight the value string + tokensBuilder.push( + valuePosition.line, + valuePosition.character, + valueNode.length, + 0, // index of token type + 0, + ); + } + + return tokensBuilder.build(); +}); + // Make the text document manager listen on the connection // for open, change and close text document events documents.listen(connection); From 4847e67aedfe7dd359423f885dc3bace900dd1c4 Mon Sep 17 00:00:00 2001 From: Marios Ntoulas Date: Wed, 4 Mar 2026 11:11:54 +0200 Subject: [PATCH 3/3] refactor: extract semantic token logic into dedicated provider --- server/src/definition.ts | 10 +++------ server/src/semanticTokens.ts | 43 ++++++++++++++++++++++++++++++++++++ server/src/server.ts | 34 +++++----------------------- server/src/utils.ts | 8 +++++++ 4 files changed, 59 insertions(+), 36 deletions(-) create mode 100644 server/src/semanticTokens.ts create mode 100644 server/src/utils.ts diff --git a/server/src/definition.ts b/server/src/definition.ts index 2158133..24976e5 100644 --- a/server/src/definition.ts +++ b/server/src/definition.ts @@ -1,14 +1,10 @@ import path from 'path'; import { URI } from 'vscode-uri'; -import { DefinitionParams, DefinitionLink, TextDocuments, Range } from 'vscode-languageserver/node'; +import { DefinitionParams, DefinitionLink, Range } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; -import { JRefSymbol, SymbolTable } from './visitor'; - -interface ServerContext { - documents: TextDocuments; - documentSymbols: WeakMap; -} +import { JRefSymbol } from './visitor'; +import { ServerContext } from './utils'; const defaultTargetRange: Range = { start: { line: 0, character: 0 }, diff --git a/server/src/semanticTokens.ts b/server/src/semanticTokens.ts new file mode 100644 index 0000000..a56f6e1 --- /dev/null +++ b/server/src/semanticTokens.ts @@ -0,0 +1,43 @@ +import { + SemanticTokensBuilder, + SemanticTokens, + SemanticTokensParams, +} from 'vscode-languageserver/node'; +import { ServerContext } from './utils'; + +export const tokenTypes = ['function']; + +const tokenToIndex = Object.fromEntries(tokenTypes.map((type, index) => [type, index])) as Record< + string, + number +>; + +export function handleSemanticTokens( + params: SemanticTokensParams, + context: ServerContext, +): SemanticTokens { + const { documents, documentSymbols } = context; + const document = documents.get(params.textDocument.uri); + if (!document) return { data: [] }; + const tokensBuilder = new SemanticTokensBuilder(); + + const symbols = documentSymbols.get(document); + if (!symbols || symbols.size === 0) return { data: [] }; + + const refs = Array.from(symbols.values()).filter((symbol) => symbol.isReference); + for (const ref of refs) { + const valueNode = ref.node; + const valuePosition = document.positionAt(valueNode.offset); + + // Highlight the value string + tokensBuilder.push( + valuePosition.line, + valuePosition.character, + valueNode.length, + tokenToIndex['function'], + 0, + ); + } + + return tokensBuilder.build(); +} diff --git a/server/src/server.ts b/server/src/server.ts index f68d955..3447b5e 100644 --- a/server/src/server.ts +++ b/server/src/server.ts @@ -6,9 +6,6 @@ import { TextDocumentSyncKind, InitializeResult, TextDocumentChangeEvent, - SemanticTokensBuilder, - SemanticTokens, - SemanticTokensParams, } from 'vscode-languageserver/node'; import { TextDocument } from 'vscode-languageserver-textdocument'; @@ -18,6 +15,7 @@ import { createParseErrorDiagnostic } from './diagnostics'; import { onDefinition } from './definition'; import { SymbolTable, visit } from './visitor'; +import { handleSemanticTokens, tokenTypes } from './semanticTokens'; // Create a connection for the server, using Node's IPC as a transport. // Also include all preview / proposed LSP features. @@ -33,7 +31,7 @@ connection.onInitialize((params: InitializeParams) => { definitionProvider: true, semanticTokensProvider: { legend: { - tokenTypes: ['function'], + tokenTypes, tokenModifiers: [], }, full: true, @@ -63,31 +61,9 @@ function sendDiagnostics(document: TextDocument, parseErrors: ParseError[]) { connection.onDefinition((params) => onDefinition(params, { documents, documentSymbols })); -connection.languages.semanticTokens.on((params: SemanticTokensParams): SemanticTokens => { - const document = documents.get(params.textDocument.uri); - if (!document) return { data: [] }; - const tokensBuilder = new SemanticTokensBuilder(); - - const symbols = documentSymbols.get(document); - if (!symbols || symbols.size === 0) return { data: [] }; - - const refs = Array.from(symbols.values()).filter((symbol) => symbol.isReference); - for (const ref of refs) { - const valueNode = ref.node; - const valuePosition = document.positionAt(valueNode.offset); - - // Highlight the value string - tokensBuilder.push( - valuePosition.line, - valuePosition.character, - valueNode.length, - 0, // index of token type - 0, - ); - } - - return tokensBuilder.build(); -}); +connection.languages.semanticTokens.on((params) => + handleSemanticTokens(params, { documents, documentSymbols }), +); // Make the text document manager listen on the connection // for open, change and close text document events diff --git a/server/src/utils.ts b/server/src/utils.ts new file mode 100644 index 0000000..6986815 --- /dev/null +++ b/server/src/utils.ts @@ -0,0 +1,8 @@ +import { TextDocuments } from 'vscode-languageserver/node'; +import { SymbolTable } from './visitor'; +import { TextDocument } from 'vscode-languageserver-textdocument'; + +export interface ServerContext { + documents: TextDocuments; + documentSymbols: WeakMap; +}