From 634a9637651f36c1c5638830e624a48e1965aac6 Mon Sep 17 00:00:00 2001 From: Fabian Jakobs Date: Mon, 17 Apr 2023 21:52:15 +0200 Subject: [PATCH] LSP: Highlight document --- .github/workflows/create-build-artifacts.yml | 2 +- .github/workflows/create-release.yml | 2 +- README.md | 1 + src/server/LoxDocument.ts | 100 +++++++++++++ src/server/LoxLspServer.ts | 145 +++++-------------- src/server/server.ts | 35 ++--- 6 files changed, 152 insertions(+), 133 deletions(-) create mode 100644 src/server/LoxDocument.ts diff --git a/.github/workflows/create-build-artifacts.yml b/.github/workflows/create-build-artifacts.yml index 608929b..e876ac9 100644 --- a/.github/workflows/create-build-artifacts.yml +++ b/.github/workflows/create-build-artifacts.yml @@ -30,7 +30,7 @@ jobs: env: RELEASE_TAG: ${{ inputs.release_tag }} - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: lox-lsp path: "*.vsix" diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 027684c..2002ac1 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -34,7 +34,7 @@ jobs: uses: ./.github/workflows/create-build-artifacts.yml secrets: inherit with: - release_tag: release-v${{ needs.prepare-release.outputs.version }} + release_tag: v${{ needs.prepare-release.outputs.version }} create-release: if: startsWith(github.event.head_commit.message, 'Release:') || github.event_name == 'workflow_dispatch' diff --git a/README.md b/README.md index 58b36df..c87da7d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,7 @@ Features: - Go to symbol - Document outline - Rename symbol +- Highlight document: highlights all references to the symbol scoped to this file ## Why???? diff --git a/src/server/LoxDocument.ts b/src/server/LoxDocument.ts new file mode 100644 index 0000000..4a3eeb6 --- /dev/null +++ b/src/server/LoxDocument.ts @@ -0,0 +1,100 @@ +import { Diagnostic, DiagnosticSeverity, DocumentSymbol, Position } from "vscode-languageserver"; +import { TextDocument } from "vscode-languageserver-textdocument"; +import { ErrorReporter } from "../jslox/Error"; +import { Scanner } from "../jslox/Scanner"; +import { Parser } from "../jslox/Parser"; +import { Resolver } from "../jslox/Resolver"; +import { Token } from "../jslox/Token"; +import { SemanticToken, SemanticTokenAnalyzer } from "./SemanticTokenAnalyzer"; + +export class LoxDocument { + public diagnostics: Diagnostic[] = []; + public hadError: boolean = false; + public definitions: Map = new Map(); + public references: Map = new Map(); + public semanticTokens: SemanticToken[] = []; + public documentSymbols?: DocumentSymbol[]; + + constructor(public document: TextDocument) {} + + analyze() { + const source = this.document.getText(); + + this.diagnostics = []; + this.semanticTokens = []; + + this.hadError = false; + const reporter: ErrorReporter = { + error: (token: Token, message: string) => { + this.hadError = true; + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Error, + range: { + start: this.document.positionAt(token.start), + end: this.document.positionAt(token.end), + }, + message: message, + source: "Lox", + }; + this.diagnostics.push(diagnostic); + }, + warn: (token: Token, message: string) => { + const diagnostic: Diagnostic = { + severity: DiagnosticSeverity.Warning, + range: { + start: this.document.positionAt(token.start), + end: this.document.positionAt(token.end), + }, + message: message, + source: "Lox", + }; + this.diagnostics.push(diagnostic); + }, + runtimeError: function (): void {}, + }; + + const tokens = new Scanner(source, reporter).scanTokens(); + const parser = new Parser(tokens, reporter); + + const statements = parser.parse(); + if (!statements || this.hadError) { + return; + } + + const resolver = new Resolver(reporter); + resolver.resolve(statements); + + if (statements && !this.hadError) { + const analyzer = new SemanticTokenAnalyzer(); + analyzer.analyze(statements, resolver); + this.semanticTokens = analyzer.tokens; + this.documentSymbols = analyzer.documentSymbols; + } + + this.definitions = resolver.definitions; + this.references = resolver.references; + } + + findAllFromPosition(position: Position): Token[] { + const offset = this.document.offsetAt(position); + let definition: Token | undefined = undefined; + for (const [def, references] of this.definitions) { + if (def.start <= offset && def.end >= offset) { + definition = def; + break; + } + + for (const reference of references || []) { + if (reference.start <= offset && reference.end >= offset) { + definition = def; + break; + } + } + } + if (!definition) { + return []; + } + + return [definition, ...(this.definitions.get(definition) || [])]; + } +} diff --git a/src/server/LoxLspServer.ts b/src/server/LoxLspServer.ts index 01fbddd..4318ee7 100644 --- a/src/server/LoxLspServer.ts +++ b/src/server/LoxLspServer.ts @@ -1,8 +1,8 @@ import { Definition, DefinitionLink, - Diagnostic, - DiagnosticSeverity, + DocumentHighlight, + DocumentHighlightKind, DocumentSymbol, HandlerResult, Location, @@ -17,81 +17,9 @@ import { WorkspaceEdit, } from "vscode-languageserver"; import { TextDocument } from "vscode-languageserver-textdocument"; -import { ErrorReporter } from "../jslox/Error"; -import { Scanner } from "../jslox/Scanner"; -import { Parser } from "../jslox/Parser"; -import { Resolver } from "../jslox/Resolver"; import { Token } from "../jslox/Token"; -import { SemanticToken, SemanticTokenAnalyzer, TOKEN_TO_ID } from "./SemanticTokenAnalyzer"; - -export class LoxDocument { - public diagnostics: Diagnostic[] = []; - public hadError: boolean = false; - public definitions: Map = new Map(); - public references: Map = new Map(); - public semanticTokens: SemanticToken[] = []; - public documentSymbols?: DocumentSymbol[]; - - constructor(public document: TextDocument) {} - - analyze() { - const source = this.document.getText(); - - this.diagnostics = []; - this.semanticTokens = []; - - this.hadError = false; - const reporter: ErrorReporter = { - error: (token: Token, message: string) => { - this.hadError = true; - const diagnostic: Diagnostic = { - severity: DiagnosticSeverity.Error, - range: { - start: this.document.positionAt(token.start), - end: this.document.positionAt(token.end), - }, - message: message, - source: "Lox", - }; - this.diagnostics.push(diagnostic); - }, - warn: (token: Token, message: string) => { - const diagnostic: Diagnostic = { - severity: DiagnosticSeverity.Warning, - range: { - start: this.document.positionAt(token.start), - end: this.document.positionAt(token.end), - }, - message: message, - source: "Lox", - }; - this.diagnostics.push(diagnostic); - }, - runtimeError: function (): void {}, - }; - - const tokens = new Scanner(source, reporter).scanTokens(); - const parser = new Parser(tokens, reporter); - - const statements = parser.parse(); - if (!statements || this.hadError) { - return; - } - - const resolver = new Resolver(reporter); - resolver.resolve(statements); - - if (statements && !this.hadError) { - const analyzer = new SemanticTokenAnalyzer(); - analyzer.analyze(statements, resolver); - this.semanticTokens = analyzer.tokens; - this.documentSymbols = analyzer.documentSymbols; - } - - this.definitions = resolver.definitions; - this.references = resolver.references; - } -} +import { TOKEN_TO_ID } from "./SemanticTokenAnalyzer"; +import { LoxDocument } from "./LoxDocument"; type LspEventListner = ((type: "diagnostics", params: PublishDiagnosticsParams) => void) & ((type: "foo", params: any) => void); @@ -209,36 +137,15 @@ export class LoxLspServer { return null; } - const offset = loxDocument.document.offsetAt(position); - let definition: Token | undefined = undefined; - for (const [def, references] of loxDocument.definitions) { - if (def.start <= offset && def.end >= offset) { - definition = def; - break; - } - - for (const reference of references || []) { - if (reference.start <= offset && reference.end >= offset) { - definition = def; - break; - } - } - } - if (!definition) { - return null; - } - - const edits: Array = [definition] - .concat(loxDocument.definitions.get(definition) || []) - .map((token) => { - return { - range: { - start: loxDocument.document.positionAt(token.start), - end: loxDocument.document.positionAt(token.end), - }, - newText: newName, - }; - }); + const edits: Array = loxDocument.findAllFromPosition(position).map((token) => { + return { + range: { + start: loxDocument.document.positionAt(token.start), + end: loxDocument.document.positionAt(token.end), + }, + newText: newName, + }; + }); return { changes: { @@ -264,7 +171,7 @@ export class LoxLspServer { if (definition.start <= offset && definition.end >= offset) { return { range: { - start: definition, + start: loxDocument.document.positionAt(definition.start), end: loxDocument.document.positionAt(definition.end), }, placeholder: definition.lexeme, @@ -274,7 +181,7 @@ export class LoxLspServer { if (reference.start <= offset && reference.end >= offset) { return { range: { - start: reference, + start: loxDocument.document.positionAt(reference.start), end: loxDocument.document.positionAt(reference.end), }, placeholder: reference.lexeme, @@ -284,4 +191,26 @@ export class LoxLspServer { } return null; } + + onDocumentHighlight(uri: string, position: Position): HandlerResult { + const loxDocument = this.loxDocuments.get(uri); + if (!loxDocument || !loxDocument.definitions) { + return null; + } + + const symbols = loxDocument.findAllFromPosition(position); + if (!symbols.length) { + return null; + } + + return symbols.map((symbol) => { + return { + range: { + start: loxDocument.document.positionAt(symbol.start), + end: loxDocument.document.positionAt(symbol.end), + }, + kind: DocumentHighlightKind.Text, + }; + }); + } } diff --git a/src/server/server.ts b/src/server/server.ts index 7973c27..b0ce6f7 100644 --- a/src/server/server.ts +++ b/src/server/server.ts @@ -34,18 +34,12 @@ const loxServer = new LoxLspServer(documents, (type, params) => { } }); -let hasWorkspaceFolderCapability = false; -let hasSemanticTokensCapability = false; -let hasSymbolProviderCapability = false; -let hasRenameCapability = false; - connection.onInitialize((params: InitializeParams) => { const capabilities = params.capabilities; - hasWorkspaceFolderCapability = !!(capabilities.workspace && !!capabilities.workspace.workspaceFolders); - hasSemanticTokensCapability = !!capabilities.textDocument?.semanticTokens?.requests?.full; - hasSymbolProviderCapability = !!capabilities.textDocument?.documentSymbol?.hierarchicalDocumentSymbolSupport; - hasRenameCapability = !!capabilities.textDocument?.rename?.prepareSupport; + const hasSemanticTokensCapability = !!capabilities.textDocument?.semanticTokens?.requests?.full; + const hasSymbolProviderCapability = !!capabilities.textDocument?.documentSymbol?.hierarchicalDocumentSymbolSupport; + const hasRenameCapability = !!capabilities.textDocument?.rename?.prepareSupport; const result: InitializeResult = { capabilities: { @@ -55,6 +49,10 @@ connection.onInitialize((params: InitializeParams) => { }, }; + if (capabilities.textDocument?.documentHighlight) { + result.capabilities.documentHighlightProvider = true; + } + if (hasRenameCapability) { result.capabilities.renameProvider = { prepareProvider: true, @@ -78,23 +76,10 @@ connection.onInitialize((params: InitializeParams) => { }; } - if (hasWorkspaceFolderCapability) { - result.capabilities.workspace = { - workspaceFolders: { - supported: true, - }, - }; - } return result; }); -connection.onInitialized(() => { - if (hasWorkspaceFolderCapability) { - connection.workspace.onDidChangeWorkspaceFolders((_event) => { - connection.console.log("Workspace folder change event received."); - }); - } -}); +connection.onInitialized(() => {}); connection.onPrepareRename((params: TextDocumentPositionParams) => { return loxServer.onPrepareRename(params.textDocument.uri, params.position); @@ -116,6 +101,10 @@ connection.onDocumentSymbol((params) => { return loxServer.onDocumentSymbol(params.textDocument.uri); }); +connection.onDocumentHighlight((params) => { + return loxServer.onDocumentHighlight(params.textDocument.uri, params.position); +}); + connection.languages.semanticTokens.on((params) => { return loxServer.onSemanticTokens(params.textDocument.uri); });