// Document symbols and go-to-definition provider for the ƿit LSP. // SymbolKind constants (LSP spec) def KIND_FUNCTION = 12 def KIND_VARIABLE = 13 def KIND_CONSTANT = 14 // Walk AST to extract document symbols (top-level vars/defs and functions). var document_symbols = function(doc) { var symbols = [] var ast = doc.ast var _i = 0 var _j = 0 var stmt = null var decl = null var name = null var kind = null var range = null if (ast == null || ast.statements == null) { return symbols } while (_i < length(ast.statements)) { stmt = ast.statements[_i] if (stmt.kind == "var" || stmt.kind == "def") { name = null kind = KIND_VARIABLE if (stmt.left != null && stmt.left.name != null) { name = stmt.left.name } if (stmt.kind == "def") { kind = KIND_CONSTANT } if (stmt.right != null && (stmt.right.kind == "function" || stmt.right.kind == "arrow function")) { kind = KIND_FUNCTION } if (name != null) { range = { start: {line: stmt.from_row, character: stmt.from_column}, end: {line: stmt.to_row, character: stmt.to_column} } symbols[] = { name: name, kind: kind, range: range, selectionRange: { start: {line: stmt.left.from_row, character: stmt.left.from_column}, end: {line: stmt.left.to_row, character: stmt.left.to_column} } } } } if (stmt.kind == "var_list" && stmt.list != null) { _j = 0 while (_j < length(stmt.list)) { decl = stmt.list[_j] if (decl.left != null && decl.left.name != null) { kind = (decl.kind == "def") ? KIND_CONSTANT : KIND_VARIABLE if (decl.right != null && (decl.right.kind == "function" || decl.right.kind == "arrow function")) { kind = KIND_FUNCTION } range = { start: {line: decl.from_row, character: decl.from_column}, end: {line: decl.to_row, character: decl.to_column} } symbols[] = { name: decl.left.name, kind: kind, range: range, selectionRange: { start: {line: decl.left.from_row, character: decl.left.from_column}, end: {line: decl.left.to_row, character: decl.left.to_column} } } } _j = _j + 1 } } _i = _i + 1 } return symbols } // Find the declaration location of a name at a given position. // Uses the semantic index when available, falls back to AST walk. var definition = function(doc, line, col, token_at) { var tok = token_at(doc, line, col) var name = null var _i = 0 var sym = null var decl = null if (tok == null || tok.kind != "name" || tok.value == null) { return null } name = tok.value // Use the semantic index if available if (doc.index != null) { _i = 0 while (_i < length(doc.index.symbols)) { sym = doc.index.symbols[_i] if (sym.name == name && sym.decl_span != null) { return { uri: doc.uri, range: { start: {line: sym.decl_span.from_row, character: sym.decl_span.from_col}, end: {line: sym.decl_span.to_row, character: sym.decl_span.to_col} } } } _i = _i + 1 } } // Fallback: walk statements for var/def with this name if (doc.ast != null) { decl = find_declaration(doc.ast.statements, name) if (decl != null) { return { uri: doc.uri, range: { start: {line: decl.from_row, character: decl.from_column}, end: {line: decl.to_row, character: decl.to_column} } } } } return null } // Recursively search statements for a var/def declaration of a given name. var find_declaration = function(statements, name) { var _i = 0 var _j = 0 var stmt = null var result = null if (statements == null) { return null } while (_i < length(statements)) { stmt = statements[_i] // Direct var/def if ((stmt.kind == "var" || stmt.kind == "def") && stmt.left != null && stmt.left.name == name) { return stmt } // var_list if (stmt.kind == "var_list" && stmt.list != null) { _j = 0 while (_j < length(stmt.list)) { if (stmt.list[_j].left != null && stmt.list[_j].left.name == name) { return stmt.list[_j] } _j = _j + 1 } } // Recurse into blocks if (stmt.statements != null) { result = find_declaration(stmt.statements, name) if (result != null) { return result } } // if/else if (stmt.kind == "if") { if (stmt.then != null && stmt.then.statements != null) { result = find_declaration(stmt.then.statements, name) if (result != null) { return result } } if (stmt.else != null && stmt.else.statements != null) { result = find_declaration(stmt.else.statements, name) if (result != null) { return result } } } // Function body if ((stmt.kind == "function" || stmt.kind == "arrow function") && stmt.statements != null) { result = find_declaration(stmt.statements, name) if (result != null) { return result } } // var/def with function right side if ((stmt.kind == "var" || stmt.kind == "def") && stmt.right != null) { if ((stmt.right.kind == "function" || stmt.right.kind == "arrow function") && stmt.right.statements != null) { result = find_declaration(stmt.right.statements, name) if (result != null) { return result } } } _i = _i + 1 } return null } return { document_symbols: document_symbols, definition: definition }