class Textbringer::Window
Constants
- ALT_ALPHA_BASE
- ALT_NUMBER_BASE
- HAVE_GET_KEY_MODIFIERS
- KEY_NAMES
Attributes
bottom_of_window[R]
buffer[R]
columns[R]
lines[R]
mode_line[R]
top_of_window[R]
window[R]
x[R]
y[R]
Public Class Methods
beep()
click to toggle source
# File lib/textbringer/window.rb, line 213 def self.beep Curses.beep end
colors()
click to toggle source
# File lib/textbringer/window.rb, line 106 def self.colors Curses.colors end
columns()
click to toggle source
# File lib/textbringer/window.rb, line 186 def self.columns Curses.cols end
current()
click to toggle source
# File lib/textbringer/window.rb, line 32 def self.current @@current end
current=(window)
click to toggle source
# File lib/textbringer/window.rb, line 36 def self.current=(window) if window.deleted? window = @@list.first end @@current.save_point if @@current && !@@current.deleted? @@current = window @@current.restore_point Buffer.current = window.buffer end
delete_other_windows()
click to toggle source
# File lib/textbringer/window.rb, line 69 def self.delete_other_windows if @@current.echo_area? raise EditorError, "Can't expand the echo area to full screen" end @@list.delete_if do |window| if window.current? || window.echo_area? false else window.delete true end end @@current.move(0, 0) @@current.resize(Window.lines - 1, @@current.columns) end
delete_window(target = @@current)
click to toggle source
# File lib/textbringer/window.rb, line 46 def self.delete_window(target = @@current) if target.echo_area? raise EditorError, "Can't delete the echo area" end if @@list.size == 2 raise EditorError, "Can't delete the sole window" end i = @@list.index(target) return if i.nil? if i == 0 window = @@list[1] window.move(0, 0) else window = @@list[i - 1] end window.resize(target.lines + window.lines, window.columns) target.delete @@list.delete_at(i) if target == @@current self.current = window end end
echo_area()
click to toggle source
# File lib/textbringer/window.rb, line 94 def self.echo_area @@echo_area end
has_colors=(value)
click to toggle source
# File lib/textbringer/window.rb, line 98 def self.has_colors=(value) @@has_colors = value end
has_colors?()
click to toggle source
# File lib/textbringer/window.rb, line 102 def self.has_colors? @@has_colors end
lines()
click to toggle source
# File lib/textbringer/window.rb, line 182 def self.lines Curses.lines end
list(include_echo_area: false)
click to toggle source
# File lib/textbringer/window.rb, line 24 def self.list(include_echo_area: false) if include_echo_area @@list.dup else @@list.reject(&:echo_area?) end end
load_faces()
click to toggle source
# File lib/textbringer/window.rb, line 115 def self.load_faces require_relative "faces/basic" require_relative "faces/programming" end
new(lines, columns, y, x)
click to toggle source
# File lib/textbringer/window.rb, line 220 def initialize(lines, columns, y, x) @lines = lines @columns = columns @y = y @x = x initialize_window(lines, columns, y, x) @window.keypad = true @window.scrollok(false) @window.idlok(true) @buffer = nil @top_of_window = nil @bottom_of_window = nil @point_mark = nil @deleted = false @raw_key_buffer = [] @key_buffer = [] end
other_window()
click to toggle source
# File lib/textbringer/window.rb, line 85 def self.other_window i = @@list.index(@@current) begin i += 1 window = @@list[i % @@list.size] end while !window.active? self.current = window end
redisplay()
click to toggle source
# File lib/textbringer/window.rb, line 160 def self.redisplay return if Controller.current.executing_keyboard_macro? return if Window.current.has_input? @@list.each do |window| window.redisplay unless window.current? end current.redisplay update end
redraw()
click to toggle source
# File lib/textbringer/window.rb, line 170 def self.redraw @@list.each do |window| window.redraw unless window.current? end current.redraw update end
resize()
click to toggle source
# File lib/textbringer/window.rb, line 190 def self.resize @@list.delete_if do |window| if !window.echo_area? && window.y > Window.lines - CONFIG[:window_min_height] window.delete true else false end end @@list.each_with_index do |window, i| unless window.echo_area? if i < @@list.size - 2 window.resize(window.lines, Window.columns) else window.resize(Window.lines - 1 - window.y, Window.columns) end end end @@echo_area.move(Window.lines - 1, 0) @@echo_area.resize(1, Window.columns) end
set_default_colors(fg, bg)
click to toggle source
# File lib/textbringer/window.rb, line 110 def self.set_default_colors(fg, bg) Curses.assume_default_colors(Color[fg], Color[bg]) Window.redraw end
start() { || ... }
click to toggle source
# File lib/textbringer/window.rb, line 120 def self.start if @@started raise EditorError, "Already started" end Curses.init_screen Curses.noecho Curses.raw Curses.nonl self.has_colors = Curses.has_colors? if has_colors? Curses.start_color Curses.use_default_colors load_faces end begin window = Textbringer::Window.new(Window.lines - 1, Window.columns, 0, 0) window.buffer = Buffer.new_buffer("*scratch*") @@list.push(window) Window.current = window @@echo_area = Textbringer::EchoArea.new(1, Window.columns, Window.lines - 1, 0) Buffer.minibuffer.keymap = MINIBUFFER_LOCAL_MAP @@echo_area.buffer = Buffer.minibuffer @@list.push(@@echo_area) @@started = true yield ensure @@list.each do |win| win.close end @@list.clear Curses.echo Curses.noraw Curses.nl Curses.close_screen @@started = false end end
update()
click to toggle source
# File lib/textbringer/window.rb, line 178 def self.update Curses.doupdate end
Public Instance Methods
active?()
click to toggle source
# File lib/textbringer/window.rb, line 242 def active? true end
buffer=(buffer)
click to toggle source
# File lib/textbringer/window.rb, line 265 def buffer=(buffer) delete_marks @buffer = buffer @top_of_window = @buffer.new_mark(@buffer.point_min) if @buffer[:top_of_window] @top_of_window.location = @buffer[:top_of_window].location end @bottom_of_window = @buffer.new_mark(@buffer.point_min) if @buffer[:bottom_of_window] @bottom_of_window.location = @buffer[:bottom_of_window].location end @point_mark = @buffer.new_mark end
close()
click to toggle source
# File lib/textbringer/window.rb, line 261 def close @window.close end
current?()
click to toggle source
# File lib/textbringer/window.rb, line 291 def current? self == @@current end
delete()
click to toggle source
# File lib/textbringer/window.rb, line 250 def delete unless @deleted if current? Window.current = @@list.first end delete_marks @window.close @deleted = true end end
deleted?()
click to toggle source
# File lib/textbringer/window.rb, line 246 def deleted? @deleted end
echo_area?()
click to toggle source
# File lib/textbringer/window.rb, line 238 def echo_area? false end
enlarge(n)
click to toggle source
# File lib/textbringer/window.rb, line 569 def enlarge(n) if n > 0 max_height = Window.lines - CONFIG[:window_min_height] * (@@list.size - 2) - 1 new_lines = [lines + n, max_height].min needed_lines = new_lines - lines resize(new_lines, columns) i = @@list.index(self) indices = (i + 1).upto(@@list.size - 2).to_a + (i - 1).downto(0).to_a indices.each do |j| break if needed_lines == 0 window = @@list[j] extended_lines = [ window.lines - CONFIG[:window_min_height], needed_lines ].min window.resize(window.lines - extended_lines, window.columns) needed_lines -= extended_lines end y = 0 @@list.each do |win| win.move(y, win.x) y += win.lines end elsif n < 0 && @@list.size > 2 new_lines = [lines + n, CONFIG[:window_min_height]].max diff = lines - new_lines resize(new_lines, columns) i = @@list.index(self) if i < @@list.size - 2 window = @@list[i + 1] window.move(window.y - diff, window.x) else window = @@list[i - 1] move(self.y + diff, self.x) end window.resize(window.lines + diff, window.columns) end end
has_input?()
click to toggle source
# File lib/textbringer/window.rb, line 338 def has_input? if !@raw_key_buffer.empty? || !@key_buffer.empty? return true end @window.nodelay = true begin c = @window.get_char if c @raw_key_buffer.push(c) end !c.nil? ensure @window.nodelay = false end end
highlight()
click to toggle source
# File lib/textbringer/window.rb, line 354 def highlight @highlight_on = {} @highlight_off = {} return if !@@has_colors || !CONFIG[:syntax_highlight] || @buffer.binary? syntax_table = @buffer.mode.syntax_table || DEFAULT_SYNTAX_TABLE if @buffer.bytesize < CONFIG[:highlight_buffer_size_limit] base_pos = @buffer.point_min s = @buffer.to_s else base_pos = @buffer.point len = columns * (lines - 1) / 2 * 3 s = @buffer.substring(@buffer.point, @buffer.point + len).scrub("") end return if !s.valid_encoding? re_str = syntax_table.map { |name, re| "(?<#{name}>#{re})" }.join("|") re = Regexp.new(re_str) names = syntax_table.keys s.scan(re) do b = base_pos + $`.bytesize e = b + $&.bytesize if b < @buffer.point && @buffer.point < e b = @buffer.point end name = names.find { |n| $~[n] } attributes = Face[name]&.attributes if attributes @highlight_on[b] = attributes @highlight_off[e] = attributes end end end
move(y, x)
click to toggle source
# File lib/textbringer/window.rb, line 494 def move(y, x) @y = y @x = x @window.move(y, x) @mode_line.move(y + @window.maxy, x) end
read_event()
click to toggle source
# File lib/textbringer/window.rb, line 295 def read_event key = get_char if key.is_a?(Integer) if HAVE_GET_KEY_MODIFIERS if Curses::ALT_0 <= key && key <= Curses::ALT_9 @key_buffer.push((key - ALT_NUMBER_BASE).chr) return "\e" elsif Curses::ALT_A <= key && key <= Curses::ALT_Z @key_buffer.push((key - ALT_ALPHA_BASE).chr) return "\e" end end KEY_NAMES[key] || key else key&.encode(Encoding::UTF_8) end end
read_event_nonblock()
click to toggle source
# File lib/textbringer/window.rb, line 313 def read_event_nonblock @window.nodelay = true begin read_event ensure @window.nodelay = false end end
recenter()
click to toggle source
# File lib/textbringer/window.rb, line 509 def recenter @buffer.save_point do |saved| max = (lines - 1) / 2 count = beginning_of_line_and_count(max) while count < max break if @buffer.point == 0 @buffer.backward_char count += beginning_of_line_and_count(max - count - 1) + 1 end @buffer.mark_to_point(@top_of_window) end end
recenter_if_needed()
click to toggle source
# File lib/textbringer/window.rb, line 522 def recenter_if_needed if @buffer.point_before_mark?(@top_of_window) || @buffer.point_after_mark?(@bottom_of_window) recenter end end
redisplay()
click to toggle source
# File lib/textbringer/window.rb, line 388 def redisplay return if @buffer.nil? redisplay_mode_line @buffer.save_point do |saved| if current? point = saved else point = @point_mark @buffer.point_to_mark(@point_mark) end framer y = x = 0 @buffer.point_to_mark(@top_of_window) highlight @window.erase @window.setpos(0, 0) @window.attrset(0) if current? && @buffer.visible_mark && @buffer.point_after_mark?(@buffer.visible_mark) @window.attron(Curses::A_REVERSE) end while !@buffer.end_of_buffer? cury, curx = @window.cury, @window.curx if @buffer.point_at_mark?(point) y, x = cury, curx if current? && @buffer.visible_mark if @buffer.point_after_mark?(@buffer.visible_mark) @window.attroff(Curses::A_REVERSE) elsif @buffer.point_before_mark?(@buffer.visible_mark) @window.attron(Curses::A_REVERSE) end end end if current? && @buffer.visible_mark && @buffer.point_at_mark?(@buffer.visible_mark) if @buffer.point_after_mark?(point) @window.attroff(Curses::A_REVERSE) elsif @buffer.point_before_mark?(point) @window.attron(Curses::A_REVERSE) end end if attrs = @highlight_off[@buffer.point] @window.attroff(attrs) end if attrs = @highlight_on[@buffer.point] @window.attron(attrs) end c = @buffer.char_after if c == "\n" @window.clrtoeol break if cury == lines - 2 # lines include mode line @window.setpos(cury + 1, 0) @buffer.forward_char next elsif c == "\t" n = calc_tab_width(curx) c = " " * n else c = escape(c) end if curx < columns - 4 newx = nil else newx = curx + Buffer.display_width(c) if newx > columns if cury == lines - 2 break else @window.clrtoeol @window.setpos(cury + 1, 0) end end end if Buffer.display_width(c) == 0 # ncurses on macOS prints U+FEFF, U+FE0F etc. as space, # so ignore it else @window.addstr(c) end break if newx == columns && cury == lines - 2 @buffer.forward_char end if current? && @buffer.visible_mark @window.attroff(Curses::A_REVERSE) end @buffer.mark_to_point(@bottom_of_window) if @buffer.point_at_mark?(point) y, x = @window.cury, @window.curx end if x == columns - 1 c = @buffer.char_after(point.location) if c && Buffer.display_width(c) > 1 y += 1 x = 0 end end @window.setpos(y, x) @window.noutrefresh end end
redraw()
click to toggle source
# File lib/textbringer/window.rb, line 489 def redraw @window.redraw @mode_line.redraw end
resize(lines, columns)
click to toggle source
# File lib/textbringer/window.rb, line 501 def resize(lines, columns) @lines = lines @columns = columns @window.resize(lines - 1, columns) @mode_line.move(@y + lines - 1, @x) @mode_line.resize(1, columns) end
restore_point()
click to toggle source
# File lib/textbringer/window.rb, line 287 def restore_point @buffer.point_to_mark(@point_mark) end
save_point()
click to toggle source
# File lib/textbringer/window.rb, line 279 def save_point @buffer[:top_of_window] ||= @buffer.new_mark @buffer[:top_of_window].location = @top_of_window.location @buffer[:bottom_of_window] ||= @buffer.new_mark @buffer[:bottom_of_window].location = @bottom_of_window.location @buffer.mark_to_point(@point_mark) end
scroll_down()
click to toggle source
# File lib/textbringer/window.rb, line 539 def scroll_down if @top_of_window.location == @buffer.point_min raise RangeError, "Beginning of buffer" end @buffer.point_to_mark(@top_of_window) @buffer.next_line @buffer.beginning_of_line @top_of_window.location = 0 end
scroll_up()
click to toggle source
# File lib/textbringer/window.rb, line 529 def scroll_up if @bottom_of_window.location == @buffer.point_max raise RangeError, "End of buffer" end @buffer.point_to_mark(@bottom_of_window) @buffer.previous_line @buffer.beginning_of_line @buffer.mark_to_point(@top_of_window) end
shrink(n)
click to toggle source
# File lib/textbringer/window.rb, line 610 def shrink(n) enlarge(-n) end
shrink_if_larger_than_buffer()
click to toggle source
# File lib/textbringer/window.rb, line 614 def shrink_if_larger_than_buffer @buffer.save_point do @buffer.end_of_buffer @buffer.skip_re_backward(/\s/) count = beginning_of_line_and_count(Window.lines) + 1 while !@buffer.beginning_of_buffer? @buffer.backward_char count += beginning_of_line_and_count(Window.lines) + 1 end if lines - 1 > count shrink(lines - 1 - count) end end end
split(other_lines = nil)
click to toggle source
# File lib/textbringer/window.rb, line 549 def split(other_lines = nil) old_lines = lines if other_lines if other_lines < CONFIG[:window_min_height] raise EditorError, "Window too small" end new_lines = lines - other_lines else new_lines = (old_lines / 2.0).ceil end if new_lines < CONFIG[:window_min_height] raise EditorError, "Window too small" end resize(new_lines, columns) new_window = Window.new(old_lines - new_lines, columns, y + new_lines, x) new_window.buffer = buffer i = @@list.index(self) @@list.insert(i + 1, new_window) end
wait_input(msecs)
click to toggle source
# File lib/textbringer/window.rb, line 322 def wait_input(msecs) if !@raw_key_buffer.empty? || !@key_buffer.empty? return @raw_key_buffer.first || @key_buffer.first end @window.timeout = msecs begin c = @window.get_char if c @raw_key_buffer.push(c) end c ensure @window.timeout = -1 end end
Private Instance Methods
beginning_of_line_and_count(max_lines, columns = @columns)
click to toggle source
# File lib/textbringer/window.rb, line 715 def beginning_of_line_and_count(max_lines, columns = @columns) e = @buffer.point @buffer.beginning_of_line bols = [@buffer.point] column = 0 while @buffer.point < e c = @buffer.char_after if c == ?\t n = calc_tab_width(column) str = " " * n else str = escape(c) end column += Buffer.display_width(str) if column > columns # Don't forward_char if column > columns # to handle multibyte characters across the end of lines. bols.push(@buffer.point) column = 0 else @buffer.forward_char if column == columns bols.push(@buffer.point) column = 0 end end end if bols.size > max_lines @buffer.goto_char(bols[-max_lines]) max_lines else @buffer.goto_char(bols.first) bols.size - 1 end end
calc_tab_width(column)
click to toggle source
# File lib/textbringer/window.rb, line 709 def calc_tab_width(column) tw = @buffer[:tab_width] n = tw - column % tw n.nonzero? || tw end
delete_marks()
click to toggle source
# File lib/textbringer/window.rb, line 751 def delete_marks if @top_of_window @top_of_window.delete @top_of_window = nil end if @bottom_of_window @bottom_of_window.delete @bottom_of_window = nil end if @point_mark @point_mark.delete @point_mark = nil end end
escape(s)
click to toggle source
# File lib/textbringer/window.rb, line 692 def escape(s) if !s.valid_encoding? s = s.b end if @buffer.binary? s.gsub(/[\0-\b\v-\x1f\x7f]/) { |c| "^" + (c.ord ^ 0x40).chr }.gsub(/[\x80-\xff]/n) { |c| "<%02X>" % c.ord } else s.gsub(/[\0-\b\v-\x1f\x7f]/) { |c| "^" + (c.ord ^ 0x40).chr } end end
framer()
click to toggle source
# File lib/textbringer/window.rb, line 636 def framer @buffer.save_point do |saved| max = lines - 1 # lines include mode line count = beginning_of_line_and_count(max) new_start_loc = @buffer.point if @buffer.point_before_mark?(@top_of_window) @buffer.mark_to_point(@top_of_window) return end while count < max break if @buffer.point_at_mark?(@top_of_window) break if @buffer.point == 0 new_start_loc = @buffer.point @buffer.backward_char count += beginning_of_line_and_count(max - count - 1) + 1 end if count >= lines - 1 # lines include mode line @top_of_window.location = new_start_loc end end end
get_char()
click to toggle source
# File lib/textbringer/window.rb, line 766 def get_char if @key_buffer.empty? Curses.save_key_modifiers(true) if HAVE_GET_KEY_MODIFIERS begin need_retry = false if @raw_key_buffer.empty? key = @window.get_char else key = @raw_key_buffer.shift end if HAVE_GET_KEY_MODIFIERS mods = Curses.get_key_modifiers if key.is_a?(String) && key.ascii_only? if (mods & Curses::PDC_KEY_MODIFIER_CONTROL) != 0 key = key == ?? ? "\x7f" : (key.ord & 0x9f).chr end if (mods & Curses::PDC_KEY_MODIFIER_ALT) != 0 if key == "\0" # Alt + `, Alt + < etc. return NUL, so ignore it. need_retry = true else @key_buffer.push(key) key = "\e" end end end end end while need_retry key else @key_buffer.shift end end
initialize_window(num_lines, num_columns, y, x)
click to toggle source
# File lib/textbringer/window.rb, line 631 def initialize_window(num_lines, num_columns, y, x) @window = Curses::Window.new(num_lines - 1, num_columns, y, x) @mode_line = Curses::Window.new(1, num_columns, y + num_lines - 1, x) end
redisplay_mode_line()
click to toggle source
# File lib/textbringer/window.rb, line 658 def redisplay_mode_line @mode_line.erase @mode_line.setpos(0, 0) attrs = @@has_colors ? Face[:mode_line].attributes : Curses::A_REVERSE @mode_line.attrset(attrs) @mode_line.addstr("#{@buffer.input_method_status} #{@buffer.name} ") @mode_line.addstr("[+]") if @buffer.modified? @mode_line.addstr("[RO]") if @buffer.read_only? @mode_line.addstr("[#{@buffer.file_encoding.name}/") @mode_line.addstr("#{@buffer.file_format}] ") if current? || @buffer.point_at_mark?(@point_mark) c = @buffer.char_after line = @buffer.current_line column = @buffer.current_column else c = @buffer.char_after(@point_mark.location) line, column = @buffer.get_line_and_column(@point_mark.location) end @mode_line.addstr(unicode_codepoint(c)) @mode_line.addstr(" #{line},#{column}") @mode_line.addstr(" (#{@buffer.mode&.name || 'None'})") @mode_line.addstr(" " * (columns - @mode_line.curx)) @mode_line.attrset(0) @mode_line.noutrefresh end
unicode_codepoint(c)
click to toggle source
# File lib/textbringer/window.rb, line 684 def unicode_codepoint(c) if c.nil? "<EOF>" else "U+%04X" % c.ord end end