Files
tia-lsp.nvim/lua/scl/variables.lua
T
2026-02-14 15:05:12 +01:00

205 lines
5.9 KiB
Lua

-- SCL Variable Extractor
-- Extracts local variables from VAR sections and provides them with # prefix
local M = {}
local ts = vim.treesitter
-- Query to find variable declarations with their types
-- Simplified query that captures all var_item nodes regardless of parent type
local var_query_string = [[
(var_item
name: (identifier) @var_name
type: (_) @var_type)
]]
-- Check if cursor is inside a VAR section
local function is_in_var_section()
local cursor_node = ts.get_node()
if not cursor_node then
return false
end
local node = cursor_node
while node do
local type = node:type()
if type == "var_declaration" then
return true
end
if type == "organization_block" or type == "function_block" then
-- Check if we're in the var declarations part (before BEGIN)
local parent = node
local children = {}
for child in parent:iter_children() do
table.insert(children, child)
end
for _, child in ipairs(children) do
if child:type() == "var_declaration" then
-- Check if cursor is before this var_declaration
local start_row = child:range()
local cursor_row = vim.api.nvim_win_get_cursor(0)[1] - 1
if cursor_row <= start_row then
return true
end
end
if child:type() == "BEGIN" then
local start_row = child:range()
local cursor_row = vim.api.nvim_win_get_cursor(0)[1] - 1
if cursor_row < start_row then
return true
end
end
end
return false
end
node = node:parent()
end
return false
end
-- Extract all local variables from the buffer with their types
function M.extract_variables(bufnr)
bufnr = bufnr or vim.api.nvim_get_current_buf()
local variables = {}
-- Try treesitter first
local ok, parser = pcall(ts.get_parser, bufnr, "scl")
if ok and parser then
local tree = parser:parse()[1]
if tree then
local root = tree:root()
local query_ok, query = pcall(ts.query.parse, "scl", var_query_string)
if query_ok then
for _, match, _ in query:iter_matches(root, bufnr, 0, -1) do
local var_name = nil
local var_type = nil
for id, nodes in pairs(match) do
-- nodes can be a single node or a table of nodes
local node_list = type(nodes) == "table" and nodes or { nodes }
for _, node in ipairs(node_list) do
if node and type(node.range) == "function" then
local text = ts.get_node_text(node, bufnr)
local capture_name = query.captures[id]
if capture_name == "var_name" then
var_name = text
elseif capture_name == "var_type" then
var_type = text
end
end
end
end
if var_name then
table.insert(variables, {
name = var_name,
type = var_type or "",
})
end
end
return variables
end
end
end
-- Fallback: simple regex-based extraction
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local in_var = false
for _, line in ipairs(lines) do
if line:match("^%s*VAR") or line:match("^%s*VAR_INPUT") or line:match("^%s*VAR_OUTPUT") or line:match("^%s*VAR_IN_OUT") then
in_var = true
elseif line:match("^%s*END_VAR") then
in_var = false
elseif in_var then
-- Match variable declarations like: "name : Type;" or "name : \"UdtType\";"
-- Handle both basic types and quoted UDT types
local var_name, var_type = line:match("^%s*([%w_]+)%s*:%s*([^;]+)")
if var_name then
-- Clean up type (remove attributes, default values, comments)
if var_type then
-- Remove attributes { ... }
var_type = var_type:gsub("%s*{.-}%s*", " ")
-- Remove default value := ...
var_type = var_type:gsub("%s*:=.*$", "")
-- Remove trailing comments
var_type = var_type:gsub("%s*//.*$", "")
-- Trim whitespace
var_type = var_type:gsub("^%s*", ""):gsub("%s*$", "")
end
table.insert(variables, {
name = var_name,
type = var_type,
})
end
end
end
return variables
end
-- Get the type of a specific variable
function M.get_variable_type(var_name, bufnr)
local variables = M.extract_variables(bufnr)
for _, var in ipairs(variables) do
if var.name == var_name then
return var.type
end
end
return nil
end
-- Get all variables and their types as a map
function M.get_variable_types(bufnr)
local variables = M.extract_variables(bufnr)
local types = {}
for _, var in ipairs(variables) do
types[var.name] = var.type
end
return types
end
-- Get completion items based on context
function M.get_completions(bufnr)
local variables = M.extract_variables(bufnr)
local in_var_section = is_in_var_section()
local items = {}
for _, var in ipairs(variables) do
if in_var_section then
-- Inside VAR section: offer plain name
table.insert(items, {
label = var.name,
kind = vim.lsp.protocol.CompletionItemKind.Variable,
detail = var.type and (var.type .. " - Local variable") or "Local variable",
})
else
-- Outside VAR section: offer with # prefix
table.insert(items, {
label = "#" .. var.name,
insertText = "#" .. var.name,
kind = vim.lsp.protocol.CompletionItemKind.Variable,
detail = var.type and (var.type .. " - Local variable (prefixed)") or "Local variable (prefixed)",
})
end
end
return items
end
-- Check if we should provide completions
function M.should_complete()
local ft = vim.bo.filetype
if ft ~= "scl" then
return false
end
-- Check if treesitter parser is available
local ok, parser = pcall(ts.get_parser, 0, "scl")
return ok and parser ~= nil
end
return M