fatty 0.99.8 → 0.99.8.2

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +316 -211
  3. data/README.org +94 -0
  4. data/exe/fatty +58 -9
  5. data/exe/themer +273 -0
  6. data/lib/fatty/ansi/renderer.rb +17 -7
  7. data/lib/fatty/api/output.rb +4 -15
  8. data/lib/fatty/api/suspend.rb +9 -0
  9. data/lib/fatty/api.rb +1 -0
  10. data/lib/fatty/callback_environment.rb +1 -0
  11. data/lib/fatty/config_files/themes/capuchin_monk.yml +4 -8
  12. data/lib/fatty/config_files/themes/catppuccin_latte.yml +4 -8
  13. data/lib/fatty/config_files/themes/catppuccin_mocha.yml +4 -8
  14. data/lib/fatty/config_files/themes/cyberpunk.yml +4 -8
  15. data/lib/fatty/config_files/themes/dracula.yml +4 -8
  16. data/lib/fatty/config_files/themes/everforest_dark.yml +4 -8
  17. data/lib/fatty/config_files/themes/gruvbox_dark.yml +4 -8
  18. data/lib/fatty/config_files/themes/gruvbox_light.yml +4 -8
  19. data/lib/fatty/config_files/themes/mono.yml +6 -10
  20. data/lib/fatty/config_files/themes/monokai.yml +4 -8
  21. data/lib/fatty/config_files/themes/nordic.yml +16 -8
  22. data/lib/fatty/config_files/themes/onedark.yml +4 -8
  23. data/lib/fatty/config_files/themes/solarized_dark.yml +4 -8
  24. data/lib/fatty/config_files/themes/solarized_light.yml +4 -8
  25. data/lib/fatty/config_files/themes/terminal.yml +8 -12
  26. data/lib/fatty/config_files/themes/tokyo_night.yml +4 -8
  27. data/lib/fatty/config_files/themes/wordperfect.yml +1 -5
  28. data/lib/fatty/log_formats/json.rb +1 -1
  29. data/lib/fatty/log_formats/text.rb +1 -2
  30. data/lib/fatty/output_buffer.rb +40 -5
  31. data/lib/fatty/renderer/curses.rb +60 -8
  32. data/lib/fatty/renderer/truecolor.rb +46 -10
  33. data/lib/fatty/session/output_session.rb +25 -9
  34. data/lib/fatty/terminal.rb +12 -0
  35. data/lib/fatty/themes/manager.rb +12 -3
  36. data/lib/fatty/themes/resolver.rb +1 -1
  37. data/lib/fatty/version.rb +1 -1
  38. metadata +4 -1
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#bd93f9"
26
26
 
27
27
  good:
28
- fg: "#50fa7b"
29
- bg: "#282a36"
28
+ fg: "#25b13d"
30
29
 
31
30
  info:
32
- fg: "#f8f8f2"
33
- bg: "#282a36"
31
+ fg: "#5f9edd"
34
32
 
35
33
  warn:
36
- fg: "#282a36"
37
- bg: "#f1fa8c"
34
+ fg: "#b99527"
38
35
 
39
36
  error:
40
- fg: "#f8f8f2"
41
- bg: "#ff5555"
37
+ fg: "#e37d7d"
42
38
 
43
39
  pager_status:
44
40
  fg: "#282a36"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#7fbbb3"
26
26
 
27
27
  good:
28
- fg: "#a7c080"
29
- bg: "#2d353b"
28
+ fg: "#28bd41"
30
29
 
31
30
  info:
32
- fg: "#d3c6aa"
33
- bg: "#2d353b"
31
+ fg: "#74abe2"
34
32
 
35
33
  warn:
36
- fg: "#2d353b"
37
- bg: "#dbbc7f"
34
+ fg: "#caa22b"
38
35
 
39
36
  error:
40
- fg: "#2d353b"
41
- bg: "#e67e80"
37
+ fg: "#e78d8d"
42
38
 
