Skip to content

Commit 1696779

Browse files
authored
vscode: add show client logs & show syntax tree cmds (#575)
1 parent ac9f90c commit 1696779

File tree

6 files changed

+175
-15
lines changed

6 files changed

+175
-15
lines changed

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: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ log.workspace = true
1313
simplelog.workspace = true
1414
lsp-server.workspace = true
1515
lsp-types.workspace = true
16+
serde.workspace = true
1617
serde_json.workspace = true
1718
squawk_linter.workspace = true
1819
squawk_syntax.workspace = true

crates/squawk_server/src/lib.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ fn main_loop(connection: Connection, params: serde_json::Value) -> Result<()> {
6363
continue;
6464
}
6565

66+
if req.method == "squawk/syntaxTree" {
67+
handle_syntax_tree(&connection, req)?;
68+
continue;
69+
}
70+
6671
info!("Ignoring unhandled request: {}", req.method);
6772
}
6873
Message::Response(resp) => {
@@ -235,3 +240,32 @@ fn lint(connection: &Connection, uri: lsp_types::Url, content: &str, version: i3
235240

236241
Ok(())
237242
}
243+
244+
#[derive(serde::Deserialize)]
245+
struct SyntaxTreeParams {
246+
#[serde(rename = "textDocument")]
247+
text_document: lsp_types::TextDocumentIdentifier,
248+
// TODO: once we start storing the text doc on the server we won't need to
249+
// send the content across the wire
250+
text: String,
251+
}
252+
253+
fn handle_syntax_tree(connection: &Connection, req: lsp_server::Request) -> Result<()> {
254+
let params: SyntaxTreeParams = serde_json::from_value(req.params)?;
255+
let uri = params.text_document.uri;
256+
let content = params.text;
257+
258+
info!("Generating syntax tree for: {}", uri);
259+
260+
let parse: Parse<SourceFile> = SourceFile::parse(&content);
261+
let syntax_tree = format!("{:#?}", parse.syntax_node());
262+
263+
let resp = Response {
264+
id: req.id,
265+
result: Some(serde_json::to_value(&syntax_tree).unwrap()),
266+
error: None,
267+
};
268+
269+
connection.sender.send(Message::Response(resp))?;
270+
Ok(())
271+
}

squawk-vscode/eslint.config.mjs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,20 @@ export default tseslint.config(
1212
globals: globals.node,
1313
},
1414
plugins: {},
15-
rules: {},
15+
rules: {
16+
"no-console": "error",
17+
"@typescript-eslint/no-unused-vars": [
18+
"error",
19+
{
20+
args: "all",
21+
argsIgnorePattern: "^_",
22+
caughtErrors: "all",
23+
caughtErrorsIgnorePattern: "^_",
24+
destructuredArrayIgnorePattern: "^_",
25+
varsIgnorePattern: "^_",
26+
ignoreRestSiblings: true,
27+
},
28+
],
29+
},
1630
},
1731
)

