charming 0.1.2 → 0.1.3

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/lib/charming/application.rb +3 -3
  3. data/lib/charming/controller/class_methods.rb +2 -2
  4. data/lib/charming/controller/command_palette.rb +2 -2
  5. data/lib/charming/controller/rendering.rb +2 -2
  6. data/lib/charming/controller/session_state.rb +1 -1
  7. data/lib/charming/generators/component_generator.rb +1 -1
  8. data/lib/charming/generators/templates/app/application.template +1 -1
  9. data/lib/charming/generators/templates/app/layout.template +3 -6
  10. data/lib/charming/generators/templates/app/view.template +1 -1
  11. data/lib/charming/generators/templates/component/component.rb.template +1 -1
  12. data/lib/charming/generators/templates/screen/view.rb.template +1 -1
  13. data/lib/charming/generators/templates/view/view.rb.template +1 -1
  14. data/lib/charming/internal/renderer/differential.rb +13 -5
  15. data/lib/charming/internal/terminal/tty_backend.rb +22 -2
  16. data/lib/charming/presentation/component.rb +3 -5
  17. data/lib/charming/presentation/components/activity_indicator.rb +173 -134
  18. data/lib/charming/presentation/components/command_palette.rb +94 -96
  19. data/lib/charming/presentation/components/command_palette_modal.rb +33 -0
  20. data/lib/charming/presentation/components/empty_state.rb +47 -49
  21. data/lib/charming/presentation/components/form/builder.rb +52 -54
  22. data/lib/charming/presentation/components/form/confirm.rb +49 -51
  23. data/lib/charming/presentation/components/form/field.rb +94 -96
  24. data/lib/charming/presentation/components/form/input.rb +53 -55
  25. data/lib/charming/presentation/components/form/note.rb +27 -29
  26. data/lib/charming/presentation/components/form/select.rb +84 -86
  27. data/lib/charming/presentation/components/form/textarea.rb +67 -69
  28. data/lib/charming/presentation/components/form.rb +120 -122
  29. data/lib/charming/presentation/components/keyboard_handler.rb +41 -43
  30. data/lib/charming/presentation/components/list.rb +123 -125
  31. data/lib/charming/presentation/components/markdown.rb +21 -23
  32. data/lib/charming/presentation/components/modal.rb +46 -48
  33. data/lib/charming/presentation/components/progressbar.rb +51 -53
  34. data/lib/charming/presentation/components/spinner.rb +40 -42
  35. data/lib/charming/presentation/components/table.rb +109 -111
  36. data/lib/charming/presentation/components/text_area.rb +219 -221
  37. data/lib/charming/presentation/components/text_input.rb +120 -122
  38. data/lib/charming/presentation/components/viewport.rb +218 -220
  39. data/lib/charming/presentation/layout/builder.rb +64 -66
  40. data/lib/charming/presentation/layout/overlay.rb +48 -50
  41. data/lib/charming/presentation/layout/pane.rb +122 -118
  42. data/lib/charming/presentation/layout/rect.rb +14 -16
  43. data/lib/charming/presentation/layout/screen_layout.rb +40 -42
  44. data/lib/charming/presentation/layout/split.rb +101 -103
  45. data/lib/charming/presentation/layout.rb +28 -30
  46. data/lib/charming/presentation/markdown/block_renderers.rb +94 -96
  47. data/lib/charming/presentation/markdown/inline_renderers.rb +52 -54
  48. data/lib/charming/presentation/markdown/render_context.rb +12 -14
  49. data/lib/charming/presentation/markdown/renderer.rb +84 -86
  50. data/lib/charming/presentation/markdown/syntax_highlighter.rb +57 -59
  51. data/lib/charming/presentation/markdown.rb +4 -6
  52. data/lib/charming/presentation/template_view.rb +22 -24
  53. data/lib/charming/presentation/templates/erb_handler.rb +4 -6
  54. data/lib/charming/presentation/templates.rb +47 -49
  55. data/lib/charming/presentation/ui/ansi_codes.rb +66 -68
  56. data/lib/charming/presentation/ui/ansi_slicer.rb +67 -69
  57. data/lib/charming/presentation/ui/border.rb +24 -26
  58. data/lib/charming/presentation/ui/border_painter.rb +37 -39
  59. data/lib/charming/presentation/ui/canvas.rb +59 -61
  60. data/lib/charming/presentation/ui/style.rb +173 -175
  61. data/lib/charming/presentation/ui/theme.rb +133 -135
  62. data/lib/charming/presentation/ui/width.rb +12 -14
  63. data/lib/charming/presentation/ui.rb +69 -71
  64. data/lib/charming/presentation/view.rb +103 -105
  65. data/lib/charming/runtime.rb +23 -10
  66. data/lib/charming/version.rb +1 -1
  67. data/lib/charming.rb +3 -2
  68. metadata +2 -1
@@ -3,140 +3,138 @@
3
3
  require "tty-table"
