Skip to content

Commit ffdc8e5

Browse files
authored
Add getSuggestionsLikeVariable (#42)
* add getSuggestionsLikeVariable * doc is already defined
1 parent 30c9276 commit ffdc8e5

File tree

4 files changed

+65
-12
lines changed

4 files changed

+65
-12
lines changed

src/language-server.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,11 @@ import postcss, {type Root as ASTRoot} from 'postcss'
1414
// import {properties, stories} from '@primer/primitives/dist/js/intellisense'
1515
import camelCase from 'lodash.camelcase'
1616
import {isColor} from './utils/is-color'
17-
import {getSuggestions} from './suggestions'
17+
import {getSuggestions, getSuggestionsLikeVariable, type SuggestionWithSortText} from './suggestions'
1818
import {getCssVariable} from './utils/get-css-variable'
1919
import {getVariableInfo} from './utils/get-variable-info'
2020
import {getDocumentation} from './documentation'
21+
import {getCurrentWord} from './utils/get-current-word'
2122

2223
// Create a connection for the server, using Node's IPC as a transport.
2324
// Also include all preview / proposed LSP features.
@@ -67,12 +68,24 @@ connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] =
6768
let property: string = ast.nodes[0].type === 'decl' ? camelCase(ast.nodes[0].prop) : undefined
6869
if (!property) return []
6970

71+
const value: string =
72+
ast.nodes[0].type === 'decl' ? ast.nodes[0].value.replace(');', '').replace('\n', '').trim() : undefined
73+
74+
const suggestedVariablesWithSortText: SuggestionWithSortText[] = []
75+
76+
// if the cursor is at the css variable, get getSuggestionsLikeVariable instead of getSuggestions(property)
77+
const offset = doc.offsetAt(params.position)
78+
const currentWord = getCurrentWord(doc, offset)
79+
if (currentWord.includes('--') && currentWord.length > 2 /* checking it's not just -- */) {
80+
const variableSuggestions = getSuggestionsLikeVariable(currentWord.replace('--', ''))
81+
suggestedVariablesWithSortText.push(...variableSuggestions)
82+
}
83+
7084
// TODO: for shorthands, property might be the second property like borderColor or paddingInline
7185
// we can be smarter about this
7286
if (property === 'padding') {
7387
try {
7488
// padding: block inline
75-
const value = currentLine.split(':')[1].trim()
7689
const [blockValue] = value.split(' ')
7790
const blockValuePositionEnd = currentLine.indexOf(blockValue) + blockValue.length
7891

@@ -87,7 +100,6 @@ connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] =
87100
} else if (property === 'border') {
88101
try {
89102
// border: width style color
90-
const value = currentLine.split(':')[1].trim()
91103
const [borderWidth] = value.split(' ')
92104
const borderWidthPositionEnd = currentLine.indexOf(borderWidth) + borderWidth.length
93105

@@ -101,7 +113,7 @@ connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] =
101113
}
102114
}
103115

104-
const suggestedVariablesWithSortText = getSuggestions(property)
116+
suggestedVariablesWithSortText.push(...getSuggestions(property))
105117

