marvi 0.1.0 → 0.1.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: 81a8d64fd49b33e6d17b7d1b2bf898bca77b6abb347ec5d4f6844f6b2969b647
4
- data.tar.gz: b8020fc1f24ab9806f1dc3d8b94baf4061e6ab329a0dc54598db747465e5c5d7
3
+ metadata.gz: 41854d878cdc8137d35b7c54ff30ffb13b90206869828a6db31bd53b8d279f1b
4
+ data.tar.gz: 396301e10a2d99ffd4c27526b1e792495c286450ae3e4c213ace369ea2b96706
5
5
  SHA512:
6
- metadata.gz: decbb5c0688e364d432aced0e156a9b0cdd1d33f3aeaa6c9bba5f7761f1bfd5182e740cc023b956926838261e1d66fa8a90f0666ded886d72dd397010c793ac7
7
- data.tar.gz: 5f969855ab9c5d49801bc4675ecd19d4c815a8e49e7532559bc6f46ffc6d76f3ae6f735e6f73c495bee7406ccbfcd091305127f29c00e0d0c859179c3447543b
6
+ metadata.gz: f7493dd561c802f8db70acf7f87fdc62f49aec9b74f9681197598e4781885598ce5a5ccf1acfad2975ab22a669556ada2282363b9d4342047ece848334686072
7
+ data.tar.gz: 2c3e48d560413d982c451a9c3a562c102ac06da98900cf1441f73c05077f9dfa8682c6d6d4ea3e44a43ef7387cbeefe67d949b651a9e1d65808a5634348ff17d
data/exe/marvi CHANGED
@@ -23,7 +23,7 @@ else
23
23
  end
24
24
 
25
25
  if $stdout.tty?
26
- Marvi::Renderer::Curses.new.render(markdown)
26
+ Marvi::Renderer::Curses.new.render(markdown, file: ARGV[0])
27
27
  else
28
28
  print Marvi::Renderer::ANSI.new.render(markdown)
29
29
  end
@@ -23,7 +23,8 @@ module Marvi
23
23
  when :header
24
24
  render_header(el)
25
25
  when :p
26
- [RichLine.new(render_inline_children(el)), RichLine.blank]
26
+ src = el.options[:location]
27
+ [RichLine.new(render_inline_children(el), source_line: src), RichLine.blank]
27
28
  when :ul
28
29
  el.children.flat_map { |child| render_block(child, indent: indent, list_type: :ul) } + [RichLine.blank]
29
30
  when :ol
@@ -37,7 +38,8 @@ module Marvi
37
38
  when :blockquote
38
39
  render_blockquote(el)
39
40
  when :hr
40
- [RichLine.new([Span.new(text: "─" * 60, color: :cyan)]), RichLine.blank]
41
+ src = el.options[:location]
42
+ [RichLine.new([Span.new(text: "─" * 60, color: :cyan)], source_line: src), RichLine.blank]
41
43
  when :table
42
44
  render_table(el)
43
45
  when :blank
@@ -50,17 +52,19 @@ module Marvi
50
52
  def render_header(el)
51
53
  level = el.options[:level]
52
54
  color = HEADER_COLORS[level - 1]
55
+ src = el.options[:location]
53
56
  prefix = Span.new(text: "#" * level + " ", bold: true, color: color)
54
57
  content = render_inline_children(el).map do |s|
55
58
  Span.new(text: s.text, bold: true, italic: s.italic, color: s.color || color, bg_color: s.bg_color)
56
59
  end
57
- [RichLine.new([prefix] + content), RichLine.blank]
60
+ [RichLine.new([prefix] + content, source_line: src), RichLine.blank]
58
61
  end
59
62
 
60
63
  def render_li(el, indent:, list_type:, list_index:)
61
64
  bullet = list_type == :ol ? "#{list_index}." : "•"
62
65
  prefix = Span.new(text: "#{" " * indent}#{bullet} ", color: :cyan)
63
- lines = []
66
+ src = el.options[:location]
67
+ lines = []
64
68
 
65
69
  el.children.each do |child|
66
70
  case child.type
@@ -70,13 +74,13 @@ module Marvi
70
74
  lines += nested
71
75
  when :p
