Skip to content

Commit 7227e2b

Browse files
authored
vscode: add show tokens to get lexer output (#579)
1 parent 2c2660f commit 7227e2b

File tree

6 files changed

+159
-27
lines changed

6 files changed

+159
-27
lines changed

.vscode/settings.json

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,10 @@
66
"[rust]": {
77
"editor.defaultFormatter": "rust-lang.rust-analyzer"
88
},
9-
"files.associations": {
10-
"*.sql": "postgres"
11-
},
12-
"[postgres]": {
9+
"[ungrammar]": {
1310
"editor.tabSize": 2
1411
},
15-
"[ungrammar]": {
12+
"[sql]": {
1613
"editor.tabSize": 2
1714
}
1815
}

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/squawk_server/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ lsp-server.workspace = true
1515
lsp-types.workspace = true
1616
serde.workspace = true
1717
serde_json.workspace = true
18+
squawk_lexer.workspace = true
1819
squawk_linter.workspace = true
1920
squawk_syntax.workspace = true
2021
line-index.workspace = true
2122

2223
[lints]
23-
workspace = true
24+
workspace = true

crates/squawk_server/src/lib.rs

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,17 +58,20 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
5858
return Ok(());
5959
}
6060

61-
if req.method == GotoDefinition::METHOD {
62-
handle_goto_definition(&connection, req)?;
63-
continue;
61+
match req.method.as_ref() {
62+
GotoDefinition::METHOD => {
63+
handle_goto_definition(&connection, req)?;
64+
}
65+
"squawk/syntaxTree" => {
66+
handle_syntax_tree(&connection, req)?;
67+
}
68+
"squawk/tokens" => {
69+
handle_tokens(&connection, req)?;
70+
}
71+
_ => {
72+
info!("Ignoring unhandled request: {}", req.method);
73+
}
6474
}
65-
66-
if req.method == "squawk/syntaxTree" {
67-
handle_syntax_tree(&connection, req)?;
68-
continue;
69-
}
70-
71-
info!("Ignoring unhandled request: {}", req.method);
7275
}
7376
Message::Response(resp) => {
7477
info!("Received response: id={:?}", resp.id);
@@ -269,3 +272,46 @@ fn handle_syntax_tree(connection: &Connection, req: lsp_server::Request) -> Resu
269272
connection.sender.send(Message::Response(resp))?;
270273
Ok(())
271274
}
275+
276+
#[derive(serde::Deserialize)]
277+
struct TokensParams {
278+
#[serde(rename = "textDocument")]
279+
text_document: lsp_types::TextDocumentIdentifier,
280+
// TODO: once we start storing the text doc on the server we won't need to
281+
// send the content across the wire
282+
text: String,
283+
}
284+
285+
fn handle_tokens(connection: &Connection, req: lsp_server::Request) -> Result<()> {
286+
let params: TokensParams = serde_json::from_value(req.params)?;
287+
let uri = params.text_document.uri;
288+
let content = params.text;
289+
290+
info!("Generating tokens for: {}", uri);
291+
292+
let tokens = squawk_lexer::tokenize(&content);
293+
294+
let mut output = Vec::new();
295+
let mut char_pos = 0;
296+
for token in tokens {
297+
let token_start = char_pos;
298+
let token_end = token_start + token.len as usize;
299+
let token_text = &content[token_start..token_end];
300+
output.push(format!(
301+
"{:?}@{}..{} {:?}",
302+
token.kind, token_start, token_end, token_text
303+
));
304+
char_pos = token_end;
305+
}
306+
307+
let tokens_output = output.join("\n");
308+
309+
let resp = Response {
310+
id: req.id,
311+
result: Some(serde_json::to_value(&tokens_output).unwrap()),
312+
error: None,
313+
};
314+
315+
connection.sender.send(Message::Response(resp))?;
316+
Ok(())
317+
}

squawk-vscode/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
"title": "Show Syntax Tree",
4848
"category": "Squawk"
4949
},
50+
{
51+
"command": "squawk.showTokens",
52+
"title": "Show Tokens",
53+
"category": "Squawk"
54+
},
5055
{
5156
"command": "squawk.showClientLogs",
5257
"title": "Show Client Logs",

squawk-vscode/src/extension.ts

Lines changed: 93 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export async function activate(context: vscode.ExtensionContext) {
3131
),
3232
)
3333

