Compare commits

...
9 Commits
Author SHA1 Message Date
lazar f219a8662f fix: guard vim.lsp.diagnostics.refresh() against nil
Neovim 0.12+ restructured LSP internals; vim.lsp.diagnostics no
longer exists as a submodule. Guard the call so :SCLRescanWorkspaceTypes
doesn't crash on startup.
2026-07-24 12:35:36 +02:00
lazar a9c1b8db16 docs: update reference project path in AGENTS.md 2026-07-24 11:57:50 +02:00
lazar a4400106db refactor: collapse all {} blocks in declaration section, drop pattern filtering 2026-07-24 09:54:22 +02:00
lazar b86ce08426 fix: bridge :SCLRescanWorkspaceTypes to LSP server + refresh diagnostics 2026-07-23 13:55:04 +02:00
lazar 0a0b5c9bf1 fix: DB completion path parsing, array-of-UDT resolution, blink source registration
- Fix path extraction pattern (missing closing quote, extra capture group)
- Add Array[..] of "UdtName" detection for nested member resolution
- Remove vim.schedule_wrap from DB member callback (causes blink.cmp timeout)
- Add built-in instruction parameter fallback (IEC_TIMER IN/PT/Q/ET)
- Ensure DB cache is populated on demand in completion source
- Make try_register() succeed without cmp.add_source_provider API
- docs: add blink.cmp source config steps to installation guide
2026-07-22 20:26:16 +02:00
lazar 3776ce33ff refactor: change the default path for local dev env 2026-07-20 17:01:37 +02:00
lazar 1acac2b73c docs: rewrite installation with architecture overview, troubleshooting, and cross-references 2026-07-19 15:09:58 +02:00
lazar 03548dab7d feat: comprehensive query files for all block types and VAR sections (Phase 3)
Update all 5 tree-sitter query files to cover the full grammar:

highlights.scm:
- Add highlights for new literals (binary, octal, date, bool, typed_int,
  wstring, char)
- Add highlights for block names (organization_block, function_block,
  function, data_block, type_definition)
- Add highlights for return_statement, goto_statement, title_statement
- Add highlights for bit_access, method_callee
- Add compound assignment operators (+=, -=, *=, /=, etc.)

indents.scm:
- Add function, type_definition, data_block to @indent.begin
- Add all var_*_declaration variants to @indent.begin
- Add struct_type to @indent.begin
- Add END_FUNCTION, END_TYPE, END_DATA_BLOCK, END_STRUCT to @indent.end

folds.scm:
- Add function, type_definition, data_block to @fold
- Add all var_*_declaration variants to @fold
- Add struct_type to @fold

locals.scm:
- Add function, type_definition, data_block as @local.scope
- Add all var_*_declaration variants as @local.scope
- Add struct_type as @local.scope
- Add block name definitions (@definition.function, @definition.class,
  @definition.type)
- Narrow @reference to prefixed_identifier only (reduces noise)

tags.scm:
- Add function, data_block, type_definition tags
- Add FB call references
- Add region_statement as @definition.module
2026-07-18 19:20:33 +02:00
lazar aebffc34e7 fix: load compiled parser.so via vim.treesitter.language.add
nvim-treesitter's :TSInstall reloads the parsers module from source
(package.loaded['nvim-treesitter.parsers'] = nil), which wipes runtime
config modifications. The old get_parser_configs() approach no longer
works for registering custom parsers with :TSInstall.

New approach:
- The mason build script now compiles parser.c into parser.so during
  :MasonInstall tia-lsp (requires cc/gcc)
- The plugin's setup_syntax finds parser.so via:
  1. opts.ts_parser_path (explicit override)
  2. Mason package dir (resolved from exepath('tia-lsp'))
  3. ~/dev/tia-lsp/parser.so (local dev fallback)
- Loads it with vim.treesitter.language.add('scl', { path = parser.so })
- Starts highlighting via vim.treesitter.start(bufnr, 'scl') in the
  FileType autocmd
