Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/squawk_wasm/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ default = ["console_error_panic_hook"]
squawk-syntax.workspace = true
squawk-linter.workspace = true
squawk-lexer.workspace = true
squawk-ide.workspace = true
rowan.workspace = true

wasm-bindgen.workspace = true
serde-wasm-bindgen.workspace = true
Expand Down
265 changes: 265 additions & 0 deletions crates/squawk_wasm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -185,3 +185,268 @@ pub fn lint(text: String) -> Result<JsValue, Error> {
fn into_error<E: std::fmt::Display>(err: E) -> Error {
Error::new(&err.to_string())
}

#[wasm_bindgen]
pub fn goto_definition(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let offset = position_to_offset(&line_index, line, col)?;
let result = squawk_ide::goto_definition::goto_definition(parse.tree(), offset);

let response = result.map(|range| {
let start = line_index.line_col(range.start());
let end = line_index.line_col(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();

LocationRange {
start_line: start_wide.line,
start_column: start_wide.col,
end_line: end_wide.line,
end_column: end_wide.col,
}
});

serde_wasm_bindgen::to_value(&response).map_err(into_error)
}

#[wasm_bindgen]
pub fn hover(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let offset = position_to_offset(&line_index, line, col)?;
let result = squawk_ide::hover::hover(&parse.tree(), offset);

serde_wasm_bindgen::to_value(&result).map_err(into_error)
}

#[wasm_bindgen]
pub fn find_references(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let offset = position_to_offset(&line_index, line, col)?;
let references = squawk_ide::find_references::find_references(&parse.tree(), offset);

let locations: Vec<LocationRange> = references
.iter()
.map(|range| {
let start = line_index.line_col(range.start());
let end = line_index.line_col(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();

LocationRange {
start_line: start_wide.line,
start_column: start_wide.col,
end_line: end_wide.line,
end_column: end_wide.col,
}
})
.collect();

serde_wasm_bindgen::to_value(&locations).map_err(into_error)
}

#[wasm_bindgen]
pub fn document_symbols(content: String) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let symbols = squawk_ide::document_symbols::document_symbols(&parse.tree());

let converted: Vec<WasmDocumentSymbol> = symbols
.into_iter()
.map(|s| convert_document_symbol(&line_index, s))
.collect();

serde_wasm_bindgen::to_value(&converted).map_err(into_error)
}

#[wasm_bindgen]
pub fn code_actions(content: String, line: u32, col: u32) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let offset = position_to_offset(&line_index, line, col)?;
let actions = squawk_ide::code_actions::code_actions(parse.tree(), offset);

let converted = actions.map(|actions| {
actions
.into_iter()
.map(|action| {
let edits = action
.edits
.into_iter()
.map(|edit| {
let start_pos = line_index.line_col(edit.text_range.start());
let end_pos = line_index.line_col(edit.text_range.end());
let start_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, start_pos)
.unwrap();
let end_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, end_pos)
.unwrap();

TextEdit {
start_line_number: start_wide.line,
start_column: start_wide.col,
end_line_number: end_wide.line,
end_column: end_wide.col,
text: edit.text.unwrap_or_default(),
}
})
.collect();

WasmCodeAction {
title: action.title,
edits,
kind: match action.kind {
squawk_ide::code_actions::ActionKind::QuickFix => "quickfix",
squawk_ide::code_actions::ActionKind::RefactorRewrite => "refactor.rewrite",
}
.to_string(),
}
})
.collect::<Vec<_>>()
});

serde_wasm_bindgen::to_value(&converted).map_err(into_error)
}

fn position_to_offset(
line_index: &LineIndex,
line: u32,
col: u32,
) -> Result<rowan::TextSize, Error> {
let wide_pos = line_index::WideLineCol { line, col };

let pos = line_index
.to_utf8(line_index::WideEncoding::Utf16, wide_pos)
.ok_or_else(|| Error::new("Invalid position"))?;

line_index
.offset(pos)
.ok_or_else(|| Error::new("Invalid position offset"))
}

#[derive(Serialize)]
struct LocationRange {
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
}

#[derive(Serialize)]
struct WasmCodeAction {
title: String,
edits: Vec<TextEdit>,
kind: String,
}

#[derive(Serialize)]
struct WasmDocumentSymbol {
name: String,
detail: Option<String>,
kind: String,
start_line: u32,
start_column: u32,
end_line: u32,
end_column: u32,
selection_start_line: u32,
selection_start_column: u32,
selection_end_line: u32,
selection_end_column: u32,
children: Vec<WasmDocumentSymbol>,
}

