marvi 0.1.2 → 0.2.0
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 +4 -4
- data/lib/marvi/renderer/curses.rb +96 -18
- data/lib/marvi/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5131e252ac808321286aa802bdd386e5b3bb9b494dda00df1761ef9700088a4e
|
|
4
|
+
data.tar.gz: 2e9bccb531018e22014e43053b6a05d081e60a422c9107966c54739829cf528d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d87aa8d7de7acd2414e5301416fd09b09d1f33e48f64c93c86b5d0b7fc833e20102c297e89a77396cdcb4176269493459e835a6ab9df2a677d6d112ad9bbb3ed
|
|
7
|
+
data.tar.gz: 673ca0703a768010c6116fa4cae6b29c723b439a50e7591c3a7ac7f9928e614eda42f2ef2fda7f377ef7453babd37b729786579ef20479b24107f800be453c59
|
|
@@ -18,23 +18,27 @@ module Marvi
|
|
|
18
18
|
|
|
19
19
|
ITALIC_ATTR = (defined?(::Curses::A_ITALIC) ? ::Curses::A_ITALIC : 0)
|
|
20
20
|
|
|
21
|
+
FILE_POLL_INTERVAL_MS = 500
|
|
22
|
+
|
|
21
23
|
def render(markdown, file: nil)
|
|
22
24
|
@file = file
|
|
23
25
|
@markdown = markdown
|
|
24
26
|
@lines = ASTWalker.new.walk(markdown)
|
|
25
27
|
@scroll = 0
|
|
28
|
+
mark_reloaded
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
::Curses.start_color
|
|
29
|
-
::Curses.use_default_colors
|
|
30
|
-
::Curses.noecho
|
|
31
|
-
::Curses.cbreak
|
|
32
|
-
::Curses.stdscr.keypad(true)
|
|
33
|
-
setup_colors
|
|
30
|
+
init_curses_state
|
|
34
31
|
draw
|
|
35
32
|
|
|
36
33
|
catch(:quit) do
|
|
37
|
-
loop
|
|
34
|
+
loop do
|
|
35
|
+
key = ::Curses.getch
|
|
36
|
+
if key.nil? || key == -1
|
|
37
|
+
check_file_updated
|
|
38
|
+
else
|
|
39
|
+
handle_key(key)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
38
42
|
end
|
|
39
43
|
ensure
|
|
40
44
|
::Curses.close_screen
|
|
@@ -42,16 +46,48 @@ module Marvi
|
|
|
42
46
|
|
|
43
47
|
private
|
|
44
48
|
|
|
45
|
-
#
|
|
46
|
-
#
|
|
49
|
+
# ncurses uses the terminfo `rep` capability ("ESC[Nb") to compress runs of
|
|
50
|
+
# identical glyphs, but ghostty mishandles it and drops the run from the
|
|
51
|
+
# screen — table borders and long padding go missing. The bug surfaces both
|
|
52
|
+
# under xterm-ghostty directly and inside multiplexers like cmux that ship a
|
|
53
|
+
# terminfo whose xterm-256color entry advertises `rep`. Detect `rep` in the
|
|
54
|
+
# active terminfo and swap to a known no-rep alternative around initscr only.
|
|
55
|
+
REP_SAFE_TERMS = %w[screen-256color tmux-256color xterm-color xterm].freeze
|
|
56
|
+
|
|
47
57
|
def with_safe_term
|
|
48
58
|
original = ENV["TERM"]
|
|
49
|
-
|
|
59
|
+
replacement = rep_safe_term_for(original)
|
|
60
|
+
ENV["TERM"] = replacement if replacement
|
|
50
61
|
yield
|
|
51
62
|
ensure
|
|
52
63
|
ENV["TERM"] = original
|
|
53
64
|
end
|
|
54
65
|
|
|
66
|
+
def rep_safe_term_for(term)
|
|
67
|
+
return nil if term.nil? || term.empty?
|
|
68
|
+
return nil unless terminfo_has_rep?(term)
|
|
69
|
+
|
|
70
|
+
REP_SAFE_TERMS.find { |candidate| candidate != term && terminfo_exists?(candidate) && !terminfo_has_rep?(candidate) }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def terminfo_has_rep?(term)
|
|
74
|
+
infocmp(term)&.include?("rep=") || false
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def terminfo_exists?(term)
|
|
78
|
+
!infocmp(term).nil?
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def infocmp(term)
|
|
82
|
+
@infocmp_cache ||= {}
|
|
83
|
+
return @infocmp_cache[term] if @infocmp_cache.key?(term)
|
|
84
|
+
|
|
85
|
+
output = IO.popen(["infocmp", "-1", term, err: File::NULL], &:read)
|
|
86
|
+
@infocmp_cache[term] = $?.success? ? output : nil
|
|
87
|
+
rescue StandardError
|
|
88
|
+
@infocmp_cache[term] = nil
|
|
89
|
+
end
|
|
90
|
+
|
|
55
91
|
def setup_colors
|
|
56
92
|
::Curses.init_pair(COLOR_PAIRS[:cyan], ::Curses::COLOR_CYAN, -1)
|
|
57
93
|
::Curses.init_pair(COLOR_PAIRS[:green], ::Curses::COLOR_GREEN, -1)
|
|
@@ -74,9 +110,41 @@ module Marvi
|
|
|
74
110
|
when "g" then @scroll = 0; draw
|
|
75
111
|
when "G" then @scroll = max_scroll; draw
|
|
76
112
|
when "e" then launch_editor if @file
|
|
113
|
+
when "r", "R" then reload_from_key if @file
|
|
77
114
|
end
|
|
78
115
|
end
|
|
79
116
|
|
|
117
|
+
def reload_from_key
|
|
118
|
+
reload
|
|
119
|
+
mark_reloaded
|
|
120
|
+
draw
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def check_file_updated
|
|
124
|
+
return unless @file
|
|
125
|
+
mtime = current_mtime
|
|
126
|
+
return if mtime.nil? || mtime == @last_mtime
|
|
127
|
+
|
|
128
|
+
@last_mtime = mtime
|
|
129
|
+
return if @file_updated
|
|
130
|
+
|
|
131
|
+
@file_updated = true
|
|
132
|
+
draw_status_bar
|
|
133
|
+
::Curses.refresh
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def mark_reloaded
|
|
137
|
+
@last_mtime = current_mtime
|
|
138
|
+
@file_updated = false
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def current_mtime
|
|
142
|
+
return nil unless @file
|
|
143
|
+
File.mtime(@file)
|
|
144
|
+
rescue SystemCallError
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
147
|
+
|
|
80
148
|
def launch_editor
|
|
81
149
|
editor = ENV["EDITOR"] || ENV["VISUAL"] || "vi"
|
|
82
150
|
line = current_source_line
|
|
@@ -85,7 +153,8 @@ module Marvi
|
|
|
85
153
|
::Curses.close_screen
|
|
86
154
|
system(cmd)
|
|
87
155
|
reload
|
|
88
|
-
|
|
156
|
+
mark_reloaded
|
|
157
|
+
init_curses_state
|
|
89
158
|
draw
|
|
90
159
|
end
|
|
91
160
|
|
|
@@ -95,13 +164,14 @@ module Marvi
|
|
|
95
164
|
@scroll = [@scroll, max_scroll].min
|
|
96
165
|
end
|
|
97
166
|
|
|
98
|
-
def
|
|
167
|
+
def init_curses_state
|
|
99
168
|
with_safe_term { ::Curses.init_screen }
|
|
100
169
|
::Curses.start_color
|
|
101
170
|
::Curses.use_default_colors
|
|
102
171
|
::Curses.noecho
|
|
103
172
|
::Curses.cbreak
|
|
104
173
|
::Curses.stdscr.keypad(true)
|
|
174
|
+
::Curses.stdscr.timeout = FILE_POLL_INTERVAL_MS
|
|
105
175
|
setup_colors
|
|
106
176
|
end
|
|
107
177
|
|
|
@@ -138,12 +208,20 @@ module Marvi
|
|
|
138
208
|
|
|
139
209
|
def draw_status_bar
|
|
140
210
|
::Curses.setpos(::Curses.lines - 1, 0)
|
|
211
|
+
top = @scroll + 1
|
|
212
|
+
bottom = [@scroll + page_size, @lines.size].min
|
|
213
|
+
edit_hint = @file ? " e edit" : ""
|
|
214
|
+
status = " #{top}-#{bottom}/#{@lines.size} j/k scroll g/G top/bottom#{edit_hint} q quit"
|
|
215
|
+
updated_hint = @file_updated ? " ● updated (r to reload) " : ""
|
|
216
|
+
available = [::Curses.cols - updated_hint.length, 0].max
|
|
217
|
+
|
|
141
218
|
::Curses.attron(::Curses.color_pair(COLOR_PAIRS[:cyan])) do
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
219
|
+
::Curses.addstr(status.ljust(available)[0, available])
|
|
220
|
+
end
|
|
221
|
+
unless updated_hint.empty?
|
|
222
|
+
::Curses.attron(::Curses.color_pair(COLOR_PAIRS[:yellow]) | ::Curses::A_BOLD) do
|
|
223
|
+
::Curses.addstr(updated_hint)
|
|
224
|
+
end
|
|
147
225
|
end
|
|
148
226
|
end
|
|
149
227
|
|
data/lib/marvi/version.rb
CHANGED