Class: Reline::ANSI

Inherits:
Object
  • Object
show all
Defined in:
lib/reline/ansi.rb

Constant Summary collapse

CAPNAME_KEY_BINDINGS =
{
  'khome' => :ed_move_to_beg,
  'kend'  => :ed_move_to_end,
  'kdch1' => :key_delete,
  'kpp' => :ed_search_prev_history,
  'knp' => :ed_search_next_history,
  'kcuu1' => :ed_prev_history,
  'kcud1' => :ed_next_history,
  'kcuf1' => :ed_next_char,
  'kcub1' => :ed_prev_char,
  'cuu' => :ed_prev_history,
  'cud' => :ed_next_history,
  'cuf' => :ed_next_char,
  'cub' => :ed_prev_char,
}
START_BRACKETED_PASTE =
String.new("\e[200~,", encoding: Encoding::ASCII_8BIT)
END_BRACKETED_PASTE =
String.new("\e[200~.", encoding: Encoding::ASCII_8BIT)
@@input =
STDIN
@@output =
STDOUT
@@buf =
[]
@@in_bracketed_paste_mode =
false
@@old_winch_handler =
nil

Class Method Summary collapse

Class Method Details

.clear_screenObject



339
340
341
342
# File 'lib/reline/ansi.rb', line 339

def self.clear_screen
  @@output.write "\e[2J"
  @@output.write "\e[1;1H"
end

.cursor_posObject



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/reline/ansi.rb', line 251

