xlsxrb 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,767 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLsp
4
+ module Xlsxrb
5
+ # CompletionListener provides intelligent, context-aware method completions
6
+ # for all xlsxrb public block variables (StreamWriter, WorkbookBuilder, WorksheetProxy,
7
+ # StreamSheet, Elements::Workbook, Elements::Worksheet, Elements::Row, Elements::Cell,
8
+ # ChartBuilder, StyleBuilder).
9
+ #
10
+ # Note: This is an interim enhancer for Ruby LSP until native RBS block argument
11
+ # type propagation is fully supported upstream in Ruby LSP / language server tools.
12
+ class CompletionListener
13
+ STREAM_WRITER_ITEMS = [
14
+ {
15
+ label: "sheet",
16
+ detail: "(name = nil, **opts) { |sheet| ... } -> void",
17
+ documentation: "Adds a new worksheet to the streaming XLSX writer and yields a `WorksheetProxy`.\n\n```ruby\nwb.sheet(\"Sales\") do |s|\n s.row([\"Product\", \"Price\"], styles: :bold)\nend\n```"
18
+ },
19
+ {
20
+ label: "style",
21
+ detail: "(name, **options) -> Symbol",
22
+ documentation: "Registers a reusable named cell style (font, fill, border, alignment, number_format).\n\n```ruby\nwb.style(:bold_blue, font: { bold: true, color: \"0000FF\" })\n```"
23
+ },
24
+ {
25
+ label: "workbook_property",
26
+ detail: "(name, value) -> void",
27
+ documentation: "Sets a workbook-level property (e.g. `:update_links`).\n\n```ruby\nwb.workbook_property(:update_links, \"never\")\n```"
28
+ },
29
+ {
30
+ label: "protect_workbook",
31
+ detail: "(**opts) -> void",
32
+ documentation: "Configures workbook protection options (e.g. `structure: true`, `windows: true`)."
33
+ },
34
+ {
35
+ label: "defined_name",
36
+ detail: "(name, value, sheet: nil, hidden: false) -> void",
37
+ documentation: "Adds a workbook-level defined named range or formula expression."
38
+ },
39
+ {
40
+ label: "print_area",
41
+ detail: "(range, sheet: nil) -> void",
42
+ documentation: "Sets the print area range for a sheet (e.g. `\"A1:F50\"`)."
43
+ },
44
+ {
45
+ label: "print_titles",
46
+ detail: "(rows: nil, cols: nil, sheet: nil) -> void",
47
+ documentation: "Sets repeating title rows or columns for printing (e.g. `rows: \"1:2\"`)."
48
+ },
49
+ {
50
+ label: "properties",
51
+ detail: "(core: nil, app: nil) -> void",
52
+ documentation: "Sets multiple core and application metadata properties at once."
53
+ },
54
+ {
55
+ label: "core_property",
56
+ detail: "(name, value) -> void",
57
+ documentation: "Sets core document metadata (e.g. `:title`, `:creator`, `:subject`, `:keywords`)."
58
+ },
59
+ {
60
+ label: "app_property",
61
+ detail: "(name, value) -> void",
62
+ documentation: "Sets application document metadata (e.g. `:company`, `:manager`)."
63
+ },
64
+ {
65
+ label: "custom_property",
66
+ detail: "(name, value, type: :string) -> void",
67
+ documentation: "Adds a custom document property (type: `:string`, `:number`, `:bool`, `:date`)."
68
+ },
69
+ {
70
+ label: "close",
71
+ detail: "() -> void",
72
+ documentation: "Finalizes the streaming writer and writes the completed XLSX archive."
73
+ }
74
+ ].freeze
75
+
76
+ WORKBOOK_BUILDER_ITEMS = [
77
+ {
78
+ label: "sheet",
79
+ detail: "(name = nil, **opts) { |sheet_builder| ... } -> WorksheetBuilder",
80
+ documentation: "Adds a new worksheet to the workbook builder and yields a `WorksheetBuilder`.\n\n```ruby\nwb.sheet(\"Summary\") do |s|\n s.row([\"Item\", \"Total\"])\nend\n```"
81
+ },
82
+ {
83
+ label: "style",
84
+ detail: "(name, **opts) { |style_builder| ... } -> Symbol",
85
+ documentation: "Defines a named style in the workbook builder."
86
+ },
87
+ {
88
+ label: "build",
89
+ detail: "() -> Elements::Workbook",
90
+ documentation: "Builds and returns the immutable `Elements::Workbook` DOM hierarchy."
91
+ },
92
+ {
93
+ label: "workbook_property",
94
+ detail: "(name, value) -> void",
95
+ documentation: "Sets a workbook property."
96
+ },
97
+ {
98
+ label: "protect_workbook",
99
+ detail: "(**opts) -> void",
100
+ documentation: "Sets workbook protection options."
101
+ },
102
+ {
103
+ label: "defined_name",
104
+ detail: "(name, value, sheet: nil, hidden: false) -> void",
105
+ documentation: "Adds a defined name or formula to the workbook."
106
+ },
107
+ {
108
+ label: "print_area",
109
+ detail: "(range, sheet: nil) -> void",
110
+ documentation: "Sets the print area for a sheet."
111
+ },
112
+ {
113
+ label: "print_titles",
114
+ detail: "(rows: nil, cols: nil, sheet: nil) -> void",
115
+ documentation: "Sets repeating print titles."
116
+ },
117
+ {
118
+ label: "properties",
119
+ detail: "(core: nil, app: nil) -> void",
120
+ documentation: "Sets multiple metadata properties."
121
+ },
122
+ {
123
+ label: "core_property",
124
+ detail: "(name, value) -> void",
125
+ documentation: "Sets core document metadata."
126
+ },
127
+ {
128
+ label: "app_property",
129
+ detail: "(name, value) -> void",
130
+ documentation: "Sets application document metadata."
131
+ },
132
+ {
133
+ label: "custom_property",
134
+ detail: "(name, value, type: :string) -> void",
135
+ documentation: "Adds a custom document metadata property."
136
+ }
137
+ ].freeze
138
+
139
+ WORKSHEET_PROXY_ITEMS = [
140
+ {
141
+ label: "row",
142
+ detail: "(values, styles: nil, height: nil, hidden: false, outline_level: nil) -> void",
143
+ documentation: "Writes a single row of cells with optional style name, height, and outline level.\n\n```ruby\ns.row([\"Product\", \"Price\"], styles: :bold)\ns.row([\"Laptop\", 1200])\n```"
144
+ },
145
+ {
146
+ label: "column",
147
+ detail: "(col_index, width: nil, hidden: false, outline_level: nil, custom_width: false) -> void",
148
+ documentation: "Configures column formatting (width in characters, visibility, grouping)."
149
+ },
150
+ {
151
+ label: "merge",
152
+ detail: "(range = nil, row: nil, col_start: nil, col_end: nil) -> void",
153
+ documentation: "Merges a range of cells.\n\n```ruby\ns.merge(\"A1:C1\")\ns.merge(row: 0, col_start: 0, col_end: 2)\n```"
154
+ },
155
+ {
156
+ label: "freeze_pane",
157
+ detail: "(row: 0, col: 0) -> void",
158
+ documentation: "Freezes rows and columns from scrolling (0-based indices).\n\n```ruby\ns.freeze_pane(row: 1, col: 0) # Freeze top header row\n```"
159
+ },
160
+ {
161
+ label: "auto_filter",
162
+ detail: "(range) -> void",
163
+ documentation: "Enables Excel auto-filter dropdown arrows over a cell range (e.g. `\"A1:D100\"`)."
164
+ },
165
+ {
166
+ label: "hyperlink",
167
+ detail: "(cell, url = nil, display: nil, tooltip: nil, location: nil) -> void",
168
+ documentation: "Adds a clickable hyperlink to a cell reference.\n\n```ruby\ns.hyperlink(\"A1\", \"https://example.com\", display: \"Example\")\n```"
169
+ },
170
+ {
171
+ label: "chart",
172
+ detail: "(type = nil, **opts) { |chart| ... } -> void",
173
+ documentation: "Adds a chart (bar, column, line, pie, scatter, area, doughnut, radar) to the worksheet."
174
+ },
175
+ {
176
+ label: "table",
177
+ detail: "(range, name: \"Table1\", **opts) -> void",
178
+ documentation: "Adds a formatted Excel Table to the range."
179
+ },
180
+ {
181
+ label: "conditional_format",
182
+ detail: "(range, type: \"cellIs\", **opts) -> void",
183
+ documentation: "Adds conditional formatting rules (cellIs, colorScale, dataBar, expression)."
184
+ },
185
+ {
186
+ label: "validate_data",
187
+ detail: "(range, type: \"list\", **opts) -> void",
188
+ documentation: "Adds data validation rules (dropdown list, whole number, decimal, date range, custom formula)."
189
+ },
190
+ {
191
+ label: "image",
192
+ detail: "(file_data, ext: \"png\", from_col: 0, from_row: 0, to_col: 5, to_row: 10, **opts) -> void",
193
+ documentation: "Inserts an image into the worksheet."
194
+ },
195
+ {
196
+ label: "comment",
197
+ detail: "(cell, text, author: nil) -> void",
198
+ documentation: "Adds an author comment / note to a cell."
199
+ },
200
+ {
201
+ label: "sparkline_group",
202
+ detail: "(range:, type: \"line\", sparklines:, **opts) -> void",
203
+ documentation: "Adds in-cell sparklines (line, column, win_loss)."
204
+ },
205
+ {
206
+ label: "pivot_table",
207
+ detail: "(range, name:, row_fields:, data_fields:, **opts) -> void",
208
+ documentation: "Creates a native Excel Pivot Table from worksheet data."
209
+ },
210
+ {
211
+ label: "shape",
212
+ detail: "(preset: \"rect\", text: nil, from_col: 0, from_row: 0, to_col: 5, to_row: 5, **opts) -> void",
213
+ documentation: "Adds a drawing shape to the sheet."
214
+ },
215
+ {
216
+ label: "page_setup",
217
+ detail: "(orientation: \"portrait\", paper_size: 9, **opts) -> void",
218
+ documentation: "Configures page orientation (portrait/landscape), paper size (e.g. 9 for A4), and scaling."
219
+ },
220
+ {
221
+ label: "page_margins",
222
+ detail: "(left: nil, right: nil, top: nil, bottom: nil, header: nil, footer: nil) -> void",
223
+ documentation: "Configures print page margins in inches."
224
+ },
225
+ {
226
+ label: "header_footer",
227
+ detail: "(**opts) -> void",
228
+ documentation: "Configures print header and footer text."
229
+ },
230
+ {
231
+ label: "print_options",
232
+ detail: "(name, value) -> void",
233
+ documentation: "Configures print options (e.g. `s.print_options(:grid_lines, true)`)."
234
+ },
235
+ {
236
+ label: "protect_sheet",
237
+ detail: "(**opts) -> void",
238
+ documentation: "Protects worksheet with optional password hashing and capability flags."
239
+ },
240
+ {
241
+ label: "sheet_view",
242
+ detail: "(name, value) -> void",
243
+ documentation: "Sets view settings (e.g. `s.sheet_view(:show_grid_lines, false)`)."
244
+ },
245
+ {
246
+ label: "sheet_properties",
247
+ detail: "(name, value) -> void",
248
+ documentation: "Sets sheet properties (e.g. `s.sheet_properties(:tab_color, \"FF0000\")`)."
249
+ },
250
+ {
251
+ label: "select_cell",
252
+ detail: "(active_cell, sqref: nil, pane: nil) -> void",
253
+ documentation: "Sets the active / selected cell when opening the worksheet in Excel."
254
+ },
255
+ {
256
+ label: "split_pane",
257
+ detail: "(x_split: 0, y_split: 0, top_left_cell: nil) -> void",
258
+ documentation: "Splits worksheet view into independent scrollable panes."
259
+ },
260
+ {
261
+ label: "filter_column",
262
+ detail: "(col_id, filter_values) -> void",
263
+ documentation: "Sets filter criteria for a column within the auto-filter range."
264
+ },
265
+ {
266
+ label: "sort_state",
267
+ detail: "(ref, sort_conditions, **opts) -> void",
268
+ documentation: "Configures sort state conditions on a cell range."
269
+ },
270
+ {
271
+ label: "page_break_row",
272
+ detail: "(row_num) -> void",
273
+ documentation: "Adds a horizontal page break before the given row index."
274
+ },
275
+ {
276
+ label: "page_break_col",
277
+ detail: "(col_index) -> void",
278
+ documentation: "Adds a vertical page break before the given column index."
279
+ }
280
+ ].freeze
281
+
282
+ STREAM_SHEET_ITEMS = [
283
+ {
284
+ label: "each_row",
285
+ detail: "() { |row| ... } -> void",
286
+ documentation: "Yields each row in the streaming sheet as an `Elements::Row`.\n\n```ruby\nsheet.each_row do |row|\n puts row.to_a.inspect\nend\n```"
287
+ },
288
+ {
289
+ label: "each",
290
+ detail: "() { |row| ... } -> void",
291
+ documentation: "Yields each row in the streaming sheet."
292
+ },
293
+ {
294
+ label: "name",
295
+ detail: "() -> String",
296
+ documentation: "Returns the worksheet name."
297
+ }
298
+ ].freeze
299
+
300
+ WORKBOOK_ELEMENT_ITEMS = [
301
+ {
302
+ label: "sheet",
303
+ detail: "(index_or_name) -> Elements::Worksheet?",
304
+ documentation: "Returns the worksheet by 0-based index or name.\n\n```ruby\nsheet = workbook.sheet(0)\n```"
305
+ },
306
+ {
307
+ label: "[]",
308
+ detail: "(index_or_name) -> Elements::Worksheet?",
309
+ documentation: "Returns the worksheet by 0-based index or name.\n\n```ruby\nsheet = workbook[\"Sheet1\"]\n```"
310
+ },
311
+ {
312
+ label: "update_sheet",
313
+ detail: "(name_or_index, sheet) -> Elements::Workbook",
314
+ documentation: "Replaces or updates a sheet and returns a new `Elements::Workbook`."
315
+ },
316
+ {
317
+ label: "sheet_names",
318
+ detail: "() -> Array<String>",
319
+ documentation: "Returns the list of all sheet names in the workbook."
320
+ },
321
+ {
322
+ label: "each",
323
+ detail: "() { |sheet| ... } -> void",
324
+ documentation: "Yields each `Elements::Worksheet` in the workbook.\n\n```ruby\nworkbook.each do |sheet|\n puts sheet.name\nend\n```"
325
+ },
326
+ {
327
+ label: "save",
328
+ detail: "(target_path_or_io) -> void",
329
+ documentation: "Writes the workbook DOM to a target XLSX file or IO stream."
330
+ },
331
+ {
332
+ label: "valid?",
333
+ detail: "() -> Boolean",
334
+ documentation: "Returns true if all workbook elements and sheets are valid."
335
+ },
336
+ {
337
+ label: "validate",
338
+ detail: "() -> Array<String>",
339
+ documentation: "Validates workbook structure and returns any validation errors."
340
+ }
341
+ ].freeze
342
+
343
+ WORKSHEET_ELEMENT_ITEMS = [
344
+ {
345
+ label: "[]",
346
+ detail: "(cell_ref) -> Elements::Cell?",
347
+ documentation: "Finds and returns a cell by A1 reference.\n\n```ruby\ncell = sheet[\"B2\"]\n```"
348
+ },
349
+ {
350
+ label: "cell_value",
351
+ detail: "(cell_ref) -> Object?",
352
+ documentation: "Returns the raw value of the cell at the given A1 reference."
353
+ },
354
+ {
355
+ label: "each_row",
356
+ detail: "() { |row| ... } -> void",
357
+ documentation: "Yields each row in the worksheet as an `Elements::Row`.\n\n```ruby\nsheet.each_row do |row|\n puts row.to_a.inspect\nend\n```"
358
+ },
359
+ {
360
+ label: "each",
361
+ detail: "() { |row| ... } -> void",
362
+ documentation: "Yields each row in the worksheet as an `Elements::Row`."
363
+ },
364
+ {
365
+ label: "each_cell",
366
+ detail: "() { |cell| ... } -> void",
367
+ documentation: "Yields every `Elements::Cell` across all rows."
368
+ },
369
+ {
370
+ label: "update_cell",
371
+ detail: "(ref, new_value) -> Elements::Worksheet",
372
+ documentation: "Returns a new worksheet with the updated cell value."
373
+ },
374
+ {
375
+ label: "row_at",
376
+ detail: "(row_index) -> Elements::Row?",
377
+ documentation: "Returns the row at the 0-based row index."
378
+ },
379
+ {
380
+ label: "first_row",
381
+ detail: "() -> Elements::Row?",
382
+ documentation: "Returns the first row in the sheet."
383
+ },
384
+ {
385
+ label: "last_row",
386
+ detail: "() -> Elements::Row?",
387
+ documentation: "Returns the last row in the sheet."
388
+ },
389
+ {
390
+ label: "cells",
391
+ detail: "() -> Array<Elements::Cell>",
392
+ documentation: "Returns a flat array of all cells in the sheet."
393
+ },
394
+ {
395
+ label: "cells_hash",
396
+ detail: "() -> Hash<String, Elements::Cell>",
397
+ documentation: "Returns a hash mapping A1 references to `Elements::Cell` objects."
398
+ },
399
+ {
400
+ label: "name",
401
+ detail: "() -> String",
402
+ documentation: "Returns the worksheet name."
403
+ },
404
+ {
405
+ label: "valid?",
406
+ detail: "() -> Boolean",
407
+ documentation: "Returns true if all cells and rows in the worksheet are valid."
408
+ },
409
+ {
410
+ label: "validate",
411
+ detail: "() -> Array<String>",
412
+ documentation: "Returns any validation errors found in the worksheet."
413
+ }
414
+ ].freeze
415
+
416
+ ROW_ELEMENT_ITEMS = [
417
+ {
418
+ label: "[]",
419
+ detail: "(col_index_or_symbol) -> Elements::Cell? | Object?",
420
+ documentation: "Returns the cell at 0-based column index, or access property symbol (`:cells`, `:index`, `:height`).\n\n```ruby\ncell = row[0]\n```"
421
+ },
422
+ {
423
+ label: "to_a",
424
+ detail: "() -> Array<Object?>",
425
+ documentation: "Returns an array containing the values of all cells in this row.\n\n```ruby\nvalues = row.to_a\n```"
426
+ },
427
+ {
428
+ label: "values",
429
+ detail: "() -> Array<Object?>",
430
+ documentation: "Returns an array of cell values in this row."
431
+ },
432
+ {
433
+ label: "cell_at",
434
+ detail: "(col_index) -> Elements::Cell?",
435
+ documentation: "Returns the cell at the 0-based column index."
436
+ },
437
+ {
438
+ label: "each",
439
+ detail: "() { |cell| ... } -> void",
440
+ documentation: "Yields each `Elements::Cell` in this row."
441
+ },
442
+ {
443
+ label: "each_cell",
444
+ detail: "() { |cell| ... } -> void",
445
+ documentation: "Yields each `Elements::Cell` in this row."
446
+ },
447
+ {
448
+ label: "cells",
449
+ detail: "() -> Array<Elements::Cell>",
450
+ documentation: "Returns the list of cells in this row."
451
+ },
452
+ {
453
+ label: "index",
454
+ detail: "() -> Integer",
455
+ documentation: "Returns the 0-based row index."
456
+ },
457
+ {
458
+ label: "height",
459
+ detail: "() -> Float?",
460
+ documentation: "Returns custom row height in points, or nil."
461
+ },
462
+ {
463
+ label: "valid?",
464
+ detail: "() -> Boolean",
465
+ documentation: "Returns true if all cells in this row are valid."
466
+ },
467
+ {
468
+ label: "validate",
469
+ detail: "() -> Array<String>",
470
+ documentation: "Validates all cells in this row and returns error messages."
471
+ }
472
+ ].freeze
473
+
474
+ CELL_ELEMENT_ITEMS = [
475
+ {
476
+ label: "value",
477
+ detail: "() -> Object?",
478
+ documentation: "Returns the cell's typed value (String, Integer, Float, Date, Time, nil)."
479
+ },
480
+ {
481
+ label: "ref",
482
+ detail: "() -> String",
483
+ documentation: "Returns the cell's A1 reference (e.g. `\"B2\"`)."
484
+ },
485
+ {
486
+ label: "to_s",
487
+ detail: "() -> String",
488
+ documentation: "Returns string representation of cell value."
489
+ },
490
+ {
491
+ label: "to_i",
492
+ detail: "() -> Integer",
493
+ documentation: "Converts cell value to Integer."
494
+ },
495
+ {
496
+ label: "to_f",
497
+ detail: "() -> Float",
498
+ documentation: "Converts cell value to Float."
499
+ },
500
+ {
501
+ label: "to_date",
502
+ detail: "() -> Date?",
503
+ documentation: "Converts cell value to Date."
504
+ },
505
+ {
506
+ label: "to_time",
507
+ detail: "() -> Time?",
508
+ documentation: "Converts cell value to Time."
509
+ },
510
+ {
511
+ label: "column_letter",
512
+ detail: "() -> String",
513
+ documentation: "Returns the column letters (e.g. `\"A\"`, `\"AA\"`)."
514
+ },
515
+ {
516
+ label: "row_index",
517
+ detail: "() -> Integer",
518
+ documentation: "Returns the 0-based row index."
519
+ },
520
+ {
521
+ label: "column_index",
522
+ detail: "() -> Integer",
523
+ documentation: "Returns the 0-based column index."
524
+ },
525
+ {
526
+ label: "style_index",
527
+ detail: "() -> Integer?",
528
+ documentation: "Returns the 0-based index into stylesheet formatting."
529
+ },
530
+ {
531
+ label: "content",
532
+ detail: "() -> String",
533
+ documentation: "Returns string content of cell value."
534
+ },
535
+ {
536
+ label: "[]",
537
+ detail: "(symbol) -> Object?",
538
+ documentation: "Hash-style access to cell attributes (`:value`, `:ref`, `:style_index`)."
539
+ },
540
+ {
541
+ label: "valid?",
542
+ detail: "() -> Boolean",
543
+ documentation: "Returns true if row_index, column_index, and value are valid."
544
+ },
545
+ {
546
+ label: "validate",
547
+ detail: "() -> Array<String>",
548
+ documentation: "Returns validation errors if indices are negative or invalid."
549
+ }
550
+ ].freeze
551
+
552
+ CHART_BUILDER_ITEMS = [
553
+ {
554
+ label: "title",
555
+ detail: "(text) -> void",
556
+ documentation: "Sets the chart title text."
557
+ },
558
+ {
559
+ label: "series",
560
+ detail: "(range, name: nil) -> void",
561
+ documentation: "Adds data series value range."
562
+ },
563
+ {
564
+ label: "categories",
565
+ detail: "(range) -> void",
566
+ documentation: "Sets category label range (e.g. `'Sheet1!$A$2:$A$10'`)."
567
+ },
568
+ {
569
+ label: "legend",
570
+ detail: "(position: 'r') -> void",
571
+ documentation: "Configures legend position (`'r'`, `'l'`, `'t'`, `'b'`, `'none'`)."
572
+ },
573
+ {
574
+ label: "plot_by",
575
+ detail: "(grouping) -> void",
576
+ documentation: "Sets series grouping (`'standard'`, `'stacked'`, `'percentStacked'`)."
577
+ },
578
+ {
579
+ label: "dimension",
580
+ detail: "(from_col: 0, from_row: 0, to_col: 8, to_row: 15) -> void",
581
+ documentation: "Sets chart position and size on the worksheet."
582
+ },
583
+ {
584
+ label: "gridlines",
585
+ detail: "(horizontal: true, vertical: false) -> void",
586
+ documentation: "Configures chart major gridlines."
587
+ },
588
+ {
589
+ label: "style",
590
+ detail: "(id) -> void",
591
+ documentation: "Sets built-in chart style number (1-48)."
592
+ }
593
+ ].freeze
594
+
595
+ STYLE_BUILDER_ITEMS = [
596
+ {
597
+ label: "font",
598
+ detail: "(name: 'Calibri', size: 11, bold: false, italic: false, underline: false, color: nil) -> void",
599
+ documentation: "Sets font typography, size, weight, and color."
600
+ },
601
+ {
602
+ label: "fill",
603
+ detail: "(pattern: 'solid', fg_color: nil, bg_color: nil) -> void",
604
+ documentation: "Sets background fill pattern and foreground color."
605
+ },
606
+ {
607
+ label: "border",
608
+ detail: "(left: nil, right: nil, top: nil, bottom: nil) -> void",
609
+ documentation: "Sets cell border lines and colors."
610
+ },
611
+ {
612
+ label: "alignment",
613
+ detail: "(horizontal: nil, vertical: nil, wrap_text: false, rotation: 0) -> void",
614
+ documentation: "Sets cell text alignment and text wrap."
615
+ },
616
+ {
617
+ label: "number_format",
618
+ detail: "(format_code_or_id) -> void",
619
+ documentation: "Sets custom number or date format code (e.g. `'#,##0.00'`, `'yyyy-mm-dd'`)."
620
+ }
621
+ ].freeze
622
+
623
+ CLASS_NAMES = {
624
+ stream_writer: "Xlsxrb::StreamWriter",
625
+ workbook_builder: "Xlsxrb::WorkbookBuilder",
626
+ worksheet_proxy: "Xlsxrb::StreamWriter::WorksheetProxy",
627
+ stream_sheet: "Xlsxrb::StreamSheet",
628
+ workbook: "Xlsxrb::Elements::Workbook",
629
+ worksheet: "Xlsxrb::Elements::Worksheet",
630
+ row: "Xlsxrb::Elements::Row",
631
+ cell: "Xlsxrb::Elements::Cell",
632
+ chart_builder: "Xlsxrb::ChartBuilder",
633
+ style_builder: "Xlsxrb::StyleBuilder"
634
+ }.freeze
635
+
636
+ ITEMS_BY_TYPE = {
637
+ stream_writer: STREAM_WRITER_ITEMS,
638
+ workbook_builder: WORKBOOK_BUILDER_ITEMS,
639
+ worksheet_proxy: WORKSHEET_PROXY_ITEMS,
640
+ stream_sheet: STREAM_SHEET_ITEMS,
641
+ workbook: WORKBOOK_ELEMENT_ITEMS,
642
+ worksheet: WORKSHEET_ELEMENT_ITEMS,
643
+ row: ROW_ELEMENT_ITEMS,
644
+ cell: CELL_ELEMENT_ITEMS,
645
+ chart_builder: CHART_BUILDER_ITEMS,
646
+ style_builder: STYLE_BUILDER_ITEMS
647
+ }.freeze
648
+
649
+ def initialize(response_builder, node_context, dispatcher, global_state)
650
+ @response_builder = response_builder
651
+ @node_context = node_context
652
+ @global_state = global_state
653
+
654
+ dispatcher.register(self, :on_call_node_enter)
655
+ end
656
+
657
+ def on_call_node_enter(node)
658
+ receiver = node.receiver
659
+ receiver_name = receiver.name if receiver.is_a?(Prism::LocalVariableReadNode) || (receiver.is_a?(Prism::CallNode) && receiver.receiver.nil?)
660
+ return unless receiver_name
661
+
662
+ target_type = infer_target_type(receiver_name)
663
+ return unless target_type
664
+
665
+ items = ITEMS_BY_TYPE[target_type]
666
+ return unless items
667
+
668
+ detail_prefix = "(#{CLASS_NAMES[target_type]}) "
669
+ items.each_with_index do |item, index|
670
+ sort_key = "#{index.to_s.rjust(3, "0")}_#{item[:label]}"
671
+ @response_builder << ::RubyLsp::Interface::CompletionItem.new(
672
+ label: item[:label],
673
+ kind: ::RubyLsp::Constant::CompletionItemKind::METHOD,
674
+ detail: "#{detail_prefix}#{item[:detail]}",
675
+ sort_text: sort_key,
676
+ documentation: {
677
+ kind: ::RubyLsp::Constant::MarkupKind::MARKDOWN,
678
+ value: item[:documentation]
679
+ }
680
+ )
681
+ end
682
+ end
683
+
684
+ private
685
+
686
+ def infer_target_type(receiver_name)
687
+ parent = @node_context&.parent
688
+
689
+ # 1. AST Context Analysis: Inspect enclosing block caller
690
+ if parent.is_a?(Prism::CallNode)
691
+ method_name = parent.name
692
+ caller_receiver = parent.receiver
693
+
694
+ if caller_receiver_is_xlsxrb?(caller_receiver)
695
+ case method_name
696
+ when :write then return :stream_writer
697
+ when :read then return :stream_sheet
698
+ when :build then return :workbook_builder
699
+ when :modify then return :workbook
700
+ end
701
+ end
702
+
703
+ case method_name
704
+ when :sheet then return :worksheet_proxy
705
+ when :chart then return :chart_builder
706
+ when :style then return :style_builder
707
+ when :each_row then return :row
708
+ when :each_cell then return :cell
709
+ when :each
710
+ return infer_each_block_target(caller_receiver)
711
+ end
712
+ end
713
+
714
+ # 2. Variable Name Heuristics (fallback if parent node wasn't direct)
715
+ infer_from_variable_name(receiver_name)
716
+ end
717
+
718
+ def infer_each_block_target(caller_receiver)
719
+ if receiver_matches?(caller_receiver, %i[workbook wb])
720
+ :worksheet
721
+ elsif receiver_matches?(caller_receiver, %i[row r stream_row])
722
+ :cell
723
+ else
724
+ :row
725
+ end
726
+ end
727
+
728
+ def infer_from_variable_name(name)
729
+ case name
730
+ when :wb, :workbook
731
+ :workbook_builder
732
+ when :s, :sheet, :ws, :worksheet
733
+ :worksheet_proxy
734
+ when :stream_sheet, :ss
735
+ :stream_sheet
736
+ when :stream_writer, :sw
737
+ :stream_writer
738
+ when :r, :row
739
+ :row
740
+ when :c, :cell
741
+ :cell
742
+ when :chart, :chart_builder, :cb
743
+ :chart_builder
744
+ when :style, :style_builder, :sb
745
+ :style_builder
746
+ end
747
+ end
748
+
749
+ def caller_receiver_is_xlsxrb?(receiver)
750
+ return false unless receiver
751
+
752
+ if receiver.is_a?(Prism::ConstantReadNode) || receiver.is_a?(Prism::ConstantPathNode)
753
+ receiver.slice == "Xlsxrb"
754
+ else
755
+ false
756
+ end
757
+ end
758
+
759
+ def receiver_matches?(receiver, names)
760
+ return false unless receiver
761
+
762
+ rec_name = receiver.name if receiver.is_a?(Prism::LocalVariableReadNode) || (receiver.is_a?(Prism::CallNode) && receiver.receiver.nil?)
763
+ names.include?(rec_name)
764
+ end
765
+ end
766
+ end
767
+ end