Compare commits
5
Commits
3776ce33ff
...
f219a8662f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f219a8662f | ||
|
|
a9c1b8db16 | ||
|
|
a4400106db | ||
|
|
b86ce08426 | ||
|
|
0a0b5c9bf1 |
@@ -17,7 +17,7 @@ lua test/test_fb.lua # Run FB parser tests
|
||||
lua test/test_integration.lua # Run integration tests
|
||||
|
||||
# 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
|
||||
```
|
||||
|
||||
@@ -158,7 +158,7 @@ The LSP validates data types and provides warnings for unknown types:
|
||||
|
||||
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:
|
||||
|
||||
@@ -11,7 +11,7 @@ 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 |
|
||||
| **Workspace UDT Scanner** | Scans project for user-defined types (.udt files) |
|
||||
| **Workspace Global DB Scanner** | Scans project for global data blocks (.db files) |
|
||||
| **blink.cmp Integration** | Smart completion source for blink.cmp |
|
||||
| **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` |
|
||||
| **Tree-sitter Highlighting** | Syntax highlighting via tree-sitter queries |
|
||||
|
||||
@@ -105,7 +105,33 @@ return {
|
||||
}
|
||||
```
|
||||
|
||||
**Step 5: Verify**
|
||||
**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.
|
||||
|
||||
|
||||
+25
-39
@@ -112,54 +112,44 @@ function M.expand_all_attr_blocks(opts)
|
||||
end
|
||||
end
|
||||
|
||||
-- Helper to make a pattern case-insensitive
|
||||
local function case_insensitive_pattern(pattern)
|
||||
return pattern:gsub("(%a)", function(c)
|
||||
return "[" .. c:upper() .. c:lower() .. "]"
|
||||
end)
|
||||
-- Find the last line of the declaration section based on filetype
|
||||
local function get_declaration_end(lines, ft)
|
||||
if ft == "udt" then
|
||||
return #lines
|
||||
end
|
||||
for i, line in ipairs(lines) do
|
||||
if line:match("^%s*BEGIN%s*$") then
|
||||
return i - 1
|
||||
end
|
||||
end
|
||||
return #lines
|
||||
end
|
||||
|
||||
-- Collapse all attribute blocks matching patterns in current buffer
|
||||
function M.collapse_all_attr_blocks(patterns)
|
||||
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
|
||||
}
|
||||
|
||||
-- Collapse all {...} blocks in the declaration section of current buffer
|
||||
function M.collapse_all_attr_blocks()
|
||||
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 count = 0
|
||||
|
||||
-- Initialize buffer state
|
||||
|
||||
local decl_end = get_declaration_end(lines, ft)
|
||||
|
||||
if not buffer_attr_state[bufnr] then
|
||||
buffer_attr_state[bufnr] = {}
|
||||
end
|
||||
|
||||
for line_idx, line in ipairs(lines) do
|
||||
|
||||
for line_idx = 1, decl_end do
|
||||
local line = lines[line_idx]
|
||||
local pos = 1
|
||||
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
|
||||
break
|
||||
end
|
||||
|
||||
|
||||
local content = line:sub(start_pos + 1, end_pos - 1)
|
||||
|
||||
-- Check if content matches any pattern
|
||||
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
|
||||
if content ~= "..." then
|
||||
local state_key = line_idx .. "_" .. start_pos
|
||||
buffer_attr_state[bufnr][state_key] = {
|
||||
collapsed = "...",
|
||||
@@ -168,21 +158,17 @@ function M.collapse_all_attr_blocks(patterns)
|
||||
line_num = line_idx,
|
||||
start_col = start_pos,
|
||||
}
|
||||
|
||||
-- Replace in line
|
||||
|
||||
line = line:sub(1, start_pos) .. "..." .. line:sub(end_pos)
|
||||
count = count + 1
|
||||
|
||||
-- Update position for next search
|
||||
pos = start_pos + 4
|
||||
else
|
||||
pos = end_pos + 1
|
||||
end
|
||||
end
|
||||
|
||||
lines[line_idx] = line
|
||||
end
|
||||
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
vim.notify("Collapsed " .. count .. " attribute block(s)", vim.log.levels.INFO)
|
||||
end
|
||||
|
||||
@@ -239,10 +239,16 @@ function M:get_completions(context, callback)
|
||||
local db_parser = require("tia.db_parser")
|
||||
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
|
||||
local db = db_parser.get_db(db_name)
|
||||
if db then
|
||||
local path = active_portion:match('"[^"]+%"%.([%w_.]*)')
|
||||
local path = active_portion:match('"[^"]+"%.([%w_.]*)$')
|
||||
|
||||
-- Resolve nested path through DB members
|
||||
local current_type = nil
|
||||
@@ -269,15 +275,23 @@ function M:get_completions(context, callback)
|
||||
|
||||
if found_member then
|
||||
resolved_path = part
|
||||
local nested_type = nil
|
||||
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")
|
||||
current_members = udt_parser.get_udt_members(found_member.type)
|
||||
current_type = found_member.type
|
||||
current_members = udt_parser.get_udt_members(nested_type)
|
||||
current_type = nested_type
|
||||
else
|
||||
-- This is a basic type, can't continue
|
||||
if i < #path_parts then
|
||||
-- There are more parts but this isn't a UDT
|
||||
found = false
|
||||
break
|
||||
end
|
||||
@@ -319,14 +333,11 @@ function M:get_completions(context, callback)
|
||||
detail = m.raw_type,
|
||||
})
|
||||
end
|
||||
local wrapped_callback = vim.schedule_wrap(function(response)
|
||||
callback({
|
||||
items = response.items,
|
||||
is_incomplete_backward = false,
|
||||
is_incomplete_forward = false,
|
||||
})
|
||||
end)
|
||||
wrapped_callback({ items = items })
|
||||
callback({
|
||||
items = items,
|
||||
is_incomplete_backward = false,
|
||||
is_incomplete_forward = false,
|
||||
})
|
||||
return
|
||||
end
|
||||
end
|
||||
@@ -959,6 +970,28 @@ function M:get_completions(context, callback)
|
||||
end
|
||||
|
||||
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 = {} })
|
||||
return
|
||||
end
|
||||
|
||||
+27
-11
@@ -177,17 +177,18 @@ function M.setup_syntax(opts)
|
||||
if opts.cmp then
|
||||
local function try_register()
|
||||
local cmp_ok, cmp = pcall(require, "blink.cmp")
|
||||
local blink_source_ok, blink_source = pcall(require, "tia.blink_cmp_source")
|
||||
if cmp_ok and cmp and cmp.add_source_provider and blink_source_ok then
|
||||
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")
|
||||
|
||||
vim.notify("SCL: blink.cmp source registered", vim.log.levels.INFO)
|
||||
local blink_source_ok = pcall(require, "tia.blink_cmp_source")
|
||||
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
|
||||
@@ -325,6 +326,21 @@ function M.rescan_workspace_types()
|
||||
.. 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
|
||||
|
||||
function M.create_commands()
|
||||
|
||||
Reference in New Issue
Block a user