summaryrefslogtreecommitdiff
path: root/lib/highline/terminal.rb
blob: 550c0c79756c7a0ffc2d5e4f1c630cd752cd61f6 (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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# coding: utf-8

#--
# terminal.rb
#
#  Originally created by James Edward Gray II on 2006-06-14 as
#  system_extensions.rb.
#  Copyright 2006 Gray Productions. All rights reserved.
#
#  This is Free Software.  See LICENSE and COPYING for details.

require "highline/compatibility"

class HighLine
  # Basic Terminal class which HighLine will direct
  # input and output to.
  # The specialized Terminals all decend from this HighLine::Terminal class
  class Terminal
    # Probe for and return a suitable Terminal instance
    # @param input [IO] input stream
    # @param output [IO] output stream
    def self.get_terminal(input, output)
      # First of all, probe for io/console
      begin
        require "io/console"
        require "highline/terminal/io_console"
        terminal = HighLine::Terminal::IOConsole.new(input, output)
      rescue LoadError
        require "highline/terminal/unix_stty"
        terminal = HighLine::Terminal::UnixStty.new(input, output)
      end

      terminal.initialize_system_extensions
      terminal
    end

    # @return [IO] input stream
    attr_reader :input

    # @return [IO] output stream
    attr_reader :output

    # Creates a terminal instance based on given input and output streams.
    # @param input [IO] input stream
    # @param output [IO] output stream
    def initialize(input, output)
      @input  = input
      @output = output
    end

    # An initialization callback.
    # It is called by {.get_terminal}.
    def initialize_system_extensions; end

    # @return [Array<Integer, Integer>] two value terminal
    #   size like [columns, lines]
    def terminal_size
      [80, 24]
    end

    # Enter Raw No Echo mode.
    def raw_no_echo_mode; end

    # Yieds a block to be executed in Raw No Echo mode and
    # then restore the terminal state.
    def raw_no_echo_mode_exec
      raw_no_echo_mode
      yield
    ensure
      restore_mode
    end

    # Restore terminal to its default mode
    def restore_mode; end

    # Get one character from the terminal
    # @return [String] one character
    def get_character; end # rubocop:disable Naming/AccessorMethodName

    # Get one line from the terminal and format accordling.
    # Use readline if question has readline mode set.
    # @param question [HighLine::Question]
    # @param highline [HighLine]
    def get_line(question, highline)
      raw_answer =
        if question.readline
          get_line_with_readline(question, highline)
        else
          get_line_default(highline)
        end

      question.format_answer(raw_answer)
    end

    # Get one line using #readline_read
    # @param (see #get_line)
    def get_line_with_readline(question, highline)
      require "readline" # load only if needed

      raw_answer = readline_read(question)

      if !raw_answer && highline.track_eof?
        raise EOFError, "The input stream is exhausted."
      end

      raw_answer || ""
    end

    # Use readline to read one line
    # @param question [HighLine::Question] question from where to get
    #   autocomplete candidate strings
    def readline_read(question)
      # prep auto-completion
      unless question.selection.empty?
        Readline.completion_proc = lambda do |str|
          question.selection.grep(/\A#{Regexp.escape(str)}/)
        end
      end

      # work-around ugly readline() warnings
      old_verbose = $VERBOSE
      $VERBOSE    = nil

      raw_answer  = run_preserving_stty do
        Readline.readline("", true)
      end

      $VERBOSE = old_verbose

      raw_answer
    end

    # Get one line from terminal using default #gets method.
    # @param highline (see #get_line)
    def get_line_default(highline)
      raise EOFError, "The input stream is exhausted." if highline.track_eof? &&
                                                          highline.input.eof?
      highline.input.gets
    end

    # @!group Enviroment queries

    # Running on JRuby?
    def jruby?
      defined?(RUBY_ENGINE) && RUBY_ENGINE == "jruby"
    end

    # Running on Rubinius?
    def rubinius?
      defined?(RUBY_ENGINE) && RUBY_ENGINE == "rbx"
    end

    # Running on Windows?
    def windows?
      defined?(RUBY_PLATFORM) && (RUBY_PLATFORM =~ /mswin|mingw|cygwin/)
    end

    # @!endgroup

    # Returns the class name as String. Useful for debuggin.
    # @return [String] class name. Ex: "HighLine::Terminal::IOConsole"
    def character_mode
      self.class.name
    end

    private

    # Yield a block using stty shell commands to preserve the terminal state.
    def run_preserving_stty
      save_stty
      yield
    ensure
      restore_stty
    end

    # Saves terminal state using shell stty command.
    def save_stty
      @stty_save = begin
                     `stty -g`.chomp
                   rescue StandardError
                     nil
                   end
    end

    # Restores terminal state using shell stty command.
    def restore_stty
      system("stty", @stty_save) if @stty_save
    end
  end
end