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
@@ -0,0 +1,104 @@
1
+ # lib/fatty/api/prompt.rb
2
+
3
+ module Fatty
4
+ module MenuApi
5
+ def menu(prompt, choices:, initial_choice_idx: 0, cancel_value: nil)
6
+ items = normalize_menu_choices(choices)
7
+ raise ArgumentError, "choices must not be empty" if items.empty?
8
+
9
+ labels = items.keys
10
+ popup = Fatty::PopUpSession.new(
11
+ source: labels,
12
+ kind: :terminal_menu,
13
+ title: "Menu",
14
+ message: prompt,
15
+ prompt: "Narrow: ",
16
+ current: :top,
17
+ validate_unique_labels: true,
18
+ history: terminal.history,
19
+ history_kind: :popup_filter,
20
+ history_ctx: { kind: :popup_filter, popup: :menu, prompt: prompt.to_s },
21
+ )
22
+ popup.instance_variable_set(
23
+ :@current,
24
+ initial_choice_idx.to_i.clamp(0, labels.length - 1),
25
+ )
26
+
27
+ done = false
28
+ result = nil
29
+ acc_proc = ->(payload) do
30
+ label = payload[:item]
31
+ action = items[label]
32
+
33
+ result =
34
+ if action
35
+ call_menu_action(action, label: label)
36
+ else
37
+ cancel_value
38
+ end
39
+
40
+ done = true
41
+ end
42
+ cancel_proc = -> do
43
+ result = cancel_value
44
+ done = true
45
+ end
46
+ owner = Fatty::Terminal::PopupOwner.new(
47
+ on_result: acc_proc,
48
+ on_cancel: cancel_proc,
49
+ )
50
+
51
+ begin
52
+ terminal.apply_command(
53
+ Command.terminal(:push_modal, session: popup, owner: owner),
54
+ )
55
+ terminal.render_frame
56
+
57
+ while !done && terminal.running?
58
+ dirty = false
59
+ command = terminal.event_source.next_event
60
+ if command
61
+ terminal.apply_command(command)
62
+ dirty = true
63
+ end
64
+
65
+ begin
66
+ dirty ||= !!terminal.tick_active_session
67
+ rescue StandardError => e
68
+ Fatty.error(
69
+ "MenuApi#menu tick failed: #{e.class}: #{e.message}",
70
+ tag: :terminal,
71
+ )
72
+ dirty = true
73
+ end
74
+
75
+ terminal.render_frame if dirty
76
+ end
77
+ ensure
78
+ terminal.render_frame
79
+ end
80
+ result
81
+ end
82
+
83
+ private
84
+
85
+ # simplecov:disable
86
+
87
+ def normalize_menu_choices(choices)
88
+ unless choices.is_a?(Hash)
89
+ raise ArgumentError, "menu choices must be a Hash of label => callable"
90
+ end
91
+
92
+ choices.each_with_object({}) do |(label, action), normalized|
93
+ unless action.respond_to?(:call)
94
+ raise ArgumentError, "menu choice #{label.inspect} is not callable"
95
+ end
96
+ normalized[label.to_s] = action
97
+ end
98
+ end
99
+
100
+ def call_menu_action(action, label:)
101
+ action.call(self, label)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ module OutputApi
5
+ def append(text, follow: true)
6
+ queue(Command.session(output_id, :append, text: text.to_s, follow: follow))
7
+ nil
8
+ end
9
+
10
+ def append_now(text, follow: true, mode: nil)
11
+ payload = { text: text.to_s, follow: follow }
12
+ payload[:mode] = mode if mode
13
+
14
+ terminal.apply_command(Command.session(output_id, :append, **payload))
15
+ terminal.render_frame
16
+
17
+ nil
18
+ end
19
+
20
+ def markdown(text)
21
+ md = Fatty::Markdown.render(
22
+ text,
23
+ palette: markdown_palette,
24
+ theme: markdown_theme,
25
+ truecolor: markdown_truecolor?,
26
+ )
27
+ append(md)
28
+ nil
29
+ end
30
+
31
+ private
32
+
33
+ def markdown_palette
34
+ terminal.renderer.palette if terminal.respond_to?(:renderer) && terminal.renderer
35
+ end
36
+
37
+ def markdown_theme
38
+ Fatty::Themes::Manager.fetch(Fatty::Themes::Manager.current)
39
+ rescue StandardError
40
+ nil
41
+ end
42
+
43
+ def markdown_truecolor?
44
+ if terminal.respond_to?(:ctx) && terminal.ctx.respond_to?(:truecolor_enabled?)
45
+ terminal.ctx.truecolor_enabled?
46
+ elsif terminal.respond_to?(:renderer) &&
47
+ terminal.renderer.respond_to?(:context) &&
48
+ terminal.renderer.context.respond_to?(:truecolor)
49
+ terminal.renderer.context.truecolor
50
+ else
51
+ false
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ module ProgressApi
5
+ # Create a transient status-line progress indicator.
6
+ # For style :spinner, total may be omitted for indeterminate progress.
7
+ def add_progress(
8
+ label: Progress::DEFAULT_LABEL,
9
+ total: nil,
10
+ style: Progress::DEFAULT_STYLE,
11
+ role: Progress::DEFAULT_ROLE,
12
+ width: Progress::DEFAULT_WIDTH
13
+ )
14
+ @progress = Progress.new(
15
+ terminal: terminal,
16
+ label: label,
17
+ total: total,
18
+ style: style,
19
+ role: role,
20
+ width: width,
21
+ )
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,77 @@
1
+ # lib/fatty/api/prompt.rb
2
+
3
+ module Fatty
4
+ module PromptApi
5
+ # Create a popup to ask the user to enter an arbitrary string. These
6
+ # prompts will keep their own history based on the history_key, or if not
7
+ # history_key is given, the prompt text. If save_history is set to false, no
8
+ # history will be recorded, which can suppress writing sensitive values
9
+ # such as passwords to the history file.
10
+ def prompt(prompt, initial: "", cancel_value: nil, history_key: nil, save_history: true)
11
+ history_ctx = lambda do
12
+ base =
13
+ if @history_ctx.respond_to?(:call)
14
+ @history_ctx.call
15
+ elsif @history_ctx.is_a?(Hash)
16
+ @history_ctx
17
+ else
18
+ {}
19
+ end
20
+
21
+ base.merge(prompt: (history_key || prompt).to_s)
22
+ end
23
+
24
+ popup = Fatty::PromptSession.new(
25
+ title: "Prompt",
26
+ message: prompt,
27
+ prompt: "> ",
28
+ initial: initial,
29
+ kind: :terminal_prompt,
30
+ save_history: save_history,
31
+ history_ctx: history_ctx,
32
+ )
33
+ done = false
34
+ result = nil
35
+
36
+ acc_proc = ->(payload) do
37
+ result = payload[:text]
38
+ done = true
39
+ end
40
+
41
+ cancel_proc = -> do
42
+ result = cancel_value
43
+ done = true
44
+ end
45
+
46
+ owner = Terminal::PopupOwner.new(on_result: acc_proc, on_cancel: cancel_proc)
47
+
48
+ begin
49
+ terminal.apply_command(
50
+ Fatty::Command.terminal(:push_modal, session: popup, owner: owner)
51
+ )
52
+ terminal.render_frame
53
+
54
+ while !done && terminal.running?
55
+ dirty = false
56
+ command = terminal.event_source.next_event
57
+
58
+ if command
59
+ terminal.apply_command(command)
60
+ dirty = true
61
+ end
62
+
63
+ begin
64
+ dirty ||= !!terminal.tick_active_session
65
+ rescue StandardError => e
66
+ Fatty.error("PromptApi#prompt tick failed: #{e.class}: #{e.message}", tag: :terminal)
67
+ dirty = true
68
+ end
69
+ terminal.render_frame if dirty
70
+ end
71
+ ensure
72
+ terminal.render_frame
73
+ end
74
+ result
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fatty
4
+ ###########################################################################################
5
+ # Public API convenience methods for use by the library user
6
+ ###########################################################################################
7
+ module StatusApi
8
+ # Display text in the status area with the given "role", which determines
9
+ # the coloring of the displayed text via themeing.
10
+ def status(text, role: :info, replace: false, render: true)
11
+ terminal.apply_command(
12
+ Command.session(
13
+ :status,
14
+ :show,
15
+ text: text,
16
+ role: role,
17
+ append: !replace,
18
+ )
19
+ )
20
+ terminal.without_cursor_restore do
21
+ terminal.render_frame
22
+ end if render
23
+ nil
24
+ end
25
+
26
+ # Display a message to the user in the status line, colored according to
27
+ # the Config for "good," i.e., success.
28
+ def good(text, replace: false, render: true)
29
+ status(text, role: :good, replace:, render:)
30
+ end
31
+
32
+ # Display a message to the user in the status line, colored according to
33
+ # the Config for "info".
34
+ def info(text, replace: false, render: true)
35
+ status(text, role: :info, replace:, render:)
36
+ end
37
+
38
+ # Display a message to the user in the status line, colored according to
39
+ # the Config for "warn," i.e., short of an error but not complete
40
+ # success either.
41
+ def warn(text, replace: false, render: true)
42
+ status(text, role: :warn, replace:, render:)
43
+ end
44
+
45
+ # Display a message to the user in the status line, colored according to
46
+ # the Config for "oops," i.e., a soft failure.
47
+ def error(text, replace: false, render: true)
48
+ status(text, role: :error, replace:, render:)
49
+ end
50
+
51
+ def oops(text, replace: false, render: true)
52
+ status(text, role: :error, replace:, render:)
53
+ end
54
+ end
55
+ end
data/lib/fatty/api.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Fatty
2
+ module ChooseApi
3
+ # simplecov:disable
4
+
5
+ private
6
+
7
+ def normalize_choices(choices)
8
+ case choices
9
+ when Hash
10
+ choices.map do |label, value|
11
+ [label.to_s, value]
12
+ end
13
+ when Array
14
+ choices.map do |choice|
15
+ unless choice.is_a?(String)
16
+ raise ArgumentError, "choices array must contain strings"
17
+ end
18
+
19
+ [choice, choice]
20
+ end
21
+ else
22
+ raise ArgumentError, "choices must be an Array of strings or a Hash"
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ # simplecov:enable
29
+
30
+ require_relative './api/output.rb'
31
+ require_relative './api/status.rb'
32
+ require_relative './api/alert.rb'
33
+ require_relative './api/choose.rb'
34
+ require_relative './api/prompt.rb'
35
+ require_relative './api/progress.rb'
36
+ require_relative './api/menu.rb'
37
+ require_relative './api/keytest.rb'
38
+ require_relative './api/environment.rb'
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+ #
3
+ # This class provides a wrapper around components that can be handed off to
4
+ # Terminal's on_accept method (or other callbacks) for access to Fatty's
5
+ # facilities. The on_accept proc will receive two parameters: (1) the edited
6
+ # line and (2) an instance of this class called `env` from which the user can
7
+ # access certain facilities that Fatty provides.
8
+ #
9
+ # For example, by using env.append("some text"), the given text will be
10
+ # displayed in the output pane with scrolling, search, etc. A related method,
11
+ # `env.markdown(<markdown_text>)` will render the given text as markdown and
12
+ # then append it to the output pane.
13
+ #
14
+ # Using env.status will display text in the status area. Likewise with
15
+ # env.alert for the alert panel.
16
+ #
17
+ # You can add a Progress object to the status_area with env.add_progress and
18
+ # update it with `env.progress.update`.
19
+ module Fatty
20
+ class CallbackEnvironment
21
+ INTERRUPT_POLL_INTERVAL = 1.0
22
+
23
+ include OutputApi
24
+ include MenuApi
25
+ include ProgressApi
26
+ include ChooseApi
27
+ include PromptApi
28
+ include StatusApi
29
+ include AlertApi
30
+ include KeytestApi
31
+ include EnvironmentApi
32
+
33
+ attr_reader :label, :payload, :commands, :terminal, :progress
34
+
35
+ def initialize(terminal:, output_id:, label: nil)
36
+ @terminal = terminal
37
+ @output_id = output_id
38
+ @label = label
39
+ @commands = []
40
+ @last_interrupt_check_at = nil
41
+ end
42
+
43
+ def queue(command)
44
+ @commands << command
45
+ command
46
+ end
47
+
48
+ def check_interrupt!
49
+ now = monotonic_time
50
+
51
+ if interrupt_check_due?(now)
52
+ @last_interrupt_check_at = now
53
+ raise Fatty::Interrupt if terminal.interrupt_pending?
54
+ end
55
+
56
+ nil
57
+ end
58
+
59
+ private
60
+
61
+ def interrupt_check_due?(now)
62
+ @last_interrupt_check_at.nil? ||
63
+ now - @last_interrupt_check_at >= INTERRUPT_POLL_INTERVAL
64
+ end
65
+
66
+ def monotonic_time
67
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
68
+ end
69
+
70
+ # simplecov:disable
71
+
72
+ attr_reader :output_id
73
+ end
74
+ end