marvi 0.4.2 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5ff0488e214dd427a028dabe9d240bee43e48ac27cef895e7f8584b41695435
4
- data.tar.gz: 71dcd3de576d8926b90cae6ec4b79732be3627b85922351585b624b064eafb94
3
+ metadata.gz: 99ac52daecfd730bdd05ef63523075b5e4ad3229637208a99a083199518864dd
4
+ data.tar.gz: 4daae68331c1edf707b6c520c3defe941e9273b937e6618711b145e13568eed4
5
5
  SHA512:
6
- metadata.gz: 1f90d2999b6336d1fa26f8ef441c8e51e551f4f7d229633053acc9ab8418fe115bcd6ea3f8fa3b67ce678781b29198acc33fd1ed69f591611a47bc97b456901c
7
- data.tar.gz: 82bcdfbe5166bcd2f48d2367da468df0290c89e831d16fa06dced445dba98a2ba0846c659a87e9f69a1dd2e68c900f9cccdaaa57790bb0f35ddcb2c4b8d8dc0e
6
+ metadata.gz: 775e51510484dac676928c537175db222f682d3d66c74691e248616442f042f294ae896c270531475e1cdc989dde1c1a5cb957df6acdf4159e0157b4d51f3d1f
7
+ data.tar.gz: b00bffa8ef8adaa9e5dab84df060e31d96ffad92e473bfb544df2962c3986cb26db06c35299902a4e341dd9c91422642b06ea9e3ab15e1c8089e64f64f352d56
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.5.0] - 2026-06-02
4
+
5
+ - Syntax-highlight fenced code blocks via [Rouge](https://github.com/rouge-ruby/rouge). Blocks without a language (or with an unknown one) fall back to the previous single-color rendering.
6
+ - Extend the dark code block background to the longest line so the block reads as a solid pane instead of a ragged shape.
7
+
3
8
  ## [0.4.2] - 2026-05-31
4
9
 
5
10
  - Add left/right padding in the curses pager so content no longer sits flush against the terminal edges. Padding scales with terminal width.
@@ -112,11 +112,24 @@ module Marvi
112
112
  lang = el.options[:lang]
113
113
  lines = []
114
114
  lines << RichLine.new([Span.new(text: lang, color: :yellow)], source_line: src) if lang
115
- el.value.chomp.split("\n").each_with_index do |line, i|
116
- line_src = if src
117
- src + i + (lang ? 1 : 0)
115
+
116
+ code = el.value.chomp
117
+ unless code.empty?
118
+ line_offset = lang ? 1 : 0
119
+ indent = Span.new(text: " ", bg_color: :dark)
120
+ indent_width = spans_display_width([indent])
121
+
122
+ highlighted = Highlighter.lines(code, lang)
123
+ content_widths = highlighted.map { |spans| spans_display_width(spans) }
124
+ block_width = [content_widths.max || 0, @max_width - indent_width].min
125
+
126
+ highlighted.each_with_index do |spans, i|
127
+ line_src = src ? src + i + line_offset : nil
128
+ line_spans = [indent] + spans
129
+ pad = block_width - content_widths[i]
130
+ line_spans << Span.new(text: " " * pad, bg_color: :dark) if pad > 0
131
+ lines << RichLine.new(line_spans, source_line: line_src)
118
132
  end
119
- lines << RichLine.new([Span.new(text: " #{line}", color: :green, bg_color: :dark)], source_line: line_src)
120
133
  end
121
134
  lines << RichLine.blank
122
135
  lines
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rouge"
4
+
5
+ module Marvi
6
+ class Highlighter
7
+ DEFAULT_COLOR = :white
8
+ FALLBACK_COLOR = :green
9
+
10
+ # Ordered so longer qualnames win over their prefixes (e.g. "Literal.String" before "Literal").
11
+ TOKEN_COLOR_RULES = [
12
+ ["Comment", :cyan],
13
+ ["Keyword", :yellow],
14
+ ["Literal.String", :green],
15
+ ["Literal.Number", :magenta],
16
+ ["Literal", :magenta],
17
+ ["Name.Function", :cyan],
18
+ ["Name.Class", :cyan],
19
+ ["Name.Namespace", :cyan],
20
+ ["Name.Tag", :cyan],
21
+ ["Name.Attribute", :cyan],
22
+ ["Name.Decorator", :cyan],
23
+ ["Name.Constant", :magenta],
24
+ ["Name.Builtin", :magenta],
25
+ ["Operator", :white],
26
+ ["Punctuation", :white],
27
+ ["Error", :magenta]
28
+ ].freeze
29
+
30
+ COMMENT_PREFIX = "Comment"
31
+
32
+ def self.lines(code, lang, bg_color: :dark)
33
+ lexer = resolve_lexer(lang)
34
+ return fallback(code, bg_color) if lexer.nil?
35
+ tokens_to_lines(lexer.lex(code), bg_color)
36
+ end
37
+
38
+ def self.resolve_lexer(lang)
39
+ return nil if lang.nil? || lang.empty?
40
+ Rouge::Lexer.find(lang)
41
+ rescue
42
+ nil
43
+ end
44
+
45
+ def self.fallback(code, bg_color)
46
+ code.split("\n", -1).map { |line| [Span.new(text: line, color: FALLBACK_COLOR, bg_color: bg_color)] }
47
+ end
48
+
49
+ def self.tokens_to_lines(tokens, bg_color)
50
+ current = []
51
+ lines = []
52
+ tokens.each do |tok, val|
53
+ color = token_color(tok)
54
+ italic = comment?(tok)
55
+ val.split("\n", -1).each_with_index do |part, idx|
56
+ if idx > 0
57
+ lines << current
58
+ current = []
59
+ end
60
+ next if part.empty?
61
+ current << Span.new(text: part, italic: italic, color: color, bg_color: bg_color)
62
+ end
63
+ end
64
+ lines << current
65
+ lines
66
+ end
67
+
68
+ def self.token_color(token)
69
+ qual = token.qualname
70
+ TOKEN_COLOR_RULES.each do |prefix, color|
71
+ return color if qual == prefix || qual.start_with?("#{prefix}.")
72
+ end
73
+ DEFAULT_COLOR
74
+ end
75
+
76
+ def self.comment?(token)
77
+ qual = token.qualname
78
+ qual == COMMENT_PREFIX || qual.start_with?("#{COMMENT_PREFIX}.")
79
+ end
80
+ end
81
+ end
@@ -12,8 +12,11 @@ module Marvi
12
12
  yellow: 3,
13
13
  magenta: 4,
14
14
  white: 5,
15
- green_on_dark: 6,
16
- cyan_on_dark: 7
15
+ cyan_on_dark: 6,
16
+ green_on_dark: 7,
17
+ yellow_on_dark: 8,
18
+ magenta_on_dark: 9,
19
+ white_on_dark: 10
17
20
  }.freeze
