978e46ba454d81ffdc78a6b9b03924fa2569e69d
Add ability to expand/collapse SCL variable attribute blocks like {EXTERNALACCESSIBLE := 'false'} to {...}
Features:
- Per-variable collapse rules via collapseVariableRules option
- Interactive toggle with <Leader>xa keybinding
- Commands: SCLToggleAttrBlock, SCLExpandAllAttrBlocks, SCLCollapseAllAttrBlocks
- Keybindings: <Leader>xa (toggle), <Leader>xae (expand all), <Leader>xac (collapse all)
- Supports both formatter-collapsed and manually collapsed blocks
Also update AGENTS.md with new documentation and troubleshooting guide
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 <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 |
Installation
LazyVim
-- ~/.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
-- 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/ |
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>fw |
Workspace Symbols (Telescope) |
<leader>ca |
Code Actions |
External UDT Support
Option 1: Create .plc.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
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
- Workspace Scanning: When opening a
.sclfile, the plugin scans the project for.dbfiles - DB Parsing: Parses
DATA_BLOCKdefinitions and extracts members fromVARsections - 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
// 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
# Generate parser from grammar.js
npm run build
# or: tree-sitter generate
# Run tests
npm test
# or: tree-sitter test
SCL Code Example
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
Languages
C
93.3%
Lua
5.7%
JavaScript
0.6%
C++
0.4%