4
4
 
5
5
  module Charming
6
- module Presentation
7
- module Components
8
- # Table renders tabular data with a header row, a selected row highlight, and keyboard
9
- # navigation. Mouse clicks within the body area also select rows. The table is rendered
10
- # via tty-table and the selected row is overlaid with reverse-video ANSI styling.
11
- class Table < Component
12
- include KeyboardHandler
13
-
14
- # Maps navigation keys to the instance methods that move the selection. Shared with
15
- # List and Viewport via KeyboardHandler.
16
- KEY_ACTIONS = {
17
- up: :move_up,
18
- down: :move_down,
19
- home: :move_home,
20
- end: :move_end
21
- }.freeze
22
-
23
- # Number of terminal rows occupied by the table's top border and header line. Used by
24
- # the mouse handler to translate absolute row coordinates to body rows.
25
- HEADER_HEIGHT = 2
26
-
27
- # The header row, the body rows, and the currently selected row index, respectively.
28
- attr_reader :header, :rows, :selected_index
29
-
30
- # *header* is an array of column labels. *rows* is the array of body rows (each either a
31
- # String, an Array, or a Hash of column-value pairs). *selected_index* defaults to 0.
32
- # *keymap* selects the keybinding style (`:vim` enables h/j/k/l → left/down/up/right).
33
- def initialize(header:, rows: [], selected_index: 0, keymap: :vim)
34
- super()
35
- @header = Array(header).map(&:to_s)
36
- @rows = Array(rows)
37
- @selected_index = clamp_index(selected_index)
38
- @keymap = keymap
39
- end
6
+ module Components
7
+ # Table renders tabular data with a header row, a selected row highlight, and keyboard
8
+ # navigation. Mouse clicks within the body area also select rows. The table is rendered
9
+ # via tty-table and the selected row is overlaid with reverse-video ANSI styling.
10
+ class Table < Component
11
+ include KeyboardHandler
12
+
13
+ # Maps navigation keys to the instance methods that move the selection. Shared with
14
+ # List and Viewport via KeyboardHandler.
15
+ KEY_ACTIONS = {
16
+ up: :move_up,
17
+ down: :move_down,
18
+ home: :move_home,
19
+ end: :move_end
20
+ }.freeze
21
+
22
+ # Number of terminal rows occupied by the table's top border and header line. Used by
23
+ # the mouse handler to translate absolute row coordinates to body rows.
24
+ HEADER_HEIGHT = 2
25
+
26
+ # The header row, the body rows, and the currently selected row index, respectively.
27
+ attr_reader :header, :rows, :selected_index
28
+
29
+ # *header* is an array of column labels. *rows* is the array of body rows (each either a
30
+ # String, an Array, or a Hash of column-value pairs). *selected_index* defaults to 0.
31
+ # *keymap* selects the keybinding style (`:vim` enables h/j/k/l → left/down/up/right).
32
+ def initialize(header:, rows: [], selected_index: 0, keymap: :vim)
33
+ super()
34
+ @header = Array(header).map(&:to_s)
35
+ @rows = Array(rows)
36
+ @selected_index = clamp_index(selected_index)
37
+ @keymap = keymap
38
+ end
40
39
 
41
- # Handles key events. Returns `[:selected, row]` on Enter; otherwise delegates to the
42
- # KeyboardHandler for navigation keys.
43
- def handle_key(event)
44
- return nil if rows.empty?
40
+ # Handles key events. Returns `[:selected, row]` on Enter; otherwise delegates to the
41
+ # KeyboardHandler for navigation keys.
42
+ def handle_key(event)
43
+ return nil if rows.empty?
45
44
 
46
- case Charming.key_of(event)
47
- when :enter then [:selected, selected_row]
48
- else super
49
- end
45
+ case Charming.key_of(event)
46
+ when :enter then [:selected, selected_row]
47
+ else super
50
48
  end
49
+ end
51
50
 
52
- # Handles mouse events: a click within the body area selects the clicked row.
53
- # Returns :handled on a successful click.
54
- def handle_mouse(event)
55
- return nil if rows.empty?
56
- return nil unless event.respond_to?(:click?) && event.click?
51
+ # Handles mouse events: a click within the body area selects the clicked row.
52
+ # Returns :handled on a successful click.
53
+ def handle_mouse(event)
54
+ return nil if rows.empty?
55
+ return nil unless event.respond_to?(:click?) && event.click?
57
56
 
58
- clicked = event.y - HEADER_HEIGHT
59
- return nil if clicked.negative? || clicked >= rows.length
57
+ clicked = event.y - HEADER_HEIGHT
58
+ return nil if clicked.negative? || clicked >= rows.length
60
59
 
61
- @selected_index = clicked
62
- :handled
63
- end
60
+ @selected_index = clicked
61
+ :handled
62
+ end
64
63
 
