Files
tia-lsp.nvim/README.md
T

11 KiB

tia-lsp.nvim - Neovim Plugin for Siemens TIA Portal

A Neovim/LazyVim plugin that launches the 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
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) — the actual language server, editor-agnostic
  • tia-mason-registry (repo) — 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).

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:

-- ~/.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

-- ~/.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 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

-- ~/.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: 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.

# 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.

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:

-- 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

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).