Skip to content

Commit 936fc4a

Browse files
authored
Merge pull request #89 from zardoy/develop
2 parents 14e13b0 + bc0d31a commit 936fc4a

File tree

6 files changed

+103
-18
lines changed

6 files changed

+103
-18
lines changed

src/apiCommands.ts

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,53 @@ import { passthroughExposedApiCommands, TriggerCharacterCommand } from '../types
44
import { sendCommand } from './sendCommand'
55

66
export default () => {
7-
const sharedRequest = (type: TriggerCharacterCommand, { offset, relativeOffset = 0 }: RequestOptions) => {
7+
/** @unique */
8+
const cacheableCommands: Set<(typeof passthroughExposedApiCommands)[number]> = new Set(['getNodePath', 'getSpanOfEnclosingComment', 'getNodeAtPosition'])
9+
const operationsCache = new Map<string, { key: string; data }>()
10+
const sharedRequest = async (type: TriggerCharacterCommand, { offset, relativeOffset = 0, document, position }: RequestOptions) => {
11+
if (position && offset) throw new Error('Only position or offset parameter can be provided')
12+
if (document && !offset && !position) throw new Error('When custom document is provided, offset or position must be provided')
13+
814
const { activeTextEditor } = vscode.window
9-
if (!activeTextEditor) return
10-
const { document, selection } = activeTextEditor
11-
offset ??= document.offsetAt(selection.active) + relativeOffset
12-
return sendCommand(type, { document, position: document.positionAt(offset) })
15+
document ??= activeTextEditor?.document
16+
if (!document) return
17+
if (!position) offset ??= document.offsetAt(activeTextEditor!.selection.active) + relativeOffset
18+
const requestOffset = offset ?? document.offsetAt(position!)
19+
const requestPos = position ?? document.positionAt(offset!)
20+
const getData = async () => sendCommand(type, { document: document!, position: requestPos })
21+
if (cacheableCommands.has(type as any)) {
22+
const cacheEntry = operationsCache.get(type)
23+
const operationKey = `${document.uri.toString()}:${document.version}:${requestOffset}`
24+
if (cacheEntry?.key === operationKey) {
25+
return cacheEntry.data
26+
}
27+
28+
const data = getData()
29+
// intentionally storing data only per one offset because it was created for this specific case:
30+
// extension 1 completion provider requests API data
31+
// at the same time:
32+
// extension 2 completion provider requests API data at the same document and position
33+
// and so on
34+
operationsCache.set(type, { key: operationKey, data })
35+
if (type === 'getNodePath') {
36+
operationsCache.set('getNodeAtPosition', { key: operationKey, data: data.then((path: any) => path[path.length - 1]) })
37+
}
38+
39+
return data
40+
}
41+
42+
return getData()
1343
}
1444

1545
type RequestOptions = Partial<{
46+
/**
47+
* Should be rarely overrided, this document must be part of opened project
48+
* If specificed, offset or position must be provided too
49+
*/
50+
document: vscode.TextDocument
1651
offset: number
1752
relativeOffset: number
53+
position: vscode.Position
1854
}>
1955
for (const cmd of passthroughExposedApiCommands)
2056
vscode.commands.registerCommand(getExtensionCommandId(cmd as never), async (options: RequestOptions = {}) => sharedRequest(cmd, options))

src/configurationType.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,16 @@ export type Configuration = {
199199
* @default []
200200
*/
201201
additionalIncludeExtensions: string[]
202+
/**
203+
* Patterns to exclude from workspace symbol search
204+
* Example: `["**\/node_modules/**"]`
205+
* Can gradually improve performance, will be set to node_modules by default in future
206+
* @uniqueItems true
207+
* @default []
208+
* @defaultTODO ["**\/node_modules/**"]
209+
*/
210+
// TODO enable node_modules default when cancellationToken is properly used
211+
workspaceSymbolSearchExcludePatterns: string[]
202212
/**
203213
* @default ["fixMissingFunctionDeclaration"]
204214
* @uniqueItems true

typescript/src/decorateProxy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import decorateDocumentHighlights from './documentHighlights'
1111
import completionEntryDetails from './completionEntryDetails'
1212
import { GetConfig } from './types'
1313
import lodashGet from 'lodash.get'
14+
import decorateWorkspaceSymbolSearch from './workspaceSymbolSearch'
1415

1516
/** @internal */
1617
export const thisPluginMarker = '__essentialPluginsMarker__'
@@ -107,6 +108,7 @@ export const decorateLanguageService = (
107108
decorateDefinitions(proxy, info, c)
108109
decorateReferences(proxy, languageService, c)
109110
decorateDocumentHighlights(proxy, languageService, c)
111+
decorateWorkspaceSymbolSearch(proxy, languageService, c, languageServiceHost)
110112

111113
if (pluginSpecificSyntaxServerConfigCheck) {
112114
if (!__WEB__) {

typescript/src/definitions.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -120,16 +120,13 @@ export default (proxy: ts.LanguageService, info: ts.server.PluginCreateInfo, c:
120120
}
121121
if (c('miscDefinitionImprovement') && prior.definitions) {
122122
const filterOutReactFcDef = prior.definitions.length === 2
123-
prior.definitions = prior.definitions.filter(({ fileName, containerName, containerKind, kind, name, ...rest }) => {
123+
prior.definitions = prior.definitions.filter(({ fileName, containerName, containerKind, kind, name, textSpan, ...rest }) => {
124124
const isFcDef = filterOutReactFcDef && fileName.endsWith('node_modules/@types/react/index.d.ts') && containerName === 'FunctionComponent'
125125
if (isFcDef) return false
126126
// filter out css modules index definition
127127
if (containerName === 'classes' && containerKind === undefined && rest['isAmbient'] && kind === 'index' && name === '__index') {
128128
// ensure we don't filter out something important?
129-
const nodeAtDefinition = findChildContainingExactPosition(
130-
info.languageService.getProgram()!.getSourceFile(fileName)!,
131-
firstDef.textSpan.start,
132-
)
129+
const nodeAtDefinition = findChildContainingExactPosition(info.languageService.getProgram()!.getSourceFile(fileName)!, textSpan.start)
133130
let moduleDeclaration: ModuleDeclaration | undefined
134131
ts.findAncestor(nodeAtDefinition, node => {
135132
if (ts.isModuleDeclaration(node)) {
@@ -138,19 +135,25 @@ export default (proxy: ts.LanguageService, info: ts.server.PluginCreateInfo, c:
138135
}
139136
return false
140137
})
141-
if (moduleDeclaration?.name.text === '*.module.css') return false
138+
const cssModules = ['*.module.css', '*.module.scss', '*.module.sass', '*.module.less', '*.module.styl']
139+
if (moduleDeclaration?.name.text && cssModules.includes(moduleDeclaration.name.text)) return false
142140
}
143141
return true
144142
})
145143
}
146144

147-
if (
148-
c('removeModuleFileDefinitions') &&
149-
prior.definitions?.length === 1 &&
150-
firstDef.kind === ts.ScriptElementKind.moduleElement &&
151-
firstDef.name.slice(1, -1).startsWith('*.')
152-
) {
153-
return
145+
if (c('removeModuleFileDefinitions')) {
146+
prior.definitions = prior.definitions?.filter(def => {
147+
if (
148+
def.kind === ts.ScriptElementKind.moduleElement &&
149+
def.name.slice(1, -1).startsWith('*.') &&
150+
def.containerKind === undefined &&
151+
def['isAmbient']
152+
) {
153+
return false
154+
}
155+
return true
156+
})
154157
}
155158

156159
return prior

typescript/src/specialCommands/handle.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export default (
4646
}
4747
}
4848
if (specialCommand === 'getNodeAtPosition') {
49+
// ensure return data is the same as for node in getNodePath
4950
const node = findChildContainingPosition(ts, sourceFile, position)
5051
return {
5152
entries: [],
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { GetConfig } from './types'
2+
3+
export default (proxy: ts.LanguageService, languageService: ts.LanguageService, c: GetConfig, languageServiceHost: ts.LanguageServiceHost) => {
4+
proxy.getNavigateToItems = (searchValue, maxResultCount, fileName, excludeDtsFiles) => {
5+
const workspaceSymbolSearchExcludePatterns = c('workspaceSymbolSearchExcludePatterns')
6+
if (!workspaceSymbolSearchExcludePatterns.length) {
7+
return languageService.getNavigateToItems(searchValue, maxResultCount, fileName, excludeDtsFiles)
8+
}
9+
10+
const program = languageService.getProgram()!
11+
const cancellationToken = languageServiceHost.getCompilerHost?.()?.getCancellationToken?.() ?? {
12+
isCancellationRequested: () => false,
13+
throwIfCancellationRequested: () => {},
14+
}
15+
let sourceFiles = fileName ? [program.getSourceFile(fileName)!] : program.getSourceFiles()
16+
if (!fileName) {
17+
const excludes = tsFull.getRegularExpressionForWildcard(workspaceSymbolSearchExcludePatterns, '', 'exclude')?.slice(1)
18+
if (excludes) {
19+
const re = new RegExp(excludes)
20+
sourceFiles = sourceFiles.filter(x => !re.test(x.fileName))
21+
}
22+
}
23+
return tsFull.NavigateTo.getNavigateToItems(
24+
sourceFiles as any,
25+
program.getTypeChecker() as any,
26+
// TODO! use real cancellationToken
27+
cancellationToken,
28+
searchValue,
29+
maxResultCount,
30+
excludeDtsFiles ?? false,
31+
)
32+
}
33+
}

0 commit comments

Comments
 (0)