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,204 @@
|
||||
-- SCL Variable Extractor
|
||||
-- Extracts local variables from VAR sections and provides them with # prefix
|
||||
|
||||
local M = {}
|
||||
|
||||
local ts = vim.treesitter
|
||||
|
||||
-- Query to find variable declarations with their types
|
||||
-- Simplified query that captures all var_item nodes regardless of parent type
|
||||
local var_query_string = [[
|
||||
(var_item
|
||||
name: (identifier) @var_name
|
||||
type: (_) @var_type)
|
||||
]]
|
||||
|
||||
-- Check if cursor is inside a VAR section
|
||||
local function is_in_var_section()
|
||||
local cursor_node = ts.get_node()
|
||||
if not cursor_node then
|
||||
return false
|
||||
end
|
||||
|
||||
local node = cursor_node
|
||||
while node do
|
||||
local type = node:type()
|
||||
if type == "var_declaration" then
|
||||
return true
|
||||
end
|
||||
if type == "organization_block" or type == "function_block" then
|
||||
-- Check if we're in the var declarations part (before BEGIN)
|
||||
local parent = node
|
||||
local children = {}
|
||||
for child in parent:iter_children() do
|
||||
table.insert(children, child)
|
||||
end
|
||||
|
||||
for _, child in ipairs(children) do
|
||||
if child:type() == "var_declaration" then
|
||||
-- Check if cursor is before this var_declaration
|
||||
local start_row = child:range()
|
||||
local cursor_row = vim.api.nvim_win_get_cursor(0)[1] - 1
|
||||
if cursor_row <= start_row then
|
||||
return true
|
||||
end
|
||||
end
|
||||
if child:type() == "BEGIN" then
|
||||
local start_row = child:range()
|
||||
local cursor_row = vim.api.nvim_win_get_cursor(0)[1] - 1
|
||||
if cursor_row < start_row then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
node = node:parent()
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Extract all local variables from the buffer with their types
|
||||
function M.extract_variables(bufnr)
|
||||
bufnr = bufnr or vim.api.nvim_get_current_buf()
|
||||
|
||||
local variables = {}
|
||||
|
||||
-- Try treesitter first
|
||||
local ok, parser = pcall(ts.get_parser, bufnr, "scl")
|
||||
if ok and parser then
|
||||
local tree = parser:parse()[1]
|
||||
if tree then
|
||||
local root = tree:root()
|
||||
local query_ok, query = pcall(ts.query.parse, "scl", var_query_string)
|
||||
if query_ok then
|
||||
for _, match, _ in query:iter_matches(root, bufnr, 0, -1) do
|
||||
local var_name = nil
|
||||
local var_type = nil
|
||||
|
||||
for id, nodes in pairs(match) do
|
||||
-- nodes can be a single node or a table of nodes
|
||||
local node_list = type(nodes) == "table" and nodes or { nodes }
|
||||
for _, node in ipairs(node_list) do
|
||||
if node and type(node.range) == "function" then
|
||||
local text = ts.get_node_text(node, bufnr)
|
||||
local capture_name = query.captures[id]
|
||||
|
||||
if capture_name == "var_name" then
|
||||
var_name = text
|
||||
elseif capture_name == "var_type" then
|
||||
var_type = text
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if var_name then
|
||||
table.insert(variables, {
|
||||
name = var_name,
|
||||
type = var_type or "",
|
||||
})
|
||||
end
|
||||
end
|
||||
return variables
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Fallback: simple regex-based extraction
|
||||
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false)
|
||||
local in_var = false
|
||||
for _, line in ipairs(lines) do
|
||||
if line:match("^%s*VAR") or line:match("^%s*VAR_INPUT") or line:match("^%s*VAR_OUTPUT") or line:match("^%s*VAR_IN_OUT") then
|
||||
in_var = true
|
||||
elseif line:match("^%s*END_VAR") then
|
||||
in_var = false
|
||||
elseif in_var then
|
||||
-- Match variable declarations like: "name : Type;" or "name : \"UdtType\";"
|
||||
-- Handle both basic types and quoted UDT types
|
||||
local var_name, var_type = line:match("^%s*([%w_]+)%s*:%s*([^;]+)")
|
||||
if var_name then
|
||||
-- Clean up type (remove attributes, default values, comments)
|
||||
if var_type then
|
||||
-- Remove attributes { ... }
|
||||
var_type = var_type:gsub("%s*{.-}%s*", " ")
|
||||
-- Remove default value := ...
|
||||
var_type = var_type:gsub("%s*:=.*$", "")
|
||||
-- Remove trailing comments
|
||||
var_type = var_type:gsub("%s*//.*$", "")
|
||||
-- Trim whitespace
|
||||
var_type = var_type:gsub("^%s*", ""):gsub("%s*$", "")
|
||||
end
|
||||
table.insert(variables, {
|
||||
name = var_name,
|
||||
type = var_type,
|
||||
})
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return variables
|
||||
end
|
||||
|
||||
-- Get the type of a specific variable
|
||||
function M.get_variable_type(var_name, bufnr)
|
||||
local variables = M.extract_variables(bufnr)
|
||||
for _, var in ipairs(variables) do
|
||||
if var.name == var_name then
|
||||
return var.type
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Get all variables and their types as a map
|
||||
function M.get_variable_types(bufnr)
|
||||
local variables = M.extract_variables(bufnr)
|
||||
local types = {}
|
||||
for _, var in ipairs(variables) do
|
||||
types[var.name] = var.type
|
||||
end
|
||||
return types
|
||||
end
|
||||
|
||||
-- Get completion items based on context
|
||||
function M.get_completions(bufnr)
|
||||
local variables = M.extract_variables(bufnr)
|
||||
local in_var_section = is_in_var_section()
|
||||
|
||||
local items = {}
|
||||
for _, var in ipairs(variables) do
|
||||
if in_var_section then
|
||||
-- Inside VAR section: offer plain name
|
||||
table.insert(items, {
|
||||
label = var.name,
|
||||
kind = vim.lsp.protocol.CompletionItemKind.Variable,
|
||||
detail = var.type and (var.type .. " - Local variable") or "Local variable",
|
||||
})
|
||||
else
|
||||
-- Outside VAR section: offer with # prefix
|
||||
table.insert(items, {
|
||||
label = "#" .. var.name,
|
||||
insertText = "#" .. var.name,
|
||||
kind = vim.lsp.protocol.CompletionItemKind.Variable,
|
||||
detail = var.type and (var.type .. " - Local variable (prefixed)") or "Local variable (prefixed)",
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
return items
|
||||
end
|
||||
|
||||
-- Check if we should provide completions
|
||||
function M.should_complete()
|
||||
local ft = vim.bo.filetype
|
||||
if ft ~= "scl" then
|
||||
return false
|
||||
end
|
||||
|
||||
-- Check if treesitter parser is available
|
||||
local ok, parser = pcall(ts.get_parser, 0, "scl")
|
||||
return ok and parser ~= nil
|
||||
end
|
||||
|
||||
return M
|
||||
Reference in New Issue
Block a user