overlay.lua
1 local config = require("cellmode.config") 2 local cell_layout = require("cellmode.view.cell_layout") 3 4 local M = {} 5 6 local ns = vim.api.nvim_create_namespace("cellmode_overlay") 7 local hl_ready = false 8 local visibility = {} 9 10 local function setup_highlights() 11 if hl_ready then 12 return 13 end 14 hl_ready = true 15 vim.api.nvim_set_hl(0, "CellmodePadding", { default = true }) 16 local target = "Delimiter" 17 local target_hl = vim.api.nvim_get_hl(0, { name = target, link = false }) 18 local normal_hl = vim.api.nvim_get_hl(0, { name = "Normal", link = false }) 19 local fg = target_hl.fg or normal_hl.fg 20 vim.api.nvim_set_hl(0, "CellmodePipe", { default = true, fg = fg, nocombine = true }) 21 vim.api.nvim_set_hl(0, "CellmodeHbar", { default = true, underline = true, sp = fg, nocombine = true }) 22 vim.api.nvim_set_hl(0, "CellmodeContinuation", { default = true, link = "NonText" }) 23 vim.api.nvim_set_hl(0, "CellmodeSpecialChar", { default = true, link = "NonText" }) 24 end 25 26 function M.setup() 27 setup_highlights() 28 end 29 30 function M.namespace() 31 return ns 32 end 33 34 local function pipe_glyph() 35 return config.marks.pipe or "│" 36 end 37 38 local function continuation_glyph() 39 return config.marks.pipec or "┊" 40 end 41 42 local function place_inline(bufnr, row0, col0, chunks, opts) 43 opts = opts or {} 44 opts.virt_text = chunks 45 opts.virt_text_pos = "inline" 46 if opts.right_gravity == nil then 47 opts.right_gravity = false 48 end 49 vim.api.nvim_buf_set_extmark(bufnr, ns, row0, col0, opts) 50 end 51 52 local function place_conceal(bufnr, row0, col0, end_col0) 53 vim.api.nvim_buf_set_extmark(bufnr, ns, row0, col0, { 54 end_row = row0, 55 end_col = end_col0, 56 conceal = "", 57 }) 58 end 59 60 local function pad_str(width) 61 if width <= 0 then 62 return "" 63 end 64 return string.rep(" ", width) 65 end 66 67 local function place_hbar(bufnr, row0) 68 local line = vim.api.nvim_buf_get_lines(bufnr, row0, row0 + 1, false)[1] or "" 69 if #line == 0 then 70 return 71 end 72 vim.api.nvim_buf_set_extmark(bufnr, ns, row0, 0, { 73 end_row = row0, 74 end_col = #line, 75 hl_group = "CellmodeHbar", 76 }) 77 end 78 79 local function decorate_single_line_record(bufnr, layout, record) 80 local row0 = record.buf_row_start - 1 81 local fields = record.fields 82 local widths = layout.widths or {} 83 local pipe = pipe_glyph() 84 85 place_inline(bufnr, row0, 0, { { pipe, { "CellmodePipe", "CellmodeHbar" } } }) 86 place_hbar(bufnr, row0) 87 88 for icol = 1, #fields do 89 local field = fields[icol] 90 local width = widths[icol] or 0 91 local field_display = cell_layout.display_width(field.value) 92 local padding = pad_str(width - field_display) 93 local chunks = {} 94 if padding ~= "" then 95 chunks[#chunks + 1] = { padding, { "CellmodePadding", "CellmodeHbar" } } 96 end 97 chunks[#chunks + 1] = { pipe, { "CellmodePipe", "CellmodeHbar" } } 98 99 local end_col 100 if field.delim_col then 101 end_col = field.delim_col 102 else 103 local line = vim.api.nvim_buf_get_lines(bufnr, row0, row0 + 1, false)[1] or "" 104 end_col = #line + 1 105 end 106 place_inline(bufnr, row0, end_col - 1, chunks, { right_gravity = true }) 107 108 if field.delim_col then 109 place_conceal(bufnr, row0, field.delim_col - 1, field.delim_col) 110 end 111 112 if field.quoted then 113 place_conceal(bufnr, row0, field.byte_start_col - 1, field.byte_start_col) 114 place_conceal(bufnr, row0, field.byte_end_col - 1, field.byte_end_col) 115 end 116 end 117 end 118 119 local function decorate_multiline_record(bufnr, record) 120 local cont = continuation_glyph() 121 for row = record.buf_row_start, record.buf_row_end - 1 do 122 vim.api.nvim_buf_set_extmark(bufnr, ns, row - 1, 0, { 123 virt_text = { { cont, "CellmodeContinuation" } }, 124 virt_text_pos = "eol", 125 right_gravity = false, 126 }) 127 end 128 place_hbar(bufnr, record.buf_row_end - 1) 129 end 130 131 local function clear_lines(bufnr, row_start, row_end) 132 vim.api.nvim_buf_clear_namespace(bufnr, ns, row_start - 1, row_end) 133 end 134 135 function M.clear(bufnr) 136 vim.api.nvim_buf_clear_namespace(bufnr, ns, 0, -1) 137 end 138 139 function M.redraw(bufnr) 140 setup_highlights() 141 if not vim.api.nvim_buf_is_valid(bufnr) then 142 return 143 end 144 M.clear(bufnr) 145 if visibility[bufnr] == false then 146 return 147 end 148 local layout = cell_layout.get(bufnr) 149 if not layout then 150 return 151 end 152 local records = layout.records 153 for irec = 1, #records do 154 local record = records[irec] 155 if record.multiline then 156 decorate_multiline_record(bufnr, record) 157 else 158 decorate_single_line_record(bufnr, layout, record) 159 end 160 end 161 end 162 163 function M.redraw_range(bufnr, first_record, last_record, widths_changed) 164 setup_highlights() 165 if not vim.api.nvim_buf_is_valid(bufnr) then 166 return 167 end 168 if visibility[bufnr] == false then 169 return 170 end 171 local layout = cell_layout.get(bufnr) 172 if not layout then 173 return 174 end 175 if widths_changed then 176 M.redraw(bufnr) 177 return 178 end 179 local records = layout.records 180 if not first_record or first_record < 1 then 181 first_record = 1 182 end 183 if not last_record or last_record > #records then 184 last_record = #records 185 end 186 if first_record > last_record then 187 return 188 end 189 local row_start = records[first_record].buf_row_start 190 local row_end = records[last_record].buf_row_end 191 clear_lines(bufnr, row_start, row_end) 192 for irec = first_record, last_record do 193 local record = records[irec] 194 if record.multiline then 195 decorate_multiline_record(bufnr, record) 196 else 197 decorate_single_line_record(bufnr, layout, record) 198 end 199 end 200 end 201 202 function M.set_visible(bufnr, visible) 203 visibility[bufnr] = visible and true or false 204 if not visible then 205 M.clear(bufnr) 206 else 207 M.redraw(bufnr) 208 end 209 end 210 211 function M.is_visible(bufnr) 212 return visibility[bufnr] ~= false 213 end 214 215 function M.forget(bufnr) 216 visibility[bufnr] = nil 217 M.clear(bufnr) 218 end 219 220 function M.apply_window_options(winid) 221 if not vim.api.nvim_win_is_valid(winid) then 222 return 223 end 224 vim.wo[winid].conceallevel = 2 225 vim.wo[winid].concealcursor = "nvic" 226 end 227 228 return M