From 0d83c6ba4d91f758578fe0a0d36deb226be65975 Mon Sep 17 00:00:00 2001 From: Carlos Gutierrez Date: Tue, 24 Mar 2026 21:02:14 -0400 Subject: [PATCH] Making changes on the parser file to pure functions --- lua/codetyper/parser.lua | 334 +----------------- lua/codetyper/parser/clean_prompt.lua | 20 ++ lua/codetyper/parser/detect_prompt_type.lua | 34 ++ .../parser/extract_file_references.lua | 24 ++ .../parser/find_prompts_in_buffer.lua | 26 ++ lua/codetyper/parser/get_file_ref_prefix.lua | 36 ++ lua/codetyper/parser/get_last_prompt.lua | 26 ++ lua/codetyper/parser/get_prompt_at_cursor.lua | 56 +++ lua/codetyper/parser/has_closing_tag.lua | 19 + lua/codetyper/parser/has_unclosed_prompts.lua | 34 ++ .../parser/is_cursor_in_open_tag.lua | 56 +++ .../parser/strip_file_references.lua | 17 + 12 files changed, 362 insertions(+), 320 deletions(-) create mode 100644 lua/codetyper/parser/clean_prompt.lua create mode 100644 lua/codetyper/parser/detect_prompt_type.lua create mode 100644 lua/codetyper/parser/extract_file_references.lua create mode 100644 lua/codetyper/parser/find_prompts_in_buffer.lua create mode 100644 lua/codetyper/parser/get_file_ref_prefix.lua create mode 100644 lua/codetyper/parser/get_last_prompt.lua create mode 100644 lua/codetyper/parser/get_prompt_at_cursor.lua create mode 100644 lua/codetyper/parser/has_closing_tag.lua create mode 100644 lua/codetyper/parser/has_unclosed_prompts.lua create mode 100644 lua/codetyper/parser/is_cursor_in_open_tag.lua create mode 100644 lua/codetyper/parser/strip_file_references.lua diff --git a/lua/codetyper/parser.lua b/lua/codetyper/parser.lua index 7ae9804..0e49987 100644 --- a/lua/codetyper/parser.lua +++ b/lua/codetyper/parser.lua @@ -1,327 +1,21 @@ ---@mod codetyper.parser Parser for /@ @/ prompt tags +local logger = require("codetyper.support.logger") + local M = {} -local utils = require("codetyper.support.utils") -local logger = require("codetyper.support.logger") -local get_config = require("codetyper.utils.get_config").get_config -local find_prompts = require("codetyper.parser.find_prompts") - ---- Find prompts in a buffer ----@param bufnr number Buffer number ----@return CoderPrompt[] List of found prompts -function M.find_prompts_in_buffer(bufnr) - logger.func_entry("parser", "find_prompts_in_buffer", { bufnr = bufnr }) - - local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) - local content = table.concat(lines, "\n") - - logger.debug( - "parser", - "find_prompts_in_buffer: bufnr=" .. bufnr .. ", lines=" .. #lines .. ", content_length=" .. #content - ) - - local cfg = get_config() - local result = find_prompts(content, cfg.patterns.open_tag, cfg.patterns.close_tag) - - logger.func_exit("parser", "find_prompts_in_buffer", "found " .. #result .. " prompts") - return result -end - ---- Get prompt at cursor position ----@param bufnr? number Buffer number (default: current) ----@return CoderPrompt|nil Prompt at cursor or nil -function M.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 = M.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 - ---- Get the last closed prompt in buffer ----@param bufnr? number Buffer number (default: current) ----@return CoderPrompt|nil Last prompt or nil -function M.get_last_prompt(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - - logger.func_entry("parser", "get_last_prompt", { bufnr = bufnr }) - - local prompts = M.find_prompts_in_buffer(bufnr) - - if #prompts > 0 then - local last = prompts[#prompts] - logger.debug("parser", "get_last_prompt: returning prompt at line " .. last.start_line) - logger.func_exit("parser", "get_last_prompt", "prompt at line " .. last.start_line) - return last - end - - logger.debug("parser", "get_last_prompt: no prompts found") - logger.func_exit("parser", "get_last_prompt", nil) - return nil -end - ---- Extract the prompt type from content ----@param content string Prompt content ----@return "refactor" | "add" | "document" | "explain" | "generic" Prompt type -function M.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 - ---- Clean prompt content (trim whitespace, normalize newlines) ----@param content string Raw prompt content ----@return string Cleaned content -function M.clean_prompt(content) - logger.func_entry("parser", "clean_prompt", { content_length = #content }) - - -- Trim leading/trailing whitespace - content = content:match("^%s*(.-)%s*$") - -- Normalize multiple newlines - content = content:gsub("\n\n\n+", "\n\n") - - logger.debug("parser", "clean_prompt: cleaned from " .. #content .. " chars") - logger.func_exit("parser", "clean_prompt", "length=" .. #content) - - return content -end - ---- Check if line contains a closing tag ----@param line string Line to check ----@param close_tag string Closing tag ----@return boolean -function M.has_closing_tag(line, close_tag) - logger.func_entry("parser", "has_closing_tag", { line_preview = line:sub(1, 30), close_tag = close_tag }) - - local result = line:find(utils.escape_pattern(close_tag)) ~= nil - - logger.debug("parser", "has_closing_tag: result=" .. tostring(result)) - logger.func_exit("parser", "has_closing_tag", result) - - return result -end - ---- Check if buffer has any unclosed prompts ----@param bufnr? number Buffer number (default: current) ----@return boolean -function M.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 - ---- Extract file references from prompt content ---- Matches @filename patterns but NOT @/ (closing tag) ----@param content string Prompt content ----@return string[] List of file references -function M.extract_file_references(content) - logger.func_entry("parser", "extract_file_references", { content_length = #content }) - - local files = {} - -- Pattern: @ followed by word char, dot, underscore, or dash as FIRST char - -- Then optionally more path characters including / - -- This ensures @/ is NOT matched (/ cannot be first char) - for file in content:gmatch("@([%w%._%-][%w%._%-/]*)") do - if file ~= "" then - table.insert(files, file) - logger.debug("parser", "extract_file_references: found file reference: " .. file) - end - end - - logger.debug("parser", "extract_file_references: found " .. #files .. " file references") - logger.func_exit("parser", "extract_file_references", files) - - return files -end - ---- Remove file references from prompt content (for clean prompt text) ----@param content string Prompt content ----@return string Cleaned content without file references -function M.strip_file_references(content) - logger.func_entry("parser", "strip_file_references", { content_length = #content }) - - -- Remove @filename patterns but preserve @/ closing tag - -- Pattern requires first char after @ to be word char, dot, underscore, or dash (NOT /) - local result = content:gsub("@([%w%._%-][%w%._%-/]*)", "") - - logger.debug("parser", "strip_file_references: stripped " .. (#content - #result) .. " chars") - logger.func_exit("parser", "strip_file_references", "length=" .. #result) - - return result -end - ---- Check if cursor is inside an unclosed prompt tag ----@param bufnr? number Buffer number (default: current) ----@return boolean is_inside Whether cursor is inside an open tag ----@return number|nil start_line Line where the open tag starts -function M.is_cursor_in_open_tag(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - - logger.func_entry("parser", "is_cursor_in_open_tag", { bufnr = bufnr }) - - local cursor = vim.api.nvim_win_get_cursor(0) - local cursor_line = cursor[1] - - local lines = vim.api.nvim_buf_get_lines(bufnr, 0, cursor_line, false) - 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 = 0 - local close_count = 0 - local last_open_line = nil - - for line_num, line in ipairs(lines) do - -- Count opens on this line - for _ in line:gmatch(escaped_open) do - open_count = open_count + 1 - last_open_line = line_num - logger.debug("parser", "is_cursor_in_open_tag: found open tag at line " .. line_num) - end - -- Count closes on this line - for _ in line:gmatch(escaped_close) do - close_count = close_count + 1 - logger.debug("parser", "is_cursor_in_open_tag: found close tag at line " .. line_num) - end - end - - local is_inside = open_count > close_count - - logger.debug( - "parser", - "is_cursor_in_open_tag: open=" - .. open_count - .. ", close=" - .. close_count - .. ", is_inside=" - .. tostring(is_inside) - .. ", last_open_line=" - .. tostring(last_open_line) - ) - logger.func_exit("parser", "is_cursor_in_open_tag", { is_inside = is_inside, last_open_line = last_open_line }) - - return is_inside, is_inside and last_open_line or nil -end - ---- Get the word being typed after @ symbol ----@param bufnr? number Buffer number ----@return string|nil prefix The text after @ being typed, or nil if not typing a file ref -function M.get_file_ref_prefix(bufnr) - bufnr = bufnr or vim.api.nvim_get_current_buf() - - logger.func_entry("parser", "get_file_ref_prefix", { bufnr = bufnr }) - - local cursor = vim.api.nvim_win_get_cursor(0) - local line = vim.api.nvim_buf_get_lines(bufnr, cursor[1] - 1, cursor[1], false)[1] - if not line then - logger.debug("parser", "get_file_ref_prefix: no line at cursor") - logger.func_exit("parser", "get_file_ref_prefix", nil) - return nil - end - - local col = cursor[2] - local before_cursor = line:sub(1, col) - - -- Check if we're typing after @ but not @/ - -- Match @ followed by optional path characters at end of string - local prefix = before_cursor:match("@([%w%._%-/]*)$") - - -- Make sure it's not the closing tag pattern - if prefix and before_cursor:sub(-2) == "@/" then - logger.debug("parser", "get_file_ref_prefix: closing tag detected, returning nil") - logger.func_exit("parser", "get_file_ref_prefix", nil) - return nil - end - - logger.debug("parser", "get_file_ref_prefix: prefix=" .. tostring(prefix)) - logger.func_exit("parser", "get_file_ref_prefix", prefix) - - return prefix -end +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") diff --git a/lua/codetyper/parser/clean_prompt.lua b/lua/codetyper/parser/clean_prompt.lua new file mode 100644 index 0000000..686bf2d --- /dev/null +++ b/lua/codetyper/parser/clean_prompt.lua @@ -0,0 +1,20 @@ +local logger = require("codetyper.support.logger") + +--- Clean prompt content (trim whitespace, normalize newlines) +---@param content string Raw prompt content +---@return string Cleaned content +local function clean_prompt(content) + logger.func_entry("parser", "clean_prompt", { content_length = #content }) + + -- Trim leading/trailing whitespace + content = content:match("^%s*(.-)%s*$") + -- Normalize multiple newlines + content = content:gsub("\n\n\n+", "\n\n") + + logger.debug("parser", "clean_prompt: cleaned from " .. #content .. " chars") + logger.func_exit("parser", "clean_prompt", "length=" .. #content) + + return content +end + +return clean_prompt diff --git a/lua/codetyper/parser/detect_prompt_type.lua b/lua/codetyper/parser/detect_prompt_type.lua new file mode 100644 index 0000000..3830e31 --- /dev/null +++ b/lua/codetyper/parser/detect_prompt_type.lua @@ -0,0 +1,34 @@ +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/extract_file_references.lua b/lua/codetyper/parser/extract_file_references.lua new file mode 100644 index 0000000..7cc2960 --- /dev/null +++ b/lua/codetyper/parser/extract_file_references.lua @@ -0,0 +1,24 @@ +local logger = require("codetyper.support.logger") + +--- Extract file references from prompt content +--- Matches @filename patterns but NOT @/ (closing tag) +---@param content string Prompt content +---@return string[] List of file references +local function extract_file_references(content) + logger.func_entry("parser", "extract_file_references", { content_length = #content }) + + local files = {} + for file in content:gmatch("@([%w%._%-][%w%._%-/]*)") do + if file ~= "" then + table.insert(files, file) + logger.debug("parser", "extract_file_references: found file reference: " .. file) + end + end + + logger.debug("parser", "extract_file_references: found " .. #files .. " file references") + logger.func_exit("parser", "extract_file_references", files) + + return files +end + +return extract_file_references diff --git a/lua/codetyper/parser/find_prompts_in_buffer.lua b/lua/codetyper/parser/find_prompts_in_buffer.lua new file mode 100644 index 0000000..dbc0a30 --- /dev/null +++ b/lua/codetyper/parser/find_prompts_in_buffer.lua @@ -0,0 +1,26 @@ +local logger = require("codetyper.support.logger") +local get_config = require("codetyper.utils.get_config").get_config +local find_prompts = require("codetyper.parser.find_prompts") + +--- Find prompts in a buffer +---@param bufnr number Buffer number +---@return CoderPrompt[] List of found prompts +local function find_prompts_in_buffer(bufnr) + logger.func_entry("parser", "find_prompts_in_buffer", { bufnr = bufnr }) + + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) + local content = table.concat(lines, "\n") + + logger.debug( + "parser", + "find_prompts_in_buffer: bufnr=" .. bufnr .. ", lines=" .. #lines .. ", content_length=" .. #content + ) + + local cfg = get_config() + local result = find_prompts(content, cfg.patterns.open_tag, cfg.patterns.close_tag) + + logger.func_exit("parser", "find_prompts_in_buffer", "found " .. #result .. " prompts") + return result +end + +return find_prompts_in_buffer diff --git a/lua/codetyper/parser/get_file_ref_prefix.lua b/lua/codetyper/parser/get_file_ref_prefix.lua new file mode 100644 index 0000000..948375d --- /dev/null +++ b/lua/codetyper/parser/get_file_ref_prefix.lua @@ -0,0 +1,36 @@ +local logger = require("codetyper.support.logger") + +--- Get the word being typed after @ symbol +---@param bufnr? number Buffer number +---@return string|nil prefix The text after @ being typed, or nil if not typing a file ref +local function get_file_ref_prefix(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() + + logger.func_entry("parser", "get_file_ref_prefix", { bufnr = bufnr }) + + local cursor = vim.api.nvim_win_get_cursor(0) + local line = vim.api.nvim_buf_get_lines(bufnr, cursor[1] - 1, cursor[1], false)[1] + if not line then + logger.debug("parser", "get_file_ref_prefix: no line at cursor") + logger.func_exit("parser", "get_file_ref_prefix", nil) + return nil + end + + local col = cursor[2] + local before_cursor = line:sub(1, col) + + local prefix = before_cursor:match("@([%w%._%-/]*)$") + + if prefix and before_cursor:sub(-2) == "@/" then + logger.debug("parser", "get_file_ref_prefix: closing tag detected, returning nil") + logger.func_exit("parser", "get_file_ref_prefix", nil) + return nil + end + + logger.debug("parser", "get_file_ref_prefix: prefix=" .. tostring(prefix)) + logger.func_exit("parser", "get_file_ref_prefix", prefix) + + return prefix +end + +return get_file_ref_prefix diff --git a/lua/codetyper/parser/get_last_prompt.lua b/lua/codetyper/parser/get_last_prompt.lua new file mode 100644 index 0000000..528b834 --- /dev/null +++ b/lua/codetyper/parser/get_last_prompt.lua @@ -0,0 +1,26 @@ +local logger = require("codetyper.support.logger") +local find_prompts_in_buffer = require("codetyper.parser.find_prompts_in_buffer") + +--- Get the last closed prompt in buffer +---@param bufnr? number Buffer number (default: current) +---@return CoderPrompt|nil Last prompt or nil +local function get_last_prompt(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() + + logger.func_entry("parser", "get_last_prompt", { bufnr = bufnr }) + + local prompts = find_prompts_in_buffer(bufnr) + + if #prompts > 0 then + local last = prompts[#prompts] + logger.debug("parser", "get_last_prompt: returning prompt at line " .. last.start_line) + logger.func_exit("parser", "get_last_prompt", "prompt at line " .. last.start_line) + return last + end + + logger.debug("parser", "get_last_prompt: no prompts found") + logger.func_exit("parser", "get_last_prompt", nil) + return nil +end + +return get_last_prompt diff --git a/lua/codetyper/parser/get_prompt_at_cursor.lua b/lua/codetyper/parser/get_prompt_at_cursor.lua new file mode 100644 index 0000000..d1b8ba4 --- /dev/null +++ b/lua/codetyper/parser/get_prompt_at_cursor.lua @@ -0,0 +1,56 @@ +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_closing_tag.lua b/lua/codetyper/parser/has_closing_tag.lua new file mode 100644 index 0000000..0472de3 --- /dev/null +++ b/lua/codetyper/parser/has_closing_tag.lua @@ -0,0 +1,19 @@ +local utils = require("codetyper.support.utils") +local logger = require("codetyper.support.logger") + +--- Check if line contains a closing tag +---@param line string Line to check +---@param close_tag string Closing tag +---@return boolean +local function has_closing_tag(line, close_tag) + logger.func_entry("parser", "has_closing_tag", { line_preview = line:sub(1, 30), close_tag = close_tag }) + + local result = line:find(utils.escape_pattern(close_tag)) ~= nil + + logger.debug("parser", "has_closing_tag: result=" .. tostring(result)) + logger.func_exit("parser", "has_closing_tag", result) + + return result +end + +return has_closing_tag diff --git a/lua/codetyper/parser/has_unclosed_prompts.lua b/lua/codetyper/parser/has_unclosed_prompts.lua new file mode 100644 index 0000000..918551e --- /dev/null +++ b/lua/codetyper/parser/has_unclosed_prompts.lua @@ -0,0 +1,34 @@ +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/parser/is_cursor_in_open_tag.lua b/lua/codetyper/parser/is_cursor_in_open_tag.lua new file mode 100644 index 0000000..9b301ed --- /dev/null +++ b/lua/codetyper/parser/is_cursor_in_open_tag.lua @@ -0,0 +1,56 @@ +local utils = require("codetyper.support.utils") +local logger = require("codetyper.support.logger") +local get_config = require("codetyper.utils.get_config").get_config + +--- Check if cursor is inside an unclosed prompt tag +---@param bufnr? number Buffer number (default: current) +---@return boolean is_inside Whether cursor is inside an open tag +---@return number|nil start_line Line where the open tag starts +local function is_cursor_in_open_tag(bufnr) + bufnr = bufnr or vim.api.nvim_get_current_buf() + + logger.func_entry("parser", "is_cursor_in_open_tag", { bufnr = bufnr }) + + local cursor = vim.api.nvim_win_get_cursor(0) + local cursor_line = cursor[1] + + local lines = vim.api.nvim_buf_get_lines(bufnr, 0, cursor_line, false) + 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 = 0 + local close_count = 0 + local last_open_line = nil + + for line_num, line in ipairs(lines) do + for _ in line:gmatch(escaped_open) do + open_count = open_count + 1 + last_open_line = line_num + logger.debug("parser", "is_cursor_in_open_tag: found open tag at line " .. line_num) + end + for _ in line:gmatch(escaped_close) do + close_count = close_count + 1 + logger.debug("parser", "is_cursor_in_open_tag: found close tag at line " .. line_num) + end + end + + local is_inside = open_count > close_count + + logger.debug( + "parser", + "is_cursor_in_open_tag: open=" + .. open_count + .. ", close=" + .. close_count + .. ", is_inside=" + .. tostring(is_inside) + .. ", last_open_line=" + .. tostring(last_open_line) + ) + logger.func_exit("parser", "is_cursor_in_open_tag", { is_inside = is_inside, last_open_line = last_open_line }) + + return is_inside, is_inside and last_open_line or nil +end + +return is_cursor_in_open_tag diff --git a/lua/codetyper/parser/strip_file_references.lua b/lua/codetyper/parser/strip_file_references.lua new file mode 100644 index 0000000..a97cc08 --- /dev/null +++ b/lua/codetyper/parser/strip_file_references.lua @@ -0,0 +1,17 @@ +local logger = require("codetyper.support.logger") + +--- Remove file references from prompt content (for clean prompt text) +---@param content string Prompt content +---@return string Cleaned content without file references +local function strip_file_references(content) + logger.func_entry("parser", "strip_file_references", { content_length = #content }) + + local result = content:gsub("@([%w%._%-][%w%._%-/]*)", "") + + logger.debug("parser", "strip_file_references: stripped " .. (#content - #result) .. " chars") + logger.func_exit("parser", "strip_file_references", "length=" .. #result) + + return result +end + +return strip_file_references