summaryrefslogtreecommitdiff
path: root/lib/reline/ansi.rb
blob: b12dc2f6d32d3f36a040a3a08a3f9b5408c525c8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
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
137
138
139
140
141
142
143
144
145
146
147
class Reline::ANSI
  RAW_KEYSTROKE_CONFIG = {
    [27, 91, 65] => :ed_prev_history,     # ↑
    [27, 91, 66] => :ed_next_history,     # ↓
    [27, 91, 67] => :ed_next_char,        # →
    [27, 91, 68] => :ed_prev_char,        # ←
    [27, 91, 51, 126] => :key_delete,     # Del
    [27, 91, 49, 126] => :ed_move_to_beg, # Home
    [27, 91, 52, 126] => :ed_move_to_end, # End
  }.each_key(&:freeze).freeze

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

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

  @@buf = []
  def self.getc
    unless @@buf.empty?
      return @@buf.shift
    end
    c = nil
    loop do
      result = select([@@input], [], [], 0.1)
      next if result.nil?
      c = @@input.read(1)
      break
    end
    c&.ord
  end

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

  def self.retrieve_keybuffer
      result = select([@@input], [], [], 0.1)
      return if result.nil?
      str = @@input.read_nonblock(1024)
      str.bytes.each do |c|
        @@buf.push(c)
      end
  end

  def self.get_screen_size
    @@input.winsize
  rescue Errno::ENOTTY
    [24, 80]
  end

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

  def self.cursor_pos
    begin
      res = ''
      @@input.raw do |stdin|
        @@output << "\e[6n"
        @@output.flush
        while (c = stdin.getc) != 'R'
          res << c if c
        end
      end
      m = res.match(/(?<row>\d+);(?<column>\d+)/)
      column = m[:column].to_i - 1
      row = m[:row].to_i - 1
    rescue Errno::ENOTTY
      buf = @@output.pread(@@output.pos, 0)
      row = buf.count("\n")
      column = buf.rindex("\n") ? (buf.size - buf.rindex("\n")) - 1 : 0
    end
    Reline::CursorPos.new(column, row)
  end

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

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

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

  def self.erase_after_cursor
    print "\e[K"
  end

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

  def self.clear_screen
    print "\e[2J"
    print "\e[1;1H"
  end

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

  def self.prep
    retrieve_keybuffer
    int_handle = Signal.trap('INT', 'IGNORE')
    otio = `stty -g`.chomp
    setting = ' -echo -icrnl cbreak'
    stty = `stty -a`
    if /-parenb\b/ =~ stty
      setting << ' pass8'
    end
    if /\bdsusp *=/ =~ stty
      setting << ' dsusp undef'
    end
    setting << ' -ixoff'
    `stty #{setting}`
    Signal.trap('INT', int_handle)
    otio
  end

  def self.deprep(otio)
    int_handle = Signal.trap('INT', 'IGNORE')
    `stty #{otio}`
    Signal.trap('INT', int_handle)
    Signal.trap('WINCH', @@old_winch_handler) if @@old_winch_handler
  end
end