43
39
  pager_status:
44
40
  fg: "#2d353b"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#fabd2f"
26
26
 
27
27
  good:
28
- fg: "#b8bb26"
29
- bg: "#282828"
28
+ fg: "#25b13d"
30
29
 
31
30
  info:
32
- fg: "#ebdbb2"
33
- bg: "#282828"
31
+ fg: "#5b9cdc"
34
32
 
35
33
  warn:
36
- fg: "#282828"
37
- bg: "#fabd2f"
34
+ fg: "#b99527"
38
35
 
39
36
  error:
40
- fg: "#fbf1c7"
41
- bg: "#cc241d"
37
+ fg: "#e27878"
42
38
 
43
39
  pager_status:
44
40
  fg: "#282828"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#af3a03"
26
26
 
27
27
  good:
28
- fg: "#79740e"
29
- bg: "#fbf1c7"
28
+ fg: "#197628"
30
29
 
31
30
  info:
32
- fg: "#3c3836"
33
- bg: "#fbf1c7"
31
+ fg: "#2466a8"
34
32
 
35
33
  warn:
36
- fg: "#282828"
37
- bg: "#d79921"
34
+ fg: "#7a621a"
38
35
 
39
36
  error:
40
- fg: "#fbf1c7"
41
- bg: "#cc241d"
37
+ fg: "#c22929"
42
38
 
43
39
  pager_status:
44
40
  fg: "#fbf1c7"
@@ -25,23 +25,19 @@ region:
25
25
  bg: "#d7d7d7"
26
26
 
27
27
  good:
28
- fg: "#ffffff"
29
- bg: "#000000"
28
+ fg: "#1f9333"
30
29
  attrs: [bold]
31
30
 
32
31
  info:
33
- fg: "#d7d7d7"
34
- bg: "#000000"
32
+ fg: "#2d80d2"
35
33
 
36
34
  warn:
37
- fg: "#000000"
38
- bg: "#d7d7d7"
39
- attrs: [bold]
35
+ fg: "#977a20"
36
+ attrs: [underline]
40
37
 
41
38
  error:
42
- fg: "#ffffff"
43
- bg: "#5f5f5f"
44
- attrs: [bold]
39
+ fg: "#d94a4a"
40
+ attrs: [bold, underline]
45
41
 
46
42
  pager_status:
47
43
  fg: "#000000"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#66d9ef"
26
26
 
27
27
  good:
28
- fg: "#a6e22e"
29
- bg: "#272822"
28
+ fg: "#25ad3b"
30
29
 
31
30
  info:
32
- fg: "#f8f8f2"
33
- bg: "#272822"
31
+ fg: "#5b9cdc"
34
32
 
35
33
  warn:
36
- fg: "#272822"
37
- bg: "#e6db74"
34
+ fg: "#b99527"
38
35
 
39
36
  error:
40
- fg: "#f8f8f2"
41
- bg: "#f92672"
37
+ fg: "#e27878"
42
38
 
43
39
  pager_status:
44
40
  fg: "#272822"
@@ -23,21 +23,29 @@ region:
23
23
  fg: "#2e3440"
24
24
  bg: "#88c0d0"
25
25
 
26
+ # good:
27
+ # fg: "#22ee00"
28
+
29
+ # info:
30
+ # fg: "#33deff"
31
+
32
+ # warn:
33
+ # fg: "#d78700"
34
+
35
+ # error:
36
+ # fg: "#d700af"
37
+
26
38
  good:
27
- fg: "green"
28
- bg: "#2e3440"
39
+ fg: "#28bd41"
29
40
 
30
41
  info:
31
- fg: "#d8dee9"
32
- bg: "#2e3440"
42
+ fg: "#74abe2"
33
43
 
34
44
  warn:
35
- fg: "#2e3440"
36
- bg: "#ebcb8b"
45
+ fg: "#caa22b"
37
46
 
