lib/textbringer/window.rb in textbringer-0.1.1 vs lib/textbringer/window.rb in textbringer-0.1.2
- old
+ new
@@ -1,22 +1,48 @@
# frozen_string_literal: true
-require "ncursesw"
+require "curses"
require "unicode/display_width"
+require "fiddle/import"
module Textbringer
+ begin
+ # These features should be provided by curses.gem.
+ module PDCurses
+ extend Fiddle::Importer
+ dlload "pdcurses.dll"
+ extern "unsigned long PDC_get_key_modifiers(void)"
+ extern "int PDC_save_key_modifiers(unsigned char)"
+ extern "int PDC_return_key_modifiers(unsigned char)"
+
+ KEY_MODIFIER_SHIFT = 1
+ KEY_MODIFIER_CONTROL = 2
+ KEY_MODIFIER_ALT = 4
+ KEY_MODIFIER_NUMLOCK = 8
+ end
+ rescue
+ remove_const :PDCurses
+ end
+
class Window
KEY_NAMES = {}
- Ncurses.constants.grep(/\AKEY_/).each do |name|
- KEY_NAMES[Ncurses.const_get(name)] =
+ Curses.constants.grep(/\AKEY_/).each do |name|
+ KEY_NAMES[Curses.const_get(name)] =
name.slice(/\AKEY_(.*)/, 1).downcase.intern
end
- UTF8_CHAR_LEN =
- Buffer::UTF8_CHAR_LEN.each_with_object(Hash.new(1)) { |(k, v), h|
- h[k.ord] = v
- }
+ ALT_IS_FUNCTION_KEY =
+ /mswin32|mingw32/ =~ RUBY_PLATFORM && /PDCurses/ =~ Curses::VERSION
+ if ALT_IS_FUNCTION_KEY
+ KEY_OFFSET = 0xec00
+ ALT_0 = KEY_OFFSET + 0x97
+ ALT_9 = KEY_OFFSET + 0xa0
+ ALT_A = KEY_OFFSET + 0xa1
+ ALT_Z = KEY_OFFSET + 0xba
+ ALT_NUMBER_BASE = ALT_0 - ?0.ord
+ ALT_ALPHA_BASE = ALT_A - ?a.ord
+ end
@@windows = []
@@current = nil
@@echo_area = nil
@@ -86,29 +112,29 @@
def self.echo_area
@@echo_area
end
def self.start
- Ncurses.initscr
- Ncurses.noecho
- Ncurses.raw
+ Curses.init_screen
+ Curses.noecho
+ Curses.raw
begin
window =
Textbringer::Window.new(Window.lines - 1, Window.columns, 0, 0)
window.buffer = Buffer.new_buffer("*scratch*")
@@windows.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
- @@windows.push(@@echo_area)
+ Buffer.minibuffer.keymap = MINIBUFFER_LOCAL_MAP
+ @@echo_area.buffer = Buffer.minibuffer
+ @@windows.push(@@echo_area)
yield
ensure
- Ncurses.echo
- Ncurses.noraw
- Ncurses.endwin
+ Curses.echo
+ Curses.noraw
+ Curses.close_screen
end
end
def self.redisplay
@@windows.each do |window|
@@ -125,61 +151,64 @@
current.redraw
update
end
def self.update
- Ncurses.doupdate
+ Curses.doupdate
end
def self.lines
- Ncurses.LINES
+ Curses.lines
end
def self.columns
- Ncurses.COLS
+ Curses.cols
end
def self.resize
@@windows.delete_if do |window|
- if window.y > Window.lines - 4
+ if !window.echo_area? && window.y > Window.lines - 4
window.delete
true
else
false
end
end
@@windows.each_with_index do |window, i|
- if i < @@windows.size - 1
- window.resize(window.lines, Window.columns)
- else
- window.resize(Window.lines - 1 - window.y, Window.columns)
+ unless window.echo_area?
+ if i < @@windows.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
def self.beep
- Ncurses.beep
+ Curses.beep
end
attr_reader :buffer, :lines, :columns, :y, :x
def initialize(lines, columns, y, x)
@lines = lines
@columns = columns
@y = y
@x = x
initialize_window(lines, columns, y, x)
- @window.keypad(true)
+ @window.keypad = true
@window.scrollok(false)
@window.idlok(true)
@buffer = nil
@top_of_window = nil
@bottom_of_window = nil
@point_mark = nil
@deleted = false
+ @key_buffer = []
end
def echo_area?
false
end
@@ -196,11 +225,11 @@
unless @deleted
if current?
Window.current = @@windows.first
end
delete_marks
- @window.del
+ @window.close
@deleted = true
end
end
def buffer=(buffer)
@@ -231,62 +260,65 @@
def current?
self == @@current
end
- def getch
- key = @window.getch
- if key.nil?
- nil
- elsif key > 0xff
- KEY_NAMES[key]
- else
- len = UTF8_CHAR_LEN[key]
- if len == 1
- key
- else
- buf = [key]
- (len - 1).times do
- c = @window.getch
- if c.nil? || c < 0x80 || c > 0xbf
- raise EditorError, "Malformed UTF-8 input"
- end
- buf.push(c)
+ def read_char
+ key = get_char
+ if key.is_a?(Integer)
+ if ALT_IS_FUNCTION_KEY
+ if ALT_0 <= key && key <= ALT_9
+ @key_buffer.push((key - ALT_NUMBER_BASE).chr)
+ return "\e"
+ elsif ALT_A <= key && key <= ALT_Z
+ @key_buffer.push((key - ALT_ALPHA_BASE).chr)
+ return "\e"
end
- s = buf.pack("C*").force_encoding(Encoding::UTF_8)
- if s.valid_encoding?
- s.ord
- else
- raise EditorError, "Malformed UTF-8 input"
- end
end
+ KEY_NAMES[key] || key
+ else
+ key&.encode(Encoding::UTF_8)&.tr("\r", "\n")
end
end
- def getch_nonblock
- @window.nodelay(true)
+ def read_char_nonblock
+ @window.nodelay = true
begin
- getch
+ read_char
ensure
- @window.nodelay(false)
+ @window.nodelay = false
end
end
def wait_input(msecs)
- @window.timeout(msecs)
+ @window.timeout = msecs
begin
- c = @window.getch
- if c && c >= 0
- Ncurses.ungetch(c)
+ c = @window.get_char
+ if c
+ Curses.unget_char(c)
end
c
ensure
- @window.timeout(-1)
+ @window.timeout = -1
end
end
+ def has_input?
+ @window.nodelay = true
+ begin
+ c = @window.get_char
+ if c
+ Curses.unget_char(c)
+ end
+ !c.nil?
+ ensure
+ @window.nodelay = false
+ end
+ end
+
def redisplay
+ return if has_input?
return if @buffer.nil?
redisplay_mode_line
@buffer.save_point do |saved|
if current?
point = saved
@@ -296,85 +328,85 @@
end
framer
y = x = 0
@buffer.point_to_mark(@top_of_window)
@window.erase
- @window.move(0, 0)
+ @window.setpos(0, 0)
if current? && @buffer.visible_mark &&
@buffer.point_after_mark?(@buffer.visible_mark)
- @window.attron(Ncurses::A_REVERSE)
+ @window.attron(Curses::A_REVERSE)
end
while !@buffer.end_of_buffer?
if @buffer.point_at_mark?(point)
- y, x = @window.getcury, @window.getcurx
+ y, x = @window.cury, @window.curx
if current? && @buffer.visible_mark
if @buffer.point_after_mark?(@buffer.visible_mark)
- @window.attroff(Ncurses::A_REVERSE)
+ @window.attroff(Curses::A_REVERSE)
elsif @buffer.point_before_mark?(@buffer.visible_mark)
- @window.attron(Ncurses::A_REVERSE)
+ @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(Ncurses::A_REVERSE)
+ @window.attroff(Curses::A_REVERSE)
elsif @buffer.point_before_mark?(point)
- @window.attron(Ncurses::A_REVERSE)
+ @window.attron(Curses::A_REVERSE)
end
end
c = @buffer.char_after
if c == "\n"
@window.clrtoeol
- break if @window.getcury == lines - 2 # lines include mode line
+ break if @window.cury == lines - 2 # lines include mode line
elsif c == "\t"
- n = calc_tab_width(@window.getcurx)
+ n = calc_tab_width(@window.curx)
c = " " * n
else
c = escape(c)
end
@window.addstr(c)
- break if @window.getcury == lines - 2 && # lines include mode line
- @window.getcurx == columns
+ break if @window.cury == lines - 2 && # lines include mode line
+ @window.curx == columns
@buffer.forward_char
end
if current? && @buffer.visible_mark
- @window.attroff(Ncurses::A_REVERSE)
+ @window.attroff(Curses::A_REVERSE)
end
@buffer.mark_to_point(@bottom_of_window)
if @buffer.point_at_mark?(point)
- y, x = @window.getcury, @window.getcurx
+ 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.move(y, x)
+ @window.setpos(y, x)
@window.noutrefresh
end
end
def redraw
- @window.redrawwin
- @mode_line.redrawwin
+ @window.redraw
+ @mode_line.redraw
end
def move(y, x)
@y = y
@x = x
- @window.mvwin(y, x)
- @mode_line.mvwin(y + @window.getmaxy, x)
+ @window.move(y, x)
+ @mode_line.move(y + @window.maxy, x)
end
def resize(lines, columns)
@lines = lines
@columns = columns
@window.resize(lines - 1, columns)
- @mode_line.mvwin(@y + lines - 1, @x)
+ @mode_line.move(@y + lines - 1, @x)
@mode_line.resize(1, columns)
end
def recenter
@buffer.save_point do |saved|
@@ -424,12 +456,12 @@
end
private
def initialize_window(num_lines, num_columns, y, x)
- @window = Ncurses::WINDOW.new(num_lines - 1, num_columns, y, x)
- @mode_line = Ncurses::WINDOW.new(1, num_columns, y + num_lines - 1, 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
def framer
@buffer.save_point do |saved|
max = lines - 1 # lines include mode line
@@ -452,12 +484,12 @@
end
end
def redisplay_mode_line
@mode_line.erase
- @mode_line.move(0, 0)
- @mode_line.attron(Ncurses::A_REVERSE)
+ @mode_line.setpos(0, 0)
+ @mode_line.attron(Curses::A_REVERSE)
@mode_line.addstr("#{@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}] ")
@@ -470,12 +502,12 @@
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(" " * (@mode_line.getmaxx - @mode_line.getcurx))
- @mode_line.attroff(Ncurses::A_REVERSE)
+ @mode_line.addstr(" " * (@mode_line.maxx - @mode_line.curx))
+ @mode_line.attroff(Curses::A_REVERSE)
@mode_line.noutrefresh
end
def unicode_codepoint(c)
if c.nil?
@@ -506,10 +538,13 @@
end
def beginning_of_line_and_count(max_lines)
e = @buffer.point
@buffer.beginning_of_line
+ if e - @buffer.point < @columns
+ return 0
+ end
s = @buffer.substring(@buffer.point, e)
bols = [@buffer.point]
column = 0
while @buffer.point < e
c = @buffer.char_after
@@ -554,10 +589,40 @@
if @point_mark
@point_mark.delete
@point_mark = nil
end
end
+
+ def get_char
+ if @key_buffer.empty?
+ PDCurses.PDC_save_key_modifiers(1) if defined?(PDCurses)
+ need_retry = false
+ begin
+ key = @window.get_char
+ if defined?(PDCurses)
+ mods = PDCurses.PDC_get_key_modifiers
+ if key.is_a?(String) && key.ascii_only?
+ if (mods & PDCurses::KEY_MODIFIER_CONTROL) != 0
+ key = key == ?? ? "\x7f" : (key.ord & 0x9f).chr
+ end
+ if (mods & PDCurses::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
end
class EchoArea < Window
attr_accessor :prompt
attr_writer :active
@@ -593,44 +658,44 @@
def redisplay
return if @buffer.nil?
@buffer.save_point do |saved|
@window.erase
- @window.move(0, 0)
+ @window.setpos(0, 0)
if @message
- @window.addstr @message
+ @window.addstr(escape(@message))
else
- @window.addstr @prompt
+ @window.addstr(escape(@prompt))
@buffer.beginning_of_line
while !@buffer.end_of_buffer?
if @buffer.point_at_mark?(saved)
- y, x = @window.getcury, @window.getcurx
+ y, x = @window.cury, @window.curx
end
c = @buffer.char_after
if c == "\n"
break
end
- @window.addstr escape(c)
+ @window.addstr(escape(c))
@buffer.forward_char
end
if @buffer.point_at_mark?(saved)
- y, x = @window.getcury, @window.getcurx
+ y, x = @window.cury, @window.curx
end
- @window.move(y, x)
+ @window.setpos(y, x)
end
@window.noutrefresh
end
end
def redraw
- @window.redrawwin
+ @window.redraw
end
def move(y, x)
@y = y
@x = x
- @window.mvwin(y, x)
+ @window.move(y, x)
end
def resize(lines, columns)
@lines = lines
@columns = columns
@@ -638,9 +703,9 @@
end
private
def initialize_window(num_lines, num_columns, y, x)
- @window = Ncurses::WINDOW.new(num_lines, num_columns, y, x)
+ @window = Curses::Window.new(num_lines, num_columns, y, x)
end
end
end