|
1 |
| -local ffi = require("ffi") |
2 | 1 | local nop = function() end
|
3 |
| - |
4 |
| ---- @class CsvView.Metrics.Field |
5 |
| ---- @field offset integer |
6 |
| ---- @field len integer |
7 |
| ---- @field display_width integer |
8 |
| ---- @field is_number boolean |
9 |
| ---- FFI struct definition for memory efficiency |
10 |
| -ffi.cdef([[ |
11 |
| - typedef struct { |
12 |
| - int32_t offset; |
13 |
| - int32_t len; |
14 |
| - int32_t display_width; |
15 |
| - bool is_number; |
16 |
| - } csvview_field_t; |
17 |
| -]]) |
18 |
| - |
19 |
| ------------------------------------------------------------------------------ |
20 |
| --- Row types |
21 |
| ------------------------------------------------------------------------------ |
22 |
| - |
23 |
| ---- @class CsvView.Metrics.RowMeta |
24 |
| -local CsvViewMetricsRow = {} |
25 |
| -CsvViewMetricsRow.__index = CsvViewMetricsRow |
26 |
| - |
27 |
| ---- @class CsvView.Metrics.CommentRow: CsvView.Metrics.RowMeta |
28 |
| ---- @field type "comment" |
29 |
| - |
30 |
| ---- @class CsvView.Metrics.MultilineStartRow: CsvView.Metrics.RowMeta |
31 |
| ---- @field type "multiline_start" |
32 |
| ---- @field _fields CsvView.Metrics.Field[] |
33 |
| ---- @field end_loffset integer -- relative end line offset |
34 |
| ---- @field terminated boolean -- whether the row is terminated, if false, parser reached lookahead limit |
35 |
| - |
36 |
| ---- |
37 |
| ---- @class CsvView.Metrics.MultilineContinuationRow: CsvView.Metrics.RowMeta |
38 |
| ---- @field type "multiline_continuation" |
39 |
| ---- |
40 |
| ---- |
41 |
| ---- @field _fields CsvView.Metrics.Field[] |
42 |
| ---- |
43 |
| ---- example: |
44 |
| ---- abc,def,"gh <--- type="multiline_start", end_loffset=4 |
45 |
| ---- i",jkl,"m <--- type="multiline_continuation", start_loffset=1, start_field_offset=1, skipped_ncol=2 |
46 |
| ---- n <--- type="multiline_continuation", start_loffset=2, start_field_offset=1, skipped_ncol=2 |
47 |
| ---- o <--- type="multiline_continuation", start_loffset=3, start_field_offset=2, skipped_ncol=4 |
48 |
| ---- p" <--- type="multiline_continuation", start_loffset=4, start_field_offset=3, skipped_ncol=4 |
49 |
| ---- @field start_loffset integer -- relative start line offset |
50 |
| ---- @field end_loffset integer -- relative end line offset |
51 |
| ---- @field start_field_offset integer -- relative start field offset |
52 |
| ---- @field skipped_ncol integer -- column number that was skipped in the continuation row |
53 |
| ---- @field terminated boolean -- whether the row is terminated, if false, parser reached lookahead limit |
54 |
| - |
55 |
| ---- @class CsvView.Metrics.SinglelineRow: CsvView.Metrics.RowMeta |
56 |
| ---- @field type "singleline" |
57 |
| ---- @field _fields CsvView.Metrics.Field[] -- empty for comment rows |
58 |
| - |
59 |
| ---- @alias CsvView.Metrics.Row |
60 |
| ---- | CsvView.Metrics.CommentRow |
61 |
| ---- | CsvView.Metrics.MultilineStartRow |
62 |
| ---- | CsvView.Metrics.MultilineContinuationRow |
63 |
| ---- | CsvView.Metrics.SinglelineRow |
| 2 | +local CsvViewMetricsRow = require("csvview.metrics_row") |
64 | 3 |
|
65 | 4 | -----------------------------------------------------------------------------
|
66 | 5 | -- Metrics class
|
@@ -275,56 +214,88 @@ local function construct_rows(lnum, is_comment, parsed_fields, parsed_endlnum, t
|
275 | 214 | end
|
276 | 215 |
|
277 | 216 | if parsed_endlnum == lnum then -- Single line row
|
278 |
| - local row = CsvViewMetricsRow.new_single_row({}) |
| 217 | + local row_fields = {} ---@type CsvView.Metrics.Field[] |
279 | 218 | for _, field in ipairs(parsed_fields) do
|
280 | 219 | local field_text = field.text
|
281 | 220 | assert(type(field_text) == "string")
|
282 | 221 |
|
283 | 222 | local width = vim.fn.strdisplaywidth(field_text)
|
284 |
| - row:append(field.start_pos - 1, #field.text, width, tonumber(field.text) ~= nil) |
| 223 | + table.insert(row_fields, { |
| 224 | + offset = field.start_pos - 1, |
| 225 | + len = #field_text, |
| 226 | + display_width = width, |
| 227 | + is_number = tonumber(field_text) ~= nil, |
| 228 | + }) |
285 | 229 | end
|
286 |
| - return { row } |
| 230 | + return { CsvViewMetricsRow.new_single_row(row_fields) } |
287 | 231 | end
|
288 | 232 |
|
289 | 233 | -- Multi-line row
|
290 |
| - local start_row = CsvViewMetricsRow.new_multiline_start_row({}, parsed_endlnum - lnum, terminated) |
291 |
| - local index = 1 |
292 |
| - local rows = { start_row } --- @type CsvView.Metrics.Row[] |
| 234 | + local total_rows = parsed_endlnum - lnum + 1 |
| 235 | + local row_fields = {} --- @type table<integer, CsvView.Metrics.Field[]> |
| 236 | + local row_skipped_ncol = {} --- @type table<integer, integer> |
| 237 | + |
| 238 | + -- Initialize field arrays for each row |
| 239 | + for i = 1, total_rows do |
| 240 | + row_fields[i] = {} |
| 241 | + row_skipped_ncol[i] = 0 |
| 242 | + end |
| 243 | + |
| 244 | + -- First pass: distribute fields to rows and calculate skipped columns |
| 245 | + local current_row_index = 1 |
293 | 246 | for field_index, field in ipairs(parsed_fields) do
|
294 | 247 | local field_text = field.text
|
295 | 248 |
|
296 | 249 | if type(field_text) == "table" then
|
297 | 250 | -- Multi-line field
|
298 |
| - local field_start_lnum = index |
299 | 251 | for i, text in ipairs(field_text) do
|
300 | 252 | -- first line starts at field.start_pos, others are 0
|
301 | 253 | local offset = i == 1 and field.start_pos - 1 or 0
|
302 | 254 | local width = vim.fn.strdisplaywidth(text)
|
303 |
| - rows[index]:append(offset, #text, width, false) |
| 255 | + table.insert(row_fields[current_row_index], { |
| 256 | + offset = offset, |
| 257 | + len = #text, |
| 258 | + display_width = width, |
| 259 | + is_number = false, |
| 260 | + }) |
| 261 | + |
| 262 | + -- Set skipped columns for continuation rows |
| 263 | + if i > 1 and row_skipped_ncol[current_row_index] == 0 then |
| 264 | + row_skipped_ncol[current_row_index] = field_index - 1 |
| 265 | + end |
304 | 266 |
|
305 |
| - -- Add next row |
| 267 | + -- Move to next row if not the last line of this field |
306 | 268 | if i ~= #field_text then
|
307 |
| - index = index + 1 |
308 |
| - rows[index] = CsvViewMetricsRow.new_multiline_continuation_row( |
309 |
| - {}, |
310 |
| - index - 1, -- relative start line offset |
311 |
| - parsed_endlnum - lnum - index + 1, -- relative end line offset |
312 |
| - index - field_start_lnum, -- relative start field offset |
313 |
| - field_index - 1, |
314 |
| - terminated |
315 |
| - ) |
| 269 | + current_row_index = current_row_index + 1 |
316 | 270 | end
|
317 | 271 | end
|
318 | 272 | else
|
319 | 273 | -- Single-line field
|
320 |
| - rows[index]:append( |
321 |
| - field.start_pos - 1, |
322 |
| - #field.text, |
323 |
| - vim.fn.strdisplaywidth(field_text), |
324 |
| - tonumber(field.text) ~= nil |
| 274 | + table.insert(row_fields[current_row_index], { |
| 275 | + offset = field.start_pos - 1, |
| 276 | + len = #field.text, |
| 277 | + display_width = vim.fn.strdisplaywidth(field_text), |
| 278 | + is_number = tonumber(field.text) ~= nil, |
| 279 | + }) |
| 280 | + end |
| 281 | + end |
| 282 | + |
| 283 | + -- Second pass: create rows with all fields initialized |
| 284 | + local rows = {} --- @type CsvView.Metrics.Row[] |
| 285 | + for i = 1, total_rows do |
| 286 | + if i == 1 then |
| 287 | + rows[i] = CsvViewMetricsRow.new_multiline_start_row(parsed_endlnum - lnum, terminated, row_fields[i]) |
| 288 | + else |
| 289 | + rows[i] = CsvViewMetricsRow.new_multiline_continuation_row( |
| 290 | + i - 1, -- relative start line offset |
| 291 | + parsed_endlnum - lnum - i + 1, -- relative end line offset |
| 292 | + row_skipped_ncol[i], |
| 293 | + terminated, |
| 294 | + row_fields[i] |
325 | 295 | )
|
326 | 296 | end
|
327 | 297 | end
|
| 298 | + |
328 | 299 | return rows
|
329 | 300 | end
|
330 | 301 |
|
@@ -685,125 +656,4 @@ end
|
685 | 656 | -- Row functions
|
686 | 657 | ----------------------------------------------------
|
687 | 658 |
|
688 |
| ---- Iterate over fields in the row |
689 |
| ----@param row CsvView.Metrics.Row |
690 |
| ----@return fun():integer?,CsvView.Metrics.Field? |
691 |
| -function CsvViewMetricsRow.iter(row) |
692 |
| - local i = 0 |
693 |
| - return function() |
694 |
| - if not row._fields then |
695 |
| - return nil, nil |
696 |
| - end |
697 |
| - |
698 |
| - i = i + 1 |
699 |
| - if i > #row._fields then |
700 |
| - return nil, nil |
701 |
| - end |
702 |
| - |
703 |
| - if row.type == "multiline_continuation" then |
704 |
| - return row.skipped_ncol + i, row._fields[i] |
705 |
| - else |
706 |
| - return i, row._fields[i] |
707 |
| - end |
708 |
| - end |
709 |
| -end |
710 |
| - |
711 |
| ---- Append field to the row |
712 |
| ----@param row CsvView.Metrics.Row |
713 |
| ----@param offset integer |
714 |
| ----@param len integer |
715 |
| ----@param display_width integer |
716 |
| ----@param is_number boolean |
717 |
| -function CsvViewMetricsRow.append(row, offset, len, display_width, is_number) |
718 |
| - ---@diagnostic disable-next-line: assign-type-mismatch |
719 |
| - local field = ffi.new("csvview_field_t") --- @type CsvView.Metrics.Field |
720 |
| - field.offset = offset |
721 |
| - field.len = len |
722 |
| - field.display_width = display_width |
723 |
| - field.is_number = is_number |
724 |
| - table.insert(row._fields, field) |
725 |
| -end |
726 |
| - |
727 |
| ---- Get field by column index |
728 |
| ---- @param row CsvView.Metrics.Row |
729 |
| ---- @param col_idx integer 1-indexed column index |
730 |
| ---- @return CsvView.Metrics.Field? |
731 |
| -function CsvViewMetricsRow.field(row, col_idx) |
732 |
| - if row.type == "comment" then |
733 |
| - return nil |
734 |
| - end |
735 |
| - |
736 |
| - if row.type == "multiline_continuation" then |
737 |
| - col_idx = col_idx - row.skipped_ncol |
738 |
| - end |
739 |
| - return row._fields[col_idx] |
740 |
| -end |
741 |
| - |
742 |
| ---- Get the number of fields in the row |
743 |
| ---- @param row CsvView.Metrics.Row |
744 |
| ---- @return integer |
745 |
| -function CsvViewMetricsRow.field_count(row) |
746 |
| - return row._fields and #row._fields or 0 |
747 |
| -end |
748 |
| - |
749 |
| ---- Create a new CommentRow instance |
750 |
| ---- @return CsvView.Metrics.CommentRow |
751 |
| -function CsvViewMetricsRow.new_comment_row() |
752 |
| - local obj = {} |
753 |
| - obj.type = "comment" |
754 |
| - return setmetatable(obj, CsvViewMetricsRow) --- @type CsvView.Metrics.CommentRow |
755 |
| -end |
756 |
| - |
757 |
| ---- Create a new SinglelineRow instance |
758 |
| ----@param fields CsvView.Metrics.Field[] fields in the row |
759 |
| ----@return CsvView.Metrics.SinglelineRow |
760 |
| -function CsvViewMetricsRow.new_single_row(fields) |
761 |
| - local obj = {} |
762 |
| - obj.type = "singleline" |
763 |
| - obj._fields = fields |
764 |
| - return setmetatable(obj, CsvViewMetricsRow) --- @type CsvView.Metrics.SinglelineRow |
765 |
| -end |
766 |
| - |
767 |
| ---- Create a new MultilineStartRow instance |
768 |
| ---- @param fields CsvView.Metrics.Field[] fields in the row |
769 |
| ---- @param end_loffset integer relative end line offset |
770 |
| ---- @param terminated boolean whether the row is terminated, if false, parser reached lookahead limit |
771 |
| ---- @return CsvView.Metrics.MultilineStartRow |
772 |
| -function CsvViewMetricsRow.new_multiline_start_row(fields, end_loffset, terminated) |
773 |
| - local obj = {} |
774 |
| - obj.type = "multiline_start" |
775 |
| - obj._fields = fields or {} |
776 |
| - obj.end_loffset = end_loffset |
777 |
| - obj.terminated = terminated |
778 |
| - |
779 |
| - return setmetatable(obj, CsvViewMetricsRow) --- @type CsvView.Metrics.MultilineStartRow |
780 |
| -end |
781 |
| - |
782 |
| ---- Create a new MultilineContinuationRow instance |
783 |
| ---- @param fields CsvView.Metrics.Field[] fields in the row |
784 |
| ---- @param start_loffset integer relative start line offset |
785 |
| ---- @param end_loffset integer relative end line offset |
786 |
| ---- @param start_field_offset integer relative start field offset |
787 |
| ---- @param skipped_ncol integer column number that was skipped in the continuation row |
788 |
| ---- @param terminated boolean whether the row is terminated, if false, parser reached lookahead limit |
789 |
| ---- @return CsvView.Metrics.MultilineContinuationRow |
790 |
| -function CsvViewMetricsRow.new_multiline_continuation_row( |
791 |
| - fields, |
792 |
| - start_loffset, |
793 |
| - end_loffset, |
794 |
| - start_field_offset, |
795 |
| - skipped_ncol, |
796 |
| - terminated |
797 |
| -) |
798 |
| - local obj = {} |
799 |
| - obj.type = "multiline_continuation" |
800 |
| - obj._fields = fields |
801 |
| - obj.start_loffset = start_loffset |
802 |
| - obj.end_loffset = end_loffset |
803 |
| - obj.start_field_offset = start_field_offset |
804 |
| - obj.skipped_ncol = skipped_ncol |
805 |
| - obj.terminated = terminated |
806 |
| - return setmetatable(obj, CsvViewMetricsRow) --- @type CsvView.Metrics.MultilineContinuationRow |
807 |
| -end |
808 |
| - |
809 | 659 | return CsvViewMetrics
|
0 commit comments