65
- # Returns the currently selected row, or nil when the table is empty.
66
- def selected_row
67
- rows[selected_index]
68
- end
64
+ # Returns the currently selected row, or nil when the table is empty.
65
+ def selected_row
66
+ rows[selected_index]
67
+ end
69
68
 
70
- # Renders the table to a string. Returns a placeholder when both header and rows are empty.
71
- def render
72
- return "(empty table)" if header.empty? && rows.empty?
69
+ # Renders the table to a string. Returns a placeholder when both header and rows are empty.
70
+ def render
71
+ return "(empty table)" if header.empty? && rows.empty?
73
72
 
74
- normalized = rows.map { |row| normalize_row(row) }
75
- lines = TTY::Table.new(header: header, rows: normalized)
76
- .render(:unicode)
77
- .lines(chomp: true)
73
+ normalized = rows.map { |row| normalize_row(row) }
74
+ lines = TTY::Table.new(header: header, rows: normalized)
75
+ .render(:unicode)
76
+ .lines(chomp: true)
78
77
 
79
- compact_layout(lines)
80
- end
78
+ compact_layout(lines)
79
+ end
81
80
 
82
- private
83
-
84
- # Coerces a *row* (Hash / String / Array) into a flat cell array matching the header.
85
- # Excess cells are merged into the last column with a space separator.
86
- def normalize_row(row)
87
- cells = case row
88
- when Hash then row.values
89
- when String then [row]
90
- else Array(row)
91
- end
92
- return cells if header.length <= 1 || cells.length <= header.length
93
-
94
- kept = cells.first(header.length - 1)
95
- merged = cells[(header.length - 1)..].join(" ")
96
- kept + [merged]
81
+ private
82
+
83
+ # Coerces a *row* (Hash / String / Array) into a flat cell array matching the header.
84
+ # Excess cells are merged into the last column with a space separator.
85
+ def normalize_row(row)
86
+ cells = case row
87
+ when Hash then row.values
88
+ when String then [row]
89
+ else Array(row)
97
90
  end
91
+ return cells if header.length <= 1 || cells.length <= header.length
98
92
 
99
- # Applies the selected-row highlight and trims unused body rows below the actual row count.
100
- def compact_layout(lines)
101
- return lines.join("\n") if lines.length < 4
93
+ kept = cells.first(header.length - 1)
94
+ merged = cells[(header.length - 1)..].join(" ")
95
+ kept + [merged]
96
+ end
102
97
 
103
- top, header_line, _separator, *rest = lines
104
- body = rest.first(rows.length)
105
- bottom = rest[rows.length]
98
+ # Applies the selected-row highlight and trims unused body rows below the actual row count.
99
+ def compact_layout(lines)
100
+ return lines.join("\n") if lines.length < 4
106
101
 
107
- highlighted = body.each_with_index.map do |line, index|
108
- (index == selected_index) ? "\e[7m#{line}\e[m" : line
109
- end
102
+ top, header_line, _separator, *rest = lines
103
+ body = rest.first(rows.length)
104
+ bottom = rest[rows.length]
110
105
 
111
- [top, header_line, *highlighted, bottom].compact.join("\n")
106
+ highlighted = body.each_with_index.map do |line, index|
107
+ (index == selected_index) ? "\e[7m#{line}\e[m" : line
112
108
  end
113
109
 
114
- # Moves the selection up one row.
115
- def move_up
116
- @selected_index -= 1 if selected_index.positive?
117
- end
110
+ [top, header_line, *highlighted, bottom].compact.join("\n")
111
+ end
118
112
 
119
- # Moves the selection down one row.
120
- def move_down
121
- @selected_index += 1 if selected_index < rows.length - 1
122
- end
113
+ # Moves the selection up one row.
114
+ def move_up
115
+ @selected_index -= 1 if selected_index.positive?
116
+ end
123
117
 
124
- # Moves the selection to the first row.
125
- def move_home
126
- @selected_index = 0
127
- end
118
+ # Moves the selection down one row.
119
+ def move_down
120
+ @selected_index += 1 if selected_index < rows.length - 1
121
+ end
128
122
 
129
- # Moves the selection to the last row.
130
- def move_end
131
- @selected_index = rows.length - 1
132
- end
123
+ # Moves the selection to the first row.
124
+ def move_home
125
+ @selected_index = 0
126
+ end
133
127
 
134
- # Clamps *value* to the valid row range, defaulting to 0 when the table is empty.
135
- def clamp_index(value)
136
- return 0 if rows.empty?
128
+ # Moves the selection to the last row.
129
+ def move_end
130
+ @selected_index = rows.length - 1
131
+ end
137
132
 
138
- value.to_i.clamp(0, rows.length - 1)
139
- end
133
+ # Clamps *value* to the valid row range, defaulting to 0 when the table is empty.
134
+ def clamp_index(value)
135
+ return 0 if rows.empty?
136
+
137
+ value.to_i.clamp(0, rows.length - 1)
140
138
  end
141
139
  end
142
140
  end