# tia-lsp - Language Server for Siemens TIA Portal 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 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** | Variable type and struct fields | | **Completion** | `#` for variables, `.` for struct fields, `(` for functions | | **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 | | **Inlay Hints** | Shows variable types after declaration | | **External UDT** | Load types from `.plc.json` or `data_types/` folder | ### Linter (Diagnostics) | Feature | Description | |---------|-------------| | **Undefined Variables** | Detects variables used without declaration | | **Invalid # Prefix** | Detects `#` prefix in VAR sections | | **Unknown Types** | Warns about unknown data types | | **Type Validation** | Basic type checking | ### Formatter | Feature | Description | |---------|-------------| | **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 | ### 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 - Control structures: IF/THEN/ELSIF/ELSE/END_IF, CASE/OF/END_CASE, FOR/TO/BY/DO/END_FOR, WHILE/END_WHILE, REPEAT/UNTIL/END_REPEAT - Built-in data types: BOOL, INT, DINT, REAL, STRING, TIME, etc. - Function block instances highlighted in calls - FB parameter names (IN, PT, CU, etc.) highlighted as properties - `#` prefix for local variables highlighted - Operators: AND, OR, NOT, XOR, MOD - Comments: line (`//`), block (`(* *)`), C-style (`/* */`) ## Running the Server ```bash # Start the LSP server (reads JSON-RPC from stdin, writes to stdout) lua src/main.lua # Run server tests make test # or: lua src/main.lua --test ``` ### Neovim + Mason (Recommended) The recommended way is via [mason.nvim](https://github.com/mason-org/mason.nvim) using the [`tia-mason-registry`](https://gitea.l-tech.rs/lazar/tia-mason-registry). See the [`tia-lsp.nvim` README](https://gitea.l-tech.rs/lazar/tia-lsp.nvim) for full step-by-step instructions. ### Manual Setup ```bash # 1. Clone this repository git clone --depth 1 https://gitea.l-tech.rs/lazar/tia-lsp.git ~/tia-lsp # 2. Compile the tree-sitter parser cc -fPIC -I ~/tia-lsp/src/tree_sitter -c ~/tia-lsp/src/parser.c -o ~/tia-lsp/parser.o cc -shared ~/tia-lsp/parser.o -o ~/tia-lsp/parser.so # 3. Start the server (or configure your editor to run this) lua ~/tia-lsp/src/main.lua ``` ## External UDT Support ### Option 1: Create `.plc.json` ```json { "dataTypes": [ { "name": "equipmentData", "struct": [ {"name": "id", "dataTypeName": "INT"}, {"name": "status", "dataTypeName": "BOOL"}, {"name": "temperature", "dataTypeName": "REAL"} ] } ] } ``` ### Option 2: Auto-scan `data_types/` folder The LSP automatically scans for SCL files in `data_types/` folder and extracts TYPE definitions. ### Option 3: Generate `.plc.json` ```bash lua src/plc_json.lua --generate ``` ## Global Data Block Support 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 server scans the project for `.db` files 2. **DB Parsing**: Parses `DATA_BLOCK` definitions and extracts members from `VAR` sections 3. **Auto-completion**: Provides completion for DB names and members ### Example ```scl // Access global DB members "HmiData".units "Parameter".machine.unit100.paramTimeStampApplied // Inside FB call parameter #instParameterManager(hmiInterface:="HmiData".units, ...) ``` ## Building Tree-sitter Parser ```bash # Generate parser from grammar.js tree-sitter generate # 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 FUNCTION_BLOCK "Em101Sequence" VAR x : INT; myVar : equipmentData; // User-defined type END_VAR IF x > 5 THEN myVar.id := 10; END_IF myVar.status := TRUE; myVar.temperature := 25.5; END_FUNCTION_BLOCK ``` ## Requirements - **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`).