5
5
6
6
import * as vscode from 'vscode'
7
7
import * as fs from 'fs-extra'
8
- import { DependencyGraph } from '../dependencyGraph/dependencyGraph'
8
+ import path = require ( 'path' )
9
9
import { BM25Document , BM25Okapi } from './rankBm25'
10
- import { isRelevant } from './editorFilesUtil'
11
10
import { ToolkitError } from '../../../shared/errors'
12
11
import { UserGroup , crossFileContextConfig , supplemetalContextFetchingTimeoutMsg } from '../../models/constants'
13
12
import { CancellationError } from '../../../shared/utilities/timeoutUtils'
14
13
import { CodeWhispererSupplementalContextItem } from './supplementalContextUtil'
15
14
import { CodeWhispererUserGroupSettings } from '../userGroupUtil'
15
+ import { isTestFile } from './codeParsingUtil'
16
+ import { getOpenFilesInWindow } from '../../../shared/utilities/editorUtilities'
17
+
18
+ type CrossFileSupportedLanguage =
19
+ | 'java'
20
+ | 'python'
21
+ | 'javascript'
22
+ | 'typescript'
23
+ | 'javascriptreact'
24
+ | 'typescriptreact'
25
+
26
+ // TODO: ugly, can we make it prettier? like we have to manually type 'java', 'javascriptreact' which is error prone
27
+ // TODO: Move to another config file or constants file
28
+ // Supported language to its corresponding file ext
29
+ const supportedLanguageToDialects : Readonly < Record < CrossFileSupportedLanguage , Set < string > > > = {
30
+ java : new Set < string > ( [ '.java' ] ) ,
31
+ python : new Set < string > ( [ '.py' ] ) ,
32
+ javascript : new Set < string > ( [ '.js' , '.jsx' ] ) ,
33
+ javascriptreact : new Set < string > ( [ '.js' , '.jsx' ] ) ,
34
+ typescript : new Set < string > ( [ '.ts' , '.tsx' ] ) ,
35
+ typescriptreact : new Set < string > ( [ '.ts' , '.tsx' ] ) ,
36
+ }
37
+
38
+ function isCrossFileSupported ( languageId : string ) : languageId is CrossFileSupportedLanguage {
39
+ return Object . keys ( supportedLanguageToDialects ) . includes ( languageId )
40
+ }
16
41
17
- const crossFileLanguageConfigs = [ 'java' ]
18
42
interface Chunk {
19
43
fileName : string
20
44
content : string
@@ -24,20 +48,21 @@ interface Chunk {
24
48
25
49
export async function fetchSupplementalContextForSrc (
26
50
editor : vscode . TextEditor ,
27
- dependencyGraph : DependencyGraph ,
28
51
cancellationToken : vscode . CancellationToken
29
52
) : Promise < CodeWhispererSupplementalContextItem [ ] | undefined > {
30
- if ( crossFileLanguageConfigs . includes ( editor . document . languageId ) === false ) {
31
- return undefined
32
- }
53
+ const shouldProceed = shouldFetchCrossFileContext (
54
+ editor . document . languageId ,
55
+ CodeWhispererUserGroupSettings . instance . userGroup
56
+ )
33
57
34
- if ( CodeWhispererUserGroupSettings . instance . userGroup !== UserGroup . CrossFile ) {
35
- return [ ]
58
+ if ( ! shouldProceed ) {
59
+ return shouldProceed === undefined ? undefined : [ ]
36
60
}
37
61
38
62
// Step 1: Get relevant cross files to refer
39
- const relevantCrossFilePaths = await getRelevantCrossFiles ( editor , dependencyGraph )
63
+ const relevantCrossFilePaths = await getCrossFileCandidates ( editor )
40
64
throwIfCancelled ( cancellationToken )
65
+
41
66
// Step 2: Split files to chunks with upper bound on chunkCount
42
67
// We restrict the total number of chunks to improve on latency.
43
68
// Chunk linking is required as we want to pass the next chunk value for matched chunk.
@@ -110,6 +135,27 @@ function getInputChunk(editor: vscode.TextEditor, chunkSize: number) {
110
135
return inputChunk
111
136
}
112
137
138
+ /**
139
+ * Util to decide if we need to fetch crossfile context since CodeWhisperer CrossFile Context feature is gated by userGroup and language level
140
+ * @param languageId: VSCode language Identifier
141
+ * @param userGroup: CodeWhisperer user group settings, refer to userGroupUtil.ts
142
+ * @returns specifically returning undefined if the langueage is not supported,
143
+ * otherwise true/false depending on if the language is fully supported or not belonging to the user group
144
+ */
145
+ function shouldFetchCrossFileContext ( languageId : string , userGroup : UserGroup ) : boolean | undefined {
146
+ if ( ! isCrossFileSupported ( languageId ) ) {
147
+ return undefined
148
+ }
149
+
150
+ if ( languageId === 'java' ) {
151
+ return true
152
+ } else if ( supportedLanguageToDialects [ languageId ] && userGroup === UserGroup . CrossFile ) {
153
+ return true
154
+ } else {
155
+ return false
156
+ }
157
+ }
158
+
113
159
/**
114
160
* This linking is required from science experimentations to pass the next contnet chunk
115
161
* when a given chunk context passes the match in BM25.
@@ -158,29 +204,27 @@ function splitFileToChunks(filePath: string, chunkSize: number): Chunk[] {
158
204
* This function will return relevant cross files for the given editor file
159
205
* by referencing open files, imported files and same package files.
160
206
*/
161
- async function getRelevantCrossFiles ( editor : vscode . TextEditor , dependencyGraph : DependencyGraph ) : Promise < string [ ] > {
162
- return getOpenFilesInWindow ( ) . filter ( file => {
163
- return isRelevant ( editor . document . fileName , file , editor . document . languageId )
207
+ async function getCrossFileCandidates ( editor : vscode . TextEditor ) : Promise < string [ ] > {
208
+ const targetFile = editor . document . uri . fsPath
209
+ const language = editor . document . languageId as CrossFileSupportedLanguage
210
+ const dialects = supportedLanguageToDialects [ language ]
211
+
212
+ /**
213
+ * Consider a file which
214
+ * 1. is different from the target
215
+ * 2. has the same file extension or it's one of the dialect of target file (e.g .js vs. .jsx)
216
+ * 3. is not a test file
217
+ */
218
+ return await getOpenFilesInWindow ( async candidateFile => {
219
+ return (
220
+ targetFile !== candidateFile &&
221
+ ( path . extname ( targetFile ) === path . extname ( candidateFile ) ||
222
+ ( dialects && dialects . has ( path . extname ( candidateFile ) ) ) ) &&
223
+ ! ( await isTestFile ( candidateFile , { languageId : language } ) )
224
+ )
164
225
} )
165
226
}
166
227
167
- function getOpenFilesInWindow ( ) : string [ ] {
168
- const filesOpenedInEditor : string [ ] = [ ]
169
-
170
- try {
171
- const tabArrays = vscode . window . tabGroups . all
172
- tabArrays . forEach ( tabArray => {
173
- tabArray . tabs . forEach ( tab => {
174
- filesOpenedInEditor . push ( ( tab . input as any ) . uri . path )
175
- } )
176
- } )
177
- } catch ( e ) {
178
- // Older versions of VSC do not have the tab API
179
- }
180
-
181
- return filesOpenedInEditor
182
- }
183
-
184
228
function throwIfCancelled ( token : vscode . CancellationToken ) : void | never {
185
229
if ( token . isCancellationRequested ) {
186
230
throw new ToolkitError ( supplemetalContextFetchingTimeoutMsg , { cause : new CancellationError ( 'timeout' ) } )
0 commit comments