72
76
  if lines.empty?
73
- lines << RichLine.new([prefix] + render_inline_children(child))
77
+ lines << RichLine.new([prefix] + render_inline_children(child), source_line: src)
74
78
  else
75
79
  lines += render_block(child)
76
80
  end
77
81
  else
78
82
  if lines.empty?
79
- lines << RichLine.new([prefix] + render_inline(child))
83
+ lines << RichLine.new([prefix] + render_inline(child), source_line: src)
80
84
  else
81
85
  lines << RichLine.new(render_inline(child))
82
86
  end
@@ -86,23 +90,27 @@ module Marvi
86
90
  end
87
91
 
88
92
  def render_codeblock(el)
93
+ src = el.options[:location]
89
94
  lang = el.options[:lang]
90
95
  lines = []
91
- lines << RichLine.new([Span.new(text: lang, color: :yellow)]) if lang
92
- el.value.chomp.split("\n").each do |line|
93
- lines << RichLine.new([Span.new(text: " #{line}", color: :green, bg_color: :dark)])
96
+ lines << RichLine.new([Span.new(text: lang, color: :yellow)], source_line: src) if lang
97
+ el.value.chomp.split("\n").each_with_index do |line, i|
98
+ line_src = src ? src + i + (lang ? 1 : 0) : nil
99
+ lines << RichLine.new([Span.new(text: " #{line}", color: :green, bg_color: :dark)], source_line: line_src)
94
100
  end
95
101
  lines << RichLine.blank
96
102
  lines
97
103
  end
98
104
 
99
105
  def render_blockquote(el)
100
- inner = el.children.flat_map { |child| render_block(child) }
106
+ inner = el.children.flat_map { |child| render_block(child) }
101
107
  prefix = Span.new(text: "│ ", color: :cyan)
102
- inner.map { |line| RichLine.new([prefix] + line.spans) } + [RichLine.blank]
108
+ # preserve source_line from inner lines
109
+ inner.map { |line| RichLine.new([prefix] + line.spans, source_line: line.source_line) } + [RichLine.blank]
103
110
  end
104
111
 
105
112
  def render_table(el)
113
+ src = el.options[:location]
106
114
  rows = el.children.flat_map(&:children)
107
115
  header_row = el.children.find { |s| s.type == :thead }&.children&.first
108
116
 
@@ -112,7 +120,7 @@ module Marvi
112
120
 
113
121
  lines = []
114
122
  top = col_widths.map { |w| "─" * (w + 2) }.join("┬")
115
- lines << RichLine.new([Span.new(text: "┌#{top}┐", color: :cyan)])
123
+ lines << RichLine.new([Span.new(text: "┌#{top}┐", color: :cyan)], source_line: src)
116
124
 
117
125
  rows.each_with_index do |row, ri|
118
126
  is_header = row == header_row
@@ -14,10 +14,11 @@ module Marvi
14
14
  end
15
15
 
16
16
  class RichLine
17
- attr_reader :spans
17
+ attr_reader :spans, :source_line
18
18
 
19
- def initialize(spans = [])
19
+ def initialize(spans = [], source_line: nil)
20
20
  @spans = spans
21
+ @source_line = source_line
21
22
  end
22
23
 
23
24
  def plain_text
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "curses"
4
+ require "shellwords"
4
5
 
5
6
  module Marvi
6
7
  module Renderer
@@ -17,9 +18,11 @@ module Marvi
17
18
 
18
19
  ITALIC_ATTR = (defined?(::Curses::A_ITALIC) ? ::Curses::A_ITALIC : 0)
19
20
 
20
- def render(markdown)
21
- @lines = ASTWalker.new.walk(markdown)
22
- @scroll = 0
21
+ def render(markdown, file: nil)
22
+ @file = file
23
+ @markdown = markdown
24
+ @lines = ASTWalker.new.walk(markdown)
25
+ @scroll = 0
23
26
 
24
27
  ::Curses.init_screen
25
28
  ::Curses.start_color
@@ -51,18 +54,68 @@ module Marvi
51
54
 
52
55
  def handle_key(key)
53
56
  case key
