# 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 | | **blink.cmp Integration** | Smart completion source for blink.cmp | ## 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 and FB count | | `:SCLRescanWorkspaceTypes` | Rescan workspace for types | | `: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/ | ## 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) | | `fw` | Workspace Symbols (Telescope) | | `ca` | Code Actions | ## 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 ``` ## 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 ├── 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 ``` ## Requirements - **Neovim 0.9+** - **nvim-lspconfig** - LSP client - **nvim-treesitter** - Syntax highlighting - **blink.cmp** - Optional, for completion - **Lua 5.1+** - LSP server runtime