Files
tia-lsp/AGENTS.md
T
lazar ae5c9fd77d refactor: rename scl_lsp → tia_lsp (project umbrella for future STL/LAD/FBD)
Rename the project from scl_lsp to tia_lsp to future-proof for additional
TIA Portal languages (STL/AWL, LAD, FBD). The 'scl' language name is kept
as-is for the SCL filetype and tree-sitter parser; future languages get
their own names (stl, lad, fbd).

Project-wide changes:
- lua/scl_lsp/ → lua/tia_lsp/  (plugin entry point)
- lua/scl/     → lua/tia/      (language modules, shared across TIA langs)
- require('scl.*')    → require('tia.*')
- require('scl_lsp')  → require('tia_lsp')
- LSP client name:    'scl_lsp' → 'tia_lsp'
- Diagnostic source:  'scl_lsp' → 'tia_lsp'  (src/diagnostics.lua)
- package.json name:  'scl-language-server' → 'tia-lsp'

lua/tia_lsp/init.lua:
- Add vim.fn.exepath('tia-lsp') detection so the plugin prefers the
  mason-installed executable and falls back to { 'lua', server_path }
  for manual installs.
- Add opts.ts_parser_url to configure the tree-sitter parser source.
- Remove hardcoded ~/dev/scl_lsp paths in favor of ~/dev/tia-lsp and
  the new ts_parser_url option.

Cleanup:
- Delete lua/tia/init.lua (dead code, unreferenced duplicate of tia_lsp).
- Delete scl_lsp.sh (replaced by the mason-installed bin/tia-lsp wrapper).
- Update README.md and AGENTS.md to reflect the two-repo split
  (tia-lsp server + tia-lsp.nvim plugin) and document Mason installation.

