Skip to content

Commit 5fd3174

Browse files
authored
fix: add support for determing workspace folder with root uri/path on initialize (#1210)
LSP lifecycle for VS extension is controlled by Visual Studio. It currently does not set the workspaceFolder as part of the Initialize Params of the LSP initialize request, however it does send this information via rootUri and rootPath properties. The workspaceFolders is utilized for the local project context indexing and so in order to support that, this change extends logic to determine the workspaceFolder to use the root properties when workspaceFolders is not set.
1 parent 18b2a18 commit 5fd3174

File tree

3 files changed

+160
-1
lines changed

3 files changed

+160
-1
lines changed

server/aws-lsp-codewhisperer/src/language-server/localProjectContext/localProjectContextServer.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { TelemetryService } from '../../shared/telemetry/telemetryService'
44
import { LocalProjectContextController } from '../../shared/localProjectContextController'
55
import { languageByExtension } from '../../shared/languageDetection'
66
import { AmazonQWorkspaceConfig } from '../../shared/amazonQServiceManager/configurationUtils'
7+
import { getWorkspaceFolders } from '../../shared/initializeUtils'
78
import { URI } from 'vscode-uri'
89

910
export const LocalProjectContextServer =
@@ -16,9 +17,10 @@ export const LocalProjectContextServer =
1617
let localProjectContextEnabled: boolean = false
1718

1819
lsp.addInitializer((params: InitializeParams) => {
20+
const workspaceFolders = getWorkspaceFolders(logging, params)
1921
localProjectContextController = new LocalProjectContextController(
2022
params.clientInfo?.name ?? 'unknown',
21-
params.workspaceFolders ?? [],
23+
workspaceFolders,
2224
logging
2325
)
2426

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { InitializeParams, WorkspaceFolder } from '@aws/language-server-runtimes/protocol'
2+
import { URI } from 'vscode-uri'
3+
import * as path from 'path'
4+
import assert = require('assert')
5+
import sinon = require('sinon')
6+
import { TestFeatures } from '@aws/language-server-runtimes/testing'
7+
import { Features } from '@aws/language-server-runtimes/server-interface/server'
8+
import { getWorkspaceFolders } from './initializeUtils'
9+
10+
describe('initializeUtils', () => {
11+
let logging: Features['logging']
12+
13+
before(function () {
14+
logging = new TestFeatures().logging
15+
})
16+
17+
describe('getWorkspaceFolders', () => {
18+
const sampleWorkspaceUri = 'file:///path/to/folder'
19+
const sampleWorkspaceName = 'folder'
20+
const sampleWorkspaceFolder: WorkspaceFolder = {
21+
name: sampleWorkspaceName,
22+
uri: sampleWorkspaceUri,
23+
}
24+
25+
const createParams = (params: Partial<InitializeParams>) => params as InitializeParams
26+
27+
it('should return workspaceFolders when provided', () => {
28+
const workspaceFolders: WorkspaceFolder[] = [
29+
sampleWorkspaceFolder,
30+
{ name: 'folder2', uri: 'file:///path/to/folder2' },
31+
]
32+
const params = createParams({ workspaceFolders })
33+
const result = getWorkspaceFolders(logging, params)
34+
35+
assert.deepStrictEqual(result, workspaceFolders)
36+
})
37+
38+
describe('should create workspace folder from rootUri when workspaceFolders is not provided', () => {
39+
const invalidWorkspaceFolderCases = [
40+
['no workspaceFolder param', { rootUri: sampleWorkspaceUri }],
41+
['empty workspaceFolder param params', { WorkspaceFolders: [], rootUri: sampleWorkspaceUri }],
42+
] as const
43+
44+
invalidWorkspaceFolderCases.forEach(([name, input]) => {
45+
it(`should return root uri for ${name}`, () => {
46+
const params = createParams(input)
47+
const result = getWorkspaceFolders(logging, params)
48+
assert.deepStrictEqual(result, [sampleWorkspaceFolder])
49+
})
50+
})
51+
const params = createParams({ rootUri: sampleWorkspaceUri })
52+
const result = getWorkspaceFolders(logging, params)
53+
54+
assert.deepStrictEqual(result, [sampleWorkspaceFolder])
55+
})
56+
57+
it('should create workspace folder from rootPath when neither workspaceFolders nor rootUri is provided', () => {
58+
const rootPath = '/path/to/folder'
59+
const params = createParams({ rootPath: rootPath })
60+
const result = getWorkspaceFolders(logging, params)
61+
62+
assert.deepStrictEqual(result, [sampleWorkspaceFolder])
63+
})
64+
65+
it('should use uri as folder name when URI basename is empty', () => {
66+
const rootUri = 'file:///'
67+
const params = createParams({ rootUri })
68+
const result = getWorkspaceFolders(logging, params)
69+
70+
assert.deepStrictEqual(result, [{ name: rootUri, uri: rootUri }])
71+
})
72+
73+
it('should handle Windows paths correctly', () => {
74+
const rootPath = 'C:\\Users\\test\\folder'
75+
const pathUri = URI.parse(rootPath).toString()
76+
const params = createParams({ rootPath })
77+
78+
const result = getWorkspaceFolders(logging, params)
79+
80+
const expectedName = path.basename(URI.parse(pathUri).fsPath)
81+
assert.deepStrictEqual(result, [{ name: expectedName, uri: pathUri }])
82+
})
83+
84+
it('should handle rootUri with special characters', () => {
85+
const rootUri = 'file:///path/to/special%20project'
86+
const decodedPath = URI.parse(rootUri).path
87+
const folderName = path.basename(decodedPath)
88+
89+
const params = createParams({ rootUri })
90+
const result = getWorkspaceFolders(logging, params)
91+
92+
assert.deepStrictEqual(result, [{ name: folderName, uri: rootUri }])
93+
assert.equal('special project', result[0].name)
94+
})
95+
96+
describe('should return empty workspaceFolder array', () => {
97+
const emptyArrayCases = [
98+
['no params', {}],
99+
['undefined params', undefined as unknown as InitializeParams],
100+
['null params', null as unknown as InitializeParams],
101+
['empty workspaceFolders', { workspaceFolders: [] }],
102+
] as const
103+
104+
emptyArrayCases.forEach(([name, input]) => {
105+
it(`should return empty array for ${name}`, () => {
106+
const result = getWorkspaceFolders(logging, input as InitializeParams)
107+
assert.equal(result.length, 0)
108+
})
109+
})
110+
})
111+
112+
it('should handle errors and return empty array', () => {
113+
const basenameStub = sinon.stub(path, 'basename').throws(new Error('Test error'))
114+
115+
try {
116+
const badParams = createParams({ rootUri: sampleWorkspaceUri })
117+
const result = getWorkspaceFolders(logging, badParams)
118+
119+
assert.deepStrictEqual(result, [])
120+
} finally {
121+
basenameStub.restore()
122+
}
123+
})
124+
})
125+
})
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { InitializeParams, WorkspaceFolder } from '@aws/language-server-runtimes/protocol'
2+
import { Logging } from '@aws/language-server-runtimes/server-interface'
3+
import * as path from 'path'
4+
import { URI } from 'vscode-uri'
5+
6+
export function getWorkspaceFolders(logging: Logging, params?: InitializeParams): WorkspaceFolder[] {
7+
if (!params) {
8+
return []
9+
}
10+
11+
if (params.workspaceFolders && params.workspaceFolders.length > 0) {
12+
return params.workspaceFolders
13+
}
14+
try {
15+
const getFolderName = (parsedUri: URI) => path.basename(parsedUri.fsPath) || parsedUri.toString()
16+
17+
if (params.rootUri) {
18+
const parsedUri = URI.parse(params.rootUri)
19+
const folderName = getFolderName(parsedUri)
20+
return [{ name: folderName, uri: params.rootUri }]
21+
}
22+
if (params.rootPath) {
23+
const parsedUri = URI.parse(params.rootPath)
24+
const folderName = getFolderName(parsedUri)
25+
return [{ name: folderName, uri: parsedUri.toString() }]
26+
}
27+
return []
28+
} catch (error) {
29+
logging.error(`Error occurred when determining workspace folders: ${error}`)
30+
return []
31+
}
32+
}

0 commit comments

Comments
 (0)