diff --git a/lua/tia_lsp/init.lua b/lua/tia_lsp/init.lua index 93a81e0..c8e4e9b 100644 --- a/lua/tia_lsp/init.lua +++ b/lua/tia_lsp/init.lua @@ -1,399 +1,422 @@ local M = {} local function setup_package_path() - local plugin_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or "" - plugin_path = plugin_path:gsub("/lua/tia_lsp", "") - local lua_path = plugin_path .. "/lua" - if not package.path:match(lua_path) then - package.path = lua_path .. "/?.lua;" .. package.path - end + local plugin_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or "" + plugin_path = plugin_path:gsub("/lua/tia_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 {} + setup_package_path() + opts = opts or {} - local server_path = opts.server_path or vim.fn.expand("~/dev/tia-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") - - -- Prefer the mason-installed executable (vim.fn.exepath resolves on each - -- buffer enter, so a :MasonInstall mid-session is picked up without restart). - -- Fall back to running the server via `lua ` for manual installs. - local tia_bin = vim.fn.exepath("tia-lsp") - local cmd = tia_bin ~= "" and { "tia-lsp" } or { "lua", server_path } - - vim.lsp.start({ - name = "tia_lsp", - cmd = cmd, - 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, "SCLFormat", function() - vim.lsp.buf.format({ name = "tia_lsp" }) - 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) - M.notify_parser_install_if_needed(args.buf) - -- Start tree-sitter highlighting if the parser is registered - pcall(vim.treesitter.start, args.buf, "scl") - end, - }) - - -- Create global format command (works on current buffer) - vim.api.nvim_create_user_command("SCLFormat", function() - vim.lsp.buf.format({ name = "tia_lsp" }) - 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) - M.notify_parser_install_if_needed(bufnr) - pcall(vim.treesitter.start, bufnr, "scl") - end - end + local server_path = opts.server_path or vim.fn.expand("~/Documents/lua/tia-lsp/src/main.lua") - M.setup_syntax(opts) - M.setup_multiline_params() - M.setup_attr_toggle() - M.create_commands() + local root_patterns = { ".git", "data_types", "plc.data.json" } - if opts.auto_prefix ~= false then - M.setup_auto_prefix() - end + 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") + + -- Prefer the mason-installed executable (vim.fn.exepath resolves on each + -- buffer enter, so a :MasonInstall mid-session is picked up without restart). + -- Fall back to running the server via `lua ` for manual installs. + local tia_bin = vim.fn.exepath("tia-lsp") + local cmd = tia_bin ~= "" and { "tia-lsp" } or { "lua", server_path } + + vim.lsp.start({ + name = "tia_lsp", + cmd = cmd, + 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, "SCLFormat", function() + vim.lsp.buf.format({ name = "tia_lsp" }) + 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) + M.notify_parser_install_if_needed(args.buf) + -- Start tree-sitter highlighting if the parser is registered + pcall(vim.treesitter.start, args.buf, "scl") + end, + }) + + -- Create global format command (works on current buffer) + vim.api.nvim_create_user_command("SCLFormat", function() + vim.lsp.buf.format({ name = "tia_lsp" }) + 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) + M.notify_parser_install_if_needed(bufnr) + pcall(vim.treesitter.start, bufnr, "scl") + 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 -- Find a compiled tree-sitter parser (.so) for SCL. -- Looks in: opts.ts_parser_path, mason package dir, ~/dev/tia-lsp. -- Returns the path to parser.so, or nil if not found. function M.find_parser_so(opts) - -- 1. Explicit path from opts - if opts.ts_parser_path and vim.fn.filereadable(opts.ts_parser_path) == 1 then - return opts.ts_parser_path - end - -- 2. Mason install: resolve exepath("tia-lsp") -> real bin -> package dir - local tia_bin = vim.fn.exepath("tia-lsp") - if tia_bin ~= "" then - local real_bin = vim.fn.resolve(vim.fn.fnamemodify(tia_bin, ":p")) - local pkg_dir = vim.fn.fnamemodify(real_bin, ":h:h") - local so = pkg_dir .. "/parser.so" - if vim.fn.filereadable(so) == 1 then - return so - end - end - -- 3. Local dev path - local dev_so = vim.fn.expand("~/dev/tia-lsp/parser.so") - if vim.fn.filereadable(dev_so) == 1 then - return dev_so - end - return nil + -- 1. Explicit path from opts + if opts.ts_parser_path and vim.fn.filereadable(opts.ts_parser_path) == 1 then + return opts.ts_parser_path + end + -- 2. Mason install: resolve exepath("tia-lsp") -> real bin -> package dir + local tia_bin = vim.fn.exepath("tia-lsp") + if tia_bin ~= "" then + local real_bin = vim.fn.resolve(vim.fn.fnamemodify(tia_bin, ":p")) + local pkg_dir = vim.fn.fnamemodify(real_bin, ":h:h") + local so = pkg_dir .. "/parser.so" + if vim.fn.filereadable(so) == 1 then + return so + end + end + -- 3. Local dev path + local dev_so = vim.fn.expand("~/dev/tia-lsp/parser.so") + if vim.fn.filereadable(dev_so) == 1 then + return dev_so + end + return nil end -- Notify the user once per session if the tree-sitter parser isn't available. local parser_notified = false function M.notify_parser_install_if_needed(bufnr) - if parser_notified then return end - if vim.bo[bufnr].filetype ~= "scl" then return end - local ok = pcall(vim.treesitter.get_parser, bufnr, "scl") - if ok then return end - parser_notified = true - vim.schedule(function() - local msg = "SCL: Tree-sitter parser not found. " - if vim.fn.exepath("tia-lsp") ~= "" then - msg = msg .. "Run :MasonInstall tia-lsp to install it." - else - msg = msg .. "Install tia-lsp via mason or compile parser.so manually." - end - vim.notify(msg, vim.log.levels.INFO) - end) + if parser_notified then + return + end + if vim.bo[bufnr].filetype ~= "scl" then + return + end + local ok = pcall(vim.treesitter.get_parser, bufnr, "scl") + if ok then + return + end + parser_notified = true + vim.schedule(function() + local msg = "SCL: Tree-sitter parser not found. " + if vim.fn.exepath("tia-lsp") ~= "" then + msg = msg .. "Run :MasonInstall tia-lsp to install it." + else + msg = msg .. "Install tia-lsp via mason or compile parser.so manually." + end + vim.notify(msg, vim.log.levels.INFO) + end) end function M.setup_syntax(opts) - -- Load the compiled tree-sitter parser (.so) via Neovim's built-in API. - -- This bypasses nvim-treesitter's install system, which reloads its - -- parsers module from source on :TSInstall and wipes runtime config. - local parser_so = M.find_parser_so(opts) - if parser_so then - local ok, err = pcall(vim.treesitter.language.add, "scl", { path = parser_so }) - if not ok then - vim.notify( - "SCL: Failed to load tree-sitter parser from " .. parser_so .. ": " .. tostring(err), - vim.log.levels.WARN - ) - end - end + -- Load the compiled tree-sitter parser (.so) via Neovim's built-in API. + -- This bypasses nvim-treesitter's install system, which reloads its + -- parsers module from source on :TSInstall and wipes runtime config. + local parser_so = M.find_parser_so(opts) + if parser_so then + local ok, err = pcall(vim.treesitter.language.add, "scl", { path = parser_so }) + if not ok then + vim.notify( + "SCL: Failed to load tree-sitter parser from " .. parser_so .. ": " .. tostring(err), + vim.log.levels.WARN + ) + end + end - -- Also register with nvim-treesitter (best-effort, for :TSInstall support - -- in sessions where the user wants to install from source). - local has_ts, parsers = pcall(require, "nvim-treesitter.parsers") - if has_ts and type(parsers) == "table" then - local parser_config - if type(parsers.get_parser_configs) == "function" then - parser_config = parsers.get_parser_configs() - else - parser_config = parsers - end - if type(parser_config) == "table" then - parser_config.scl = { - install_info = { - url = opts.ts_parser_url - or "https://gitea.l-tech.rs/lazar/tia-lsp.git", - files = { "src/parser.c" }, - generate_requires_npm = false, - requires_generate_from_grammar = false, - }, - filetype = "scl", - } - end - end + -- Also register with nvim-treesitter (best-effort, for :TSInstall support + -- in sessions where the user wants to install from source). + local has_ts, parsers = pcall(require, "nvim-treesitter.parsers") + if has_ts and type(parsers) == "table" then + local parser_config + if type(parsers.get_parser_configs) == "function" then + parser_config = parsers.get_parser_configs() + else + parser_config = parsers + end + if type(parser_config) == "table" then + parser_config.scl = { + install_info = { + url = opts.ts_parser_url or "https://gitea.l-tech.rs/lazar/tia-lsp.git", + 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, "tia.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") + if opts.cmp then + local function try_register() + local cmp_ok, cmp = pcall(require, "blink.cmp") + local blink_source_ok, blink_source = pcall(require, "tia.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 + 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 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, "tia.workspace_types") - if ok then - workspace_types.setup({ - project_root = opts.project_root, - library_paths = opts.library_paths, - debug = opts.debug, - }) - end - end + if opts.workspace_types ~= false then + local ok, workspace_types = pcall(require, "tia.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, "tia.auto_prefix") - if ok then - auto_prefix.setup() - end + local ok, auto_prefix = pcall(require, "tia.auto_prefix") + if ok then + auto_prefix.setup() + end end function M.setup_multiline_params() - local ok, mp = pcall(require, "tia.multiline_params") - if not ok then - vim.notify("SCL multiline_params module not found", vim.log.levels.WARN) - return - end + local ok, mp = pcall(require, "tia.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.api.nvim_create_user_command("SCLMultilineParams", function() + mp.fill_multiline_params() + end, {}) - vim.keymap.set("i", "mp", function() - mp.fill_multiline_params() - end, { noremap = true, silent = true, desc = "SCL: Fill multiline parameters" }) + vim.keymap.set("i", "mp", function() + mp.fill_multiline_params() + end, { noremap = true, silent = true, desc = "SCL: Fill multiline parameters" }) - vim.keymap.set("i", "", function() - local ft = vim.bo.filetype - if ft == "scl" or ft == "udt" then - if mp.jump_to_next_param() then - return "" - end - end - return "" - end, { noremap = true, silent = true, desc = "SCL: Jump to next param or Tab" }) + vim.keymap.set("i", "", function() + local ft = vim.bo.filetype + if ft == "scl" or ft == "udt" then + if mp.jump_to_next_param() then + return "" + end + end + return "" + 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, "tia.auto_prefix") - if ok then - auto_prefix.prefix_word() - end + local ok, auto_prefix = pcall(require, "tia.auto_prefix") + if ok then + auto_prefix.prefix_word() + end end function M.show_variables() - local ok, variables = pcall(require, "tia.variables") - if not ok then - vim.notify("SCL variables module not found", vim.log.levels.WARN) - return - end + local ok, variables = pcall(require, "tia.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 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) + 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, "tia.workspace_types") - local ok_udt, udt = pcall(require, "tia.udt_parser") - local ok_fb, fb = pcall(require, "tia.fb_parser") + local ok_ws, ws = pcall(require, "tia.workspace_types") + local ok_udt, udt = pcall(require, "tia.udt_parser") + local ok_fb, fb = pcall(require, "tia.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 + 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() + 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 + 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) + 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, "tia.workspace_types") - local ok_udt, udt = pcall(require, "tia.udt_parser") - local ok_fb, fb = pcall(require, "tia.fb_parser") + local ok_ws, ws = pcall(require, "tia.workspace_types") + local ok_udt, udt = pcall(require, "tia.udt_parser") + local ok_fb, fb = pcall(require, "tia.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 + 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() + udt.clear_cache() + fb.clear_cache() + ws.clear_root_cache() - local udt_result = ws.scan_project_udts() - local fb_result = ws.scan_project_fbs() + 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) + 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, "tia.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, "tia.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, "tia.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, {}) + 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, "tia.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, "tia.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, "tia.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, "tia.attr_toggle") - if not ok then - vim.notify("SCL: attr_toggle module not found", vim.log.levels.WARN) - return - end - - at.setup() + local ok, at = pcall(require, "tia.attr_toggle") + if not ok then + vim.notify("SCL: attr_toggle module not found", vim.log.levels.WARN) + return + end - 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 + at.setup() - -- Toggle individual attribute block under cursor - vim.keymap.set({ "n", "i" }, "xa", function() - at.toggle_attr_block() - end, vim.tbl_extend("force", opts, { desc = "SCL: Toggle attribute block" })) + local function setup_buffer_keymaps(bufnr) + local opts = { buffer = bufnr, silent = true } + local ft = vim.bo[bufnr].filetype - -- Expand all collapsed blocks - vim.keymap.set("n", "xae", function() - at.expand_all_attr_blocks() - end, vim.tbl_extend("force", opts, { desc = "SCL: Expand all attribute blocks" })) + if ft ~= "scl" and ft ~= "udt" then + return + end - -- Collapse all attribute blocks - vim.keymap.set("n", "xac", function() - at.collapse_all_attr_blocks() - end, vim.tbl_extend("force", opts, { desc = "SCL: Collapse all attribute blocks" })) - end + -- Toggle individual attribute block under cursor + vim.keymap.set({ "n", "i" }, "xa", function() + at.toggle_attr_block() + end, vim.tbl_extend("force", opts, { desc = "SCL: Toggle attribute block" })) - -- 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 + -- Expand all collapsed blocks + vim.keymap.set("n", "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", "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