def self.cursor_pos
  begin
    res = +''
    m = nil
    @@input.raw do |stdin|
      @@output << "\e[6n"
      @@output.flush
      loop do
        c = stdin.getc
        next if c.nil?
        res << c
        m = res.match(/\e\[(?<row>\d+);(?<column>\d+)R/)
        break if m
      end
      (m.pre_match + m.post_match).chars.reverse_each do |ch|
        stdin.ungetc ch
      end
    end
    column = m[:column].to_i - 1
    row = m[:row].to_i - 1
  rescue Errno::ENOTTY
    begin
      buf = @@output.pread(@@output.pos, 0)
      row = buf.count("\n")
      column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
    rescue Errno::ESPIPE
      # Just returns column 1 for ambiguous width because this I/O is not
      # tty and can't seek.
      row = 0
      column = 1
    end
  end
  Reline::CursorPos.new(column, row)
end

.deprep(otio) ⇒ Object



354
355
356
# File 'lib/reline/ansi.rb', line 354

def self.deprep(otio)
  Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
end

.empty_buffer?Boolean

Returns:

  • (Boolean)


212
213
214
215
216
217
# File 'lib/reline/ansi.rb', line 212

def self.empty_buffer?
  unless @@buf.empty?
    return false
  end
  !@@input.wait_readable(0)
end

.encodingObject



27
28
29
# File 'lib/reline/ansi.rb', line 27

def self.encoding
  Encoding.default_external
end

.erase_after_cursorObject



330
331
332
# File 'lib/reline/ansi.rb', line 330

def self.erase_after_cursor
  @@output.write "\e[K"
end

.get_screen_sizeObject



234
235
236
237
238
239
240
241
242
# File 'lib/reline/ansi.rb', line 234

def self.get_screen_size
  s = @@input.winsize
  return s if s[0] > 0 && s[1] > 0
  s = [ENV["LINES"].to_i, ENV["COLUMNS"].to_i]
  return s if s[0] > 0 && s[1] > 0
  [24, 80]
rescue Errno::ENOTTY
  [24, 80]
end

.getcObject



200
201
202
203
204
205
206
# File 'lib/reline/ansi.rb', line 200

def self.getc
  if Reline.core.config.enable_bracketed_paste
    getc_with_bracketed_paste
  else
    inner_getc
  end
end

.getc_with_bracketed_pasteObject



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/reline/ansi.rb', line 171

def self.getc_with_bracketed_paste
  buffer = String.new(encoding: Encoding::ASCII_8BIT)
  buffer << inner_getc
  while START_BRACKETED_PASTE.start_with?(buffer) or END_BRACKETED_PASTE.start_with?(buffer) do
    if START_BRACKETED_PASTE == buffer
      @@in_bracketed_paste_mode = true
      return inner_getc
    elsif END_BRACKETED_PASTE == buffer
      @@in_bracketed_paste_mode = false
      ungetc(-1)
      return inner_getc
    end
    begin
      succ_c = nil
      Timeout.timeout(Reline.core.config.keyseq_timeout * 100) {
        succ_c = inner_getc
      }
    rescue Timeout::Error
      break
    else
      buffer << succ_c
    end
  end
  buffer.bytes.reverse_each do |ch|
    ungetc ch
  end
  inner_getc
end

.hide_cursorObject



306
307
308
309
310
311
312
313
314
315
316
# File 'lib/reline/ansi.rb', line 306

def self.hide_cursor
  if Reline::Terminfo.enabled?
    begin
      @@output.write Reline::Terminfo.tigetstr('civis')
    rescue Reline::Terminfo::TerminfoError
      # civis is undefined
    end
  else
    # ignored
  end
end

.in_pasting?Boolean

Returns:

  • (Boolean)


208
209
210
# File 'lib/reline/ansi.rb', line 208

def self.in_pasting?
  @@in_bracketed_paste_mode or (not Reline::IOGate.empty_buffer?)
end

.inner_getcObject



153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/reline/ansi.rb', line 153

def self.inner_getc
  unless @@buf.empty?
    return @@buf.shift
  end
  until c = @@input.raw(intr: true) { @@input.wait_readable(0.1) && @@input.getbyte }
    Reline.core.line_editor.resize
  end
  (c == 0x16 && @@input.raw(min: 0, time: 0, &:getbyte)) || c
rescue Errno::EIO
  # Maybe the I/O has been closed.
  nil
rescue Errno::ENOTTY
  nil
end

.input=(val) ⇒ Object



139
140
141
# File 'lib/reline/ansi.rb', line 139

def self.input=(val)
  @@input = val
end

.move_cursor_column(x) ⇒ Object



286
287
288
# File 'lib/reline/ansi.rb', line 286

def self.move_cursor_column(x)
  @@output.write "\e[#{x + 1}G"
end

.move_cursor_down(x) ⇒ Object



298
299
300
301
302
303
304
# File 'lib/reline/ansi.rb', line 298

def self.move_cursor_down(x)
  if x > 0
    @@output.write "\e[#{x}B"
  elsif x < 0
    move_cursor_up(-x)
  end
end

.move_cursor_up(x) ⇒ Object



290
291
292
293
294
295
296
# File 'lib/reline/ansi.rb', line 290

def self.move_cursor_up(x)
  if x > 0
    @@output.write "\e[#{x}A"
  elsif x < 0
    move_cursor_down(-x)
  end
end

.output=(val) ⇒ Object



144
145
146
# File 'lib/reline/ansi.rb', line 144

def self.output=(val)
  @@output = val
end

.prepObject



349
350
351
352
# File 'lib/reline/ansi.rb', line 349

def self.prep
  retrieve_keybuffer
  nil
end

.retrieve_keybufferObject



223
224
225
226
227
228
229
230
231
232
# File 'lib/reline/ansi.rb', line 223

def self.retrieve_keybuffer
  begin
    return unless @@input.wait_readable(0.001)
    str = @@input.read_nonblock(1024)
    str.bytes.each do |c|
      @@buf.push(c)
    end
  rescue EOFError
  end
end

.scroll_down(x) ⇒ Object



334
335
336
337
# File 'lib/reline/ansi.rb', line 334

def self.scroll_down(x)
  return if x.zero?
  @@output.write "\e[#{x}S"
end

.set_default_key_bindings(config, allow_terminfo: true) ⇒ Object



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/reline/ansi.rb', line 35

def self.set_default_key_bindings(config, allow_terminfo: true)
  if allow_terminfo && 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
  {
    [27, 91, 90] => :completion_journey_up, # S-Tab
  }.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)
  end
  {
    # default bindings
    [27, 32] => :em_set_mark,             # M-<space>
    [24, 24] => :em_exchange_mark,        # C-x C-x
  }.each_pair do |key, func|
    config.add_default_key_binding_by_keymap(:emacs, key, func)
  end