Unchanged (intentional):
- grammar.js, src/grammar.json, src/parser.c: tree-sitter language name
  remains 'scl' (it's the language, not the project).
- Filetype patterns ('scl', 'udt', 'db') in autocmds.
- :SCL* command names (per-filetype prefix; future :STL* etc.).

Tests pass with the same pre-existing failures as before the rename
(formatter VAR_INPUT test, udt_parser line 199 const-variable bug,
plc_json reference-project-missing). No new failures introduced.
2026-07-18 11:30:55 +02:00

6.5 KiB

AGENTS.md - tia-lsp

A Neovim/LazyVim plugin and standalone LSP server for Siemens TIA Portal languages. Currently supports SCL; planned support for STL/AWL, LAD, FBD. The project is split into two repos:

  • tia-lsp (this repo) — LSP server (src/) + tree-sitter grammar (grammar.js). Editor-agnostic.
  • tia-lsp.nvim — Neovim plugin (lua/) that launches the server and adds editor features.

Build/Lint/Test Commands

# LSP Server
make start                    # Start LSP server
make test                     # Run LSP in test mode
lua src/main.lua --test       # Direct test execution

# Tree-sitter Parser
tree-sitter generate          # Generate parser from grammar.js
tree-sitter test              # Run all parser tests
tree-sitter parse <file.scl>  # Parse single SCL file

# Lua Linting
luacheck src/                 # Lint LSP server code
luacheck lua/tia/             # Lint plugin modules

# Unit & Integration Tests
lua test/run_tests.lua       # Run all tests
lua test/test_udt.lua        # Run UDT parser tests
lua test/test_db.lua         # Run DB parser tests
lua test/test_fb.lua         # Run FB parser tests
lua test/test_formatter.lua  # Run formatter tests
lua test/test_plc_json.lua   # Run plc_json tests
lua test/test_integration.lua # Run integration tests

# Reference Project
# Tests use files from: ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/
# Override with: SCL_REF_PROJECT=/path/to/ref lua tests/run_tests.lua

Project Structure

tia-lsp/
├── src/                     # LSP server (uses dofile)
│   ├── main.lua            # Entry point, JSON-RPC protocol
│   ├── parser.lua          # SCL parser (will become parser_scl.lua + dispatch)
│   ├── diagnostics.lua     # Linter (source = "tia_lsp")
│   ├── formatter.lua       # Document formatter
│   ├── treesitter.lua      # Tree-sitter integration
│   ├── plc_json.lua        # External UDT/DB loading (language-agnostic)
│   ├── builtin_instructions.lua  # TIA Portal built-in types
│   └── json.lua            # JSON encoder/decoder
├── lua/tia/                 # Neovim plugin (uses require)
│   ├── udt_parser.lua      # UDT parser
│   ├── db_parser.lua       # Global DB parser
│   ├── fb_parser.lua       # Function Block parser
│   ├── variables.lua       # Variable extraction
│   ├── workspace_types.lua # Workspace scanning
│   ├── multiline_params.lua
│   ├── auto_prefix.lua
│   └── attr_toggle.lua
├── lua/tia_lsp/             # Plugin entry point
│   ├── init.lua            # Main plugin setup (vim.lsp.start)
│   └── plugins/scl.lua     # LazyVim plugin spec
├── queries/                 # Tree-sitter queries (per-language: queries/scl/)
└── grammar.js               # Tree-sitter grammar (language name: 'scl')

Naming Convention

  • Project name (repo, Lua module, LSP server, mason package): tia-lsp / tia_lsp — umbrella for all TIA languages.
  • Language name (filetype, tree-sitter parser, grammar): scl — stays as-is. Future languages (stl, lad, fbd) get their own names.
  • LSP client name: tia_lsp (one client handles all TIA languages; dispatches by filetype / languageId).
  • Diagnostic source: tia_lsp.
  • Commands: :SCLFormat, :SCLShowVariables, etc. — per-filetype prefix. Future: :STLFormat, etc.

Filetype Detection

After the split, ftdetect/scl.vim will live in tia-lsp.nvim. Currently required in Neovim config:

au BufRead,BufNewFile *.scl set filetype=scl
au BufRead,BufNewFile *.udt set filetype=scl
au BufRead,BufNewFile *.db set filetype=scl

Code Style Guidelines

Module Pattern

local M = {}

local cache = {}

function M.public_function()
end

local function private_function()
end

return M

Naming Conventions

  • Functions/variables: snake_case (e.g., get_udt_members)
  • Constants: UPPER_SNAKE_CASE (e.g., PROJECT_MARKERS)
  • Private functions: declare as local function before public functions
  • Booleans: prefix with is_, has_ (e.g., is_udt, has_members)

Imports

  • lua/tia/ modules: require("tia.module_name")
  • lua/tia_lsp/ entry: require("tia_lsp")
  • src/ modules: dofile(script_path .. "/module.lua")
  • External dependencies: wrap in pcall() for safety

Code Formatting

  • lua/tia/ and lua/tia_lsp/: 2 spaces indentation
  • src/: tab-based indentation
  • Max line length: 120 characters
  • No trailing whitespace

Comments

Add comments to explain non-obvious code sections, complex logic, and public API functions. Keep comments concise and relevant.

Parser Module API

All parser modules must implement:

  • parse_*_file(filepath) - Parse and cache
  • parse_*_content(content, filename) - Parse string
  • get_*(name) - Get cached item
  • get_all_*_names() - List all cached names
  • get_*_members(name) - Get members/fields
  • is_*_type(name) - Check if known type
  • clear_cache() - Clear cached data
  • get_cache_count() - Return cache size

Cache Management

local cache = {}

function M.clear_cache()
  for k in pairs(cache) do
    cache[k] = nil
  end
end

Error Handling

function M.parse_file(filepath)
  local file = io.open(filepath, "r")
  if not file then
    return nil, "Cannot open file: " .. filepath
  end
  return result
end

-- External dependencies
local ok, module = pcall(require, "some_module")
if ok and module then
  module.do_something()
end

Lua Reserved Keywords

Cannot use reserved keywords as table keys directly:

-- WRONG
local range = { start = pos1, end = pos2 }

-- CORRECT
local range = { start = pos1, ["end"] = pos2 }

Critical for LSP range objects with end field.

Type System

The LSP validates data types and provides warnings for unknown types:

  • Elementary: BOOL, BYTE, WORD, DWORD, INT, DINT, REAL, TIME, STRING, etc.
  • Timer/counter: IEC_TIMER, TON_TIME, TOF_TIME, CTU, CTD, CTUD
  • Built-in FBs: TON, TOF, TP, R_TRIG, F_TRIG, etc.
  • Built-in functions: ADD, SUB, MUL, DIV, SIN, COS, SQRT, etc.
  • Workspace types: UDTs from .udt files, DBs from .db files

Testing

Use the reference project for testing:

~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/

Contains .scl, .db, .udt, and .xml files for comprehensive testing of:

  • LSP features (hover, completion, go-to-definition)
  • Formatter (:SCLFormat)
  • Attribute block toggle
  • Workspace type scanning