Skip to content

Commit 333d756

Browse files
feat: add support for js ts files and use textEdit for completions (#481)
* feat: add support for js ts files and use textEdit for completions * chpre: update plugin readme
1 parent f3e8e02 commit 333d756

File tree

6 files changed

+132
-62
lines changed

6 files changed

+132
-62
lines changed

.vscode/tasks.json

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,30 @@
2727
"label": "plugin-vscode: stop-dev",
2828
"command": "echo ${input:terminate}",
2929
"type": "shell",
30-
"problemMatcher": []
30+
"problemMatcher": [],
31+
"options": {
32+
"shell": {
33+
"executable": "bash",
34+
"args": ["-c"]
35+
}
36+
},
37+
"presentation": {
38+
"echo": false,
39+
"reveal": "never",
40+
"focus": false,
41+
"panel": "new",
42+
"showReuseMessage": false,
43+
"clear": false,
44+
"close": true
45+
}
3146
}
3247
],
3348
"inputs": [
3449
{
3550
"id": "terminate",
3651
"type": "command",
3752
"command": "workbench.action.tasks.terminate",
38-
"args": "terminateAll"
53+
"args": "plugin-vscode: start-dev"
3954
}
4055
]
4156
}

packages/plugin-vscode/README.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,10 +35,14 @@ Instantly see the exact values of Apsara Design Tokens with rich visual previews
3535
5. Browse through the intelligent suggestions
3636
6. Select a token to insert it with proper `var()` syntax
3737

38-
### Supported File Types
38+
### Supported Languages
3939

40-
- CSS (`.css`)
41-
- SCSS (`.scss`)
40+
- css (`.css`)
41+
- scss (`.scss`)
42+
- javascript (`.js`)
43+
- typescript (`.ts`)
44+
- javascriptreact (`.jsx`)
45+
- typescriptreact (`.tsx`)
4246

4347
## Configuration
4448

@@ -58,4 +62,4 @@ The trigger characters that activate autocomplete for Apsara Design Tokens. You
5862
## Support
5963

