Skip to content

Commit 7b1943f

Browse files
authored
feat(codewhisperer): consider filedistance for crossfile context #3679
Before: Crossfile file candidates are fetched by opened tabs without any sorting, e.g. files will be returned based on theirs orders in the IDE tabs (from the left to the right) After: Crossfile file candidates fetched will be sorted by their file distance ascendingly. Files with smaller file distance will be prioritized than others.
1 parent f9ca3d4 commit 7b1943f

File tree

3 files changed

+106
-4
lines changed

3 files changed

+106
-4
lines changed

src/codewhisperer/util/supplementalContext/crossFileContextUtil.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { CancellationError } from '../../../shared/utilities/timeoutUtils'
1313
import { CodeWhispererSupplementalContextItem } from './supplementalContextUtil'
1414
import { CodeWhispererUserGroupSettings } from '../userGroupUtil'
1515
import { isTestFile } from './codeParsingUtil'
16+
import { getFileDistance } from '../../../shared/filesystemUtilities'
1617
import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities'
1718

1819
type CrossFileSupportedLanguage =
@@ -201,10 +202,10 @@ function splitFileToChunks(filePath: string, chunkSize: number): Chunk[] {
201202
}
202203

203204
/**
204-
* This function will return relevant cross files for the given editor file
205+
* This function will return relevant cross files sorted by file distance for the given editor file
205206
* by referencing open files, imported files and same package files.
206207
*/
207-
async function getCrossFileCandidates(editor: vscode.TextEditor): Promise<string[]> {
208+
export async function getCrossFileCandidates(editor: vscode.TextEditor): Promise<string[]> {
208209
const targetFile = editor.document.uri.fsPath
209210
const language = editor.document.languageId as CrossFileSupportedLanguage
210211
const dialects = supportedLanguageToDialects[language]
@@ -215,14 +216,28 @@ async function getCrossFileCandidates(editor: vscode.TextEditor): Promise<string
215216
* 2. has the same file extension or it's one of the dialect of target file (e.g .js vs. .jsx)
216217
* 3. is not a test file
217218
*/
218-
return await getOpenFilesInWindow(async candidateFile => {
219+
const unsortedCandidates = await getOpenFilesInWindow(async candidateFile => {
219220
return (
220221
targetFile !== candidateFile &&
221222
(path.extname(targetFile) === path.extname(candidateFile) ||
222223
(dialects && dialects.has(path.extname(candidateFile)))) &&
223224
!(await isTestFile(candidateFile, { languageId: language }))
224225
)
225226
})
227+
228+
return unsortedCandidates
229+
.map(candidate => {
230+
return {
231+
file: candidate,
232+
fileDistance: getFileDistance(targetFile, candidate),
233+
}
234+
})
235+
.sort((file1, file2) => {
236+
return file1.fileDistance - file2.fileDistance
237+
})
238+
.map(fileToDistance => {
239+
return fileToDistance.file
240+
})
226241
}
227242

228243
function throwIfCancelled(token: vscode.CancellationToken): void | never {

src/test/codewhisperer/util/crossFileContextUtil.test.ts

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ import * as crossFile from '../../../codewhisperer/util/supplementalContext/cros
1111
import { createMockTextEditor } from '../testUtil'
1212
import { CodeWhispererUserGroupSettings } from '../../../codewhisperer/util/userGroupUtil'
1313
import { UserGroup } from '../../../codewhisperer/models/constants'
14-
import { assertTabCount, closeAllEditors, createTestWorkspaceFolder, openATextEditorWithText } from '../../testUtil'
14+
import {
15+
assertTabCount,
16+
closeAllEditors,
17+
createTestWorkspaceFolder,
18+
openATextEditorWithText,
19+
shuffleList,
20+
} from '../../testUtil'
21+
import { areEqual, normalize } from '../../../shared/utilities/pathUtils'
22+
import * as path from 'path'
1523
import { getMinVscodeVersion } from '../../../shared/vscode/env'
1624

1725
const userGroupSettings = CodeWhispererUserGroupSettings.instance
@@ -49,6 +57,71 @@ describe('crossFileContextUtil', function () {
4957
})
5058
})
5159

60+
describe('getCrossFileCandidate', function () {
61+
before(async function () {
62+
this.timeout(60000)
63+
})
64+
65+
beforeEach(async function () {
66+
tempFolder = (await createTestWorkspaceFolder()).uri.fsPath
67+
})
68+
69+
afterEach(async function () {
70+
await closeAllEditors()
71+
})
72+
73+
it('should return opened files, exclude test files and sorted ascendingly by file distance', async function () {
74+
if (!shouldRunTheTest()) {
75+
this.skip()
76+
}
77+
78+
const targetFile = path.join('src', 'service', 'microService', 'CodeWhispererFileContextProvider.java')
79+
const fileWithDistance3 = path.join('src', 'service', 'CodewhispererRecommendationService.java')
80+
const fileWithDistance5 = path.join('src', 'util', 'CodeWhispererConstants.java')
81+
const fileWithDistance6 = path.join('src', 'ui', 'popup', 'CodeWhispererPopupManager.java')
82+
const fileWithDistance7 = path.join('src', 'ui', 'popup', 'components', 'CodeWhispererPopup.java')
83+
const fileWithDistance8 = path.join(
84+
'src',
85+
'ui',
86+
'popup',
87+
'components',
88+
'actions',
89+
'AcceptRecommendationAction.java'
90+
)
91+
const testFile1 = path.join('test', 'service', 'CodeWhispererFileContextProviderTest.java')
92+
const testFile2 = path.join('test', 'ui', 'CodeWhispererPopupManagerTest.java')
93+
94+
const expectedFilePaths = [
95+
fileWithDistance3,
96+
fileWithDistance5,
97+
fileWithDistance6,
98+
fileWithDistance7,
99+
fileWithDistance8,
100+
]
101+
102+
const shuffledFilePaths = shuffleList(expectedFilePaths)
103+
104+
for (const filePath of shuffledFilePaths) {
105+
await openATextEditorWithText('', filePath, tempFolder, { preview: false })
106+
}
107+
108+
await openATextEditorWithText('', testFile1, tempFolder, { preview: false })
109+
await openATextEditorWithText('', testFile2, tempFolder, { preview: false })
110+
const editor = await openATextEditorWithText('', targetFile, tempFolder, { preview: false })
111+
112+
await assertTabCount(shuffledFilePaths.length + 3)
113+
114+
const actual = await crossFile.getCrossFileCandidates(editor)
115+
116+
assert.ok(actual.length === 5)
117+
actual.forEach((actualFile, index) => {
118+
const expectedFile = path.join(tempFolder, expectedFilePaths[index])
119+
assert.strictEqual(normalize(expectedFile), normalize(actualFile))
120+
assert.ok(areEqual(tempFolder, actualFile, expectedFile))
121+
})
122+
})
123+
})
124+
52125
describe('partial support - control group', function () {
53126
before(async function () {
54127
this.timeout(60000)

src/test/testUtil.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,3 +388,17 @@ export function captureEventOnce<T>(event: vscode.Event<T>, timeout?: number): P
388388
}
389389
})
390390
}
391+
392+
/**
393+
* Shuffle a list, Fisher-Yates Sorting Algorithm
394+
*/
395+
export function shuffleList<T>(list: T[]): T[] {
396+
const shuffledList = [...list]
397+
398+
for (let i = shuffledList.length - 1; i > 0; i--) {
399+
const j = Math.floor(Math.random() * (i + 1))
400+
;[shuffledList[i], shuffledList[j]] = [shuffledList[j], shuffledList[i]]
401+
}
402+
403+
return shuffledList
404+
}

0 commit comments

Comments
 (0)