106118
const items = suggestedVariablesWithSortText.map(variable => {
107119
const documentation = getDocumentation(variable.name)
@@ -114,8 +126,8 @@ connection.onCompletion((params: TextDocumentPositionParams): CompletionItem[] =
114126
typeof variable.value === 'string' && isColor(variable.value)
115127
? CompletionItemKind.Color
116128
: variable.kind === 'functional'
117-
? CompletionItemKind.Field
118-
: CompletionItemKind.Constructor,
129+
? CompletionItemKind.Field
130+
: CompletionItemKind.Constructor,
119131
sortText: variable.sortText,
120132
// this is slightly silly because what about multiple variables in one line
121133
// like shorthands or fallbacks

src/suggestions.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,19 @@
11
import {describe, it, expect} from 'vitest'
2-
import {getSuggestions} from './suggestions'
2+
import {getSuggestions, getSuggestionsLikeVariable} from './suggestions'
33

44
describe('Suggestions', () => {
55
it('returns suggestions for paddingBottom', () => {
66
const suggestions = getSuggestions('paddingBottom')
77
expect(suggestions).toMatchSnapshot()
88
})
9+
10+
it('returns suggestions for variable part: --fgColor', () => {
11+
const suggestions = getSuggestionsLikeVariable('--fgColor')
12+
expect(suggestions[0].name).toBe('--fgColor-accent')
13+
})
14+
15+
it.todo('returns suggestions for property: outline', () => {
16+
const suggestions = getSuggestions('outline')
17+
expect(suggestions[0].name).toBe('--focus-outline')
18+
})
919
})

src/suggestions.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import {propertiesMap, aliases, type Suggestion} from './data'
44
// eslint-disable-next-line import/no-namespace
55
import type * as CSS from 'csstype'
66

7-
export const getSuggestions = (property: keyof CSS.Properties) => {
7+
export type SuggestionWithSortText = Suggestion & {sortText: string}
8+
9+
export const getSuggestions = (property: keyof CSS.Properties): SuggestionWithSortText[] => {
810
// TODO: for shorthands, property might be the second property like borderColor or paddingInline
911
// we can be smarter about this
1012
let suggestedVariables: Suggestion[]
@@ -24,6 +26,22 @@ export const getSuggestions = (property: keyof CSS.Properties) => {
2426

2527
if (!suggestedVariables) return []
2628

29+
const suggestedVariablesWithSortText = sortSuggestions(suggestedVariables)
30+
return suggestedVariablesWithSortText
31+
}
32+
33+
// slower than getSuggestions, use judiciously
34+
export const getSuggestionsLikeVariable = (variableQuery: string): SuggestionWithSortText[] => {
35+
const allVariables = flatten(Object.values(propertiesMap))
36+
const suggestedVariables: Suggestion[] = allVariables.filter(variable => variable.name.includes(variableQuery))
37+
38+
if (!suggestedVariables) return []
39+
40+
const suggestedVariablesWithSortText = sortSuggestions(suggestedVariables)
41+
return suggestedVariablesWithSortText
42+
}
43+
44+
const sortSuggestions = (suggestedVariables: Suggestion[]): SuggestionWithSortText[] => {
2745
const functionalVariables = suggestedVariables.filter(variable => variable.kind === 'functional')
2846
const baseVariables = suggestedVariables.filter(variable => variable.kind === 'base')
2947

@@ -35,6 +53,10 @@ export const getSuggestions = (property: keyof CSS.Properties) => {
3553
// TODO 2: contextual repetition
3654
// if there are other variables in the same block/document
3755
// we should take hints from them like hover state or control-small or button-primary
56+
57+
// TODO 3: for shorthands like outline and font, the shorthand token should come before
58+
// the tokens for subproperties
59+
3860
const suggestedVariablesWithSortText = [
3961
...functionalVariables.map((variable, index) => {
4062
// we have to use alphabet instead of numbers to sort because it uses text

src/utils/get-current-word.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,24 @@
11
import {TextDocument} from 'vscode-languageserver-textdocument'
22

33
export function getCurrentWord(document: TextDocument, offset: number): string {
4-
let left = offset - 1
5-
let right = offset + 1
64
const text = document.getText()
5+
const delimiters = ' \t\n\r":{[()]},*>+'
6+
7+
// Ensure offset is within bounds
8+
if (offset < 0 || offset > text.length) {
9+
return ''
10+
}
11+
12+
let left = offset - 1
13+
let right = offset
714

8-
while (left >= 0 && ' \t\n\r":{[()]},*>+'.indexOf(text.charAt(left)) === -1) {
15+
// Move left to find start of word
16+
while (left >= 0 && delimiters.indexOf(text.charAt(left)) === -1) {
917
left--
1018
}
1119

12-
while (right <= text.length && ' \t\n\r":{[()]},*>+'.indexOf(text.charAt(right)) === -1) {
20+
// Move right to find end of word
21+
while (right < text.length && delimiters.indexOf(text.charAt(right)) === -1) {
1322
right++
1423
}
1524

0 commit comments

Comments
 (0)