formatter: fix indent to match TIA Portal output
- Strip UTF-8 BOM so FUNCTION_BLOCK is correctly detected - Add no_indent=true on DECLARATIONS blocks so they don't inflate body indent - Fix is_case_label to match quoted strings (was using \s instead of %s) - Push CASE_CONTENT block with label so content inside case labels gets proper nesting (one extra level beyond the label) - All 38 formatter tests pass - LLT_EquipmentManager.scl now matches TIA Portal export exactly
This commit is contained in:
+65
-45
@@ -72,7 +72,6 @@ local NEEDS_SEMICOLON = {
|
|||||||
["END_WHILE"] = true,
|
["END_WHILE"] = true,
|
||||||
["END_REPEAT"] = true,
|
["END_REPEAT"] = true,
|
||||||
["END_CASE"] = true,
|
["END_CASE"] = true,
|
||||||
["END_REGION"] = true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
local VAR_KEYWORDS = {
|
local VAR_KEYWORDS = {
|
||||||
@@ -111,9 +110,6 @@ local CONTINUATION_OPERATORS = {
|
|||||||
--- @return table Array of TextEdit objects with range and newText
|
--- @return table Array of TextEdit objects with range and newText
|
||||||
function M.format_document(content, options)
|
function M.format_document(content, options)
|
||||||
options = options or {}
|
options = options or {}
|
||||||
local use_spaces = options.insertSpaces or false
|
|
||||||
local indent_size = options.tabSize or 1
|
|
||||||
local indent_char = use_spaces and string.rep(" ", indent_size) or "\t"
|
|
||||||
|
|
||||||
local collapse_attributes = options.collapseAttributes
|
local collapse_attributes = options.collapseAttributes
|
||||||
if collapse_attributes == nil then
|
if collapse_attributes == nil then
|
||||||
@@ -127,6 +123,11 @@ function M.format_document(content, options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local bom = string.char(239, 187, 191)
|
||||||
|
if content:sub(1, 3) == bom then
|
||||||
|
content = content:sub(4)
|
||||||
|
end
|
||||||
|
|
||||||
local lines = {}
|
local lines = {}
|
||||||
for line in content:gmatch("([^\n]*)\n") do
|
for line in content:gmatch("([^\n]*)\n") do
|
||||||
table.insert(lines, line)
|
table.insert(lines, line)
|
||||||
@@ -141,15 +142,29 @@ function M.format_document(content, options)
|
|||||||
|
|
||||||
local stack = {}
|
local stack = {}
|
||||||
local formatted_lines = {}
|
local formatted_lines = {}
|
||||||
|
local in_code_body = false
|
||||||
|
local in_var_block = false
|
||||||
|
|
||||||
local function get_indent()
|
local function get_indent()
|
||||||
local level = 0
|
if in_code_body then
|
||||||
for _, block in ipairs(stack) do
|
local level = 0
|
||||||
if not block.no_indent then
|
for _, block in ipairs(stack) do
|
||||||
level = level + 1
|
if not block.no_indent then
|
||||||
|
level = level + 1
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return "\t" .. string.rep(" ", level)
|
||||||
|
else
|
||||||
|
if in_var_block then
|
||||||
|
return " "
|
||||||
|
else
|
||||||
|
return ""
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return string.rep(indent_char, level)
|
end
|
||||||
|
|
||||||
|
local function get_code_indent(level)
|
||||||
|
return "\t" .. string.rep(" ", level)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function is_assignment_continuation(trimmed)
|
local function is_assignment_continuation(trimmed)
|
||||||
@@ -285,7 +300,7 @@ function M.format_document(content, options)
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function is_case_label(trimmed)
|
local function is_case_label(trimmed)
|
||||||
return trimmed:match("^[%w_#]+%s*:%s*$") ~= nil
|
return trimmed:match("^\"[^\"]+\"%s*:%s*$") ~= nil or trimmed:match("^[%w_#]+%s*:%s*$") ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
local function extract_variable_name(line)
|
local function extract_variable_name(line)
|
||||||
@@ -498,7 +513,7 @@ function M.format_document(content, options)
|
|||||||
if DECLARATIONS[kw] then
|
if DECLARATIONS[kw] then
|
||||||
stack = {}
|
stack = {}
|
||||||
table.insert(formatted_lines, trimmed)
|
table.insert(formatted_lines, trimmed)
|
||||||
table.insert(stack, { type = kw, indent = 0, is_declaration = true })
|
table.insert(stack, { type = kw, indent = 0, is_declaration = true, no_indent = true })
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -516,25 +531,38 @@ function M.format_document(content, options)
|
|||||||
while #stack > 0 and VAR_KEYWORDS[stack[#stack].type] do
|
while #stack > 0 and VAR_KEYWORDS[stack[#stack].type] do
|
||||||
table.remove(stack)
|
table.remove(stack)
|
||||||
end
|
end
|
||||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
in_var_block = false
|
||||||
|
in_code_body = true
|
||||||
|
table.insert(formatted_lines, trimmed)
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
if VAR_KEYWORDS[kw] then
|
if in_code_body then
|
||||||
while #stack > 0 and not stack[#stack].is_declaration do
|
if VAR_KEYWORDS[kw] then
|
||||||
table.remove(stack)
|
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||||
|
table.insert(stack, { type = kw, indent = #stack, is_var = true })
|
||||||
|
goto continue
|
||||||
end
|
end
|
||||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
|
||||||
table.insert(stack, { type = kw, indent = #stack, is_var = true })
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
|
|
||||||
if kw == "END_VAR" then
|
if kw == "END_VAR" then
|
||||||
while #stack > 0 and VAR_KEYWORDS[stack[#stack].type] do
|
while #stack > 0 and VAR_KEYWORDS[stack[#stack].type] do
|
||||||
table.remove(stack)
|
table.remove(stack)
|
||||||
|
end
|
||||||
|
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if VAR_KEYWORDS[kw] then
|
||||||
|
in_var_block = true
|
||||||
|
table.insert(formatted_lines, trimmed)
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
|
||||||
|
if kw == "END_VAR" then
|
||||||
|
in_var_block = false
|
||||||
|
table.insert(formatted_lines, trimmed)
|
||||||
|
goto continue
|
||||||
end
|
end
|
||||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
|
||||||
goto continue
|
|
||||||
end
|
end
|
||||||
|
|
||||||
if kw and BLOCKS[kw] == nil then
|
if kw and BLOCKS[kw] == nil then
|
||||||
@@ -565,7 +593,7 @@ function M.format_document(content, options)
|
|||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
then_indent = if_block and string.rep(indent_char, if_block.indent) or get_indent()
|
then_indent = if_block and get_code_indent(if_block.indent) or get_indent()
|
||||||
end
|
end
|
||||||
table.insert(formatted_lines, then_indent .. trimmed)
|
table.insert(formatted_lines, then_indent .. trimmed)
|
||||||
table.insert(stack, { type = "THEN_CONTENT", indent = #stack, no_indent = true })
|
table.insert(stack, { type = "THEN_CONTENT", indent = #stack, no_indent = true })
|
||||||
@@ -582,7 +610,7 @@ function M.format_document(content, options)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
if if_block then
|
if if_block then
|
||||||
formatted = string.rep(indent_char, if_block.indent) .. trimmed
|
formatted = get_code_indent(if_block.indent) .. trimmed
|
||||||
else
|
else
|
||||||
formatted = get_indent() .. trimmed
|
formatted = get_indent() .. trimmed
|
||||||
end
|
end
|
||||||
@@ -591,7 +619,7 @@ function M.format_document(content, options)
|
|||||||
-- Check if this is a multi-line condition (doesn't end with THEN on same line)
|
-- Check if this is a multi-line condition (doesn't end with THEN on same line)
|
||||||
if not trimmed:match("THEN%s*$") then
|
if not trimmed:match("THEN%s*$") then
|
||||||
in_condition = true
|
in_condition = true
|
||||||
condition_indent = string.rep(indent_char, (if_block and if_block.indent or 0) + 1)
|
condition_indent = get_code_indent((if_block and if_block.indent or 0) + 1)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
formatted = get_indent() .. trimmed
|
formatted = get_indent() .. trimmed
|
||||||
@@ -606,9 +634,10 @@ function M.format_document(content, options)
|
|||||||
while #stack > case_idx do
|
while #stack > case_idx do
|
||||||
table.remove(stack)
|
table.remove(stack)
|
||||||
end
|
end
|
||||||
local label_indent = string.rep(indent_char, case_block.indent + 1)
|
local label_indent = get_code_indent(case_block.indent + 1)
|
||||||
table.insert(formatted_lines, label_indent .. trimmed)
|
table.insert(formatted_lines, label_indent .. trimmed)
|
||||||
table.insert(stack, { type = "CASE_LABEL", indent = case_block.indent + 1, no_indent = true })
|
table.insert(stack, { type = "CASE_LABEL", indent = case_block.indent + 1, no_indent = true })
|
||||||
|
table.insert(stack, { type = "CASE_CONTENT", indent = case_block.indent + 2 })
|
||||||
else
|
else
|
||||||
table.insert(formatted_lines, get_indent() .. trimmed)
|
table.insert(formatted_lines, get_indent() .. trimmed)
|
||||||
end
|
end
|
||||||
@@ -622,7 +651,7 @@ function M.format_document(content, options)
|
|||||||
current_level = current_level + 1
|
current_level = current_level + 1
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
local formatted = string.rep(indent_char, current_level) .. trimmed
|
local formatted = get_code_indent(current_level) .. trimmed
|
||||||
table.insert(formatted_lines, formatted)
|
table.insert(formatted_lines, formatted)
|
||||||
table.insert(stack, { type = kw, indent = current_level })
|
table.insert(stack, { type = kw, indent = current_level })
|
||||||
if kw == "IF" or kw == "ELSIF" then
|
if kw == "IF" or kw == "ELSIF" then
|
||||||
@@ -630,33 +659,24 @@ function M.format_document(content, options)
|
|||||||
if not trimmed:match("THEN%s*$") then
|
if not trimmed:match("THEN%s*$") then
|
||||||
in_condition = true
|
in_condition = true
|
||||||
-- Continuation lines should be indented one level deeper than IF/ELSIF
|
-- Continuation lines should be indented one level deeper than IF/ELSIF
|
||||||
condition_indent = string.rep(indent_char, current_level + 1)
|
condition_indent = get_code_indent(current_level + 1)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
goto continue
|
goto continue
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Regular line - check for assignment start
|
-- Regular line - check for assignment start
|
||||||
-- But not if its inside an attribute block, exiting one, or a single-line attribute block
|
if in_code_body and not in_attr_block and not exiting_attr_block and trimmed:match(":=") and not trimmed:match("^{.*}$") then
|
||||||
if not in_attr_block and not exiting_attr_block and trimmed:match(":=") and not trimmed:match("^{.*}$") then
|
if trimmed:match(";%s*$") then
|
||||||
local base_indent = get_indent()
|
in_assignment = false
|
||||||
local assign_pos = trimmed:find(":=")
|
else
|
||||||
if assign_pos then
|
in_assignment = true
|
||||||
local var_part = trimmed:sub(1, assign_pos - 1)
|
assignment_continuation_indent = get_indent()
|
||||||
local var_trimmed = var_part:match("^(.-)%s*$")
|
|
||||||
assignment_continuation_indent = base_indent .. string.rep(" ", #var_trimmed + 4)
|
|
||||||
|
|
||||||
if trimmed:match(";%s*$") then
|
|
||||||
in_assignment = false
|
|
||||||
else
|
|
||||||
in_assignment = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
in_assignment = false
|
in_assignment = false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Also reset assignment state when in attribute block
|
|
||||||
if in_attr_block then
|
if in_attr_block then
|
||||||
in_assignment = false
|
in_assignment = false
|
||||||
end
|
end
|
||||||
|
|||||||
Reference in New Issue
Block a user