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" }) 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" }) 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", "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" }) 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" }, "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", "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