squawk-vscode/package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@
4141
"command": "squawk.showLogs",
4242
"title": "Show Server Logs",
4343
"category": "Squawk"
44+
},
45+
{
46+
"command": "squawk.showSyntaxTree",
47+
"title": "Show Syntax Tree",
48+
"category": "Squawk"
49+
},
50+
{
51+
"command": "squawk.showClientLogs",
52+
"title": "Show Client Logs",
53+
"category": "Squawk"
4454
}
4555
],
4656
"languages": [

squawk-vscode/src/extension.ts

Lines changed: 114 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,28 @@ import {
88
} from "vscode-languageclient/node"
99

1010
let client: LanguageClient | undefined
11+
let log: Pick<
12+
vscode.LogOutputChannel,
13+
"trace" | "debug" | "info" | "warn" | "error" | "show"
14+
>
1115

1216
export async function activate(context: vscode.ExtensionContext) {
13-
console.log("Squawk activate")
17+
log = vscode.window.createOutputChannel("Squawk Client", {
18+
log: true,
19+
})
1420

15-
const serverVersionCommand = vscode.commands.registerCommand(
16-
"squawk.serverVersion",
17-
() => {
21+
log.info("Squawk activate")
22+
23+
const syntaxTreeProvider = new SyntaxTreeProvider(context)
24+
context.subscriptions.push(
25+
vscode.workspace.registerTextDocumentContentProvider(
26+
"squawk-syntax-tree",
27+
syntaxTreeProvider,
28+
),
29+
)
30+
31+
context.subscriptions.push(
32+
vscode.commands.registerCommand("squawk.serverVersion", () => {
1833
try {
1934
const serverPath = getSquawkPath(context)
2035
const stdout = execFileSync(serverPath.path, ["--version"], {
@@ -28,17 +43,20 @@ export async function activate(context: vscode.ExtensionContext) {
2843
} catch (error) {
2944
vscode.window.showErrorMessage(`Failed to get server version: ${error}`)
3045
}
31-
},
46+
}),
3247
)
33-
context.subscriptions.push(serverVersionCommand)
3448

35-
const showLogsCommand = vscode.commands.registerCommand(
36-
"squawk.showLogs",
37-
() => {
49+
context.subscriptions.push(
50+
vscode.commands.registerCommand("squawk.showLogs", () => {
3851
client?.outputChannel?.show()
39-
},
52+
}),
53+
)
54+
55+
context.subscriptions.push(
56+
vscode.commands.registerCommand("squawk.showClientLogs", () => {
57+
log.show()
58+
}),
4059
)
41-
context.subscriptions.push(showLogsCommand)
4260

4361
const statusBarItem = vscode.window.createStatusBarItem(
4462
vscode.StatusBarAlignment.Right,
@@ -57,13 +75,21 @@ export async function deactivate() {
5775
await client?.stop()
5876
}
5977

78+
function isSqlDocument(document: vscode.TextDocument): boolean {
79+
return document.languageId === "sql" || document.languageId === "postgres"
80+
}
81+
82+
function isSqlEditor(editor: vscode.TextEditor): boolean {
83+
return isSqlDocument(editor.document)
84+
}
85+
6086
function getSquawkPath(context: vscode.ExtensionContext): vscode.Uri {
6187
const ext = process.platform === "win32" ? ".exe" : ""
6288
return vscode.Uri.joinPath(context.extensionUri, "server", `squawk${ext}`)
6389
}
6490

6591
async function startServer(context: vscode.ExtensionContext) {
66-
console.log("starting squawk server")
92+
log.info("Starting Squawk Language Server...")
6793

6894
const squawkPath = getSquawkPath(context)
6995
const hasBinary = await vscode.workspace.fs.stat(squawkPath).then(
@@ -72,11 +98,11 @@ async function startServer(context: vscode.ExtensionContext) {
7298
)
7399
if (!hasBinary) {
74100
const errorMsg = `Squawk binary not found at: ${squawkPath.path}`
75-
console.error(errorMsg)
101+
log.error(`ERROR: ${errorMsg}`)
76102
vscode.window.showErrorMessage(errorMsg)
77103
return
78104
}
79-
console.log(`Found Squawk binary at: ${squawkPath}`)
105+
log.info(`Found Squawk binary at: ${squawkPath.path}`)
80106

81107
const serverExecutable: Executable = {
82108
command: squawkPath.path,
@@ -94,5 +120,79 @@ async function startServer(context: vscode.ExtensionContext) {
94120
clientOptions,
95121
)
96122

123+
log.info("Language client created, starting...")
97124
client.start()
125+
log.info("Language client started")
126+
}
127+
128+
// Based on rust-analyzer's SyntaxTree support:
129+
// https://github.com/rust-lang/rust-analyzer/blob/c0eaff7dd1fdfffed4e5706780e79967760d1d9b/editors/code/src/commands.ts#L432-L510
130+
class SyntaxTreeProvider implements vscode.TextDocumentContentProvider {
131+
_eventEmitter = new vscode.EventEmitter<vscode.Uri>()
132+
_activeEditor: vscode.TextEditor | undefined
133+
// TODO: for now we only show syntax highlighting when someone has
134+
// rust-analyzer installed which ships with the rast grammar
135+
_uri = vscode.Uri.parse("squawk-syntax-tree://syntaxtree/tree.rast")
136+
137+
constructor(context: vscode.ExtensionContext) {
138+
context.subscriptions.push(
139+
vscode.window.onDidChangeActiveTextEditor((editor) => {
140+
this._onDidChangeActiveTextEditor(editor)
141+
}),
142+
)
143+
context.subscriptions.push(
144+
vscode.workspace.onDidChangeTextDocument((event) => {
145+
this._onDidChangeTextDocument(event.document)
146+
}),
147+
)
148+
context.subscriptions.push(
149+
vscode.commands.registerCommand("squawk.showSyntaxTree", async () => {
150+
const doc = await vscode.workspace.openTextDocument(this._uri)
151+
await vscode.window.showTextDocument(doc, vscode.ViewColumn.Beside)
152+
}),
153+
)
154+
155+
// initial kick off to make sure we have the editor set
156+
this._onDidChangeActiveTextEditor(vscode.window.activeTextEditor)
157+
}
158+
159+
onDidChange = this._eventEmitter.event
160+
161+
_onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
162+
if (editor && isSqlEditor(editor)) {
163+
this._activeEditor = editor
164+
this._eventEmitter.fire(this._uri)
165+
}
166+
}
167+
168+
_onDidChangeTextDocument(document: vscode.TextDocument) {
169+
if (
170+
isSqlDocument(document) &&
171+
this._activeEditor &&
172+
document === this._activeEditor.document
173+
) {
174+
this._eventEmitter.fire(this._uri)
175+
}
176+
}
177+
178+
async provideTextDocumentContent(_uri: vscode.Uri): Promise<string> {
179+
try {
180+
const document = this._activeEditor?.document
181+
if (!document) {
182+
return "Error: no active editor found"
183+
}
184+
const text = document.getText()
185+
const uri = document.uri.toString()
186+
log.info(`Requesting syntax tree for: ${uri}`)
187+
const response = await client?.sendRequest("squawk/syntaxTree", {
188+
textDocument: { uri },
189+
text,
190+
})
191+
log.info("Syntax tree received")
192+
return response as string
193+
} catch (error) {
194+
log.error(`Failed to get syntax tree: ${error}`)
195+
return `Error: Failed to get syntax tree: ${error}`
196+
}
197+
}
98198
}

0 commit comments

Comments
 (0)