Class: Echoes::Editor

Inherits:
Object
  • Object
show all
Defined in:
lib/echoes/editor.rb

Overview

Vim-equivalent editor pane backed by ‘Rvim::Editor`. Editing, `:w` (write), `:q` (quit), search, visual mode, undo/redo and the rest of rvim’s surface all work — this class is the rendering shim that turns rvim’s editor state into the styled segments echoes’ Screen wants. The bottom row is reserved for the statusline (mode / filename / modified marker / line:col) or, when in command/search mode, for the cmdline (‘:`, `/`, or `?` prompt with the typed text and cursor).

Constant Summary collapse

COLOR_MAP =

rvim’s syntax highlighter labels each token with a vim-style color symbol (‘:Comment`, `:String`, …). Map to ANSI palette indices that this Screen’s Cell.fg uses.

{
  Comment:    8,    # bright black / grey
  String:     2,    # green
  Keyword:    5,    # magenta
  Symbol:     3,    # yellow
  Number:     3,    # yellow
  Constant:   6,    # cyan
  Function:   4,    # blue
  Type:       6,    # cyan
  Special:    3,    # yellow
  PreProc:    5,    # magenta
  Operator:  14,    # bright cyan
  Identifier: 7,    # white
}.freeze
DEFAULT_SEGMENT =
{
  fg: nil, bg: nil,
  bold: false, italic: false, underline: false, inverse: false,
}.freeze
SPECIAL_KEY_MAP =

Forward a single character (or escape sequence) to the editor. Accepts Strings like ‘j’, ‘G’, “x04” (Ctrl-D), or “e[A”. Maps macOS NSEvent special-key codepoints to vim equivalents so the arrow keys “just work” in viewer mode.

{
  "\u{F700}" => 'k',  # Up
  "\u{F701}" => 'j',  # Down
  "\u{F702}" => 'h',  # Left
  "\u{F703}" => 'l',  # Right
  "\u{F72C}" => "\x02",  # PageUp → Ctrl-B
  "\u{F72D}" => "\x06",  # PageDown → Ctrl-F
  "\u{F729}" => 'g',  # Home (caller may follow with another 'g')
  "\u{F72B}" => 'G',  # End
}.freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file:, rows:, cols:) ⇒ Editor

Returns a new instance of Editor.



40
41
42
43
44
45
46
47
48
49
# File 'lib/echoes/editor.rb', line 40

def initialize(file:, rows:, cols:)
  require 'rvim'
  @editor = Rvim::Editor.new(Reline.core.config)
  @rows = rows
  @cols = cols
  @file = file
  resize(rows: rows, cols: cols)
  @editor.open(file) if file && File.exist?(file)
  @lang = @file ? Rvim::Syntax.detect_language(@file) : nil
end

Instance Attribute Details

#fileObject (readonly)

Returns the value of attribute file.



38
39
40
# File 'lib/echoes/editor.rb', line 38

def file
  @file
end

Instance Method Details

#closed?Boolean

rvim sets ‘quit` after `:q` / `:q!` / `:wq`. The host polls this via `Pane#alive?` and reaps the pane like it would a dead shell.

Returns:

  • (Boolean)


124
125
126
# File 'lib/echoes/editor.rb', line 124

def closed?
  @editor.quit?
end

#cursor_positionObject

(row, col) of the cursor within the viewport. In cmdline modes (:ex / :search_*) the cursor sits on the bottom row at the end of the cmdline text; otherwise it tracks the editor cursor in the buffer body.



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/echoes/editor.rb', line 108

def cursor_position
  if cmdline_mode?
    text = cmdline_text
    [@rows - 1, text.length.clamp(0, @cols - 1)]
  else
    win = @editor.current_window
    top = win&.scroll_top || 0
    row = (@editor.line_index || 0) - top
    col = @editor.byte_pointer || 0
    body_max = [@rows - 2, 0].max
    [row.clamp(0, body_max), col.clamp(0, @cols - 1)]
  end
end

#display_filenameObject

Filename for the window title; appends ‘[+]` while the buffer has unsaved changes (vim convention). Returns `’[No Name]‘` for buffers opened with no path (e.g. `:enew`).



140
141
142
143
# File 'lib/echoes/editor.rb', line 140

def display_filename
  base = @file && !@file.empty? ? File.basename(@file) : '[No Name]'
  @editor.modified ? "#{base} [+]" : base
end

#feed_key(ch) ⇒ Object



77
78
79
80
81
82
83
# File 'lib/echoes/editor.rb', line 77

def feed_key(ch)
  mapped = SPECIAL_KEY_MAP[ch] || ch
  mapped.each_char { |c| @editor.send(:dispatch_synthesized_key, c) }
  true
rescue
  false
end

#mode_labelObject

Short label for the current vim mode (used in the statusline and exposed for window-title / future status-bar plumbing).



130
131
132
133
134
135
# File 'lib/echoes/editor.rb', line 130

def mode_label
  return :cmdline if cmdline_mode?
  return :visual  if @editor.visual_mode
  return :insert  if @editor.send(:editing_mode_label) == :vi_insert
  :normal
end

#resize(rows:, cols:) ⇒ Object

Match rvim’s window dimensions to the host pane’s. Called on construction and on later ‘Pane#resize`.



53
54
55
56
57
58
59
60
# File 'lib/echoes/editor.rb', line 53

def resize(rows:, cols:)
  @rows = rows
  @cols = cols
  win = @editor.current_window
  return unless win
  win.height = rows
  win.width  = cols
end

#visible_segmentsObject

Visible window’s lines as Arrays of styled-segment Hashes (‘fg:, bg:, bold:, italic:, underline:, inverse:`), one Array per visible row. The last row is the statusline (or cmdline, in `:`/`/`/`?` modes); the rows above are buffer text padded with vim-style `~` markers when shorter than the pane.



90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/echoes/editor.rb', line 90

def visible_segments
  win = @editor.current_window
  lines = @editor.current_buffer&.lines || []
  top = win&.scroll_top || 0
  body_rows = [@rows - 1, 1].max  # reserve last row for status/cmdline
  slice = lines[top, body_rows] || []
  out = slice.map { |line| line_to_segments(line) }
  while out.size < body_rows
    out << [DEFAULT_SEGMENT.merge(text: '~', fg: 4)]
  end
  out << bottom_row_segments
  out
end