38
47
  error:
39
- fg: "#eceff4"
40
- bg: "#bf616a"
48
+ fg: "#e78d8d"
41
49
 
42
50
  pager_status:
43
51
  fg: "black"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#61afef"
26
26
 
27
27
  good:
28
- fg: "#98c379"
29
- bg: "#282c34"
28
+ fg: "#26b53e"
30
29
 
31
30
  info:
32
- fg: "#abb2bf"
33
- bg: "#282c34"
31
+ fg: "#63a1de"
34
32
 
35
33
  warn:
36
- fg: "#282c34"
37
- bg: "#e5c07b"
34
+ fg: "#bd9828"
38
35
 
39
36
  error:
40
- fg: "#282c34"
41
- bg: "#e06c75"
37
+ fg: "#e48181"
42
38
 
43
39
  pager_status:
44
40
  fg: "#282c34"
@@ -24,20 +24,16 @@ region:
24
24
  bg: "#b58900"
25
25
 
26
26
  good:
27
- fg: "green"
28
- bg: "#002b36"
27
+ fg: "#25ad3b"
29
28
 
30
29
  info:
31
- fg: "#839496"
32
- bg: "#002b36"
30
+ fg: "#5b9cdc"
33
31
 
34
32
  warn:
35
- fg: "#002b36"
36
- bg: "#cb4b16"
33
+ fg: "#b59126"
37
34
 
38
35
  error:
39
- fg: "#fdf6e3"
40
- bg: "red"
36
+ fg: "#e27878"
41
37
 
42
38
  pager_status:
43
39
  fg: "black"
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#268bd2"
26
26
 
27
27
  good:
28
- fg: "#859900"
29
- bg: "#fdf6e3"
28
+ fg: "#1a7a2a"
30
29
 
31
30
  info:
32
- fg: "#657b83"
33
- bg: "#fdf6e3"
31
+ fg: "#256bb1"
34
32
 
35
33
  warn:
36
- fg: "#073642"
37
- bg: "#b58900"
34
+ fg: "#7e651b"
38
35
 
39
36
  error:
40
- fg: "#fdf6e3"
41
- bg: "#dc322f"
37
+ fg: "#ca2b2b"
42
38
 
43
39
  pager_status:
44
40
  fg: "#fdf6e3"
@@ -27,24 +27,20 @@ region:
27
27
  bg: default
28
28
  attrs: [reverse]
29
29
 
30
- info:
31
- fg: default
32
- bg: default
33
-
34
30
  good:
35
- fg: default
36
- bg: default
31
+ fg: green
37
32
  attrs: [bold]
38
33
 
34
+ info:
35
+ fg: blue
36
+
39
37
  warn:
40
- fg: default
41
- bg: default
42
- attrs: [reverse]
38
+ fg: yellow
39
+ attrs: [bold]
43
40
 
44
41
  error:
45
- fg: default
46
- bg: default
47
- attrs: [bold, reverse]
42
+ fg: red
43
+ attrs: [bold]
48
44
 
49
45
  pager_status:
50
46
  fg: default
@@ -25,20 +25,16 @@ region:
25
25
  bg: "#7aa2f7"
26
26
 
27
27
  good:
28
- fg: "#9ece6a"
29
- bg: "#1a1b26"
28
+ fg: "#22a037"
30
29
 
31
30
  info:
32
- fg: "#c0caf5"
33
- bg: "#1a1b26"
31
+ fg: "#468fd8"
34
32
 
35
33
  warn:
36
- fg: "#1a1b26"
37
- bg: "#e0af68"
34
+ fg: "#a88724"
38
35
 
39
36
  error:
40
- fg: "#1a1b26"
41
- bg: "#f7768e"
37
+ fg: "#df6868"
42
38
 
43
39
  pager_status:
44
40
  fg: "#1a1b26"
