Skip to content

Commit ec0d59e

Browse files
authored
playground: inlay hints, doc symbols, hover, goto def/refs, actions (#779)
1 parent c6664a8 commit ec0d59e

File tree

7 files changed

+610
-4
lines changed

7 files changed

+610
-4
lines changed

Cargo.lock

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

crates/squawk_wasm/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ default = ["console_error_panic_hook"]
2121
squawk-syntax.workspace = true
2222
squawk-linter.workspace = true
2323
squawk-lexer.workspace = true
24+
squawk-ide.workspace = true
25+
rowan.workspace = true
2426

2527
wasm-bindgen.workspace = true
2628
serde-wasm-bindgen.workspace = true

crates/squawk_wasm/src/lib.rs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,3 +185,268 @@ pub fn lint(text: String) -> Result<JsValue, Error> {
185185
fn into_error<E: std::fmt::Display>(err: E) -> Error {
186186
Error::new(&err.to_string())
187187
}
188+
189+
#[wasm_bindgen]
190+
pub fn goto_definition(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
191+
let parse = squawk_syntax::SourceFile::parse(&content);
192+
let line_index = LineIndex::new(&content);
193+
let offset = position_to_offset(&line_index, line, col)?;
194+
let result = squawk_ide::goto_definition::goto_definition(parse.tree(), offset);
195+
196+
let response = result.map(|range| {
197+
let start = line_index.line_col(range.start());
198+
let end = line_index.line_col(range.end());
199+
let start_wide = line_index
200+
.to_wide(line_index::WideEncoding::Utf16, start)
201+
.unwrap();
202+
let end_wide = line_index
203+
.to_wide(line_index::WideEncoding::Utf16, end)
204+
.unwrap();
205+
206+
LocationRange {
207+
start_line: start_wide.line,
208+
start_column: start_wide.col,
209+
end_line: end_wide.line,
210+
end_column: end_wide.col,
211+
}
212+
});
213+
214+
serde_wasm_bindgen::to_value(&response).map_err(into_error)
215+
}
216+
217+
#[wasm_bindgen]
218+
pub fn hover(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
219+
let parse = squawk_syntax::SourceFile::parse(&content);
220+
let line_index = LineIndex::new(&content);
221+
let offset = position_to_offset(&line_index, line, col)?;
222+
let result = squawk_ide::hover::hover(&parse.tree(), offset);
223+
224+
serde_wasm_bindgen::to_value(&result).map_err(into_error)
225+
}
226+
227+
#[wasm_bindgen]
228+
pub fn find_references(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
229+
let parse = squawk_syntax::SourceFile::parse(&content);
230+
let line_index = LineIndex::new(&content);
231+
let offset = position_to_offset(&line_index, line, col)?;
232+
let references = squawk_ide::find_references::find_references(&parse.tree(), offset);
233+
234+
let locations: Vec<LocationRange> = references
235+
.iter()
236+
.map(|range| {
237+
let start = line_index.line_col(range.start());
238+
let end = line_index.line_col(range.end());
239+
let start_wide = line_index
240+
.to_wide(line_index::WideEncoding::Utf16, start)
241+
.unwrap();
242+
let end_wide = line_index
243+
.to_wide(line_index::WideEncoding::Utf16, end)
244+
.unwrap();
245+
246+
LocationRange {
247+
start_line: start_wide.line,
248+
start_column: start_wide.col,
249+
end_line: end_wide.line,
250+
end_column: end_wide.col,
251+
}
252+
})
253+
.collect();
254+
255+
serde_wasm_bindgen::to_value(&locations).map_err(into_error)
256+
}
257+
258+
#[wasm_bindgen]
259+
pub fn document_symbols(content: String) -> Result<JsValue, Error> {
260+
let parse = squawk_syntax::SourceFile::parse(&content);
261+
let line_index = LineIndex::new(&content);
262+
let symbols = squawk_ide::document_symbols::document_symbols(&parse.tree());
263+
264+
let converted: Vec<WasmDocumentSymbol> = symbols
265+
.into_iter()
266+
.map(|s| convert_document_symbol(&line_index, s))
267+
.collect();
268+
269+
serde_wasm_bindgen::to_value(&converted).map_err(into_error)
270+
}
271+
272+
#[wasm_bindgen]
273+
pub fn code_actions(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
274+
let parse = squawk_syntax::SourceFile::parse(&content);
275+
let line_index = LineIndex::new(&content);
276+
let offset = position_to_offset(&line_index, line, col)?;
277+
let actions = squawk_ide::code_actions::code_actions(parse.tree(), offset);
278+
279+
let converted = actions.map(|actions| {
280+
actions
281+
.into_iter()
282+
.map(|action| {
283+
let edits = action
284+
.edits
285+
.into_iter()
286+
.map(|edit| {
287+
let start_pos = line_index.line_col(edit.text_range.start());
288+
let end_pos = line_index.line_col(edit.text_range.end());
289+
let start_wide = line_index
290+
.to_wide(line_index::WideEncoding::Utf16, start_pos)
291+
.unwrap();
292+
let end_wide = line_index
293+
.to_wide(line_index::WideEncoding::Utf16, end_pos)
294+
.unwrap();
295+
296+
TextEdit {
297+
start_line_number: start_wide.line,
298+
start_column: start_wide.col,
299+
end_line_number: end_wide.line,
300+
end_column: end_wide.col,
301+
text: edit.text.unwrap_or_default(),
302+
}
303+
})
304+
.collect();
305+
306+
WasmCodeAction {
307+
title: action.title,
308+
edits,
309+
kind: match action.kind {
310+
squawk_ide::code_actions::ActionKind::QuickFix => "quickfix",
311+
squawk_ide::code_actions::ActionKind::RefactorRewrite => "refactor.rewrite",
312+
}
313+
.to_string(),
314+
}
315+
})
316+
.collect::<Vec<_>>()
317+
});
318+
319+
serde_wasm_bindgen::to_value(&converted).map_err(into_error)
320+
}
321+
322+
fn position_to_offset(
323+
line_index: &LineIndex,
324+
line: u32,
325+
col: u32,
326+
) -> Result<rowan::TextSize, Error> {
327+
let wide_pos = line_index::WideLineCol { line, col };
328+
329+
let pos = line_index
330+
.to_utf8(line_index::WideEncoding::Utf16, wide_pos)
331+
.ok_or_else(|| Error::new("Invalid position"))?;
332+
333+
line_index
334+
.offset(pos)
335+
.ok_or_else(|| Error::new("Invalid position offset"))
336+
}
337+
338+
#[derive(Serialize)]
339+
struct LocationRange {
340+
start_line: u32,
341+
start_column: u32,
342+
end_line: u32,
343+
end_column: u32,
344+
}
345+
346+
#[derive(Serialize)]
347+
struct WasmCodeAction {
348+
title: String,
349+
edits: Vec<TextEdit>,
350+
kind: String,
351+
}
352+
353+
#[derive(Serialize)]
354+
struct WasmDocumentSymbol {
355+
name: String,
356+
detail: Option<String>,
357+
kind: String,
358+
start_line: u32,
359+
start_column: u32,
360+
end_line: u32,
361+
end_column: u32,
362+
selection_start_line: u32,
363+
selection_start_column: u32,
364+
selection_end_line: u32,
365+
selection_end_column: u32,
366+
children: Vec<WasmDocumentSymbol>,
367+
}
368+
369+
fn convert_document_symbol(
370+
line_index: &LineIndex,
371+
symbol: squawk_ide::document_symbols::DocumentSymbol,
372+
) -> WasmDocumentSymbol {
373+
let full_start = line_index.line_col(symbol.full_range.start());
374+
let full_end = line_index.line_col(symbol.full_range.end());
375+
let full_start_wide = line_index
376+
.to_wide(line_index::WideEncoding::Utf16, full_start)
377+
.unwrap();
378+
let full_end_wide = line_index
379+
.to_wide(line_index::WideEncoding::Utf16, full_end)
380+
.unwrap();
381+
382+
let focus_start = line_index.line_col(symbol.focus_range.start());
383+
let focus_end = line_index.line_col(symbol.focus_range.end());
384+
let focus_start_wide = line_index
385+
.to_wide(line_index::WideEncoding::Utf16, focus_start)
386+
.unwrap();
387+
let focus_end_wide = line_index
388+
.to_wide(line_index::WideEncoding::Utf16, focus_end)
389+
.unwrap();
390+
391+
WasmDocumentSymbol {
392+
name: symbol.name,
393+
detail: symbol.detail,
394+
kind: match symbol.kind {
395+
squawk_ide::document_symbols::DocumentSymbolKind::Table => "table",
396+
squawk_ide::document_symbols::DocumentSymbolKind::Function => "function",
397+
squawk_ide::document_symbols::DocumentSymbolKind::Column => "column",
398+
}
399+
.to_string(),
400+
start_line: full_start_wide.line,
401+
start_column: full_start_wide.col,
402+
end_line: full_end_wide.line,
403+
end_column: full_end_wide.col,
404+
selection_start_line: focus_start_wide.line,
405+
selection_start_column: focus_start_wide.col,
406+
selection_end_line: focus_end_wide.line,
407+
selection_end_column: focus_end_wide.col,
408+
children: symbol
409+
.children
410+
.into_iter()
411+
.map(|child| convert_document_symbol(line_index, child))
412+
.collect(),
413+
}
414+
}
415+
416+
#[wasm_bindgen]
417+
pub fn inlay_hints(content: String) -> Result<JsValue, Error> {
418+
let parse = squawk_syntax::SourceFile::parse(&content);
419+
let line_index = LineIndex::new(&content);
420+
let hints = squawk_ide::inlay_hints::inlay_hints(&parse.tree());
421+
422+
let converted: Vec<WasmInlayHint> = hints
423+
.into_iter()
424+
.map(|hint| {
425+
let position = line_index.line_col(hint.position);
426+
let position_wide = line_index
427+
.to_wide(line_index::WideEncoding::Utf16, position)
428+
.unwrap();
429+
430+
WasmInlayHint {
431+
line: position_wide.line,
432+
column: position_wide.col,
433+
label: hint.label,
434+
kind: match hint.kind {
435+
squawk_ide::inlay_hints::InlayHintKind::Type => "type",
436+
squawk_ide::inlay_hints::InlayHintKind::Parameter => "parameter",
437+
}
438+
.to_string(),
439+
}
440+
})
441+
.collect();
442+
443+
serde_wasm_bindgen::to_value(&converted).map_err(into_error)
444+
}
445+
446+
#[derive(Serialize)]
447+
struct WasmInlayHint {
448+
line: u32,
449+
column: u32,
450+
label: String,
451+
kind: String,
452+
}

playground/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@
44
"version": "0.0.0",
55
"type": "module",
66
"scripts": {
7-
"dev": "vite",
8-
"build": "wasm-pack build --target web ../crates/squawk_wasm --out-dir ../../playground/src/pkg && vite build",
7+
"dev": "npm run build:wasm && vite",
8+
"build": "npm run build:wasm && vite build",
9+
"build:wasm": "wasm-pack build --target web ../crates/squawk_wasm --out-dir ../../playground/src/pkg",
910
"deploy": "netlify deploy --prod --dir dist",
1011
"lint": "eslint .",
1112
"preview": "vite preview"

playground/src/App.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ import {
1313
decompress,
1414
decompressFromEncodedURIComponent,
1515
} from "lz-string"
16+
import {
17+
provideInlayHints,
18+
provideHover,
19+
provideDefinition,
20+
provideReferences,
21+
provideDocumentSymbols,
22+
} from "./providers"
1623

1724
const modes = ["Lint", "Syntax Tree", "Tokens"] as const
1825
const STORAGE_KEY = "playground-history-v1"
@@ -424,6 +431,36 @@ function Editor({
424431
},
425432
)
426433

434+
const hoverProvider = monaco.languages.registerHoverProvider("pgsql", {
435+
provideHover,
436+
})
437+
438+
const definitionProvider = monaco.languages.registerDefinitionProvider(
439+
"pgsql",
440+
{
441+
provideDefinition,
442+
},
443+
)
444+
445+
const referencesProvider = monaco.languages.registerReferenceProvider(
446+
"pgsql",
447+
{
448+
provideReferences,
449+
},
450+
)
451+
452+
const documentSymbolProvider =
453+
monaco.languages.registerDocumentSymbolProvider("pgsql", {
454+
provideDocumentSymbols,
455+
})
456+
457+
const inlayHintsProvider = monaco.languages.registerInlayHintsProvider(
458+
"pgsql",
459+
{
460+
provideInlayHints,
461+
},
462+
)
463+
427464
editor.onDidChangeModelContent(() => {
428465
onChangeText(editor.getValue())
429466
})
@@ -434,6 +471,11 @@ function Editor({
434471
return () => {
435472
editorRef.current = null
436473
codeActionProvider.dispose()
474+
hoverProvider.dispose()
475+
definitionProvider.dispose()
476+
referencesProvider.dispose()
477+
documentSymbolProvider.dispose()
478+
inlayHintsProvider.dispose()
437479
editor?.dispose()
438480
tokenProvider.dispose()
439481
}

0 commit comments

Comments
 (0)