fatty 0.99.0 → 0.99.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.org +11 -29
  3. data/README.md +2207 -18
  4. data/README.org +2066 -104
  5. data/exe/fatty +236 -93
  6. data/help/help.md +233 -0
  7. data/lib/fatty/action.rb +3 -7
  8. data/lib/fatty/actionable.rb +2 -0
  9. data/lib/fatty/alert.rb +65 -39
  10. data/lib/fatty/ansi/renderer.rb +26 -6
  11. data/lib/fatty/ansi.rb +1 -0
  12. data/lib/fatty/api/alert.rb +15 -0
  13. data/lib/fatty/api/choose.rb +145 -0
  14. data/lib/fatty/api/environment.rb +12 -0
  15. data/lib/fatty/api/keytest.rb +11 -0
  16. data/lib/fatty/api/menu.rb +104 -0
  17. data/lib/fatty/api/output.rb +55 -0
  18. data/lib/fatty/api/progress.rb +24 -0
  19. data/lib/fatty/api/prompt.rb +77 -0
  20. data/lib/fatty/api/status.rb +55 -0
  21. data/lib/fatty/api.rb +38 -0
  22. data/lib/fatty/callback_environment.rb +74 -0
  23. data/lib/fatty/colors/color.rb +366 -232
  24. data/lib/fatty/colors/pairs.rb +2 -0
  25. data/lib/fatty/command.rb +78 -0
  26. data/lib/fatty/config.rb +227 -39
  27. data/lib/fatty/config_files/config.yml +4 -12
  28. data/lib/fatty/config_files/keydefs.yml +13 -23
  29. data/lib/fatty/config_files/themes/capuchin_monk.yml +140 -0
  30. data/lib/fatty/config_files/themes/catppuccin_latte.yml +140 -0
  31. data/lib/fatty/config_files/themes/catppuccin_mocha.yml +140 -0
  32. data/lib/fatty/config_files/themes/cyberpunk.yml +141 -0
  33. data/lib/fatty/config_files/themes/dracula.yml +140 -0
  34. data/lib/fatty/config_files/themes/everforest_dark.yml +140 -0
  35. data/lib/fatty/config_files/themes/gruvbox_dark.yml +140 -0
  36. data/lib/fatty/config_files/themes/gruvbox_light.yml +140 -0
  37. data/lib/fatty/config_files/themes/mono.yml +119 -47
  38. data/lib/fatty/config_files/themes/monokai.yml +140 -0
  39. data/lib/fatty/config_files/themes/nordic.yml +1 -0
  40. data/lib/fatty/config_files/themes/onedark.yml +140 -0
  41. data/lib/fatty/config_files/themes/solarized_dark.yml +1 -0
  42. data/lib/fatty/config_files/themes/solarized_light.yml +140 -0
  43. data/lib/fatty/config_files/themes/terminal.yml +67 -0
  44. data/lib/fatty/config_files/themes/tokyo_night.yml +140 -0
  45. data/lib/fatty/config_files/themes/wordperfect.yml +80 -9
  46. data/lib/fatty/core_ext/hash.rb +19 -0
  47. data/lib/fatty/core_ext/string.rb +19 -12
  48. data/lib/fatty/core_ext.rb +1 -0
  49. data/lib/fatty/curses/context.rb +86 -84
  50. data/lib/fatty/curses/event_source.rb +128 -40
  51. data/lib/fatty/curses/key_decoder.rb +77 -11
  52. data/lib/fatty/env.rb +21 -20
  53. data/lib/fatty/help.rb +1 -29
  54. data/lib/fatty/history/entry.rb +2 -0
  55. data/lib/fatty/history.rb +17 -14
  56. data/lib/fatty/input_buffer.rb +71 -47
  57. data/lib/fatty/input_field.rb +216 -149
  58. data/lib/fatty/key_event.rb +67 -66
  59. data/lib/fatty/key_map.rb +54 -16
  60. data/lib/fatty/keymaps/emacs.rb +40 -6
  61. data/lib/fatty/log_formats/json.rb +2 -0
  62. data/lib/fatty/log_formats/text.rb +2 -0
  63. data/lib/fatty/logger.rb +7 -2
  64. data/lib/fatty/markdown/ansi_renderer.rb +75 -6
  65. data/lib/fatty/markdown/render.rb +2 -3
  66. data/lib/fatty/mouse_event.rb +142 -2
  67. data/lib/fatty/output_buffer.rb +2 -0
  68. data/lib/fatty/pager.rb +158 -47
  69. data/lib/fatty/progress.rb +417 -0
  70. data/lib/fatty/renderer/curses.rb +340 -184
  71. data/lib/fatty/renderer/truecolor.rb +266 -163
  72. data/lib/fatty/renderer.rb +115 -97
  73. data/lib/fatty/screen.rb +2 -0
  74. data/lib/fatty/session/alert_session.rb +39 -21
  75. data/lib/fatty/session/isearch_session.rb +92 -38
  76. data/lib/fatty/session/keytest_session.rb +105 -85
  77. data/lib/fatty/session/modal_session.rb +38 -1
  78. data/lib/fatty/session/output_session.rb +472 -21
  79. data/lib/fatty/session/popup_session.rb +251 -134
  80. data/lib/fatty/session/prompt_session.rb +68 -26
  81. data/lib/fatty/session/search_session.rb +79 -31
  82. data/lib/fatty/session/shell_session.rb +383 -368
  83. data/lib/fatty/session/status_session.rb +167 -0
  84. data/lib/fatty/session.rb +95 -95
  85. data/lib/fatty/terminal/popup_owner.rb +14 -11
  86. data/lib/fatty/terminal.rb +515 -813
  87. data/lib/fatty/themes/loader.rb +4 -2
  88. data/lib/fatty/themes/manager.rb +4 -0
  89. data/lib/fatty/themes/registry.rb +2 -0
  90. data/lib/fatty/themes/resolver.rb +175 -162
  91. data/lib/fatty/version.rb +1 -1
  92. data/lib/fatty/viewport.rb +8 -1
  93. data/lib/fatty.rb +35 -7
  94. metadata +42 -40
  95. data/.envrc +0 -2
  96. data/.simplecov +0 -23
  97. data/.yardopts +0 -4
  98. data/CHANGELOG.md +0 -34
  99. data/Rakefile +0 -15
  100. data/TODO.org +0 -163
  101. data/examples/markdown/native-markdown.md +0 -370
  102. data/examples/markdown/ox-gfm-markdown.md +0 -373
  103. data/examples/markdown/ox-gfm-markdown.org +0 -376
  104. data/fatty.gemspec +0 -42
  105. data/lib/fatty/accept_env.rb +0 -32
  106. data/lib/fatty/config_files/help.md +0 -120
  107. data/lib/fatty/config_files/help.org +0 -124
  108. data/lib/fatty/config_files/themes/wordperfect_light.yml +0 -77
  109. data/lib/fatty/menu_env.rb +0 -22
  110. data/lib/fatty/session/input_session.rb +0 -99
  111. data/lib/fatty/sessions.rb +0 -14
  112. data/lib/fatty/terminal/progress.rb +0 -374
  113. data/lib/fatty/view/alert_view.rb +0 -14
  114. data/lib/fatty/view/cursor_view.rb +0 -18
  115. data/lib/fatty/view/input_view.rb +0 -9
  116. data/lib/fatty/view/output_view.rb +0 -9
  117. data/lib/fatty/view/status_view.rb +0 -14
  118. data/lib/fatty/view.rb +0 -33
  119. data/lib/fatty/views.rb +0 -9
  120. data/sig/fatty.rbs +0 -4
