feat: Add Global Data Block recognition in workspace scope
- Add db_parser.lua to parse .db files (DATA_BLOCK with VAR sections) - Update workspace_types.lua to scan for .db files in project - Update blink_cmp_source.lua to provide DB auto-completion: - DB names available in general completion (without quotes) - Type quote to get DB members - Multi-level nested member access support - Add DB count to workspace commands - Update README with Global DB documentation
This commit is contained in:
@@ -8,7 +8,7 @@ function M.new(opts)
|
||||
end
|
||||
|
||||
function M:get_trigger_characters()
|
||||
return { ".", "(", "#", " " }
|
||||
return { ".", "(", "#", " ", '"' }
|
||||
end
|
||||
|
||||
function M.is_available(context)
|
||||
@@ -34,8 +34,8 @@ function M.is_available(context)
|
||||
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
|
||||
prev_char == "." or prev_char == "(" or prev_char == "#" then
|
||||
if 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
|
||||
end
|
||||
@@ -155,6 +155,167 @@ function M:get_completions(context, callback)
|
||||
return
|
||||
end
|
||||
|
||||
-- Check for Global DB completion (quoted DB names like "HmiData")
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
|
||||
-- Check for "DBName" pattern (quoted DB name access with dot)
|
||||
local db_member_pattern = trimmed:match('"([%w_]+)"%.([%w_]*)')
|
||||
|
||||
-- Check if we're typing a quoted DB name (after " but before closing ")
|
||||
local is_in_db_name = trimmed:match('"[%w_]*$') ~= nil
|
||||
|
||||
-- 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 after a colon followed by quote (type declaration context)
|
||||
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
|
||||
|
||||
if db_member_pattern or after_closed_db then
|
||||
local db_parser = require("scl.db_parser")
|
||||
local db_name = trimmed:match('"([%w_]+)"')
|
||||
|
||||
if db_name then
|
||||
local db = db_parser.get_db(db_name)
|
||||
if db then
|
||||
local path = trimmed:match('"[^"]+%"%.([%w_.]*)')
|
||||
|
||||
-- Resolve nested path through DB members
|
||||
local current_type = nil
|
||||
local current_members = db_parser.get_db_members(db_name)
|
||||
local resolved_path = ""
|
||||
|
||||
if path and path ~= "" then
|
||||
-- Parse the path parts
|
||||
local path_parts = {}
|
||||
for part in path:gmatch("[^.]+") do
|
||||
table.insert(path_parts, part)
|
||||
end
|
||||
|
||||
-- Resolve each part of the path
|
||||
local found = true
|
||||
for i, part in ipairs(path_parts) do
|
||||
local found_member = nil
|
||||
for _, m in ipairs(current_members) do
|
||||
if m.name == part then
|
||||
found_member = m
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if found_member then
|
||||
resolved_path = part
|
||||
if found_member.is_udt then
|
||||
-- This member is a UDT type, continue resolving
|
||||
local udt_parser = require("scl.udt_parser")
|
||||
current_members = udt_parser.get_udt_members(found_member.type)
|
||||
current_type = found_member.type
|
||||
else
|
||||
-- This is a basic type, can't continue
|
||||
if i < #path_parts then
|
||||
-- There are more parts but this isn't a UDT
|
||||
found = false
|
||||
break
|
||||
end
|
||||
current_type = found_member.type
|
||||
current_members = {}
|
||||
end
|
||||
else
|
||||
found = false
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
if not found then
|
||||
wrapped_callback({ items = {} })
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- If we have a resolved path and it's not complete (ends with .), show next level members
|
||||
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 = {} })
|
||||
return
|
||||
end
|
||||
|
||||
-- Get members to show (either from DB or from resolved UDT type)
|
||||
if current_type then
|
||||
local udt_parser = require("scl.udt_parser")
|
||||
current_members = udt_parser.get_udt_members(current_type)
|
||||
end
|
||||
|
||||
if #current_members > 0 then
|
||||
local items = {}
|
||||
for _, m in ipairs(current_members) do
|
||||
table.insert(items, {
|
||||
label = m.name,
|
||||
kind = m.is_udt and vim.lsp.protocol.CompletionItemKind.Struct or vim.lsp.protocol.CompletionItemKind.Field,
|
||||
detail = m.raw_type,
|
||||
})
|
||||
end
|
||||
local wrapped_callback = vim.schedule_wrap(function(response)
|
||||
callback({
|
||||
items = response.items,
|
||||
is_incomplete_backward = false,
|
||||
is_incomplete_forward = false,
|
||||
})
|
||||
end)
|
||||
wrapped_callback({ items = items })
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Check for just " pattern (start of DB name completion or partial DB name)
|
||||
if is_in_db_name or is_partial_db or is_after_colon_quote then
|
||||
local db_parser = require("scl.db_parser")
|
||||
|
||||
-- If cache is empty, try to scan workspace for DBs
|
||||
if db_parser.get_cache_count() == 0 then
|
||||
local ws = require("scl.workspace_types")
|
||||
ws.scan_project_dbs(bufnr)
|
||||
end
|
||||
|
||||
local db_names = db_parser.get_all_db_names()
|
||||
if #db_names > 0 then
|
||||
local items = {}
|
||||
local partial_name = partial_db_match or ""
|
||||
|
||||
for _, name in ipairs(db_names) do
|
||||
-- Filter by partial match if user is typing
|
||||
if partial_name == "" or name:lower():find(partial_name:lower(), 1, true) == 1 then
|
||||
table.insert(items, {
|
||||
label = name, -- Show without quotes in popup
|
||||
insertText = name, -- Don't include quotes - user already typed opening "
|
||||
kind = vim.lsp.protocol.CompletionItemKind.Struct,
|
||||
detail = "Global DB",
|
||||
-- Custom field to identify this as a DB that needs quote wrapping
|
||||
custom = { is_global_db = true },
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
if #items > 0 then
|
||||
local wrapped_callback = vim.schedule_wrap(function(response)
|
||||
callback({
|
||||
items = response.items,
|
||||
is_incomplete_backward = false,
|
||||
is_incomplete_forward = false,
|
||||
})
|
||||
end)
|
||||
wrapped_callback({ items = items })
|
||||
return
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local scl_vars = require("scl.variables")
|
||||
local variables = scl_vars.extract_variables(bufnr)
|
||||
|
||||
@@ -527,6 +688,7 @@ function M:get_completions(context, callback)
|
||||
|
||||
vim.schedule(function()
|
||||
local udt_parser = require("scl.udt_parser")
|
||||
local db_parser = require("scl.db_parser")
|
||||
|
||||
-- No member access = show local variables, UDT types, and basic types
|
||||
if not base_var then
|
||||
@@ -555,6 +717,29 @@ function M:get_completions(context, callback)
|
||||
for _, name in ipairs(udt_parser.get_all_udt_names()) do
|
||||
table.insert(items, { label = '"' .. name .. '"', kind = 14 })
|
||||
end
|
||||
|
||||
-- Add Global DB names (without quotes - will be wrapped in resolve)
|
||||
local db_parser = require("scl.db_parser")
|
||||
if db_parser.get_cache_count() == 0 then
|
||||
local ws = require("scl.workspace_types")
|
||||
ws.scan_project_dbs(bufnr)
|
||||
end
|
||||
|
||||
-- Check if cursor is after a quote (user typed " first)
|
||||
local line_before_cursor = line:sub(1, col)
|
||||
local is_after_quote = line_before_cursor:match('"%s*$') ~= nil
|
||||
|
||||
for _, name in ipairs(db_parser.get_all_db_names()) do
|
||||
-- If user already typed opening quote, don't add quotes. Otherwise wrap in quotes.
|
||||
local insert_name = is_after_quote and name or '"' .. name .. '"'
|
||||
table.insert(items, {
|
||||
label = name, -- No quotes in label
|
||||
insertText = insert_name,
|
||||
kind = vim.lsp.protocol.CompletionItemKind.Struct,
|
||||
detail = "Global DB",
|
||||
})
|
||||
end
|
||||
|
||||
-- Add basic types
|
||||
for _, t in ipairs({ "Bool", "Int", "DInt", "Real", "Word", "DWord", "Byte", "Time", "String", "SINT", "USINT", "UINT", "LREAL" }) do
|
||||
table.insert(items, { label = t, kind = 19 })
|
||||
@@ -588,6 +773,18 @@ function M:get_completions(context, callback)
|
||||
for _, name in ipairs(udt_parser.get_all_udt_names()) do
|
||||
table.insert(items, { label = '"' .. name .. '"', kind = 14 })
|
||||
end
|
||||
-- Add Global DB names (without quotes - will be wrapped in resolve)
|
||||
local line_before_cursor_fallback = line:sub(1, col)
|
||||
local is_after_quote_fallback = line_before_cursor_fallback:match('"%s*$') ~= nil
|
||||
for _, name in ipairs(db_parser.get_all_db_names()) do
|
||||
local insert_name = is_after_quote_fallback and name or '"' .. name .. '"'
|
||||
table.insert(items, {
|
||||
label = name,
|
||||
insertText = insert_name,
|
||||
kind = vim.lsp.protocol.CompletionItemKind.Struct,
|
||||
detail = "Global DB",
|
||||
})
|
||||
end
|
||||
for _, t in ipairs({ "Bool", "Int", "DInt", "Real", "Word", "DWord", "Byte", "Time", "String", "SINT", "USINT", "UINT", "LREAL" }) do
|
||||
table.insert(items, { label = t, kind = 19 })
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user