# SCL Language Server - Unified Plugin for Siemens SCL A comprehensive Neovim/LazyVim plugin providing **Language Server Protocol (LSP)**, **Linting**, **Formatting**, and **Syntax Highlighting** for Siemens SCL (Structured Control Language) used in Siemens TIA Portal. ## Features ### LSP (Language Server Protocol) | Feature | Description | |---------|-------------| | **Hover** | Press `K` to see variable type and struct fields | | **Completion** | `#` for variables, `.` for struct fields, `(` for functions | | **Go to Definition** | `gd` jump to variable declaration | | **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 | | **Indentation** | Proper block indentation | | **Keyword Casing** | Maintains SCL keyword casing | ### Syntax Highlighting - Full SCL syntax support via tree-sitter - ORGANIZATION_BLOCK, FUNCTION_BLOCK, FUNCTION definitions - Variable declarations (VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_TEMP, etc.) - Control structures (IF/THEN/ELSE, CASE, FOR, WHILE, REPEAT) - Code regions and comments - All SCL data types and operators ### 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` | ## Installation ### LazyVim ```lua -- ~/.config/nvim/lua/plugins/scl.lua return { "lazar/scl_lsp", ft = "scl", lazy = true, dependencies = { "neovim/nvim-lspconfig", "nvim-treesitter/nvim-treesitter", "saghen/blink.cmp", }, config = function() require("scl_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, } ``` ### Manual Setup ```lua -- In your init.lua or plugin file require("scl_lsp").setup({ server_path = "/path/to/scl_lsp/src/main.lua", cmp = true, workspace_types = true, auto_prefix = true, }) ``` ## Configuration Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `server_path` | string | `~/dev/scl_lsp/src/main.lua` | Path to LSP server | | `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 | | `:LspSCLFormat` | Format current SCL file | | `:SCLGeneratePlcJson` | Generate plc.data.json from data_types/ | | `: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:** The formatter has automatic collapsing disabled by default to preserve compatibility with the toggle feature. Format with `:LspSCLFormat` first, then use `:SCLCollapseAllAttrBlocks` to collapse while preserving the ability to expand later. ## 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 /home/lazar/dev/scl_lsp/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. ### How it works 1. **Workspace Scanning**: When opening a `.scl` file, the plugin 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` ### Example ```scl // Access global DB members "HmiData".units "Parameter".machine.unit100.paramTimeStampApplied // Inside FB call parameter #instParameterManager(hmiInterface:="HmiData".units, ...) ``` ### Project Structure ``` scl_lsp/ ├── README.md # This file ├── package.json # NPM scripts (tree-sitter) ├── Makefile # Build commands ├── grammar.js # Tree-sitter grammar ├── tree-sitter.json # Parser config ├── queries/ # Tree-sitter queries │ ├── 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 │ ├── parser.lua # Regex-based parser │ ├── treesitter.lua # Tree-sitter integration │ ├── json.lua # JSON encoder/decoder │ ├── plc_json.lua # External UDT loading │ ├── diagnostics.lua # Linter diagnostics │ └── formatter.lua # Document formatter └── lua/ └── scl_lsp/ ├── init.lua # Main plugin setup ├── plugins/ │ └── scl.lua # LazyVim plugin spec └── scl/ # Syntax/completion modules ├── init.lua ├── auto_prefix.lua ├── blink_cmp_source.lua ├── db_parser.lua ├── fb_parser.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 # Run tests npm test # or: tree-sitter test ``` ## 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 ``` ## 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/scl_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