From 0050cb69cb14b638cf5f3f3bf613bdc72251be9c Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Tue, 31 Jan 2023 16:31:35 +1100 Subject: [PATCH 1/3] rename files associated with lsp --- extension/src/extension.ts | 4 +- .../index.ts} | 8 +- languageServer/src/LanguageServer.ts | 43 ++---- languageServer/src/TextDocumentWrapper.ts | 127 ----------------- languageServer/src/server.ts | 2 +- .../src/test/utils/setup-test-connections.ts | 2 +- languageServer/src/textDocument.ts | 134 ++++++++++++++++++ 7 files changed, 155 insertions(+), 165 deletions(-) rename extension/src/{lspClient/languageClient.ts => languageClient/index.ts} (89%) delete mode 100644 languageServer/src/TextDocumentWrapper.ts create mode 100644 languageServer/src/textDocument.ts diff --git a/extension/src/extension.ts b/extension/src/extension.ts index 518337986c..f95d0583da 100644 --- a/extension/src/extension.ts +++ b/extension/src/extension.ts @@ -50,7 +50,7 @@ import { Setup } from './setup' import { definedAndNonEmpty } from './util/array' import { stopProcesses } from './processExecution' import { Flag } from './cli/dvc/constants' -import { LanguageClientWrapper } from './lspClient/languageClient' +import { LanguageClient } from './languageClient' export class Extension extends Disposable { protected readonly internalCommands: InternalCommands @@ -257,7 +257,7 @@ export class Extension extends Disposable { void showWalkthroughOnFirstUse(env.isNewAppInstall) this.dispose.track(recommendRedHatExtensionOnce()) - this.dispose.track(new LanguageClientWrapper()) + this.dispose.track(new LanguageClient()) } public async initialize() { diff --git a/extension/src/lspClient/languageClient.ts b/extension/src/languageClient/index.ts similarity index 89% rename from extension/src/lspClient/languageClient.ts rename to extension/src/languageClient/index.ts index 7efc6900c3..81652da2e0 100644 --- a/extension/src/lspClient/languageClient.ts +++ b/extension/src/languageClient/index.ts @@ -1,6 +1,6 @@ import { workspace } from 'vscode' import { - LanguageClient, + LanguageClient as Client, LanguageClientOptions, ServerOptions, TransportKind @@ -9,8 +9,8 @@ import { documentSelector, serverModule } from 'dvc-vscode-lsp' import { Disposable } from '../class/dispose' import { readFileContents } from '../fileSystem' -export class LanguageClientWrapper extends Disposable { - private client: LanguageClient +export class LanguageClient extends Disposable { + private client: Client constructor() { super() @@ -24,7 +24,7 @@ export class LanguageClientWrapper extends Disposable { } this.client = this.dispose.track( - new LanguageClient( + new Client( 'dvc-vscode-lsp', 'DVC Language Server', this.getServerOptions(), diff --git a/languageServer/src/LanguageServer.ts b/languageServer/src/LanguageServer.ts index d6f3e279ba..9c0958aa79 100644 --- a/languageServer/src/LanguageServer.ts +++ b/languageServer/src/LanguageServer.ts @@ -14,7 +14,11 @@ import { } from 'vscode-languageserver/node' import { TextDocument } from 'vscode-languageserver-textdocument' import { URI } from 'vscode-uri' -import { TextDocumentWrapper } from './TextDocumentWrapper' +import { + getTextDocumentLocation, + getUriLocation, + symbolAt +} from './textDocument' export class LanguageServer { private documentsKnownToEditor!: TextDocuments @@ -37,25 +41,9 @@ export class LanguageServer { connection.listen() } - private getAllDocuments() { - const openDocuments = this.documentsKnownToEditor.all() - return openDocuments.map(doc => this.wrap(doc)) - } - private getDocument(params: TextDocumentPositionParams | CodeActionParams) { const uri = params.textDocument.uri - - const doc = this.documentsKnownToEditor.get(uri) - - if (!doc) { - return null - } - - return this.wrap(doc) - } - - private wrap(doc: TextDocument) { - return new TextDocumentWrapper(doc) + return this.documentsKnownToEditor.get(uri) } private getKnownDocumentLocations(symbolUnderCursor: DocumentSymbol) { @@ -65,17 +53,17 @@ export class LanguageServer { const filePath = symbolUnderCursor.name - const matchingFiles = this.getAllDocuments().filter(doc => - URI.parse(doc.uri).fsPath.endsWith(filePath) - ) + const matchingFiles = this.documentsKnownToEditor + .all() + .filter(doc => URI.parse(doc.uri).fsPath.endsWith(filePath)) - return matchingFiles.map(doc => doc.getLocation()) + return matchingFiles.map(doc => getTextDocumentLocation(doc)) } private async onDefinition(params: DefinitionParams, connection: Connection) { const document = this.getDocument(params) - const symbolUnderCursor = document?.symbolAt(params.position) + const symbolUnderCursor = symbolAt(document, params.position) if (!(document && symbolUnderCursor)) { return null @@ -101,7 +89,7 @@ export class LanguageServer { private async checkIfSymbolsAreFiles( connection: _Connection, - document: TextDocumentWrapper, + document: TextDocument, symbolUnderCursor: DocumentSymbol, fileLocations: Location[] ) { @@ -113,17 +101,12 @@ export class LanguageServer { contents: string } | null>('readFileContents', possiblePath) if (file) { - const location = this.getLocation(possiblePath, file.contents) + const location = getUriLocation(possiblePath, file.contents) fileLocations.push(location) } } } - private getLocation(path: string, contents: string) { - const doc = this.wrap(TextDocument.create(path, 'plain/text', 0, contents)) - return doc.getLocation() - } - private arrayOrSingleResponse(elements: T[]) { if (elements.length === 1) { return elements[0] diff --git a/languageServer/src/TextDocumentWrapper.ts b/languageServer/src/TextDocumentWrapper.ts deleted file mode 100644 index d88ac5f3d5..0000000000 --- a/languageServer/src/TextDocumentWrapper.ts +++ /dev/null @@ -1,127 +0,0 @@ -import { - DocumentSymbol, - Position, - SymbolKind, - Range, - Location -} from 'vscode-languageserver/node' -import { TextDocument } from 'vscode-languageserver-textdocument' -import { - isNode, - isScalar, - parseDocument, - visit, - Node, - Scalar, - Pair, - isPair -} from 'yaml' -import { ITextDocumentWrapper } from './ITextDocumentWrapper' - -export class TextDocumentWrapper implements ITextDocumentWrapper { - uri: string - - private textDocument: TextDocument - - constructor(textDocument: TextDocument) { - this.textDocument = textDocument - this.uri = this.textDocument.uri - } - - public getLocation() { - const uri = this.uri - const start = Position.create(0, 0) - const end = this.positionAt(this.getText().length - 1) - const range = Range.create(start, end) - - return Location.create(uri, range) - } - - public offsetAt(position: Position) { - return this.textDocument.offsetAt(position) - } - - public positionAt(offset: number) { - return this.textDocument.positionAt(offset) - } - - public getYamlDocument() { - return parseDocument(this.getText()) - } - - public symbolAt(position: Position): DocumentSymbol | undefined { - return this.symbolScopeAt(position).pop() - } - - private getText() { - return this.textDocument.getText() - } - - private yamlScalarNodeToDocumentSymbols( - node: Scalar, - [nodeStart, valueEnd, nodeEnd]: [number, number, number] - ) { - const nodeValue = String(node.value) - - let symbolKind: SymbolKind = SymbolKind.String - - if (/\.[A-Za-z]+$/.test(nodeValue) && !nodeValue.includes(' ')) { - symbolKind = SymbolKind.File - } - - return [ - DocumentSymbol.create( - nodeValue, - undefined, - symbolKind, - Range.create(this.positionAt(nodeStart), this.positionAt(nodeEnd)), - Range.create(this.positionAt(nodeStart), this.positionAt(valueEnd)) - ) - ] - } - - private yamlNodeToDocumentSymbols( - node: Node | Pair, - range: [number, number, number] - ): DocumentSymbol[] { - if (isScalar(node)) { - return this.yamlScalarNodeToDocumentSymbols(node, range) - } - - if (isPair(node)) { - return this.yamlNodeToDocumentSymbols(node.value as Node | Pair, range) - } - - return [] - } - - private symbolScopeAt(position: Position): DocumentSymbol[] { - const cursorOffset: number = this.offsetAt(position) - - const symbolsFound: Array = [] - - visit(this.getYamlDocument(), (_, node) => { - if (isNode(node) && node.range) { - const range = node.range - const nodeStart = range[0] - const nodeEnd = range[2] - const isCursorInsideNode = - cursorOffset >= nodeStart && cursorOffset <= nodeEnd - - if (isCursorInsideNode) { - symbolsFound.push(...this.yamlNodeToDocumentSymbols(node, range)) - } - } - }) - const symbolStack = (symbolsFound.filter(Boolean) as DocumentSymbol[]).sort( - (a, b) => { - const offA = this.offsetAt(a.range.end) - this.offsetAt(a.range.start) - const offB = this.offsetAt(b.range.end) - this.offsetAt(b.range.start) - - return offB - offA // We want the tighter fits for last, so we can just pop them - } - ) - - return [...symbolStack] - } -} diff --git a/languageServer/src/server.ts b/languageServer/src/server.ts index acaf19a268..af4c078266 100644 --- a/languageServer/src/server.ts +++ b/languageServer/src/server.ts @@ -1,5 +1,5 @@ import { createConnection, ProposedFeatures } from 'vscode-languageserver/node' -import { LanguageServer } from './LanguageServer' +import { LanguageServer } from './languageServer' const dvcLanguageServer = new LanguageServer() diff --git a/languageServer/src/test/utils/setup-test-connections.ts b/languageServer/src/test/utils/setup-test-connections.ts index 73b98fa038..08928f2694 100644 --- a/languageServer/src/test/utils/setup-test-connections.ts +++ b/languageServer/src/test/utils/setup-test-connections.ts @@ -1,6 +1,6 @@ import { Duplex } from 'stream' import { Connection, createConnection } from 'vscode-languageserver/node' -import { LanguageServer } from '../../LanguageServer' +import { LanguageServer } from '../../languageServer' class TestStream extends Duplex { _write(chunk: string, _encoding: string, done: () => void) { diff --git a/languageServer/src/textDocument.ts b/languageServer/src/textDocument.ts new file mode 100644 index 0000000000..509582ebea --- /dev/null +++ b/languageServer/src/textDocument.ts @@ -0,0 +1,134 @@ +import { TextDocument } from 'vscode-languageserver-textdocument' +import { + DocumentSymbol, + Location, + Position, + Range, + SymbolKind +} from 'vscode-languageserver/node' +import { + isNode, + isPair, + isScalar, + Node, + Pair, + parseDocument, + Scalar, + visit +} from 'yaml' + +export const getTextDocumentLocation = ( + textDocument: TextDocument +): Location => { + const start = Position.create(0, 0) + const end = textDocument.positionAt(textDocument.getText().length - 1) + const range = Range.create(start, end) + + return Location.create(textDocument.uri, range) +} + +export const getUriLocation = (uri: string, content: string): Location => { + const textDocument = TextDocument.create(uri, 'plain/text', 0, content) + return getTextDocumentLocation(textDocument) +} + +const yamlScalarNodeToDocumentSymbols = ( + textDocument: TextDocument, + node: Scalar, + [nodeStart, valueEnd, nodeEnd]: [number, number, number] +) => { + const nodeValue = String(node.value) + + let symbolKind: SymbolKind = SymbolKind.String + + if (/\.[A-Za-z]+$/.test(nodeValue) && !nodeValue.includes(' ')) { + symbolKind = SymbolKind.File + } + + return [ + DocumentSymbol.create( + nodeValue, + undefined, + symbolKind, + Range.create( + textDocument.positionAt(nodeStart), + textDocument.positionAt(nodeEnd) + ), + Range.create( + textDocument.positionAt(nodeStart), + textDocument.positionAt(valueEnd) + ) + ) + ] +} + +const yamlNodeToDocumentSymbols = ( + textDocument: TextDocument, + node: Node | Pair, + range: [number, number, number] +): DocumentSymbol[] => { + if (isScalar(node)) { + return yamlScalarNodeToDocumentSymbols(textDocument, node, range) + } + + if (isPair(node)) { + return yamlNodeToDocumentSymbols( + textDocument, + node.value as Node | Pair, + range + ) + } + + return [] +} + +const symbolScopeAt = ( + textDocument: TextDocument, + position: Position +): DocumentSymbol[] => { + const cursorOffset: number = textDocument.offsetAt(position) + + const symbolsFound: Array = [] + + const doc = parseDocument(textDocument.getText()) + + visit(doc, (_, node) => { + if (isNode(node) && node.range) { + const range = node.range + const nodeStart = range[0] + const nodeEnd = range[2] + const isCursorInsideNode = + cursorOffset >= nodeStart && cursorOffset <= nodeEnd + + if (isCursorInsideNode) { + symbolsFound.push( + ...yamlNodeToDocumentSymbols(textDocument, node, range) + ) + } + } + }) + const symbolStack = (symbolsFound.filter(Boolean) as DocumentSymbol[]).sort( + (a, b) => { + const offA = + textDocument.offsetAt(a.range.end) - + textDocument.offsetAt(a.range.start) + const offB = + textDocument.offsetAt(b.range.end) - + textDocument.offsetAt(b.range.start) + + return offB - offA // We want the tighter fits for last, so we can just pop them + } + ) + + return [...symbolStack] +} + +export const symbolAt = ( + textDocument: TextDocument | undefined, + position: Position +): DocumentSymbol | undefined => { + if (!textDocument) { + return + } + return symbolScopeAt(textDocument, position).pop() +} From 0fef689a92987d97ca2b8c222e7ad770a68882dc Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Tue, 31 Jan 2023 16:48:22 +1100 Subject: [PATCH 2/3] rename language server instance --- languageServer/src/server.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/languageServer/src/server.ts b/languageServer/src/server.ts index af4c078266..80aaf6b66d 100644 --- a/languageServer/src/server.ts +++ b/languageServer/src/server.ts @@ -1,8 +1,8 @@ import { createConnection, ProposedFeatures } from 'vscode-languageserver/node' import { LanguageServer } from './languageServer' -const dvcLanguageServer = new LanguageServer() +const languageServer = new LanguageServer() const connection = createConnection(ProposedFeatures.all) -dvcLanguageServer.listen(connection) +languageServer.listen(connection) From 5b2cbcd9c4c753562f300bdff212205910195312 Mon Sep 17 00:00:00 2001 From: Matt Seddon Date: Tue, 31 Jan 2023 16:51:33 +1100 Subject: [PATCH 3/3] rename language server --- languageServer/src/{LanguageServer.ts => languageServer.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename languageServer/src/{LanguageServer.ts => languageServer.ts} (100%) diff --git a/languageServer/src/LanguageServer.ts b/languageServer/src/languageServer.ts similarity index 100% rename from languageServer/src/LanguageServer.ts rename to languageServer/src/languageServer.ts