18
21
 
19
22
  ITALIC_ATTR = (defined?(::Curses::A_ITALIC) ? ::Curses::A_ITALIC : 0)
@@ -100,8 +103,11 @@ module Marvi
100
103
  ::Curses.init_pair(COLOR_PAIRS[:yellow], ::Curses::COLOR_YELLOW, -1)
101
104
  ::Curses.init_pair(COLOR_PAIRS[:magenta], ::Curses::COLOR_MAGENTA, -1)
102
105
  ::Curses.init_pair(COLOR_PAIRS[:white], ::Curses::COLOR_WHITE, -1)
103
- ::Curses.init_pair(COLOR_PAIRS[:green_on_dark], ::Curses::COLOR_GREEN, ::Curses::COLOR_BLACK)
104
106
  ::Curses.init_pair(COLOR_PAIRS[:cyan_on_dark], ::Curses::COLOR_CYAN, ::Curses::COLOR_BLACK)
107
+ ::Curses.init_pair(COLOR_PAIRS[:green_on_dark], ::Curses::COLOR_GREEN, ::Curses::COLOR_BLACK)
108
+ ::Curses.init_pair(COLOR_PAIRS[:yellow_on_dark], ::Curses::COLOR_YELLOW, ::Curses::COLOR_BLACK)
109
+ ::Curses.init_pair(COLOR_PAIRS[:magenta_on_dark], ::Curses::COLOR_MAGENTA, ::Curses::COLOR_BLACK)
110
+ ::Curses.init_pair(COLOR_PAIRS[:white_on_dark], ::Curses::COLOR_WHITE, ::Curses::COLOR_BLACK)
105
111
  end
106
112
 
107
113
  def handle_key(key)
@@ -278,7 +284,7 @@ module Marvi
278
284
  attr |= ITALIC_ATTR if span.italic
279
285
 
280
286
  pair_key = if span.bg_color == :dark
281
- (span.color == :cyan) ? :cyan_on_dark : :green_on_dark
287
+ :"#{span.color || :green}_on_dark"
282
288
  elsif span.color
283
289
  span.color
284
290
  end
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.4.2"
4
+ VERSION = "0.5.0"
5
5
  end
data/lib/marvi.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require_relative "marvi/version"
4
4
  require_relative "marvi/ansi"
5
5
  require_relative "marvi/document"
6
+ require_relative "marvi/highlighter"
6
7
  require_relative "marvi/ast_walker"
7
8
  require_relative "marvi/renderer/ansi"
8
9
  require_relative "marvi/renderer/curses"
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.2
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitsutaka Mimura
@@ -65,6 +65,26 @@ dependencies:
65
65
  - - "~>"
66
66
  - !ruby/object:Gem::Version
67
67
  version: '3.0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rouge
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '4.0'
75
+ - - "<"
76
+ - !ruby/object:Gem::Version
77
+ version: '6'
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '4.0'
85
+ - - "<"
86
+ - !ruby/object:Gem::Version
87
+ version: '6'
68
88
  description: Renders Markdown with ANSI colors in pipes and an interactive curses
69
89
  pager in TTY.
70
90
  email:
@@ -86,6 +106,7 @@ files:
86
106
  - lib/marvi/ansi.rb
87
107
  - lib/marvi/ast_walker.rb
88
108
  - lib/marvi/document.rb
109
+ - lib/marvi/highlighter.rb
89
110
  - lib/marvi/renderer/ansi.rb
90
111
  - lib/marvi/renderer/curses.rb
91
112
  - lib/marvi/version.rb