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

Remove read file contents request sent to extension by language server (use is file check) #3198

Merged
merged 3 commits into from
Feb 2, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 8 additions & 15 deletions extension/src/fileSystem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ import { delay } from '../util/time'

export const exists = (path: string): boolean => existsSync(path)

export const isDirectory = (path: string): boolean => {
const checkStats = (path: string, check: 'isDirectory' | 'isFile'): boolean => {
try {
return lstatSync(path).isDirectory()
return lstatSync(path)[check]()
} catch {
return false
}
}

export const isDirectory = (path: string): boolean =>
checkStats(path, 'isDirectory')

export const isFile = (path: string): boolean => checkStats(path, 'isFile')

export const getModifiedTime = (path: string): number =>
lstatSync(path).mtime.getTime()

Expand Down Expand Up @@ -75,7 +80,7 @@ export const findAbsoluteDvcRootPath = async (
const findDotGitDir = (gitRoot: string) => {
const dotGitPath = join(gitRoot, gitPath.DOT_GIT)

const isSubmodule = lstatSync(dotGitPath).isFile()
const isSubmodule = isFile(dotGitPath)
if (isSubmodule) {
const dotGitAsFileContent = readFileSync(dotGitPath, 'utf8')
const gitDirPrefix = 'gitdir: '
Expand Down Expand Up @@ -204,15 +209,3 @@ export const getBinDisplayText = (
? '.' + sep + relative(workspaceRoot, path)
: path
}

export const readFileContents = (
uriString: string
): { contents: string } | null => {
try {
const uri = Uri.parse(uriString)
if (!isDirectory(uri.fsPath)) {
return { contents: readFileSync(uri.fsPath, 'utf8') }
}
} catch {}
return null
}
13 changes: 10 additions & 3 deletions extension/src/languageClient/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { workspace } from 'vscode'
import { Uri, workspace } from 'vscode'
import {
LanguageClient as Client,
LanguageClientOptions,
Expand All @@ -7,7 +7,7 @@ import {
} from 'vscode-languageclient/node'
import { documentSelector, serverModule } from 'dvc-vscode-lsp'
import { Disposable } from '../class/dispose'
import { readFileContents } from '../fileSystem'
import { isFile } from '../fileSystem'

export class LanguageClient extends Disposable {
private client: Client
Expand All @@ -32,7 +32,14 @@ export class LanguageClient extends Disposable {
)

this.dispose.track(
this.client.onRequest('readFileContents', readFileContents)
this.client.onRequest('isFile', (uri: string) => {
try {
const path = Uri.parse(uri).fsPath
return { isFile: isFile(path) }
} catch {
return null
}
})
)

void this.client.start()
Expand Down
31 changes: 20 additions & 11 deletions extension/src/test/suite/fileSystem/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { join, resolve } from 'path'
import process from 'process'
import { Uri } from 'vscode'
import { afterEach, beforeEach, describe, it, suite } from 'mocha'
import { expect } from 'chai'
import { Disposable } from '@hediet/std/disposable'
Expand All @@ -11,7 +10,8 @@ import {
checkSignalFile,
exists,
getGitPath,
readFileContents
isDirectory,
isFile
} from '../../../fileSystem'
import { gitPath } from '../../../cli/git/constants'
import { GitReader } from '../../../cli/git/reader'
Expand Down Expand Up @@ -100,19 +100,28 @@ suite('File System Test Suite', () => {
})
})

describe('readFileContents', () => {
it('should read the contents of a file when it exists', () => {
const uriString = Uri.file(join(dvcDemoPath, 'train.py')).toString()
describe('isFile', () => {
it('should return true when the path is a file', () => {
const path = standardizePath(join(dvcDemoPath, 'train.py'))

const file = readFileContents(uriString)
expect(file?.contents).to.contain('main()')
const result = isFile(path)
expect(result).to.be.true
})

it('should return null when the file cannot be found', () => {
const uriString = 'file:///some/fun/file.txt'
it('should return false when the file cannot be found', () => {
const path = standardizePath(join('some', 'fun', 'file.txt'))

const contents = readFileContents(uriString)
expect(contents).to.be.null
const result = isFile(path)
expect(result).to.be.false
})

it('should return false for a directory', () => {
const path = standardizePath(join(dvcDemoPath, 'training'))

expect(isDirectory(path)).to.be.true

const result = isFile(path)
expect(result).to.be.false
})
})
})
18 changes: 7 additions & 11 deletions languageServer/src/languageServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@ import {
} from 'vscode-languageserver/node'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { URI } from 'vscode-uri'
import {
getTextDocumentLocation,
getUriLocation,
symbolAt
} from './textDocument'
import { getLocationToOpen, symbolAt } from './textDocument'

export class LanguageServer {
private documentsKnownToEditor!: TextDocuments<TextDocument>
Expand Down Expand Up @@ -58,7 +54,7 @@ export class LanguageServer {
.all()
.filter(doc => URI.parse(doc.uri).fsPath.endsWith(filePath))

return matchingFiles.map(doc => getTextDocumentLocation(doc))
return matchingFiles.map(doc => getLocationToOpen(doc.uri))
}

private async onDefinition(params: DefinitionParams, connection: Connection) {
Expand Down Expand Up @@ -98,11 +94,11 @@ export class LanguageServer {
const possiblePath = URI.parse(
[dirname(document.uri), possibleFile].join('/')
).toString()
const file = await connection.sendRequest<{
contents: string
} | null>('readFileContents', possiblePath)
if (file) {
const location = getUriLocation(possiblePath, file.contents)
const result = await connection.sendRequest<{
isFile: boolean
} | null>('isFile', possiblePath)
if (result?.isFile) {
const location = getLocationToOpen(possiblePath)
fileLocations.push(location)
}
}
Expand Down
7 changes: 3 additions & 4 deletions languageServer/src/test/definitions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import {
train_dvc_yaml
} from './fixtures/examples/valid'
import { params } from './fixtures/params'
import { train } from './fixtures/python'
import { requestDefinitions } from './utils/requestDefinitions'
import { openTheseFilesAndNotifyServer } from './utils/openTheseFilesAndNotifyServer'
import {
Expand Down Expand Up @@ -58,7 +57,7 @@ describe('textDocument/definitions', () => {

expect(response).toBeTruthy()
expect(response).toStrictEqual({
range: Range.create(Position.create(0, 0), Position.create(5, 9)),
range: Range.create(Position.create(0, 0), Position.create(0, 0)),
uri: 'file:///params.yaml'
})
})
Expand All @@ -68,7 +67,7 @@ describe('textDocument/definitions', () => {

mockedReadFileContents.mockImplementation(path => {
if (path === trainUri) {
return { contents: train }
return { isFile: true }
}
return null
})
Expand All @@ -89,7 +88,7 @@ describe('textDocument/definitions', () => {

expect(response).toBeTruthy()
expect(response).toStrictEqual({
range: Range.create(Position.create(0, 0), Position.create(7, 13)),
range: Range.create(Position.create(0, 0), Position.create(0, 0)),
uri: trainUri
})
})
Expand Down
2 changes: 1 addition & 1 deletion languageServer/src/test/utils/setup-test-connections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const setupTestConnections = (

server = createConnection(up, down)
client = createConnection(down, up)
client.onRequest('readFileContents', mockedReadFileContents)
client.onRequest('isFile', mockedReadFileContents)

dvcLanguageService.listen(server)
client.listen()
Expand Down
16 changes: 4 additions & 12 deletions languageServer/src/textDocument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,11 @@ import {
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 getLocationToOpen = (uri: string): Location => {
const position = Position.create(0, 0)
const range = Range.create(position, position)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[F] Turns out that we can open the file using an empty range (previously this didn't work or I had done the wrong thing to test). This means we don't need the file contents at all (for now). We can simply check whether or not the URI relates to a file. If it does then we can create a location with the URI string and an empty range. Passing this back to the client will open the file. If there are multiple locations (files) then the user will be prompted which one they want to open. That's the reason that we can't just ask the extension to open the file without first returning information to the server.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also have to check whether or not the file is a file on the extension side because the LSP might not be running in a context that can access the file system (e.g Codespaces)


export const getUriLocation = (uri: string, content: string): Location => {
const textDocument = TextDocument.create(uri, 'plain/text', 0, content)
return getTextDocumentLocation(textDocument)
return Location.create(uri, range)
}

const yamlScalarNodeToDocumentSymbols = (
Expand Down