diff --git a/AGENTS.md b/AGENTS.md index 8e6c0d9..94cd132 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -30,7 +30,8 @@ scl_lsp/ │ ├── diagnostics.lua # Linter │ ├── formatter.lua # Document formatter │ ├── treesitter.lua # Tree-sitter integration -│ ├── plc_json.lua # External UDT loading +│ ├── plc_json.lua # External UDT/DB loading +│ ├── builtin_instructions.lua # TIA Portal built-in types/FBs/functions │ └── json.lua # JSON encoder/decoder ├── lua/scl/ # Neovim plugin (uses require) │ ├── init.lua # Main plugin setup @@ -155,12 +156,30 @@ if ok and module then end ``` +## Diagnostics + +The LSP validates data types and provides warnings for unknown types. It recognizes: +- **Elementary data types** - BOOL, BYTE, WORD, DWORD, LWORD, CHAR, WCHAR, STRING, WSTRING, INT, DINT, LINT, USINT, SINT, UINT, UDINT, ULINT, REAL, LREAL, TIME, DATE, TOD, TIME_OF_DAY, DATE_AND_TIME, DT, S5TIME, LTIME, DTL +- **Timer/counter types** - IEC_TIMER, TON_TIME, TOF_TIME, TONR_TIME, TP_TIME, CTU, CTD, CTUD +- **TIA Portal built-in instructions** - Functions and function blocks +- **Workspace types** - UDTs from `.udt` files and data blocks from `.db` files + ## Completion Triggers - `#` - Local variables (after BEGIN) - `.` - Member access (UDT/DB) - `"` - Global DB names - `(` - FB/Function parameters - ` ` (space) - General completion +- `:` - Type declaration (in VAR section) + +### Completion Types +The LSP provides auto-completion for: +- **Local variables** - Variables declared in VAR sections +- **UDT types** - User-defined types from `.udt` files +- **Global DBs** - Data blocks from `.db` files +- **Elementary types** - BOOL, INT, DINT, REAL, TIME, etc. +- **TIA Portal built-in FBs** - TON, TOF, TP, CTU, CTD, CTUD, R_TRIG, etc. +- **TIA Portal built-in functions** - ADD, SUB, MUL, DIV, SIN, COS, SQRT, etc. ## Formatter Options diff --git a/lua/scl/blink_cmp_source.lua b/lua/scl/blink_cmp_source.lua index 7e5f813..686a718 100644 --- a/lua/scl/blink_cmp_source.lua +++ b/lua/scl/blink_cmp_source.lua @@ -34,10 +34,31 @@ function M.is_available(context) local prev_char = col > 1 and line:sub(col - 1, col - 1) or "" -- Only trigger on our specific characters (including space for := assignments) - if char_at == "." or char_at == "(" or char_at == "#" or char_at == '"' or char_at == " " or - prev_char == "." or prev_char == "(" or prev_char == "#" or prev_char == '"' then + if char_at == "." or char_at == "(" or char_at == "#" or char_at == '"' or char_at == " " or char_at == ":" or + prev_char == "." or prev_char == "(" or prev_char == "#" or prev_char == '"' or prev_char == " " or prev_char == ":" then return true end + + -- Also trigger when typing a word character if we're in VAR section (type declaration) + if char_at:match("[%w_]") or prev_char:match("[%w_]") then + -- Check if we're in VAR section + local in_var = false + local lines = vim.api.nvim_buf_get_lines(context.bufnr, 0, row, false) + for i = 1, #lines do + local l = lines[i]:gsub("^%s+", ""):gsub("%s+$", "") + if l:match("^END_VAR") then + in_var = false + elseif l:match("^VAR%s*$") or l:match("^VAR$") or l:match("^VAR_INPUT") or l:match("^VAR_OUTPUT") or l:match("^VAR_IN_OUT") or l:match("^VAR_TEMP") then + in_var = true + end + end + + -- Also check current line - if it has ":" followed by space or word, we're declaring a type + local line_before = col > 1 and line:sub(1, col - 1) or "" + if in_var and (line_before:match(":%s*$") or line_before:match(":%s+$")) then + return true + end + end end return false @@ -779,10 +800,69 @@ function M:get_completions(context, callback) }) end - -- Add basic types - for _, t in ipairs({ "Bool", "Int", "DInt", "Real", "Word", "DWord", "Byte", "Time", "String", "SINT", "USINT", "UINT", "LREAL" }) do + -- Add basic types - expanded list + local basic_types = { + -- Elementary types + "BOOL", "BYTE", "WORD", "DWORD", "LWORD", + "CHAR", "WCHAR", "STRING", "WSTRING", + "INT", "DINT", "LINT", "USINT", "SINT", "UINT", "UDINT", "ULINT", + "REAL", "LREAL", + "TIME", "DATE", "TOD", "TIME_OF_DAY", "DATE_AND_TIME", "DT", "S5TIME", "LTIME", "DTL", + -- Timer and counter types + "IEC_TIMER", "TON_TIME", "TOF_TIME", "TONR_TIME", "TP_TIME", + "IEC_TON", "IEC_TOF", "IEC_TP", "IEC_COUNTER", + "CTU", "CTD", "CTUD", + } + for _, t in ipairs(basic_types) do table.insert(items, { label = t, kind = 19 }) end + + -- Add built-in function blocks + local builtin_fbs = { + -- Timers and counters + "TON", "TOF", "TP", "TONR", + "CTU", "CTD", "CTUD", + "IEC_TIMER", "IEC_TON", "IEC_TOF", "IEC_TP", + "IEC_COUNTER", "IEC_CTU", "IEC_CTD", "IEC_CTUD", + -- Bit logic + "R_TRIG", "F_TRIG", "SR", "RS", + -- Other + "SCHEDULE", "BPM", "LOG", "DATALOG", + } + for _, fb in ipairs(builtin_fbs) do + table.insert(items, { label = fb, kind = 6, detail = "Function block" }) + end + + -- Add built-in functions + local builtin_fcs = { + -- Math + "ADD", "SUB", "MUL", "DIV", "MOD", "ABS", "NEG", "SQR", "SQRT", + "EXP", "LN", "LOG", "SIN", "COS", "TAN", "ASIN", "ACOS", "ATAN", + "MIN", "MAX", "LIMIT", "CALCULATE", + -- Conversion + "CONVERT", "ROUND", "TRUNC", "CEIL", "FLOOR", "SCALE_X", "NORM_X", + "BCD_I", "I_BCD", "DI_BCD", "DI_R", "I_DI", "R_DI", "R_DB", + -- Move/String + "MOVE", "FILL", "SWAP", "PEEK", "POKE", + "LEN", "CONCAT", "LEFT", "RIGHT", "MID", "INSERT", "DELETE", "REPLACE", "FIND", + -- Bit logic + "AND", "OR", "XOR", "NOT", "SHL", "SHR", "ROL", "ROR", "DECO", "ENCO", + -- Time/Date + "T_CONV", "T_ADD", "T_SUB", "T_DIFF", "T_COMBINE", + "DT_DATE", "DT_TOD", "DATE_TO_DTL", "DTL_TO_DATE", "TOD_TO_DTL", "DTL_TO_TOD", + "TimeToTicks", "TicksToTime", + -- Error handling + "GET_ERROR", "GET_ERR_ID", "RESET_ERR", "GET_DIAG", "DPRINT", + -- Communication + "RDREC", "WRREC", "RALRM", + "TSEND", "TRCV", "TCON", "TDISCON", "TSEND_C", "TRCV_C", + "TUSEND", "TURCV", "TCONNECT", "TDISCONNECT", + "MODBUSPN", "MODBUSCP", + } + for _, fc in ipairs(builtin_fcs) do + table.insert(items, { label = fc, kind = 3, detail = "Function" }) + end + wrapped_callback({ items = items }) return end @@ -827,6 +907,43 @@ function M:get_completions(context, callback) for _, t in ipairs({ "Bool", "Int", "DInt", "Real", "Word", "DWord", "Byte", "Time", "String", "SINT", "USINT", "UINT", "LREAL" }) do table.insert(items, { label = t, kind = 19 }) end + -- Add more basic types + local more_types = { "BOOL", "DINT", "WORD", "DWORD", "LWORD", "CHAR", "WSTRING", "UDINT", "LINT", "ULINT", "USINT", "SINT", "DATE", "TOD", "DTL", "LTIME", "S5TIME", "TIME_OF_DAY", "DATE_AND_TIME" } + for _, t in ipairs(more_types) do + table.insert(items, { label = t, kind = 19 }) + end + -- Add built-in FBs + local builtin_fbs = { + "TON", "TOF", "TP", "TONR", + "CTU", "CTD", "CTUD", + "IEC_TIMER", "IEC_TON", "IEC_TOF", "IEC_TP", + "IEC_COUNTER", "IEC_CTU", "IEC_CTD", "IEC_CTUD", + "R_TRIG", "F_TRIG", "SR", "RS", + "SCHEDULE", "BPM", "LOG", "DATALOG", + } + for _, fb in ipairs(builtin_fbs) do + table.insert(items, { label = fb, kind = 6, detail = "Function block" }) + end + -- Add built-in functions + local builtin_fcs = { + "ADD", "SUB", "MUL", "DIV", "MOD", "ABS", "NEG", "SQR", "SQRT", + "EXP", "LN", "LOG", "SIN", "COS", "TAN", "ASIN", "ACOS", "ATAN", + "MIN", "MAX", "LIMIT", "CALCULATE", + "CONVERT", "ROUND", "TRUNC", "CEIL", "FLOOR", "SCALE_X", "NORM_X", + "BCD_I", "I_BCD", "DI_BCD", "DI_R", "I_DI", "R_DI", "R_DB", + "MOVE", "FILL", "SWAP", "PEEK", "POKE", + "LEN", "CONCAT", "LEFT", "RIGHT", "MID", "INSERT", "DELETE", "REPLACE", "FIND", + "AND", "OR", "XOR", "NOT", "SHL", "SHR", "ROL", "ROR", "DECO", "ENCO", + "T_CONV", "T_ADD", "T_SUB", "T_DIFF", "T_COMBINE", + "DT_DATE", "DT_TOD", "DATE_TO_DTL", "DTL_TO_DATE", "TOD_TO_DTL", "DTL_TO_TOD", + "GET_ERROR", "GET_ERR_ID", "RESET_ERR", "GET_DIAG", "DPRINT", + "RDREC", "WRREC", "RALRM", + "TSEND", "TRCV", "TCON", "TDISCON", "TSEND_C", "TRCV_C", + "TUSEND", "TURCV", "MODBUSPN", "MODBUSCP", + } + for _, fc in ipairs(builtin_fcs) do + table.insert(items, { label = fc, kind = 3, detail = "Function" }) + end wrapped_callback({ items = items }) return end