marvi 0.2.0 → 0.3.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/.claude/hooks/standardrb-check.sh +23 -0
- data/.claude/settings.json +17 -0
- data/CHANGELOG.md +5 -0
- data/lib/marvi/ansi.rb +7 -7
- data/lib/marvi/ast_walker.rb +93 -21
- data/lib/marvi/renderer/ansi.rb +13 -5
- data/lib/marvi/renderer/curses.rb +50 -37
- data/lib/marvi/version.rb +1 -1
- metadata +17 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c5df9bb69ae060cf562d656a78f4aa152223a4ad58178af6b6b18891e19cb525
|
|
4
|
+
data.tar.gz: 1590ce11f925c30f25e256e1e0f0571355a4a4fd7690cc636310d9bf20e349eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1541589372fbdd04e4dbf7fe929de77788f7308c4abdf31ed8fea657712de058d1e38f7a48000c1122405b0c8a44f679b57946937ed8c779dcc81e2e7f4cfd10
|
|
7
|
+
data.tar.gz: 8637f420f782945d396f8dfa97ec3f4b748ffd056bb43baf23c6e6adbd54c6e8d9f8b3dbe896a1540faf46c6affa772cb7637bc929a9c9fe60e626c0420392e3
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PostToolUse hook: run standardrb on the edited file and block the turn
|
|
3
|
+
# when violations remain so Claude must fix them before continuing.
|
|
4
|
+
|
|
5
|
+
set -u
|
|
6
|
+
|
|
7
|
+
payload="$(cat)"
|
|
8
|
+
file_path=$(printf '%s' "$payload" | jq -r '.tool_response.filePath // .tool_input.file_path // empty')
|
|
9
|
+
[ -z "$file_path" ] && exit 0
|
|
10
|
+
|
|
11
|
+
case "$file_path" in
|
|
12
|
+
"$PWD"/*.rb | "$PWD"/*.gemspec | "$PWD"/Rakefile | "$PWD"/Gemfile) ;;
|
|
13
|
+
*) exit 0 ;;
|
|
14
|
+
esac
|
|
15
|
+
|
|
16
|
+
output=$(bundle exec standardrb "$file_path" 2>&1)
|
|
17
|
+
if [ $? -ne 0 ]; then
|
|
18
|
+
reason="standardrb violations in $file_path. Fix manually or run \`bundle exec rake standard:fix\`:
|
|
19
|
+
|
|
20
|
+
$output"
|
|
21
|
+
jq -n --arg reason "$reason" '{decision:"block",reason:$reason}'
|
|
22
|
+
fi
|
|
23
|
+
exit 0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.3.0] - 2026-05-18
|
|
4
|
+
|
|
5
|
+
- Render tables correctly when cells contain East Asian wide characters and emoji (uses `unicode-display_width`).
|
|
6
|
+
- Wrap long table cells to fit the terminal width so borders no longer break across physical lines; curses pager re-flows on window resize.
|
|
7
|
+
|
|
3
8
|
## [0.1.0] - 2026-03-17
|
|
4
9
|
|
|
5
10
|
- Initial release
|
data/lib/marvi/ansi.rb
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
module Marvi
|
|
4
4
|
module ANSI
|
|
5
|
-
RESET
|
|
6
|
-
BOLD
|
|
7
|
-
ITALIC
|
|
8
|
-
CYAN
|
|
9
|
-
YELLOW
|
|
10
|
-
GREEN
|
|
5
|
+
RESET = "\e[0m"
|
|
6
|
+
BOLD = "\e[1m"
|
|
7
|
+
ITALIC = "\e[3m"
|
|
8
|
+
CYAN = "\e[36m"
|
|
9
|
+
YELLOW = "\e[33m"
|
|
10
|
+
GREEN = "\e[32m"
|
|
11
11
|
MAGENTA = "\e[35m"
|
|
12
|
-
WHITE
|
|
12
|
+
WHITE = "\e[37m"
|
|
13
13
|
BG_DARK = "\e[48;5;236m"
|
|
14
14
|
|
|
15
15
|
HEADER_COLORS = [CYAN, GREEN, YELLOW, MAGENTA, WHITE, WHITE].freeze
|
data/lib/marvi/ast_walker.rb
CHANGED
|
@@ -2,12 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
require "kramdown"
|
|
4
4
|
require "kramdown-parser-gfm"
|
|
5
|
+
require "unicode/display_width"
|
|
5
6
|
|
|
6
7
|
module Marvi
|
|
7
8
|
class ASTWalker
|
|
8
9
|
HEADER_COLORS = %i[cyan green yellow magenta white white].freeze
|
|
9
10
|
|
|
10
|
-
|
|
11
|
+
DEFAULT_MAX_WIDTH = 80
|
|
12
|
+
MIN_COL_WIDTH = 4
|
|
13
|
+
|
|
14
|
+
def walk(markdown, max_width: nil)
|
|
15
|
+
@max_width = max_width || Integer(ENV["COLUMNS"] || DEFAULT_MAX_WIDTH)
|
|
11
16
|
doc = Kramdown::Document.new(markdown, input: "GFM")
|
|
12
17
|
lines = render_block(doc.root)
|
|
13
18
|
lines.pop while lines.last&.plain_text&.empty?
|
|
@@ -52,7 +57,7 @@ module Marvi
|
|
|
52
57
|
def render_header(el)
|
|
53
58
|
level = el.options[:level]
|
|
54
59
|
color = HEADER_COLORS[level - 1]
|
|
55
|
-
src
|
|
60
|
+
src = el.options[:location]
|
|
56
61
|
prefix = Span.new(text: "#" * level + " ", bold: true, color: color)
|
|
57
62
|
content = render_inline_children(el).map do |s|
|
|
58
63
|
Span.new(text: s.text, bold: true, italic: s.italic, color: s.color || color, bg_color: s.bg_color)
|
|
@@ -61,10 +66,10 @@ module Marvi
|
|
|
61
66
|
end
|
|
62
67
|
|
|
63
68
|
def render_li(el, indent:, list_type:, list_index:)
|
|
64
|
-
bullet = list_type == :ol ? "#{list_index}." : "•"
|
|
69
|
+
bullet = (list_type == :ol) ? "#{list_index}." : "•"
|
|
65
70
|
prefix = Span.new(text: "#{" " * indent}#{bullet} ", color: :cyan)
|
|
66
|
-
src
|
|
67
|
-
lines
|
|
71
|
+
src = el.options[:location]
|
|
72
|
+
lines = []
|
|
68
73
|
|
|
69
74
|
el.children.each do |child|
|
|
70
75
|
case child.type
|
|
@@ -79,10 +84,10 @@ module Marvi
|
|
|
79
84
|
lines += render_block(child)
|
|
80
85
|
end
|
|
81
86
|
else
|
|
82
|
-
if lines.empty?
|
|
83
|
-
|
|
87
|
+
lines << if lines.empty?
|
|
88
|
+
RichLine.new([prefix] + render_inline(child), source_line: src)
|
|
84
89
|
else
|
|
85
|
-
|
|
90
|
+
RichLine.new(render_inline(child))
|
|
86
91
|
end
|
|
87
92
|
end
|
|
88
93
|
end
|
|
@@ -90,12 +95,14 @@ module Marvi
|
|
|
90
95
|
end
|
|
91
96
|
|
|
92
97
|
def render_codeblock(el)
|
|
93
|
-
src
|
|
98
|
+
src = el.options[:location]
|
|
94
99
|
lang = el.options[:lang]
|
|
95
100
|
lines = []
|
|
96
101
|
lines << RichLine.new([Span.new(text: lang, color: :yellow)], source_line: src) if lang
|
|
97
102
|
el.value.chomp.split("\n").each_with_index do |line, i|
|
|
98
|
-
line_src =
|
|
103
|
+
line_src = if src
|
|
104
|
+
src + i + (lang ? 1 : 0)
|
|
105
|
+
end
|
|
99
106
|
lines << RichLine.new([Span.new(text: " #{line}", color: :green, bg_color: :dark)], source_line: line_src)
|
|
100
107
|
end
|
|
101
108
|
lines << RichLine.blank
|
|
@@ -103,20 +110,21 @@ module Marvi
|
|
|
103
110
|
end
|
|
104
111
|
|
|
105
112
|
def render_blockquote(el)
|
|
106
|
-
inner
|
|
113
|
+
inner = el.children.flat_map { |child| render_block(child) }
|
|
107
114
|
prefix = Span.new(text: "│ ", color: :cyan)
|
|
108
115
|
# preserve source_line from inner lines
|
|
109
116
|
inner.map { |line| RichLine.new([prefix] + line.spans, source_line: line.source_line) } + [RichLine.blank]
|
|
110
117
|
end
|
|
111
118
|
|
|
112
119
|
def render_table(el)
|
|
113
|
-
src
|
|
120
|
+
src = el.options[:location]
|
|
114
121
|
rows = el.children.flat_map(&:children)
|
|
115
122
|
header_row = el.children.find { |s| s.type == :thead }&.children&.first
|
|
116
123
|
|
|
117
124
|
cell_spans = rows.map { |row| row.children.map { |cell| render_inline_children(cell) } }
|
|
118
|
-
|
|
125
|
+
natural_widths = cell_spans.map { |row| row.map { |spans| spans_display_width(spans) } }
|
|
119
126
|
.transpose.map { |col| col.max }
|
|
127
|
+
col_widths = shrink_col_widths(natural_widths, @max_width)
|
|
120
128
|
|
|
121
129
|
lines = []
|
|
122
130
|
top = col_widths.map { |w| "─" * (w + 2) }.join("┬")
|
|
@@ -124,16 +132,23 @@ module Marvi
|
|
|
124
132
|
|
|
125
133
|
rows.each_with_index do |row, ri|
|
|
126
134
|
is_header = row == header_row
|
|
127
|
-
|
|
128
|
-
row.children.each_with_index do |cell, ci|
|
|
135
|
+
wrapped = row.children.each_with_index.map do |_cell, ci|
|
|
129
136
|
content = cell_spans[ri][ci]
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
styled = is_header ? content.map { |s| Span.new(text: s.text, bold: true, italic: s.italic, color: :cyan, bg_color: s.bg_color) } : content
|
|
138
|
+
wrap_spans(styled, col_widths[ci])
|
|
139
|
+
end
|
|
140
|
+
sub_row_count = wrapped.map(&:size).max
|
|
141
|
+
sub_row_count.times do |j|
|
|
142
|
+
row_spans = []
|
|
143
|
+
wrapped.each_with_index do |cell_lines, ci|
|
|
144
|
+
sub_spans = cell_lines[j] || []
|
|
145
|
+
sub_len = spans_display_width(sub_spans)
|
|
146
|
+
padding = col_widths[ci] - sub_len
|
|
147
|
+
row_spans += [Span.new(text: "│ ", color: :cyan)] + sub_spans + [Span.new(text: " " * (padding + 1))]
|
|
148
|
+
end
|
|
149
|
+
row_spans << Span.new(text: "│", color: :cyan)
|
|
150
|
+
lines << RichLine.new(row_spans)
|
|
134
151
|
end
|
|
135
|
-
row_spans << Span.new(text: "│", color: :cyan)
|
|
136
|
-
lines << RichLine.new(row_spans)
|
|
137
152
|
|
|
138
153
|
if is_header
|
|
139
154
|
sep = col_widths.map { |w| "─" * (w + 2) }.join("┼")
|
|
@@ -147,6 +162,63 @@ module Marvi
|
|
|
147
162
|
lines
|
|
148
163
|
end
|
|
149
164
|
|
|
165
|
+
def shrink_col_widths(widths, max_width)
|
|
166
|
+
budget = max_width - (3 * widths.size + 1)
|
|
167
|
+
total = widths.sum
|
|
168
|
+
return widths if total <= budget
|
|
169
|
+
|
|
170
|
+
shrunk = widths.dup
|
|
171
|
+
while total > budget
|
|
172
|
+
max_w, i = shrunk.each_with_index.max_by { |w, _| w }
|
|
173
|
+
break if max_w <= MIN_COL_WIDTH
|
|
174
|
+
shrunk[i] -= 1
|
|
175
|
+
total -= 1
|
|
176
|
+
end
|
|
177
|
+
shrunk
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def wrap_spans(spans, width)
|
|
181
|
+
lines = [[]]
|
|
182
|
+
current_width = 0
|
|
183
|
+
spans.each do |span|
|
|
184
|
+
text = span.text.dup
|
|
185
|
+
until text.empty?
|
|
186
|
+
remaining = width - current_width
|
|
187
|
+
if remaining <= 0
|
|
188
|
+
lines << []
|
|
189
|
+
current_width = 0
|
|
190
|
+
remaining = width
|
|
191
|
+
end
|
|
192
|
+
taken = 0
|
|
193
|
+
chunk_width = 0
|
|
194
|
+
text.each_char do |c|
|
|
195
|
+
cw = Unicode::DisplayWidth.of(c)
|
|
196
|
+
break if chunk_width + cw > remaining
|
|
197
|
+
chunk_width += cw
|
|
198
|
+
taken += c.bytesize
|
|
199
|
+
end
|
|
200
|
+
if taken.zero?
|
|
201
|
+
first_char = text.each_char.first
|
|
202
|
+
taken = first_char.bytesize
|
|
203
|
+
chunk_width = Unicode::DisplayWidth.of(first_char)
|
|
204
|
+
end
|
|
205
|
+
chunk = text.byteslice(0, taken)
|
|
206
|
+
text = text.byteslice(taken..) || ""
|
|
207
|
+
lines.last << Span.new(text: chunk, bold: span.bold, italic: span.italic, color: span.color, bg_color: span.bg_color)
|
|
208
|
+
current_width += chunk_width
|
|
209
|
+
unless text.empty?
|
|
210
|
+
lines << []
|
|
211
|
+
current_width = 0
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
lines
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def spans_display_width(spans)
|
|
219
|
+
spans.sum { |s| Unicode::DisplayWidth.of(s.text) }
|
|
220
|
+
end
|
|
221
|
+
|
|
150
222
|
def render_inline_children(el)
|
|
151
223
|
el.children.flat_map { |child| render_inline(child) }
|
|
152
224
|
end
|
data/lib/marvi/renderer/ansi.rb
CHANGED
|
@@ -1,23 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "io/console"
|
|
4
|
+
|
|
3
5
|
module Marvi
|
|
4
6
|
module Renderer
|
|
5
7
|
class ANSI
|
|
6
8
|
COLOR_MAP = {
|
|
7
|
-
cyan:
|
|
8
|
-
green:
|
|
9
|
-
yellow:
|
|
9
|
+
cyan: Marvi::ANSI::CYAN,
|
|
10
|
+
green: Marvi::ANSI::GREEN,
|
|
11
|
+
yellow: Marvi::ANSI::YELLOW,
|
|
10
12
|
magenta: Marvi::ANSI::MAGENTA,
|
|
11
|
-
white:
|
|
13
|
+
white: Marvi::ANSI::WHITE
|
|
12
14
|
}.freeze
|
|
13
15
|
|
|
14
16
|
def render(markdown)
|
|
15
|
-
lines = ASTWalker.new.walk(markdown)
|
|
17
|
+
lines = ASTWalker.new.walk(markdown, max_width: terminal_width)
|
|
16
18
|
lines.map { |line| render_line(line) }.join("\n") + "\n"
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
private
|
|
20
22
|
|
|
23
|
+
def terminal_width
|
|
24
|
+
IO.console&.winsize&.last || Integer(ENV["COLUMNS"] || ASTWalker::DEFAULT_MAX_WIDTH)
|
|
25
|
+
rescue
|
|
26
|
+
ASTWalker::DEFAULT_MAX_WIDTH
|
|
27
|
+
end
|
|
28
|
+
|
|
21
29
|
def render_line(line)
|
|
22
30
|
line.spans.map { |span| render_span(span) }.join
|
|
23
31
|
end
|
|
@@ -7,13 +7,13 @@ module Marvi
|
|
|
7
7
|
module Renderer
|
|
8
8
|
class Curses
|
|
9
9
|
COLOR_PAIRS = {
|
|
10
|
-
cyan:
|
|
11
|
-
green:
|
|
12
|
-
yellow:
|
|
13
|
-
magenta:
|
|
14
|
-
white:
|
|
10
|
+
cyan: 1,
|
|
11
|
+
green: 2,
|
|
12
|
+
yellow: 3,
|
|
13
|
+
magenta: 4,
|
|
14
|
+
white: 5,
|
|
15
15
|
green_on_dark: 6,
|
|
16
|
-
cyan_on_dark:
|
|
16
|
+
cyan_on_dark: 7
|
|
17
17
|
}.freeze
|
|
18
18
|
|
|
19
19
|
ITALIC_ATTR = (defined?(::Curses::A_ITALIC) ? ::Curses::A_ITALIC : 0)
|
|
@@ -21,13 +21,13 @@ module Marvi
|
|
|
21
21
|
FILE_POLL_INTERVAL_MS = 500
|
|
22
22
|
|
|
23
23
|
def render(markdown, file: nil)
|
|
24
|
-
@file
|
|
24
|
+
@file = file
|
|
25
25
|
@markdown = markdown
|
|
26
|
-
@
|
|
27
|
-
@scroll = 0
|
|
26
|
+
@scroll = 0
|
|
28
27
|
mark_reloaded
|
|
29
28
|
|
|
30
29
|
init_curses_state
|
|
30
|
+
rewalk
|
|
31
31
|
draw
|
|
32
32
|
|
|
33
33
|
catch(:quit) do
|
|
@@ -84,36 +84,49 @@ module Marvi
|
|
|
84
84
|
|
|
85
85
|
output = IO.popen(["infocmp", "-1", term, err: File::NULL], &:read)
|
|
86
86
|
@infocmp_cache[term] = $?.success? ? output : nil
|
|
87
|
-
rescue
|
|
87
|
+
rescue
|
|
88
88
|
@infocmp_cache[term] = nil
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
def setup_colors
|
|
92
|
-
::Curses.init_pair(COLOR_PAIRS[:cyan],
|
|
93
|
-
::Curses.init_pair(COLOR_PAIRS[:green],
|
|
94
|
-
::Curses.init_pair(COLOR_PAIRS[:yellow],
|
|
95
|
-
::Curses.init_pair(COLOR_PAIRS[:magenta],
|
|
96
|
-
::Curses.init_pair(COLOR_PAIRS[:white],
|
|
97
|
-
::Curses.init_pair(COLOR_PAIRS[:green_on_dark], ::Curses::COLOR_GREEN,
|
|
98
|
-
::Curses.init_pair(COLOR_PAIRS[:cyan_on_dark],
|
|
92
|
+
::Curses.init_pair(COLOR_PAIRS[:cyan], ::Curses::COLOR_CYAN, -1)
|
|
93
|
+
::Curses.init_pair(COLOR_PAIRS[:green], ::Curses::COLOR_GREEN, -1)
|
|
94
|
+
::Curses.init_pair(COLOR_PAIRS[:yellow], ::Curses::COLOR_YELLOW, -1)
|
|
95
|
+
::Curses.init_pair(COLOR_PAIRS[:magenta], ::Curses::COLOR_MAGENTA, -1)
|
|
96
|
+
::Curses.init_pair(COLOR_PAIRS[:white], ::Curses::COLOR_WHITE, -1)
|
|
97
|
+
::Curses.init_pair(COLOR_PAIRS[:green_on_dark], ::Curses::COLOR_GREEN, ::Curses::COLOR_BLACK)
|
|
98
|
+
::Curses.init_pair(COLOR_PAIRS[:cyan_on_dark], ::Curses::COLOR_CYAN, ::Curses::COLOR_BLACK)
|
|
99
99
|
end
|
|
100
100
|
|
|
101
101
|
def handle_key(key)
|
|
102
102
|
case key
|
|
103
|
-
when "q", "Q", 27
|
|
104
|
-
when "j", ::Curses::Key::DOWN
|
|
105
|
-
when "k", ::Curses::Key::UP
|
|
106
|
-
when "d"
|
|
107
|
-
when "u"
|
|
103
|
+
when "q", "Q", 27 then throw :quit
|
|
104
|
+
when "j", ::Curses::Key::DOWN then scroll_by(1)
|
|
105
|
+
when "k", ::Curses::Key::UP then scroll_by(-1)
|
|
106
|
+
when "d" then scroll_by(page_size / 2)
|
|
107
|
+
when "u" then scroll_by(-page_size / 2)
|
|
108
108
|
when "f", " ", ::Curses::Key::NPAGE then scroll_by(page_size)
|
|
109
|
-
when "b", ::Curses::Key::PPAGE
|
|
110
|
-
when "g"
|
|
111
|
-
|
|
112
|
-
when "
|
|
113
|
-
|
|
109
|
+
when "b", ::Curses::Key::PPAGE then scroll_by(-page_size)
|
|
110
|
+
when "g" then @scroll = 0
|
|
111
|
+
draw
|
|
112
|
+
when "G" then @scroll = max_scroll
|
|
113
|
+
draw
|
|
114
|
+
when "e" then launch_editor if @file
|
|
115
|
+
when "r", "R" then reload_from_key if @file
|
|
116
|
+
when ::Curses::Key::RESIZE then handle_resize
|
|
114
117
|
end
|
|
115
118
|
end
|
|
116
119
|
|
|
120
|
+
def handle_resize
|
|
121
|
+
rewalk
|
|
122
|
+
@scroll = [@scroll, max_scroll].min
|
|
123
|
+
draw
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def rewalk
|
|
127
|
+
@lines = ASTWalker.new.walk(@markdown, max_width: ::Curses.cols)
|
|
128
|
+
end
|
|
129
|
+
|
|
117
130
|
def reload_from_key
|
|
118
131
|
reload
|
|
119
132
|
mark_reloaded
|
|
@@ -134,7 +147,7 @@ module Marvi
|
|
|
134
147
|
end
|
|
135
148
|
|
|
136
149
|
def mark_reloaded
|
|
137
|
-
@last_mtime
|
|
150
|
+
@last_mtime = current_mtime
|
|
138
151
|
@file_updated = false
|
|
139
152
|
end
|
|
140
153
|
|
|
@@ -147,21 +160,21 @@ module Marvi
|
|
|
147
160
|
|
|
148
161
|
def launch_editor
|
|
149
162
|
editor = ENV["EDITOR"] || ENV["VISUAL"] || "vi"
|
|
150
|
-
line
|
|
151
|
-
cmd
|
|
163
|
+
line = current_source_line
|
|
164
|
+
cmd = build_editor_command(editor, @file, line)
|
|
152
165
|
|
|
153
166
|
::Curses.close_screen
|
|
154
167
|
system(cmd)
|
|
168
|
+
init_curses_state
|
|
155
169
|
reload
|
|
156
170
|
mark_reloaded
|
|
157
|
-
init_curses_state
|
|
158
171
|
draw
|
|
159
172
|
end
|
|
160
173
|
|
|
161
174
|
def reload
|
|
162
175
|
@markdown = File.read(@file)
|
|
163
|
-
|
|
164
|
-
@scroll
|
|
176
|
+
rewalk
|
|
177
|
+
@scroll = [@scroll, max_scroll].min
|
|
165
178
|
end
|
|
166
179
|
|
|
167
180
|
def init_curses_state
|
|
@@ -208,7 +221,7 @@ module Marvi
|
|
|
208
221
|
|
|
209
222
|
def draw_status_bar
|
|
210
223
|
::Curses.setpos(::Curses.lines - 1, 0)
|
|
211
|
-
top
|
|
224
|
+
top = @scroll + 1
|
|
212
225
|
bottom = [@scroll + page_size, @lines.size].min
|
|
213
226
|
edit_hint = @file ? " e edit" : ""
|
|
214
227
|
status = " #{top}-#{bottom}/#{@lines.size} j/k scroll g/G top/bottom#{edit_hint} q quit"
|
|
@@ -245,10 +258,10 @@ module Marvi
|
|
|
245
258
|
def build_attr(span)
|
|
246
259
|
attr = 0
|
|
247
260
|
attr |= ::Curses::A_BOLD if span.bold
|
|
248
|
-
attr |= ITALIC_ATTR
|
|
261
|
+
attr |= ITALIC_ATTR if span.italic
|
|
249
262
|
|
|
250
263
|
pair_key = if span.bg_color == :dark
|
|
251
|
-
span.color == :cyan ? :cyan_on_dark : :green_on_dark
|
|
264
|
+
(span.color == :cyan) ? :cyan_on_dark : :green_on_dark
|
|
252
265
|
elsif span.color
|
|
253
266
|
span.color
|
|
254
267
|
end
|
|
@@ -270,7 +283,7 @@ module Marvi
|
|
|
270
283
|
end
|
|
271
284
|
|
|
272
285
|
def scroll_by(delta)
|
|
273
|
-
@scroll =
|
|
286
|
+
@scroll = (@scroll + delta).clamp(0, max_scroll)
|
|
274
287
|
draw
|
|
275
288
|
end
|
|
276
289
|
end
|
data/lib/marvi/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: marvi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Mitsutaka Mimura
|
|
@@ -51,6 +51,20 @@ dependencies:
|
|
|
51
51
|
- - "~>"
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '1.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: unicode-display_width
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.0'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.0'
|
|
54
68
|
description: Renders Markdown with ANSI colors in pipes and an interactive curses
|
|
55
69
|
pager in TTY.
|
|
56
70
|
email:
|
|
@@ -60,6 +74,8 @@ executables:
|
|
|
60
74
|
extensions: []
|
|
61
75
|
extra_rdoc_files: []
|
|
62
76
|
files:
|
|
77
|
+
- ".claude/hooks/standardrb-check.sh"
|
|
78
|
+
- ".claude/settings.json"
|
|
63
79
|
- CHANGELOG.md
|
|
64
80
|
- CODE_OF_CONDUCT.md
|
|
65
81
|
- LICENSE.txt
|