Skip to content

Commit

Permalink
Merge pull request #287 from aycabta/add-terminfo-support
Browse files Browse the repository at this point in the history
Add terminfo support
  • Loading branch information
aycabta committed May 22, 2021
2 parents 4968ca1 + 4b74c3d commit 028656c
Show file tree
Hide file tree
Showing 2 changed files with 138 additions and 14 deletions.
68 changes: 54 additions & 14 deletions lib/reline/ansi.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
require 'io/console'
require 'timeout'
require_relative 'terminfo'

class Reline::ANSI
if Reline::Terminfo.enabled?
Reline::Terminfo.setupterm(0, 2)
end

def self.encoding
Encoding.default_external
end
Expand All @@ -11,6 +16,53 @@ def self.win?
end

def self.set_default_key_bindings(config)
if Reline::Terminfo.enabled?
set_default_key_bindings_terminfo(config)
else
set_default_key_bindings_comprehensive_list(config)
end
{
# extended entries of terminfo
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→, extended entry
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←, extended entry
[27, 91, 49, 59, 51, 67] => :em_next_word, # Meta+→, extended entry
[27, 91, 49, 59, 51, 68] => :ed_prev_word, # Meta+←, extended entry
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
{
# default bindings
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
end

def self.set_default_key_bindings_terminfo(config)
{
Reline::Terminfo.tigetstr('khome').bytes => :ed_move_to_beg,
Reline::Terminfo.tigetstr('kend').bytes => :ed_move_to_end,
Reline::Terminfo.tigetstr('kcuu1').bytes => :ed_prev_history,
Reline::Terminfo.tigetstr('kcud1').bytes => :ed_next_history,
Reline::Terminfo.tigetstr('kcuf1').bytes => :ed_next_char,
Reline::Terminfo.tigetstr('kcub1').bytes => :ed_prev_char,
# Escape sequences that omit the move distance and are set to defaults
# value 1 may be sometimes sent by pressing the arrow-key.
Reline::Terminfo.tigetstr('cuu').sub(/%p1%d/, '').bytes => :ed_prev_history,
Reline::Terminfo.tigetstr('cud').sub(/%p1%d/, '').bytes => :ed_next_history,
Reline::Terminfo.tigetstr('cuf').sub(/%p1%d/, '').bytes => :ed_next_char,
Reline::Terminfo.tigetstr('cub').sub(/%p1%d/, '').bytes => :ed_prev_char,
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end
end

def self.set_default_key_bindings_comprehensive_list(config)
{
# Console (80x25)
[27, 91, 49, 126] => :ed_move_to_beg, # Home
Expand Down Expand Up @@ -41,15 +93,11 @@ def self.set_default_key_bindings(config)
# Arrow keys are the same of KDE

# iTerm2
[27, 27, 91, 67] => :em_next_word, # Option+→
[27, 27, 91, 68] => :ed_prev_word, # Option+←
[27, 27, 91, 67] => :em_next_word, # Option+→, extended entry
[27, 27, 91, 68] => :ed_prev_word, # Option+←, extended entry
[195, 166] => :em_next_word, # Option+f
[195, 162] => :ed_prev_word, # Option+b

# others
[27, 91, 49, 59, 53, 67] => :em_next_word, # Ctrl+→
[27, 91, 49, 59, 53, 68] => :ed_prev_word, # Ctrl+←

[27, 79, 65] => :ed_prev_history, # ↑
[27, 79, 66] => :ed_next_history, # ↓
[27, 79, 67] => :ed_next_char, # →
Expand All @@ -59,14 +107,6 @@ def self.set_default_key_bindings(config)
config.add_default_key_binding_by_keymap(:vi_insert, key, func)
config.add_default_key_binding_by_keymap(:vi_command, key, func)
end

{
# others
[27, 32] => :em_set_mark, # M-<space>
[24, 24] => :em_exchange_mark, # C-x C-x TODO also add Windows
}.each_pair do |key, func|
config.add_default_key_binding_by_keymap(:emacs, key, func)
end
end

@@input = STDIN
Expand Down
84 changes: 84 additions & 0 deletions lib/reline/terminfo.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
require 'fiddle'
require 'fiddle/import'

module Reline::Terminfo
extend Fiddle::Importer

class TerminfoError < StandardError; end

@curses_dl = nil
def self.curses_dl
return @curses_dl if @curses_dl
if Fiddle.const_defined?(:VERSION) and Gem::Version.create(Fiddle::VERSION) >= Gem::Version.create('1.0.1')
# Fiddle::TYPE_VARIADIC is supported from Fiddle 1.0.1.
%w[libncursesw.so libcursesw.so libncurses.so libcurses.so].each do |curses_name|
result = Fiddle::Handle.new(curses_name)
rescue Fiddle::DLError
next
else
@curses_dl = result
break
end
end
@curses_dl
end
end

module Reline::Terminfo
dlload curses_dl
#extern 'int setupterm(char *term, int fildes, int *errret)'
@setupterm = Fiddle::Function.new(curses_dl['setupterm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP], Fiddle::TYPE_INT)
#extern 'char *tigetstr(char *capname)'
@tigetstr = Fiddle::Function.new(curses_dl['tigetstr'], [Fiddle::TYPE_VOIDP], Fiddle::TYPE_VOIDP)
#extern 'char *tiparm(const char *str, ...)'
@tiparm = Fiddle::Function.new(curses_dl['tiparm'], [Fiddle::TYPE_VOIDP, Fiddle::TYPE_VARIADIC], Fiddle::TYPE_VOIDP)

def self.setupterm(term, fildes)
errret_int = String.new("\x00" * 8, encoding: 'ASCII-8BIT')
ret = @setupterm.(term, fildes, errret_int)
errret = errret_int.unpack('i')[0]
case ret
when 0 # OK
0
when -1 # ERR
case errret
when 1
raise TerminfoError.new('The terminal is hardcopy, cannot be used for curses applications.')
when 0
raise TerminfoError.new('The terminal could not be found, or that it is a generic type, having too little information for curses applications to run.')
when -1
raise TerminfoError.new('The terminfo database could not be found.')
else # unknown
-1
end
else # unknown
-2
end
end

def self.tigetstr(capname)
result = @tigetstr.(capname).to_s
def result.tiparm(*args) # for method chain
Reline::Terminfo.tiparm(self, *args)
end
result
end

def self.tiparm(str, *args)
new_args = []
args.each do |a|
new_args << Fiddle::TYPE_INT << a
end
@tiparm.(str, *new_args).to_s
end

def self.enabled?
true
end
end if Reline::Terminfo.curses_dl

module Reline::Terminfo
def self.enabled?
false
end
end unless Reline::Terminfo.curses_dl

0 comments on commit 028656c

Please sign in to comment.