Skip to content

Commit 710ca2a

Browse files
authored
Merge pull request #55 from zardoy/develop
2 parents 610f70d + c4d2ee5 commit 710ca2a

File tree

8 files changed

+158
-35
lines changed

8 files changed

+158
-35
lines changed

src/extension.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import * as vscode from 'vscode'
33
import { defaultJsSupersetLangs } from '@zardoy/vscode-utils/build/langs'
44
import { getActiveRegularEditor } from '@zardoy/vscode-utils'
5-
import { extensionCtx, getExtensionSettingId, getExtensionCommandId } from 'vscode-framework'
5+
import { extensionCtx, getExtensionSettingId, getExtensionCommandId, registerActiveDevelopmentCommand } from 'vscode-framework'
66
import { pickObj } from '@zardoy/utils'
7+
import { TriggerCharacterCommand } from '../typescript/src/ipcTypes'
78
import { Configuration } from './configurationType'
89
import webImports from './webImports'
910
import { sendCommand } from './sendCommand'
@@ -76,15 +77,22 @@ export const activateTsPlugin = (tsApi: { configurePlugin; onCompletionAccepted
7677
}
7778
})
7879

80+
const sharedRequest = (type: TriggerCharacterCommand, { offset, relativeOffset = 0 }: RequestOptions) => {
81+
const { activeTextEditor } = vscode.window
82+
if (!activeTextEditor) return
83+
const { document, selection } = activeTextEditor
84+
offset ??= document.offsetAt(selection.active) + relativeOffset
85+
return sendCommand(type, { document, position: document.positionAt(offset) })
86+
}
87+
7988
type RequestOptions = Partial<{
8089
offset: number
90+
relativeOffset: number
8191
}>
82-
vscode.commands.registerCommand(getExtensionCommandId('getNodeAtPosition' as never), async ({ offset }: RequestOptions = {}) => {
83-
const { activeTextEditor } = vscode.window
84-
if (!activeTextEditor) return
85-
const { document } = activeTextEditor
86-
return sendCommand('nodeAtPosition', { document, position: offset ? document.positionAt(offset) : activeTextEditor.selection.active })
87-
})
92+
vscode.commands.registerCommand(getExtensionCommandId('getNodeAtPosition' as never), async (options: RequestOptions = {}) =>
93+
sharedRequest('nodeAtPosition', options),
94+
)
95+
vscode.commands.registerCommand(getExtensionCommandId('getNodePath' as never), async (options: RequestOptions = {}) => sharedRequest('nodePath', options))
8896