data/help/help.md ADDED
@@ -0,0 +1,233 @@
1
+ - [Fatty Help](#org245a92e)
2
+ - [The `fatty` demo command](#orgffe615f)
3
+ - [Builtin commands](#orgd27e40e)
4
+ - [Keybindings](#orgab9d7b6)
5
+ - [Input Context](#org68708fa)
6
+ - [Paging Context](#org7729d50)
7
+ - [Popup Context](#org684d15c)
8
+ - [Prompt Context](#org741e059)
9
+
10
+
11
+ <a id="org245a92e"></a>
12
+
13
+ # Fatty Help
14
+
15
+
16
+ <a id="orgffe615f"></a>
17
+
18
+ ## The `fatty` demo command
19
+
20
+ When you run `fatty`, it operates as a simple demo of the `fatty` gem by presenting an editable command-line using emacs key bindings. It has several builtin commands to demonstrate `fatty` features, and any command it does not recognize is handed off to the shell.
21
+
22
+
23
+ <a id="orgd27e40e"></a>
24
+
25
+ ## Builtin commands
26
+
27
+ Here are the commands builtin to `fatty`
28
+
29
+ | Command | Description |
30
+ |---------------------------- |-------------------------------------------------------------------------- |
31
+ | cd | Change the current directory used by the shell |
32
+ | colors | Display all available colors, names and rgb codes |
33
+ | choose | Present a series of choices in a popup window |
34
+ | choose\_multi | Present choices with a "checkbox" for selecting multiple values |
35
+ | choosevals | Also present choices in a popup window but return an associated value |
36
+ | choosevals\_multi | Also present a checkbox but return associated values |
37
+ | environment | Print out the environment detected by Fatty |
38
+ | good | Display a "good" message colored to indicate success |
39
+ | help | Display this file on the output pane |
40
+ | info | Display an "info" message on the status line |
41
+ | keytest | Enter key diagnostic mode report keycodes, key names, and bindings |
42
+ | markdown <file.md> | Render the markdown file to the output pane; defaults to a demo file |
43
+ | markdown\_org | Render the markdown file exported from org mode with ofx-gfm package |
44
+ | menu | Present a menu of labeled routines to run |
45
+ | oops | Display an "oops" message colored to indicate failure |
46
+ | progress bar | Show progress by a filling bar using ASCII characters |
47
+ | progress braille\_bar | Same, but using braille characters |
48
+ | progress count <N> | Display an animated progress indicator counting up to 40 or the given N |
49
+ | progress percent <N> | Same but also show the percent complete |
50
+ | progress simple\_percent <N> | Same but show only the percent complete |
51
+ | progress spinner | Animate a "spinner" showing a busy state |
52
+ | progress trail | Show progress by displaying an "indicator" character for each step |
53
+ | progress unicode\_bar | Same, but using unicode characters |
54
+ | prompt | Popup a text box for entering a value in response to a prompt |
55
+ | <shell\_command> | Anything else is treated as a shell command, run, and its output displayed |
56
+ | warn | Display a "warn" message colored to indicate caution |
57
+
58
+
59
+ <a id="orgab9d7b6"></a>
60
+
61
+ ## Keybindings
62
+
63
+ The following tables explain the keybindings available in \`fatty\` in different contexts. Named keys are indicated by \`:name\` and key categories, such as \`<digits>\` are indicated with brackets.
64
+
65
+
66
+ <a id="org68708fa"></a>
67
+
68
+ ### Input Context
69
+
70
+ When editing the input line or text input for widgets like the \`prompt\`, \`fatty\` provides emacs-like editing keybindings by default. Many of these commands can take a count prefix argument to repeat the command count times. For example, \`M-8 M-0 #\` will insert 80 '#' characters at the cursor.
71
+
72
+ | Key | Description |
73
+ |---------- |------------------------------------------------------ |
74
+ | C-a | move to the beginning of the line |
75
+ | :home | move to the beginning of the line |
76
+ | C-e | move to the end of the line |
77
+ | :end | move to the end of the line |
78
+ | C-f | move cursor right one character |
79
+ | :right | move cursor right one character |
80
+ | C-b | move cursor left one character |
81
+ | :left | move cursor left one character |
82
+ | M-f | move cursor right one word |
83
+ | M-:right | move cursor right one word |
84
+ | C-:right | move cursor right one word |
85
+ | M-b | move cursor left one word |
86
+ | M-:left | move cursor left one word |
87
+ | C-:left | move cursor left one word |
88
+ | C-t | transpose characters |
89
+ | M-t | transpose words |
90
+ | | |
91
+ | C-d | delete character at cursor |
92
+ | :delete | delete character at cursor |
93
+ | :backspace | delete character before cursor |
94
+ | M-d | kill word at cursor |
95
+ | C-w | kill word before cursor |
96
+ | C-k | kill to end of line |
97
+ | | |
98
+ | C-/ | undo |
99
+ | C-\_ | undo |
100
+ | C-M-/ | redo |
101
+ | M-/ | redo |
102
+ | | |
103
+ | C-:space | set the mark at the current cursor position |
104
+ | C-@ | set the mark at the current cursor position |
105
+ | C-g | clear the mark |
106
+ | C-w | kill the region |
107
+ | M-w | copy the region |
108
+ | | |
109
+ | C-y | yank last kill at cursor |
110
+ | M-y | replace last yank with next in kill ring |
111
+ | | |
112
+ | C-u | universal count argument (time 4 each press) |
113
+ | M-<digit> | accumulate count argument |
114
+ | | |
115
+ | C-p | replace the line with the prior history item |
116
+ | :up | replace the line with the prior history item |
117
+ | C-n | replace the line with the next history item |
118
+ | :down | replace the line with the prior history item |
119
+ | C-r | search the history in a popup |
120
+ | | |
121
+ | :enter | feed the line to the on\_accept proc and page output |
122
+ | M-:enter | feed the line to the on\_accept proc and scroll output |
123
+ | C-c | quit \`fatty\` |
124
+ | C-d | quit \`fatty\` only if the input line is empty |
125
+ | C-l | clear the output pane |
126
+ | | |
127
+ | :tab | Suggest the next completion |
128
+ | S-:tab | Suggest the prior completion |
129
+ | M-:tab | Popup all completions based on input-so-far |
130
+ | | |
131
+
132
+
133
+ <a id="org7729d50"></a>
134
+
135
+ ### Paging Context
136
+
137
+ By default, \`fatty\` sends output to the large output pane, and if the output is more than one screen long presents a paging environment for viewing and searching the environment.
138
+
139
+ | Key | Description |
140
+ |----------- |-------------------------------------- |
141
+ | :up | move output one line up |
142
+ | k | move output one line up |
143
+ | :down | move output one line down |
144
+ | j | move output one line down |
145
+ | | |
146
+ | :space | move to next page |
147
+ | :page\_down | move to next page |
148
+ | C-v | move to next page |
149
+ | d | move to next page |
150
+ | :backspace | move to prior page |
151
+ | :page\_up | move to prior page |
152
+ | M-v | move to prior page |
153
+ | u | move to prior page |
154
+ | | |
155
+ | `/` | initiate a forward string search |
156
+ | ? | initiate a backward string search |
157
+ | | |
158
+ | C-/ | initiate a forward regex search |
159
+ | C-M-S | initiate a forward regex search |
160
+ | | |
161
+ | C-s | initiate a forward incremental search |
162
+ | C-r | initiate a backward incremental search |
163
+ | | |
164
+ | C-c | cancel the search |
165
+ | C-g | cancel the search |
166
+ | | |
167
+ | :return | use the typed string for the search |
168
+ | :enter | use the typed string for the search |
169
+ | | |
170
+ | n | find the next match |
171
+ | N | find the prior match |
172
+ | | |
173
+
174
+
175
+ <a id="org684d15c"></a>
176
+
177
+ ### Popup Context
178
+
179
+ Certain keys cause a popup, such as C-r to bring up history items and M-:tab to bring up completions.
180
+
181
+ | Key | Description |
182
+ |-------------- |--------------------------------------------------------------------- |
183
+ | <search terms> | typing text narrows to those items matching all space-separated words |
184
+ | C-c | cancel the popup with no return |
185
+ | C-g | cancel the popup with no return |
186
+ | :escape | cancel the popup with no return |
187
+ | | |
188
+ | :enter | select the current item |
189
+ | :return | select the current item |
190
+ | | |
191
+ | :up | select the prior item |
192
+ | C-p | select the prior item |
193
+ | :down | select the next item |
194
+ | C-n | select the next item |
195
+ | | |
196
+ | :page\_up | display prior page of items |
197
+ | M-v | display prior page of items |
198
+ | :page\_down | display next page of items |
199
+ | C-v | display next page of items |
200
+ | | |
201
+ | :home | display first page of items |
202
+ | M-< | display first page of items |
203
+ | :end | display last page of items |
204
+ | M-> | display last page of items |
205
+ | | |
206
+ | C-l | re-center display on current item |
207
+ | | |
208
+ | :space | if popup allows multiple selections, add current item to selections |
209
+ | | |
210
+
211
+
212
+ <a id="org741e059"></a>
213
+
214
+ ### Prompt Context
215
+
216
+ Fatty can popup a prompt for getting text from the user by presenting a prompt together with an input field. The input field has all the line-editing capabilities of the primary input context.
217
+
218
+ | Key | Description |
219
+ |------- |--------------------------------- |
220
+ | C-c | cancel with no input |
221
+ | C-g | cancel with no input |
222
+ | :escape | cancel with no input |
223
+ | | |
224
+ | :return | return with current input |
225
+ | :enter | return with current input |
226
+ | C-j | return with current input |
227
+ | | |
228
+ | C-p | display prior prompt history item |
229
+ | :up | display prior prompt history item |
230
+ | C-n | display next prompt history item |
231
+ | :down | display next prompt history item |
232
+ | | |
233
+ | | |
data/lib/fatty/action.rb CHANGED
@@ -71,7 +71,7 @@ module Fatty
71
71
  end
72
72
 
73
73
  def self.call(name, env, *args, **kwargs)
74
- arg_str = "name: #{name}, env: #{env}, args: #{args}, kwargs: #{kwargs}"
74
+ arg_str = "name: #{name}, session: #{env.session.id}, #{env}, args: #{args}, kwargs: #{kwargs}"
75
75
  Fatty.debug("Action.call(#{arg_str})", tag: :action)
76
76
  key = name.to_sym
77
77
  defn = @defs[key] or raise ActionError, "Unknown action: #{key}"
@@ -79,7 +79,6 @@ module Fatty
79
79
  target = env.public_send(defn[:on])
80
80
  raise ActionError, "env.#{defn[:on]} is nil for action #{key}" unless target
81
81
 
82
- # Inject count: from env.counter when the target accepts it.
83
82
  if env.counter&.active? && !kwargs.key?(:count)
84
83
  meth = target.method(defn[:method])
85
84
  params = meth.parameters
@@ -88,12 +87,9 @@ module Fatty
88
87
  params.any? { |(kind, pname)| kind == :key && pname == :count } ||
89
88
  params.any? { |(kind, _pname)| kind == :keyrest }
90
89
 
91
- if accepts_count
92
- kwargs = kwargs.merge(count: env.counter.consume(default: 1))
93
- end
90
+ kwargs = kwargs.merge(count: env.counter.consume(default: 1)) if accepts_count
94
91
  end
95
-
96
- target.public_send(defn[:method], *args, **kwargs)
92
+ target.__send__(defn[:method], *args, **kwargs)
97
93
  end
98
94
 
99
95
  def self.registered?(name)
@@ -59,6 +59,8 @@ module Fatty
59
59
 
60
60
  private
61
61
 
62
+ # simplecov:disable
63
+
62
64
  def infer_action_target
63
65
  base = name.split("::").last
64
66
  return :buffer if base == "InputBuffer"
data/lib/fatty/alert.rb CHANGED
@@ -2,84 +2,99 @@
2
2
 
3
3
  module Fatty
4
4
  class Alert
5
- DETAIL_ORDER = %i[terminal key ctrl meta shift].freeze
6
-
7
- attr_reader :level, :message, :details
5
+ attr_reader :role, :text, :details
8
6
 
9
7
  # Return a new Alert object
10
8
  #
11
9
  # @param message [String]
12
- # @param level [:info, :warn, :error]
10
+ # @param role [:good, :info, :warn, :error]
13
11
  # @param details [Hash|String]
14
12
  # @param sticky [Boolean]
15
13
  # @return [Alert]
16
- def initialize(message:, level: :info, details: nil, sticky: false)
17
- @message = message
18
- @level = level.to_sym
14
+ def initialize(text:, role: :info, details: nil, sticky: false)
15
+ @text = text
16
+ @role = role.to_sym
19
17
  @details = details
20
18
  @sticky = !!sticky
21
19
  end
22
20
 
23
- # Return a new Alert object at level info
21
+ def inspect
22
+ txt = +"Alert: "
23
+ txt << "text: #{@text}; " if @text
24
+ txt << "role: #{@role}; " if @role
25
+ txt << "details: #{@details}; " if @details
26
+ txt << "sticky: #{@sticky}."
27
+ end
28
+
29
+ # Return a new Alert object at role good
30
+ #
31
+ # @param msg [String]
32
+ # @return [Alert] with role :good
33
+ def self.good(msg)
34
+ new(text: msg, role: :good)
35
+ end
36
+
37
+ # Return a new Alert object at role :info
24
38
  #
25
39
  # @param msg [String]
26
- # @return [Alert] with level info
40
+ # @return [Alert] with role :info
27
41
  def self.info(msg)
28
- new(message: msg, level: :info)
42
+ new(text: msg, role: :info)
29
43
  end
30
44
 
31
- # Return a new Alert object at level warn
45
+ # Return a new Alert object at role :warn
32
46
  #
33
47
  # @param msg [String]
34
- # @return [Alert] with level warn
48
+ # @return [Alert] with role :warn
35
49
  def self.warn(msg)
36
- new(message: msg, level: :warn)
50
+ new(text: msg, role: :warn)
37
51
  end
38
52
 
39
- # Return a new Alert object at level error
53
+ # Return a new Alert object at role error
40
54
  #
41
55
  # @param msg [String]
42
- # @return [Alert] with level error
56
+ # @return [Alert] with role :error
43
57
  def self.error(msg)
44
- new(message: msg, level: :error)
58
+ new(text: msg, role: :error)
45
59
  end
46
60
 
47
61
  # Translate the "level" to a "role" used by the renderers. The returned
48
62
  # roles are "composite" roles in the resolver in that they take the
49
- # background ot the alert panel and apply a foreground color based on
63
+ # background of the alert panel and apply a foreground color based on
50
64
  # severity.
51
65
  # @param level [:info, :warn, :error]
52
66
  # @return [Symbol] composite or semantic role
53
67
  # used by renderer (e.g., alert_good, :alert_info, :alert_warn, :alert_error)
54
- def role
55
- case level
56
- when :good then :alert_good
57
- when :warn then :alert_warn
58
- when :error then :alert_error
59
- else :alert_info
60
- end
61
- end
68
+ # def role
69
+ # case level
70
+ # when :good then :alert_good
71
+ # when :warn then :alert_warn
72
+ # when :error then :alert_error
73
+ # else :alert_info
74
+ # end
75
+ # end
62
76
 
63
77
  # Build a string version of the Alert suitable for display to the user.
64
78
  def format
65
79
  icon =
66
- case level
67
- when :warn then ""
68
- when :error then ""
69
- else ""
80
+ case role
81
+ when :good then ""
82
+ when :info then ""
83
+ when :warn then ""
84
+ when :error then " ✖ "
85
+ else " "
70
86
  end
71
- msg = message.to_s
72
- details_str = ""
73
- if details.respond_to?(:empty?) ? !details.empty? : !!details
74
- details_str = if details.is_a?(Hash)
75
- " (" +
76
- DETAIL_ORDER.filter_map { |k| "#{k}=#{details[k]}" if details.key?(k) }.join(" ") +
77
- ")"
87
+ details_str =
88
+ if details.nil? || details.empty?
89
+ ""
78
90
  else
79
- " (#{details})"
91
+ key_strs = []
92
+ detail_key_order.each do |k|
93
+ key_strs << "#{k}=#{details[k]}"
94
+ end
95
+ " (#{key_strs.join(' ')})"
80
96
  end
81
- end
82
- "#{icon} #{msg}#{details_str}"
97
+ "#{icon} #{text}#{details_str}"
83
98
  end
84
99
 
85
100
  # Return whether this Alert is sticky, meaning that it should not be
@@ -89,5 +104,16 @@ module Fatty
89
104
  def sticky?
90
105
  @sticky
91
106
  end
107
+
108
+ def detail_key_order
109
+ keys = []
110
+ keys << :terminal if details.key?(:terminal)
111
+ keys << :key if details.key?(:key)
112
+ keys << :shift if details.key?(:shift)
113
+ keys << :ctrl if details.key?(:ctrl)
114
+ keys << :meta if details.key?(:meta)
115
+ other_keys = details.keys - [:terminal, :key, :shift, :ctrl, :meta]
116
+ keys + other_keys.sort
117
+ end
92
118
  end
93
119
  end
@@ -14,12 +14,23 @@ module Fatty
14
14
  return unless spec
15
15
 
16
16
  msg = text.to_s.tr("\r\n", " ")
17
- msg = Fatty::Ansi.truncate_visible(msg, width)
18
- visible = Fatty::Ansi.visible_length(msg)
19
- pad = width - visible
20
- sgr = sgr_for_spec(spec)
21
- padding = pad.positive? ? " " * pad : ""
22
- write_ansi("#{CSI}#{row + 1};#{col + 1}H", sgr, msg, sgr, padding, reset)
17
+
18
+ segments = Fatty::Ansi.segment(msg).map do |segment_text, style|
19
+ {
20
+ text: segment_text,
21
+ role: role,
22
+ style: style,
23
+ }
24
+ end
25
+
26
+ render_segments_line(
27
+ row: row,
28
+ col: col,
29
+ width: width,
30
+ segments: segments,
31
+ palette: palette,
32
+ fill_role: role,
33
+ )
23
34
  end
24
35
 
25
36
  def render_segments_line(row:, col:, width:, segments:, palette:, fill_role: :output)
@@ -73,6 +84,15 @@ module Fatty
73
84
  @io || $stdout
74
85
  end
75
86
 
87
+ def show_cursor
88
+ "#{CSI}?25h"
89
+ end
90
+
91
+ def hide_cursor
92
+ "#{CSI}?25l"
93
+ end
94
+
95
+ # simplecov:disable
76
96
  private
77
97
 
78
98
  def save_cursor
data/lib/fatty/ansi.rb CHANGED
@@ -346,6 +346,7 @@ module Fatty
346
346
  out
347
347
  end
348
348
 
349
+ # simplecov:disable
349
350
  private_class_method :consume_escape!, :parse_params, :apply_sgr!
350
351
  private_class_method :merge_adjacent_segments, :same_style?, :sgr_for
351
352
  end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ ###########################################################################################
5
+ # Public API convenience methods for use by the library user
6
+ ###########################################################################################
7
+ module AlertApi
8
+ # Display text in the status area with the given "role", which determines
9
+ # the coloring of the displayed text via themeing.
10
+ def alert(text, role: :info)
11
+ terminal.apply_command(Command.session(:alert, :show, text: text, role: role))
12
+ nil
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,145 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ module ChooseApi
5
+ # The consumer can call #choose to cause an interactive popup session to
6
+ # present the user with a series of choices to select from.
7
+ def choose(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)
8
+ items = normalize_choices(choices)
9
+ raise ArgumentError, "choices must not be empty" if items.empty?
10
+
11
+ labels = items.map(&:first)
12
+ popup = PopUpSession.new(
13
+ source: labels,
14
+ kind: :terminal_choose,
15
+ title: "Choose",
16
+ message: prompt,
17
+ prompt: "Narrow: ",
18
+ current: :top,
19
+ show_counts: false,
20
+ validate_unique_labels: true,
21
+ history: terminal.history,
22
+ history_kind: :popup_filter,
23
+ history_ctx: { kind: :popup_filter, popup: :choose, prompt: prompt.to_s },
24
+ )
25
+ popup.instance_variable_set(:@current, initial_choice_idx.to_i.clamp(0, labels.length - 1))
26
+
27
+ done = false
28
+ result = nil
29
+
30
+ acc_proc = ->(payload) do
31
+ item = payload[:item]
32
+ idx = labels.index(item)
33
+ result = idx ? items[idx][1] : cancel_value
34
+ done = true
35
+ end
36
+ cancel_proc = -> do
37
+ result = cancel_value
38
+ done = true
39
+ end
40
+ owner = Terminal::PopupOwner.new(on_result: acc_proc, on_cancel: cancel_proc)
41
+
42
+ begin
43
+ terminal.apply_command(Fatty::Command.terminal(:push_modal, session: popup, owner: owner))
44
+ terminal.render_frame
45
+
46
+ while !done && terminal.running?
47
+ dirty = false
48
+ command = terminal.event_source.next_event
49
+
50
+ if command
51
+ terminal.apply_command(command)
52
+ dirty = true
53
+ end
54
+
55
+ begin
56
+ dirty ||= !!terminal.tick_active_session
57
+ rescue StandardError => e
58
+ Fatty.error("PromptApi#choose tick failed: #{e.class}: #{e.message}", tag: :terminal)
59
+ dirty = true
60
+ end
61
+
62
+ terminal.render_frame if dirty
63
+ end
64
+ ensure
65
+ terminal.render_frame
66
+ end
67
+ result
68
+ end
69
+
70
+ # A simple Yes/No chooser.
71
+ def confirm(prompt, yes_label: "Yes", no_label: "No", cancel_value: false)
72
+ choose(
73
+ prompt,
74
+ choices: { yes_label => true, no_label => false },
75
+ cancel_value: cancel_value,
76
+ )
77
+ end
78
+
79
+ # The consumer can call #choose_multi to cause an interactive popup session to
80
+ # present the user with a series of choices to select from.
81
+ def choose_multi(prompt, choices:, cancel_value: nil)
82
+ items = normalize_choices(choices)
83
+ raise ArgumentError, "choices must not be empty" if items.empty?
84
+
85
+ labels = items.map(&:first)
86
+ popup = PopUpSession.new(
87
+ source: labels,
88
+ kind: :terminal_choose_multi,
89
+ title: "Choose Many",
90
+ message: prompt,
91
+ prompt: "> ",
92
+ current: :top,
93
+ show_counts: true,
94
+ selection_mode: :multiple,
95
+ validate_unique_labels: true,
96
+ history: terminal.history,
97
+ history_kind: :popup_filter,
98
+ history_ctx: { kind: :popup_filter, popup: :choose_multi, prompt: prompt.to_s },
99
+ )
100
+ done = false
101
+ result = nil
102
+
103
+ label_to_value = items.to_h
104
+ acc_proc = ->(payload) do
105
+ selected = payload[:items] || {}
106
+ result =
107
+ selected.each_with_object({}) do |(label, _), h|
108
+ h[label] = label_to_value.fetch(label, cancel_value)
109
+ end
110
+ done = true
111
+ end
112
+ cancel_proc = -> do
113
+ result = cancel_value
114
+ done = true
115
+ end
116
+ owner = Terminal::PopupOwner.new(on_result: acc_proc, on_cancel: cancel_proc)
117
+
118
+ begin
119
+ terminal.apply_command(Fatty::Command.terminal(:push_modal, session: popup, owner: owner))
120
+ terminal.render_frame
121
+
122
+ while !done && terminal.running?
123
+ dirty = false
124
+ command = terminal.event_source.next_event
125
+ if command
126
+ terminal.apply_command(command)
127
+ dirty = true
128
+ end
129
+
130
+ begin
131
+ dirty ||= !!terminal.tick_active_session
132
+ rescue StandardError => e
133
+ Fatty.error("Terminal#choose_multi tick failed: #{e.class}: #{e.message}", tag: :terminal)
134
+ dirty = true
135
+ end
136
+
137
+ terminal.render_frame if dirty
138
+ end
139
+ ensure
140
+ terminal.render_frame
141
+ end
142
+ result
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ module EnvironmentApi
5
+ def environment
6
+ h = Fatty::Env.detect
7
+ h[:truecolor_enabled] = terminal.ctx.truecolor_enabled?
8
+ h[:curses] = terminal.ctx.environment if terminal.ctx
9
+ h
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ module KeytestApi
5
+ def keytest
6
+ queue(Command.terminal(:push_modal, session: Fatty::KeyTestSession.new))
7
+ queue(Command.session(:alert, :clear))
8
+ nil
9
+ end
10
+ end
11
+ end