From 2e0990c1e2372dbec3f3fa2a2541d4ae18427e37 Mon Sep 17 00:00:00 2001 From: Steve Dignam Date: Sat, 14 Mar 2026 20:48:19 -0400 Subject: [PATCH] playground: add code folding support --- crates/squawk_wasm/src/lib.rs | 40 +++++++++++++++++++++++++++++++++++ playground/src/App.tsx | 13 ++++++++++-- playground/src/providers.tsx | 25 ++++++++++++++++++++++ playground/src/squawk.tsx | 13 ++++++++++++ 4 files changed, 89 insertions(+), 2 deletions(-) diff --git a/crates/squawk_wasm/src/lib.rs b/crates/squawk_wasm/src/lib.rs index d722b622..f57de406 100644 --- a/crates/squawk_wasm/src/lib.rs +++ b/crates/squawk_wasm/src/lib.rs @@ -5,6 +5,7 @@ use salsa::Setter; use serde::{Deserialize, Serialize}; use squawk_ide::builtins::builtins_line_index; use squawk_ide::db::{self, Database, File}; +use squawk_ide::folding_ranges::{FoldKind, folding_ranges}; use squawk_ide::goto_definition::FileId; use squawk_syntax::ast::AstNode; use wasm_bindgen::prelude::*; @@ -350,6 +351,38 @@ impl SquawkDatabase { serde_wasm_bindgen::to_value(&converted).map_err(into_error) } + pub fn folding_ranges(&self) -> Result { + let file = self.file()?; + let line_index = db::line_index(&self.db, file); + let folds = folding_ranges(&self.db, file); + + let converted: Vec = folds + .into_iter() + .map(|fold| { + let start = line_index.line_col(fold.range.start()); + let end = line_index.line_col(fold.range.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(); + + WasmFoldingRange { + start_line: start_wide.line, + end_line: end_wide.line, + kind: match fold.kind { + FoldKind::Comment => "comment", + _ => "region", + } + .to_string(), + } + }) + .collect(); + + serde_wasm_bindgen::to_value(&converted).map_err(into_error) + } + pub fn selection_ranges(&self, positions: Vec) -> Result { let file = self.file()?; let parse = db::parse(&self.db, file); @@ -609,6 +642,13 @@ struct WasmInlayHint { kind: String, } +#[derive(Serialize)] +struct WasmFoldingRange { + start_line: u32, + end_line: u32, + kind: String, +} + #[derive(Serialize)] struct WasmSelectionRange { start_line: u32, diff --git a/playground/src/App.tsx b/playground/src/App.tsx index 30691a99..75fb77dc 100644 --- a/playground/src/App.tsx +++ b/playground/src/App.tsx @@ -19,6 +19,7 @@ import { provideDefinition, provideReferences, provideDocumentSymbols, + provideFoldingRanges, provideSelectionRanges, provideCompletionItems, } from "./providers" @@ -51,8 +52,8 @@ const SETTINGS = { minimap: { enabled: false }, automaticLayout: true, scrollBeyondLastLine: false, - folding: false, - showFoldingControls: "never", + folding: true, + showFoldingControls: "mouseover", occurrencesHighlight: "off", stickyScroll: { enabled: false }, fontSize: 16, @@ -452,6 +453,13 @@ function registerMonacoProvidersOnce() { }, ) + const foldingRangeProvider = monaco.languages.registerFoldingRangeProvider( + "pgsql", + { + provideFoldingRanges, + }, + ) + const selectionRangeProvider = monaco.languages.registerSelectionRangeProvider("pgsql", { provideSelectionRanges, @@ -472,6 +480,7 @@ function registerMonacoProvidersOnce() { definitionProvider.dispose() referencesProvider.dispose() documentSymbolProvider.dispose() + foldingRangeProvider.dispose() inlayHintsProvider.dispose() selectionRangeProvider.dispose() completionProvider.dispose() diff --git a/playground/src/providers.tsx b/playground/src/providers.tsx index 0eb27bcb..8c8ff07b 100644 --- a/playground/src/providers.tsx +++ b/playground/src/providers.tsx @@ -4,6 +4,7 @@ import { completion, document_symbols, find_references, + folding_ranges, goto_definition, hover, inlay_hints, @@ -267,6 +268,30 @@ export async function provideSelectionRanges( } } +export async function provideFoldingRanges( + model: monaco.editor.ITextModel, +): Promise { + const content = model.getValue() + const version = model.getVersionId() + if (!content) return [] + + try { + const wasmRanges = folding_ranges(content, version) + + return wasmRanges.map((range) => ({ + start: range.start_line + 1, + end: range.end_line + 1, + kind: + range.kind === "comment" + ? monaco.languages.FoldingRangeKind.Comment + : monaco.languages.FoldingRangeKind.Region, + })) + } catch (e) { + console.error("Error in provideFoldingRanges:", e) + return [] + } +} + function convertCompletionKind( kind: string, ): monaco.languages.CompletionItemKind { diff --git a/playground/src/squawk.tsx b/playground/src/squawk.tsx index f5dec7e2..50269bb9 100644 --- a/playground/src/squawk.tsx +++ b/playground/src/squawk.tsx @@ -105,6 +105,13 @@ export function selection_ranges( return getDb(content, version).selection_ranges(positions) } +export function folding_ranges( + content: string, + version: number, +): FoldingRange[] { + return getDb(content, version).folding_ranges() +} + export function completion( content: string, version: number, @@ -206,6 +213,12 @@ interface InlayHint { kind: string } +export interface FoldingRange { + start_line: number + end_line: number + kind: string +} + export interface SelectionRange { start_line: number start_column: number