summaryrefslogtreecommitdiff
path: root/lua/99/editor
diff options
context:
space:
mode:
authorThePrimeAgain <theprimeagain@theprimeagain.com>2026-01-14 18:32:30 -0700
committerThePrimeAgain <theprimeagain@theprimeagain.com>2026-01-14 18:32:30 -0700
commit6c88a9537ae829cd8a4b92312e115c311e6beb42 (patch)
treed2b1796d453cbbd7374ed740fa50a1be411362b1 /lua/99/editor
parent701b4c34a1e3de34327d21cc6f51c79b49d26b51 (diff)
downloada4-6c88a9537ae829cd8a4b92312e115c311e6beb42.tar.xz
a4-6c88a9537ae829cd8a4b92312e115c311e6beb42.zip
fuzzy_find: for agents stuff
Diffstat (limited to 'lua/99/editor')
-rw-r--r--lua/99/editor/init.lua4
-rw-r--r--lua/99/editor/lsp.lua776
-rw-r--r--lua/99/editor/treesitter.lua330
3 files changed, 551 insertions, 559 deletions
diff --git a/lua/99/editor/init.lua b/lua/99/editor/init.lua
index 70c24a3..ff0e8ab 100644
--- a/lua/99/editor/init.lua
+++ b/lua/99/editor/init.lua
@@ -1,4 +1,4 @@
return {
- treesitter = require("99.editor.treesitter"),
- -- lsp = require("99.editor.lsp"),
+ treesitter = require("99.editor.treesitter"),
+ -- lsp = require("99.editor.lsp"),
}
diff --git a/lua/99/editor/lsp.lua b/lua/99/editor/lsp.lua
index 7f8e7d3..caf4b94 100644
--- a/lua/99/editor/lsp.lua
+++ b/lua/99/editor/lsp.lua
@@ -23,8 +23,8 @@
--- @param node _99.treesitter.Node The treesitter node to convert
--- @return LspPosition The LSP-compatible position (0-based line and character)
local function ts_node_to_lsp_position(node)
- local start_row, start_col, _, _ = node:range()
- return { line = start_row, character = start_col }
+ local start_row, start_col, _, _ = node:range()
+ return { line = start_row, character = start_col }
end
--- Makes an LSP textDocument/definition request for a given position.
@@ -33,17 +33,17 @@ end
--- @param position LspPosition The position in the document to get definitions for
--- @param cb fun(res: LspDefinitionResult[] | nil): nil Callback receiving the definition results
local function get_lsp_definitions(buffer, position, cb)
- local params = vim.lsp.util.make_position_params()
- params.position = position
+ local params = vim.lsp.util.make_position_params()
+ params.position = position
- vim.lsp.buf_request(
- buffer,
- "textDocument/definition",
- params,
- function(_, result, _, _)
- cb(result)
- end
- )
+ vim.lsp.buf_request(
+ buffer,
+ "textDocument/definition",
+ params,
+ function(_, result, _, _)
+ cb(result)
+ end
+ )
end
--- Resolves a Lua require path to an absolute file path using Neovim's runtime.
@@ -51,22 +51,22 @@ end
--- @param require_path string The Lua require path (e.g., "99.logger.logger")
--- @return string|nil The absolute file path, or nil if it can't be resolved
local function resolve_require_path(require_path)
- local relative_path = "lua/" .. require_path:gsub("%.", "/") .. ".lua"
- local results = vim.api.nvim_get_runtime_file(relative_path, false)
+ local relative_path = "lua/" .. require_path:gsub("%.", "/") .. ".lua"
+ local results = vim.api.nvim_get_runtime_file(relative_path, false)
- if results and #results > 0 then
- return results[1]
- end
+ if results and #results > 0 then
+ return results[1]
+ end
- -- Also try init.lua for module directories
- local init_path = "lua/" .. require_path:gsub("%.", "/") .. "/init.lua"
- results = vim.api.nvim_get_runtime_file(init_path, false)
+ -- Also try init.lua for module directories
+ local init_path = "lua/" .. require_path:gsub("%.", "/") .. "/init.lua"
+ results = vim.api.nvim_get_runtime_file(init_path, false)
- if results and #results > 0 then
- return results[1]
- end
+ if results and #results > 0 then
+ return results[1]
+ end
- return nil
+ return nil
end
--- Ensures a buffer is loaded and has LSP attached, then calls the callback.
@@ -74,23 +74,23 @@ end
--- @param filepath string The file path to load
--- @param cb fun(bufnr: number|nil, err: string|nil): nil Callback with buffer number or error
local function ensure_buffer_with_lsp(filepath, cb)
- local bufnr = vim.fn.bufnr(filepath)
- if bufnr == -1 then
- bufnr = vim.fn.bufadd(filepath)
- end
+ local bufnr = vim.fn.bufnr(filepath)
+ if bufnr == -1 then
+ bufnr = vim.fn.bufadd(filepath)
+ end
- if not vim.api.nvim_buf_is_loaded(bufnr) then
- vim.fn.bufload(bufnr)
- end
+ if not vim.api.nvim_buf_is_loaded(bufnr) then
+ vim.fn.bufload(bufnr)
+ end
- vim.schedule(function()
- local clients = vim.lsp.get_clients({ bufnr = bufnr })
- if #clients == 0 then
- cb(nil, "No LSP client attached to buffer for: " .. filepath)
- return
- end
- cb(bufnr, nil)
- end)
+ vim.schedule(function()
+ local clients = vim.lsp.get_clients({ bufnr = bufnr })
+ if #clients == 0 then
+ cb(nil, "No LSP client attached to buffer for: " .. filepath)
+ return
+ end
+ cb(bufnr, nil)
+ end)
end
--- Makes an LSP textDocument/hover request for a given position.
@@ -99,23 +99,23 @@ end
--- @param position LspPosition The position to hover at
--- @param cb fun(result: table|nil, err: string|nil): nil Callback with hover result
local function get_lsp_hover(bufnr, position, cb)
- local params = {
- textDocument = { uri = vim.uri_from_bufnr(bufnr) },
- position = position,
- }
+ local params = {
+ textDocument = { uri = vim.uri_from_bufnr(bufnr) },
+ position = position,
+ }
- vim.lsp.buf_request(
- bufnr,
- "textDocument/hover",
- params,
- function(err, result, _, _)
- if err then
- cb(nil, vim.inspect(err))
- return
- end
- cb(result, nil)
- end
- )
+ vim.lsp.buf_request(
+ bufnr,
+ "textDocument/hover",
+ params,
+ function(err, result, _, _)
+ if err then
+ cb(nil, vim.inspect(err))
+ return
+ end
+ cb(result, nil)
+ end
+ )
end
--- Finds the return statement in a Lua file and extracts the exported keys.
@@ -123,51 +123,51 @@ end
--- @param bufnr number The buffer number
--- @return { name: string, line: number, col: number }[] List of exported names with positions
local function find_export_keys(bufnr)
- local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
- local exports = {}
+ local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
+ local exports = {}
- -- Find the last return statement
- local return_line_idx = nil
- for i = #lines, 1, -1 do
- if lines[i]:match("^%s*return%s+") then
- return_line_idx = i
- break
- end
+ -- Find the last return statement
+ local return_line_idx = nil
+ for i = #lines, 1, -1 do
+ if lines[i]:match("^%s*return%s+") then
+ return_line_idx = i
+ break
end
+ end
- if not return_line_idx then
- return exports
- end
+ if not return_line_idx then
+ return exports
+ end
- -- Check if it's a simple `return M` style
- local simple_return =
- lines[return_line_idx]:match("^%s*return%s+([%w_]+)%s*$")
- if simple_return then
- local col = lines[return_line_idx]:find(simple_return)
+ -- Check if it's a simple `return M` style
+ local simple_return =
+ lines[return_line_idx]:match("^%s*return%s+([%w_]+)%s*$")
+ if simple_return then
+ local col = lines[return_line_idx]:find(simple_return)
+ table.insert(exports, {
+ name = simple_return,
+ line = return_line_idx - 1,
+ col = col - 1,
+ })
+ return exports
+ end
+
+ -- Parse `return { Key = Value, ... }` style
+ for i = return_line_idx, #lines do
+ local line = lines[i]
+ for key, col_start in line:gmatch("()([%w_]+)%s*=") do
+ key, col_start = col_start, key
+ if key ~= "" and not key:match("^%d") then
table.insert(exports, {
- name = simple_return,
- line = return_line_idx - 1,
- col = col - 1,
+ name = key,
+ line = i - 1,
+ col = col_start - 1,
})
- return exports
- end
-
- -- Parse `return { Key = Value, ... }` style
- for i = return_line_idx, #lines do
- local line = lines[i]
- for key, col_start in line:gmatch("()([%w_]+)%s*=") do
- key, col_start = col_start, key
- if key ~= "" and not key:match("^%d") then
- table.insert(exports, {
- name = key,
- line = i - 1,
- col = col_start - 1,
- })
- end
- end
+ end
end
+ end
- return exports
+ return exports
end
--- Gets the hover information for each exported symbol using LSP.
@@ -176,59 +176,55 @@ end
--- @param export_keys { name: string, line: number, col: number }[] The export positions
--- @param cb fun(results: table<string, string>): nil Callback with name -> hover info map
local function get_exports_hover_info(bufnr, export_keys, cb)
- if #export_keys == 0 then
- cb({})
- return
- end
+ if #export_keys == 0 then
+ cb({})
+ return
+ end
- local results = {}
- local pending = #export_keys
+ local results = {}
+ local pending = #export_keys
- for _, export in ipairs(export_keys) do
- local line_text = vim.api.nvim_buf_get_lines(
- bufnr,
- export.line,
- export.line + 1,
- false
- )[1]
+ for _, export in ipairs(export_keys) do
+ local line_text =
+ vim.api.nvim_buf_get_lines(bufnr, export.line, export.line + 1, false)[1]
- local pattern = export.name .. "%s*=%s*()"
- local value_start = line_text:match(pattern)
- local hover_col = value_start and (value_start - 1) or export.col
- local position = { line = export.line, character = hover_col }
+ local pattern = export.name .. "%s*=%s*()"
+ local value_start = line_text:match(pattern)
+ local hover_col = value_start and (value_start - 1) or export.col
+ local position = { line = export.line, character = hover_col }
- get_lsp_hover(bufnr, position, function(result, _)
- if result and result.contents then
- local content = result.contents
- if type(content) == "table" then
- if content.value then
- results[export.name] = content.value
- elseif content.kind == "markdown" then
- results[export.name] = content.value
- else
- local parts = {}
- for _, part in ipairs(content) do
- if type(part) == "string" then
- table.insert(parts, part)
- elseif part.value then
- table.insert(parts, part.value)
- end
- end
- results[export.name] = table.concat(parts, "\n")
- end
- else
- results[export.name] = tostring(content)
- end
- else
- results[export.name] = "unknown"
+ get_lsp_hover(bufnr, position, function(result, _)
+ if result and result.contents then
+ local content = result.contents
+ if type(content) == "table" then
+ if content.value then
+ results[export.name] = content.value
+ elseif content.kind == "markdown" then
+ results[export.name] = content.value
+ else
+ local parts = {}
+ for _, part in ipairs(content) do
+ if type(part) == "string" then
+ table.insert(parts, part)
+ elseif part.value then
+ table.insert(parts, part.value)
+ end
end
+ results[export.name] = table.concat(parts, "\n")
+ end
+ else
+ results[export.name] = tostring(content)
+ end
+ else
+ results[export.name] = "unknown"
+ end
- pending = pending - 1
- if pending == 0 then
- cb(results)
- end
- end)
- end
+ pending = pending - 1
+ if pending == 0 then
+ cb(results)
+ end
+ end)
+ end
end
--- Finds all method/field definitions for a class in the source file.
@@ -237,32 +233,32 @@ end
--- @param class_name string The name of the class (e.g., "Lsp")
--- @return { name: string, line: number, col: number }[] List of member positions
local function find_class_member_positions(file_lines, class_name)
- local members = {}
+ local members = {}
- for i, line in ipairs(file_lines) do
- local method_name =
- line:match("^%s*function%s+" .. class_name .. "[%.:]([%w_]+)%s*%(")
- if method_name then
- local col = line:find(method_name, 1, true)
- table.insert(members, {
- name = method_name,
- line = i - 1,
- col = col and (col - 1) or 0,
- })
- end
+ for i, line in ipairs(file_lines) do
+ local method_name =
+ line:match("^%s*function%s+" .. class_name .. "[%.:]([%w_]+)%s*%(")
+ if method_name then
+ local col = line:find(method_name, 1, true)
+ table.insert(members, {
+ name = method_name,
+ line = i - 1,
+ col = col and (col - 1) or 0,
+ })
+ end
- local field_name = line:match("^%s*" .. class_name .. "%.([%w_]+)%s*=")
- if field_name and not line:match("^%s*function") then
- local col = line:find(field_name, 1, true)
- table.insert(members, {
- name = field_name,
- line = i - 1,
- col = col and (col - 1) or 0,
- })
- end
+ local field_name = line:match("^%s*" .. class_name .. "%.([%w_]+)%s*=")
+ if field_name and not line:match("^%s*function") then
+ local col = line:find(field_name, 1, true)
+ table.insert(members, {
+ name = field_name,
+ line = i - 1,
+ col = col and (col - 1) or 0,
+ })
end
+ end
- return members
+ return members
end
--- Gets hover information for each class member using LSP.
@@ -271,52 +267,52 @@ end
--- @param member_positions { name: string, line: number, col: number }[] Member positions
--- @param cb fun(results: table<string, string>): nil Callback with name -> type info map
local function get_class_members_hover(bufnr, member_positions, cb)
- if #member_positions == 0 then
- cb({})
- return
- end
+ if #member_positions == 0 then
+ cb({})
+ return
+ end
- local results = {}
- local pending = #member_positions
+ local results = {}
+ local pending = #member_positions
- for _, member in ipairs(member_positions) do
- local position = { line = member.line, character = member.col }
+ for _, member in ipairs(member_positions) do
+ local position = { line = member.line, character = member.col }
- get_lsp_hover(bufnr, position, function(result, _)
- local hover_text = "unknown"
+ get_lsp_hover(bufnr, position, function(result, _)
+ local hover_text = "unknown"
- if result and result.contents then
- local content = result.contents
+ if result and result.contents then
+ local content = result.contents
- if type(content) == "table" then
- if content.value then
- hover_text = content.value
- elseif content.kind then
- hover_text = content.value or ""
- else
- local parts = {}
- for _, part in ipairs(content) do
- if type(part) == "string" then
- table.insert(parts, part)
- elseif part.value then
- table.insert(parts, part.value)
- end
- end
- hover_text = table.concat(parts, "\n")
- end
- else
- hover_text = tostring(content)
- end
+ if type(content) == "table" then
+ if content.value then
+ hover_text = content.value
+ elseif content.kind then
+ hover_text = content.value or ""
+ else
+ local parts = {}
+ for _, part in ipairs(content) do
+ if type(part) == "string" then
+ table.insert(parts, part)
+ elseif part.value then
+ table.insert(parts, part.value)
+ end
end
+ hover_text = table.concat(parts, "\n")
+ end
+ else
+ hover_text = tostring(content)
+ end
+ end
- results[member.name] = hover_text
+ results[member.name] = hover_text
- pending = pending - 1
- if pending == 0 then
- cb(results)
- end
- end)
- end
+ pending = pending - 1
+ if pending == 0 then
+ cb(results)
+ end
+ end)
+ end
end
--- Removes markdown fencing and cleans hover output.
@@ -324,24 +320,24 @@ end
--- @param hover_text string The raw hover text from LSP
--- @return string The cleaned type information
local function format_hover_output(hover_text)
- if not hover_text or hover_text == "unknown" then
- return "unknown"
- end
+ if not hover_text or hover_text == "unknown" then
+ return "unknown"
+ end
- local lines = {}
+ local lines = {}
- for line in hover_text:gmatch("[^\n]+") do
- if not line:match("^```") then
- local cleaned = line
- cleaned = cleaned:gsub("^local%s+", "")
- cleaned = cleaned:gsub("^[%w_]+:%s*", "")
- if cleaned ~= "" then
- table.insert(lines, cleaned)
- end
- end
+ for line in hover_text:gmatch("[^\n]+") do
+ if not line:match("^```") then
+ local cleaned = line
+ cleaned = cleaned:gsub("^local%s+", "")
+ cleaned = cleaned:gsub("^[%w_]+:%s*", "")
+ if cleaned ~= "" then
+ table.insert(lines, cleaned)
+ end
end
+ end
- return table.concat(lines, "\n")
+ return table.concat(lines, "\n")
end
--- Formats a function hover result into TypeScript-style signature.
@@ -349,21 +345,21 @@ end
--- @param hover_text string The hover text from LSP
--- @return string The formatted signature like "(a: number, b: string): boolean"
local function format_function_signature(hover_text)
- local clean = hover_text:gsub("```%w*\n?", ""):gsub("```", "")
- clean = clean:gsub("^%s*", ""):gsub("%s*$", "")
+ local clean = hover_text:gsub("```%w*\n?", ""):gsub("```", "")
+ clean = clean:gsub("^%s*", ""):gsub("%s*$", "")
- local params, ret =
- clean:match("function%s*[%w_%.%:]*%((.-)%)%s*:%s*([^\n]+)")
+ local params, ret =
+ clean:match("function%s*[%w_%.%:]*%((.-)%)%s*:%s*([^\n]+)")
+ if params then
+ return string.format("(%s): %s", params, ret or "nil")
+ else
+ params = clean:match("function%s*[%w_%.%:]*%((.-)%)")
if params then
- return string.format("(%s): %s", params, ret or "nil")
- else
- params = clean:match("function%s*[%w_%.%:]*%((.-)%)")
- if params then
- return string.format("(%s): nil", params)
- end
+ return string.format("(%s): nil", params)
end
+ end
- return clean
+ return clean
end
--- Extracts all enum values from source (not truncated like hover).
@@ -372,34 +368,34 @@ end
--- @param symbol_name string The name of the enum symbol
--- @return string[] Array of enum entries like "Key = value"
local function expand_enum_values(file_lines, symbol_name)
- local values = {}
-
- for i, line in ipairs(file_lines) do
- if
- line:match("local%s+" .. symbol_name .. "%s*=")
- or line:match(symbol_name .. "%s*=%s*{")
- then
- local j = i
- while j <= #file_lines do
- local enum_line = file_lines[j]
+ local values = {}
- if enum_line:match("^%s*}") then
- break
- end
+ for i, line in ipairs(file_lines) do
+ if
+ line:match("local%s+" .. symbol_name .. "%s*=")
+ or line:match(symbol_name .. "%s*=%s*{")
+ then
+ local j = i
+ while j <= #file_lines do
+ local enum_line = file_lines[j]
- local key, value = enum_line:match("^%s*([%w_]+)%s*=%s*([^,]+)")
- if key and value then
- value = value:match("^%s*(.-)%s*,?%s*$")
- table.insert(values, key .. " = " .. value)
- end
+ if enum_line:match("^%s*}") then
+ break
+ end
- j = j + 1
- end
- break
+ local key, value = enum_line:match("^%s*([%w_]+)%s*=%s*([^,]+)")
+ if key and value then
+ value = value:match("^%s*(.-)%s*,?%s*$")
+ table.insert(values, key .. " = " .. value)
end
+
+ j = j + 1
+ end
+ break
end
+ end
- return values
+ return values
end
--------------------------------------------------------------------------------
@@ -416,9 +412,9 @@ Lsp.__index = Lsp
--- @param config _99.Options The configuration options
--- @return Lsp A new Lsp instance
function Lsp.new(config)
- return setmetatable({
- config = config,
- }, Lsp)
+ return setmetatable({
+ config = config,
+ }, Lsp)
end
--------------------------------------------------------------------------------
@@ -431,97 +427,97 @@ end
--- @param require_path string The Lua require path (e.g., "99", "99.logger.logger")
--- @param cb fun(result: string, err: string|nil): nil Callback with formatted string or error
function Lsp.stringify_module_exports(require_path, cb)
- local resolved_path = resolve_require_path(require_path)
+ local resolved_path = resolve_require_path(require_path)
- if not resolved_path then
- cb(
- "",
- "Could not resolve module path: "
- .. require_path
- .. ". The module may not be in runtimepath."
- )
- return
- end
+ if not resolved_path then
+ cb(
+ "",
+ "Could not resolve module path: "
+ .. require_path
+ .. ". The module may not be in runtimepath."
+ )
+ return
+ end
- local uri = vim.uri_from_fname(resolved_path)
+ local uri = vim.uri_from_fname(resolved_path)
- ensure_buffer_with_lsp(resolved_path, function(bufnr, err)
- if err then
- cb("", err)
- return
- end
+ ensure_buffer_with_lsp(resolved_path, function(bufnr, err)
+ if err then
+ cb("", err)
+ return
+ end
- local export_keys = find_export_keys(bufnr)
+ local export_keys = find_export_keys(bufnr)
- if #export_keys == 0 then
- cb("", "No exports found in return statement")
- return
- end
+ if #export_keys == 0 then
+ cb("", "No exports found in return statement")
+ return
+ end
- get_exports_hover_info(bufnr, export_keys, function(hover_results)
- local file_lines = vim.fn.readfile(resolved_path)
+ get_exports_hover_info(bufnr, export_keys, function(hover_results)
+ local file_lines = vim.fn.readfile(resolved_path)
- -- Collect classes that need member expansion
- local classes_to_expand = {}
- for _, export in ipairs(export_keys) do
- local hover = hover_results[export.name] or "unknown"
- local is_class = hover:match("__index") ~= nil
- or hover:match(":%s*[%w_]+%s*{") ~= nil
+ -- Collect classes that need member expansion
+ local classes_to_expand = {}
+ for _, export in ipairs(export_keys) do
+ local hover = hover_results[export.name] or "unknown"
+ local is_class = hover:match("__index") ~= nil
+ or hover:match(":%s*[%w_]+%s*{") ~= nil
- if is_class then
- local member_positions =
- find_class_member_positions(file_lines, export.name)
- if #member_positions > 0 then
- table.insert(classes_to_expand, {
- name = export.name,
- positions = member_positions,
- })
- end
- end
- end
+ if is_class then
+ local member_positions =
+ find_class_member_positions(file_lines, export.name)
+ if #member_positions > 0 then
+ table.insert(classes_to_expand, {
+ name = export.name,
+ positions = member_positions,
+ })
+ end
+ end
+ end
- -- If no classes, format immediately
- if #classes_to_expand == 0 then
- local result = Lsp._format_exports(
- require_path,
- uri,
- export_keys,
- hover_results,
- file_lines,
- {}
- )
- cb(result, nil)
- return
- end
+ -- If no classes, format immediately
+ if #classes_to_expand == 0 then
+ local result = Lsp._format_exports(
+ require_path,
+ uri,
+ export_keys,
+ hover_results,
+ file_lines,
+ {}
+ )
+ cb(result, nil)
+ return
+ end
- -- Get hover for class members
- local pending = #classes_to_expand
- local all_member_hovers = {}
+ -- Get hover for class members
+ local pending = #classes_to_expand
+ local all_member_hovers = {}
- for _, class_info in ipairs(classes_to_expand) do
- get_class_members_hover(
- bufnr,
- class_info.positions,
- function(member_hovers)
- all_member_hovers[class_info.name] = member_hovers
- pending = pending - 1
+ for _, class_info in ipairs(classes_to_expand) do
+ get_class_members_hover(
+ bufnr,
+ class_info.positions,
+ function(member_hovers)
+ all_member_hovers[class_info.name] = member_hovers
+ pending = pending - 1
- if pending == 0 then
- local result = Lsp._format_exports(
- require_path,
- uri,
- export_keys,
- hover_results,
- file_lines,
- all_member_hovers
- )
- cb(result, nil)
- end
- end
- )
+ if pending == 0 then
+ local result = Lsp._format_exports(
+ require_path,
+ uri,
+ export_keys,
+ hover_results,
+ file_lines,
+ all_member_hovers
+ )
+ cb(result, nil)
end
- end)
+ end
+ )
+ end
end)
+ end)
end
--- Internal function to format exports into a string.
@@ -534,88 +530,84 @@ end
--- @param class_member_hovers table<string, table<string, string>> Class name -> member hovers
--- @return string The formatted export string
function Lsp._format_exports(
- module_path,
- uri,
- export_keys,
- hover_results,
- file_lines,
- class_member_hovers
+ module_path,
+ uri,
+ export_keys,
+ hover_results,
+ file_lines,
+ class_member_hovers
)
- local out = {}
+ local out = {}
- table.insert(out, "Module: " .. module_path)
- table.insert(out, "URI: " .. uri)
- table.insert(out, string.rep("-", 60))
+ table.insert(out, "Module: " .. module_path)
+ table.insert(out, "URI: " .. uri)
+ table.insert(out, string.rep("-", 60))
- for _, export in ipairs(export_keys) do
- table.insert(out, "")
-
- local hover = hover_results[export.name] or "unknown"
+ for _, export in ipairs(export_keys) do
+ table.insert(out, "")
- local is_enum = hover:match("enum%s+") ~= nil
- local is_class = hover:match("__index") ~= nil
- or hover:match(":%s*[%w_]+%s*{") ~= nil
+ local hover = hover_results[export.name] or "unknown"
- if is_enum then
- local values = expand_enum_values(file_lines, export.name)
- if #values > 0 then
- table.insert(out, export.name .. " = {")
- for _, v in ipairs(values) do
- table.insert(out, " " .. v)
- end
- table.insert(out, "}")
- else
- table.insert(
- out,
- export.name .. ": " .. format_hover_output(hover)
- )
- end
- elseif is_class then
- local member_hovers = class_member_hovers[export.name] or {}
- table.insert(out, export.name .. " {")
+ local is_enum = hover:match("enum%s+") ~= nil
+ local is_class = hover:match("__index") ~= nil
+ or hover:match(":%s*[%w_]+%s*{") ~= nil
- -- Extract fields from class hover
- local class_fields = {}
- for line in hover:gmatch("[^\n]+") do
- local field_name, field_type =
- line:match("^%s*([%w_]+):%s*([^,}]+)")
- if field_name and field_type then
- field_type = field_type:match("^%s*(.-)%s*,?$")
- if field_type ~= "function" then
- class_fields[field_name] = field_type
- end
- end
- end
+ if is_enum then
+ local values = expand_enum_values(file_lines, export.name)
+ if #values > 0 then
+ table.insert(out, export.name .. " = {")
+ for _, v in ipairs(values) do
+ table.insert(out, " " .. v)
+ end
+ table.insert(out, "}")
+ else
+ table.insert(out, export.name .. ": " .. format_hover_output(hover))
+ end
+ elseif is_class then
+ local member_hovers = class_member_hovers[export.name] or {}
+ table.insert(out, export.name .. " {")
- -- Print fields
- for field_name, field_type in pairs(class_fields) do
- if field_name ~= "__index" then
- table.insert(out, " " .. field_name .. ": " .. field_type)
- end
- end
+ -- Extract fields from class hover
+ local class_fields = {}
+ for line in hover:gmatch("[^\n]+") do
+ local field_name, field_type = line:match("^%s*([%w_]+):%s*([^,}]+)")
+ if field_name and field_type then
+ field_type = field_type:match("^%s*(.-)%s*,?$")
+ if field_type ~= "function" then
+ class_fields[field_name] = field_type
+ end
+ end
+ end
- -- Print methods with full signatures
- for method_name, method_hover in pairs(member_hovers) do
- if method_name ~= "__index" then
- local sig = format_function_signature(method_hover)
- table.insert(out, " " .. method_name .. sig)
- end
- end
+ -- Print fields
+ for field_name, field_type in pairs(class_fields) do
+ if field_name ~= "__index" then
+ table.insert(out, " " .. field_name .. ": " .. field_type)
+ end
+ end
- table.insert(out, "}")
- else
- local formatted = format_hover_output(hover)
- table.insert(out, export.name .. ": " .. formatted)
+ -- Print methods with full signatures
+ for method_name, method_hover in pairs(member_hovers) do
+ if method_name ~= "__index" then
+ local sig = format_function_signature(method_hover)
+ table.insert(out, " " .. method_name .. sig)
end
+ end
+
+ table.insert(out, "}")
+ else
+ local formatted = format_hover_output(hover)
+ table.insert(out, export.name .. ": " .. formatted)
end
+ end
- return table.concat(out, "\n")
+ return table.concat(out, "\n")
end
Lsp.stringify_module_exports("99.editor.lsp", function(res)
- print(res)
+ print(res)
end)
return {
- Lsp = Lsp,
+ Lsp = Lsp,
}
diff --git a/lua/99/editor/treesitter.lua b/lua/99/editor/treesitter.lua
index b758266..d219ffb 100644
--- a/lua/99/editor/treesitter.lua
+++ b/lua/99/editor/treesitter.lua
@@ -22,69 +22,69 @@ local fn_call_query = "99-fn-call"
--- @param buffer number
---@param lang string
local function tree_root(buffer, lang)
- -- Load the parser and the query.
- local ok, parser = pcall(vim.treesitter.get_parser, buffer, lang)
- if not ok then
- return nil
- end
+ -- Load the parser and the query.
+ local ok, parser = pcall(vim.treesitter.get_parser, buffer, lang)
+ if not ok then
+ return nil
+ end
- local tree = parser:parse()[1]
- return tree:root()
+ local tree = parser:parse()[1]
+ return tree:root()
end
--- @param context _99.RequestContext
--- @param cursor _99.Point
--- @return _99.treesitter.TSNode | nil
function M.fn_call(context, cursor)
- local buffer = context.buffer
- local lang = context.file_type
- local logger = context.logger:set_area("treesitter")
- local root = tree_root(buffer, lang)
- if not root then
- Logger:error(
- "unable to find treeroot, this should never happen",
- "buffer",
- buffer,
- "lang",
- lang
- )
- return nil
- end
+ local buffer = context.buffer
+ local lang = context.file_type
+ local logger = context.logger:set_area("treesitter")
+ local root = tree_root(buffer, lang)
+ if not root then
+ Logger:error(
+ "unable to find treeroot, this should never happen",
+ "buffer",
+ buffer,
+ "lang",
+ lang
+ )
+ return nil
+ end
- local ok, query = pcall(vim.treesitter.query.get, lang, fn_call_query)
- if not ok or query == nil then
- logger:error(
- "unable to get the fn_call_query",
- "lang",
- lang,
- "buffer",
- buffer,
- "ok",
- type(ok),
- "query",
- type(query)
- )
- return nil
- end
+ local ok, query = pcall(vim.treesitter.query.get, lang, fn_call_query)
+ if not ok or query == nil then
+ logger:error(
+ "unable to get the fn_call_query",
+ "lang",
+ lang,
+ "buffer",
+ buffer,
+ "ok",
+ type(ok),
+ "query",
+ type(query)
+ )
+ return nil
+ end
- --- likely something that needs to be done with treesitter#get_node
- local found = nil
- for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do
- for _, nodes in pairs(match) do
- for _, node in ipairs(nodes) do
- local range = Range:from_ts_node(node, buffer)
- if range:contains(cursor) then
- found = node
- goto end_of_loops
- end
- end
+ --- likely something that needs to be done with treesitter#get_node
+ local found = nil
+ for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do
+ for _, nodes in pairs(match) do
+ for _, node in ipairs(nodes) do
+ local range = Range:from_ts_node(node, buffer)
+ if range:contains(cursor) then
+ found = node
+ goto end_of_loops
end
+ end
end
- ::end_of_loops::
+ end
+ ::end_of_loops::
- logger:debug("treesitter#fn_call", "found", found ~= nil)
+ logger:debug("treesitter#fn_call", "found", found ~= nil)
- return found
+ return found
end
--- @class _99.treesitter.Function
@@ -99,7 +99,7 @@ Function.__index = Function
--- to replace at the exact function begin / end
--- @param replace_with string[]
function Function:replace_text(replace_with)
- self.function_range:replace_text(replace_with)
+ self.function_range:replace_text(replace_with)
end
--- @param ts_node _99.treesitter.TSNode
@@ -107,149 +107,149 @@ end
---@param context _99.RequestContext
---@return _99.treesitter.Function
function Function.from_ts_node(ts_node, cursor, context)
- local ok, query =
- pcall(vim.treesitter.query.get, context.file_type, function_query)
- local logger = context.logger:set_area("Function")
- if not ok or query == nil then
- logger:fatal("not query or not ok")
- error("failed")
- end
+ local ok, query =
+ pcall(vim.treesitter.query.get, context.file_type, function_query)
+ local logger = context.logger:set_area("Function")
+ if not ok or query == nil then
+ logger:fatal("not query or not ok")
+ error("failed")
+ end
- local func = {}
- for id, node, _ in
- query:iter_captures(ts_node, context.buffer, 0, -1, { all = true })
- do
- local range = Range:from_ts_node(node, context.buffer)
- local name = query.captures[id]
- if range:contains(cursor) then
- if name == "context.function" then
- func.function_node = node
- func.function_range = range
- elseif name == "context.body" then
- func.body_node = node
- func.body_range = range
- end
- end
+ local func = {}
+ for id, node, _ in
+ query:iter_captures(ts_node, context.buffer, 0, -1, { all = true })
+ do
+ local range = Range:from_ts_node(node, context.buffer)
+ local name = query.captures[id]
+ if range:contains(cursor) then
+ if name == "context.function" then
+ func.function_node = node
+ func.function_range = range
+ elseif name == "context.body" then
+ func.body_node = node
+ func.body_range = range
+ end
end
+ end
- --- NOTE: not all functions have bodies... (lua: local function foo() end)
- logger:assert(func.function_node ~= nil, "function_node not found")
- logger:assert(func.function_range ~= nil, "function_range not found")
+ --- NOTE: not all functions have bodies... (lua: local function foo() end)
+ logger:assert(func.function_node ~= nil, "function_node not found")
+ logger:assert(func.function_range ~= nil, "function_range not found")
- return setmetatable(func, Function)
+ return setmetatable(func, Function)
end
--- @param context _99.RequestContext
--- @param cursor _99.Point
--- @return _99.treesitter.Function?
function M.containing_function(context, cursor)
- local buffer = context.buffer
- local lang = context.file_type
- local logger = context and context.logger:set_area("treesitter") or Logger
-
- logger:error("loading lang", "buffer", buffer, "lang", lang)
- local root = tree_root(buffer, lang)
- if not root then
- logger:debug("LSP: could not find tree root")
- return nil
- end
+ local buffer = context.buffer
+ local lang = context.file_type
+ local logger = context and context.logger:set_area("treesitter") or Logger
- local ok, query = pcall(vim.treesitter.query.get, lang, function_query)
- if not ok or query == nil then
- logger:debug(
- "LSP: not ok or query",
- "query",
- vim.inspect(query),
- "lang",
- lang,
- "ok",
- vim.inspect(ok)
- )
- return nil
- end
-
- --- @type _99.Range
- local found_range = nil
- --- @type _99.treesitter.TSNode
- local found_node = nil
- for id, node, _ in query:iter_captures(root, buffer, 0, -1, { all = true }) do
- local range = Range:from_ts_node(node, buffer)
- local name = query.captures[id]
- if name == "context.function" and range:contains(cursor) then
- if not found_range then
- found_range = range
- found_node = node
- elseif found_range:area() > range:area() then
- found_range = range
- found_node = node
- end
- end
- end
+ logger:error("loading lang", "buffer", buffer, "lang", lang)
+ local root = tree_root(buffer, lang)
+ if not root then
+ logger:debug("LSP: could not find tree root")
+ return nil
+ end
+ local ok, query = pcall(vim.treesitter.query.get, lang, function_query)
+ if not ok or query == nil then
logger:debug(
- "treesitter#containing_function",
- "found_range",
- found_range and found_range:to_string() or "found_range is nil"
+ "LSP: not ok or query",
+ "query",
+ vim.inspect(query),
+ "lang",
+ lang,
+ "ok",
+ vim.inspect(ok)
)
+ return nil
+ end
- if not found_range then
- return nil
+ --- @type _99.Range
+ local found_range = nil
+ --- @type _99.treesitter.TSNode
+ local found_node = nil
+ for id, node, _ in query:iter_captures(root, buffer, 0, -1, { all = true }) do
+ local range = Range:from_ts_node(node, buffer)
+ local name = query.captures[id]
+ if name == "context.function" and range:contains(cursor) then
+ if not found_range then
+ found_range = range
+ found_node = node
+ elseif found_range:area() > range:area() then
+ found_range = range
+ found_node = node
+ end
end
- logger:assert(
- found_node,
- "INVARIANT: found_range is not nil but found node is"
- )
+ end
- ok, query = pcall(vim.treesitter.query.get, lang, function_query)
- if not ok or query == nil then
- logger:fatal("INVARIANT: found_range ", "range", found_range:to_text())
- return
- end
+ logger:debug(
+ "treesitter#containing_function",
+ "found_range",
+ found_range and found_range:to_string() or "found_range is nil"
+ )
+
+ if not found_range then
+ return nil
+ end
+ logger:assert(
+ found_node,
+ "INVARIANT: found_range is not nil but found node is"
+ )
+
+ ok, query = pcall(vim.treesitter.query.get, lang, function_query)
+ if not ok or query == nil then
+ logger:fatal("INVARIANT: found_range ", "range", found_range:to_text())
+ return
+ end
- --- TODO: we need some language specific things here.
- --- that is because comments above the function needs to considered
- return Function.from_ts_node(found_node, cursor, context)
+ --- TODO: we need some language specific things here.
+ --- that is because comments above the function needs to considered
+ return Function.from_ts_node(found_node, cursor, context)
end
--- @param buffer number
--- @return _99.treesitter.Node[]
function M.imports(buffer)
- Logger:assert(false, "not implemented yet", "id", 69420)
- local lang = vim.bo[buffer].ft
- local root = tree_root(buffer, lang)
- if not root then
- Logger:debug("imports: could not find tree root")
- return {}
- end
+ Logger:assert(false, "not implemented yet", "id", 69420)
+ local lang = vim.bo[buffer].ft
+ local root = tree_root(buffer, lang)
+ if not root then
+ Logger:debug("imports: could not find tree root")
+ return {}
+ end
- local ok, query = pcall(vim.treesitter.query.get, lang, imports_query)
+ local ok, query = pcall(vim.treesitter.query.get, lang, imports_query)
- if not ok or query == nil then
- Logger:debug(
- "imports: not ok or query",
- "query",
- vim.inspect(query),
- "lang",
- lang,
- "ok",
- vim.inspect(ok)
- )
- return {}
- end
+ if not ok or query == nil then
+ Logger:debug(
+ "imports: not ok or query",
+ "query",
+ vim.inspect(query),
+ "lang",
+ lang,
+ "ok",
+ vim.inspect(ok)
+ )
+ return {}
+ end
- local imports = {}
- for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do
- for id, nodes in pairs(match) do
- local name = query.captures[id]
- if name == "import.name" then
- for _, node in ipairs(nodes) do
- table.insert(imports, node)
- end
- end
+ local imports = {}
+ for _, match, _ in query:iter_matches(root, buffer, 0, -1, { all = true }) do
+ for id, nodes in pairs(match) do
+ local name = query.captures[id]
+ if name == "import.name" then
+ for _, node in ipairs(nodes) do
+ table.insert(imports, node)
end
+ end
end
+ end
- return imports
+ return imports
end
return M