6064
- 📖 [Documentation](https://apsara.raystack.org)
61-
- 🐛 [Report Issues](https://github.com/raystack/apsara/issues)
65+
- 🐛 [Report Issues](https://github.com/raystack/apsara/issues)

packages/plugin-vscode/package.json

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "apsara-for-vscode",
33
"displayName": "Apsara for VSCode",
44
"publisher": "Raystack",
5-
"version": "0.1.0",
5+
"version": "0.1.1",
66
"private": true,
77
"description": "VS Code extension for @raystack/apsara",
88
"license": "SEE LICENSE IN LICENSE.md",
@@ -21,7 +21,14 @@
2121
},
2222
"keywords": ["apsara", "raystack"],
2323
"categories": ["Programming Languages", "Snippets", "Other"],
24-
"activationEvents": ["onLanguage:css", "onLanguage:scss"],
24+
"activationEvents": [
25+
"onLanguage:css",
26+
"onLanguage:scss",
27+
"onLanguage:javascript",
28+
"onLanguage:typescript",
29+
"onLanguage:javascriptreact",
30+
"onLanguage:typescriptreact"
31+
],
2532
"main": "./dist/client.js",
2633
"contributes": {
2734
"configuration": {

packages/plugin-vscode/src/client.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ let client: LanguageClient;
1212

1313
const settings = workspace.getConfiguration('apsaraForVSCode');
1414

15+
const SUPPORTED_LANGUAGES = [
16+
'css',
17+
'scss',
18+
'javascript',
19+
'typescript',
20+
'javascriptreact',
21+
'typescriptreact'
22+
];
23+
const SUPPORTED_SCHEMES = ['file', 'untitled'];
24+
1525
export function activate(context: ExtensionContext) {
1626
// The server is implemented in node
1727
const serverModule = context.asAbsolutePath(path.join('dist', 'server.js'));
@@ -32,11 +42,13 @@ export function activate(context: ExtensionContext) {
3242

3343
// Options to control the language client
3444
const clientOptions: LanguageClientOptions = {
35-
// Register the server for css and scss files
36-
documentSelector: [
37-
{ scheme: 'file', language: 'css' },
38-
{ scheme: 'file', language: 'scss' }
39-
],
45+
// Register the server for supported languages
46+
documentSelector: SUPPORTED_LANGUAGES.reduce((acc, language) => {
47+
SUPPORTED_SCHEMES.forEach(scheme => {
48+
acc.push({ scheme, language });
49+
});
50+
return acc;
51+
}, []),
4052
synchronize: {
4153
// Notify the server about file changes to '.clientrc files contained in the workspace
4254
fileEvents: workspace.createFileSystemWatcher('**/.clientrc')

packages/plugin-vscode/src/lib/utils.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,25 @@ export const VSCodeColorToColor = (color: VSCodeColorType): string => {
8888
.alpha(color.alpha)
8989
.hex();
9090
};
91+
92+
// Function to get the pattern match and the range around the cursor
93+
export const getPatternMatch = (
94+
pattern: RegExp,
95+
text: string,
96+
cursorPosition: number
97+
) => {
98+
let match: RegExpExecArray | null;
99+
100+
while ((match = pattern.exec(text)) !== null) {
101+
const start = match.index;
102+
const end = start + match[0].length;
103+
104+
// Check if cursor is within this match
105+
if (cursorPosition >= start && cursorPosition <= end) {
106+
return { start, end, match: match[0] };
107+
}
108+
}
109+
110+
// Fallback: return the cursor position
111+
return { start: cursorPosition, end: cursorPosition, match: null };
112+
};

packages/plugin-vscode/src/server.ts

Lines changed: 59 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
import {
1616
VSCodeColorToColor,
1717
colorToVSCodeColor,
18+
getPatternMatch,
1819
getTokenName,
1920
getTokenValueFromName
2021
} from './lib/utils';
@@ -52,7 +53,6 @@ const groupedCompletionItems = Object.fromEntries(
5253
const generatedTokenName = getTokenName(tokenGroupName, tokenName);
5354
return {
5455
label: generatedTokenName,
55-
insertText: `var(${generatedTokenName})`,
5656
detail: tokenValue,
5757
filterText: generatedTokenName,
5858
kind:
@@ -105,33 +105,42 @@ connection.onCompletion(
105105
let matchedCompletionItems: CompletionItem[] = [];
106106

107107
// if the doc can't be found, return nothing
108-
if (!doc) {
109-
return [];
110-
}
108+
if (!doc) return [];
111109

112110
const currentText = doc.getText({
113111
start: { line: textDocumentPosition.position.line, character: 0 },
114112
end: { line: textDocumentPosition.position.line, character: 1000 }
115113
});
116114

117-
// if the current text is empty or doesn't include a colon, return nothing
118-
if (currentText.trim().length === 0 || !currentText.includes(':')) {
115+
// if the current text is empty or doesn't include a colon or =, return nothing
116+
if (
117+
currentText.trim().length === 0 ||
118+
!(currentText.includes(':') || currentText.includes('='))
119+
)
119120
return [];
120-
}
121121

122-
// Find if the cursor is inside a var(...) wrapper
123122
const cursor = textDocumentPosition.position.character;
124-
let hasVarWrapper = false;
125-
const varRegex = /var\s*\(([^)]*)\)/g;
126-
let match;
127-
while ((match = varRegex.exec(currentText)) !== null) {
128-
const start = match.index;
129-
const end = match.index + match[0].length;
130-
if (cursor >= start && cursor <= end) {
131-
hasVarWrapper = true;
132-
break;
133-
}
134-
}
123+
124+
// Look for complete var(...) wrappers
125+
const completeVarMatch = getPatternMatch(
126+
/var\s*\(([^)]*)\)/g,
127+
currentText,
128+
cursor
129+
);
130+
131+
// Look for incomplete var(... wrappers
132+
const incompleteVarMatch = getPatternMatch(
133+
/var\s*\(([^)]*)/g,
134+
currentText,
135+
cursor
136+
);
137+
138+
// Look for token patterns: -- followed by word characters, or just --
139+
const tokenMatch = getPatternMatch(
140+
/--[a-zA-Z0-9-_]*/g,
141+
currentText,
142+
cursor
143+
);
135144

136145
for (const [tokenGroupName, pattern] of Object.entries(
137146
tokenGroupPatterns
@@ -141,27 +150,35 @@ connection.onCompletion(
141150
const currentCompletionItems =
142151
groupedCompletionItems[tokenGroupName as TokenGroupType];
143152

144-
// Create new completion items with adjusted insertText based on whether var() already exists at the cursor
145-
const adjustedCompletionItems = currentCompletionItems.map(item => ({
146-
...item,
147-
insertText: hasVarWrapper ? item.label : item.insertText
148-
}));
149-
150153
matchedCompletionItems = matchedCompletionItems.concat(
151-
adjustedCompletionItems
154+
currentCompletionItems
152155
);
153156
}
154157

155-
// if there were matches above, send them
156-
if (matchedCompletionItems.length) return matchedCompletionItems;
158+
const completionItems = matchedCompletionItems.length
159+
? matchedCompletionItems
160+
: allCompletionItems;
157161

158-
// if there were no matches, send everything with adjusted insertText
159-
const adjustedAllItems = allCompletionItems.map(item => ({
162+
return completionItems.map(item => ({
160163
...item,
161-
insertText: hasVarWrapper ? item.label : item.insertText
164+
textEdit: {
165+
range: {
166+
start: {
167+
line: textDocumentPosition.position.line,
168+
character: tokenMatch.start
169+
},
170+
end: {
171+
line: textDocumentPosition.position.line,
172+
character: tokenMatch.end
173+
}
174+
},
175+
newText: completeVarMatch.match
176+
? item.label
177+
: incompleteVarMatch.match
178+
? `${item.label})`
179+
: `var(${item.label})`
180+
}
162181
}));
163-
164-
return adjustedAllItems;
165182
}
166183
);
167184

@@ -180,28 +197,21 @@ connection.onHover(
180197
end: { line: textDocumentPosition.position.line, character: 1000 }
181198
});
182199

183-
const cursorChar = textDocumentPosition.position.character;
200+
const cursorPosition = textDocumentPosition.position.character;
184201

185-
// Match a token at the cursor like --rs-space-11
186-
const tokenRegex = /--[a-zA-Z0-9-_]+/g;
187-
let match: RegExpExecArray | null;
188-
let matchedToken: string | undefined;
189-
190-
while ((match = tokenRegex.exec(currentText)) !== null) {
191-
const start = match.index;
192-
const end = start + match[0].length;
193-
if (cursorChar >= start && cursorChar <= end) {
194-
matchedToken = match[0];
195-
break;
196-
}
197-
}
202+
// Look for token match at the cursor position
203+
const tokenMatch = getPatternMatch(
204+
/--[a-zA-Z0-9-_]*/g,
205+
currentText,
206+
cursorPosition
207+
);
198208

199-
if (!matchedToken) {
209+
if (!tokenMatch.match) {
200210
return { contents: [] };
201211
}
202212

203213
const result = allCompletionItems.find(
204-
token => token.label === matchedToken
214+
token => token.label === tokenMatch.match
205215
);
206216

207217
if (result === undefined) {

0 commit comments

Comments
 (0)