From 8496cc865e94800045c76fd951dee3061795631b Mon Sep 17 00:00:00 2001 From: Lazar Bulovan Date: Sat, 18 Jul 2026 11:46:12 +0200 Subject: [PATCH] chore: adjust README, AGENTS, and test runner for server-only scope After splitting from the original scl_lsp repo, update documentation and test infrastructure to reflect the server-only scope of this repo. - README.md: focused on LSP server features, tree-sitter grammar, running the server, and Mason installation reference - AGENTS.md: server-only project structure and conventions - test/run_tests.lua: run only server tests (formatter, plc_json) --- AGENTS.md | 48 +------ README.md | 327 +++++++++++++-------------------------------- test/run_tests.lua | 8 +- 3 files changed, 99 insertions(+), 284 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 81aa766..0f50882 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,9 +1,6 @@ # AGENTS.md - tia-lsp -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. +A standalone LSP server for Siemens TIA Portal languages. Currently supports **SCL**; planned support for STL/AWL, LAD, FBD. This repo is the server only (`src/` + tree-sitter grammar). The companion Neovim plugin lives in `tia-lsp.nvim`. ## Build/Lint/Test Commands @@ -20,16 +17,11 @@ tree-sitter parse # Parse single SCL file # Lua Linting luacheck src/ # Lint LSP server code -luacheck lua/tia/ # Lint plugin modules -# Unit & Integration Tests -lua test/run_tests.lua # Run all tests -lua test/test_udt.lua # Run UDT parser tests -lua test/test_db.lua # Run DB parser tests -lua test/test_fb.lua # Run FB parser tests +# Unit Tests +lua test/run_tests.lua # Run all server tests lua test/test_formatter.lua # Run formatter tests lua test/test_plc_json.lua # Run plc_json tests -lua test/test_integration.lua # Run integration tests # Reference Project # Tests use files from: ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/ @@ -49,38 +41,16 @@ tia-lsp/ │ ├── plc_json.lua # External UDT/DB loading (language-agnostic) │ ├── builtin_instructions.lua # TIA Portal built-in types │ └── json.lua # JSON encoder/decoder -├── lua/tia/ # Neovim plugin (uses require) -│ ├── udt_parser.lua # UDT parser -│ ├── db_parser.lua # Global DB parser -│ ├── fb_parser.lua # Function Block parser -│ ├── variables.lua # Variable extraction -│ ├── workspace_types.lua # Workspace scanning -│ ├── multiline_params.lua -│ ├── auto_prefix.lua -│ └── attr_toggle.lua -├── 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') +├── grammar.js # Tree-sitter grammar (language name: 'scl') +└── test/ # Server tests ``` ## Naming Convention -- **Project name** (repo, Lua module, LSP server, mason package): `tia-lsp` / `tia_lsp` — umbrella for all TIA languages. +- **Project name** (repo, 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 - -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 -au BufRead,BufNewFile *.db set filetype=scl -``` ## Code Style Guidelines @@ -106,13 +76,10 @@ return M - Booleans: prefix with `is_`, `has_` (e.g., `is_udt`, `has_members`) ### Imports -- `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/tia/` and `lua/tia_lsp/`: 2 spaces indentation - `src/`: tab-based indentation - Max line length: 120 characters - No trailing whitespace @@ -190,6 +157,5 @@ Use the reference project for testing: Contains `.scl`, `.db`, `.udt`, and `.xml` files for comprehensive testing of: - LSP features (hover, completion, go-to-definition) -- Formatter (`:SCLFormat`) -- Attribute block toggle +- Formatter - Workspace type scanning diff --git a/README.md b/README.md index b7541f5..67147c6 100644 --- a/README.md +++ b/README.md @@ -1,20 +1,17 @@ # tia-lsp - Language Server for 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**. +A standalone LSP (Language Server Protocol) server 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). +The server speaks JSON-RPC over stdio and is editor-agnostic — usable from Neovim, VS Code, or any LSP client. For Neovim users, the companion plugin [`tia-lsp.nvim`](https://gitea.l-tech.rs/lazar/tia-lsp.nvim) provides seamless integration (auto-prefix, blink.cmp source, workspace scanning, attribute toggle, etc.). ## Features ### LSP (Language Server Protocol) | Feature | Description | |---------|-------------| -| **Hover** | Press `K` to see variable type and struct fields | +| **Hover** | Variable type and struct fields | | **Completion** | `#` for variables, `.` for struct fields, `(` for functions | -| **Go to Definition** | `gD` jump to function, FB, DB, or UDT definition | +| **Go to Definition** | Jump to function, FB, DB, or UDT definition | | **Document Symbols** | Shows functions, variables, types | | **Workspace Symbols** | Search across all `.scl` files with fuzzy matching | | **Semantic Tokens** | Keywords, variables highlighted | @@ -32,13 +29,13 @@ The project is split into two repos: ### Formatter | Feature | Description | |---------|-------------| -| **Document Formatting** | Format entire SCL files with `:SCLFormat` | +| **Document Formatting** | Format entire SCL files | | **Multi-line Assignments** | Preserves and formats multi-line expressions with proper alignment | | **FB Call Formatting** | Formats function block calls in multiline style | | **Indentation** | Proper block indentation | | **Keyword Casing** | Maintains SCL keyword casing | -### Syntax Highlighting +### Tree-sitter Grammar - Full SCL syntax support via tree-sitter - Block definitions: ORGANIZATION_BLOCK, FUNCTION_BLOCK, FUNCTION - Variable sections: VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_TEMP, VAR_CONSTANT @@ -50,164 +47,25 @@ The project is split into two repos: - Operators: AND, OR, NOT, XOR, MOD - Comments: line (`//`), block (`(* *)`), C-style (`/* */`) -### Additional Features -| Feature | Description | -|---------|-------------| -| **Auto-Prefix** | Automatically adds `#` prefix to local variables (like TIA Portal) | -| **Multiline Parameters** | Fill FB/Function call parameters across multiple lines with `mp` or `:SCLMultilineParams` | -| **Tab Navigation** | Jump between parameters with `` in insert mode | -| **Workspace UDT Scanner** | Scans project for user-defined types (.udt files) | -| **Workspace Global DB Scanner** | Scans project for global data blocks (.db files) | -| **blink.cmp Integration** | Smart completion source for blink.cmp | -| **Attribute Block Toggle** | Interactive expand/collapse of `{...}` blocks with `xa` | +## Running the Server -## Installation +```bash +# Start the LSP server (reads JSON-RPC from stdin, writes to stdout) +lua src/main.lua -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/tia-lsp.lua -return { - "lazar/tia-lsp.nvim", - ft = "scl", - lazy = true, - dependencies = { - "neovim/nvim-lspconfig", - "nvim-treesitter/nvim-treesitter", - "saghen/blink.cmp", - }, - config = function() - require("tia_lsp").setup({ - -- LSP options - lsp = { - on_attach = function(client, bufnr) - -- custom keymaps - end, - }, - cmp = true, -- Enable blink.cmp integration - workspace_types = true, -- Enable workspace UDT scanning - auto_prefix = true, -- Enable auto-# prefixing - debug = false, - }) - end, -} +# Run server tests +make test +# or: lua src/main.lua --test ``` -### Manual Setup -```lua --- In your init.lua or plugin file -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 (Neovim) -### Mason installation +For Neovim users, install via [mason.nvim](https://github.com/mason-org/mason.nvim) using a custom registry. See the [`tia-lsp.nvim` README](https://gitea.l-tech.rs/lazar/tia-lsp.nvim) for full setup instructions. -`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/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 | -| `workspace_types` | boolean | `true` | Enable workspace UDT scanning | -| `auto_prefix` | boolean | `true` | Enable auto-# prefixing | -| `project_root` | string | `nil` | Custom project root | -| `library_paths` | table | `{}` | Additional library paths | -| `debug` | boolean | `false` | Enable debug logging | - -## Commands - -| Command | Description | -|---------|-------------| -| `:SCLShowVariables` | Show local variables in current file | -| `:SCLShowWorkspaceTypes` | Show workspace UDTs, FBs, and Global DBs count | -| `:SCLRescanWorkspaceTypes` | Rescan workspace for types and DBs | -| `:SCLPrefixWord` | Manually prefix current word with `#` | -| `:SCLMultilineParams` | Fill multiline parameters for FB/Function call | -| `:SCLFormat` | Format current SCL file | -| `:SCLToggleAttrBlock` | Toggle attribute block under cursor | -| `:SCLExpandAllAttrBlocks` | Expand all `{...}` blocks in buffer | -| `:SCLCollapseAllAttrBlocks` | Collapse all matching attribute blocks | - -## Keybindings - -| Key | Action | -|-----|--------| -| `K` | Hover (show variable info) | -| `gd` | Go to Definition | -| `s` | Document Symbols | -| `w` | Workspace Symbols | -| `mp` | Fill multiline FB/Function parameters (insert mode) | -| `` | Jump to next parameter (insert mode) | -| `xa` | Toggle attribute block under cursor | -| `xae` | Expand all attribute blocks | -| `xac` | Collapse all attribute blocks | -| `fw` | Workspace Symbols (Telescope) | -| `ca` | Code Actions | - -## Attribute Block Toggle - -Interactive expand/collapse of SCL variable attribute blocks: - -```scl --- Before: Verbose attributes -statCounter{EXTERNALACCESSIBLE := 'false'; EXTERNALVISIBLE := 'false'} : Int; - --- After: Collapsed -statCounter{...} : Int; -``` - -**Usage:** -- Place cursor inside any `{...}` block -- Press `xa` to toggle expand/collapse -- Use `xae` to expand all blocks in buffer -- Use `xac` to collapse all blocks - -**Note:** Collapsed blocks are automatically expanded before saving to preserve full content on disk. Use `:SCLFormat` to format, then `:SCLCollapseAllAttrBlocks` to collapse while preserving the ability to expand later. +Quick summary: +1. Add the `tia-mason-registry` as a lazy.nvim plugin +2. Register it with mason: `registries = { "file:~/.local/share/nvim/lazy/tia-mason-registry", "github:mason-org/mason-registry" }` +3. `:MasonInstall tia-lsp` ## External UDT Support @@ -232,24 +90,18 @@ The LSP automatically scans for SCL files in `data_types/` folder and extracts T ### Option 3: Generate `.plc.json` ```bash -lua ~/dev/tia-lsp/src/plc_json.lua --generate +lua src/plc_json.lua --generate ``` ## Global Data Block Support -The plugin automatically scans for `.db` files in the workspace and provides auto-completion for global data blocks and their members. +The server scans for `.db` files in the workspace and provides auto-completion for global data blocks and their members. ### How it works -1. **Workspace Scanning**: When opening a `.scl` file, the plugin scans the project for `.db` files +1. **Workspace Scanning**: When opening a `.scl` file, the server scans the project for `.db` files 2. **DB Parsing**: Parses `DATA_BLOCK` definitions and extracts members from `VAR` sections -3. **Auto-completion**: Provides smart completion for DB names and members - -### Usage - -- Type a space or `#` to trigger completion - global DB names appear alongside local variables -- Type `"` followed by a DB name to get member completions -- Access nested members: `"MyDB".member.submember` +3. **Auto-completion**: Provides completion for DB names and members ### Example @@ -262,60 +114,61 @@ The plugin automatically scans for `.db` files in the workspace and provides aut #instParameterManager(hmiInterface:="HmiData".units, ...) ``` -### Project Structure - -``` -tia-lsp/ -├── README.md # This file -├── package.json # NPM scripts (tree-sitter) -├── Makefile # Build commands -├── 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/ - ├── 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 ```bash # Generate parser from grammar.js -npm run build -# or: tree-sitter generate +tree-sitter generate -# Run tests -npm test -# or: tree-sitter test +# Run parser tests +tree-sitter test + +# Parse a single SCL file +tree-sitter parse ``` +## Project Structure + +``` +tia-lsp/ +├── src/ # LSP server (uses dofile) +│ ├── main.lua # Entry point, JSON-RPC protocol +│ ├── 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 (language-agnostic) +│ ├── builtin_instructions.lua # TIA Portal built-in types +│ ├── json.lua # JSON encoder/decoder +│ ├── grammar.json # Generated tree-sitter grammar +│ ├── node-types.json # Generated node types +│ ├── parser.c # Generated tree-sitter parser +│ └── tree_sitter/ # Tree-sitter C runtime headers +├── grammar.js # Tree-sitter grammar (language name: 'scl') +├── tree-sitter.json # Parser config +├── package.json # NPM scripts (tree-sitter) +├── Makefile # Build commands +├── generate_plc_json.lua # .plc.json generator utility +└── test/ # Server tests + ├── run_tests.lua + ├── test_harness.lua + ├── test_formatter.lua + └── test_plc_json.lua +``` + +## Architecture + +The server (`src/main.lua`) communicates via JSON-RPC over stdio and handles: +- Text document synchronization +- Diagnostics (linting) +- Formatting +- Completion, hover, go-to-definition +- Semantic tokens +- Inlay hints +- Workspace symbols + +The Neovim plugin (`tia-lsp.nvim`) manages the LSP client lifecycle, editor features, and workspace scanning. See its README for details. + ## SCL Code Example ```pascal @@ -335,24 +188,24 @@ myVar.temperature := 25.5; END_FUNCTION_BLOCK ``` -## Architecture - -The plugin uses a standalone LSP server (`src/main.lua`) that communicates via JSON-RPC over stdio. The server handles: -- Text document synchronization -- Diagnostics (linting) -- Formatting -- Completion, hover, go-to-definition -- Semantic tokens - -The Neovim plugin (`lua/tia_lsp/init.lua`) manages: -- LSP client lifecycle via `vim.lsp.start()` -- FileType autocmd for lazy loading -- blink.cmp integration -- Workspace scanning for UDTs/DBs - ## Requirements -- **Neovim 0.11+** (uses `vim.lsp.start()`) -- **nvim-treesitter** - Syntax highlighting -- **blink.cmp** - Optional, for completion -- **Lua 5.1+** - LSP server runtime +- **Lua 5.1+** — LSP server runtime +- **tree-sitter CLI** — For regenerating the parser from `grammar.js` (only needed during development) +- **Node.js** — For tree-sitter CLI (only needed during development) + +## Testing + +```bash +# Run server tests +lua test/run_tests.lua + +# Run individual test files +lua test/test_formatter.lua +lua test/test_plc_json.lua + +# Run LSP server in test mode +lua src/main.lua --test +``` + +Tests use files from a reference project (override with `SCL_REF_PROJECT=/path/to/ref lua test/run_tests.lua`). diff --git a/test/run_tests.lua b/test/run_tests.lua index 4f622ee..61cb910 100644 --- a/test/run_tests.lua +++ b/test/run_tests.lua @@ -1,11 +1,11 @@ #!/usr/bin/env lua -package.path = package.path .. ";lua/?.lua;src/?.lua" +package.path = package.path .. ";src/?.lua" local test = dofile("test/test_harness.lua") print("========================================") -print(" SCL LSP - Test Suite") +print(" tia-lsp - Server Test Suite") print("========================================") print(string.format("Reference Project: %s", test.REF_PROJECT)) @@ -25,12 +25,8 @@ local function run_all_tests() end end - run_test_file("test_udt.lua") - run_test_file("test_db.lua") - run_test_file("test_fb.lua") run_test_file("test_formatter.lua") run_test_file("test_plc_json.lua") - run_test_file("test_integration.lua") test.report()