54
- when "q", "Q", 27 then throw :quit
55
- when "j", ::Curses::Key::DOWN then scroll_by(1)
56
- when "k", ::Curses::Key::UP then scroll_by(-1)
57
- when "d" then scroll_by(page_size / 2)
58
- when "u" then scroll_by(-page_size / 2)
57
+ when "q", "Q", 27 then throw :quit
58
+ when "j", ::Curses::Key::DOWN then scroll_by(1)
59
+ when "k", ::Curses::Key::UP then scroll_by(-1)
60
+ when "d" then scroll_by(page_size / 2)
61
+ when "u" then scroll_by(-page_size / 2)
59
62
  when "f", " ", ::Curses::Key::NPAGE then scroll_by(page_size)
60
- when "b", ::Curses::Key::PPAGE then scroll_by(-page_size)
61
- when "g" then @scroll = 0; draw
62
- when "G" then @scroll = max_scroll; draw
63
+ when "b", ::Curses::Key::PPAGE then scroll_by(-page_size)
64
+ when "g" then @scroll = 0; draw
65
+ when "G" then @scroll = max_scroll; draw
66
+ when "e" then launch_editor if @file
63
67
  end
64
68
  end
65
69
 
70
+ def launch_editor
71
+ editor = ENV["EDITOR"] || ENV["VISUAL"] || "vi"
72
+ line = current_source_line
73
+ cmd = build_editor_command(editor, @file, line)
74
+
75
+ ::Curses.close_screen
76
+ system(cmd)
77
+ reload
78
+ reinit_curses
79
+ draw
80
+ end
81
+
82
+ def reload
83
+ @markdown = File.read(@file)
84
+ @lines = ASTWalker.new.walk(@markdown)
85
+ @scroll = [@scroll, max_scroll].min
86
+ end
87
+
88
+ def reinit_curses
89
+ ::Curses.init_screen
90
+ ::Curses.start_color
91
+ ::Curses.use_default_colors
92
+ ::Curses.noecho
93
+ ::Curses.cbreak
94
+ ::Curses.stdscr.keypad(true)
95
+ setup_colors
96
+ end
97
+
98
+ def build_editor_command(editor, file, line)
99
+ base = File.basename(editor.split.first)
100
+ escaped = Shellwords.escape(file)
101
+ case base
102
+ when "code"
103
+ "#{editor} --goto #{escaped}:#{line}"
104
+ when "subl", "sublime_text"
105
+ "#{editor} #{escaped}:#{line}"
106
+ else
107
+ # vim, nvim, nano, emacs, micro, etc.
108
+ "#{editor} +#{line} #{escaped}"
109
+ end
110
+ end
111
+
112
+ def current_source_line
113
+ visible_lines.each { |line| return line.source_line if line.source_line }
114
+ # fall back to searching upward from scroll position
115
+ @scroll.downto(0) { |i| return @lines[i].source_line if @lines[i]&.source_line }
116
+ 1
117
+ end
118
+
66
119
  def draw
67
120
  ::Curses.clear
68
121
  visible_lines.each_with_index do |line, row|
@@ -78,7 +131,8 @@ module Marvi
78
131
  ::Curses.attron(::Curses.color_pair(COLOR_PAIRS[:cyan])) do
79
132
  top = @scroll + 1
80
133
  bottom = [@scroll + page_size, @lines.size].min
81
- status = " #{top}-#{bottom}/#{@lines.size} j/k scroll g/G top/bottom q quit"
134
+ edit_hint = @file ? " e edit" : ""
135
+ status = " #{top}-#{bottom}/#{@lines.size} j/k scroll g/G top/bottom#{edit_hint} q quit"
82
136
  ::Curses.addstr(status.ljust(::Curses.cols)[0, ::Curses.cols])
83
137
  end
84
138
  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.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
data/marvi-0.1.0.gem ADDED
Binary file
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.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mitsutaka Mimura
@@ -73,6 +73,7 @@ files:
73
73
  - lib/marvi/renderer/ansi.rb
74
74
  - lib/marvi/renderer/curses.rb
75
75
  - lib/marvi/version.rb
76
+ - marvi-0.1.0.gem
76
77
  - sig/marvi.rbs
77
78
  homepage: https://github.com/takkanm/marvi
78
79
  licenses: