From 1e50b596de0019cd9acb22e6a2d322c0babf08fb Mon Sep 17 00:00:00 2001 From: Lazar Bulovan Date: Wed, 18 Feb 2026 09:21:38 +0100 Subject: [PATCH] fix(blink_cmp_source): Handling of DB patterns after operators The completion now correctly handles DB patterns after any operator (:=, =>, =, <>, >=, <=, >, <, AND, OR, NOT) by finding the last operator and only matching DB patterns in the active portion after it. --- lua/scl/blink_cmp_source.lua | 73 +++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/lua/scl/blink_cmp_source.lua b/lua/scl/blink_cmp_source.lua index 4750fe0..7e5f813 100644 --- a/lua/scl/blink_cmp_source.lua +++ b/lua/scl/blink_cmp_source.lua @@ -33,8 +33,8 @@ function M.is_available(context) local char_at = line:sub(col, col) local prev_char = col > 1 and line:sub(col - 1, col - 1) or "" - -- Only trigger on our specific characters - if char_at == "." or char_at == "(" or char_at == "#" or char_at == '"' or + -- Only trigger on our specific characters (including space for := assignments) + if char_at == "." or char_at == "(" or char_at == "#" or char_at == '"' or char_at == " " or prev_char == "." or prev_char == "(" or prev_char == "#" or prev_char == '"' then return true end @@ -157,31 +157,71 @@ function M:get_completions(context, callback) -- Check for Global DB completion (quoted DB names like "HmiData") local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "") + + -- Find the "active portion" of the line - after the last operator if present + -- Operators: :=, =>, =, <>, >=, <=, >, <, AND, OR, NOT + -- This ensures we match DB patterns nearest to the cursor, not the first in line + local active_portion = line_before_cursor - -- Check for "DBName" pattern (quoted DB name access with dot) - local db_member_pattern = trimmed:match('"([%w_]+)"%.([%w_]*)') + local function find_last_operator_pos(text) + local last_pos = 0 + local operators = {":=", "=>", "<>", ">=", "<=", "=", ">", "<"} + for _, op in ipairs(operators) do + local search_start = 1 + while true do + local pos = text:find(op, search_start, true) + if not pos then break end + if pos > last_pos then + last_pos = pos + #op + end + search_start = pos + 1 + end + end + -- Also check for AND/OR/NOT keywords (case insensitive) + local keywords = {"%sAND%s", "%sOR%s", "%sNOT%s"} + for _, kw in ipairs(keywords) do + local search_start = 1 + while true do + local pos = text:upper():find(kw, search_start) + if not pos then break end + if pos > last_pos then + last_pos = pos + #kw - 1 + end + search_start = pos + 1 + end + end + return last_pos + end - -- Check if we're typing a quoted DB name (after " but before closing ") - local is_in_db_name = trimmed:match('"[%w_]*$') ~= nil + local last_op_pos = find_last_operator_pos(line_before_cursor) + if last_op_pos > 0 then + active_portion = line_before_cursor:sub(last_op_pos):gsub("^%s+", "") + end + + -- Check for "DBName" pattern (quoted DB name access with dot) - in active portion only + local db_member_pattern = active_portion:match('"([%w_]+)"%.([%w_.]*)') - -- Check for partial DB name like "HmiData (unclosed quote) - local partial_db_match = trimmed:match('"([%w_]+)$') - local is_partial_db = partial_db_match ~= nil and trimmed:match('"[^"]*$') ~= nil + -- Check if we're typing a quoted DB name (after " but before closing ") - in active portion + local is_in_db_name = active_portion:match('"[%w_]*$') ~= nil - -- Check if after a colon followed by quote (type declaration context) + -- Check for partial DB name like "HmiData (unclosed quote) - in active portion + local partial_db_match = active_portion:match('"([%w_]+)$') + local is_partial_db = partial_db_match ~= nil and active_portion:match('"[^"]*$') ~= nil + + -- Check if after a colon followed by quote (type declaration context) - use full trimmed line local is_after_colon_quote = trimmed:match(':%s*"$') ~= nil - -- Check if cursor is right after a closed quoted DB name (before dot) - local after_closed_db = trimmed:match('"([%w_]+)"%s*%.') ~= nil + -- Check if cursor is right after a closed quoted DB name (before dot) - in active portion + local after_closed_db = active_portion:match('"([%w_]+)"%s*%.') ~= nil if db_member_pattern or after_closed_db then local db_parser = require("scl.db_parser") - local db_name = trimmed:match('"([%w_]+)"') + local db_name = active_portion:match('"([%w_]+)"') if db_name then local db = db_parser.get_db(db_name) if db then - local path = trimmed:match('"[^"]+%"%.([%w_.]*)') + local path = active_portion:match('"[^"]+%"%.([%w_.]*)') -- Resolve nested path through DB members local current_type = nil @@ -230,7 +270,7 @@ function M:get_completions(context, callback) end if not found then - wrapped_callback({ items = {} }) + callback({ items = {}, is_incomplete_backward = false, is_incomplete_forward = false }) return end end @@ -239,8 +279,7 @@ function M:get_completions(context, callback) if path and path ~= "" and path:match("%.$") then -- User wants members of the last type elseif path and path ~= "" and not path:match("%.$") then - -- User is typing a member name, don't show completions yet - wrapped_callback({ items = {} }) + callback({ items = {}, is_incomplete_backward = false, is_incomplete_forward = false }) return end