diff --git a/AGENTS.md b/AGENTS.md index 6317527..81aa766 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,9 @@ -# AGENTS.md - SCL Language Server +# AGENTS.md - tia-lsp -A Neovim/LazyVim plugin for Siemens SCL language support providing LSP, linting, formatting, syntax highlighting, and auto-completion. +A Neovim/LazyVim plugin and standalone LSP server for Siemens TIA Portal languages. Currently supports **SCL**; planned support for STL/AWL, LAD, FBD. The project is split into two repos: + +- **`tia-lsp`** (this repo) — LSP server (`src/`) + tree-sitter grammar (`grammar.js`). Editor-agnostic. +- **`tia-lsp.nvim`** — Neovim plugin (`lua/`) that launches the server and adds editor features. ## Build/Lint/Test Commands @@ -17,7 +20,7 @@ tree-sitter parse # Parse single SCL file # Lua Linting luacheck src/ # Lint LSP server code -luacheck lua/scl/ # Lint plugin modules +luacheck lua/tia/ # Lint plugin modules # Unit & Integration Tests lua test/run_tests.lua # Run all tests @@ -36,18 +39,17 @@ lua test/test_integration.lua # Run integration tests ## Project Structure ``` -scl_lsp/ +tia-lsp/ ├── src/ # LSP server (uses dofile) │ ├── main.lua # Entry point, JSON-RPC protocol -│ ├── parser.lua # SCL parser -│ ├── diagnostics.lua # Linter +│ ├── parser.lua # SCL parser (will become parser_scl.lua + dispatch) +│ ├── diagnostics.lua # Linter (source = "tia_lsp") │ ├── formatter.lua # Document formatter │ ├── treesitter.lua # Tree-sitter integration -│ ├── plc_json.lua # External UDT/DB loading +│ ├── plc_json.lua # External UDT/DB loading (language-agnostic) │ ├── builtin_instructions.lua # TIA Portal built-in types │ └── json.lua # JSON encoder/decoder -├── lua/scl/ # Neovim plugin (uses require) -│ ├── init.lua # Main plugin setup +├── lua/tia/ # Neovim plugin (uses require) │ ├── udt_parser.lua # UDT parser │ ├── db_parser.lua # Global DB parser │ ├── fb_parser.lua # Function Block parser @@ -56,13 +58,24 @@ scl_lsp/ │ ├── multiline_params.lua │ ├── auto_prefix.lua │ └── attr_toggle.lua -├── queries/ # Tree-sitter queries -└── grammar.js # Tree-sitter grammar +├── lua/tia_lsp/ # Plugin entry point +│ ├── init.lua # Main plugin setup (vim.lsp.start) +│ └── plugins/scl.lua # LazyVim plugin spec +├── queries/ # Tree-sitter queries (per-language: queries/scl/) +└── grammar.js # Tree-sitter grammar (language name: 'scl') ``` +## Naming Convention + +- **Project name** (repo, Lua module, LSP server, mason package): `tia-lsp` / `tia_lsp` — umbrella for all TIA languages. +- **Language name** (filetype, tree-sitter parser, grammar): `scl` — stays as-is. Future languages (`stl`, `lad`, `fbd`) get their own names. +- **LSP client name**: `tia_lsp` (one client handles all TIA languages; dispatches by `filetype` / `languageId`). +- **Diagnostic source**: `tia_lsp`. +- **Commands**: `:SCLFormat`, `:SCLShowVariables`, etc. — per-filetype prefix. Future: `:STLFormat`, etc. + ## Filetype Detection -Required in Neovim config (ftdetect/scl.vim): +After the split, `ftdetect/scl.vim` will live in `tia-lsp.nvim`. Currently required in Neovim config: ```vim au BufRead,BufNewFile *.scl set filetype=scl au BufRead,BufNewFile *.udt set filetype=scl @@ -93,12 +106,13 @@ return M - Booleans: prefix with `is_`, `has_` (e.g., `is_udt`, `has_members`) ### Imports -- `lua/scl/` modules: `require("scl.module_name")` +- `lua/tia/` modules: `require("tia.module_name")` +- `lua/tia_lsp/` entry: `require("tia_lsp")` - `src/` modules: `dofile(script_path .. "/module.lua")` - External dependencies: wrap in `pcall()` for safety ### Code Formatting -- `lua/scl/`: 2 spaces indentation +- `lua/tia/` and `lua/tia_lsp/`: 2 spaces indentation - `src/`: tab-based indentation - Max line length: 120 characters - No trailing whitespace diff --git a/README.md b/README.md index 6e177c1..b7541f5 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# SCL Language Server - Unified Plugin for Siemens SCL +# tia-lsp - Language Server for Siemens TIA Portal -A comprehensive Neovim/LazyVim plugin providing **Language Server Protocol (LSP)**, **Linting**, **Formatting**, and **Syntax Highlighting** for Siemens SCL (Structured Control Language) used in Siemens TIA Portal. +A Neovim/LazyVim plugin and standalone LSP server providing **Language Server Protocol (LSP)**, **Linting**, **Formatting**, and **Syntax Highlighting** for Siemens TIA Portal languages. Currently supports **SCL** (Structured Control Language), with planned support for **STL/AWL**, **LAD**, and **FBD**. + +The project is split into two repos: + +- **`tia-lsp`** (this repo) — the standalone LSP server (`src/`) plus the tree-sitter grammar (`grammar.js`, `src/parser.c`). Speaks JSON-RPC over stdio. Editor-agnostic: usable from Neovim, VS Code, or any LSP client. +- **`tia-lsp.nvim`** — the Neovim plugin (`lua/`) that launches the server via `vim.lsp.start()` and adds editor-specific features (auto-prefix, blink.cmp source, workspace UDT scanning, attribute toggle, multiline params). ## Features @@ -58,11 +63,13 @@ A comprehensive Neovim/LazyVim plugin providing **Language Server Protocol (LSP) ## Installation +The plugin auto-detects the mason-installed `tia-lsp` executable via `vim.fn.exepath("tia-lsp")` and falls back to `lua ` for manual installs. See [Mason installation](#mason-installation) below for the recommended path. + ### LazyVim ```lua --- ~/.config/nvim/lua/plugins/scl.lua +-- ~/.config/nvim/lua/plugins/tia-lsp.lua return { - "lazar/scl_lsp", + "lazar/tia-lsp.nvim", ft = "scl", lazy = true, dependencies = { @@ -71,7 +78,7 @@ return { "saghen/blink.cmp", }, config = function() - require("scl_lsp").setup({ + require("tia_lsp").setup({ -- LSP options lsp = { on_attach = function(client, bufnr) @@ -90,19 +97,59 @@ return { ### Manual Setup ```lua -- In your init.lua or plugin file -require("scl_lsp").setup({ - server_path = "/path/to/scl_lsp/src/main.lua", +require("tia_lsp").setup({ + server_path = "/path/to/tia-lsp/src/main.lua", -- only used if tia-lsp is not on PATH + ts_parser_url = "https://your-gitea/tia-lsp.git", -- tree-sitter parser source cmp = true, workspace_types = true, auto_prefix = true, }) ``` +### Mason installation + +`tia-lsp` can be installed via [mason.nvim](https://github.com/mason-org/mason.nvim) using a custom registry. The setup below uses a `file:` registry sourced from a Gitea-hosted `tia-mason-registry` repo (cloned by lazy.nvim), and falls back to the official mason registry for any other packages. + +1. Clone the registry repo as a lazy.nvim plugin: + ```lua + -- ~/.config/nvim/lua/plugins/tia-mason-registry.lua + return { + url = "https://your-gitea/tia-mason-registry.git", + name = "tia-mason-registry", + lazy = false, -- must be available before mason setup + } + ``` +2. Register the registry with mason: + ```lua + -- ~/.config/nvim/lua/plugins/mason.lua + return { + "mason-org/mason.nvim", + opts = { + registries = { + "file:" .. vim.fn.expand("~/.local/share/nvim/lazy/tia-mason-registry"), + "github:mason-org/mason-registry", + }, + }, + } + ``` +3. Install `yq` (one-time prerequisite for mason's `file:` registry): + ``` + :MasonInstall yq + ``` +4. Install the server: + ``` + :MasonInstall tia-lsp + ``` +5. Verify: open a `.scl` file and run `:LspInfo` — `tia_lsp` should be attached, and `vim.fn.exepath("tia-lsp")` should resolve to `~/.local/share/nvim/mason/bin/tia-lsp`. + +To upgrade: tag a new `vX.Y.Z` release on the `tia-lsp` Gitea repo, bump `source.id` in `tia-mason-registry/packages/tia-lsp/package.yaml`, push the registry repo, then `:MasonInstall tia-lsp` again. + ## Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| -| `server_path` | string | `~/dev/scl_lsp/src/main.lua` | Path to LSP server | +| `server_path` | string | `~/dev/tia-lsp/src/main.lua` | Path to LSP server (only used if `tia-lsp` is not on PATH) | +| `ts_parser_url` | string | `file://~/dev/tia-lsp` | URL passed to nvim-treesitter for the SCL parser source | | `lsp.on_attach` | function | `nil` | Custom LSP attach callback | | `lsp.formatting` | boolean | `false` | Enable format command | | `cmp` | boolean | `true` | Enable blink.cmp integration | @@ -185,7 +232,7 @@ The LSP automatically scans for SCL files in `data_types/` folder and extracts T ### Option 3: Generate `.plc.json` ```bash -lua /home/lazar/dev/scl_lsp/src/plc_json.lua --generate +lua ~/dev/tia-lsp/src/plc_json.lua --generate ``` ## Global Data Block Support @@ -218,40 +265,43 @@ The plugin automatically scans for `.db` files in the workspace and provides aut ### Project Structure ``` -scl_lsp/ +tia-lsp/ ├── README.md # This file ├── package.json # NPM scripts (tree-sitter) ├── Makefile # Build commands -├── grammar.js # Tree-sitter grammar -├── tree-sitter.json # Parser config -├── queries/ # Tree-sitter queries -│ ├── highlights.scm # Syntax highlighting -│ ├── indents.scm # Indentation -│ ├── folds.scm # Code folding -│ ├── locals.scm # Variable scoping -│ └── tags.scm # Navigation tags -├── src/ # LSP server -│ ├── main.lua # Main entry point -│ ├── parser.lua # Regex-based parser -│ ├── treesitter.lua # Tree-sitter integration -│ ├── json.lua # JSON encoder/decoder -│ ├── plc_json.lua # External UDT loading -│ ├── diagnostics.lua # Linter diagnostics -│ └── formatter.lua # Document formatter +├── grammar.js # Tree-sitter grammar (SCL language) +├── tree-sitter.json # Parser config +├── queries/ # Tree-sitter queries +│ └── scl/ # Per-language query dir (SCL) +│ ├── highlights.scm # Syntax highlighting +│ ├── indents.scm # Indentation +│ ├── folds.scm # Code folding +│ ├── locals.scm # Variable scoping +│ └── tags.scm # Navigation tags +├── src/ # LSP server +│ ├── main.lua # Main entry point (JSON-RPC over stdio) +│ ├── parser.lua # SCL parser (will become parser_scl.lua + dispatch) +│ ├── treesitter.lua # Tree-sitter integration +│ ├── json.lua # JSON encoder/decoder +│ ├── plc_json.lua # External UDT loading (language-agnostic) +│ ├── diagnostics.lua # Linter diagnostics +│ └── formatter.lua # Document formatter └── lua/ - └── scl_lsp/ - ├── init.lua # Main plugin setup - ├── plugins/ - │ └── scl.lua # LazyVim plugin spec - └── scl/ # Syntax/completion modules - ├── init.lua - ├── auto_prefix.lua - ├── blink_cmp_source.lua - ├── db_parser.lua - ├── fb_parser.lua - ├── udt_parser.lua - ├── variables.lua - └── workspace_types.lua + ├── tia_lsp/ # Neovim plugin + │ ├── init.lua # Main plugin setup + │ └── plugins/ + │ └── scl.lua # LazyVim plugin spec (SCL filetype) + └── tia/ # Language modules (shared across TIA languages) + ├── auto_prefix.lua + ├── attr_toggle.lua + ├── blink_cmp_source.lua + ├── builtin_instructions.lua + ├── db_parser.lua + ├── fb_parser.lua + ├── multiline_params.lua + ├── udt_parser.lua + ├── variables.lua + └── workspace_types.lua ``` ## Building Tree-sitter Parser @@ -294,7 +344,7 @@ The plugin uses a standalone LSP server (`src/main.lua`) that communicates via J - Completion, hover, go-to-definition - Semantic tokens -The Neovim plugin (`lua/scl_lsp/init.lua`) manages: +The Neovim plugin (`lua/tia_lsp/init.lua`) manages: - LSP client lifecycle via `vim.lsp.start()` - FileType autocmd for lazy loading - blink.cmp integration diff --git a/lua/scl/init.lua b/lua/scl/init.lua deleted file mode 100644 index a032d4d..0000000 --- a/lua/scl/init.lua +++ /dev/null @@ -1,266 +0,0 @@ --- SCL (Siemens Structured Control Language) support for Neovim --- Main setup module - -local M = {} - -function M.setup(opts) - opts = opts or {} - - -- Add plugin to runtime path - vim.opt.runtimepath:prepend(vim.fn.stdpath("data") .. "/lazy/scl_lsp") - - M.create_commands() - - -- Treesitter setup for Neovim 0.11+ - local function setup_treesitter() - local lang = vim.treesitter.language - local parser_path = "/home/lazar/dev/scl_lsp/src/parser.so" - - -- Register the SCL language - lang.add("scl", { - path = parser_path, - filetype = "scl", - }) - end - - pcall(setup_treesitter) - - -- Install queries - local queries_src = "/home/lazar/dev/scl_lsp/queries/highlights.scm" - local queries_dest = vim.fn.stdpath("data") .. "/site/queries/scl/highlights.scm" - if vim.fn.filereadable(queries_src) == 1 then - vim.fn.mkdir(vim.fn.stdpath("data") .. "/site/queries/scl", "p") - vim.fn.writefile(vim.fn.readfile(queries_src), queries_dest) - end - - -- Enable treesitter manually for SCL files - bypass LazyVim - vim.api.nvim_create_autocmd("FileType", { - pattern = "scl", - callback = function(args) - vim.treesitter.start(args.buf, "scl") - 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", "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.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" }, "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 diff --git a/lua/scl/attr_toggle.lua b/lua/tia/attr_toggle.lua similarity index 100% rename from lua/scl/attr_toggle.lua rename to lua/tia/attr_toggle.lua diff --git a/lua/scl/auto_prefix.lua b/lua/tia/auto_prefix.lua similarity index 99% rename from lua/scl/auto_prefix.lua rename to lua/tia/auto_prefix.lua index e861493..20328b1 100644 --- a/lua/scl/auto_prefix.lua +++ b/lua/tia/auto_prefix.lua @@ -71,7 +71,7 @@ local function get_local_variables() end end - local scl_vars = require("scl.variables") + local scl_vars = require("tia.variables") local vars = scl_vars.extract_variables(bufnr) var_cache = {} @@ -545,7 +545,7 @@ function M.setup() local before_semicolon = line:sub(1, cursor_col - 1) local type_name = before_semicolon:match(":%s*([%w_]+)%s*$") if type_name then - local udt_parser = require("scl.udt_parser") + local udt_parser = require("tia.udt_parser") local is_udt = udt_parser.is_udt_type(type_name) if is_udt and not before_semicolon:match(":%s*\"" .. type_name .. "\"") then local new_before = before_semicolon:gsub(":%s*" .. type_name .. "%s*$", ':"' .. type_name .. '"') diff --git a/lua/scl/blink_cmp_source.lua b/lua/tia/blink_cmp_source.lua similarity index 98% rename from lua/scl/blink_cmp_source.lua rename to lua/tia/blink_cmp_source.lua index 686a718..1ef739c 100644 --- a/lua/scl/blink_cmp_source.lua +++ b/lua/tia/blink_cmp_source.lua @@ -104,7 +104,7 @@ end -- Get all known local variables local function get_known_variables(bufnr) - local scl_vars = require("scl.variables") + local scl_vars = require("tia.variables") local variables = scl_vars.extract_variables(bufnr) local known = {} for _, v in ipairs(variables) do @@ -115,12 +115,12 @@ end -- Check if a type is an FB/Function and get its interface local function get_fb_interface(var_type) - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") local clean_type = var_type:gsub('"', '') -- If cache is empty, try to scan workspace for FBs if fb_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_fbs(vim.api.nvim_get_current_buf()) end @@ -129,12 +129,12 @@ end -- Check if a type is an FB/Function local function is_fb_type(var_type) - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") local clean_type = var_type:gsub('"', '') -- If cache is empty, try to scan workspace for FBs if fb_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_fbs(vim.api.nvim_get_current_buf()) end @@ -236,7 +236,7 @@ function M:get_completions(context, callback) local after_closed_db = active_portion:match('"([%w_]+)"%s*%.') ~= nil if db_member_pattern or after_closed_db then - local db_parser = require("scl.db_parser") + local db_parser = require("tia.db_parser") local db_name = active_portion:match('"([%w_]+)"') if db_name then @@ -271,7 +271,7 @@ function M:get_completions(context, callback) resolved_path = part if found_member.is_udt then -- This member is a UDT type, continue resolving - local udt_parser = require("scl.udt_parser") + local udt_parser = require("tia.udt_parser") current_members = udt_parser.get_udt_members(found_member.type) current_type = found_member.type else @@ -306,7 +306,7 @@ function M:get_completions(context, callback) -- Get members to show (either from DB or from resolved UDT type) if current_type then - local udt_parser = require("scl.udt_parser") + local udt_parser = require("tia.udt_parser") current_members = udt_parser.get_udt_members(current_type) end @@ -335,11 +335,11 @@ function M:get_completions(context, callback) -- Check for just " pattern (start of DB name completion or partial DB name) if is_in_db_name or is_partial_db or is_after_colon_quote then - local db_parser = require("scl.db_parser") + local db_parser = require("tia.db_parser") -- If cache is empty, try to scan workspace for DBs if db_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_dbs(bufnr) end @@ -376,7 +376,7 @@ function M:get_completions(context, callback) end end - local scl_vars = require("scl.variables") + local scl_vars = require("tia.variables") local variables = scl_vars.extract_variables(bufnr) -- Get all known local variables and their types @@ -747,8 +747,8 @@ function M:get_completions(context, callback) end) vim.schedule(function() - local udt_parser = require("scl.udt_parser") - local db_parser = require("scl.db_parser") + local udt_parser = require("tia.udt_parser") + local db_parser = require("tia.db_parser") -- No member access = show local variables, UDT types, and basic types if not base_var then @@ -779,9 +779,9 @@ function M:get_completions(context, callback) end -- Add Global DB names (without quotes - will be wrapped in resolve) - local db_parser = require("scl.db_parser") + local db_parser = require("tia.db_parser") if db_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_dbs(bufnr) end diff --git a/lua/scl/builtin_instructions.lua b/lua/tia/builtin_instructions.lua similarity index 100% rename from lua/scl/builtin_instructions.lua rename to lua/tia/builtin_instructions.lua diff --git a/lua/scl/db_parser.lua b/lua/tia/db_parser.lua similarity index 100% rename from lua/scl/db_parser.lua rename to lua/tia/db_parser.lua diff --git a/lua/scl/fb_parser.lua b/lua/tia/fb_parser.lua similarity index 100% rename from lua/scl/fb_parser.lua rename to lua/tia/fb_parser.lua diff --git a/lua/scl/multiline_params.lua b/lua/tia/multiline_params.lua similarity index 95% rename from lua/scl/multiline_params.lua rename to lua/tia/multiline_params.lua index 7492093..b512bf3 100644 --- a/lua/scl/multiline_params.lua +++ b/lua/tia/multiline_params.lua @@ -7,7 +7,7 @@ local M = {} --- @param var_type string The FB type name --- @return table|nil Interface with inputs, outputs, inouts local function get_builtin_interface(var_type) - local builtin = require("scl.builtin_instructions") + local builtin = require("tia.builtin_instructions") local clean_type = var_type:gsub('"', ''):gsub("#", ""):upper() local params_info = builtin.get_parameters(clean_type) if params_info then @@ -24,11 +24,11 @@ end --- @param var_type string The FB type name --- @return table|nil Interface with inputs, outputs, inouts local function get_fb_interface(var_type) - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") local clean_type = var_type:gsub('"', '') if fb_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_fbs(vim.api.nvim_get_current_buf()) end @@ -39,11 +39,11 @@ end --- @param var_type string The type name to check --- @return boolean True if type is an FB local function is_fb_type(var_type) - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") local clean_type = var_type:gsub('"', '') if fb_parser.get_cache_count() == 0 then - local ws = require("scl.workspace_types") + local ws = require("tia.workspace_types") ws.scan_project_fbs(vim.api.nvim_get_current_buf()) end @@ -54,7 +54,7 @@ end --- @param bufnr number Buffer number --- @return table Set of variable names local function get_known_variables(bufnr) - local scl_vars = require("scl.variables") + local scl_vars = require("tia.variables") local variables = scl_vars.extract_variables(bufnr) local known = {} for _, v in ipairs(variables) do @@ -68,7 +68,7 @@ end --- @param bufnr number Buffer number --- @return string|nil Variable type, or nil if not found local function get_variable_type(var_name, bufnr) - local scl_vars = require("scl.variables") + local scl_vars = require("tia.variables") return scl_vars.get_variable_type(var_name, bufnr) end @@ -84,7 +84,7 @@ function M.fill_multiline_params() local line_before_cursor = line:sub(1, col) local known_vars = get_known_variables(bufnr) - local builtin = require("scl.builtin_instructions") + local builtin = require("tia.builtin_instructions") local fb_name = nil local fb_prefix = "" diff --git a/lua/scl/udt_parser.lua b/lua/tia/udt_parser.lua similarity index 100% rename from lua/scl/udt_parser.lua rename to lua/tia/udt_parser.lua diff --git a/lua/scl/variables.lua b/lua/tia/variables.lua similarity index 100% rename from lua/scl/variables.lua rename to lua/tia/variables.lua diff --git a/lua/scl/workspace_types.lua b/lua/tia/workspace_types.lua similarity index 96% rename from lua/scl/workspace_types.lua rename to lua/tia/workspace_types.lua index e955371..bd03636 100644 --- a/lua/scl/workspace_types.lua +++ b/lua/tia/workspace_types.lua @@ -361,7 +361,7 @@ end -- Parse all UDT files in project and libraries function M.scan_project_udts(bufnr) - local udt_parser = require("scl.udt_parser") + local udt_parser = require("tia.udt_parser") local files = M.get_project_udt_files(bufnr) local lib_files = M.get_library_udt_files() @@ -391,7 +391,7 @@ end -- Parse all FB/Function files in project and libraries function M.scan_project_fbs(bufnr) - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") local files = M.get_project_fb_files(bufnr) local lib_files = M.get_library_fb_files() @@ -421,7 +421,7 @@ end -- Parse all Global DB files in project and libraries function M.scan_project_dbs(bufnr) - local db_parser = require("scl.db_parser") + local db_parser = require("tia.db_parser") local files = M.get_project_db_files(bufnr) local lib_files = M.get_library_db_files() @@ -486,9 +486,9 @@ function M.setup(opts) pattern = "*.scl", callback = function(args) -- Clear cache and rescan - local udt_parser = require("scl.udt_parser") - local fb_parser = require("scl.fb_parser") - local db_parser = require("scl.db_parser") + local udt_parser = require("tia.udt_parser") + local fb_parser = require("tia.fb_parser") + local db_parser = require("tia.db_parser") udt_parser.clear_cache() fb_parser.clear_cache() db_parser.clear_cache() @@ -506,7 +506,7 @@ function M.setup(opts) pattern = "*.udt", callback = function(args) -- Re-parse the changed UDT file - local udt_parser = require("scl.udt_parser") + local udt_parser = require("tia.udt_parser") udt_parser.parse_udt_file(args.file) end, }) @@ -517,7 +517,7 @@ function M.setup(opts) pattern = "*.scl", callback = function(args) -- Re-parse the changed FB file - local fb_parser = require("scl.fb_parser") + local fb_parser = require("tia.fb_parser") fb_parser.parse_fb_file(args.file) end, }) @@ -527,7 +527,7 @@ function M.setup(opts) group = augroup, pattern = "*.db", callback = function(args) - local db_parser = require("scl.db_parser") + local db_parser = require("tia.db_parser") db_parser.parse_db_file(args.file) end, }) diff --git a/lua/scl_lsp/init.lua b/lua/tia_lsp/init.lua similarity index 81% rename from lua/scl_lsp/init.lua rename to lua/tia_lsp/init.lua index 22cab3d..9ba4357 100644 --- a/lua/scl_lsp/init.lua +++ b/lua/tia_lsp/init.lua @@ -2,7 +2,7 @@ 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", "") + 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 @@ -13,7 +13,7 @@ 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 server_path = opts.server_path or vim.fn.expand("~/dev/tia-lsp/src/main.lua") local root_patterns = { ".git", "data_types", "plc.data.json" } @@ -24,9 +24,15 @@ function M.setup(opts) 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 = "scl_lsp", - cmd = { "lua", server_path }, + name = "tia_lsp", + cmd = cmd, root_dir = root_dir, on_attach = function(client, b) if opts.lsp and opts.lsp.on_attach then @@ -35,7 +41,7 @@ function M.setup(opts) if opts.lsp and opts.lsp.formatting then vim.api.nvim_buf_create_user_command(b, "SCLFormat", function() - vim.lsp.buf.format({ name = "scl_lsp" }) + vim.lsp.buf.format({ name = "tia_lsp" }) end, {}) end end, @@ -52,7 +58,7 @@ function M.setup(opts) -- Create global format command (works on current buffer) vim.api.nvim_create_user_command("SCLFormat", function() - vim.lsp.buf.format({ name = "scl_lsp" }) + vim.lsp.buf.format({ name = "tia_lsp" }) end, {}) -- Start LSP for any existing scl buffers (handles lazy loading case) @@ -79,7 +85,11 @@ function M.setup_syntax(opts) if ok and parser_config then parser_config.scl = { install_info = { - url = "file://" .. vim.fn.expand("~/dev/scl_lsp"), + -- Override via setup({ ts_parser_url = "https://your-gitea/tia-lsp.git" }). + -- Default is the upstream TIA Portal LSP server repo (placeholder until + -- the split is complete; see README "Mason installation"). + url = opts.ts_parser_url + or "file://" .. vim.fn.expand("~/dev/tia-lsp"), files = { "src/parser.c" }, generate_requires_npm = false, requires_generate_from_grammar = false, @@ -92,7 +102,7 @@ function M.setup_syntax(opts) 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") + 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", @@ -118,7 +128,7 @@ function M.setup_syntax(opts) end if opts.workspace_types ~= false then - local ok, workspace_types = pcall(require, "scl.workspace_types") + local ok, workspace_types = pcall(require, "tia.workspace_types") if ok then workspace_types.setup({ project_root = opts.project_root, @@ -130,14 +140,14 @@ function M.setup_syntax(opts) end function M.setup_auto_prefix() - local ok, auto_prefix = pcall(require, "scl.auto_prefix") + 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, "scl.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 @@ -163,14 +173,14 @@ function M.setup_multiline_params() end function M.prefix_current_word() - local ok, auto_prefix = pcall(require, "scl.auto_prefix") + 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, "scl.variables") + local ok, variables = pcall(require, "tia.variables") if not ok then vim.notify("SCL variables module not found", vim.log.levels.WARN) return @@ -191,9 +201,9 @@ function M.show_variables() 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") + 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) @@ -213,9 +223,9 @@ function M.show_workspace_types() 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") + 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) @@ -241,7 +251,7 @@ function M.create_commands() -- Attribute toggle commands vim.api.nvim_create_user_command("SCLToggleAttrBlock", function() - local ok, at = pcall(require, "scl.attr_toggle") + local ok, at = pcall(require, "tia.attr_toggle") if ok then at.toggle_attr_block() else @@ -250,7 +260,7 @@ function M.create_commands() end, {}) vim.api.nvim_create_user_command("SCLExpandAllAttrBlocks", function() - local ok, at = pcall(require, "scl.attr_toggle") + local ok, at = pcall(require, "tia.attr_toggle") if ok then at.expand_all_attr_blocks() else @@ -259,7 +269,7 @@ function M.create_commands() end, {}) vim.api.nvim_create_user_command("SCLCollapseAllAttrBlocks", function() - local ok, at = pcall(require, "scl.attr_toggle") + local ok, at = pcall(require, "tia.attr_toggle") if ok then at.collapse_all_attr_blocks() else @@ -269,7 +279,7 @@ function M.create_commands() end function M.setup_attr_toggle() - local ok, at = pcall(require, "scl.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 diff --git a/lua/scl_lsp/plugins/scl.lua b/lua/tia_lsp/plugins/scl.lua similarity index 83% rename from lua/scl_lsp/plugins/scl.lua rename to lua/tia_lsp/plugins/scl.lua index 57af9f5..dadd845 100644 --- a/lua/scl_lsp/plugins/scl.lua +++ b/lua/tia_lsp/plugins/scl.lua @@ -1,5 +1,5 @@ return { - "lazar/scl_lsp", + "lazar/tia-lsp.nvim", ft = "scl", lazy = true, dependencies = { @@ -8,8 +8,9 @@ return { "saghen/blink.cmp", }, config = function(_, opts) - require("scl_lsp").setup({ + require("tia_lsp").setup({ server_path = opts.server_path, + ts_parser_url = opts.ts_parser_url, lsp = opts.lsp, cmp = opts.cmp ~= false, workspace_types = opts.workspace_types ~= false, diff --git a/scl_lsp.sh b/scl_lsp.sh deleted file mode 100755 index 4361bf8..0000000 --- a/scl_lsp.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -cd /home/lazar/dev/scl_lsp -lua src/main.lua diff --git a/test/test_db.lua b/test/test_db.lua index 2dfe115..301d091 100644 --- a/test/test_db.lua +++ b/test/test_db.lua @@ -2,7 +2,7 @@ local test = dofile("test/test_harness.lua") package.path = package.path .. ";lua/?.lua" -local db_parser = require("scl.db_parser") +local db_parser = require("tia.db_parser") local function test_db_parser_basic() local content = [[ diff --git a/test/test_fb.lua b/test/test_fb.lua index 87745fe..22094be 100644 --- a/test/test_fb.lua +++ b/test/test_fb.lua @@ -2,7 +2,7 @@ local test = dofile("test/test_harness.lua") package.path = package.path .. ";lua/?.lua" -local fb_parser = require("scl.fb_parser") +local fb_parser = require("tia.fb_parser") local function test_fb_parser_basic() local content = [[ diff --git a/test/test_integration.lua b/test/test_integration.lua index 4a3633d..49e1214 100644 --- a/test/test_integration.lua +++ b/test/test_integration.lua @@ -2,9 +2,9 @@ local test = dofile("test/test_harness.lua") package.path = package.path .. ";lua/?.lua;src/?.lua" -local udt_parser = require("scl.udt_parser") -local db_parser = require("scl.db_parser") -local fb_parser = require("scl.fb_parser") +local udt_parser = require("tia.udt_parser") +local db_parser = require("tia.db_parser") +local fb_parser = require("tia.fb_parser") local script_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or "." script_path = script_path:gsub("^test/", ""):gsub("/tests$", "") diff --git a/test/test_udt.lua b/test/test_udt.lua index 2a1a510..db2d42e 100644 --- a/test/test_udt.lua +++ b/test/test_udt.lua @@ -2,7 +2,7 @@ local test = dofile("test/test_harness.lua") package.path = package.path .. ";lua/?.lua" -local udt_parser = require("scl.udt_parser") +local udt_parser = require("tia.udt_parser") local function test_udt_parser_basic() local content = [[