Skip to content

Commit 1f9b800

Browse files
authored
Merge pull request #6 from jaseci-labs/jsx_semantic_color
Jsx Syntax Highlighting
2 parents f67feb4 + 6084528 commit 1f9b800

11 files changed

Lines changed: 547 additions & 37 deletions

File tree

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,15 @@ To visualize the Jac graph while debugging, open the graph visualize view using
7272
- Document symbols, workspace symbols
7373
- Variables hint, and documentation on hover
7474
- Diagnostics
75-
- Diagnostics
75+
76+
# Developer Mode
77+
78+
Developer mode enables additional tools for extension development and debugging. To enable it:
79+
80+
1. Open Command Palette (`Ctrl+Shift+P`)
81+
2. Run `Jac: Toggle Developer Mode`
82+
83+
When enabled, the following features become available:
84+
85+
- **Restart Language Server** - Button in editor title bar to restart the LSP server
86+
- **Inspect Token Scopes** - Dumps all TextMate token scopes for the current Jac file to help debug syntax highlighting

examples/app.jac

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
2+
3+
4+
5+
with entry{
6+
print("Hello, Jac!");
7+
}
8+
9+
cl import from react {useState}
10+
11+
cl {
12+
def app() -> any {
13+
let [count, setCount] = useState(0);
14+
return <div>
15+
<h1>Hello, World!</h1>
16+
<p>Count: {count}</p>
17+
<button onClick={lambda e: any -> None { setCount(count + 1); }}>
18+
Increment
19+
</button>
20+
<ButtonComponent label="Click Me" />
21+
<NavLink to="/about">About Page</NavLink>
22+
</div>;
23+
}
24+
}

package-lock.json

