feat: add interactive attribute block toggle with keybindings and commands

Add ability to expand/collapse SCL variable attribute blocks like {EXTERNALACCESSIBLE := 'false'} to {...}

Features:
- Per-variable collapse rules via collapseVariableRules option
- Interactive toggle with <Leader>xa keybinding
- Commands: SCLToggleAttrBlock, SCLExpandAllAttrBlocks, SCLCollapseAllAttrBlocks
- Keybindings: <Leader>xa (toggle), <Leader>xae (expand all), <Leader>xac (collapse all)
- Supports both formatter-collapsed and manually collapsed blocks

Also update AGENTS.md with new documentation and troubleshooting guide
This commit is contained in:
2026-02-19 10:12:58 +01:00
parent 37ab917422
commit 978e46ba45
4 changed files with 377 additions and 125 deletions
+26 -10
View File
@@ -98,8 +98,8 @@ function M.validate_diagnostics(content, workspace_types, root_dir)
end
end
if in_var_section and trimmed:match("^[%w_]+%s*:") then
local var_name = trimmed:match("^([%w_]+)%s*:")
if in_var_section and (trimmed:match("^[%w_]+%s*%b{}%s*:") or trimmed:match("^[%w_]+%s*:")) then
local var_name = trimmed:match("^([%w_]+)%s*%b{}%s*:") or trimmed:match("^([%w_]+)%s*:")
local var_start = line:find(var_name, 1, true)
if var_name and not var_name:match("^%d") then
@@ -114,21 +114,37 @@ function M.validate_diagnostics(content, workspace_types, root_dir)
})
end
local type_part = line:match(":%s*([^;]+)")
local type_part = nil
local brace_depth = 0
local last_colon = nil
for i = 1, #line do
local c = line:sub(i, i)
if c == "{" then
brace_depth = brace_depth + 1
elseif c == "}" then
brace_depth = brace_depth - 1
elseif c == ":" and brace_depth == 0 then
last_colon = i
end
end
if last_colon then
type_part = line:sub(last_colon + 1)
end
if type_part then
type_part = type_part:gsub("%s*:=.*$", "")
type_part = type_part:gsub("%s*$", "")
type_part = type_part:gsub("^%s+", "")
local base_type = type_part:match("^(%w+)")
local upper_type = base_type and base_type:upper() or nil
if base_type and not all_types[base_type] and
base_type ~= "BOOL" and base_type ~= "INT" and base_type ~= "DINT" and
base_type ~= "REAL" and base_type ~= "LREAL" and base_type ~= "BYTE" and
base_type ~= "WORD" and base_type ~= "DWORD" and base_type ~= "LWORD" and
base_type ~= "CHAR" and base_type ~= "STRING" and base_type ~= "WCHAR" and
base_type ~= "WSTRING" and base_type ~= "TIME" and base_type ~= "DATE" and
base_type ~= "TIME_OF_DAY" and base_type ~= "DATE_AND_TIME" and
base_type ~= "S5TIME" and base_type ~= "LTIME" then
upper_type ~= "BOOL" and upper_type ~= "INT" and upper_type ~= "DINT" and
upper_type ~= "REAL" and upper_type ~= "LREAL" and upper_type ~= "BYTE" and
upper_type ~= "WORD" and upper_type ~= "DWORD" and upper_type ~= "LWORD" and
upper_type ~= "CHAR" and upper_type ~= "STRING" and upper_type ~= "WCHAR" and
upper_type ~= "WSTRING" and upper_type ~= "TIME" and upper_type ~= "DATE" and
upper_type ~= "TIME_OF_DAY" and upper_type ~= "DATE_AND_TIME" and
upper_type ~= "S5TIME" and upper_type ~= "LTIME" then
table.insert(diagnostics, {
range = range_to_lsp(line_num, 0, line_num, #line),
severity = DiagnosticSeverity.Warning,