34+
const tokensProvider = new TokensProvider(context)
35+
context.subscriptions.push(
36+
vscode.workspace.registerTextDocumentContentProvider(
37+
"squawk-tokens",
38+
tokensProvider,
39+
),
40+
)
41+
3442
context.subscriptions.push(
3543
vscode.commands.registerCommand("squawk.serverVersion", () => {
3644
try {
@@ -82,9 +90,7 @@ export async function activate(context: vscode.ExtensionContext) {
8290
await startServer(context)
8391
}
8492

85-
export async function deactivate() {
86-
await client?.stop()
87-
}
93+
export async function deactivate() {}
8894

8995
function isSqlDocument(document: vscode.TextDocument): boolean {
9096
return document.languageId === "sql" || document.languageId === "postgres"
@@ -166,14 +172,17 @@ function getSquawkPath(context: vscode.ExtensionContext): vscode.Uri {
166172
}
167173

168174
async function startServer(context: vscode.ExtensionContext) {
169-
if (client?.state === State.Running) {
170-
log.info("Server is already running")
171-
return
172-
}
173-
174-
if (client?.state === State.Starting) {
175-
log.info("Server is already starting")
176-
return
175+
const state = client?.state
176+
switch (state) {
177+
case State.Running:
178+
case State.Starting:
179+
log.info("Server is already running")
180+
break
181+
case State.Stopped:
182+
case undefined:
183+
break
184+
default:
185+
assertNever(state)
177186
}
178187

179188
log.info("Starting Squawk Language Server...")
@@ -212,6 +221,7 @@ async function startServer(context: vscode.ExtensionContext) {
212221
onClientStateChange.fire(event)
213222
}),
214223
)
224+
context.subscriptions.push(client)
215225

216226
log.info("server starting...")
217227
try {
@@ -315,6 +325,78 @@ class SyntaxTreeProvider implements vscode.TextDocumentContentProvider {
315325
}
316326
}
317327
}
328+
329+
class TokensProvider implements vscode.TextDocumentContentProvider {
330+
_eventEmitter = new vscode.EventEmitter<vscode.Uri>()
331+
_activeEditor: vscode.TextEditor | undefined
332+
_uri = vscode.Uri.parse("squawk-tokens://tokens/tokens.rast")
333+
334+
constructor(context: vscode.ExtensionContext) {
335+
context.subscriptions.push(
336+
vscode.window.onDidChangeActiveTextEditor((editor) => {
337+
this._onDidChangeActiveTextEditor(editor)
338+
}),
339+
)
340+
context.subscriptions.push(
341+
vscode.workspace.onDidChangeTextDocument((event) => {
342+
this._onDidChangeTextDocument(event.document)
343+
}),
344+
)
345+
context.subscriptions.push(
346+
vscode.commands.registerCommand("squawk.showTokens", async () => {
347+
const doc = await vscode.workspace.openTextDocument(this._uri)
348+
await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)
349+
}),
350+
)
351+
352+
// initial kick off to make sure we have the editor set
353+
this._onDidChangeActiveTextEditor(vscode.window.activeTextEditor)
354+
}
355+
356+
onDidChange = this._eventEmitter.event
357+
358+
_onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
359+
if (editor && isSqlEditor(editor)) {
360+
this._activeEditor = editor
361+
this._eventEmitter.fire(this._uri)
362+
}
363+
}
364+
365+
_onDidChangeTextDocument(document: vscode.TextDocument) {
366+
if (
367+
isSqlDocument(document) &&
368+
this._activeEditor &&
369+
document === this._activeEditor.document
370+
) {
371+
this._eventEmitter.fire(this._uri)
372+
}
373+
}
374+
375+
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
376+
try {
377+
const document = this._activeEditor?.document
378+
if (!document) {
379+
return "Error: no active editor found"
380+
}
381+
if (!client) {
382+
return "Error: no client found"
383+
}
384+
const text = document.getText()
385+
const uri = document.uri.toString()
386+
log.info(`Requesting tokens for: ${uri}`)
387+
const response = await client.sendRequest<string>("squawk/tokens", {
388+
textDocument: { uri },
389+
text,
390+
})
391+
log.info("Tokens received")
392+
return response
393+
} catch (error) {
394+
log.error(`Failed to get tokens:`, error)
395+
return `Error: Failed to get tokens: ${String(error)}`
396+
}
397+
}
398+
}
399+
318400
function assertNever(param: never): never {
319401
throw new Error(`should never get here, but got ${String(param)}`)
320402
}

0 commit comments

Comments
 (0)