Lines changed: 15 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "jaclang-extension",
3-
"version": "2025.11.3",
3+
"version": "2025.12.2",
44
"displayName": "%extension.displayName%",
55
"description": "%extension.description%",
66
"publisher": "jaseci-labs",
@@ -76,6 +76,10 @@
7676
"command": "jaclang-extension.serveCurrentFile",
7777
"title": "Jac: Serve",
7878
"icon": "$(cloud)"
79+
},
80+
{
81+
"command": "jaclang-extension.inspectTokenScopes",
82+
"title": "Jac: Inspect Token Scopes"
7983
}
8084
],
8185
"menus": {
@@ -102,6 +106,12 @@
102106
"group": "navigation@99",
103107
"when": "resourceLangId == jac && config.jaclang-extension.showServeCommand"
104108
}
109+
],
110+
"commandPalette": [
111+
{
112+
"command": "jaclang-extension.inspectTokenScopes",
113+
"when": "config.jaclang-extension.developerMode"
114+
}
105115
]
106116
},
107117
"languages": [
@@ -147,10 +157,17 @@
147157
"items": {
148158
"type": "string"
149159
},
150-
"default": ["run", "${file}"]
160+
"default": [
161+
"run",
162+
"${file}"
163+
]
151164
},
152165
"console": {
153-
"enum": ["internalConsole", "integratedTerminal", "externalTerminal"],
166+
"enum": [
167+
"internalConsole",
168+
"integratedTerminal",
169+
"externalTerminal"
170+
],
154171
"description": "Where to launch the debug target",
155172
"default": "integratedTerminal"
156173
},
@@ -213,7 +230,9 @@
213230
"ms-python.python"
214231
],
215232
"dependencies": {
216-
"vscode-languageclient": "^9.0.1"
233+
"vscode-languageclient": "^9.0.1",
234+
"vscode-oniguruma": "^2.0.1",
235+
"vscode-textmate": "^9.3.0"
217236
},
218237
"devDependencies": {
219238
"@types/jest": "^29.5.14",
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
/*
2+
* Jest tests for inspectTokenScopesHandler functionality in VSCode extension.
3+
* Uses real vscode-textmate and vscode-oniguruma libraries for actual grammar testing.
4+
*/
5+
6+
import * as path from 'path';
7+
import * as fs from 'fs';
8+
import { tokenizeContent } from '../commands/inspectTokenScopes';
9+
10+
// Read the actual app.jac file
11+
const appJacPath = path.join(process.cwd(), 'examples', 'app.jac');
12+
const appJacContent = fs.readFileSync(appJacPath, 'utf-8');
13+
14+
// Paths for grammar and wasm
15+
const grammarPath = path.join(process.cwd(), 'syntaxes', 'jac.tmLanguage.json');
16+
const wasmPath = path.join(process.cwd(), 'node_modules', 'vscode-oniguruma', 'release', 'onig.wasm');
17+
18+
describe('inspectTokenScopesHandler', () => {
19+
test('should tokenize Jac keywords correctly', async () => {
20+
const tokenMap = await tokenizeContent(appJacContent, grammarPath, wasmPath);
21+
22+
// Check 'with' keyword (storage.type.function.jac)
23+
const withScopes = tokenMap.get('with');
24+
expect(withScopes).toBeDefined();
25+
expect(withScopes).toContain('source.jac');
26+
expect(withScopes).toContain('storage.type.function.jac');
27+
28+
// Check 'entry' keyword
29+
const entryScopes = tokenMap.get('entry');
30+
expect(entryScopes).toBeDefined();
31+
expect(entryScopes).toContain('keyword.control.flow.jac');
32+
33+
// Check 'lambda' keyword
34+
const lambdaScopes = tokenMap.get('lambda');
35+
expect(lambdaScopes).toBeDefined();
36+
expect(lambdaScopes).toContain('keyword.control.flow.jac');
37+
38+
// Check 'print' builtin
39+
const printScopes = tokenMap.get('print');
40+
expect(printScopes).toBeDefined();
41+
expect(printScopes).toContain('support.function.builtin.jac');
42+
});
43+
44+
test('should tokenize JSX elements correctly', async () => {
45+
const tokenMap = await tokenizeContent(appJacContent, grammarPath, wasmPath);
46+
47+
// Check that we have JSX-related tokens
48+
const allScopes = Array.from(tokenMap.values()).flat();
49+
50+
// Check for meta.jsx scope or entity.name.tag for JSX elements
51+
const hasJsxScopes = allScopes.some(s =>
52+
s.includes('jsx') ||
53+
s.includes('entity.name.tag') ||
54+
s.includes('punctuation.definition.tag')
55+
);
56+
expect(hasJsxScopes).toBe(true);
57+
58+
// Check specific JSX HTML tags
59+
const divScopes = tokenMap.get('div');
60+
expect(divScopes).toBeDefined();
61+
expect(divScopes).toContain('entity.name.tag.html.jsx.jac');
62+
63+
const h1Scopes = tokenMap.get('h1');
64+
expect(h1Scopes).toBeDefined();
65+
expect(h1Scopes).toContain('entity.name.tag.html.jsx.jac');
66+
67+
const buttonScopes = tokenMap.get('button');
68+
expect(buttonScopes).toBeDefined();
69+
expect(buttonScopes).toContain('entity.name.tag.html.jsx.jac');
70+
71+
// Check JSX attributes
72+
const onClickScopes = tokenMap.get('onClick');
73+
expect(onClickScopes).toBeDefined();
74+
expect(onClickScopes).toContain('entity.other.attribute-name.jsx.jac');
75+
76+
// Check PascalCase component names
77+
const buttonComponentScopes = tokenMap.get('ButtonComponent');
78+
expect(buttonComponentScopes).toBeDefined();
79+
expect(buttonComponentScopes).toContain('support.class.component.jsx.jac');
80+
81+
const navLinkScopes = tokenMap.get('NavLink');
82+
expect(navLinkScopes).toBeDefined();
83+
expect(navLinkScopes).toContain('support.class.component.jsx.jac');
84+
});
85+
});

src/commands/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { runJacCommandForCurrentFile } from '../utils';
33
import { COMMANDS } from '../constants';
44
import { getLspManager } from '../extension';
55
import { EnvManager } from '../environment/manager';
6+
import { inspectTokenScopesHandler } from './inspectTokenScopes';
67

78
export function registerAllCommands(context: vscode.ExtensionContext, envManager: EnvManager) {
89
context.subscriptions.push(
@@ -83,4 +84,11 @@ export function registerAllCommands(context: vscode.ExtensionContext, envManager
8384
}
8485
})
8586
);
87+
88+
// Inspect Token Scopes command - dumps all TextMate token scopes for the current Jac file
89+
context.subscriptions.push(
90+
vscode.commands.registerCommand(COMMANDS.INSPECT_SCOPES, async () => {
91+
await inspectTokenScopesHandler(context);
92+
})
93+
);
8694
}

0 commit comments

Comments
 (0)