- Falls back to notifying the user if no parser.so is found
- Still registers with nvim-treesitter's parsers table as best-effort
  for :TSInstall support (won't survive reload, but works in-session)
2026-07-18 18:01:32 +02:00
10 changed files with 737 additions and 401 deletions
+2 -2
View File
@@ -17,7 +17,7 @@ lua test/test_fb.lua # Run FB parser tests
lua test/test_integration.lua # Run integration tests lua test/test_integration.lua # Run integration tests
# Reference Project # Reference Project
# Tests use files from: ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/ # Tests use files from: ~/Documents/siemens/scl_lang_support_lazyvim_ref_project/
# Override with: SCL_REF_PROJECT=/path/to/ref lua tests/run_tests.lua # Override with: SCL_REF_PROJECT=/path/to/ref lua tests/run_tests.lua
``` ```
@@ -158,7 +158,7 @@ The LSP validates data types and provides warnings for unknown types:
Use the reference project for testing: Use the reference project for testing:
``` ```
~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/ ~/Documents/siemens/scl_lang_support_lazyvim_ref_project/
``` ```
Contains `.scl`, `.db`, `.udt`, and `.xml` files for comprehensive testing of: Contains `.scl`, `.db`, `.udt`, and `.xml` files for comprehensive testing of:
+139 -59
View File
@@ -11,81 +11,150 @@ A Neovim/LazyVim plugin that launches the [`tia-lsp`](https://gitea.l-tech.rs/la
| **Tab Navigation** | Jump between parameters with `<Tab>` in insert mode | | **Tab Navigation** | Jump between parameters with `<Tab>` in insert mode |
| **Workspace UDT Scanner** | Scans project for user-defined types (.udt files) | | **Workspace UDT Scanner** | Scans project for user-defined types (.udt files) |
| **Workspace Global DB Scanner** | Scans project for global data blocks (.db files) | | **Workspace Global DB Scanner** | Scans project for global data blocks (.db files) |
| **blink.cmp Integration** | Smart completion source for blink.cmp | | **blink.cmp Integration** | Smart completion source for blink.cmp — DB members, FB parameters, built-in types (IEC_TIMER IN/PT/Q/ET), nested UDT/array-of-UDT path resolution |
| **Attribute Block Toggle** | Interactive expand/collapse of `{...}` blocks with `<Leader>xa` | | **Attribute Block Toggle** | Interactive expand/collapse of `{...}` blocks with `<Leader>xa` |
| **Tree-sitter Highlighting** | Syntax highlighting via tree-sitter queries | | **Tree-sitter Highlighting** | Syntax highlighting via tree-sitter queries |
## Architecture
This plugin is part of a three-repo ecosystem:
```
tia-lsp.nvim ← Neovim plugin (you are here) — launches the LSP client,
│ adds editor features (auto-# prefix, blink.cmp source,
│ attribute toggle, workspace scanning, ...)
tia-lsp ← Standalone LSP server — reads JSON-RPC over stdio,
│ provides hover, completion, diagnostics, formatting
│ Requires: lua src/main.lua + compiled parser.so
tia-mason-registry ← mason.nvim recipe — tells mason how to download tia-lsp,
compile parser.so, and create the tia-lsp executable
```
- **tia-lsp** ([repo](https://gitea.l-tech.rs/lazar/tia-lsp)) — the actual language server, editor-agnostic
- **tia-mason-registry** ([repo](https://gitea.l-tech.rs/lazar/tia-mason-registry)) — a single `package.yaml` for mason
- **tia-lsp.nvim** (this repo) — the Neovim-specific integration
## Installation ## Installation
The plugin auto-detects the mason-installed `tia-lsp` executable via `vim.fn.exepath("tia-lsp")` and falls back to `lua <server_path>` for manual installs. The plugin auto-detects the mason-installed `tia-lsp` executable via `vim.fn.exepath("tia-lsp")`. If not found, it falls back to running `lua <server_path>` (configurable).
### LazyVim + Mason (Recommended) ### LazyVim + Mason (Recommended)
1. Add the mason registry as a lazy.nvim plugin: Mason downloads and builds the `tia-lsp` server; this plugin configures the LSP client.
```lua
-- ~/.config/nvim/lua/plugins/tia-mason-registry.lua
return {
url = "https://gitea.l-tech.rs/lazar/tia-mason-registry.git",
name = "tia-mason-registry",
lazy = false, -- must be available before mason setup
}
```
2. Register the registry with mason: **Step 1: Add the mason registry as a lazy.nvim plugin**
```lua
-- ~/.config/nvim/lua/plugins/mason.lua
return {
"mason-org/mason.nvim",
opts = {
registries = {
"file:" .. vim.fn.expand("~/.local/share/nvim/lazy/tia-mason-registry"),
"github:mason-org/mason-registry",
},
},
}
```
3. Add the plugin: mason needs a local copy of the registry to read the install recipe. Adding it as a lazy.nvim plugin gets it cloned automatically:
```lua
-- ~/.config/nvim/lua/plugins/tia-lsp.lua
return {
"lazar/tia-lsp.nvim",
ft = "scl",
lazy = true,
dependencies = {
"neovim/nvim-lspconfig",
"nvim-treesitter/nvim-treesitter",
"saghen/blink.cmp",
},
config = function()
require("tia_lsp").setup({
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,
}
```
4. Install: ```lua
``` -- ~/.config/nvim/lua/plugins/tia-mason-registry.lua
:MasonInstall yq " one-time prerequisite for file: registry return {
:MasonInstall tia-lsp " installs the LSP server url = "https://gitea.l-tech.rs/lazar/tia-mason-registry.git",
``` name = "tia-mason-registry",
}
```
5. Verify: open a `.scl` file and run `:LspInfo` — `tia_lsp` should be attached. **Step 2: Register the registry with mason**
```lua
-- ~/.config/nvim/lua/plugins/mason.lua
return {
"mason-org/mason.nvim",
opts = {
registries = {
"file:" .. vim.fn.expand("~/.local/share/nvim/lazy/tia-mason-registry"),
"github:mason-org/mason-registry",
},
},
}
```
> **Why this works:** lazy.nvim clones `tia-mason-registry` to `~/.local/share/nvim/lazy/tia-mason-registry`. The `file:` registry path points mason to that directory, where it finds `packages/tia-lsp/package.yaml`. Mason needs [`yq`](https://github.com/mikefarah/yq) to parse YAML custom registries — install it once: `:MasonInstall yq`
**Step 3: Install the LSP server**
```
:MasonInstall tia-lsp
```
This clones `tia-lsp` from the source repo, compiles `parser.so` (tree-sitter), and places the `tia-lsp` executable on PATH.
**Step 4: Add this plugin**
```lua
-- ~/.config/nvim/lua/plugins/tia-lsp.lua
return {
"https://gitea.l-tech.rs/lazar/tia-lsp.nvim",
ft = "scl",
lazy = true,
dependencies = {
"neovim/nvim-lspconfig",
"nvim-treesitter/nvim-treesitter",
"saghen/blink.cmp", -- optional, for completion
},
opts = {
lsp = {
-- on_attach = function(client, bufnr) ... end,
},
cmp = true, -- enable blink.cmp integration
workspace_types = true, -- scan project for UDTs and DBs
auto_prefix = true, -- auto-# prefix for local variables
},
}
```
**Step 5: Configure blink.cmp defaults**
The `scl` completion source must be listed in blink.cmp's `sources.default` with the corresponding provider definition. Create or update your blink.cmp config:
```lua
-- ~/.config/nvim/lua/plugins/blink.lua (or extend your existing blink.cmp spec)
return {
"saghen/blink.cmp",
optional = true,
opts = {
sources = {
default = { "lsp", "path", "snippets", "buffer", "scl" },
providers = {
scl = {
name = "scl",
module = "tia.blink_cmp_source",
score_offset = 100,
},
},
},
},
}
```
> **Why this is needed:** blink.cmp only queries sources listed in `sources.default`. Without `"scl"` there, the DB member and built-in type completions (e.g., `"MyDB".myField` or `#myTimer.IN`) will not appear.
**Step 6: Verify**
Open a `.scl` file and run `:LspInfo`. `tia_lsp` should be attached. Run `:checkhealth` for detailed diagnostics.
---
### Manual Setup (without Mason) ### Manual Setup (without Mason)
Clone and build the server manually, then point the plugin at it.
```bash
# 1. Clone the LSP server
git clone --depth 1 https://gitea.l-tech.rs/lazar/tia-lsp.git ~/tia-lsp
# 2. Compile the tree-sitter parser (required for parsing SCL)
cc -fPIC -I ~/tia-lsp/src/tree_sitter -c ~/tia-lsp/src/parser.c -o ~/tia-lsp/parser.o
cc -shared ~/tia-lsp/parser.o -o ~/tia-lsp/parser.so
```
Then configure the plugin. The plugin first looks for `tia-lsp` on PATH; if not found, it falls back to running `lua <server_path>`. The tree-sitter parser is loaded from `parser.so` adjacent to the server.
```lua ```lua
require("tia_lsp").setup({ require("tia_lsp").setup({
server_path = "/path/to/tia-lsp/src/main.lua", -- only used if tia-lsp is not on PATH server_path = "~/tia-lsp/src/main.lua", -- fallback if tia-lsp not on PATH
ts_parser_url = "https://gitea.l-tech.rs/lazar/tia-lsp.git", ts_parser_url = "https://gitea.l-tech.rs/lazar/tia-lsp.git",
cmp = true, cmp = true,
workspace_types = true, workspace_types = true,
@@ -97,7 +166,7 @@ require("tia_lsp").setup({
| Option | Type | Default | Description | | Option | Type | Default | Description |
|--------|------|---------|-------------| |--------|------|---------|-------------|
| `server_path` | string | `~/dev/tia-lsp/src/main.lua` | Path to LSP server (only used if `tia-lsp` is not on PATH) | | `server_path` | string | `~/dev/tia-lsp/src/main.lua` | Path to LSP server (fallback when `tia-lsp` not on PATH; change for manual installs) |
| `ts_parser_url` | string | `file://~/dev/tia-lsp` | URL passed to nvim-treesitter for the SCL parser source | | `ts_parser_url` | string | `file://~/dev/tia-lsp` | URL passed to nvim-treesitter for the SCL parser source |
| `lsp.on_attach` | function | `nil` | Custom LSP attach callback | | `lsp.on_attach` | function | `nil` | Custom LSP attach callback |
| `lsp.formatting` | boolean | `false` | Enable format command | | `lsp.formatting` | boolean | `false` | Enable format command |
@@ -211,6 +280,17 @@ tia-lsp.nvim/
- **blink.cmp** — Optional, for completion - **blink.cmp** — Optional, for completion
- **Lua 5.1+** — LSP server runtime - **Lua 5.1+** — LSP server runtime
## Troubleshooting
| Symptom | Likely Cause | Fix |
|---------|-------------|-----|
| `:MasonInstall tia-lsp` fails with "unknown package" | Custom registry not registered with mason | Check the `registries` list in your mason config includes the `file:` path to the cloned registry |
| `:MasonInstall tia-lsp` fails with "yq: command not found" | Mason needs `yq` to parse the YAML registry | Run `:MasonInstall yq` to install it, then retry `:MasonInstall tia-lsp` |
| `tia_lsp` not attached after opening `.scl` | Server not on PATH or `server_path` points to wrong location | Run `:LspInfo` to see what server is configured. Check `vim.fn.exepath("tia-lsp")` returns a path |
| LSP starts but no syntax highlighting | Tree-sitter parser (`parser.so`) not found | mason install generates it automatically. For manual setups, run the `cc` commands in the Manual Setup section |
| `parser.so` load error at startup | Compiled for wrong architecture or Lua version | Recompile: clean the old `.so` and run the `cc` build commands again |
| LSP starts but no completions/hover | Server can't find its modules | Mason installs to a managed directory. If using manual setup, ensure `src/` contents are present next to `main.lua` |
## Testing ## Testing
```bash ```bash
+25 -39
View File
@@ -112,54 +112,44 @@ function M.expand_all_attr_blocks(opts)
end end
end end
-- Helper to make a pattern case-insensitive -- Find the last line of the declaration section based on filetype
local function case_insensitive_pattern(pattern) local function get_declaration_end(lines, ft)
return pattern:gsub("(%a)", function(c) if ft == "udt" then
return "[" .. c:upper() .. c:lower() .. "]" return #lines
end) end
for i, line in ipairs(lines) do
if line:match("^%s*BEGIN%s*$") then
return i - 1
end
end
return #lines
end end
-- Collapse all attribute blocks matching patterns in current buffer -- Collapse all {...} blocks in the declaration section of current buffer
function M.collapse_all_attr_blocks(patterns) function M.collapse_all_attr_blocks()
patterns = patterns or {
case_insensitive_pattern("^%s*EXTERNAL"), -- EXTERNALACCESSIBLE, EXTERNALVISIBLE
case_insensitive_pattern("^%s*S7_"), -- S7_Optimized_Access
case_insensitive_pattern("^%s*NonRetain"), -- NonRetain
case_insensitive_pattern("^%s*Retain"), -- Retain (but not NonRetain)
"^%s*%w+%s*:=", -- key := value
"^%s*%w+%s*$", -- single word attribute
}
local bufnr = vim.api.nvim_get_current_buf() local bufnr = vim.api.nvim_get_current_buf()
local ft = vim.bo[bufnr].filetype
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
local count = 0 local count = 0
-- Initialize buffer state local decl_end = get_declaration_end(lines, ft)
if not buffer_attr_state[bufnr] then if not buffer_attr_state[bufnr] then
buffer_attr_state[bufnr] = {} buffer_attr_state[bufnr] = {}
end end
for line_idx, line in ipairs(lines) do for line_idx = 1, decl_end do
local line = lines[line_idx]
local pos = 1 local pos = 1
while pos <= #line do while pos <= #line do
local start_pos, end_pos, capture = line:find("{(.-)}", pos) local start_pos, end_pos = line:find("{(.-)}", pos)
if not start_pos then if not start_pos then
break break
end end
local content = line:sub(start_pos + 1, end_pos - 1) local content = line:sub(start_pos + 1, end_pos - 1)
-- Check if content matches any pattern if content ~= "..." then
local should_collapse = false
for _, pattern in ipairs(patterns) do
if content:match(pattern) then
should_collapse = true
break
end
end
-- Don't collapse if already collapsed
if should_collapse and content ~= "..." then
local state_key = line_idx .. "_" .. start_pos local state_key = line_idx .. "_" .. start_pos
buffer_attr_state[bufnr][state_key] = { buffer_attr_state[bufnr][state_key] = {
collapsed = "...", collapsed = "...",
@@ -168,21 +158,17 @@ function M.collapse_all_attr_blocks(patterns)
line_num = line_idx, line_num = line_idx,
start_col = start_pos, start_col = start_pos,
} }
-- Replace in line
line = line:sub(1, start_pos) .. "..." .. line:sub(end_pos) line = line:sub(1, start_pos) .. "..." .. line:sub(end_pos)
count = count + 1 count = count + 1
-- Update position for next search
pos = start_pos + 4 pos = start_pos + 4
else else
pos = end_pos + 1 pos = end_pos + 1
end end
end end
lines[line_idx] = line lines[line_idx] = line
end end
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines) vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
vim.notify("Collapsed " .. count .. " attribute block(s)", vim.log.levels.INFO) vim.notify("Collapsed " .. count .. " attribute block(s)", vim.log.levels.INFO)
end end
+46 -13
View File
@@ -239,10 +239,16 @@ function M:get_completions(context, callback)
local db_parser = require("tia.db_parser") local db_parser = require("tia.db_parser")
local db_name = active_portion:match('"([%w_]+)"') local db_name = active_portion:match('"([%w_]+)"')
-- Ensure cache is populated
if db_name and db_parser.get_cache_count() == 0 then
local ws = require("tia.workspace_types")
ws.scan_project_dbs(bufnr)
end
if db_name then if db_name then
local db = db_parser.get_db(db_name) local db = db_parser.get_db(db_name)
if db then if db then
local path = active_portion:match('"[^"]+%"%.([%w_.]*)') local path = active_portion:match('"[^"]+"%.([%w_.]*)$')
-- Resolve nested path through DB members -- Resolve nested path through DB members
local current_type = nil local current_type = nil
@@ -269,15 +275,23 @@ function M:get_completions(context, callback)
if found_member then if found_member then
resolved_path = part resolved_path = part
local nested_type = nil
if found_member.is_udt then if found_member.is_udt then
-- This member is a UDT type, continue resolving nested_type = found_member.type
else
-- Check for array of UDT: Array[..] of "TypeName"
local array_udt = found_member.raw_type:match('Array%b[]%s*of%s*"([^"]+)"')
if array_udt then
nested_type = array_udt
end
end
if nested_type then
local udt_parser = require("tia.udt_parser") local udt_parser = require("tia.udt_parser")
current_members = udt_parser.get_udt_members(found_member.type) current_members = udt_parser.get_udt_members(nested_type)
current_type = found_member.type current_type = nested_type
else else
-- This is a basic type, can't continue -- This is a basic type, can't continue
if i < #path_parts then if i < #path_parts then
-- There are more parts but this isn't a UDT
found = false found = false
break break
end end
@@ -319,14 +333,11 @@ function M:get_completions(context, callback)
detail = m.raw_type, detail = m.raw_type,
}) })
end end
local wrapped_callback = vim.schedule_wrap(function(response) callback({
callback({ items = items,
items = response.items, is_incomplete_backward = false,
is_incomplete_backward = false, is_incomplete_forward = false,
is_incomplete_forward = false, })
})
end)
wrapped_callback({ items = items })
return return
end end
end end
@@ -959,6 +970,28 @@ function M:get_completions(context, callback)
end end
if not is_udt and not is_fb then if not is_udt and not is_fb then
-- Check built-in instructions (IEC_TIMER, TON, etc.)
local builtin = require("tia.builtin_instructions")
local builtin_params = builtin.get_parameters(var_type)
if builtin_params then
local items = {}
for _, inp in ipairs(builtin_params.inputs or {}) do
table.insert(items, {
label = inp.name,
kind = vim.lsp.protocol.CompletionItemKind.Field,
detail = (inp.type or "ANY") .. " (Input)",
})
end
for _, out in ipairs(builtin_params.outputs or {}) do
table.insert(items, {
label = out.name,
kind = vim.lsp.protocol.CompletionItemKind.Field,
detail = (out.type or "ANY") .. " (Output)",
})
end
wrapped_callback({ items = items })
return
end
wrapped_callback({ items = {} }) wrapped_callback({ items = {} })
return return
end end
+377 -267
View File
@@ -1,328 +1,438 @@
local M = {} local M = {}
local function setup_package_path() local function setup_package_path()
local plugin_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or "" local plugin_path = debug.getinfo(1, "S").source:gsub("^@", ""):match("(.*/)") or ""
plugin_path = plugin_path:gsub("/lua/tia_lsp", "") plugin_path = plugin_path:gsub("/lua/tia_lsp", "")
local lua_path = plugin_path .. "/lua" local lua_path = plugin_path .. "/lua"
if not package.path:match(lua_path) then if not package.path:match(lua_path) then
package.path = lua_path .. "/?.lua;" .. package.path package.path = lua_path .. "/?.lua;" .. package.path
end end
end end
function M.setup(opts) function M.setup(opts)
setup_package_path() setup_package_path()
opts = opts or {} opts = opts or {}
local server_path = opts.server_path or vim.fn.expand("~/dev/tia-lsp/src/main.lua") local server_path = opts.server_path or vim.fn.expand("~/Documents/lua/tia-lsp/src/main.lua")
local root_patterns = { ".git", "data_types", "plc.data.json" }
local function start_lsp(bufnr)
local fname = vim.api.nvim_buf_get_name(bufnr)
if fname == "" then return end
local root_dir = vim.fs.root(fname, root_patterns)
or vim.fn.fnamemodify(fname, ":p:h")
-- Prefer the mason-installed executable (vim.fn.exepath resolves on each
-- buffer enter, so a :MasonInstall mid-session is picked up without restart).
-- Fall back to running the server via `lua <server_path>` for manual installs.
local tia_bin = vim.fn.exepath("tia-lsp")
local cmd = tia_bin ~= "" and { "tia-lsp" } or { "lua", server_path }
vim.lsp.start({
name = "tia_lsp",
cmd = cmd,
root_dir = root_dir,
on_attach = function(client, b)
if opts.lsp and opts.lsp.on_attach then
opts.lsp.on_attach(client, b)
end
if opts.lsp and opts.lsp.formatting then
vim.api.nvim_buf_create_user_command(b, "SCLFormat", function()
vim.lsp.buf.format({ name = "tia_lsp" })
end, {})
end
end,
})
end
-- Create FileType autocmd for future buffers
vim.api.nvim_create_autocmd("FileType", {
pattern = "scl",
callback = function(args)
start_lsp(args.buf)
end,
})
-- Create global format command (works on current buffer)
vim.api.nvim_create_user_command("SCLFormat", function()
vim.lsp.buf.format({ name = "tia_lsp" })
end, {})
-- Start LSP for any existing scl buffers (handles lazy loading case)
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.bo[bufnr].filetype == "scl" and vim.api.nvim_buf_is_loaded(bufnr) then
start_lsp(bufnr)
end
end
M.setup_syntax(opts) local root_patterns = { ".git", "data_types", "plc.data.json" }
M.setup_multiline_params()
M.setup_attr_toggle()
M.create_commands()
if opts.auto_prefix ~= false then local function start_lsp(bufnr)
M.setup_auto_prefix() local fname = vim.api.nvim_buf_get_name(bufnr)
end if fname == "" then
return
end
local root_dir = vim.fs.root(fname, root_patterns) or vim.fn.fnamemodify(fname, ":p:h")
-- Prefer the mason-installed executable (vim.fn.exepath resolves on each
-- buffer enter, so a :MasonInstall mid-session is picked up without restart).
-- Fall back to running the server via `lua <server_path>` for manual installs.
local tia_bin = vim.fn.exepath("tia-lsp")
local cmd = tia_bin ~= "" and { "tia-lsp" } or { "lua", server_path }
vim.lsp.start({
name = "tia_lsp",
cmd = cmd,
root_dir = root_dir,
on_attach = function(client, b)
if opts.lsp and opts.lsp.on_attach then
opts.lsp.on_attach(client, b)
end
if opts.lsp and opts.lsp.formatting then
vim.api.nvim_buf_create_user_command(b, "SCLFormat", function()
vim.lsp.buf.format({ name = "tia_lsp" })
end, {})
end
end,
})
end
-- Create FileType autocmd for future buffers
vim.api.nvim_create_autocmd("FileType", {
pattern = "scl",
callback = function(args)
start_lsp(args.buf)
M.notify_parser_install_if_needed(args.buf)
-- Start tree-sitter highlighting if the parser is registered
pcall(vim.treesitter.start, args.buf, "scl")
end,
})
-- Create global format command (works on current buffer)
vim.api.nvim_create_user_command("SCLFormat", function()
vim.lsp.buf.format({ name = "tia_lsp" })
end, {})
-- Start LSP for any existing scl buffers (handles lazy loading case)
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.bo[bufnr].filetype == "scl" and vim.api.nvim_buf_is_loaded(bufnr) then
start_lsp(bufnr)
M.notify_parser_install_if_needed(bufnr)
pcall(vim.treesitter.start, bufnr, "scl")
end
end
M.setup_syntax(opts)
M.setup_multiline_params()
M.setup_attr_toggle()
M.create_commands()
if opts.auto_prefix ~= false then
M.setup_auto_prefix()
end
end
-- Find a compiled tree-sitter parser (.so) for SCL.
-- Looks in: opts.ts_parser_path, mason package dir, ~/dev/tia-lsp.
-- Returns the path to parser.so, or nil if not found.
function M.find_parser_so(opts)
-- 1. Explicit path from opts
if opts.ts_parser_path and vim.fn.filereadable(opts.ts_parser_path) == 1 then
return opts.ts_parser_path
end
-- 2. Mason install: resolve exepath("tia-lsp") -> real bin -> package dir
local tia_bin = vim.fn.exepath("tia-lsp")
if tia_bin ~= "" then
local real_bin = vim.fn.resolve(vim.fn.fnamemodify(tia_bin, ":p"))
local pkg_dir = vim.fn.fnamemodify(real_bin, ":h:h")
local so = pkg_dir .. "/parser.so"
if vim.fn.filereadable(so) == 1 then
return so
end
end
-- 3. Local dev path
local dev_so = vim.fn.expand("~/dev/tia-lsp/parser.so")
if vim.fn.filereadable(dev_so) == 1 then
return dev_so
end
return nil
end
-- Notify the user once per session if the tree-sitter parser isn't available.
local parser_notified = false
function M.notify_parser_install_if_needed(bufnr)
if parser_notified then
return
end
if vim.bo[bufnr].filetype ~= "scl" then
return
end
local ok = pcall(vim.treesitter.get_parser, bufnr, "scl")
if ok then
return
end
parser_notified = true
vim.schedule(function()
local msg = "SCL: Tree-sitter parser not found. "
if vim.fn.exepath("tia-lsp") ~= "" then
msg = msg .. "Run :MasonInstall tia-lsp to install it."
else
msg = msg .. "Install tia-lsp via mason or compile parser.so manually."
end
vim.notify(msg, vim.log.levels.INFO)
end)
end end
function M.setup_syntax(opts) function M.setup_syntax(opts)
local has_ts, ts = pcall(require, "nvim-treesitter.parsers") -- Load the compiled tree-sitter parser (.so) via Neovim's built-in API.
if has_ts and ts.get_parser_configs then -- This bypasses nvim-treesitter's install system, which reloads its
local ok, parser_config = pcall(ts.get_parser_configs) -- parsers module from source on :TSInstall and wipes runtime config.
if ok and parser_config then local parser_so = M.find_parser_so(opts)
parser_config.scl = { if parser_so then
install_info = { local ok, err = pcall(vim.treesitter.language.add, "scl", { path = parser_so })
-- Override via setup({ ts_parser_url = "https://your-gitea/tia-lsp.git" }). if not ok then
url = opts.ts_parser_url vim.notify(
or "https://gitea.l-tech.rs/lazar/tia-lsp.git", "SCL: Failed to load tree-sitter parser from " .. parser_so .. ": " .. tostring(err),
files = { "src/parser.c" }, vim.log.levels.WARN
generate_requires_npm = false, )
requires_generate_from_grammar = false, end
}, end
filetype = "scl",
}
end
end
if opts.cmp then -- Also register with nvim-treesitter (best-effort, for :TSInstall support
local function try_register() -- in sessions where the user wants to install from source).
local cmp_ok, cmp = pcall(require, "blink.cmp") local has_ts, parsers = pcall(require, "nvim-treesitter.parsers")
local blink_source_ok, blink_source = pcall(require, "tia.blink_cmp_source") if has_ts and type(parsers) == "table" then
if cmp_ok and cmp and cmp.add_source_provider and blink_source_ok then local parser_config
local source_config = { if type(parsers.get_parser_configs) == "function" then
name = "scl", parser_config = parsers.get_parser_configs()
provider = blink_source, else
score_offset = 100, parser_config = parsers
} end
pcall(cmp.add_source_provider, "scl", source_config) if type(parser_config) == "table" then
pcall(cmp.add_filetype_source, "scl", "scl") parser_config.scl = {
install_info = {
url = opts.ts_parser_url or "https://gitea.l-tech.rs/lazar/tia-lsp.git",
files = { "src/parser.c" },
generate_requires_npm = false,
requires_generate_from_grammar = false,
},
filetype = "scl",
}
end
end
vim.notify("SCL: blink.cmp source registered", vim.log.levels.INFO) if opts.cmp then
return true local function try_register()
end local cmp_ok, cmp = pcall(require, "blink.cmp")
return false local blink_source_ok = pcall(require, "tia.blink_cmp_source")
end if cmp_ok and cmp and blink_source_ok then
if cmp.add_source_provider then
local blink_source = require("tia.blink_cmp_source")
local source_config = {
name = "scl",
provider = blink_source,
score_offset = 100,
}
pcall(cmp.add_source_provider, "scl", source_config)
pcall(cmp.add_filetype_source, "scl", "scl")
end
return true
end
return false
end
if not try_register() then if not try_register() then
vim.api.nvim_create_autocmd("BufEnter", { vim.api.nvim_create_autocmd("BufEnter", {
pattern = "*.scl", pattern = "*.scl",
once = true, once = true,
callback = try_register, callback = try_register,
}) })
end end
end end
if opts.workspace_types ~= false then if opts.workspace_types ~= false then
local ok, workspace_types = pcall(require, "tia.workspace_types") local ok, workspace_types = pcall(require, "tia.workspace_types")
if ok then if ok then
workspace_types.setup({ workspace_types.setup({
project_root = opts.project_root, project_root = opts.project_root,
library_paths = opts.library_paths, library_paths = opts.library_paths,
debug = opts.debug, debug = opts.debug,
}) })
end end
end end
end end
function M.setup_auto_prefix() function M.setup_auto_prefix()
local ok, auto_prefix = pcall(require, "tia.auto_prefix") local ok, auto_prefix = pcall(require, "tia.auto_prefix")
if ok then if ok then
auto_prefix.setup() auto_prefix.setup()
end end
end end
function M.setup_multiline_params() function M.setup_multiline_params()
local ok, mp = pcall(require, "tia.multiline_params") local ok, mp = pcall(require, "tia.multiline_params")
if not ok then if not ok then
vim.notify("SCL multiline_params module not found", vim.log.levels.WARN) vim.notify("SCL multiline_params module not found", vim.log.levels.WARN)
return return
end end
vim.api.nvim_create_user_command("SCLMultilineParams", function() vim.api.nvim_create_user_command("SCLMultilineParams", function()
mp.fill_multiline_params() mp.fill_multiline_params()
end, {}) end, {})
vim.keymap.set("i", "<Space>mp", function() vim.keymap.set("i", "<Space>mp", function()
mp.fill_multiline_params() mp.fill_multiline_params()
end, { noremap = true, silent = true, desc = "SCL: Fill multiline parameters" }) end, { noremap = true, silent = true, desc = "SCL: Fill multiline parameters" })
vim.keymap.set("i", "<Tab>", function() vim.keymap.set("i", "<Tab>", function()
local ft = vim.bo.filetype local ft = vim.bo.filetype
if ft == "scl" or ft == "udt" then if ft == "scl" or ft == "udt" then
if mp.jump_to_next_param() then if mp.jump_to_next_param() then
return "" return ""
end end
end end
return "<Tab>" return "<Tab>"
end, { noremap = true, silent = true, desc = "SCL: Jump to next param or Tab" }) end, { noremap = true, silent = true, desc = "SCL: Jump to next param or Tab" })
end end
function M.prefix_current_word() function M.prefix_current_word()
local ok, auto_prefix = pcall(require, "tia.auto_prefix") local ok, auto_prefix = pcall(require, "tia.auto_prefix")
if ok then if ok then
auto_prefix.prefix_word() auto_prefix.prefix_word()
end end
end end
function M.show_variables() function M.show_variables()
local ok, variables = pcall(require, "tia.variables") local ok, variables = pcall(require, "tia.variables")
if not ok then if not ok then
vim.notify("SCL variables module not found", vim.log.levels.WARN) vim.notify("SCL variables module not found", vim.log.levels.WARN)
return return
end end
local vars = variables.extract_variables() local vars = variables.extract_variables()
if #vars == 0 then if #vars == 0 then
vim.notify("No local variables found", vim.log.levels.INFO) vim.notify("No local variables found", vim.log.levels.INFO)
return return
end end
local lines = { "Local Variables:", "---" } local lines = { "Local Variables:", "---" }
for _, var in ipairs(vars) do for _, var in ipairs(vars) do
local type_info = var.type and (" : " .. var.type) or "" local type_info = var.type and (" : " .. var.type) or ""
table.insert(lines, "#" .. var.name .. type_info) table.insert(lines, "#" .. var.name .. type_info)
end end
vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO) vim.notify(table.concat(lines, "\n"), vim.log.levels.INFO)
end end
function M.show_workspace_types() function M.show_workspace_types()
local ok_ws, ws = pcall(require, "tia.workspace_types") local ok_ws, ws = pcall(require, "tia.workspace_types")
local ok_udt, udt = pcall(require, "tia.udt_parser") local ok_udt, udt = pcall(require, "tia.udt_parser")
local ok_fb, fb = pcall(require, "tia.fb_parser") local ok_fb, fb = pcall(require, "tia.fb_parser")
if not ok_ws or not ok_udt or not ok_fb then if not ok_ws or not ok_udt or not ok_fb then
vim.notify("SCL workspace modules not found", vim.log.levels.WARN) vim.notify("SCL workspace modules not found", vim.log.levels.WARN)
return return
end end
local root = ws.get_project_root() local root = ws.get_project_root()
local udt_names = udt.get_all_udt_names() local udt_names = udt.get_all_udt_names()
local fb_names = fb.get_all_fb_names() local fb_names = fb.get_all_fb_names()
if not root then if not root then
vim.notify("No project root detected", vim.log.levels.WARN) vim.notify("No project root detected", vim.log.levels.WARN)
return return
end end
vim.notify("Project: " .. root .. "\nUDTs: " .. #udt_names .. "\nFBs/Functions: " .. #fb_names, vim.log.levels.INFO) vim.notify("Project: " .. root .. "\nUDTs: " .. #udt_names .. "\nFBs/Functions: " .. #fb_names, vim.log.levels.INFO)
end end
function M.rescan_workspace_types() function M.rescan_workspace_types()
local ok_ws, ws = pcall(require, "tia.workspace_types") local ok_ws, ws = pcall(require, "tia.workspace_types")
local ok_udt, udt = pcall(require, "tia.udt_parser") local ok_udt, udt = pcall(require, "tia.udt_parser")
local ok_fb, fb = pcall(require, "tia.fb_parser") local ok_fb, fb = pcall(require, "tia.fb_parser")
if not ok_ws or not ok_udt or not ok_fb then if not ok_ws or not ok_udt or not ok_fb then
vim.notify("SCL workspace modules not found", vim.log.levels.WARN) vim.notify("SCL workspace modules not found", vim.log.levels.WARN)
return return
end end
udt.clear_cache() udt.clear_cache()
fb.clear_cache() fb.clear_cache()
ws.clear_root_cache() ws.clear_root_cache()
local udt_result = ws.scan_project_udts() local udt_result = ws.scan_project_udts()
local fb_result = ws.scan_project_fbs() local fb_result = ws.scan_project_fbs()
vim.notify("Scanned UDTs: " .. udt_result.parsed_count .. "/" .. udt_result.total_files .. vim.notify(
", FBs: " .. fb_result.parsed_count .. "/" .. fb_result.total_files, vim.log.levels.INFO) "Scanned UDTs: "
.. udt_result.parsed_count
.. "/"
.. udt_result.total_files
.. ", FBs: "
.. fb_result.parsed_count
.. "/"
.. fb_result.total_files,
vim.log.levels.INFO
)
local clients = vim.lsp.get_clients({ name = "tia_lsp" })
for _, client in ipairs(clients) do
client.request("workspace/executeCommand", {
command = "SCLRescanWorkspaceTypes",
}, function(err, _)
if err then
vim.notify("SCL RescanWorkspaceTypes LSP error: " .. err.message, vim.log.levels.ERROR)
end
end, 0)
end
if vim.lsp.diagnostics then
vim.lsp.diagnostics.refresh()
end
end end
function M.create_commands() function M.create_commands()
vim.api.nvim_create_user_command("SCLShowVariables", function() M.show_variables() end, {}) vim.api.nvim_create_user_command("SCLShowVariables", function()
vim.api.nvim_create_user_command("SCLShowWorkspaceTypes", function() M.show_workspace_types() end, {}) M.show_variables()
vim.api.nvim_create_user_command("SCLRescanWorkspaceTypes", function() M.rescan_workspace_types() end, {}) end, {})
vim.api.nvim_create_user_command("SCLPrefixWord", function() M.prefix_current_word() end, {}) vim.api.nvim_create_user_command("SCLShowWorkspaceTypes", function()
M.show_workspace_types()
-- Attribute toggle commands end, {})
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function() vim.api.nvim_create_user_command("SCLRescanWorkspaceTypes", function()
local ok, at = pcall(require, "tia.attr_toggle") M.rescan_workspace_types()
if ok then end, {})
at.toggle_attr_block() vim.api.nvim_create_user_command("SCLPrefixWord", function()
else M.prefix_current_word()
vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR) end, {})
end
end, {}) -- Attribute toggle commands
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function()
vim.api.nvim_create_user_command("SCLExpandAllAttrBlocks", function() local ok, at = pcall(require, "tia.attr_toggle")
local ok, at = pcall(require, "tia.attr_toggle") if ok then
if ok then at.toggle_attr_block()
at.expand_all_attr_blocks() else
else vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR)
vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR) end
end end, {})
end, {})
vim.api.nvim_create_user_command("SCLExpandAllAttrBlocks", function()
vim.api.nvim_create_user_command("SCLCollapseAllAttrBlocks", function() local ok, at = pcall(require, "tia.attr_toggle")
local ok, at = pcall(require, "tia.attr_toggle") if ok then
if ok then at.expand_all_attr_blocks()
at.collapse_all_attr_blocks() else
else vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR)
vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR) end
end end, {})
end, {})
vim.api.nvim_create_user_command("SCLCollapseAllAttrBlocks", function()
local ok, at = pcall(require, "tia.attr_toggle")
if ok then
at.collapse_all_attr_blocks()
else
vim.notify("SCL: Failed to load attr_toggle module: " .. tostring(at), vim.log.levels.ERROR)
end
end, {})
end end
function M.setup_attr_toggle() function M.setup_attr_toggle()
local ok, at = pcall(require, "tia.attr_toggle") local ok, at = pcall(require, "tia.attr_toggle")
if not ok then if not ok then
vim.notify("SCL: attr_toggle module not found", vim.log.levels.WARN) vim.notify("SCL: attr_toggle module not found", vim.log.levels.WARN)
return return
end end
at.setup()
local function setup_buffer_keymaps(bufnr) at.setup()
local opts = { buffer = bufnr, silent = true }
local ft = vim.bo[bufnr].filetype
if ft ~= "scl" and ft ~= "udt" then
return
end
-- Toggle individual attribute block under cursor local function setup_buffer_keymaps(bufnr)
vim.keymap.set({ "n", "i" }, "<Leader>xa", function() local opts = { buffer = bufnr, silent = true }
at.toggle_attr_block() local ft = vim.bo[bufnr].filetype
end, vim.tbl_extend("force", opts, { desc = "SCL: Toggle attribute block" }))
-- Expand all collapsed blocks if ft ~= "scl" and ft ~= "udt" then
vim.keymap.set("n", "<Leader>xae", function() return
at.expand_all_attr_blocks() end
end, vim.tbl_extend("force", opts, { desc = "SCL: Expand all attribute blocks" }))
-- Collapse all attribute blocks -- Toggle individual attribute block under cursor
vim.keymap.set("n", "<Leader>xac", function() vim.keymap.set({ "n", "i" }, "<Leader>xa", function()
at.collapse_all_attr_blocks() at.toggle_attr_block()
end, vim.tbl_extend("force", opts, { desc = "SCL: Collapse all attribute blocks" })) end, vim.tbl_extend("force", opts, { desc = "SCL: Toggle attribute block" }))
end
-- Set up keybindings for SCL files only -- Expand all collapsed blocks
vim.api.nvim_create_autocmd("FileType", { vim.keymap.set("n", "<Leader>xae", function()
pattern = { "scl", "udt" }, at.expand_all_attr_blocks()
callback = function(args) end, vim.tbl_extend("force", opts, { desc = "SCL: Expand all attribute blocks" }))
setup_buffer_keymaps(args.buf)
end, -- Collapse all attribute blocks
}) vim.keymap.set("n", "<Leader>xac", function()
at.collapse_all_attr_blocks()
-- Also check if there are already SCL buffers open end, vim.tbl_extend("force", opts, { desc = "SCL: Collapse all attribute blocks" }))
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do end
if vim.api.nvim_buf_is_loaded(bufnr) then
setup_buffer_keymaps(bufnr) -- Set up keybindings for SCL files only
end vim.api.nvim_create_autocmd("FileType", {
end pattern = { "scl", "udt" },
callback = function(args)
setup_buffer_keymaps(args.buf)
end,
})
-- Also check if there are already SCL buffers open
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(bufnr) then
setup_buffer_keymaps(bufnr)
end
end
end end
return M return M
+9
View File
@@ -3,12 +3,21 @@
[ [
(organization_block) (organization_block)
(function_block) (function_block)
(function)
(type_definition)
(data_block)
(var_declaration) (var_declaration)
(var_temp_declaration)
(var_constant_declaration)
(var_retain_declaration)
(var_non_retain_declaration)
(var_db_specific_declaration)
(if_statement) (if_statement)
(case_statement) (case_statement)
(for_statement) (for_statement)
(while_statement) (while_statement)
(repeat_statement) (repeat_statement)
(region_statement) (region_statement)
(struct_type)
(block_comment) (block_comment)
] @fold ] @fold
+58 -11
View File
@@ -1,66 +1,99 @@
; SCL highlights ; SCL highlights
; Comments
(line_comment) @comment (line_comment) @comment
(block_comment) @comment (block_comment) @comment
(c_style_comment) @comment (c_style_comment) @comment
; Literals
(number) @number (number) @number
(hex_number) @number (hex_number) @number
(string) @string (binary_number) @number
(octal_number) @number
(time_value) @number (time_value) @number
(date_literal) @number
(bool_literal) @constant.builtin
(typed_int_literal) @number
(wstring_literal) @string
(char_literal) @character
(string) @string
; Prefix
(prefix) @punctuation.special (prefix) @punctuation.special
; Variables and identifiers
(identifier) @variable (identifier) @variable
; FB instance calls - highlight instance name as type ; FB instance calls
(fb_call (fb_call
instance: (prefixed_identifier instance: (prefixed_identifier
(identifier) @type)) (identifier) @type))
(fb_call (fb_call
instance: (identifier) @type) instance: (identifier) @type)
; FB parameter names (IN :=, PT :=, etc.) ; FB parameter names
(fb_parameter (fb_parameter
name: (identifier) @property) name: (identifier) @property)
; Function call names ; Function call names
(function_call (function_call
(identifier) @function) (identifier) @function)
(function_call
(string) @function)
; Method call names
(method_callee
(identifier) @function)
; Variable declarations ; Variable declarations
(var_item (var_item
name: (identifier) @variable) name: (identifier) @variable)
; Type references in declarations ; Type references
(type (type
(identifier) @type) (identifier) @type)
(type_builtin) @type.builtin (type_builtin) @type.builtin
; Keywords and operators
(keyword) @keyword (keyword) @keyword
(constant) @constant.builtin (constant) @constant.builtin
(operator) @operator (operator) @operator
(assignment) @operator ; Block names
(binary_expression) @operator (organization_block
(unary_expression) @operator name: (string) @type)
(function_block
name: (string) @type)
(function
name: (string) @function)
(data_block
name: (string) @type)
(type_definition
name: (string) @type)
; Access
(field_access (field_access
"." "."
(identifier) @property) (identifier) @property)
(array_access) @property (array_access) @property
(bit_access) @property
; Attributes
(attribute_list) @attribute (attribute_list) @attribute
(attribute) @attribute (attribute) @attribute
(attribute_value) @string (attribute_value) @string
(var_item) @property ; Statements
(assignment) @operator
(binary_expression) @operator
(unary_expression) @operator
(chained_assignment) @operator
; Function/FB calls
(function_call) @function.call (function_call) @function.call
(fb_call) @function.call (fb_call) @function.call
; Control flow
(if_statement) @conditional (if_statement) @conditional
(elsif_clause) @conditional (elsif_clause) @conditional
(else_clause) @conditional (else_clause) @conditional
@@ -71,6 +104,13 @@
(while_statement) @repeat (while_statement) @repeat
(repeat_statement) @repeat (repeat_statement) @repeat
(return_statement) @keyword
(goto_statement) @keyword
; Title
(title_statement) @comment
; Punctuation
";" @punctuation.delimiter ";" @punctuation.delimiter
":" @punctuation.delimiter ":" @punctuation.delimiter
"," @punctuation.delimiter "," @punctuation.delimiter
@@ -80,15 +120,22 @@
"]" @punctuation.bracket "]" @punctuation.bracket
"." @punctuation.delimiter "." @punctuation.delimiter
; Operators
":=" @operator ":=" @operator
"=>" @operator "=>" @operator
"+=" @operator
"-=" @operator
"*=" @operator
"/=" @operator
"&=" @operator
"|=" @operator
"^=" @operator
"=" @operator "=" @operator
"<>" @operator "<>" @operator
"<" @operator "<" @operator
">" @operator ">" @operator
"<=" @operator "<=" @operator
">=" @operator ">=" @operator
"+" @operator "+" @operator
"-" @operator "-" @operator
"*" @operator "*" @operator
+26 -2
View File
@@ -1,10 +1,26 @@
; Indentation rules for SCL ; Indentation rules for SCL
; Indent after block keywords ; Indent after block definitions
[ [
(organization_block) (organization_block)
(function_block) (function_block)
(function)
(type_definition)
(data_block)
] @indent.begin
; Indent after VAR sections
[
(var_declaration) (var_declaration)
(var_temp_declaration)
(var_constant_declaration)
(var_retain_declaration)
(var_non_retain_declaration)
(var_db_specific_declaration)
] @indent.begin
; Indent after control structures
[
(if_statement) (if_statement)
(case_statement) (case_statement)
(for_statement) (for_statement)
@@ -13,12 +29,16 @@
(region_statement) (region_statement)
] @indent.begin ] @indent.begin
; Indent after THEN, DO, etc. ; Indent after STRUCT in UDTs and inline structs
(struct_type) @indent.begin
; Indent after THEN, DO, ELSE, ELSIF, BEGIN
[ [
"THEN" "THEN"
"DO" "DO"
"ELSE" "ELSE"
"ELSIF" "ELSIF"
(keyword) @indent.branch
] @indent.branch ] @indent.branch
; Outdent at end keywords ; Outdent at end keywords
@@ -30,8 +50,12 @@
"END_WHILE" "END_WHILE"
"END_REPEAT" "END_REPEAT"
"END_REGION" "END_REGION"
"END_STRUCT"
"END_ORGANIZATION_BLOCK" "END_ORGANIZATION_BLOCK"
"END_FUNCTION_BLOCK" "END_FUNCTION_BLOCK"
"END_FUNCTION"
"END_TYPE"
"END_DATA_BLOCK"
] @indent.end ] @indent.end
; Outdent for ELSE and ELSIF ; Outdent for ELSE and ELSIF
+30 -6
View File
@@ -1,15 +1,39 @@
; Local variable scoping for SCL - matches grammar.js structure ; Local variable scoping for SCL
; Variable declarations define local variables ; Variable declarations define local variables
(var_item (var_item
name: (identifier) @definition.var) name: (identifier) @definition.var)
; Variable sections create scopes
(var_declaration) @local.scope
; Blocks create scopes ; Blocks create scopes
(organization_block) @local.scope (organization_block) @local.scope
(function_block) @local.scope (function_block) @local.scope
(function) @local.scope
(type_definition) @local.scope
(data_block) @local.scope
; Identifiers are references ; VAR sections create scopes
(identifier) @reference (var_declaration) @local.scope
(var_temp_declaration) @local.scope
(var_constant_declaration) @local.scope
(var_retain_declaration) @local.scope
(var_non_retain_declaration) @local.scope
(var_db_specific_declaration) @local.scope
; Struct types create scopes
(struct_type) @local.scope
; Block names are definitions
(organization_block
name: (string) @definition.function)
(function_block
name: (string) @definition.function)
(function
name: (string) @definition.function)
(data_block
name: (string) @definition.class)
(type_definition
name: (string) @definition.type)
; References
(prefixed_identifier
(identifier) @reference)
+25 -2
View File
@@ -1,13 +1,25 @@
; Tag queries for SCL - matches grammar.js structure ; Tag queries for SCL - symbol navigation
; Organization blocks ; Organization blocks
(organization_block (organization_block
name: (string) @name) @definition.function name: (string) @name) @definition.function
; Function blocks ; Function blocks
(function_block (function_block
name: (string) @name) @definition.function name: (string) @name) @definition.function
; Functions
(function
name: (string) @name) @definition.function
; Data blocks
(data_block
name: (string) @name) @definition.class
; Type definitions (UDTs)
(type_definition
name: (string) @name) @definition.type
; Variable definitions within blocks ; Variable definitions within blocks
(var_item (var_item
name: (identifier) @name name: (identifier) @name
@@ -25,3 +37,14 @@
(function_call (function_call
(string) @name) @reference.call (string) @name) @reference.call
; FB calls (references)
(fb_call
instance: (identifier) @name) @reference.call
(fb_call
instance: (prefixed_identifier
(identifier) @name)) @reference.call
; Region statements
(region_statement) @definition.module