8997
if (process.env.PLATFORM === 'web') {
9098
const possiblySyncConfig = async () => {

typescript/src/completions/boostKeywordSuggestions.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { findChildContainingPosition } from '../utils'
1+
import { boostOrAddSuggestions, findChildContainingPosition } from '../utils'
22

33
export default (entries: ts.CompletionEntry[], position: number, node: ts.Node): ts.CompletionEntry[] | undefined => {
44
// todo-not-sure for now, requires explicit completion trigger
@@ -22,8 +22,8 @@ export default (entries: ts.CompletionEntry[], position: number, node: ts.Node):
2222
}
2323
if (extendsKeyword) addOrBoostKeywords.push('extends')
2424
if (addOrBoostKeywords.length === 0) return
25-
return [
26-
...addOrBoostKeywords.map(keyword => ({ name: keyword, kind: ts.ScriptElementKind.keyword, sortText: '07' })),
27-
...entries.filter(({ name }) => !addOrBoostKeywords.includes(name)),
28-
]
25+
return boostOrAddSuggestions(
26+
entries,
27+
addOrBoostKeywords.map(name => ({ name, kind: ts.ScriptElementKind.keyword })),
28+
)
2929
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import { boostExistingSuggestions, boostOrAddSuggestions, findChildContainingPosition } from '../utils'
2+
3+
// 1. add suggestions for unresolved indentifiers in code
4+
// 2. boost identifer or type name suggestion
5+
export default (
6+
entries: ts.CompletionEntry[],
7+
position: number,
8+
sourceFile: ts.SourceFile,
9+
node: ts.Node,
10+
languageService: ts.LanguageService,
11+
): ts.CompletionEntry[] | undefined => {
12+
// todo getPreviousPartNode() util
13+
// todo object key
14+
const fileText = sourceFile.getFullText()
15+
const fileTextBeforePos = fileText.slice(0, position)
16+
const preConstNodeOffset = fileTextBeforePos.match(/(?:const|let) ([\w\d]*)$/i)?.[1]
17+
/** false - pick all identifiers after cursor
18+
* node - pick identifiers that within node */
19+
let filterBlock: undefined | false | ts.Node
20+
if (preConstNodeOffset !== undefined) {
21+
const node = findChildContainingPosition(ts, sourceFile, position - preConstNodeOffset.length - 2)
22+
if (!node || !ts.isVariableDeclarationList(node)) return
23+
filterBlock = false
24+
} else if (ts.isIdentifier(node) && node.parent?.parent) {
25+
// node > parent1 > parent2
26+
let parent1 = node.parent
27+
let parent2 = parent1.parent
28+
if (ts.isParameter(parent1) && isFunction(parent2)) {
29+
filterBlock = parent2.body ?? false
30+
}
31+
if (ts.isQualifiedName(parent1)) parent1 = parent1.parent
32+
parent2 = parent1.parent
33+
if (ts.isTypeReferenceNode(parent1) && ts.isParameter(parent2) && isFunction(parent2.parent) && ts.isIdentifier(parent2.name)) {
34+
const name = parent2.name.text.replace(/^_/, '')
35+
// its name convention in TS
36+
const nameUpperFirst = name[0]!.toUpperCase() + name.slice(1)
37+
return boostExistingSuggestions(entries, ({ name }) => {
38+
if (!name.includes(nameUpperFirst)) return false
39+
return true
40+
})
41+
}
42+
}
43+
44+
if (filterBlock === undefined) return
45+
const semanticDiagnostics = languageService.getSemanticDiagnostics(sourceFile.fileName)
46+
47+
const notFoundIdentifiers = semanticDiagnostics
48+
.filter(({ code }) => [2552, 2304].includes(code))
49+
.filter(({ start, length }) => {
50+
if ([start, length].some(x => x === undefined)) return false
51+
if (filterBlock === false) return start! > position
52+
const diagnosticEnd = start! + length!
53+
const { pos, end } = filterBlock!
54+
if (start! < pos) return false
55+
if (diagnosticEnd > end) return false
56+
return true
57+
})
58+
const generalNotFoundNames = [...new Set(notFoundIdentifiers.map(({ start, length }) => fileText.slice(start!, start! + length!)))]
59+
return boostOrAddSuggestions(
60+
entries,
61+
generalNotFoundNames.map(name => ({ name, kind: ts.ScriptElementKind.warning })),
62+
)
63+
64+
function isFunction(node: ts.Node): node is ts.ArrowFunction | ts.FunctionDeclaration {
65+
if (!node) return false
66+
return ts.isArrowFunction(node) || ts.isFunctionDeclaration(node)
67+
}
68+
}

typescript/src/completions/isGoodPositionMethodCompletion.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { GetConfig } from '../types'
33
import { findChildContainingPosition, findChildContainingPositionMaxDepth } from '../utils'
44

55
export const isGoodPositionBuiltinMethodCompletion = (ts: typeof tslib, sourceFile: ts.SourceFile, position: number, c: GetConfig) => {
6-
const importClauseCandidate = findChildContainingPositionMaxDepth(ts, sourceFile, position, 3)
6+
const importClauseCandidate = findChildContainingPositionMaxDepth(sourceFile, position, 3)
77
if (importClauseCandidate && ts.isImportClause(importClauseCandidate)) return false
88
const textBeforePos = sourceFile.getFullText().slice(position - 1, position)
99
let currentNode = findChildContainingPosition(ts, sourceFile, textBeforePos === ':' ? position - 1 : position)

typescript/src/completionsAtPosition.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import objectLiteralHelpers from './completions/objectLiteralHelpers'
1414
import switchCaseExcludeCovered from './completions/switchCaseExcludeCovered'
1515
import additionalTypesSuggestions from './completions/additionalTypesSuggestions'
1616
import boostKeywordSuggestions from './completions/boostKeywordSuggestions'
17+
import boostTextSuggestions from './completions/boostNameSuggestions'
1718

1819
export type PrevCompletionMap = Record<string, { originalName?: string; documentationOverride?: string | ts.SymbolDisplayPart[] }>
1920

@@ -98,6 +99,11 @@ export const getCompletionsAtPosition = (
9899
prior.entries = [...prior.entries, ...addSignatureAccessCompletions]
99100
}
100101

102+
if (leftNode) {
103+
const newEntries = boostTextSuggestions(prior?.entries ?? [], position, sourceFile, leftNode, languageService)
104+
if (newEntries?.length && ensurePrior() && prior) prior.entries = newEntries
105+
}
106+
101107
if (!prior) return
102108

103109
if (c('fixSuggestionsSorting')) prior.entries = fixPropertiesSorting(prior.entries, leftNode, sourceFile, program) ?? prior.entries

typescript/src/ipcTypes.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export const triggerCharacterCommands = ['find-in-import', 'getPostfixes', 'nodeAtPosition', 'emmet-completions'] as const
1+
export const triggerCharacterCommands = ['find-in-import', 'getPostfixes', 'nodeAtPosition', 'nodePath', 'emmet-completions'] as const
22
export type TriggerCharacterCommand = typeof triggerCharacterCommands[number]
33

44
export type NodeAtPositionResponse = {

typescript/src/specialCommands/handle.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import postfixesAtPosition from '../completions/postfixesAtPosition'
22
import { NodeAtPositionResponse, TriggerCharacterCommand, triggerCharacterCommands } from '../ipcTypes'
3-
import { findChildContainingPosition } from '../utils'
3+
import { findChildContainingPosition, getNodePath } from '../utils'
44
import getEmmetCompletions from './emmet'
55

66
export default (
@@ -29,13 +29,14 @@ export default (
2929
const node = findChildContainingPosition(ts, sourceFile, position)
3030
return {
3131
entries: [],
32-
typescriptEssentialsResponse: !node
33-
? undefined
34-
: ({
35-
kindName: ts.SyntaxKind[node.kind],
36-
start: node.getStart(),
37-
end: node.getEnd(),
38-
} as NodeAtPositionResponse),
32+
typescriptEssentialsResponse: !node ? undefined : nodeToApiResponse(node),
33+
}
34+
}
35+
if (specialCommand === 'nodePath') {
36+
const nodes = getNodePath(sourceFile, position)
37+
return {
38+
entries: [],
39+
typescriptEssentialsResponse: nodes.map(node => nodeToApiResponse(node)),
3940
}
4041
}
4142
if (specialCommand === 'getPostfixes') {
@@ -47,3 +48,11 @@ export default (
4748
} as any
4849
}
4950
}
51+
52+
function nodeToApiResponse(node: ts.Node): NodeAtPositionResponse {
53+
return {
54+
kindName: ts.SyntaxKind[node.kind]!,
55+
start: node.getStart(),
56+
end: node.getEnd(),
57+
}
58+
}

typescript/src/utils.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import type tslib from 'typescript/lib/tsserverlibrary'
2+
import { SetOptional } from 'type-fest'
23

3-
export function findChildContainingPosition(
4-
typescript: typeof import('typescript/lib/tsserverlibrary'),
5-
sourceFile: ts.SourceFile,
6-
position: number,
7-
): ts.Node | undefined {
4+
export function findChildContainingPosition(typescript: typeof tslib, sourceFile: ts.SourceFile, position: number): ts.Node | undefined {
85
function find(node: ts.Node): ts.Node | undefined {
96
if (position >= node.getStart() && position < node.getEnd()) {
107
return typescript.forEachChild(node, find) || node
@@ -15,25 +12,41 @@ export function findChildContainingPosition(
1512
return find(sourceFile)
1613
}
1714

18-
export function findChildContainingPositionMaxDepth(
19-
typescript: typeof import('typescript/lib/tsserverlibrary'),
20-
sourceFile: ts.SourceFile,
21-
position: number,
22-
maxDepth?: number,
23-
): ts.Node | undefined {
15+
export function findChildContainingPositionMaxDepth(sourceFile: ts.SourceFile, position: number, maxDepth?: number): ts.Node | undefined {
2416
let currentDepth = 0
2517
function find(node: ts.Node): ts.Node | undefined {
2618
if (position >= node.getStart() && position < node.getEnd()) {
2719
if (++currentDepth === maxDepth) return node
28-
return typescript.forEachChild(node, find) || node
20+
return ts.forEachChild(node, find) || node
2921
}
3022

3123
return
3224
}
3325
return find(sourceFile)
3426
}
3527

36-
export const getIndentFromPos = (typescript: typeof import('typescript/lib/tsserverlibrary'), sourceFile: ts.SourceFile, position: number) => {
28+
export function getNodePath(sourceFile: ts.SourceFile, position: number): ts.Node[] {
29+
const nodes: ts.Node[] = []
30+
function find(node: ts.Node): ts.Node | undefined {
31+
if (position >= node.getStart() && position < node.getEnd()) {
32+
if (node !== sourceFile) nodes.push(node)
33+
return ts.forEachChild(node, find) || node
34+
}
35+
36+
return
37+
}
38+
find(sourceFile)
39+
return nodes
40+
}
41+
42+
// todo not impl
43+
type MatchStringValue = keyof typeof ts.SyntaxKind | '*'
44+
45+
export const matchNodePath = (sourceFile: ts.SourceFile, position: number, candidates: MatchStringValue[][]) => {
46+
const nodesPath = getNodePath(sourceFile, position)
47+
}
48+
49+
export const getIndentFromPos = (typescript: typeof tslib, sourceFile: ts.SourceFile, position: number) => {
3750
const { character } = typescript.getLineAndCharacterOfPosition(sourceFile, position)
3851
return (
3952
sourceFile
@@ -63,6 +76,25 @@ export const cleanupEntryName = ({ name }: Pick<ts.CompletionEntry, 'name'>) =>
6376
return name.replace(/^ /, '')
6477
}
6578

79+
export const boostOrAddSuggestions = (existingEntries: ts.CompletionEntry[], topEntries: SetOptional<ts.CompletionEntry, 'sortText'>[]) => {
80+
const topEntryNames = topEntries.map(({ name }) => name)
81+
return [
82+
...topEntries.map(entry => ({ ...entry, sortText: entry.sortText ?? `07` })),
83+
...existingEntries.filter(({ name }) => !topEntryNames.includes(name)),
84+
]
85+
}
86+
87+
export const boostExistingSuggestions = (entries: ts.CompletionEntry[], predicate: (entry: ts.CompletionEntry) => boolean | number) => {
88+
return [...entries].sort((a, b) => {
89+
return [a, b]
90+
.map(x => {
91+
const res = predicate(x)
92+
return res === true ? 0 : res === false ? 1 : res
93+
})
94+
.reduce((a, b) => a - b)
95+
})
96+
}
97+
6698
// Workaround esbuild bundle modules
6799
export const nodeModules = __WEB__
68100
? null

0 commit comments

Comments
 (0)