fatty 0.99.8.2 → 0.99.8.4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7587a39968cfcfad6e8f4d1c0cd61be2a471c5a0946830108315f5b6eeec9cba
4
- data.tar.gz: a5c84ea5819e495d554ffc5cfcd1570a0547795c7cd09967f7768a552e6b6379
3
+ metadata.gz: b46a42de2b3925689cb1b1383a85b01e143493db9271294b031eb78e3bcceb6b
4
+ data.tar.gz: 5ace58f489a5a0cfc0dfd623b76d2ff3f0074da7ceb776cbea86b25c42f49eaa
5
5
  SHA512:
6
- metadata.gz: b3e988c6ca1404032644fb8de4c606b072ffe59aa1324ebae3fb58212fab46272e3d52a51bfd660a8f0a7e45606a3c4362ffecde98cad375a06c1786cc2c2a89
7
- data.tar.gz: 607be99da8e0fd128ceebb55b666e7f17c0d6c33318b4ce2d339439cef1155cb0c887c2f1896e0fc5cb6156720f92f0d5e489ba9a09ef2814e2d8b468350b3a1
6
+ metadata.gz: 1d01d6ee255dc289fe9828c96a16aaf267abdf4d3a95fb1e9eb26cee0dbdb1cc0523d66dd7f6cfd14a200a0654cb49df4c483e74b56865007e58a2365fb63e4d
7
+ data.tar.gz: 12aa3e2a67f801583f43a6de8e07dc560fb247c61d90cafe2285c49c4f12502307dde31f5c07942f982f272a3b19933f981527bb378628b4bb5a3343532f699d
data/lib/fatty/config.rb CHANGED
@@ -148,6 +148,35 @@ module Fatty
148
148
  File.join(app_user_config_dir, "#{name}.yml")
149
149
  end
150
150
 
151
+ # Persist a user-adjustable preference in the current application's config
152
+ # file. Preferences are kept in the app layer so applications using Fatty
153
+ # do not overwrite one another's selections. When no app layer is
154
+ # configured, use Fatty's global user config.
155
+ def self.set_preference(name, value)
156
+ path = app_config_path || File.join(user_config_dir, "config.yml")
157
+
158
+ config = if app_config_path
159
+ read_app("config")
160
+ elsif File.exist?(path)
161
+ YAML.safe_load_file(
162
+ path,
163
+ aliases: true,
164
+ permitted_classes: [Date, DateTime, Time],
165
+ symbolize_names: true,
166
+ ) || {}
167
+ else
168
+ {}
169
+ end
170
+ config[name.to_sym] = value
171
+ FileUtils.mkdir_p(File.dirname(path))
172
+ File.write(path, YAML.dump(yaml_safe_value(config)))
173
+ reset_reader!
174
+ true
175
+ rescue StandardError => e
176
+ Fatty.warn("Could not save preference #{name.inspect}: #{e.class}: #{e.message}", tag: :config)
177
+ false
178
+ end
179
+
151
180
  # The per-app themes directory, either under
152
181
  # ~/.config/fatty/apps/<app_name>/themes (by default) or under the themes
153
182
  # directory in the config directory the library consumer sets in
@@ -216,7 +245,12 @@ module Fatty
216
245
  return {} unless path
217
246
  return {} unless File.exist?(path)
218
247
 
219
- YAML.load_file(path, symbolize_names: true) || {}
248
+ YAML.safe_load_file(
249
+ path,
250
+ aliases: true,
251
+ permitted_classes: [Date, DateTime, Time],
252
+ symbolize_names: true,
253
+ ) || {}
220
254
  end
221
255
 
222
256
  def self.merge_config(base, overlay)
@@ -256,6 +290,19 @@ module Fatty
256
290
  value || {}
257
291
  end
258
292
 
293
+ def self.yaml_safe_value(value)
294
+ case value
295
+ when Hash
296
+ value.to_h { |key, item| [key.to_s, yaml_safe_value(item)] }
297
+ when Array
298
+ value.map { |item| yaml_safe_value(item) }
299
+ when Symbol
300
+ value.to_s
301
+ else
302
+ value
303
+ end
304
+ end
305
+
259
306
  def self.normalize_app_name(name)