@@ -26,21 +26,17 @@ region:
26
26
 
27
27
  good:
28
28
  fg: lightgreen
29
- bg: navy
30
29
  attrs: [bold]
31
30
 
32
31
  info:
33
32
  fg: white
34
- bg: navy
35
33
 
36
34
  warn:
37
35
  fg: orange
38
- bg: navy
39
36
  attrs: [bold]
40
37
 
41
38
  error:
42
- fg: white
43
- bg: red
39
+ fg: red
44
40
  attrs: [bold]
45
41
 
46
42
  pager_status:
@@ -5,7 +5,7 @@ module Fatty
5
5
  class JsonFormatter < ::Logger::Formatter
6
6
  def call(level, time, progname, msg)
7
7
  rec = {
8
- t: time.utc.iso8601(6),
8
+ t: time.getlocal.iso8601(6),
9
9
  sev: level,
10
10
  prog: progname
11
11
  }
@@ -27,8 +27,7 @@ module Fatty
27
27
  module Logger
28
28
  class TextFormatter < ::Logger::Formatter
29
29
  def call(level, time, progname, msg)
30
- ts = time.utc.iso8601(6)
31
-
30
+ ts = time.getlocal.iso8601(6)
32
31
  body =
33
32
  case msg
34
33
  when Hash
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Fatty
4
4
  class OutputBuffer
5
+ Fragment = Data.define(:text, :role)
6
+
5
7
  DEFAULT_MAX_LINES = 10_000
6
8
 
7
9
  attr_reader :lines
@@ -9,15 +11,25 @@ module Fatty
9
11
 
10
12
  def initialize(max_lines: DEFAULT_MAX_LINES)
11
13
  @lines = []
14
+ @fragments = []
12
15
  @scroll = 0
13
16
  @max_lines = Integer(max_lines)
14
17
  @line_open = false
15
18
  end
16
19
 
20
+ def clear
21
+ @lines.clear
22
+ @fragments.clear
23
+ @scroll = 0
24
+ @line_open = false
25
+ nil
26
+ end
27
+
28
+
17
29
  # Append text to the output buffer. Text may contain complete lines,
18
30
  # partial lines, or both. Returns the number of lines trimmed. The
19
31
  # Terminal can use this to adjust the Viewport.
20
- def append(text)
32
+ def append(text, role: nil)
21
33
  ntrimmed = 0
22
34
  str = text.to_s
23
35
  return ntrimmed if str.empty?
@@ -30,13 +42,17 @@ module Fatty
30
42
  ntrimmed += append_new_line("")
31
43
  end
32
44
  elsif !part.empty?
33
- ntrimmed += append_fragment(part)
45
+ ntrimmed += append_fragment(part, role: role)
34
46
  end
35
47
  end
36
48
 
37
49
  ntrimmed
38
50
  end
39
51
 
52
+ def fragments_for(index)
53
+ @fragments[index] || []
54
+ end
55
+
40
56
  def visible_lines(height)
41
57
  start = [@lines.length - height - @scroll, 0].max
42
58
  @lines[start, height] || []
@@ -54,27 +70,46 @@ module Fatty
54
70
 
55
71
  # simplecov:disable
56
72
 
57
- def append_fragment(fragment)
73
+ def append_fragment(fragment, role:)
58
74
  ntrimmed = 0
59
75
  if @line_open && @lines.any?
60
76
  @lines[-1] = "#{@lines[-1]}#{fragment}".freeze
77
+ append_fragment_metadata(@fragments[-1], fragment, role)
61
78
  else
62
- ntrimmed += append_new_line(fragment)
79
+ ntrimmed += append_new_line(fragment, role: role)
63
80
  @line_open = true
64
81
  end
65
82
  ntrimmed
66
83
  end
67
84
 
68
- def append_new_line(line)
85
+ def append_new_line(line, role: nil)
69
86
  ntrimmed = 0
70
87
 
