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
250 lines
7.8 KiB
Lua
250 lines
7.8 KiB
Lua
-- SCL (Siemens Structured Control Language) support for Neovim
|
|
-- Main setup module
|
|
|
|
local M = {}
|
|
|
|
function M.setup(opts)
|
|
opts = opts or {}
|
|
|
|
M.create_commands()
|
|
|
|
-- Treesitter parser config
|
|
local has_ts, ts = pcall(require, "nvim-treesitter.parsers")
|
|
if has_ts and ts.get_parser_configs then
|
|
local ok, parser_config = pcall(ts.get_parser_configs)
|
|
if ok and parser_config then
|
|
parser_config.scl = {
|
|
install_info = {
|
|
files = { "src/parser.c" },
|
|
generate_requires_npm = false,
|
|
requires_generate_from_grammar = false,
|
|
},
|
|
filetype = "scl",
|
|
}
|
|
end
|
|
end
|
|
|
|
-- Register blink.cmp source
|
|
if opts.cmp then
|
|
local function try_register()
|
|
local cmp_ok, cmp = pcall(require, "blink.cmp")
|
|
if cmp_ok and cmp and cmp.add_source_provider then
|
|
local source_config = {
|
|
name = "scl",
|
|
module = "scl.blink_cmp_source",
|
|
score_offset = 100,
|
|
}
|
|
pcall(cmp.add_source_provider, "scl", source_config)
|
|
pcall(cmp.add_filetype_source, "scl", "scl")
|
|
|
|
-- Create autocmd to trigger completion on ( for SCL files
|
|
local augroup = vim.api.nvim_create_augroup("SCLCompletionTrigger", { clear = true })
|
|
vim.api.nvim_create_autocmd("TextChangedI", {
|
|
group = augroup,
|
|
pattern = "*.scl",
|
|
callback = function(args)
|
|
local line = vim.api.nvim_get_current_line()
|
|
local col = vim.api.nvim_win_get_cursor(0)[2]
|
|
-- Check if the character before cursor is (
|
|
if col > 0 and line:sub(col, col) == "(" then
|
|
-- Check if there's a known FB variable before the (
|
|
local before_paren = line:sub(1, col - 1):match("[%w_]+%s*$")
|
|
if before_paren then
|
|
vim.schedule(function()
|
|
pcall(require("blink.cmp").show, { providers = { "scl" } })
|
|
end)
|
|
end
|
|
end
|
|
end,
|
|
})
|
|
|
|
vim.notify("SCL: blink.cmp source registered", vim.log.levels.INFO)
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
if not try_register() then
|
|
vim.api.nvim_create_autocmd("BufEnter", {
|
|
pattern = "*.scl",
|
|
once = true,
|
|
callback = try_register,
|
|
})
|
|
end
|
|
end
|
|
|
|
-- Workspace type scanner
|
|
if opts.workspace_types ~= false then
|
|
local workspace_types = require("scl.workspace_types")
|
|
workspace_types.setup({
|
|
project_root = opts.project_root,
|
|
library_paths = opts.library_paths,
|
|
debug = opts.debug,
|
|
})
|
|
end
|
|
|
|
-- Auto-prefixing (enabled by default)
|
|
M.setup_auto_prefix()
|
|
|
|
-- Multiline parameters feature
|
|
M.setup_multiline_params()
|
|
|
|
-- Attribute block toggle feature
|
|
M.setup_attr_toggle()
|
|
end
|
|
|
|
function M.setup_multiline_params()
|
|
local mp = require("scl.multiline_params")
|
|
|
|
vim.api.nvim_create_user_command("SCLMultilineParams", function()
|
|
mp.fill_multiline_params()
|
|
end, {})
|
|
|
|
vim.keymap.set("i", "<Space>mp", function()
|
|
mp.fill_multiline_params()
|
|
end, { noremap = true, silent = true, desc = "SCL: Fill multiline parameters" })
|
|
|
|
vim.keymap.set("i", "<Tab>", function()
|
|
local ft = vim.bo.filetype
|
|
if ft == "scl" or ft == "udt" then
|
|
if mp.jump_to_next_param() then
|
|
return ""
|
|
end
|
|
end
|
|
return "<Tab>"
|
|
end, { noremap = true, silent = true, desc = "SCL: Jump to next param or Tab" })
|
|
end
|
|
|
|
function M.setup_auto_prefix()
|
|
require("scl.auto_prefix").setup()
|
|
end
|
|
|
|
function M.prefix_current_word()
|
|
require("scl.auto_prefix").prefix_word()
|
|
end
|
|
|
|
function M.show_variables()
|
|
local vars = require("scl.variables").extract_variables()
|
|
if #vars == 0 then
|
|
vim.notify("No local variables found", vim.log.levels.INFO)
|
|
return
|
|
end
|
|
local lines = { "Local Variables:", "---" }
|
|
for _, var in ipairs(vars) do
|
|
local type_info = var.type and (" : " .. var.type) or ""
|
|
table.insert(lines, "#" .. var.name .. type_info)
|
|
end
|
|
vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
|
|
end
|
|
|
|
function M.show_workspace_types()
|
|
local ws = require("scl.workspace_types")
|
|
local udt = require("scl.udt_parser")
|
|
local fb = require("scl.fb_parser")
|
|
local db = require("scl.db_parser")
|
|
local root = ws.get_project_root()
|
|
local udt_names = udt.get_all_udt_names()
|
|
local fb_names = fb.get_all_fb_names()
|
|
local db_names = db.get_all_db_names()
|
|
if not root then
|
|
vim.notify("No project root detected", vim.log.levels.WARN)
|
|
return
|
|
end
|
|
vim.notify("Project: " .. root .. "\nUDTs: " .. #udt_names .. "\nFBs/Functions: " .. #fb_names .. "\nGlobal DBs: " .. #db_names, vim.log.levels.INFO)
|
|
end
|
|
|
|
function M.rescan_workspace_types()
|
|
local ws = require("scl.workspace_types")
|
|
local udt = require("scl.udt_parser")
|
|
local fb = require("scl.fb_parser")
|
|
local db = require("scl.db_parser")
|
|
udt.clear_cache()
|
|
fb.clear_cache()
|
|
db.clear_cache()
|
|
ws.clear_root_cache()
|
|
local udt_result = ws.scan_project_udts()
|
|
local fb_result = ws.scan_project_fbs()
|
|
local db_result = ws.scan_project_dbs()
|
|
vim.notify("Scanned UDTs: " .. udt_result.parsed_count .. "/" .. udt_result.total_files ..
|
|
", FBs: " .. fb_result.parsed_count .. "/" .. fb_result.total_files ..
|
|
", DBs: " .. db_result.parsed_count .. "/" .. db_result.total_files, vim.log.levels.INFO)
|
|
end
|
|
|
|
function M.create_commands()
|
|
vim.api.nvim_create_user_command("SCLShowVariables", function() M.show_variables() end, {})
|
|
vim.api.nvim_create_user_command("SCLShowWorkspaceTypes", function() M.show_workspace_types() end, {})
|
|
vim.api.nvim_create_user_command("SCLRescanWorkspaceTypes", function() M.rescan_workspace_types() end, {})
|
|
|
|
-- Attribute toggle commands
|
|
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function()
|
|
local ok, at = pcall(require, "scl.attr_toggle")
|
|
if ok then
|
|
at.toggle_attr_block()
|
|
else
|
|
vim.notify("SCL: Failed to load attr_toggle module", vim.log.levels.ERROR)
|
|
end
|
|
end, {})
|
|
vim.api.nvim_create_user_command("SCLExpandAllAttrBlocks", function()
|
|
local ok, at = pcall(require, "scl.attr_toggle")
|
|
if ok then
|
|
at.expand_all_attr_blocks()
|
|
else
|
|
vim.notify("SCL: Failed to load attr_toggle module", vim.log.levels.ERROR)
|
|
end
|
|
end, {})
|
|
vim.api.nvim_create_user_command("SCLCollapseAllAttrBlocks", function()
|
|
local ok, at = pcall(require, "scl.attr_toggle")
|
|
if ok then
|
|
at.collapse_all_attr_blocks()
|
|
else
|
|
vim.notify("SCL: Failed to load attr_toggle module", vim.log.levels.ERROR)
|
|
end
|
|
end, {})
|
|
end
|
|
|
|
function M.setup_attr_toggle()
|
|
local at = require("scl.attr_toggle")
|
|
at.setup()
|
|
|
|
local function setup_buffer_keymaps(bufnr)
|
|
local opts = { buffer = bufnr, silent = true }
|
|
local ft = vim.bo[bufnr].filetype
|
|
|
|
if ft ~= "scl" and ft ~= "udt" then
|
|
return
|
|
end
|
|
|
|
-- Toggle individual attribute block under cursor
|
|
vim.keymap.set({ "n", "i" }, "<Leader>xa", function()
|
|
at.toggle_attr_block()
|
|
end, vim.tbl_extend("force", opts, { desc = "SCL: Toggle attribute block" }))
|
|
|
|
-- Expand all collapsed blocks
|
|
vim.keymap.set("n", "<Leader>xae", function()
|
|
at.expand_all_attr_blocks()
|
|
end, vim.tbl_extend("force", opts, { desc = "SCL: Expand all attribute blocks" }))
|
|
|
|
-- Collapse all attribute blocks
|
|
vim.keymap.set("n", "<Leader>xac", function()
|
|
at.collapse_all_attr_blocks()
|
|
end, vim.tbl_extend("force", opts, { desc = "SCL: Collapse all attribute blocks" }))
|
|
end
|
|
|
|
-- Set up keybindings for SCL files only
|
|
vim.api.nvim_create_autocmd("FileType", {
|
|
pattern = { "scl", "udt" },
|
|
callback = function(args)
|
|
setup_buffer_keymaps(args.buf)
|
|
end,
|
|
})
|
|
|
|
-- Also check if there are already SCL buffers open
|
|
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
|
if vim.api.nvim_buf_is_loaded(bufnr) then
|
|
setup_buffer_keymaps(bufnr)
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|