marvi 0.4.2 → 0.6.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: 0e3091d68f7f5ccd8b131401fd1dce1b010e7d8108125430de3c47bd68b403d4
4
+ data.tar.gz: f52d7849ff7982cb1b988b0e5e5e1c9a496661c56e689bd008fe34347b84dd7f
5
5
  SHA512:
6
- metadata.gz: 1f90d2999b6336d1fa26f8ef441c8e51e551f4f7d229633053acc9ab8418fe115bcd6ea3f8fa3b67ce678781b29198acc33fd1ed69f591611a47bc97b456901c
7
- data.tar.gz: 82bcdfbe5166bcd2f48d2367da468df0290c89e831d16fa06dced445dba98a2ba0846c659a87e9f69a1dd2e68c900f9cccdaaa57790bb0f35ddcb2c4b8d8dc0e
6
+ metadata.gz: 54848bb6e78b5833c96eb6aaea4de9869c5dcd18c381c9b05dc69e1d2d9528f93be9846068bf89acc56f569c54b37e96bbd7868c070802b0ae644f98fa8914ed
7
+ data.tar.gz: d645f16e52d118bf57ef02dc5009cede7e7400377e7c6640030c78a36392ff37adab60a2bfbe8f88e749107bed3f57b153ab8f50754fc22ba822ff948adcbfcf
data/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.6.0] - 2026-06-11
4
+
5
+ - Render Mermaid fenced code blocks as Unicode box-drawing art. Supports `flowchart`/`graph` (TD/TB/LR/RL), `sequenceDiagram`, `classDiagram`, and `stateDiagram`/`stateDiagram-v2`. Unsupported diagram types, malformed syntax, and over-width output fall back to the highlighted code block.
6
+
7
+ ## [0.5.0] - 2026-06-02
8
+
9
+ - 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.
10
+ - Extend the dark code block background to the longest line so the block reads as a solid pane instead of a ragged shape.
11
+
3
12
  ## [0.4.2] - 2026-05-31
4
13
 
5
14
  - Add left/right padding in the curses pager so content no longer sits flush against the terminal edges. Padding scales with terminal width.
@@ -110,13 +110,37 @@ module Marvi
110
110
  def render_codeblock(el)
111
111
  src = el.options[:location]
112
112
  lang = el.options[:lang]
113
+
114
+ if lang == "mermaid"
115
+ diagram = Diagram.render(el.value.chomp, max_width: @max_width)
116
+ if diagram
117
+ lines = diagram.each_with_index.map do |spans, i|
118
+ RichLine.new(spans, source_line: (i.zero? ? src : nil))
119
+ end
120
+ return lines + [RichLine.blank]
121
+ end
122
+ end
123
+
113
124
  lines = []
114
125
  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)
126
+
127
+ code = el.value.chomp
128
+ unless code.empty?
129
+ line_offset = lang ? 1 : 0
130
+ indent = Span.new(text: " ", bg_color: :dark)
131
+ indent_width = spans_display_width([indent])
132
+
133
+ highlighted = Highlighter.lines(code, lang)
134
+ content_widths = highlighted.map { |spans| spans_display_width(spans) }
135
+ block_width = [content_widths.max || 0, @max_width - indent_width].min
136
+
137
+ highlighted.each_with_index do |spans, i|
138
+ line_src = src ? src + i + line_offset : nil
139
+ line_spans = [indent] + spans
140
+ pad = block_width - content_widths[i]
141
+ line_spans << Span.new(text: " " * pad, bg_color: :dark) if pad > 0
142
+ lines << RichLine.new(line_spans, source_line: line_src)
118
143
  end
119
- lines << RichLine.new([Span.new(text: " #{line}", color: :green, bg_color: :dark)], source_line: line_src)
120
144
  end
121
145
  lines << RichLine.blank
122
146
  lines
Binary file
@@ -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.6.0"
5
5
  end
data/lib/marvi.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require_relative "marvi/version"
4
4
  require_relative "marvi/ansi"
5
5
  require_relative "marvi/document"
6
+ require_relative "marvi/highlighter"
7
+ require_relative "marvi/diagram"
6
8
  require_relative "marvi/ast_walker"
7
9
  require_relative "marvi/renderer/ansi"
8
10
  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.6.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:
@@ -85,7 +105,9 @@ files:
85
105
  - lib/marvi.rb
86
106
  - lib/marvi/ansi.rb
87
107
  - lib/marvi/ast_walker.rb
108
+ - lib/marvi/diagram.rb
88
109
  - lib/marvi/document.rb
110
+ - lib/marvi/highlighter.rb
89
111
  - lib/marvi/renderer/ansi.rb
90
112
  - lib/marvi/renderer/curses.rb
91
113
  - lib/marvi/version.rb