71
88
  if @lines.length >= @max_lines
72
89
  @lines.shift
90
+ @fragments.shift
73
91
  ntrimmed = 1
74
92
  end
75
93
 
76
94
  @lines << line.to_s.dup.freeze
95
+ @fragments << []
96
+ append_fragment_metadata(@fragments[-1], line, role) unless line.to_s.empty?
77
97
  ntrimmed
78
98
  end
99
+
100
+ def append_fragment_metadata(fragments, text, role)
101
+ if fragments.any? && fragments[-1].role == role
102
+ previous = fragments[-1]
103
+ fragments[-1] = Fragment.new(
104
+ text: "#{previous.text}#{text}".freeze,
105
+ role: role,
106
+ )
107
+ else
108
+ fragments << Fragment.new(
109
+ text: text.to_s.dup.freeze,
110
+ role: role,
111
+ )
112
+ end
113
+ end
79
114
  end
80
115
  end
@@ -522,11 +522,13 @@ module Fatty
522
522
  start_y = 0 if start_y < 0
523
523
 
524
524
  (start_y...height).each do |y|
525
+ visible_line = lines[y]
525
526
  draw_output_row(
526
527
  win,
527
- line: lines[y],
528
+ line: visible_line.text,
529
+ fragments: visible_line.fragments,
528
530
  y: y,
529
- abs_line: viewport.top + y,
531
+ abs_line: visible_line.number - 1,
530
532
  highlights: highlights,
531
533
  )
532
534
  end
@@ -535,11 +537,13 @@ module Fatty
535
537
  count = height if count > height
536
538
 
537
539
  (0...count).each do |y|
540
+ visible_line = lines[y]
538
541
  draw_output_row(
539
542
  win,
540
- line: lines[y],
543
+ line: visible_line.text,
544
+ fragments: visible_line.fragments,
541
545
  y: y,
542
- abs_line: viewport.top + y,
546
+ abs_line: visible_line.number - 1,
543
547
  highlights: highlights,
544
548
  )
545
549
  end
@@ -618,6 +622,7 @@ module Fatty
618
622
  draw_output_row(
619
623
  win,
620
624
  line: visible_line.text,
625
+ fragments: visible_line.fragments,
621
626
  y: y,
622
627
  abs_line: visible_line.number - 1,
623
628
  highlights: highlights,
@@ -628,7 +633,7 @@ module Fatty
628
633
  stage_window(win)
629
634
  end
630
635
 
631
- def draw_output_row(win, line:, y:, abs_line:, highlights:, line_number: nil, line_number_width: nil)
636
+ def draw_output_row(win, line:, fragments: nil, y:, abs_line:, highlights:, line_number: nil, line_number_width: nil)
632
637
  base_attr = pair_attr(:output, fallback: ::Curses::A_NORMAL)
633
638
  line_number_attr = base_attr | ::Curses::A_DIM
634
639
  hi_attr = pair_attr(:match_current, fallback: ::Curses::A_REVERSE)
@@ -656,9 +661,7 @@ module Fatty
656
661
  [from.to_i, to.to_i, attr]
657
662
  end
658
663
 
659
- slices = build_line_slices(line.to_s, ranges: curses_ranges) do |style|
660
- ansi_style_attr(style, fallback: base_attr)
661
- end
664
+ slices = output_slices(line, fragments: fragments, ranges: curses_ranges, base_attr: base_attr)
662
665
  render_slices(win, slices)
663
666
  win.clrtoeol
664
667
  end
@@ -729,6 +732,55 @@ module Fatty
729
732
  slices
730
733
  end
731
734
 
735
+ def output_slices(line, fragments:, ranges:, base_attr:)
736
+ fragments = Array(fragments)
737
+ return build_line_slices(line.to_s, ranges: ranges) { |style|
738
+ ansi_style_attr(style, fallback: base_attr)
739
+ } if fragments.empty?
740
+
741
+ slices = []
742
+ offset = 0
743
+
744
+ fragments.each do |fragment|
745
+ role = fragment.role
746
+ role = :output unless role && palette[role]
747
+
748
+ fragment_attr = pair_attr(role, fallback: base_attr)
749
+ text = fragment.text.to_s
750
+ fragment_end = offset + Fatty::Ansi.plain_text(text).length
751
+
752
+ fragment_ranges =
753
+ Array(ranges).filter_map do |from, to, attr|
754
+ clipped_from = [from, offset].max
755
+ clipped_to = [to, fragment_end].min
756
+ next unless clipped_from < clipped_to
757
+
758
+ [
759
+ clipped_from - offset,
760
+ clipped_to - offset,
761
+ attr,
762
+ ]
763
+ end
764
+
765
+ fragment_slices =
766
+ build_line_slices(text, ranges: fragment_ranges) do |style|
767
+ ansi_style_attr(
768
+ style,
769
+ fallback: fragment_attr,
770
+ fallback_role: role,
771
+ )
772
+ end
773
+
774
+ fragment_slices.each do |attr, slice_text|
775
+ emit_slice(slices, attr, slice_text)
776
+ end
777
+
778
+ offset = fragment_end
779
+ end
780
+
781
+ slices
782
+ end
783
+
732
784
  def render_slices(win, slices)
733
785
  Array(slices).each do |attr, text|
734
786
  win.attrset(attr)
@@ -523,7 +523,13 @@ module Fatty
523
523
  }
