fix(formatter): Add comment re-indenting and FB call continuation support
- Re-indent comments to match current block level - Add FB/function call continuation detection and alignment - Support both #fbname and "FBName" syntax - Fix FB call detection to exclude assignment statements - Continuation lines get +1 indent level
This commit is contained in:
+114
-17
@@ -67,11 +67,21 @@ function M.format_document(content, options)
|
||||
local in_var_section = false
|
||||
local in_code_section = false
|
||||
local prev_line_keyword = nil
|
||||
|
||||
-- FB call continuation tracking
|
||||
local in_fb_call = false
|
||||
local fb_call_indent = 0
|
||||
local fb_name_length = 0
|
||||
|
||||
local function get_indent()
|
||||
return string.rep(indent_char, indent_level * indent_size)
|
||||
end
|
||||
|
||||
local function get_continuation_indent()
|
||||
-- Continuation is one additional indent level beyond current
|
||||
return string.rep(indent_char, (indent_level + 1) * indent_size)
|
||||
end
|
||||
|
||||
local function get_keyword(line)
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
local kw = trimmed:match("^(%w+)")
|
||||
@@ -118,63 +128,152 @@ function M.format_document(content, options)
|
||||
return trimmed:match("^%w+%s*:") ~= nil
|
||||
end
|
||||
|
||||
-- Check if line is an FB/function call start
|
||||
-- Check if line is an FB/function call start
|
||||
-- Matches: #fbname( or "FBName"( - but NOT if it has := before the (
|
||||
local function is_fb_call_start(line)
|
||||
local trimmed = line:gsub("^%s+", "")
|
||||
-- Must start with # or "
|
||||
local has_fb_syntax = trimmed:match("^#%w+") or trimmed:match('^"')
|
||||
if not has_fb_syntax then return false end
|
||||
|
||||
-- Check if there's a ( in the line
|
||||
local paren_pos = trimmed:find("%(")
|
||||
if not paren_pos then return false end -- No parentheses, not an FB call
|
||||
|
||||
-- Check if := appears BEFORE the ( (which would make it an assignment, not an FB call)
|
||||
local assign_pos = trimmed:find(":=")
|
||||
if assign_pos and assign_pos < paren_pos then
|
||||
return false -- Has := before (, it's an assignment
|
||||
end
|
||||
|
||||
-- Check if it has unclosed parentheses
|
||||
local open_count = 0
|
||||
local close_count = 0
|
||||
for i = 1, #trimmed do
|
||||
local c = trimmed:sub(i, i)
|
||||
if c == "(" then open_count = open_count + 1
|
||||
elseif c == ")" then close_count = close_count + 1
|
||||
end
|
||||
end
|
||||
return open_count > 0 and close_count < open_count
|
||||
end
|
||||
|
||||
-- Check if line is inside an FB call (has unclosed parentheses)
|
||||
local function is_fb_call_inside(line)
|
||||
local open_count = 0
|
||||
local close_count = 0
|
||||
for i = 1, #line do
|
||||
local c = line:sub(i, i)
|
||||
if c == "(" then open_count = open_count + 1
|
||||
elseif c == ")" then close_count = close_count + 1
|
||||
end
|
||||
end
|
||||
return open_count > close_count
|
||||
end
|
||||
|
||||
-- Check if line ends an FB call
|
||||
local function is_fb_call_end(line)
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
return trimmed:match(".*%)%s*;?%s*$") ~= nil and
|
||||
not trimmed:match(".*%([^)]*%:?[=]") -- not still inside parameters
|
||||
end
|
||||
|
||||
-- Check if line is a continuation (ends with ,)
|
||||
local function is_fb_call_continuation(line)
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
return trimmed:match(".*[,]%s*$") ~= nil
|
||||
end
|
||||
|
||||
for i, line in ipairs(lines) do
|
||||
local trimmed = line:gsub("^%s+", ""):gsub("%s+$", "")
|
||||
local original_indent = line:match("^%s*")
|
||||
|
||||
-- Extract full keyword (handle compound keywords like END_IF, END_FUNCTION_BLOCK)
|
||||
local kw = trimmed:match("^(%w+_%w+)") or trimmed:match("^(%w+)")
|
||||
|
||||
-- Preserve empty lines and comments as-is
|
||||
if trimmed == "" or trimmed:match("^//") or trimmed:match("^%(%*") or trimmed:match("^%*/") then
|
||||
-- Check for FB call start (before comment handling)
|
||||
-- Only detect if we're not already in an FB call
|
||||
if not in_fb_call and is_fb_call_start(trimmed) then
|
||||
-- Extract FB name length (without quotes)
|
||||
local fb_name = trimmed:match("^#?([%w_]+)") or trimmed:match('^"?([%w_]+)"?')
|
||||
if fb_name then
|
||||
fb_name_length = #fb_name
|
||||
in_fb_call = true
|
||||
fb_call_indent = indent_level
|
||||
end
|
||||
end
|
||||
|
||||
-- Handle comments - re-indent them
|
||||
if trimmed:match("^//") then
|
||||
-- Line comment
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
elseif trimmed:match("^%(%*") or trimmed:match("^%*%/") then
|
||||
-- Block comment at start of line
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
elseif trimmed == "" then
|
||||
-- Preserve empty lines as-is
|
||||
table.insert(formatted_lines, line)
|
||||
else
|
||||
-- Handle FB call continuation
|
||||
local formatted_line
|
||||
if in_fb_call then
|
||||
-- Check if this ends the FB call
|
||||
if not is_fb_call_inside(trimmed) then
|
||||
formatted_line = get_continuation_indent() .. trimmed
|
||||
in_fb_call = false
|
||||
-- Check if this is a continuation line
|
||||
elseif is_fb_call_continuation(trimmed) then
|
||||
formatted_line = get_continuation_indent() .. trimmed
|
||||
else
|
||||
formatted_line = get_indent() .. trimmed
|
||||
end
|
||||
else
|
||||
formatted_line = get_indent() .. trimmed
|
||||
end
|
||||
|
||||
-- Check for BEGIN keyword (starts code section)
|
||||
if kw == "BEGIN" then
|
||||
in_code_section = true
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
indent_level = 1
|
||||
-- Check for VAR section start
|
||||
elseif is_var_keyword(kw) then
|
||||
in_var_section = true
|
||||
in_code_section = false
|
||||
indent_level = 0
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
indent_level = 1
|
||||
-- Check for END_VAR
|
||||
elseif kw == "END_VAR" then
|
||||
in_var_section = false
|
||||
indent_level = 0
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
-- Check for block declaration enders
|
||||
elseif is_block_declaration_ender(kw) then
|
||||
indent_level = math.max(0, indent_level - 1)
|
||||
local formatted_line = get_indent() .. trimmed
|
||||
if needs_semicolon(trimmed) then
|
||||
formatted_line = formatted_line .. ";"
|
||||
end
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
-- Check for block declaration starters (FUNCTION, FUNCTION_BLOCK, etc.)
|
||||
elseif is_block_declaration(kw) then
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
indent_level = 0
|
||||
-- Check for block enders
|
||||
elseif is_block_ender(kw) then
|
||||
if kw == "END_IF" then
|
||||
if_base_level = nil
|
||||
end
|
||||
in_fb_call = false -- Reset FB call state
|
||||
indent_level = math.max(0, indent_level - 1)
|
||||
local formatted_line = get_indent() .. trimmed
|
||||
if needs_semicolon(trimmed) then
|
||||
formatted_line = formatted_line .. ";"
|
||||
end
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
-- Check for CASE label
|
||||
-- Check for CASE labels (like "1:")
|
||||
elseif is_case_label(trimmed) and prev_line_keyword == "CASE" then
|
||||
-- CASE label stays at same indent as CASE (already increased)
|
||||
local formatted_line = get_indent() .. trimmed
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
-- Increase for content after label
|
||||
indent_level = indent_level + 1
|
||||
-- Check for same-level keywords (THEN, ELSE, DO, OF on their own line)
|
||||
elseif same_level_keywords[kw] then
|
||||
@@ -182,7 +281,7 @@ function M.format_document(content, options)
|
||||
if (kw == "ELSIF" or kw == "ELSE") and if_base_level then
|
||||
indent_level = if_base_level
|
||||
end
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
-- After ELSE/ELSIF, increase indent for content
|
||||
if (kw == "ELSIF" or kw == "ELSE") and if_base_level then
|
||||
indent_level = indent_level + 1
|
||||
@@ -193,13 +292,11 @@ function M.format_document(content, options)
|
||||
if kw == "IF" then
|
||||
if_base_level = indent_level
|
||||
end
|
||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||
-- Always increase indent after block starters
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
indent_level = indent_level + 1
|
||||
-- Regular line
|
||||
else
|
||||
local formatted_line = get_indent() .. trimmed
|
||||
if needs_semicolon(trimmed) then
|
||||
if needs_semicolon(trimmed) and not in_fb_call then
|
||||
formatted_line = formatted_line .. ";"
|
||||
end
|
||||
table.insert(formatted_lines, formatted_line)
|
||||
|
||||
Reference in New Issue
Block a user