-- 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 -- Find the last line of the declaration section based on filetype local function get_declaration_end(lines, ft) if ft == "udt" then return #lines end for i, line in ipairs(lines) do if line:match("^%s*BEGIN%s*$") then return i - 1 end end return #lines end -- Collapse all {...} blocks in the declaration section of current buffer function M.collapse_all_attr_blocks() local bufnr = vim.api.nvim_get_current_buf() local ft = vim.bo[bufnr].filetype local lines = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) local count = 0 local decl_end = get_declaration_end(lines, ft) if not buffer_attr_state[bufnr] then buffer_attr_state[bufnr] = {} end for line_idx = 1, decl_end do local line = lines[line_idx] local pos = 1 while pos <= #line do local start_pos, end_pos = line:find("{(.-)}", pos) if not start_pos then break end local content = line:sub(start_pos + 1, end_pos - 1) if 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, } line = line:sub(1, start_pos) .. "..." .. line:sub(end_pos) count = count + 1 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