260
307
  text = name.to_s.strip
261
308
  text.empty? ? nil : text
@@ -269,6 +316,7 @@ module Fatty
269
316
  private_class_method :merge_config
270
317
  private_class_method :blank_config?
271
318
  private_class_method :normalize_config_value
319
+ private_class_method :yaml_safe_value
272
320
  private_class_method :normalize_app_name
273
321
  end
274
322
  end
@@ -153,7 +153,9 @@ module Fatty
153
153
  def resume
154
154
  return unless @started
155
155
 
156
+ ::Curses.reset_prog_mode
156
157
  ::Curses.refresh
158
+ touch_windows!
157
159
  enable_bracketed_paste!
158
160
  nil
159
161
  end
@@ -171,6 +173,14 @@ module Fatty
171
173
  @started = false
172
174
  end
173
175
 
176
+ def touch_windows!
177
+ [@output_win, @status_win, @input_win, @alert_win].each do |win|
178
+ win.touch if win&.respond_to?(:touch)
179
+ end
180
+ ::Curses.stdscr.touch if ::Curses.respond_to?(:stdscr) && ::Curses.stdscr.respond_to?(:touch)
181
+ nil
182
+ end
183
+
174
184
  # Map a Fatty::Ansi::Style to a curses attribute.
175
185
  #
176
186
  # - If style has no explicit fg/bg, we keep the themed role pair.
@@ -519,9 +519,13 @@ module Fatty
519
519
  end
520
520
 
521
521
  def autosuggestion_suffix
522
- return "" unless autosuggestion_visible?
522
+ suggestion = autosuggestion.to_s
523
+ text = buffer.text.to_s
524
+ return "" if suggestion.empty?
525
+ return "" unless suggestion.start_with?(text)
526
+ return "" if suggestion == text
523
527
 
524
- autosuggestion.to_s.delete_prefix(buffer.text.to_s)
528
+ suggestion.delete_prefix(text)
525
529
  end
526
530
 
527
531
  def accept_autosuggestion!
@@ -346,6 +346,12 @@ module Fatty
346
346
  stage_window(win)
347
347
  end
348
348
 
349
+ def clear_physical_screen!
350
+ ::Curses.clear
351
+ invalidate!
352
+ self
353
+ end
354
+
349
355
  def begin_frame
350
356
  end
351
357
 
@@ -101,6 +101,10 @@ module Fatty
101
101
  raise NotImplementedError, "#{self.class} must implement #render_alert"
102
102
  end
103
103
 
104
+ def clear_physical_screen!
105
+ self
106
+ end
107
+
104
108
  def invalidate!
105
109
  @last_output_state = nil
106
110
  @last_input_state = nil
@@ -24,6 +24,7 @@ module Fatty
24
24
  @line_numbers = false
25
25
  @narrow_query = nil
26
26
  @visible_lines = nil
27
+ @pending_command_reset = false
27
28
  mode = Fatty::Config.config.dig(:output, :mode)&.to_sym || :paging
28
29
  @default_output_mode = mode
29
30
  @pager = Fatty::Pager.new(output: @output, viewport: @viewport, mode: mode, lines: -> { visible_lines })
@@ -52,6 +53,7 @@ module Fatty
52
53
  []
53
54
  end
54
55
  when :append
56
+ prepare_for_command_output!
55
57
  case payload[:mode]
56
58
  when :scrolling
57
59
  pager.toggle_paging_mode if pager.mode == :paging
@@ -80,16 +82,21 @@ module Fatty
80
82
  []
81
83
  when :clear
82
84
  reset_output!
85
+ renderer.clear_physical_screen!
83
86
  []
84
87
  when :resize
85
88
  resize_output!
86
89
  []
87
90
  when :begin_command
88
- reset_for_command!
89
- pager.begin_command!(anchor: output.lines.length)
91
+ @narrow_query = nil
92
+ invalidate_visible_lines!
93
+ @pending_command_reset = true
90
94
  []
91
95
  when :finish_command
92
- pager.finish_command!
96
+ unless @pending_command_reset
97
+ pager.finish_command!
98
+ end
99
+ @pending_command_reset = false
93
100
  []
