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
28 changes: 26 additions & 2 deletions crate/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -893,10 +893,34 @@ impl ParsedDocument {
}
}

let is_multi_expr = matches!(&parse_result.root.kind,
NodeKind::Module(exprs) if exprs.items.len() > 1);

for bind_idx in bind_sites {
if used_binds.contains(&bind_idx) { continue; }
let cps_id = CpsId(bind_idx);
if resolved.bind_scope.get(cps_id) == &Some(root_scope_id) { continue; }

// Skip non-Ident bind sites (synthetic CPS nodes from lambdas,
// pattern matching, etc.)
let is_ident = cps.origin.get(cps_id)
.and_then(|ast_id| *ast_index.get(ast_id))
.is_some_and(|node| matches!(&node.kind, NodeKind::Ident(_)));
if !is_ident { continue; }

// Skip module-level bindings (they are exports).
// Single-expr module: bindings are directly in root scope.
// Multi-expr module: bindings are in the synthetic module fn
// scope, whose parent_scope is root.
if let Some(scope) = resolved.bind_scope.get(cps_id) {
if *scope == root_scope_id { continue; }
if is_multi_expr {
let parent_is_root = resolved.parent_scope.try_get(*scope)
.and_then(|p| *p)
.is_some_and(|parent| parent == root_scope_id);
if parent_is_root { continue; }
}
}

if let Some(ast_id) = *cps.origin.get(cps_id) {
if let Some(node) = *ast_index.get(ast_id) {
let line = node.loc.start.line.saturating_sub(1);
Expand All @@ -905,7 +929,7 @@ impl ParsedDocument {
let end_col = node.loc.end.col;
let name = match &node.kind {
NodeKind::Ident(s) => s.replace('\\', "\\\\").replace('"', "\\\""),
_ => "?".to_string(),
_ => continue,
};
diag_entries.push(format!(
r#"{{"line":{line},"col":{col},"endLine":{end_line},"endCol":{end_col},"message":"unused binding '{name}'","source":"name_res","severity":"warning"}}"#
Expand Down
6 changes: 0 additions & 6 deletions src/ast-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ export class AstPanel {

this.renderNode(root, this.container, 0)
this.rebuildFlat()

// Sync with current cursor position after update
const pos = this.editor.getPosition()
if (pos) {
this.highlightAtPosition(pos.lineNumber - 1, pos.column - 1)
}
}

// Render one node as a group (row + children wrapper) and recurse.
Expand Down
44 changes: 43 additions & 1 deletion src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ import 'monaco-editor/esm/vs/editor/contrib/wordHighlighter/browser/highlightDec
import 'monaco-editor/esm/vs/editor/contrib/caretOperations/browser/caretOperations.js' // transpose chars
import 'monaco-editor/esm/vs/editor/contrib/cursorUndo/browser/cursorUndo.js' // cursor stack undo
import 'monaco-editor/esm/vs/editor/contrib/hover/browser/hoverContribution.js' // hover tooltips (diagnostics, symbols)
import 'monaco-editor/esm/vs/editor/contrib/gotoSymbol/browser/goToCommands.js' // go-to-definition (F12 / Cmd+click)
import 'monaco-editor/esm/vs/editor/contrib/gotoSymbol/browser/link/goToDefinitionAtPosition.js' // Ctrl/Cmd+click inline
import { compile } from './compiler.js'
import { run } from './wasi-shim.js'
import { FinkTokenizer, type LexToken } from './tokenizer.js'
Expand Down Expand Up @@ -59,6 +61,11 @@ let lastSemanticTokens: Uint32Array = new Uint32Array(0)
let lastDiagnostics: string = '[]'
let lastParseMs = 0

// Last ParsedDocument kept alive for cursor-time queries (go-to-def, references).
// Freed and replaced on every successful re-parse.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let lastDoc: any = null

// Forward declaration — defined after the editor is created (needs DOM + editor).
let applyDiagnosticToggles: () => void = () => {}

Expand Down Expand Up @@ -105,7 +112,9 @@ function _reparse(src: string, _modelVersion: number): void {
lastDiagnostics = doc.get_diagnostics()
cpsJson = doc.get_cps()
cpsLiftedJson = doc.get_cps_lifted()
doc.free()
// Keep doc alive for cursor-time queries (go-to-def, references).
if (lastDoc) try { lastDoc.free() } catch (_) { /* already freed */ }
lastDoc = doc
} catch (e) {
console.warn('[fink] CPS analysis crashed (name resolution disabled):', e)
try { doc.free() } catch (_) { /* handle already poisoned */ }
Expand Down Expand Up @@ -235,6 +244,27 @@ monaco.languages.registerDocumentSemanticTokensProvider('fink', {
releaseDocumentSemanticTokens() {},
})

// Definition provider — delegates to the WASM ParsedDocument kept alive across
// re-parses. Requires run_analysis() to have succeeded (name resolution).
monaco.languages.registerDefinitionProvider('fink', {
provideDefinition(model, position) {
if (!lastDoc) return undefined
// Monaco positions are 1-based; WASM API is 0-based.
const data: Uint32Array = lastDoc.get_definition(
position.lineNumber - 1,
position.column - 1,
)
if (data.length !== 4) return undefined
return {
uri: model.uri,
range: new monaco.Range(
data[0] + 1, data[1] + 1,
data[2] + 1, data[3] + 1,
),
}
},
})

// ---------------------------------------------------------------------------
// Theme — reads colors from CSS variables (set by embedding page or dev wrapper)
// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -382,6 +412,18 @@ for (const tab of document.querySelectorAll<HTMLElement>('.fink-tab')) {
if (!SYNC_TABS.has(activeTab)) {
// Passive tab (e.g. Output) — clear all decorations and stop syncing.
clearAllDecorations()
} else {
// Sync tab activated — sync to current cursor position.
clearAllDecorations()
const pos = editor.getPosition()
if (pos) {
const line = pos.lineNumber - 1
const col = pos.column - 1
if (activeTab === 'fink-tokens') tokensPanel?.highlightAtPosition(line, col)
if (activeTab === 'fink-ast') astPanel?.highlightAtPosition(line, col)
if (activeTab === 'fink-cps') cpsPanel?.syncFromSource(line, col)
if (activeTab === 'fink-cps-lifted') cpsPanelLifted?.syncFromSource(line, col)
}
}
})
}
Expand Down
6 changes: 0 additions & 6 deletions src/tokens-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,6 @@ export class TokensPanel {

this.container.innerHTML = ''
this.container.appendChild(frag)

// Sync with current cursor position
const pos = this.editor.getPosition()
if (pos) {
this.highlightAtPosition(pos.lineNumber - 1, pos.column - 1)
}
}

clearEditorHighlight(): void {
Expand Down
Loading