lazar 0880df4ef5 fix: Attribute block expand/collapse, TIME literal diagnostics, and formatter bugs
- SCLFormat: Remove clear_buffer_state() call that was preventing
       expand after formatting (attr_toggle state now preserved)
     - Diagnostics: Fix false 'Undefined variable' for IEC literals
       like T#2s, D#1d, S5T#1s (TIME, DATE, S5TIME types)
     - Formatter: Fix block comments (* ... *) being misidentified as
       FB calls in VAR_IN_OUT/VAR_INPUT/VAR_OUTPUT sections
     - Formatter: Fix array-based FB calls like instTimers[idx].TON()
       not being properly formatted with aligned parameters
2026-02-24 15:27:58 +01:00
2026-02-23 14:16:46 +01:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00

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 function, FB, DB, or UDT definition
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 with :SCLFormat
Multi-line Assignments Preserves and formats multi-line expressions with proper alignment
FB Call Formatting Formats function block calls in multiline style
Indentation Proper block indentation
Keyword Casing Maintains SCL keyword casing

Syntax Highlighting

  • Full SCL syntax support via tree-sitter
  • Block definitions: ORGANIZATION_BLOCK, FUNCTION_BLOCK, FUNCTION
  • Variable sections: VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_TEMP, VAR_CONSTANT
  • Control structures: IF/THEN/ELSIF/ELSE/END_IF, CASE/OF/END_CASE, FOR/TO/BY/DO/END_FOR, WHILE/END_WHILE, REPEAT/UNTIL/END_REPEAT
  • Built-in data types: BOOL, INT, DINT, REAL, STRING, TIME, etc.
  • Function block instances highlighted in calls
  • FB parameter names (IN, PT, CU, etc.) highlighted as properties
  • # prefix for local variables highlighted
  • Operators: AND, OR, NOT, XOR, MOD
  • Comments: line (//), block ((* *)), C-style (/* */)

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
Attribute Block Toggle Interactive expand/collapse of {...} blocks with <Leader>xa

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
: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. Use :SCLFormat to format, then :SCLCollapseAllAttrBlocks to collapse while preserving the ability to expand later.

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

  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

// 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
S
Description
No description provided
Readme
897 KiB
Languages
C 93.3%
Lua 5.7%
JavaScript 0.6%
C++ 0.4%