From bfd69d21043720fe48e65f876326727d135faafd Mon Sep 17 00:00:00 2001 From: Tony Spataro Date: Sun, 3 Jul 2022 16:10:57 -0700 Subject: [PATCH 1/2] Bump LSP client to gain support for inlay hints --- package.json | 2 +- yarn.lock | 38 +++++++++++++++++++------------------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index 62b067a..f9845da 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "watch": "tsc --watch -p ./" }, "dependencies": { - "vscode-languageclient": "^8.0.1" + "vscode-languageclient": "8.0.2-next.5" }, "devDependencies": { "@types/node": "^18.0.0", diff --git a/yarn.lock b/yarn.lock index 8c74cf4..6bf680b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -720,32 +720,32 @@ vsce@^2.9.2: yauzl "^2.3.1" yazl "^2.2.2" -vscode-jsonrpc@8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.1.tgz#f30b0625ebafa0fb3bc53e934ca47b706445e57e" - integrity sha512-N/WKvghIajmEvXpatSzvTvOIz61ZSmOSa4BRA4pTLi+1+jozquQKP/MkaylP9iB68k73Oua1feLQvH3xQuigiQ== +vscode-jsonrpc@8.0.2-next.1: + version "8.0.2-next.1" + resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6" + integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA== -vscode-languageclient@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.1.tgz#bf5535c4463a78daeaca0bcb4f5868aec86bb301" - integrity sha512-9XoE+HJfaWvu7Y75H3VmLo5WLCtsbxEgEhrLPqwt7eyoR49lUIyyrjb98Yfa50JCMqF2cePJAEVI6oe2o1sIhw== +vscode-languageclient@8.0.2-next.5: + version "8.0.2-next.5" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2-next.5.tgz#3238a388585c3119e247f761b4355273cc2fd909" + integrity sha512-g87RJLHz0XlRyk6DOTbAk4JHcj8CKggXy4JiFL7OlhETkcYzTOR8d+Qdb4GqZr37PDs1Cl21omtTNK5LyR/RQg== dependencies: minimatch "^3.0.4" semver "^7.3.5" - vscode-languageserver-protocol "3.17.1" + vscode-languageserver-protocol "3.17.2-next.6" -vscode-languageserver-protocol@3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.1.tgz#e801762c304f740208b6c804a0cf21f2c87509ed" - integrity sha512-BNlAYgQoYwlSgDLJhSG+DeA8G1JyECqRzM2YO6tMmMji3Ad9Mw6AW7vnZMti90qlAKb0LqAlJfSVGEdqMMNzKg== +vscode-languageserver-protocol@3.17.2-next.6: + version "3.17.2-next.6" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc" + integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA== dependencies: - vscode-jsonrpc "8.0.1" - vscode-languageserver-types "3.17.1" + vscode-jsonrpc "8.0.2-next.1" + vscode-languageserver-types "3.17.2-next.2" -vscode-languageserver-types@3.17.1: - version "3.17.1" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16" - integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ== +vscode-languageserver-types@3.17.2-next.2: + version "3.17.2-next.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596" + integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w== wrappy@1: version "1.0.2" From 871e7629f51017275670f96970cb198682824601 Mon Sep 17 00:00:00 2001 From: Tony Spataro Date: Sun, 3 Jul 2022 16:23:23 -0700 Subject: [PATCH 2/2] Remove proprietary inlay hinter --- package.json | 12 +-- src/InlayHints.ts | 190 ---------------------------------------------- src/extension.ts | 2 - 3 files changed, 1 insertion(+), 203 deletions(-) delete mode 100644 src/InlayHints.ts diff --git a/package.json b/package.json index f9845da..b6f3d71 100644 --- a/package.json +++ b/package.json @@ -73,17 +73,7 @@ } } }, - "colors": [ - { - "id": "syntaxTree.inlayHints", - "description": "Text color for the inserted inlay hints", - "defaults": { - "dark": "#707070", - "light": "#999999", - "highContrast": "foreground" - } - } - ] + "colors": [] }, "scripts": { "compile": "tsc -p ./", diff --git a/src/InlayHints.ts b/src/InlayHints.ts deleted file mode 100644 index 5f427ed..0000000 --- a/src/InlayHints.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { DecorationOptions, DecorationRangeBehavior, Disposable, OutputChannel, Range, TextDocument, TextDocumentChangeEvent, TextEditor, TextEditorDecorationType, ThemeColor, window, workspace } from "vscode"; -import { LanguageClient } from "vscode-languageclient/node"; - -type InlayHint = { position: number, text: string }; -type InlayHintSet = { before: InlayHint[], after: InlayHint[] }; - -class InlayHints implements Disposable { - // The client used to communicate with the language server. In the case of - // this class it's used to send a textDocument/inlayHints request. - private readonly languageClient: LanguageClient; - - // The output channel used for logging for this class. It's given from the - // main file so that it uses the same as the rest of the extension. - private readonly outputChannel: OutputChannel; - - private readonly decorationType: TextEditorDecorationType; - private readonly inlayHintsCache: WeakMap; - private readonly debouncedHandleTextDocumentChange: Debounced; - - private readonly disposables: Disposable[]; - - constructor(languageClient: LanguageClient, outputChannel: OutputChannel) { - this.languageClient = languageClient; - this.outputChannel = outputChannel; - - const color = new ThemeColor("syntaxTree.inlayHints"); - this.decorationType = window.createTextEditorDecorationType({ - before: { color, fontStyle: "normal" }, - after: { color, fontStyle: "normal" }, - rangeBehavior: DecorationRangeBehavior.ClosedClosed - }); - - this.inlayHintsCache = new WeakMap(); - this.setInlayHintsForEditors(window.visibleTextEditors); - - // Here we're going to debounce the handleTextDocumentChange callback so - // that we're not reacting too quickly to user inputs and making it flash - // alll around the editor. - this.debouncedHandleTextDocumentChange = debounce(300, (editor: TextEditor) => { - this.outputChannel.appendLine("Handling text document changes (debounced)"); - this.inlayHintsCache.delete(editor.document); - this.setInlayHintsForEditor(editor); - }); - - // Track all of the various callbacks and objects that implement Disposable - // so that when we need to dispose of the entire InlayHints instance we can - // iterate through and dispose all of them. - this.disposables = [ - this.decorationType, - window.onDidChangeVisibleTextEditors(this.setInlayHintsForEditors, this), - workspace.onDidChangeTextDocument(this.handleTextDocumentChange, this), - this.debouncedHandleTextDocumentChange - ]; - } - - dispose() { - this.disposables.forEach((disposable) => disposable.dispose()); - } - - handleTextDocumentChange(event: TextDocumentChangeEvent) { - const editor = window.activeTextEditor; - - if (editor !== undefined && event.document === editor.document) { - this.debouncedHandleTextDocumentChange(editor); - } else { - this.inlayHintsCache.delete(event.document); - } - } - - // Asynchronously get the inlay hints for a given text document, optionally - // using a cache if it has already been populated. - async getInlayHintsForTextDocument(document: TextDocument): Promise { - if (document.languageId !== "ruby") { - // This editor may have previously been a Ruby file, but that has now - // changed. So we should delete the inlay hints that may be there. - if (this.inlayHintsCache.has(document)) { - this.inlayHintsCache.delete(document); - - // Return an empty set of inlay hints so that it gets properly cleared - // from the document. - return { before: [], after: [] }; - } - - // Otherwise, we're going to return undefined so that we don't bother with - // this file, as it's not a Ruby file. - return undefined; - } - - // Check the cache first to see if we have already computed the inlay hints - // for this document. Return them if we have them. - let inlayHints = this.inlayHintsCache.get(document); - if (inlayHints) { - this.outputChannel.appendLine("Loading inlay hints from cache"); - return inlayHints; - } - - // Otherwise, asynchronously request the inlay hints from the language - // server, cache the response, and return it. - this.outputChannel.appendLine("Requesting inlay hints"); - inlayHints = await this.languageClient.sendRequest("textDocument/inlayHints", { - textDocument: { uri: document.uri.toString() } - }); - - // In case of a syntax error, this is not going to return anything. In that - // case, we don't want to set the cache to anything, but we also don't want - // to clear the previous inlay hints either. So we're just going to return - // undefined - if (!inlayHints) { - return undefined; - } - - this.inlayHintsCache.set(document, inlayHints); - return inlayHints; - } - - async setInlayHintsForEditor(editor: TextEditor) { - const inlayHints = await this.getInlayHintsForTextDocument(editor.document); - if (!inlayHints) { - return; - } - - const decorations: DecorationOptions[] = [ - ...inlayHints.before.map(({ position, text: contentText }) => ({ - range: new Range(editor.document.positionAt(position), editor.document.positionAt(position)), - renderOptions: { before: { contentText } } - })), - ...inlayHints.after.map(({ position, text: contentText }) => ({ - range: new Range(editor.document.positionAt(position), editor.document.positionAt(position)), - renderOptions: { after: { contentText } } - })) - ]; - - this.outputChannel.appendLine("Settings inlay hints"); - editor.setDecorations(this.decorationType, decorations); - } - - setInlayHintsForEditors(editors: readonly TextEditor[]) { - editors.forEach((editor) => this.setInlayHintsForEditor(editor)); - } -} - -// The return value of the debounce function below. -type Debounced = Disposable & ((argument: T) => void); - -// This function will take a given callback and delay it by a given delay. If -// another call to the same function with the same argument comes in before the -// first one finishes, it will cancel the first invocation and start the delay -// again. -function debounce(delay: number, callback: (argument: T) => void): Debounced { - let allTimeouts = new WeakMap(); - const liveTimeouts = new Set(); - - const debounced = (argument: T) => { - // First, clear out the timeout for the previous call to this debounced - // callback. - const previousTimeout = allTimeouts.get(argument); - if (previousTimeout !== undefined) { - clearTimeout(previousTimeout); - liveTimeouts.delete(previousTimeout); - } - - // Next, create a new timeout for this call to the debounced callback. - // If it doesn't get cancelled by a subsequent call, it will be invoked with - // the given argument. - const timeout = setTimeout(() => { - allTimeouts.delete(argument); - liveTimeouts.delete(timeout); - callback(argument); - }, delay); - - // Finally, track the timeout that we just created in both a WeakMap - // that links the editor to the timeout and a set. We track both so that - // we can iterate through the timeouts when VSCode needs to dispose of - // this subscription. - allTimeouts.set(argument, timeout); - liveTimeouts.add(timeout); - }; - - // Define the necessary dispose function so that VSCode knows how to - // trigger disposal of this entire debounced function. - debounced.dispose = () => { - liveTimeouts.forEach((timeout) => clearTimeout(timeout)); - liveTimeouts.clear(); - allTimeouts = new WeakMap(); - }; - - return debounced; -} - -export default InlayHints; diff --git a/src/extension.ts b/src/extension.ts index 068c4c0..cb23ade 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,7 +5,6 @@ import { LanguageClient, ServerOptions } from "vscode-languageclient/node"; import { promisify } from "util"; import { exec } from "child_process"; -import InlayHints from "./InlayHints"; import Visualize from "./Visualize"; const promiseExec = promisify(exec); @@ -121,7 +120,6 @@ export async function activate(context: ExtensionContext) { // implements Disposable so that they clean up their own resources. visualizer = new Visualize(languageClient, outputChannel); context.subscriptions.push( - new InlayHints(languageClient, outputChannel), visualizer ); }