- Fix path extraction pattern (missing closing quote, extra capture group) - Add Array[..] of "UdtName" detection for nested member resolution - Remove vim.schedule_wrap from DB member callback (causes blink.cmp timeout) - Add built-in instruction parameter fallback (IEC_TIMER IN/PT/Q/ET) - Ensure DB cache is populated on demand in completion source - Make try_register() succeed without cmp.add_source_provider API - docs: add blink.cmp source config steps to installation guide
301 lines
12 KiB
Markdown
301 lines
12 KiB
Markdown
# tia-lsp.nvim - Neovim Plugin for Siemens TIA Portal
|
|
|
|
A Neovim/LazyVim plugin that launches the [`tia-lsp`](https://gitea.l-tech.rs/lazar/tia-lsp) language server and adds editor-specific features for Siemens TIA Portal languages. Currently supports **SCL** (Structured Control Language), with planned support for **STL/AWL**, **LAD**, and **FBD**.
|
|
|
|
## 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 `<Space>mp` or `:SCLMultilineParams` |
|
|
| **Tab Navigation** | Jump between parameters with `<Tab>` 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 — DB members, FB parameters, built-in types (IEC_TIMER IN/PT/Q/ET), nested UDT/array-of-UDT path resolution |
|
|
| **Attribute Block Toggle** | Interactive expand/collapse of `{...}` blocks with `<Leader>xa` |
|
|
| **Tree-sitter Highlighting** | Syntax highlighting via tree-sitter queries |
|
|
|
|
## Architecture
|
|
|
|
This plugin is part of a three-repo ecosystem:
|
|
|
|
```
|
|
tia-lsp.nvim ← Neovim plugin (you are here) — launches the LSP client,
|
|
│ adds editor features (auto-# prefix, blink.cmp source,
|
|
│ attribute toggle, workspace scanning, ...)
|
|
▼
|
|
tia-lsp ← Standalone LSP server — reads JSON-RPC over stdio,
|
|
│ provides hover, completion, diagnostics, formatting
|
|
│ Requires: lua src/main.lua + compiled parser.so
|
|
▲
|
|
tia-mason-registry ← mason.nvim recipe — tells mason how to download tia-lsp,
|
|
compile parser.so, and create the tia-lsp executable
|
|
```
|
|
|
|
- **tia-lsp** ([repo](https://gitea.l-tech.rs/lazar/tia-lsp)) — the actual language server, editor-agnostic
|
|
- **tia-mason-registry** ([repo](https://gitea.l-tech.rs/lazar/tia-mason-registry)) — a single `package.yaml` for mason
|
|
- **tia-lsp.nvim** (this repo) — the Neovim-specific integration
|
|
|
|
## Installation
|
|
|
|
The plugin auto-detects the mason-installed `tia-lsp` executable via `vim.fn.exepath("tia-lsp")`. If not found, it falls back to running `lua <server_path>` (configurable).
|
|
|
|
### LazyVim + Mason (Recommended)
|
|
|
|
Mason downloads and builds the `tia-lsp` server; this plugin configures the LSP client.
|
|
|
|
**Step 1: Add the mason registry as a lazy.nvim plugin**
|
|
|
|
mason needs a local copy of the registry to read the install recipe. Adding it as a lazy.nvim plugin gets it cloned automatically:
|
|
|
|
```lua
|
|
-- ~/.config/nvim/lua/plugins/tia-mason-registry.lua
|
|
return {
|
|
url = "https://gitea.l-tech.rs/lazar/tia-mason-registry.git",
|
|
name = "tia-mason-registry",
|
|
}
|
|
```
|
|
|
|
**Step 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",
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
> **Why this works:** lazy.nvim clones `tia-mason-registry` to `~/.local/share/nvim/lazy/tia-mason-registry`. The `file:` registry path points mason to that directory, where it finds `packages/tia-lsp/package.yaml`. Mason needs [`yq`](https://github.com/mikefarah/yq) to parse YAML custom registries — install it once: `:MasonInstall yq`
|
|
|
|
**Step 3: Install the LSP server**
|
|
|
|
```
|
|
:MasonInstall tia-lsp
|
|
```
|
|
|
|
This clones `tia-lsp` from the source repo, compiles `parser.so` (tree-sitter), and places the `tia-lsp` executable on PATH.
|
|
|
|
**Step 4: Add this plugin**
|
|
|
|
```lua
|
|
-- ~/.config/nvim/lua/plugins/tia-lsp.lua
|
|
return {
|
|
"https://gitea.l-tech.rs/lazar/tia-lsp.nvim",
|
|
ft = "scl",
|
|
lazy = true,
|
|
dependencies = {
|
|
"neovim/nvim-lspconfig",
|
|
"nvim-treesitter/nvim-treesitter",
|
|
"saghen/blink.cmp", -- optional, for completion
|
|
},
|
|
opts = {
|
|
lsp = {
|
|
-- on_attach = function(client, bufnr) ... end,
|
|
},
|
|
cmp = true, -- enable blink.cmp integration
|
|
workspace_types = true, -- scan project for UDTs and DBs
|
|
auto_prefix = true, -- auto-# prefix for local variables
|
|
},
|
|
}
|
|
```
|
|
|
|
**Step 5: Configure blink.cmp defaults**
|
|
|
|
The `scl` completion source must be listed in blink.cmp's `sources.default` with the corresponding provider definition. Create or update your blink.cmp config:
|
|
|
|
```lua
|
|
-- ~/.config/nvim/lua/plugins/blink.lua (or extend your existing blink.cmp spec)
|
|
return {
|
|
"saghen/blink.cmp",
|
|
optional = true,
|
|
opts = {
|
|
sources = {
|
|
default = { "lsp", "path", "snippets", "buffer", "scl" },
|
|
providers = {
|
|
scl = {
|
|
name = "scl",
|
|
module = "tia.blink_cmp_source",
|
|
score_offset = 100,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
```
|
|
|
|
> **Why this is needed:** blink.cmp only queries sources listed in `sources.default`. Without `"scl"` there, the DB member and built-in type completions (e.g., `"MyDB".myField` or `#myTimer.IN`) will not appear.
|
|
|
|
**Step 6: Verify**
|
|
|
|
Open a `.scl` file and run `:LspInfo`. `tia_lsp` should be attached. Run `:checkhealth` for detailed diagnostics.
|
|
|
|
---
|
|
|
|
### Manual Setup (without Mason)
|
|
|
|
Clone and build the server manually, then point the plugin at it.
|
|
|
|
```bash
|
|
# 1. Clone the LSP server
|
|
git clone --depth 1 https://gitea.l-tech.rs/lazar/tia-lsp.git ~/tia-lsp
|
|
|
|
# 2. Compile the tree-sitter parser (required for parsing SCL)
|
|
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
|
|
```
|
|
|
|
Then configure the plugin. The plugin first looks for `tia-lsp` on PATH; if not found, it falls back to running `lua <server_path>`. The tree-sitter parser is loaded from `parser.so` adjacent to the server.
|
|
|
|
```lua
|
|
require("tia_lsp").setup({
|
|
server_path = "~/tia-lsp/src/main.lua", -- fallback if tia-lsp not on PATH
|
|
ts_parser_url = "https://gitea.l-tech.rs/lazar/tia-lsp.git",
|
|
cmp = true,
|
|
workspace_types = true,
|
|
auto_prefix = true,
|
|
})
|
|
```
|
|
|
|
## Configuration Options
|
|
|
|
| Option | Type | Default | Description |
|
|
|--------|------|---------|-------------|
|
|
| `server_path` | string | `~/dev/tia-lsp/src/main.lua` | Path to LSP server (fallback when `tia-lsp` not on PATH; change for manual installs) |
|
|
| `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 |
|
|
| `<leader>s` | Document Symbols |
|
|
| `<leader>w` | Workspace Symbols |
|
|
| `<Space>mp` | Fill multiline FB/Function parameters (insert mode) |
|
|
| `<Tab>` | Jump to next parameter (insert mode) |
|
|
| `<Leader>xa` | Toggle attribute block under cursor |
|
|
| `<Leader>xae` | Expand all attribute blocks |
|
|
| `<Leader>xac` | Collapse all attribute blocks |
|
|
| `<leader>fw` | Workspace Symbols (Telescope) |
|
|
| `<leader>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 `<Leader>xa` to toggle expand/collapse
|
|
- Use `<Leader>xae` to expand all blocks in buffer
|
|
- Use `<Leader>xac` to collapse all blocks
|
|
|
|
**Note:** Collapsed blocks are automatically expanded before saving to preserve full content on disk.
|
|
|
|
## Filetype Detection
|
|
|
|
The plugin includes `ftdetect/scl.vim` which registers the following file extensions as `scl` filetype:
|
|
- `*.scl` — SCL source files
|
|
- `*.udt` — User-defined type files
|
|
- `*.db` — Data block files
|
|
|
|
## Project Structure
|
|
|
|
```
|
|
tia-lsp.nvim/
|
|
├── lua/
|
|
│ ├── tia_lsp/ # Plugin entry point
|
|
│ │ ├── init.lua # Main setup (vim.lsp.start, exepath detection)
|
|
│ │ └── plugins/
|
|
│ │ └── scl.lua # LazyVim plugin spec (SCL filetype)
|
|
│ └── tia/ # Language modules (shared across TIA languages)
|
|
│ ├── auto_prefix.lua # Auto-# prefixing
|
|
│ ├── attr_toggle.lua # Attribute block expand/collapse
|
|
│ ├── blink_cmp_source.lua # blink.cmp completion source
|
|
│ ├── builtin_instructions.lua
|
|
│ ├── db_parser.lua # Global DB parser
|
|
│ ├── fb_parser.lua # Function Block parser
|
|
│ ├── multiline_params.lua # Multiline parameter filling
|
|
│ ├── udt_parser.lua # UDT parser
|
|
│ ├── variables.lua # Variable extraction
|
|
│ └── workspace_types.lua # Workspace scanning
|
|
├── queries/
|
|
│ └── scl/ # Tree-sitter queries (per-language)
|
|
│ ├── highlights.scm # Syntax highlighting
|
|
│ ├── indents.scm # Indentation
|
|
│ ├── folds.scm # Code folding
|
|
│ ├── locals.scm # Variable scoping
|
|
│ └── tags.scm # Navigation tags
|
|
├── ftdetect/
|
|
│ └── scl.vim # Filetype detection
|
|
└── test/ # Plugin tests
|
|
├── run_tests.lua
|
|
├── test_harness.lua
|
|
├── test_udt.lua
|
|
├── test_db.lua
|
|
├── test_fb.lua
|
|
└── test_integration.lua
|
|
```
|
|
|
|
## Requirements
|
|
|
|
- **Neovim 0.11+** (uses `vim.lsp.start()`)
|
|
- **tia-lsp** — the LSP server (install via mason or manually)
|
|
- **nvim-treesitter** — Syntax highlighting
|
|
- **blink.cmp** — Optional, for completion
|
|
- **Lua 5.1+** — LSP server runtime
|
|
|
|
## Troubleshooting
|
|
|
|
| Symptom | Likely Cause | Fix |
|
|
|---------|-------------|-----|
|
|
| `:MasonInstall tia-lsp` fails with "unknown package" | Custom registry not registered with mason | Check the `registries` list in your mason config includes the `file:` path to the cloned registry |
|
|
| `:MasonInstall tia-lsp` fails with "yq: command not found" | Mason needs `yq` to parse the YAML registry | Run `:MasonInstall yq` to install it, then retry `:MasonInstall tia-lsp` |
|
|
| `tia_lsp` not attached after opening `.scl` | Server not on PATH or `server_path` points to wrong location | Run `:LspInfo` to see what server is configured. Check `vim.fn.exepath("tia-lsp")` returns a path |
|
|
| LSP starts but no syntax highlighting | Tree-sitter parser (`parser.so`) not found | mason install generates it automatically. For manual setups, run the `cc` commands in the Manual Setup section |
|
|
| `parser.so` load error at startup | Compiled for wrong architecture or Lua version | Recompile: clean the old `.so` and run the `cc` build commands again |
|
|
| LSP starts but no completions/hover | Server can't find its modules | Mason installs to a managed directory. If using manual setup, ensure `src/` contents are present next to `main.lua` |
|
|
|
|
## Testing
|
|
|
|
```bash
|
|
lua test/run_tests.lua
|
|
```
|
|
|
|
Tests use files from a reference project (override with `SCL_REF_PROJECT=/path/to/ref lua test/run_tests.lua`).
|