fn convert_document_symbol(
line_index: &LineIndex,
symbol: squawk_ide::document_symbols::DocumentSymbol,
) -> WasmDocumentSymbol {
let full_start = line_index.line_col(symbol.full_range.start());
let full_end = line_index.line_col(symbol.full_range.end());
let full_start_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, full_start)
.unwrap();
let full_end_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, full_end)
.unwrap();

let focus_start = line_index.line_col(symbol.focus_range.start());
let focus_end = line_index.line_col(symbol.focus_range.end());
let focus_start_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, focus_start)
.unwrap();
let focus_end_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, focus_end)
.unwrap();

WasmDocumentSymbol {
name: symbol.name,
detail: symbol.detail,
kind: match symbol.kind {
squawk_ide::document_symbols::DocumentSymbolKind::Table => "table",
squawk_ide::document_symbols::DocumentSymbolKind::Function => "function",
squawk_ide::document_symbols::DocumentSymbolKind::Column => "column",
}
.to_string(),
start_line: full_start_wide.line,
start_column: full_start_wide.col,
end_line: full_end_wide.line,
end_column: full_end_wide.col,
selection_start_line: focus_start_wide.line,
selection_start_column: focus_start_wide.col,
selection_end_line: focus_end_wide.line,
selection_end_column: focus_end_wide.col,
children: symbol
.children
.into_iter()
.map(|child| convert_document_symbol(line_index, child))
.collect(),
}
}

#[wasm_bindgen]
pub fn inlay_hints(content: String) -> Result<JsValue, Error> {
let parse = squawk_syntax::SourceFile::parse(&content);
let line_index = LineIndex::new(&content);
let hints = squawk_ide::inlay_hints::inlay_hints(&parse.tree());

let converted: Vec<WasmInlayHint> = hints
.into_iter()
.map(|hint| {
let position = line_index.line_col(hint.position);
let position_wide = line_index
.to_wide(line_index::WideEncoding::Utf16, position)
.unwrap();

WasmInlayHint {
line: position_wide.line,
column: position_wide.col,
label: hint.label,
kind: match hint.kind {
squawk_ide::inlay_hints::InlayHintKind::Type => "type",
squawk_ide::inlay_hints::InlayHintKind::Parameter => "parameter",
}
.to_string(),
}
})
.collect();

serde_wasm_bindgen::to_value(&converted).map_err(into_error)
}

#[derive(Serialize)]
struct WasmInlayHint {
line: u32,
column: u32,
label: String,
kind: String,
}
5 changes: 3 additions & 2 deletions playground/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "wasm-pack build --target web ../crates/squawk_wasm --out-dir ../../playground/src/pkg && vite build",
"dev": "npm run build:wasm && vite",
"build": "npm run build:wasm && vite build",
"build:wasm": "wasm-pack build --target web ../crates/squawk_wasm --out-dir ../../playground/src/pkg",
"deploy": "netlify deploy --prod --dir dist",
"lint": "eslint .",
"preview": "vite preview"
Expand Down
42 changes: 42 additions & 0 deletions playground/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@ import {
decompress,
decompressFromEncodedURIComponent,
} from "lz-string"
import {
provideInlayHints,
provideHover,
provideDefinition,
provideReferences,
provideDocumentSymbols,
} from "./providers"

const modes = ["Lint", "Syntax Tree", "Tokens"] as const
const STORAGE_KEY = "playground-history-v1"
Expand Down Expand Up @@ -424,6 +431,36 @@ function Editor({
},
)

const hoverProvider = monaco.languages.registerHoverProvider("pgsql", {
provideHover,
})

const definitionProvider = monaco.languages.registerDefinitionProvider(
"pgsql",
{
provideDefinition,
},
)

const referencesProvider = monaco.languages.registerReferenceProvider(
"pgsql",
{
provideReferences,
},
)

const documentSymbolProvider =
monaco.languages.registerDocumentSymbolProvider("pgsql", {
provideDocumentSymbols,
})

const inlayHintsProvider = monaco.languages.registerInlayHintsProvider(
"pgsql",
{
provideInlayHints,
},
)

editor.onDidChangeModelContent(() => {
onChangeText(editor.getValue())
})
Expand All @@ -434,6 +471,11 @@ function Editor({
return () => {
editorRef.current = null
codeActionProvider.dispose()
hoverProvider.dispose()
definitionProvider.dispose()
referencesProvider.dispose()
documentSymbolProvider.dispose()
inlayHintsProvider.dispose()
editor?.dispose()
tokenProvider.dispose()
}
Expand Down
Loading
Loading