marvi 0.3.0 → 0.4.1

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: c5df9bb69ae060cf562d656a78f4aa152223a4ad58178af6b6b18891e19cb525
4
- data.tar.gz: 1590ce11f925c30f25e256e1e0f0571355a4a4fd7690cc636310d9bf20e349eb
3
+ metadata.gz: 519a600dd8ba2e4c6934cf57f37914337ce252ced2a51d6b05dff2ab2c1dda4b
4
+ data.tar.gz: '02669ac614cde421459555a5068429e1371036dd90eb549f5a90d6f3e3aea90c'
5
5
  SHA512:
6
- metadata.gz: 1541589372fbdd04e4dbf7fe929de77788f7308c4abdf31ed8fea657712de058d1e38f7a48000c1122405b0c8a44f679b57946937ed8c779dcc81e2e7f4cfd10
7
- data.tar.gz: 8637f420f782945d396f8dfa97ec3f4b748ffd056bb43baf23c6e6adbd54c6e8d9f8b3dbe896a1540faf46c6affa772cb7637bc929a9c9fe60e626c0420392e3
6
+ metadata.gz: 37a67589e93f21f02d2c7cec09130e2a63ce83e88ebe21c61cf643b4810506ff4ece12ca39d8c36a40cbccaca35ecf8984e1c8276f953b0e17bf2b9caf4f98de
7
+ data.tar.gz: 12fa9234e6fb3dd1b13b72b5008a239acea242b049ae4086d6e2a8565435b4727fd5850c5766d79f05bbc48332719f7a2b97609725c8834ca1f6083d4a7acf02
data/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.1] - 2026-05-26
4
+
5
+ - Wrap long text in bulleted/ordered lists, paragraphs, headers, and blockquotes so it no longer overflows the terminal width. List items and headers use a hanging indent for continuation lines. (#1)
6
+
7
+ ## [0.4.0] - 2026-05-18
8
+
9
+ - Bind `Ctrl-D` / `Ctrl-U` for vim-style half-page scrolling in the curses pager.
10
+
3
11
  ## [0.3.0] - 2026-05-18
4
12
 
5
13
  - Render tables correctly when cells contain East Asian wide characters and emoji (uses `unicode-display_width`).
@@ -29,7 +29,8 @@ module Marvi
29
29
  render_header(el)
30
30
  when :p
31
31
  src = el.options[:location]
32
- [RichLine.new(render_inline_children(el), source_line: src), RichLine.blank]
32
+ wrapped = wrap_spans(render_inline_children(el), @max_width)
33
+ wrapped.each_with_index.map { |spans, i| RichLine.new(spans, source_line: (i.zero? ? src : nil)) } + [RichLine.blank]
33
34
  when :ul
34
35
  el.children.flat_map { |child| render_block(child, indent: indent, list_type: :ul) } + [RichLine.blank]
35
36
  when :ol
@@ -62,12 +63,15 @@ module Marvi
62
63
  content = render_inline_children(el).map do |s|
63
64
  Span.new(text: s.text, bold: true, italic: s.italic, color: s.color || color, bg_color: s.bg_color)
64
65
  end
65
- [RichLine.new([prefix] + content, source_line: src), RichLine.blank]
66
+ wrap_with_prefix([prefix], content, @max_width, source_line: src) + [RichLine.blank]
66
67
  end
67
68
 
68
69
  def render_li(el, indent:, list_type:, list_index:)
69
70
  bullet = (list_type == :ol) ? "#{list_index}." : "•"
70
71
  prefix = Span.new(text: "#{" " * indent}#{bullet} ", color: :cyan)
72
+ prefix_width = spans_display_width([prefix])
73
+ hanging = Span.new(text: " " * prefix_width)
74
+ inner_width = [@max_width - prefix_width, MIN_COL_WIDTH].max
71
75
  src = el.options[:location]
72
76
  lines = []
73
77
 
@@ -78,16 +82,25 @@ module Marvi
78
82
  nested.pop while nested.last&.plain_text&.empty?
79
83
  lines += nested
80
84
  when :p
85
+ content_spans = render_inline_children(child)
81
86
  if lines.empty?
82
- lines << RichLine.new([prefix] + render_inline_children(child), source_line: src)
87
+ lines += wrap_with_prefix([prefix], content_spans, @max_width, source_line: src)
83
88
  else
84
- lines += render_block(child)
89
+ child_src = child.options[:location]
90
+ wrapped = wrap_spans(content_spans, inner_width)
91
+ wrapped.each_with_index do |spans, i|
92
+ lines << RichLine.new([hanging] + spans, source_line: (i.zero? ? child_src : nil))
93
+ end
94
+ lines << RichLine.blank
85
95
  end
86
96
  else
87
- lines << if lines.empty?
88
- RichLine.new([prefix] + render_inline(child), source_line: src)
97
+ content_spans = render_inline(child)
98
+ if lines.empty?
99
+ lines += wrap_with_prefix([prefix], content_spans, @max_width, source_line: src)
89
100
  else
90
- RichLine.new(render_inline(child))
101
+ wrap_spans(content_spans, inner_width).each do |spans|
102
+ lines << RichLine.new([hanging] + spans)
103
+ end
91
104
  end
92
105
  end
93
106
  end
@@ -110,9 +123,14 @@ module Marvi
110
123
  end
111
124
 
112
125
  def render_blockquote(el)
113
- inner = el.children.flat_map { |child| render_block(child) }
114
126
  prefix = Span.new(text: "│ ", color: :cyan)
115
- # preserve source_line from inner lines
127
+ prefix_width = spans_display_width([prefix])
128
+ # Reduce @max_width while rendering inner content so the │ prefix fits within @max_width
129
+ # without forcing a second wrap pass on already-wrapped lines.
130
+ saved_width = @max_width
131
+ @max_width = [saved_width - prefix_width, MIN_COL_WIDTH].max
132
+ inner = el.children.flat_map { |child| render_block(child) }
133
+ @max_width = saved_width
116
134
  inner.map { |line| RichLine.new([prefix] + line.spans, source_line: line.source_line) } + [RichLine.blank]
117
135
  end
118
136
 
@@ -177,6 +195,17 @@ module Marvi
177
195
  shrunk
178
196
  end
179
197
 
198
+ def wrap_with_prefix(prefix_spans, content_spans, max_width, source_line: nil)
199
+ prefix_width = spans_display_width(prefix_spans)
200
+ inner_width = [max_width - prefix_width, MIN_COL_WIDTH].max
201
+ wrapped = wrap_spans(content_spans, inner_width)
202
+ indent = Span.new(text: " " * prefix_width)
203
+ wrapped.each_with_index.map do |spans, i|
204
+ line_prefix = i.zero? ? prefix_spans : [indent]
205
+ RichLine.new(line_prefix + spans, source_line: (i.zero? ? source_line : nil))
206
+ end
207
+ end
208
+
180
209
  def wrap_spans(spans, width)
181
210
  lines = [[]]
182
211
  current_width = 0
@@ -20,6 +20,9 @@ module Marvi
20
20
 
21
21
  FILE_POLL_INTERVAL_MS = 500
22
22
 
23
+ CTRL_D = 4
24
+ CTRL_U = 21
25
+
23
26
  def render(markdown, file: nil)
24
27
  @file = file
25
28
  @markdown = markdown
@@ -103,8 +106,8 @@ module Marvi
103
106
  when "q", "Q", 27 then throw :quit
104
107
  when "j", ::Curses::Key::DOWN then scroll_by(1)
105
108
  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)
109
+ when "d", CTRL_D then scroll_by(page_size / 2)
110
+ when "u", CTRL_U then scroll_by(-page_size / 2)
108
111
  when "f", " ", ::Curses::Key::NPAGE then scroll_by(page_size)
109
112
  when "b", ::Curses::Key::PPAGE then scroll_by(-page_size)
110
113
  when "g" then @scroll = 0
data/lib/marvi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Marvi
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.1"
5
5
  end
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.3.0
4
+ version: 0.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitsutaka Mimura