refactor: rename scl_lsp → tia_lsp (project umbrella for future STL/LAD/FBD)
Rename the project from scl_lsp to tia_lsp to future-proof for additional
TIA Portal languages (STL/AWL, LAD, FBD). The 'scl' language name is kept
as-is for the SCL filetype and tree-sitter parser; future languages get
their own names (stl, lad, fbd).
Project-wide changes:
- lua/scl_lsp/ → lua/tia_lsp/ (plugin entry point)
- lua/scl/ → lua/tia/ (language modules, shared across TIA langs)
- require('scl.*') → require('tia.*')
- require('scl_lsp') → require('tia_lsp')
- LSP client name: 'scl_lsp' → 'tia_lsp'
- Diagnostic source: 'scl_lsp' → 'tia_lsp' (src/diagnostics.lua)
- package.json name: 'scl-language-server' → 'tia-lsp'
lua/tia_lsp/init.lua:
- Add vim.fn.exepath('tia-lsp') detection so the plugin prefers the
mason-installed executable and falls back to { 'lua', server_path }
for manual installs.
- Add opts.ts_parser_url to configure the tree-sitter parser source.
- Remove hardcoded ~/dev/scl_lsp paths in favor of ~/dev/tia-lsp and
the new ts_parser_url option.
Cleanup:
- Delete lua/tia/init.lua (dead code, unreferenced duplicate of tia_lsp).
- Delete scl_lsp.sh (replaced by the mason-installed bin/tia-lsp wrapper).
- Update README.md and AGENTS.md to reflect the two-repo split
(tia-lsp server + tia-lsp.nvim plugin) and document Mason installation.
Unchanged (intentional):
- grammar.js, src/grammar.json, src/parser.c: tree-sitter language name
remains 'scl' (it's the language, not the project).
- Filetype patterns ('scl', 'udt', 'db') in autocmds.
- :SCL* command names (per-filetype prefix; future :STL* etc.).
Tests pass with the same pre-existing failures as before the rename
(formatter VAR_INPUT test, udt_parser line 199 const-variable bug,
plc_json reference-project-missing). No new failures introduced.
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
-- Attribute block toggle - Interactive expand/collapse of {...} blocks
|
||||
local M = {}
|
||||
|
||||
-- Store expanded content per buffer
|
||||
-- Format: { [bufnr] = { [line_num] = { collapsed = "...", expanded = "actual content", is_collapsed = boolean } } }
|
||||
local buffer_attr_state = {}
|
||||
|
||||
-- Find attribute block under cursor
|
||||
-- Returns: start_col, end_col, content (or nil if not found)
|
||||
function M.find_attr_block_under_cursor()
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
local col = vim.api.nvim_win_get_cursor(0)[2] + 1 -- 1-indexed
|
||||
|
||||
-- Find all {...} blocks in the line
|
||||
local pos = 1
|
||||
while pos <= #line do
|
||||
local start_pos, end_pos = line:find("{(.-)}", pos)
|
||||
if not start_pos then
|
||||
break
|
||||
end
|
||||
|
||||
-- Check if cursor is within this block (including braces)
|
||||
if col >= start_pos and col <= end_pos then
|
||||
local content = line:sub(start_pos + 1, end_pos - 1)
|
||||
return start_pos, end_pos, content
|
||||
end
|
||||
|
||||
pos = end_pos + 1
|
||||
end
|
||||
|
||||
return nil, nil, nil
|
||||
end
|
||||
|
||||
-- Toggle the attribute block under cursor
|
||||
function M.toggle_attr_block()
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local line_num = vim.api.nvim_win_get_cursor(0)[1]
|
||||
local line = vim.api.nvim_get_current_line()
|
||||
|
||||
local start_col, end_col, content = M.find_attr_block_under_cursor()
|
||||
if not start_col then
|
||||
vim.notify("No attribute block found under cursor", vim.log.levels.WARN)
|
||||
return
|
||||
end
|
||||
|
||||
-- Initialize buffer state if needed
|
||||
if not buffer_attr_state[bufnr] then
|
||||
buffer_attr_state[bufnr] = {}
|
||||
end
|
||||
|
||||
local state_key = line_num .. "_" .. start_col
|
||||
local state = buffer_attr_state[bufnr][state_key]
|
||||
|
||||
-- Check if this is currently collapsed
|
||||
local is_collapsed = content == "..."
|
||||
|
||||
if is_collapsed then
|
||||
-- Expand: restore original content
|
||||
if state and state.expanded then
|
||||
local new_line = line:sub(1, start_col) .. state.expanded .. line:sub(end_col)
|
||||
vim.api.nvim_set_current_line(new_line)
|
||||
buffer_attr_state[bufnr][state_key].is_collapsed = false
|
||||
vim.notify("Attribute block expanded", vim.log.levels.INFO)
|
||||
else
|
||||
vim.notify("Cannot expand: original content not stored", vim.log.levels.WARN)
|
||||
end
|
||||
else
|
||||
-- Collapse: store original and show {...}
|
||||
local new_line = line:sub(1, start_col) .. "..." .. line:sub(end_col)
|
||||
vim.api.nvim_set_current_line(new_line)
|
||||
buffer_attr_state[bufnr][state_key] = {
|
||||
collapsed = "...",
|
||||
expanded = content,
|
||||
is_collapsed = true,
|
||||
line_num = line_num,
|
||||
start_col = start_col,
|
||||
}
|
||||
vim.notify("Attribute block collapsed", vim.log.levels.INFO)
|
||||
end
|
||||
end
|
||||
|
||||
-- Expand all collapsed blocks in current buffer
|
||||
function M.expand_all_attr_blocks(opts)
|
||||
opts = opts or {}
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local count = 0
|
||||
|
||||
if not buffer_attr_state[bufnr] then
|
||||
if not opts.silent then
|
||||
vim.notify("No collapsed blocks to expand", vim.log.levels.INFO)
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
for state_key, state in pairs(buffer_attr_state[bufnr]) do
|
||||
if state.is_collapsed then
|
||||
local line = lines[state.line_num]
|
||||
|
||||
if line then
|
||||
local new_line = line:sub(1, state.start_col) .. state.expanded .. line:sub(state.start_col + 4)
|
||||
lines[state.line_num] = new_line
|
||||
state.is_collapsed = false
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
vim.api.nvim_buf_set_lines(bufnr, 0, -1, false, lines)
|
||||
if not opts.silent and count > 0 then
|
||||
vim.notify("Expanded " .. count .. " attribute block(s)", vim.log.levels.INFO)
|
||||
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)
|
||||
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
|
||||
}
|
||||
|
||||
local bufnr = vim.api.nvim_get_current_buf()
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local count = 0
|
||||
|
||||
-- Initialize buffer state
|
||||
if not buffer_attr_state[bufnr] then
|
||||
buffer_attr_state[bufnr] = {}
|
||||
end
|
||||
|
||||
for line_idx, line in ipairs(lines) do
|
||||
local pos = 1
|
||||
while pos <= #line do
|
||||
local start_pos, end_pos, capture = 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
|
||||
local state_key = line_idx .. "_" .. start_pos
|
||||
buffer_attr_state[bufnr][state_key] = {
|
||||
collapsed = "...",
|
||||
expanded = content,
|
||||
is_collapsed = true,
|
||||
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
|
||||
|
||||
-- Clear state when buffer is unloaded
|
||||
function M.clear_buffer_state(bufnr)
|
||||
buffer_attr_state[bufnr] = nil
|
||||
end
|
||||
|
||||
-- Setup autocmd to clear state on buffer unload
|
||||
function M.setup()
|
||||
local augroup = vim.api.nvim_create_augroup("SCLAttrToggle", { clear = true })
|
||||
|
||||
vim.api.nvim_create_autocmd("BufWritePre", {
|
||||
group = augroup,
|
||||
pattern = { "*.scl", "*.udt", "*.db" },
|
||||
callback = function(args)
|
||||
if buffer_attr_state[args.buf] then
|
||||
local has_collapsed = false
|
||||
for _, state in pairs(buffer_attr_state[args.buf]) do
|
||||
if state.is_collapsed then
|
||||
has_collapsed = true
|
||||
break
|
||||
end
|
||||
end
|
||||
if has_collapsed then
|
||||
M.expand_all_attr_blocks({ silent = true })
|
||||
end
|
||||
end
|
||||
end,
|
||||
})
|
||||
|
||||
vim.api.nvim_create_autocmd("BufUnload", {
|
||||
group = augroup,
|
||||
pattern = { "*.scl", "*.udt", "*.db" },
|
||||
callback = function(args)
|
||||
M.clear_buffer_state(args.buf)
|
||||
end,
|
||||
})
|
||||
|
||||
-- Create commands
|
||||
vim.api.nvim_create_user_command("SCLToggleAttrBlock", function()
|
||||
M.toggle_attr_block()
|
||||
end, {})
|
||||
|
||||
vim.api.nvim_create_user_command("SCLExpandAllAttrBlocks", function()
|
||||
M.expand_all_attr_blocks()
|
||||
end, {})
|
||||
|
||||
vim.api.nvim_create_user_command("SCLCollapseAllAttrBlocks", function()
|
||||
M.collapse_all_attr_blocks()
|
||||
end, {})
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user