end

.set_default_key_bindings_comprehensive_list(config) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/reline/ansi.rb', line 91

def self.set_default_key_bindings_comprehensive_list(config)
  {
    # Console (80x25)
    [27, 91, 49, 126] => :ed_move_to_beg, # Home
    [27, 91, 52, 126] => :ed_move_to_end, # End
    [27, 91, 51, 126] => :key_delete,     # Del
    [27, 91, 65] => :ed_prev_history,     # ↑
    [27, 91, 66] => :ed_next_history,     # ↓
    [27, 91, 67] => :ed_next_char,        # →
    [27, 91, 68] => :ed_prev_char,        # ←

    # KDE
    [27, 91, 72] => :ed_move_to_beg,      # Home
    [27, 91, 70] => :ed_move_to_end,      # End
    # Del is 0x08
    [27, 71, 65] => :ed_prev_history,     # ↑
    [27, 71, 66] => :ed_next_history,     # ↓
    [27, 71, 67] => :ed_next_char,        # →
    [27, 71, 68] => :ed_prev_char,        # ←

    # urxvt / exoterm
    [27, 91, 55, 126] => :ed_move_to_beg, # Home
    [27, 91, 56, 126] => :ed_move_to_end, # End

    # GNOME
    [27, 79, 72] => :ed_move_to_beg,      # Home
    [27, 79, 70] => :ed_move_to_end,      # End
    # Del is 0x08
    # Arrow keys are the same of KDE

    # iTerm2
    [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

    [27, 79, 65] => :ed_prev_history,     # ↑
    [27, 79, 66] => :ed_next_history,     # ↓
    [27, 79, 67] => :ed_next_char,        # →
    [27, 79, 68] => :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

.set_default_key_bindings_terminfo(config) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/reline/ansi.rb', line 67

def self.set_default_key_bindings_terminfo(config)
  key_bindings = CAPNAME_KEY_BINDINGS.map do |capname, key_binding|
    begin
      key_code = Reline::Terminfo.tigetstr(capname)
      case capname
      # Escape sequences that omit the move distance and are set to defaults
      # value 1 may be sometimes sent by pressing the arrow-key.
      when 'cuu', 'cud', 'cuf', 'cub'
        [ key_code.sub(/%p1%d/, '').bytes, key_binding ]
      else
        [ key_code.bytes, key_binding ]
      end
    rescue Reline::Terminfo::TerminfoError
      # capname is undefined
    end
  end.compact.to_h

  key_bindings.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

.set_screen_size(rows, columns) ⇒ Object



244
245
246
247
248
249
# File 'lib/reline/ansi.rb', line 244

def self.set_screen_size(rows, columns)
  @@input.winsize = [rows, columns]
  self
rescue Errno::ENOTTY
  self
end

.set_winch_handler(&handler) ⇒ Object



345
346
347
# File 'lib/reline/ansi.rb', line 345

def self.set_winch_handler(&handler)
  @@old_winch_handler = Signal.trap('WINCH', &handler)
end

.show_cursorObject



318
319
320
321
322
323
324
325
326
327
328
# File 'lib/reline/ansi.rb', line 318

def self.show_cursor
  if Reline::Terminfo.enabled?
    begin
      @@output.write Reline::Terminfo.tigetstr('cnorm')
    rescue Reline::Terminfo::TerminfoError
      # cnorm is undefined
    end
  else
    # ignored
  end
end

.ungetc(c) ⇒ Object



219
220
221
# File 'lib/reline/ansi.rb', line 219

def self.ungetc(c)
  @@buf.unshift(c)
end

.win?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/reline/ansi.rb', line 31

def self.win?
  false
end

.with_raw_inputObject



148
149
150
# File 'lib/reline/ansi.rb', line 148

def self.with_raw_input
  @@input.raw { yield }
end