Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement simple onDefinition feature in LSP #3175

Merged
merged 13 commits into from
Jan 31, 2023
3 changes: 3 additions & 0 deletions extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +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'

export class Extension extends Disposable {
protected readonly internalCommands: InternalCommands
Expand Down Expand Up @@ -255,6 +256,8 @@ export class Extension extends Disposable {

void showWalkthroughOnFirstUse(env.isNewAppInstall)
this.dispose.track(recommendRedHatExtensionOnce())

this.dispose.track(new LanguageClientWrapper())
}

public async initialize() {
Expand Down
44 changes: 19 additions & 25 deletions extension/src/lspClient/languageClient.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { extname } from 'path'
import { Uri, workspace } from 'vscode'
import {
LanguageClient,
LanguageClientOptions,
ServerOptions,
TransportKind
} from 'vscode-languageclient/node'
import { documentSelector, serverModule } from 'dvc-vscode-lsp'
import { readFileSync } from 'fs-extra'
import { documentSelector, serverModule } from 'dvc-vscode-lsp'
import { Disposable } from '../class/dispose'
import { findFiles } from '../fileSystem/workspace'

export class LanguageClientWrapper extends Disposable {
private client: LanguageClient
Expand All @@ -35,33 +35,27 @@ export class LanguageClientWrapper extends Disposable {
)
)

// Start the client. This will also launch the server
void this.start()
}

async start() {
await this.client.start()

const files = await findFiles('**/*.{yaml,json,py,toml}', '.??*')

const textDocuments = files.map(filePath => {
const uri = Uri.file(filePath).toString()
const languageId = filePath.endsWith('yaml') ? 'yaml' : 'json'
const text = readFileSync(filePath, 'utf8')

return {
languageId,
text,
uri,
version: 0
this.client.onRequest('isFile', (path: string) => {
const uri = Uri.file(path).toString()
const languageId = extname(path) === '.py' ? 'python' : 'other'
try {
const text = readFileSync(path, 'utf8')
return {
languageId,
text,
uri,
version: 0
}
} catch {
return null
}
})

await this.client.sendRequest('initialTextDocuments', {
textDocuments
})
void this.start()
}

return this
start() {
return this.client.start()
}

stop() {
Expand Down
74 changes: 35 additions & 39 deletions languageServer/src/LanguageServer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { dirname, join } from 'path'
import {
TextDocuments,
InitializeResult,
Expand All @@ -11,15 +12,14 @@ import {
Position,
Range,
DocumentSymbol,
TextDocumentItem
Connection
} from 'vscode-languageserver/node'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
import { TextDocumentWrapper } from './TextDocumentWrapper'

export class LanguageServer {
private documentsKnownToEditor!: TextDocuments<TextDocument>
private documentsFromDvcClient: TextDocumentWrapper[] = []

public listen(connection: _Connection) {
this.documentsKnownToEditor = new TextDocuments(TextDocument)
Expand All @@ -31,60 +31,28 @@ export class LanguageServer {
return null
}

return this.onDefinition(params)
return this.onDefinition(params, connection)
})

connection.onRequest(
'initialTextDocuments',
(params: { textDocuments: TextDocumentItem[] }) => {
this.documentsFromDvcClient = params.textDocuments.map(
({ uri, languageId, version, text: content }) => {
const textDocument = TextDocument.create(
uri,
languageId,
version,
content
)

return this.wrap(textDocument)
}
)
}
)

this.documentsKnownToEditor.listen(connection)

connection.listen()
}

private getAllDocuments() {
const openDocuments = this.documentsKnownToEditor.all()
const acc: TextDocumentWrapper[] = openDocuments.map(doc => this.wrap(doc))

for (const textDocument of this.documentsFromDvcClient) {
const userAlreadyOpenedIt = this.documentsKnownToEditor.get(
textDocument.uri
)

if (!userAlreadyOpenedIt) {
acc.push(textDocument)
}
}

return acc
return openDocuments.map(doc => this.wrap(doc))
}

private getDvcTextDocument(
params: TextDocumentPositionParams | CodeActionParams
) {
const uri = params.textDocument.uri

const doc = this.documentsKnownToEditor.get(uri)

if (!doc) {
const alternative = this.documentsFromDvcClient.find(
txtDoc => txtDoc.uri === uri
)
return alternative ?? null
return null
}

return this.wrap(doc)
Expand Down Expand Up @@ -132,7 +100,8 @@ export class LanguageServer {
return locationsAccumulator
}

private onDefinition(params: DefinitionParams) {
// eslint-disable-next-line sonarjs/cognitive-complexity
private async onDefinition(params: DefinitionParams, connection: Connection) {
mattseddon marked this conversation as resolved.
Show resolved Hide resolved
mattseddon marked this conversation as resolved.
Show resolved Hide resolved
mattseddon marked this conversation as resolved.
Show resolved Hide resolved
mattseddon marked this conversation as resolved.
Show resolved Hide resolved
mattseddon marked this conversation as resolved.
Show resolved Hide resolved
const document = this.getDvcTextDocument(params)
const symbolUnderCursor = document?.symbolAt(params.position)

Expand All @@ -147,6 +116,33 @@ export class LanguageServer {

locationsAccumulator.push(...fileLocations)

for (const possibleFile of symbolUnderCursor.name.split(' ')) {
const possiblePath = join(
dirname(document.uri).replace('file:///', ''),
possibleFile
)
const isFile = await connection.sendRequest<{
languageId: string
text: string
uri: URI
version: number
} | null>('isFile', possiblePath)
if (isFile) {
const { languageId, text, version } = isFile
const doc = TextDocument.create(
possiblePath,
languageId,
version,
text
)
const start = Position.create(0, 0)
const end = doc.positionAt(doc.getText().length - 1)
const range = Range.create(start, end)

locationsAccumulator.push(Location.create(possiblePath, range))
}
}

const locationsFromOtherDocuments = this.getLocationsFromOtherDocuments(
symbolUnderCursor,
allDocs
Expand Down
29 changes: 1 addition & 28 deletions languageServer/src/test/definitions.test.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { Position, Range } from 'vscode-languageserver/node'
import {
foreach_dvc_yaml,
params_dvc_yaml,
vars_dvc_yaml
} from './fixtures/examples/valid'
import { foreach_dvc_yaml, params_dvc_yaml } from './fixtures/examples/valid'
import { params } from './fixtures/params'
import { requestDefinitions } from './utils/requestDefinitions'
import { openTheseFilesAndNotifyServer } from './utils/openTheseFilesAndNotifyServer'
import {
disposeTestConnections,
setupTestConnections
} from './utils/setup-test-connections'
import { sendTheseFilesToServer } from './utils/sendTheseFilesToServer'

describe('textDocument/definitions', () => {
beforeEach(() => {
Expand Down Expand Up @@ -74,26 +69,4 @@ describe('textDocument/definitions', () => {
uri: 'file:///dvc.yaml'
})
})

it('should provide a single location that points to the top of the file path symbol', async () => {
const [dvcYaml] = await sendTheseFilesToServer([
{
languageId: 'yaml',
mockContents: vars_dvc_yaml,
mockPath: 'dvc.yaml'
},
{
languageId: 'json',
mockContents: '',
mockPath: 'params.json'
}
])
const response = await requestDefinitions(dvcYaml, 'params.json')

expect(response).toBeTruthy()
expect(response).toStrictEqual({
range: Range.create(Position.create(0, 0), Position.create(0, 0)),
uri: 'file:///params.json'
})
})
})
29 changes: 0 additions & 29 deletions languageServer/src/test/fixtures/examples/valid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,32 +151,3 @@ stages:
- output

`
export const vars_dvc_yaml = `
vars:
- custom_params.yaml
- models:
us:
threshold: 10
- desc: 'Reusable description'
- params.json
- myvar: 'value'
- config/myapp.yaml
- params.json:clean,feats

stages:
test_vars:
vars:
- params.json:build
- model:
filename: 'model-us.hdf5'
cmd: echo hello world
test_foreach_vars:
foreach: \${vars}
do:
vars:
- params.json:build
- model:
filename: 'model-us.hdf5'
cmd: echo \${item} \${model.filename}

`
27 changes: 0 additions & 27 deletions languageServer/src/test/utils/sendTheseFilesToServer.ts

This file was deleted.

1 change: 1 addition & 0 deletions languageServer/src/test/utils/setup-test-connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export const setupTestConnections = () => {

server = createConnection(up, down)
client = createConnection(down, up)
client.onRequest('isFile', () => null)

dvcLanguageService.listen(server)
client.listen()
Expand Down