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 lspconfig_avail, lspconfig = pcall(require, "lspconfig") if not lspconfig_avail then vim.notify("lspconfig not found. Please install nvim-lspconfig.", vim.log.levels.ERROR) return end 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 get_root_dir(fname) local root = vim.fs.root(fname, root_patterns) return root or vim.fn.fnamemodify(fname, ":h") end local function on_attach(client, bufnr) if opts.lsp and opts.lsp.on_attach then opts.lsp.on_attach(client, bufnr) end if opts.lsp.formatting then vim.api.nvim_buf_create_user_command(bufnr, "LspSCLFormat", function() vim.lsp.buf.format({ name = "scl-language-server" }) end, {}) end end lspconfig.scl_lsp = { default_config = { name = "scl-language-server", cmd = { "lua", server_path }, filetypes = { "scl" }, root_dir = get_root_dir, settings = {}, on_attach = on_attach, }, docs = { description = "Language server for Siemens SCL (LSP, Linter, Formatter)", default_config = { name = "scl-language-server", cmd = { "lua", server_path }, filetypes = { "scl" }, root_dir = "root_pattern('.git', 'data_types', 'plc.data.json')", }, }, } M.setup_syntax(opts) 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.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, {}) end return M