Compare commits
9
Commits
develop
...
f219a8662f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f219a8662f | ||
|
|
a9c1b8db16 | ||
|
|
a4400106db | ||
|
|
b86ce08426 | ||
|
|
0a0b5c9bf1 | ||
|
|
3776ce33ff | ||
|
|
1acac2b73c | ||
|
|
03548dab7d | ||
|
|
aebffc34e7 |
@@ -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:
|
||||||
|
|||||||
@@ -11,27 +11,53 @@ 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.
|
||||||
|
|
||||||
|
**Step 1: Add the mason registry as a lazy.nvim 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
|
```lua
|
||||||
-- ~/.config/nvim/lua/plugins/tia-mason-registry.lua
|
-- ~/.config/nvim/lua/plugins/tia-mason-registry.lua
|
||||||
return {
|
return {
|
||||||
url = "https://gitea.l-tech.rs/lazar/tia-mason-registry.git",
|
url = "https://gitea.l-tech.rs/lazar/tia-mason-registry.git",
|
||||||
name = "tia-mason-registry",
|
name = "tia-mason-registry",
|
||||||
lazy = false, -- must be available before mason setup
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
2. Register the registry with mason:
|
**Step 2: Register the registry with mason**
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
-- ~/.config/nvim/lua/plugins/mason.lua
|
-- ~/.config/nvim/lua/plugins/mason.lua
|
||||||
return {
|
return {
|
||||||
@@ -45,47 +71,90 @@ The plugin auto-detects the mason-installed `tia-lsp` executable via `vim.fn.exe
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
3. Add the plugin:
|
> **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
|
```lua
|
||||||
-- ~/.config/nvim/lua/plugins/tia-lsp.lua
|
-- ~/.config/nvim/lua/plugins/tia-lsp.lua
|
||||||
return {
|
return {
|
||||||
"lazar/tia-lsp.nvim",
|
"https://gitea.l-tech.rs/lazar/tia-lsp.nvim",
|
||||||
ft = "scl",
|
ft = "scl",
|
||||||
lazy = true,
|
lazy = true,
|
||||||
dependencies = {
|
dependencies = {
|
||||||
"neovim/nvim-lspconfig",
|
"neovim/nvim-lspconfig",
|
||||||
"nvim-treesitter/nvim-treesitter",
|
"nvim-treesitter/nvim-treesitter",
|
||||||
"saghen/blink.cmp",
|
"saghen/blink.cmp", -- optional, for completion
|
||||||
},
|
},
|
||||||
config = function()
|
opts = {
|
||||||
require("tia_lsp").setup({
|
|
||||||
lsp = {
|
lsp = {
|
||||||
on_attach = function(client, bufnr)
|
-- on_attach = function(client, bufnr) ... end,
|
||||||
-- custom keymaps
|
},
|
||||||
end,
|
cmp = true, -- enable blink.cmp integration
|
||||||
|
workspace_types = true, -- scan project for UDTs and DBs
|
||||||
|
auto_prefix = true, -- auto-# prefix for local variables
|
||||||
},
|
},
|
||||||
cmp = true, -- Enable blink.cmp integration
|
|
||||||
workspace_types = true, -- Enable workspace UDT scanning
|
|
||||||
auto_prefix = true, -- Enable auto-# prefixing
|
|
||||||
debug = false,
|
|
||||||
})
|
|
||||||
end,
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
4. Install:
|
**Step 5: Configure blink.cmp defaults**
|
||||||
```
|
|
||||||
:MasonInstall yq " one-time prerequisite for file: registry
|
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:
|
||||||
:MasonInstall tia-lsp " installs the LSP server
|
|
||||||
|
```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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
5. Verify: open a `.scl` file and run `:LspInfo` — `tia_lsp` should be attached.
|
> **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
|
||||||
|
|||||||
+20
-34
@@ -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 = "...",
|
||||||
@@ -169,17 +159,13 @@ function M.collapse_all_attr_blocks(patterns)
|
|||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -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 = response.items,
|
items = 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
|
||||||
|
|||||||
+131
-21
@@ -13,16 +13,17 @@ 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 root_patterns = { ".git", "data_types", "plc.data.json" }
|
||||||
|
|
||||||
local function start_lsp(bufnr)
|
local function start_lsp(bufnr)
|
||||||
local fname = vim.api.nvim_buf_get_name(bufnr)
|
local fname = vim.api.nvim_buf_get_name(bufnr)
|
||||||
if fname == "" then return end
|
if fname == "" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
local root_dir = vim.fs.root(fname, root_patterns)
|
local root_dir = vim.fs.root(fname, root_patterns) or vim.fn.fnamemodify(fname, ":p:h")
|
||||||
or vim.fn.fnamemodify(fname, ":p:h")
|
|
||||||
|
|
||||||
-- Prefer the mason-installed executable (vim.fn.exepath resolves on each
|
-- Prefer the mason-installed executable (vim.fn.exepath resolves on each
|
||||||
-- buffer enter, so a :MasonInstall mid-session is picked up without restart).
|
-- buffer enter, so a :MasonInstall mid-session is picked up without restart).
|
||||||
@@ -53,6 +54,9 @@ function M.setup(opts)
|
|||||||
pattern = "scl",
|
pattern = "scl",
|
||||||
callback = function(args)
|
callback = function(args)
|
||||||
start_lsp(args.buf)
|
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,
|
end,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -65,6 +69,8 @@ function M.setup(opts)
|
|||||||
for _, bufnr in ipairs(vim.api.nvim_list_bufs()) do
|
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
|
if vim.bo[bufnr].filetype == "scl" and vim.api.nvim_buf_is_loaded(bufnr) then
|
||||||
start_lsp(bufnr)
|
start_lsp(bufnr)
|
||||||
|
M.notify_parser_install_if_needed(bufnr)
|
||||||
|
pcall(vim.treesitter.start, bufnr, "scl")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -78,16 +84,87 @@ function M.setup(opts)
|
|||||||
end
|
end
|
||||||
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
|
||||||
|
|
||||||
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)
|
||||||
|
if parser_so then
|
||||||
|
local ok, err = pcall(vim.treesitter.language.add, "scl", { path = parser_so })
|
||||||
|
if not ok then
|
||||||
|
vim.notify(
|
||||||
|
"SCL: Failed to load tree-sitter parser from " .. parser_so .. ": " .. tostring(err),
|
||||||
|
vim.log.levels.WARN
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Also register with nvim-treesitter (best-effort, for :TSInstall support
|
||||||
|
-- in sessions where the user wants to install from source).
|
||||||
|
local has_ts, parsers = pcall(require, "nvim-treesitter.parsers")
|
||||||
|
if has_ts and type(parsers) == "table" then
|
||||||
|
local parser_config
|
||||||
|
if type(parsers.get_parser_configs) == "function" then
|
||||||
|
parser_config = parsers.get_parser_configs()
|
||||||
|
else
|
||||||
|
parser_config = parsers
|
||||||
|
end
|
||||||
|
if type(parser_config) == "table" then
|
||||||
parser_config.scl = {
|
parser_config.scl = {
|
||||||
install_info = {
|
install_info = {
|
||||||
-- Override via setup({ ts_parser_url = "https://your-gitea/tia-lsp.git" }).
|
url = opts.ts_parser_url or "https://gitea.l-tech.rs/lazar/tia-lsp.git",
|
||||||
url = opts.ts_parser_url
|
|
||||||
or "https://gitea.l-tech.rs/lazar/tia-lsp.git",
|
|
||||||
files = { "src/parser.c" },
|
files = { "src/parser.c" },
|
||||||
generate_requires_npm = false,
|
generate_requires_npm = false,
|
||||||
requires_generate_from_grammar = false,
|
requires_generate_from_grammar = false,
|
||||||
@@ -100,8 +177,10 @@ function M.setup_syntax(opts)
|
|||||||
if opts.cmp then
|
if opts.cmp then
|
||||||
local function try_register()
|
local function try_register()
|
||||||
local cmp_ok, cmp = pcall(require, "blink.cmp")
|
local cmp_ok, cmp = pcall(require, "blink.cmp")
|
||||||
local blink_source_ok, blink_source = pcall(require, "tia.blink_cmp_source")
|
local blink_source_ok = pcall(require, "tia.blink_cmp_source")
|
||||||
if cmp_ok and cmp and cmp.add_source_provider and blink_source_ok then
|
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 = {
|
local source_config = {
|
||||||
name = "scl",
|
name = "scl",
|
||||||
provider = blink_source,
|
provider = blink_source,
|
||||||
@@ -109,8 +188,7 @@ function M.setup_syntax(opts)
|
|||||||
}
|
}
|
||||||
pcall(cmp.add_source_provider, "scl", source_config)
|
pcall(cmp.add_source_provider, "scl", source_config)
|
||||||
pcall(cmp.add_filetype_source, "scl", "scl")
|
pcall(cmp.add_filetype_source, "scl", "scl")
|
||||||
|
end
|
||||||
vim.notify("SCL: blink.cmp source registered", vim.log.levels.INFO)
|
|
||||||
return true
|
return true
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
@@ -237,15 +315,47 @@ function M.rescan_workspace_types()
|
|||||||
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()
|
||||||
|
end, {})
|
||||||
|
vim.api.nvim_create_user_command("SCLRescanWorkspaceTypes", function()
|
||||||
|
M.rescan_workspace_types()
|
||||||
|
end, {})
|
||||||
|
vim.api.nvim_create_user_command("SCLPrefixWord", function()
|
||||||
|
M.prefix_current_word()
|
||||||
|
end, {})
|
||||||
|
|
||||||
-- Attribute toggle commands
|
-- Attribute toggle commands
|
||||||
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function()
|
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function()
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||||
|
|||||||
+24
-1
@@ -1,4 +1,4 @@
|
|||||||
; Tag queries for SCL - matches grammar.js structure
|
; Tag queries for SCL - symbol navigation
|
||||||
|
|
||||||
; Organization blocks
|
; Organization blocks
|
||||||
(organization_block
|
(organization_block
|
||||||
@@ -8,6 +8,18 @@
|
|||||||
(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
|
||||||
|
|||||||
Reference in New Issue
Block a user