94
101
  when :quit_paging
95
102
  pager.quit
@@ -508,6 +515,14 @@ module Fatty
508
515
  @pager.reset!(mode: mode)
509
516
  end
510
517
 
518
+ def prepare_for_command_output!
519
+ return unless @pending_command_reset
520
+
521
+ reset_for_command!
522
+ pager.begin_command!(anchor: output.lines.length)
523
+ @pending_command_reset = false
524
+ end
525
+
511
526
  # When the pager is active, the last output row is reserved for the pager
512
527
  # InputField. This returns the viewport to use for rendering the output
513
528
  # content itself.
@@ -97,6 +97,7 @@ module Fatty
97
97
  @last_render_time = nil
98
98
  @restore_cursor_after_render = true
99
99
  @render_count = 0
100
+ @deferred_render = false
100
101
  @deferred_count = 0
101
102
  end
102
103
 
@@ -161,6 +162,9 @@ module Fatty
161
162
  loop_count = 0
162
163
  while @running
163
164
  loop_count += 1
165
+ deferred_render = @deferred_render
166
+ @deferred_render = false
167
+ render_frame if deferred_render
164
168
  @session_dirty = false
165
169
  @immediate_render = false
166
170
  if (cmd = event_source.next_event)
@@ -194,8 +198,11 @@ module Fatty
194
198
  yield
195
199
  ensure
196
200
  ctx.resume
201
+ reconcile_terminal_size!
197
202
  renderer.invalidate!
203
+ renderer.clear_physical_screen! if renderer.context.truecolor
198
204
  render_frame
205
+ @deferred_render = true
199
206
  end
200
207
 
201
208
  # A command is either bound for this Terminal or it's meant to be
@@ -470,9 +477,9 @@ module Fatty
470
477
  command.action == :resize
471
478
  end
472
479
 
473
- def handle_resize
474
- rows = ::Curses.lines
475
- cols = ::Curses.cols
480
+ def handle_resize(rows: nil, cols: nil)
481
+ rows ||= ::Curses.lines
482
+ cols ||= ::Curses.cols
476
483
  size = [rows, cols]
477
484
 
478
485
  return [] if size == @last_handled_resize_size
@@ -520,6 +527,22 @@ module Fatty
520
527
  @inside_resize_term = false if did_resize_term
521
528
  end
522
529
 
530
+ def reconcile_terminal_size!
531
+ return unless @screen && @ctx
532
+
533
+ rows, cols = STDOUT.winsize
534
+ return if rows.to_i <= 0 || cols.to_i <= 0
535
+
536
+ if [rows, cols] == [@screen.rows, @screen.cols]
537
+ @ctx.apply_layout(@screen)
538
+ renderer.screen = @screen
539
+ else
540
+ handle_resize(rows:, cols:)
541
+ end
542
+ rescue SystemCallError, IOError
543
+ nil
544
+ end
545
+
523
546
  # --- Initialization ---------------------------------------------------------
524
547
 
525
548
  #
@@ -53,6 +53,8 @@ module Fatty
53
53
  if theme_names.include?(t)
54
54
  @warning = nil
55
55
  @current = t
56
+ Fatty::Config.set_preference(:theme, t)
57
+ @current
56
58
  else
57
59
  @warning = "Unknown theme in config: '#{theme}'; using '#{FALLBACK_THEME}'"
58
60
  Fatty.warn(@warning, tag: :theme)
data/lib/fatty/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fatty
4
- VERSION = "0.99.8.2"
4
+ VERSION = "0.99.8.4"
5
5
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fatty
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.99.8.2
4
+ version: 0.99.8.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Daniel E. Doherty
8
8
  bindir: exe
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 2026-09-18 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: curses
@@ -263,7 +263,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
263
263
  - !ruby/object:Gem::Version
264
264
  version: '0'
265
265
  requirements: []
266
- rubygems_version: 4.0.17
266
+ rubygems_version: 3.6.2
267
267
  specification_version: 4
268
268
  summary: Stateful terminal UI with editable input and scrollback
269
269
  test_files: []