Class: Marvi::Renderer::Curses::Tab

Inherits:
Object
  • Object
show all
Defined in:
lib/marvi/renderer/curses/tab.rb

Overview

Per-document state for one open file: source markdown, walked lines, scroll position, search state, and file-watch bookkeeping. All layout inputs (content width, page size) are passed in by the renderer so this class stays free of curses calls and can be tested headlessly.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file: nil, markdown: nil) ⇒ Tab

Returns a new instance of Tab.



14
15
16
17
18
19
20
21
22
# File 'lib/marvi/renderer/curses/tab.rb', line 14

def initialize(file: nil, markdown: nil)
  @file = file
  @markdown = markdown || read_file
  @scroll = 0
  @lines = []
  @walked_width = nil
  clear_search
  mark_reloaded
end

Instance Attribute Details

#current_matchObject (readonly)

Returns the value of attribute current_match.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def current_match
  @current_match
end

#fileObject (readonly)

Returns the value of attribute file.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def file
  @file
end

#linesObject (readonly)

Returns the value of attribute lines.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def lines
  @lines
end

#markdownObject (readonly)

Returns the value of attribute markdown.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def markdown
  @markdown
end

#matchesObject (readonly)

Returns the value of attribute matches.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def matches
  @matches
end

#scrollObject

Returns the value of attribute scroll.



12
13
14
# File 'lib/marvi/renderer/curses/tab.rb', line 12

def scroll
  @scroll
end

#search_queryObject (readonly)

Returns the value of attribute search_query.



11
12
13
# File 'lib/marvi/renderer/curses/tab.rb', line 11

def search_query
  @search_query
end

Instance Method Details

#check_file_updatedObject

Returns true when the file changed on disk since the last check and the updated flag was newly raised.



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/marvi/renderer/curses/tab.rb', line 77

def check_file_updated
  return false unless @file

  mtime = current_mtime
  return false if mtime.nil? || mtime == @last_mtime

  @last_mtime = mtime
  return false if @file_updated

  @file_updated = true
  true
end

#clamp_scroll(page_size) ⇒ Object



54
55
56
# File 'lib/marvi/renderer/curses/tab.rb', line 54

def clamp_scroll(page_size)
  @scroll = @scroll.clamp(0, max_scroll(page_size))
end

#clear_searchObject



111
112
113
114
115
# File 'lib/marvi/renderer/curses/tab.rb', line 111

def clear_search
  @search_query = nil
  @matches = []
  @current_match = nil
end

#commit_searchObject



107
108
109
# File 'lib/marvi/renderer/curses/tab.rb', line 107

def commit_search
  clear_search if @search_query.to_s.empty? || @matches.empty?
end

#current_source_line(page_size) ⇒ Object



66
67
68
69
70
71
# File 'lib/marvi/renderer/curses/tab.rb', line 66

def current_source_line(page_size)
  visible_lines(page_size).each { |line| return line.source_line if line.source_line }
  # fall back to searching upward from scroll position
  @scroll.downto(0) { |i| return @lines[i].source_line if @lines[i]&.source_line }
  1
end

#ensure_walked(width, page_size) ⇒ Object

Walk lazily: tabs that are not visible keep their lines from the last width they were displayed at and re-walk only when shown again.



30
31
32
33
34
35
36
37
# File 'lib/marvi/renderer/curses/tab.rb', line 30

def ensure_walked(width, page_size)
  return if @walked_width == width

  @walked_width = width
  @lines = ASTWalker.new.walk(@markdown, max_width: width)
  clamp_scroll(page_size)
  refresh_search_after_rewalk(page_size)
end

#file_updated?Boolean

Returns:

  • (Boolean)


90
91
92
# File 'lib/marvi/renderer/curses/tab.rb', line 90

def file_updated?
  @file_updated
end

#highlight_ranges_for(line_index) ⇒ Object



127
128
129
130
131
132
133
134
135
# File 'lib/marvi/renderer/curses/tab.rb', line 127

def highlight_ranges_for(line_index)
  return nil if @matches.empty?

  ranges = []
  @matches.each_with_index do |(li, start, finish), i|
    ranges << [start, finish, i == @current_match] if li == line_index
  end
  ranges.empty? ? nil : ranges
end

#jump_match(direction, page_size) ⇒ Object



117
118
119
120
121
122
123
124
125
# File 'lib/marvi/renderer/curses/tab.rb', line 117

def jump_match(direction, page_size)
  return if @matches.empty?

  if @current_match.nil?
    focus_match_from(@scroll, page_size)
  else
    set_current_match(@current_match + direction, page_size)
  end
end

#labelObject



24
25
26
# File 'lib/marvi/renderer/curses/tab.rb', line 24

def label
  @file ? File.basename(@file) : "(stdin)"
end

#mark_reloadedObject



94
95
96
97
# File 'lib/marvi/renderer/curses/tab.rb', line 94

def mark_reloaded
  @last_mtime = current_mtime
  @file_updated = false
end

#max_scroll(page_size) ⇒ Object



50
51
52
# File 'lib/marvi/renderer/curses/tab.rb', line 50

def max_scroll(page_size)
  [@lines.size - page_size, 0].max
end

#reload(width, page_size) ⇒ Object



44
45
46
47
48
# File 'lib/marvi/renderer/curses/tab.rb', line 44

def reload(width, page_size)
  @markdown = read_file
  rewalk(width, page_size)
  mark_reloaded
end

#rewalk(width, page_size) ⇒ Object



39
40
41
42
# File 'lib/marvi/renderer/curses/tab.rb', line 39

def rewalk(width, page_size)
  @walked_width = nil
  ensure_walked(width, page_size)
end

#scroll_by(delta, page_size) ⇒ Object



58
59
60
# File 'lib/marvi/renderer/curses/tab.rb', line 58

def scroll_by(delta, page_size)
  @scroll = (@scroll + delta).clamp(0, max_scroll(page_size))
end

#search_hintObject



137
138
139
140
141
142
143
# File 'lib/marvi/renderer/curses/tab.rb', line 137

def search_hint
  return "  n/N next/prev" if @search_query.nil? || @search_query.empty?
  return "  no matches: #{@search_query}" if @matches.empty?

  position = @current_match ? @current_match + 1 : 0
  "  [#{position}/#{@matches.size}] n/N"
end

#update_search(query, page_size) ⇒ Object

--- search ---



101
102
103
104
105
# File 'lib/marvi/renderer/curses/tab.rb', line 101

def update_search(query, page_size)
  @search_query = query
  recompute_matches
  focus_match_from(@scroll, page_size) unless @matches.empty?
end

#visible_lines(page_size) ⇒ Object



62
63
64
# File 'lib/marvi/renderer/curses/tab.rb', line 62

def visible_lines(page_size)
  @lines[@scroll, page_size] || []
end