From 4552bb70b9a72ec2f1d9f84a121969a474e6d8d4 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Tue, 6 Jan 2026 22:37:26 -0500 Subject: [PATCH] playground: expand selection support --- crates/squawk_wasm/src/lib.rs | 65 ++++++++++++++++++++++++++++++++++- playground/src/App.tsx | 8 +++++ playground/src/providers.tsx | 32 +++++++++++++++++ playground/src/squawk.tsx | 15 ++++++++ 4 files changed, 119 insertions(+), 1 deletion(-) diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index b7250633..62570506 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -1,6 +1,8 @@ use line_index::LineIndex; use log::info; -use serde::Serialize; +use rowan::TextRange; +use serde::{Deserialize, Serialize}; +use squawk_syntax::ast::AstNode; use wasm_bindgen::prelude::*; use web_sys::js_sys::Error; @@ -456,6 +458,59 @@ pub fn inlay_hints(content: String) -> Result { serde_wasm_bindgen::to_value(&converted).map_err(into_error) } +#[derive(Deserialize)] +struct Position { + line: u32, + column: u32, +} + +#[wasm_bindgen] +pub fn selection_ranges(content: String, positions: Vec) -> Result { + let parse = squawk_syntax::SourceFile::parse(&content); + let line_index = LineIndex::new(&content); + let tree = parse.tree(); + let root = tree.syntax(); + + let mut results: Vec> = vec![]; + + for pos in positions { + let pos: Position = serde_wasm_bindgen::from_value(pos).map_err(into_error)?; + let offset = position_to_offset(&line_index, pos.line, pos.column)?; + + let mut ranges = vec![]; + let mut range = TextRange::new(offset, offset); + + for _ in 0..20 { + let next = squawk_ide::expand_selection::extend_selection(root, range); + if next == range { + break; + } + + let start = line_index.line_col(next.start()); + let end = line_index.line_col(next.end()); + let start_wide = line_index + .to_wide(line_index::WideEncoding::Utf16, start) + .unwrap(); + let end_wide = line_index + .to_wide(line_index::WideEncoding::Utf16, end) + .unwrap(); + + ranges.push(WasmSelectionRange { + start_line: start_wide.line, + start_column: start_wide.col, + end_line: end_wide.line, + end_column: end_wide.col, + }); + + range = next; + } + + results.push(ranges); + } + + serde_wasm_bindgen::to_value(&results).map_err(into_error) +} + #[derive(Serialize)] struct WasmInlayHint { line: u32, @@ -463,3 +518,11 @@ struct WasmInlayHint { label: String, kind: String, } + +#[derive(Serialize)] +struct WasmSelectionRange { + start_line: u32, + start_column: u32, + end_line: u32, + end_column: u32, +} diff --git a/playground/src/App.tsx b/playground/src/App.tsx index 98dac012..ef0448fd 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -19,6 +19,7 @@ import { provideDefinition, provideReferences, provideDocumentSymbols, + provideSelectionRanges, } from "./providers" const modes = ["Lint", "Syntax Tree", "Tokens"] as const @@ -410,6 +411,12 @@ function registerMonacoProviders() { provideInlayHints, }, ) + + const selectionRangeProvider = + monaco.languages.registerSelectionRangeProvider("pgsql", { + provideSelectionRanges, + }) + return () => { languageConfig.dispose() codeActionProvider.dispose() @@ -418,6 +425,7 @@ function registerMonacoProviders() { referencesProvider.dispose() documentSymbolProvider.dispose() inlayHintsProvider.dispose() + selectionRangeProvider.dispose() tokenProvider.dispose() } } diff --git a/playground/src/providers.tsx b/playground/src/providers.tsx index 020e927a..fc6dbcc1 100644 --- a/playground/src/providers.tsx +++ b/playground/src/providers.tsx @@ -6,6 +6,7 @@ import { goto_definition, hover, inlay_hints, + selection_ranges, DocumentSymbol, } from "./squawk" @@ -211,3 +212,34 @@ export async function provideReferences( return [] } } + +export async function provideSelectionRanges( + model: monaco.editor.ITextModel, + positions: monaco.Position[], +): Promise { + const content = model.getValue() + if (!content) return [] + + try { + const wasmPositions = positions.map((pos) => ({ + line: pos.lineNumber - 1, + column: pos.column - 1, + })) + + const results = selection_ranges(content, wasmPositions) + + return results.map((ranges) => + ranges.map((range) => ({ + range: { + startLineNumber: range.start_line + 1, + startColumn: range.start_column + 1, + endLineNumber: range.end_line + 1, + endColumn: range.end_column + 1, + }, + })), + ) + } catch (e) { + console.error("Error in provideSelectionRanges:", e) + return [] + } +} diff --git a/playground/src/squawk.tsx b/playground/src/squawk.tsx index 3b1bb357..e906c4ee 100644 --- a/playground/src/squawk.tsx +++ b/playground/src/squawk.tsx @@ -9,6 +9,7 @@ import initWasm, { document_symbols as document_symbols_, code_actions as code_actions_, inlay_hints as inlay_hints_, + selection_ranges as selection_ranges_, } from "./pkg/squawk_wasm" export type TextEdit = { @@ -82,6 +83,13 @@ export function find_references( return find_references_(content, line, column) } +export function selection_ranges( + content: string, + positions: Array<{ line: number; column: number }>, +): SelectionRange[][] { + return selection_ranges_(content, positions) +} + export function useErrors(text: string) { const isReady = useWasmStatus() return isReady ? lint(text) : [] @@ -164,3 +172,10 @@ interface InlayHint { label: string kind: string } + +export interface SelectionRange { + start_line: number + start_column: number + end_line: number + end_column: number +}