Files
tia-lsp.nvim/AGENTS.md
T
lazar 246fa85442 fix(lsp): Fix LSP server attachment and JSON parsing issues
- Replace lspconfig.scl_lsp registration with vim.lsp.start() + FileType autocmd
  for Neovim 0.11+ compatibility
- Fix Lua reserved keyword 'end' in tables (use bracket notation ['end'])
- Fix JSON decoder pattern matching bugs (s:find -> s:sub:match)
- Add LSP method name conversion (textDocument/didOpen -> textDocument_didOpen)
- Handle request vs notification correctly (only respond to requests with id)
- Strip CRLF line endings for Windows compatibility
- Add semantic token handler aliases
- Update documentation with implementation notes
2026-02-18 13:03:14 +01:00

6.3 KiB

AGENTS.md - SCL Language Server (Unified)

A unified Neovim/LazyVim plugin for Siemens SCL language support providing LSP, linting, formatting, syntax highlighting, and auto-completion.

Reference Project for Testing

For manual testing and SCL/UDT/DB file examples, use:

~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project

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 mode execution

Tree-sitter Parser

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

Project Structure

scl_lsp/
├── src/                    # Standalone LSP server (uses dofile)
│   ├── main.lua           # Entry point, JSON-RPC protocol
│   ├── parser.lua         # Regex-based SCL parser
│   ├── diagnostics.lua    # Linter diagnostics
│   └── formatter.lua      # Document formatter
├── lua/scl/                # Neovim plugin modules (uses require)
│   ├── blink_cmp_source.lua   # blink.cmp completion source
│   ├── udt_parser.lua         # UDT (.udt) parser
│   ├── db_parser.lua          # Global DB (.db) parser
│   ├── fb_parser.lua          # Function Block parser
│   ├── variables.lua          # Local variable extraction
│   ├── workspace_types.lua    # Workspace scanning
│   └── multiline_params.lua   # FB parameter filling
└── queries/                # Tree-sitter queries

Code Style Guidelines

Module Pattern

All modules follow the standard Lua module pattern:

-- Module description comment at top
local M = {}

-- Private module-level cache
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, var_types)
  • Constants: UPPER_SNAKE_CASE (e.g., PROJECT_MARKERS)
  • Private functions: declare as local function before public functions
  • Boolean variables: prefix with is_, has_ (e.g., is_udt, has_members)

Imports

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

Comments

  • Every file must have a header comment explaining its purpose
  • All functions should have a brief comment describing what they do
  • Complex logic requires inline comments explaining the "why"
  • Non-obvious regex patterns must have explanatory comments

Parser Module API Convention

All parser modules (udt_parser, db_parser, fb_parser) must implement:

  • parse_*_file(filepath) - Parse a file and cache result
  • parse_*_content(content, filename) - Parse string content
  • get_*(name) - Get single cached item by name
  • get_all_*_names() - Return list of all cached names
  • get_*_members(name) - Get members/fields for a type
  • is_*_type(name) - Check if name is a known type
  • clear_cache() - Clear all cached data
  • get_cache_count() - Return number of cached items

Cache Management

  • Use module-level local tables for caching: local cache = {}
  • Clear caches by iterating and setting to nil, not by reassigning:
function M.clear_cache()
  for k in pairs(cache) do
    cache[k] = nil
  end
end

Lua Reserved Keywords

Lua reserved keywords (like end, for, in, etc.) cannot be used as table keys directly. Use bracket notation:

-- WRONG: causes syntax error
local range = { start = pos1, end = pos2 }

-- CORRECT: use bracket notation
local range = { start = pos1, ["end"] = pos2 }

This is especially important for LSP range objects which require an end field per the LSP specification.

Error Handling

Return Pattern

Functions that can fail return nil, "error message":

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

External Dependencies

Always wrap external requires in pcall():

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

Validation

Check required parameters early and return sensible defaults:

function M.get_members(type_name)
  if not type_name then
    return {}
  end
  -- ... continue
end

Completion System

Trigger Characters

  • # - Local variables (after BEGIN)
  • . - Member access (UDT/DB members)
  • " - Global DB names
  • ( - FB/Function parameters
  • (space) - General completion

Active Portion Detection

When matching patterns in completion, use the "active portion" of the line (after the last operator) to correctly handle expressions like:

"DB1".member := "DB2".member

Find the last :=, =>, =, <>, >=, <=, >, <, AND, OR, NOT and only match patterns after it.

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/

LSP Server Implementation Notes

Method Name Conversion

LSP methods like textDocument/didOpen are converted to handler names like textDocument_didOpen:

local method_name = message.method:gsub("/", "_")
local handler = handlers[method_name]

Request vs Notification

  • Requests have id field and require a response
  • Notifications have no id and should not receive a response
if message.id then
  -- Only send response for requests
  return { id = message.id, result = result }
end

Line Ending Handling

The LSP server strips CRLF (\r\n) line endings for Windows compatibility:

line = line:gsub("\r$", "")

JSON Parser

The standalone JSON parser in src/json.lua handles:

  • Objects, arrays, strings, numbers, booleans, null
  • Escape sequences in strings
  • Whitespace skipping