524
524
  end
525
525
 
526
- segments.concat(output_segments(text, ranges: ranges))
526
+ segments.concat(
527
+ output_segments(
528
+ text,
529
+ fragments: visible_line&.fragments,
530
+ ranges: ranges,
531
+ ),
532
+ )
527
533
  queue_ansi_segments_line(
528
534
  row: rect.row + y,
529
535
  col: rect.col,
@@ -549,18 +555,48 @@ module Fatty
549
555
  end
550
556
  end
551
557
 
552
- def output_segments(line, ranges:)
553
- plain = Fatty::Ansi.plain_text(line.to_s)
558
+ def output_segments(line, fragments:, ranges:)
559
+ fragments = Array(fragments)
554
560
  base_segments = []
555
561
 
556
- Fatty::Ansi.segment(line.to_s).each do |text, style|
557
- base_segments << {
558
- text: text.to_s,
559
- role: :output,
560
- style: style,
561
- }
562
+ if fragments.empty?
563
+ Fatty.debug(
564
+ "output fragment role=:output spec=#{palette[:output].inspect}",
565
+ tag: :render,
566
+ )
567
+ Fatty::Ansi.segment(line.to_s).each do |text, style|
568
+ base_segments << {
569
+ text: text.to_s,
570
+ role: :output,
571
+ style: style,
572
+ }
573
+ end
574
+ else
575
+ fragments.each do |fragment|
576
+ role = fragment.role
577
+ role = :output unless role && palette[role]
578
+
579
+ Fatty.debug(
580
+ "output fragment role=#{role.inspect} spec=#{palette[role].inspect}",
581
+ tag: :render,
582
+ )
583
+ Fatty::Ansi.segment(fragment.text.to_s).each do |text, style|
584
+ base_segments << {
585
+ text: text.to_s,
586
+ role: role,
587
+ style: style,
588
+ }
589
+ end
590
+ end
562
591
  end
563
- apply_highlight_ranges_to_segments(base_segments, plain:, ranges:)
592
+
593
+ plain = base_segments.map { |segment| segment[:text] }.join
594
+
595
+ apply_highlight_ranges_to_segments(
596
+ base_segments,
597
+ plain: plain,
598
+ ranges: ranges,
599
+ )
564
600
  end
565
601
 
566
602
  def apply_highlight_ranges_to_segments(segments, plain:, ranges:)