Files
tia-lsp.nvim/lua/scl_lsp/init.lua
T
lazar becf45d348 fix: clear attr_toggle state after formatting
When :LspSCLFormat is used, line numbers and positions change,
but the attr_toggle module stores state based on old positions.
This caused toggle/expand/collapse to not work after formatting.

Now clears the attr_toggle buffer state after formatting so that
users can collapse blocks fresh after formatting.
2026-02-19 11:17:59 +01:00

331 lines
9.8 KiB
Lua

local M = {}
local function setup_package_path()
local plugin_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or ""
plugin_path = plugin_path:gsub("/lua/scl_lsp", "")
local lua_path = plugin_path .. "/lua"
if not package.path:match(lua_path) then
package.path = lua_path .. "/?.lua;" .. package.path
end
end
function M.setup(opts)
setup_package_path()
opts = opts or {}
local server_path = opts.server_path or vim.fn.expand("~/dev/scl_lsp/src/main.lua")
local root_patterns = { ".git", "data_types", "plc.data.json" }
local function start_lsp(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
if fname == "" then return end
local root_dir = vim.fs.root(fname, root_patterns)
or vim.fn.fnamemodify(fname, ":p:h")
vim.lsp.start({
name = "scl_lsp",
cmd = { "lua", server_path },
root_dir = root_dir,
on_attach = function(client, b)
if opts.lsp and opts.lsp.on_attach then
opts.lsp.on_attach(client, b)
end
if opts.lsp and opts.lsp.formatting then
vim.api.nvim_buf_create_user_command(b, "LspSCLFormat", function()
vim.lsp.buf.format({ name = "scl_lsp" })
-- Clear attr_toggle state since line positions may have changed
local ok, at = pcall(require, "scl.attr_toggle")
if ok then
at.clear_buffer_state(vim.api.nvim_get_current_buf())
end
end, {})
end
end,
})
end
-- Create FileType autocmd for future buffers
vim.api.nvim_create_autocmd("FileType", {
pattern = "scl",
callback = function(args)
start_lsp(args.buf)
end,
})
-- Create global format command (works on current buffer)
vim.api.nvim_create_user_command("LspSCLFormat", function()
vim.lsp.buf.format({ name = "scl_lsp" })
-- Clear attr_toggle state since line positions may have changed
local ok, at = pcall(require, "scl.attr_toggle")
if ok then
at.clear_buffer_state(vim.api.nvim_get_current_buf())
end
end, {})
-- Start LSP for any existing scl buffers (handles lazy loading case)
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.bo[bufnr].filetype == "scl" and vim.api.nvim_buf_is_loaded(bufnr) then
start_lsp(bufnr)
end
end
M.setup_syntax(opts)
M.setup_multiline_params()
M.setup_attr_toggle()
M.create_commands()
if opts.auto_prefix ~= false then
M.setup_auto_prefix()
end
end
function M.setup_syntax(opts)
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 = {
url = "file://" .. vim.fn.expand("~/dev/scl_lsp"),
files = { "src/parser.c" },
generate_requires_npm = false,
requires_generate_from_grammar = false,
},
filetype = "scl",
}
end
end
if opts.cmp then
local function try_register()
local cmp_ok, cmp = pcall(require, "blink.cmp")
local blink_source_ok, blink_source = pcall(require, "scl.blink_cmp_source")
if cmp_ok and cmp and cmp.add_source_provider and blink_source_ok then
local source_config = {
name = "scl",
provider = blink_source,
score_offset = 100,
}
pcall(cmp.add_source_provider, "scl", source_config)
pcall(cmp.add_filetype_source, "scl", "scl")
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
if opts.workspace_types ~= false then
local ok, workspace_types = pcall(require, "scl.workspace_types")
if ok then
workspace_types.setup({
project_root = opts.project_root,
library_paths = opts.library_paths,
debug = opts.debug,
})
end
end
end
function M.setup_auto_prefix()
local ok, auto_prefix = pcall(require, "scl.auto_prefix")
if ok then
auto_prefix.setup()
end
end
function M.setup_multiline_params()
local ok, mp = pcall(require, "scl.multiline_params")
if not ok then
vim.notify("SCL multiline_params module not found", vim.log.levels.WARN)
return
end
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.prefix_current_word()
local ok, auto_prefix = pcall(require, "scl.auto_prefix")
if ok then
auto_prefix.prefix_word()
end
end
function M.show_variables()
local ok, variables = pcall(require, "scl.variables")
if not ok then
vim.notify("SCL variables module not found", vim.log.levels.WARN)
return
end
local vars = 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 ok_ws, ws = pcall(require, "scl.workspace_types")
local ok_udt, udt = pcall(require, "scl.udt_parser")
local ok_fb, fb = pcall(require, "scl.fb_parser")
if not ok_ws or not ok_udt or not ok_fb then
vim.notify("SCL workspace modules not found", vim.log.levels.WARN)
return
end
local root = ws.get_project_root()
local udt_names = udt.get_all_udt_names()
local fb_names = fb.get_all_fb_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, vim.log.levels.INFO)
end
function M.rescan_workspace_types()
local ok_ws, ws = pcall(require, "scl.workspace_types")
local ok_udt, udt = pcall(require, "scl.udt_parser")
local ok_fb, fb = pcall(require, "scl.fb_parser")
if not ok_ws or not ok_udt or not ok_fb then
vim.notify("SCL workspace modules not found", vim.log.levels.WARN)
return
end
udt.clear_cache()
fb.clear_cache()
ws.clear_root_cache()
local udt_result = ws.scan_project_udts()
local fb_result = ws.scan_project_fbs()
vim.notify("Scanned UDTs: " .. udt_result.parsed_count .. "/" .. udt_result.total_files ..
", FBs: " .. fb_result.parsed_count .. "/" .. fb_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, {})
vim.api.nvim_create_user_command("SCLPrefixWord", function() M.prefix_current_word() 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: " .. tostring(at), 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: " .. tostring(at), 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: " .. tostring(at), vim.log.levels.ERROR)
end
end, {})
end
function M.setup_attr_toggle()
local ok, at = pcall(require, "scl.attr_toggle")
if not ok then
vim.notify("SCL: attr_toggle module not found", vim.log.levels.WARN)
return
end
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