223 lines
7.4 KiB
Markdown
223 lines
7.4 KiB
Markdown
# 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 <file.scl>
|
|
```
|
|
|
|
## 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`).
|