From f8ce4738771e0e8235eafac4fd89eee524905e8a Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Tue, 24 Mar 2026 21:18:06 -0400 Subject: [PATCH] migrating the cmp brain --- lua/codetyper/adapters/nvim/autocmds.lua | 32 +-- .../nvim/cmp/get_brain_completions.lua | 65 +++++ .../nvim/cmp/get_buffer_completions.lua | 28 +++ .../nvim/cmp/get_copilot_suggestion.lua | 30 +++ .../nvim/cmp/get_indexer_completions.lua | 65 +++++ lua/codetyper/adapters/nvim/cmp/has_cmp.lua | 7 + lua/codetyper/adapters/nvim/cmp/init.lua | 228 +----------------- lua/codetyper/features/completion/inline.lua | 9 +- lua/codetyper/parser.lua | 22 -- lua/codetyper/parser/detect_prompt_type.lua | 34 --- lua/codetyper/parser/get_prompt_at_cursor.lua | 56 ----- lua/codetyper/parser/has_unclosed_prompts.lua | 34 --- lua/codetyper/utils/cmp_source.lua | 28 +++ 13 files changed, 252 insertions(+), 386 deletions(-) create mode 100644 lua/codetyper/adapters/nvim/cmp/get_brain_completions.lua create mode 100644 lua/codetyper/adapters/nvim/cmp/get_buffer_completions.lua create mode 100644 lua/codetyper/adapters/nvim/cmp/get_copilot_suggestion.lua create mode 100644 lua/codetyper/adapters/nvim/cmp/get_indexer_completions.lua create mode 100644 lua/codetyper/adapters/nvim/cmp/has_cmp.lua delete mode 100644 lua/codetyper/parser.lua delete mode 100644 lua/codetyper/parser/detect_prompt_type.lua delete mode 100644 lua/codetyper/parser/get_prompt_at_cursor.lua delete mode 100644 lua/codetyper/parser/has_unclosed_prompts.lua create mode 100644 lua/codetyper/utils/cmp_source.lua diff --git a/lua/codetyper/adapters/nvim/autocmds.lua b/lua/codetyper/adapters/nvim/autocmds.lua index e5ea37c..94f94f8 100644 --- a/lua/codetyper/adapters/nvim/autocmds.lua +++ b/lua/codetyper/adapters/nvim/autocmds.lua @@ -305,8 +305,8 @@ end ---@param base_path string Base path to resolve relative file paths ---@return table[] attached_files List of {path, content} tables local function read_attached_files(prompt_content, base_path) - local parser = require("codetyper.parser") - local file_refs = parser.extract_file_references(prompt_content) + local extract_file_references = require("codetyper.parser.extract_file_references") + local file_refs = extract_file_references(prompt_content) local attached = {} local cwd = vim.fn.getcwd() local base_dir = vim.fn.fnamemodify(base_path, ":h") @@ -349,7 +349,10 @@ function M.check_for_closed_prompt() end is_processing = true - local parser = require("codetyper.parser") + local has_closing_tag = require("codetyper.parser.has_closing_tag") + local get_last_prompt = require("codetyper.parser.get_last_prompt") + local clean_prompt = require("codetyper.parser.clean_prompt") + local strip_file_references = require("codetyper.parser.strip_file_references") local bufnr = vim.api.nvim_get_current_buf() local current_file = vim.fn.expand("%:p") @@ -373,9 +376,9 @@ function M.check_for_closed_prompt() local current_line = lines[1] -- Check if line contains closing tag - if parser.has_closing_tag(current_line, config.patterns.close_tag) then + if has_closing_tag(current_line, config.patterns.close_tag) then -- Find the complete prompt - local prompt = parser.get_last_prompt(bufnr) + local prompt = get_last_prompt(bufnr) if prompt and prompt.content and prompt.content ~= "" then -- Generate unique key for this prompt local prompt_key = get_prompt_key(bufnr, prompt) @@ -421,7 +424,7 @@ function M.check_for_closed_prompt() local attached_files = read_attached_files(prompt.content, current_file) -- Clean prompt content (strip file references) - local cleaned = parser.clean_prompt(parser.strip_file_references(prompt.content)) + local cleaned = clean_prompt(strip_file_references(prompt.content)) -- Check if we're working from a coder file local is_from_coder_file = utils.is_coder_file(current_file) @@ -556,7 +559,8 @@ end ---@param current_file string Current file path ---@param skip_processed_check? boolean Skip the processed check (for manual mode) function M.process_single_prompt(bufnr, prompt, current_file, skip_processed_check) - local parser = require("codetyper.parser") + local clean_prompt = require("codetyper.parser.clean_prompt") + local strip_file_references = require("codetyper.parser.strip_file_references") local scheduler = require("codetyper.core.scheduler.scheduler") if not prompt.content or prompt.content == "" then @@ -606,7 +610,7 @@ function M.process_single_prompt(bufnr, prompt, current_file, skip_processed_che local attached_files = read_attached_files(prompt.content, current_file) -- Clean prompt content (strip file references) - local cleaned = parser.clean_prompt(parser.strip_file_references(prompt.content)) + local cleaned = clean_prompt(strip_file_references(prompt.content)) -- Resolve scope in target file FIRST (need it to adjust intent) -- Only resolve scope if NOT from coder file (line numbers don't apply) @@ -744,7 +748,7 @@ end --- Check and process all closed prompts in the buffer (works on ANY file) function M.check_all_prompts() - local parser = require("codetyper.parser") + local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") local bufnr = vim.api.nvim_get_current_buf() local current_file = vim.fn.expand("%:p") @@ -754,7 +758,7 @@ function M.check_all_prompts() end -- Find all prompts in buffer - local prompts = parser.find_prompts_in_buffer(bufnr) + local prompts = find_prompts_in_buffer(bufnr) if #prompts == 0 then return @@ -777,11 +781,11 @@ end --- Check for closed prompt with preference check --- If user hasn't chosen auto/manual mode, ask them first function M.check_for_closed_prompt_with_preference() - local parser = require("codetyper.parser") + local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") -- First check if there are any prompts to process local bufnr = vim.api.nvim_get_current_buf() - local prompts = parser.find_prompts_in_buffer(bufnr) + local prompts = find_prompts_in_buffer(bufnr) if #prompts == 0 then return end @@ -796,11 +800,11 @@ end --- Check all prompts with preference check function M.check_all_prompts_with_preference() local preferences = require("codetyper.config.preferences") - local parser = require("codetyper.parser") + local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") -- First check if there are any prompts to process local bufnr = vim.api.nvim_get_current_buf() - local prompts = parser.find_prompts_in_buffer(bufnr) + local prompts = find_prompts_in_buffer(bufnr) if #prompts == 0 then return end diff --git a/lua/codetyper/adapters/nvim/cmp/get_brain_completions.lua b/lua/codetyper/adapters/nvim/cmp/get_brain_completions.lua new file mode 100644 index 0000000..5153bf3 --- /dev/null +++ b/lua/codetyper/adapters/nvim/cmp/get_brain_completions.lua @@ -0,0 +1,65 @@ +--- Get completion items from brain context +---@param prefix string Current word prefix +---@return table[] items +local function get_brain_completions(prefix) + local items = {} + + local ok_brain, brain = pcall(require, "codetyper.brain") + if not ok_brain then + return items + end + + -- Check if brain is initialized safely + local is_init = false + if brain.is_initialized then + local ok, result = pcall(brain.is_initialized) + is_init = ok and result + end + + if not is_init then + return items + end + + -- Query brain for relevant patterns + local ok_query, result = pcall(brain.query, { + query = prefix, + max_results = 10, + types = { "pattern" }, + }) + + if ok_query and result and result.nodes then + for _, node in ipairs(result.nodes) do + if node.c and node.c.s then + local summary = node.c.s + for name in summary:gmatch("functions:%s*([^;]+)") do + for func in name:gmatch("([%w_]+)") do + if func:lower():find(prefix:lower(), 1, true) then + table.insert(items, { + label = func, + kind = 3, -- Function + detail = "[brain]", + documentation = summary, + }) + end + end + end + for name in summary:gmatch("classes:%s*([^;]+)") do + for class in name:gmatch("([%w_]+)") do + if class:lower():find(prefix:lower(), 1, true) then + table.insert(items, { + label = class, + kind = 7, -- Class + detail = "[brain]", + documentation = summary, + }) + end + end + end + end + end + end + + return items +end + +return get_brain_completions diff --git a/lua/codetyper/adapters/nvim/cmp/get_buffer_completions.lua b/lua/codetyper/adapters/nvim/cmp/get_buffer_completions.lua new file mode 100644 index 0000000..392e6e5 --- /dev/null +++ b/lua/codetyper/adapters/nvim/cmp/get_buffer_completions.lua @@ -0,0 +1,28 @@ +--- Get completion items from current buffer (fallback) +---@param prefix string Current word prefix +---@param bufnr number Buffer number +---@return table[] items +local function get_buffer_completions(prefix, bufnr) + local items = {} + local seen = {} + + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local prefix_lower = prefix:lower() + + for _, line in ipairs(lines) do + for word in line:gmatch("[%a_][%w_]*") do + if #word >= 3 and word:lower():find(prefix_lower, 1, true) and not seen[word] and word ~= prefix then + seen[word] = true + table.insert(items, { + label = word, + kind = 1, -- Text + detail = "[buffer]", + }) + end + end + end + + return items +end + +return get_buffer_completions diff --git a/lua/codetyper/adapters/nvim/cmp/get_copilot_suggestion.lua b/lua/codetyper/adapters/nvim/cmp/get_copilot_suggestion.lua new file mode 100644 index 0000000..39363c9 --- /dev/null +++ b/lua/codetyper/adapters/nvim/cmp/get_copilot_suggestion.lua @@ -0,0 +1,30 @@ +--- Try to get Copilot suggestion if plugin is installed +---@param prefix string +---@return string|nil suggestion +local function get_copilot_suggestion(prefix) + -- Try copilot.lua suggestion API first + local ok, copilot_suggestion = pcall(require, "copilot.suggestion") + if ok and copilot_suggestion and type(copilot_suggestion.get_suggestion) == "function" then + local ok2, suggestion = pcall(copilot_suggestion.get_suggestion) + if ok2 and suggestion and suggestion ~= "" then + if prefix == "" or suggestion:lower():match(prefix:lower(), 1) then + return suggestion + else + return suggestion + end + end + end + + -- Fallback: try older copilot module if present + local ok3, copilot = pcall(require, "copilot") + if ok3 and copilot and type(copilot.get_suggestion) == "function" then + local ok4, suggestion = pcall(copilot.get_suggestion) + if ok4 and suggestion and suggestion ~= "" then + return suggestion + end + end + + return nil +end + +return get_copilot_suggestion diff --git a/lua/codetyper/adapters/nvim/cmp/get_indexer_completions.lua b/lua/codetyper/adapters/nvim/cmp/get_indexer_completions.lua new file mode 100644 index 0000000..c07ce7b --- /dev/null +++ b/lua/codetyper/adapters/nvim/cmp/get_indexer_completions.lua @@ -0,0 +1,65 @@ +--- Get completion items from indexer symbols +---@param prefix string Current word prefix +---@return table[] items +local function get_indexer_completions(prefix) + local items = {} + + local ok_indexer, indexer = pcall(require, "codetyper.indexer") + if not ok_indexer then + return items + end + + local ok_load, index = pcall(indexer.load_index) + if not ok_load or not index then + return items + end + + -- Search symbols + if index.symbols then + for symbol, files in pairs(index.symbols) do + if symbol:lower():find(prefix:lower(), 1, true) then + local files_str = type(files) == "table" and table.concat(files, ", ") or tostring(files) + table.insert(items, { + label = symbol, + kind = 6, -- Variable (generic) + detail = "[index] " .. files_str:sub(1, 30), + documentation = "Symbol found in: " .. files_str, + }) + end + end + end + + -- Search functions in files + if index.files then + for filepath, file_index in pairs(index.files) do + if file_index and file_index.functions then + for _, func in ipairs(file_index.functions) do + if func.name and func.name:lower():find(prefix:lower(), 1, true) then + table.insert(items, { + label = func.name, + kind = 3, -- Function + detail = "[index] " .. vim.fn.fnamemodify(filepath, ":t"), + documentation = func.docstring or ("Function at line " .. (func.line or "?")), + }) + end + end + end + if file_index and file_index.classes then + for _, class in ipairs(file_index.classes) do + if class.name and class.name:lower():find(prefix:lower(), 1, true) then + table.insert(items, { + label = class.name, + kind = 7, -- Class + detail = "[index] " .. vim.fn.fnamemodify(filepath, ":t"), + documentation = class.docstring or ("Class at line " .. (class.line or "?")), + }) + end + end + end + end + end + + return items +end + +return get_indexer_completions diff --git a/lua/codetyper/adapters/nvim/cmp/has_cmp.lua b/lua/codetyper/adapters/nvim/cmp/has_cmp.lua new file mode 100644 index 0000000..9e3c6da --- /dev/null +++ b/lua/codetyper/adapters/nvim/cmp/has_cmp.lua @@ -0,0 +1,7 @@ +--- Check if cmp is available +---@return boolean +local function has_cmp() + return pcall(require, "cmp") +end + +return has_cmp diff --git a/lua/codetyper/adapters/nvim/cmp/init.lua b/lua/codetyper/adapters/nvim/cmp/init.lua index 1733b62..af73a90 100644 --- a/lua/codetyper/adapters/nvim/cmp/init.lua +++ b/lua/codetyper/adapters/nvim/cmp/init.lua @@ -6,226 +6,13 @@ local M = {} -local source = {} +local has_cmp = require("codetyper.adapters.nvim.cmp.has_cmp") +local get_brain_completions = require("codetyper.adapters.nvim.cmp.get_brain_completions") +local get_indexer_completions = require("codetyper.adapters.nvim.cmp.get_indexer_completions") +local get_buffer_completions = require("codetyper.adapters.nvim.cmp.get_buffer_completions") +local get_copilot_suggestion = require("codetyper.adapters.nvim.cmp.get_copilot_suggestion") ---- Check if cmp is available ----@return boolean -local function has_cmp() - return pcall(require, "cmp") -end - ---- Get completion items from brain context ----@param prefix string Current word prefix ----@return table[] items -local function get_brain_completions(prefix) - local items = {} - - local ok_brain, brain = pcall(require, "codetyper.brain") - if not ok_brain then - return items - end - - -- Check if brain is initialized safely - local is_init = false - if brain.is_initialized then - local ok, result = pcall(brain.is_initialized) - is_init = ok and result - end - - if not is_init then - return items - end - - -- Query brain for relevant patterns - local ok_query, result = pcall(brain.query, { - query = prefix, - max_results = 10, - types = { "pattern" }, - }) - - if ok_query and result and result.nodes then - for _, node in ipairs(result.nodes) do - if node.c and node.c.s then - -- Extract function/class names from summary - local summary = node.c.s - for name in summary:gmatch("functions:%s*([^;]+)") do - for func in name:gmatch("([%w_]+)") do - if func:lower():find(prefix:lower(), 1, true) then - table.insert(items, { - label = func, - kind = 3, -- Function - detail = "[brain]", - documentation = summary, - }) - end - end - end - for name in summary:gmatch("classes:%s*([^;]+)") do - for class in name:gmatch("([%w_]+)") do - if class:lower():find(prefix:lower(), 1, true) then - table.insert(items, { - label = class, - kind = 7, -- Class - detail = "[brain]", - documentation = summary, - }) - end - end - end - end - end - end - - return items -end - ---- Get completion items from indexer symbols ----@param prefix string Current word prefix ----@return table[] items -local function get_indexer_completions(prefix) - local items = {} - - local ok_indexer, indexer = pcall(require, "codetyper.indexer") - if not ok_indexer then - return items - end - - local ok_load, index = pcall(indexer.load_index) - if not ok_load or not index then - return items - end - - -- Search symbols - if index.symbols then - for symbol, files in pairs(index.symbols) do - if symbol:lower():find(prefix:lower(), 1, true) then - local files_str = type(files) == "table" and table.concat(files, ", ") or tostring(files) - table.insert(items, { - label = symbol, - kind = 6, -- Variable (generic) - detail = "[index] " .. files_str:sub(1, 30), - documentation = "Symbol found in: " .. files_str, - }) - end - end - end - - -- Search functions in files - if index.files then - for filepath, file_index in pairs(index.files) do - if file_index and file_index.functions then - for _, func in ipairs(file_index.functions) do - if func.name and func.name:lower():find(prefix:lower(), 1, true) then - table.insert(items, { - label = func.name, - kind = 3, -- Function - detail = "[index] " .. vim.fn.fnamemodify(filepath, ":t"), - documentation = func.docstring or ("Function at line " .. (func.line or "?")), - }) - end - end - end - if file_index and file_index.classes then - for _, class in ipairs(file_index.classes) do - if class.name and class.name:lower():find(prefix:lower(), 1, true) then - table.insert(items, { - label = class.name, - kind = 7, -- Class - detail = "[index] " .. vim.fn.fnamemodify(filepath, ":t"), - documentation = class.docstring or ("Class at line " .. (class.line or "?")), - }) - end - end - end - end - end - - return items -end - ---- Get completion items from current buffer (fallback) ----@param prefix string Current word prefix ----@param bufnr number Buffer number ----@return table[] items -local function get_buffer_completions(prefix, bufnr) - local items = {} - local seen = {} - - -- Get all lines in buffer - local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) - local prefix_lower = prefix:lower() - - for _, line in ipairs(lines) do - -- Extract words that could be identifiers - for word in line:gmatch("[%a_][%w_]*") do - if #word >= 3 and word:lower():find(prefix_lower, 1, true) and not seen[word] and word ~= prefix then - seen[word] = true - table.insert(items, { - label = word, - kind = 1, -- Text - detail = "[buffer]", - }) - end - end - end - - return items -end - ---- Try to get Copilot suggestion if plugin is installed ----@param prefix string ----@return string|nil suggestion -local function get_copilot_suggestion(prefix) - -- Try copilot.lua suggestion API first - local ok, copilot_suggestion = pcall(require, "copilot.suggestion") - if ok and copilot_suggestion and type(copilot_suggestion.get_suggestion) == "function" then - local ok2, suggestion = pcall(copilot_suggestion.get_suggestion) - if ok2 and suggestion and suggestion ~= "" then - -- Only return if suggestion seems to start with prefix (best-effort) - if prefix == "" or suggestion:lower():match(prefix:lower(), 1) then - return suggestion - else - return suggestion - end - end - end - - -- Fallback: try older copilot module if present - local ok3, copilot = pcall(require, "copilot") - if ok3 and copilot and type(copilot.get_suggestion) == "function" then - local ok4, suggestion = pcall(copilot.get_suggestion) - if ok4 and suggestion and suggestion ~= "" then - return suggestion - end - end - - return nil -end - ---- Create new cmp source instance -function source.new() - return setmetatable({}, { __index = source }) -end - ---- Get source name -function source:get_keyword_pattern() - return [[\k\+]] -end - ---- Check if source is available -function source:is_available() - return true -end - ---- Get debug name -function source:get_debug_name() - return "codetyper" -end - ---- Get trigger characters -function source:get_trigger_characters() - return { ".", ":", "_" } -end +local source = require("codetyper.utils.cmp_source") --- Complete ---@param params table @@ -290,9 +77,7 @@ function source:complete(params, callback) suggestion = res end if suggestion and suggestion ~= "" then - -- Truncate suggestion to first line for label display local first_line = suggestion:match("([^\n]+)") or suggestion - -- Avoid duplicates if not seen[first_line] then seen[first_line] = true table.insert(items, 1, { @@ -335,7 +120,6 @@ function M.is_registered() return false end - -- Try to get registered sources local config = cmp.get_config() if config and config.sources then for _, src in ipairs(config.sources) do diff --git a/lua/codetyper/features/completion/inline.lua b/lua/codetyper/features/completion/inline.lua index a9533b8..457b04d 100644 --- a/lua/codetyper/features/completion/inline.lua +++ b/lua/codetyper/features/completion/inline.lua @@ -2,7 +2,8 @@ --- local M = {} -local parser = require("codetyper.parser") +local is_cursor_in_open_tag = require("codetyper.parser.is_cursor_in_open_tag") +local get_file_ref_prefix = require("codetyper.parser.get_file_ref_prefix") local utils = require("codetyper.support.utils") --- Get list of files for completion @@ -110,13 +111,13 @@ end --- Show file completion popup function M.show_file_completion() -- Check if we're in an open prompt tag - local is_inside = parser.is_cursor_in_open_tag() + local is_inside = is_cursor_in_open_tag() if not is_inside then return false end -- Get the prefix being typed - local prefix = parser.get_file_ref_prefix() + local prefix = get_file_ref_prefix() if prefix == nil then return false end @@ -160,7 +161,7 @@ function M.setup() -- Schedule completion popup after the @ is inserted vim.schedule(function() -- Check we're in an open tag - local is_inside = parser.is_cursor_in_open_tag() + local is_inside = is_cursor_in_open_tag() if not is_inside then return end diff --git a/lua/codetyper/parser.lua b/lua/codetyper/parser.lua deleted file mode 100644 index 0e49987..0000000 --- a/lua/codetyper/parser.lua +++ /dev/null @@ -1,22 +0,0 @@ ----@mod codetyper.parser Parser for /@ @/ prompt tags - -local logger = require("codetyper.support.logger") - -local M = {} - -M.find_prompts = require("codetyper.parser.find_prompts") -M.find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") -M.get_prompt_at_cursor = require("codetyper.parser.get_prompt_at_cursor") -M.get_last_prompt = require("codetyper.parser.get_last_prompt") -M.detect_prompt_type = require("codetyper.parser.detect_prompt_type") -M.clean_prompt = require("codetyper.parser.clean_prompt") -M.has_closing_tag = require("codetyper.parser.has_closing_tag") -M.has_unclosed_prompts = require("codetyper.parser.has_unclosed_prompts") -M.extract_file_references = require("codetyper.parser.extract_file_references") -M.strip_file_references = require("codetyper.parser.strip_file_references") -M.is_cursor_in_open_tag = require("codetyper.parser.is_cursor_in_open_tag") -M.get_file_ref_prefix = require("codetyper.parser.get_file_ref_prefix") - -logger.info("parser", "Parser module loaded") - -return M diff --git a/lua/codetyper/parser/detect_prompt_type.lua b/lua/codetyper/parser/detect_prompt_type.lua deleted file mode 100644 index 3830e31..0000000 --- a/lua/codetyper/parser/detect_prompt_type.lua +++ /dev/null @@ -1,34 +0,0 @@ -local logger = require("codetyper.support.logger") - ---- Extract the prompt type from content ----@param content string Prompt content ----@return "refactor" | "add" | "document" | "explain" | "generic" Prompt type -local function detect_prompt_type(content) - logger.func_entry("parser", "detect_prompt_type", { content_preview = content:sub(1, 50) }) - - local lower = content:lower() - - if lower:match("refactor") then - logger.debug("parser", "detect_prompt_type: detected 'refactor'") - logger.func_exit("parser", "detect_prompt_type", "refactor") - return "refactor" - elseif lower:match("add") or lower:match("create") or lower:match("implement") then - logger.debug("parser", "detect_prompt_type: detected 'add'") - logger.func_exit("parser", "detect_prompt_type", "add") - return "add" - elseif lower:match("document") or lower:match("comment") or lower:match("jsdoc") then - logger.debug("parser", "detect_prompt_type: detected 'document'") - logger.func_exit("parser", "detect_prompt_type", "document") - return "document" - elseif lower:match("explain") or lower:match("what") or lower:match("how") then - logger.debug("parser", "detect_prompt_type: detected 'explain'") - logger.func_exit("parser", "detect_prompt_type", "explain") - return "explain" - end - - logger.debug("parser", "detect_prompt_type: detected 'generic'") - logger.func_exit("parser", "detect_prompt_type", "generic") - return "generic" -end - -return detect_prompt_type diff --git a/lua/codetyper/parser/get_prompt_at_cursor.lua b/lua/codetyper/parser/get_prompt_at_cursor.lua deleted file mode 100644 index d1b8ba4..0000000 --- a/lua/codetyper/parser/get_prompt_at_cursor.lua +++ /dev/null @@ -1,56 +0,0 @@ -local logger = require("codetyper.support.logger") -local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") - ---- Get prompt at cursor position ----@param bufnr? number Buffer number (default: current) ----@return CoderPrompt|nil Prompt at cursor or nil -local function get_prompt_at_cursor(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - local cursor = vim.api.nvim_win_get_cursor(0) - local line = cursor[1] - local col = cursor[2] + 1 -- Convert to 1-indexed - - logger.func_entry("parser", "get_prompt_at_cursor", { - bufnr = bufnr, - line = line, - col = col, - }) - - local prompts = find_prompts_in_buffer(bufnr) - - logger.debug("parser", "get_prompt_at_cursor: checking " .. #prompts .. " prompts") - - for i, prompt in ipairs(prompts) do - logger.debug( - "parser", - "get_prompt_at_cursor: checking prompt " .. i .. " (lines " .. prompt.start_line .. "-" .. prompt.end_line .. ")" - ) - if line >= prompt.start_line and line <= prompt.end_line then - logger.debug("parser", "get_prompt_at_cursor: cursor line " .. line .. " is within prompt line range") - if line == prompt.start_line and col < prompt.start_col then - logger.debug( - "parser", - "get_prompt_at_cursor: cursor col " .. col .. " is before prompt start_col " .. prompt.start_col - ) - goto continue - end - if line == prompt.end_line and col > prompt.end_col then - logger.debug( - "parser", - "get_prompt_at_cursor: cursor col " .. col .. " is after prompt end_col " .. prompt.end_col - ) - goto continue - end - logger.debug("parser", "get_prompt_at_cursor: found prompt at cursor") - logger.func_exit("parser", "get_prompt_at_cursor", "prompt found") - return prompt - end - ::continue:: - end - - logger.debug("parser", "get_prompt_at_cursor: no prompt found at cursor") - logger.func_exit("parser", "get_prompt_at_cursor", nil) - return nil -end - -return get_prompt_at_cursor diff --git a/lua/codetyper/parser/has_unclosed_prompts.lua b/lua/codetyper/parser/has_unclosed_prompts.lua deleted file mode 100644 index 918551e..0000000 --- a/lua/codetyper/parser/has_unclosed_prompts.lua +++ /dev/null @@ -1,34 +0,0 @@ -local utils = require("codetyper.support.utils") -local logger = require("codetyper.support.logger") -local get_config = require("codetyper.utils.get_config").get_config - ---- Check if buffer has any unclosed prompts ----@param bufnr? number Buffer number (default: current) ----@return boolean -local function has_unclosed_prompts(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - - logger.func_entry("parser", "has_unclosed_prompts", { bufnr = bufnr }) - - local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) - local content = table.concat(lines, "\n") - - local cfg = get_config() - local escaped_open = utils.escape_pattern(cfg.patterns.open_tag) - local escaped_close = utils.escape_pattern(cfg.patterns.close_tag) - - local _, open_count = content:gsub(escaped_open, "") - local _, close_count = content:gsub(escaped_close, "") - - local has_unclosed = open_count > close_count - - logger.debug( - "parser", - "has_unclosed_prompts: open=" .. open_count .. ", close=" .. close_count .. ", unclosed=" .. tostring(has_unclosed) - ) - logger.func_exit("parser", "has_unclosed_prompts", has_unclosed) - - return has_unclosed -end - -return has_unclosed_prompts diff --git a/lua/codetyper/utils/cmp_source.lua b/lua/codetyper/utils/cmp_source.lua new file mode 100644 index 0000000..235b628 --- /dev/null +++ b/lua/codetyper/utils/cmp_source.lua @@ -0,0 +1,28 @@ +local source = {} + +--- Create new cmp source instance +function source.new() + return setmetatable({}, { __index = source }) +end + +--- Get source name +function source:get_keyword_pattern() + return [[\k\+]] +end + +--- Check if source is available +function source:is_available() + return true +end + +--- Get debug name +function source:get_debug_name() + return "codetyper" +end + +--- Get trigger characters +function source:get_trigger_characters() + return { ".", ":", "_" } +end + +return source