Skip to content

Commit 1c6e950

Browse files
committed
feat: e.code key codes hint suggestions
1 parent fd7a6c5 commit 1c6e950

File tree

6 files changed

+109
-16
lines changed

6 files changed

+109
-16
lines changed

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,8 @@
1111
],
1212
"githubPullRequests.ignoredPullRequestBranches": [
1313
"develop"
14+
],
15+
"cSpell.words": [
16+
"unpatch"
1417
]
1518
}

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { matchParents, buildNotStrictStringCompletion } from '../utils'
2+
import { sharedCompletionContext } from './sharedContext'
3+
4+
export default (): ts.CompletionEntry[] | void => {
5+
const { node, program } = sharedCompletionContext
6+
if (!node) return
7+
const comparisonNode = getComparisonNode(node)
8+
if (!comparisonNode) return
9+
if (ts.isPropertyAccessExpression(comparisonNode) && ts.isIdentifier(comparisonNode.name) && comparisonNode.name.text === 'code') {
10+
const typeChecker = program.getTypeChecker()
11+
const symbol = typeChecker.getSymbolAtLocation(comparisonNode.name)
12+
const decl = symbol?.declarations?.[0]
13+
if (!decl) return
14+
if (
15+
decl.getSourceFile().fileName.endsWith('node_modules/typescript/lib/lib.dom.d.ts') &&
16+
matchParents(decl.parent, ['InterfaceDeclaration'])?.name.text === 'KeyboardEvent'
17+
) {
18+
return allKeyCodes.map(keyCode => buildNotStrictStringCompletion(node as ts.StringLiteralLike, keyCode))
19+
}
20+
}
21+
}
22+
23+
// type: https://github.com/zardoy/contro-max/blob/master/src/types/keyCodes.ts
24+
const singleNumber = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
25+
const fNumbers = [...singleNumber.filter(x => x !== 0), 10, 11, 12].map(x => `F${x}`) // actually can go up to 24, but used rarely
26+
const letterKeys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'].map(
27+
x => `Key${x.toUpperCase()}`,
28+
)
29+
const someOtherKeys = [
30+
'Space',
31+
'Esc',
32+
'Tab',
33+
'Enter',
34+
'Equal',
35+
'Minus',
36+
'Backslash',
37+
'Slash',
38+
'Period',
39+
'Comma',
40+
'Capslock',
41+
'Numlock',
42+
'PrintScreen',
43+
'Scrolllock',
44+
'Pause',
45+
'Backspace',
46+
'Delete',
47+
'Insert',
48+
'Backquote',
49+
'BracketLeft',
50+
'BracketRight',
51+
...['Up', 'Down', 'Left', 'Right'].map(x => `Arrow${x}`),
52+
'Home',
53+
'End',
54+
'PageUp',
55+
'PageDown',
56+
]
57+
58+
const digitKeys = singleNumber.map(x => `Digit${x}`)
59+
const modifierOnlyKeys = ['Meta', 'Control', 'Alt', 'Shift'].flatMap(x => ['', 'Left', 'Right'].map(j => x + j))
60+
61+
const numpadKeys = [...singleNumber, 'Divide', 'Multiply', 'Subtract', 'Add', 'Enter', 'Decimal'].map(x => `Numpad${x}`)
62+
const allKeyCodes = [...digitKeys, ...letterKeys, ...fNumbers, ...someOtherKeys, ...modifierOnlyKeys, ...numpadKeys]
63+
64+
const getComparisonNode = (node: ts.Node) => {
65+
if (!ts.isStringLiteralLike(node)) return
66+
const binaryExpr = matchParents(node.parent, ['BinaryExpression'])
67+
return binaryExpr?.right === node &&
68+
[ts.SyntaxKind.EqualsEqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(binaryExpr.operatorToken.kind)
69+
? binaryExpr.left
70+
: matchParents(node.parent, ['CaseClause', 'CaseBlock', 'SwitchStatement'])?.expression
71+
}

typescript/src/completionsAtPosition.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { getTupleSignature } from './tupleSignature'
3131
import stringTemplateTypeCompletions from './completions/stringTemplateType'
3232
import localityBonus from './completions/localityBonus'
3333
import functionCompletions from './completions/functionCompletions'
34+
import staticHintSuggestions from './completions/staticHintSuggestions'
3435

3536
export type PrevCompletionMap = Record<
3637
string,
@@ -53,7 +54,7 @@ export type GetCompletionAtPositionReturnType = {
5354
completions: ts.CompletionInfo
5455
/** Let default getCompletionEntryDetails to know original name or let add documentation from here */
5556
prevCompletionsMap: PrevCompletionMap
56-
prevCompletionsAdittionalData: PrevCompletionsAdditionalData
57+
prevCompletionsAdditionalData: PrevCompletionsAdditionalData
5758
}
5859

5960
export const getCompletionsAtPosition = (
@@ -289,6 +290,7 @@ export const getCompletionsAtPosition = (
289290
if (c('improveJsxCompletions') && leftNode) prior.entries = improveJsxCompletions(prior.entries, leftNode, position, sourceFile, c('jsxCompletionsMap'))
290291

291292
prior.entries = localityBonus(prior.entries) ?? prior.entries
293+
prior.entries.push(...(staticHintSuggestions() ?? []))
292294

293295
const processedEntries = new Set<ts.CompletionEntry>()
294296
for (const rule of c('replaceSuggestions')) {
@@ -410,7 +412,7 @@ export const getCompletionsAtPosition = (
410412
return {
411413
completions: prior,
412414
prevCompletionsMap,
413-
prevCompletionsAdittionalData: {
415+
prevCompletionsAdditionalData: {
414416
enableMethodCompletion: goodPositionForMethodCompletions,
415417
completionsSymbolMap,
416418
},

typescript/src/decorateProxy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const decorateLanguageService = (
4444
const proxy = getInitialProxy(languageService, existingProxy)
4545

4646
let prevCompletionsMap: PrevCompletionMap
47-
let prevCompletionsAdittionalData: PrevCompletionsAdditionalData
47+
let prevCompletionsAdditionalData: PrevCompletionsAdditionalData
4848

4949
proxy.getEditsForFileRename = (oldFilePath, newFilePath, formatOptions, preferences) => {
5050
let edits = languageService.getEditsForFileRename(oldFilePath, newFilePath, formatOptions, preferences)
@@ -127,11 +127,11 @@ export const decorateLanguageService = (
127127
const result = getCompletionsAtPosition(fileName, position, options, c, languageService, scriptSnapshot, formatOptions, { scriptKind, compilerOptions })
128128
if (!result) return
129129
prevCompletionsMap = result.prevCompletionsMap
130-
prevCompletionsAdittionalData = result.prevCompletionsAdittionalData
130+
prevCompletionsAdditionalData = result.prevCompletionsAdditionalData
131131
return result.completions
132132
}
133133

134-
proxy.getCompletionEntryDetails = (...inputArgs) => completionEntryDetails(inputArgs, languageService, prevCompletionsMap, c, prevCompletionsAdittionalData)
134+
proxy.getCompletionEntryDetails = (...inputArgs) => completionEntryDetails(inputArgs, languageService, prevCompletionsMap, c, prevCompletionsAdditionalData)
135135

136136
decorateCodeActions(proxy, languageService, languageServiceHost, c)
137137
decorateCodeFixes(proxy, languageService, languageServiceHost, c)

typescript/src/utils.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-require-imports */
22
import { Except, SetOptional } from 'type-fest'
33
import * as semver from 'semver'
4+
import type { MatchParentsType } from './utilTypes'
45

56
export function findChildContainingPosition(typescript: typeof ts, sourceFile: ts.SourceFile, position: number): ts.Node | undefined {
67
function find(node: ts.Node): ts.Node | undefined {
@@ -164,12 +165,13 @@ export const isWeb = () => {
164165
}
165166

166167
// spec isnt strict as well
167-
export const notStrictStringCompletion = (entry: ts.CompletionEntry): ts.CompletionEntry => ({
168-
...entry,
169-
// todo
170-
name: `◯${entry.name}`,
171-
insertText: entry.insertText ?? entry.name,
172-
})
168+
export const buildNotStrictStringCompletion = (node: ts.StringLiteralLike, text: string): ts.CompletionEntry =>
169+
buildStringCompletion(node, {
170+
// ...entry,
171+
sortText: '07',
172+
name: `💡${text}`,
173+
insertText: text,
174+
})
173175

174176
export function addObjectMethodResultInterceptors<T extends Record<string, any>>(
175177
object: T,
@@ -295,3 +297,18 @@ export const patchMethod = <T, K extends keyof T>(obj: T, method: K, overriden:
295297

296298
export const insertTextAfterEntry = (entryOrName: ts.CompletionEntry | string, appendText: string) =>
297299
(typeof entryOrName === 'string' ? entryOrName : entryOrName.name).replace(/\$/g, '\\$') + appendText
300+
301+
export const matchParents: MatchParentsType = (node, treeToCompare) => {
302+
let first = true
303+
for (const toCompare of treeToCompare) {
304+
if (!first) {
305+
node = node?.parent
306+
first = false
307+
}
308+
if (!node) return
309+
if (!(ts[`is${toCompare}` as keyof typeof ts] as (node) => boolean)(node)) {
310+
return
311+
}
312+
}
313+
return node as any
314+
}

0 commit comments

Comments
 (0)