File: line.rb

package info (click to toggle)
ruby-tty-reader 0.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 772 kB
  • sloc: ruby: 1,759; sh: 4; makefile: 4
file content (261 lines) | stat: -rw-r--r-- 5,411 bytes parent folder | download
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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# frozen_string_literal: true

require "forwardable"

module TTY
  class Reader
    class Line
      ANSI_MATCHER = /(\[)?\033(\[)?[;?\d]*[\dA-Za-z](\])?/

      # Strip ANSI characters from the text
      #
      # @param [String] text
      #
      # @return [String]
      #
      # @api public
      def self.sanitize(text)
        text.dup.gsub(ANSI_MATCHER, "")
      end

      # The editable text
      # @api public
      attr_reader :text

      # The current cursor position witin the text
      # @api public
      attr_reader :cursor

      # The line mode
      # @api public
      attr_reader :mode

      # The prompt displayed before input
      # @api public
      attr_reader :prompt

      # Create a Line instance
      #
      # @api private
      def initialize(text = "", prompt: "")
        @prompt = prompt.dup
        @text   = text.dup
        @cursor = [0, @text.length].max
        @mode   = :edit

        yield self if block_given?
      end

      # Check if line is in edit mode
      #
      # @return [Boolean]
      #
      # @public
      def editing?
        @mode == :edit
      end

      # Enable edit mode
      #
      # @return [Boolean]
      #
      # @public
      def edit_mode
        @mode = :edit
      end

      # Check if line is in replace mode
      #
      # @return [Boolean]
      #
      # @public
      def replacing?
        @mode == :replace
      end

      # Enable replace mode
      #
      # @return [Boolean]
      #
      # @public
      def replace_mode
        @mode = :replace
      end

      # Check if cursor reached beginning of the line
      #
      # @return [Boolean]
      #
      # @api public
      def start?
        @cursor.zero?
      end

      # Check if cursor reached end of the line
      #
      # @return [Boolean]
      #
      # @api public
      def end?
        @cursor == @text.length
      end

      # Move line position to the left by n chars
      #
      # @api public
      def left(n = 1)
        @cursor = [0, @cursor - n].max
      end

      # Move line position to the right by n chars
      #
      # @api public
      def right(n = 1)
        @cursor = [@text.length, @cursor + n].min
      end

      # Move cursor to beginning position
      #
      # @api public
      def move_to_start
        @cursor = 0
      end

      # Move cursor to end position
      #
      # @api public
      def move_to_end
        @cursor = @text.length # put cursor outside of text
      end

      # Insert characters inside a line. When the lines exceeds
      # maximum length, an extra space is added to accomodate index.
      #
      # @param [Integer] i
      #   the index to insert at
      #
      # @param [String] chars
      #   the characters to insert
      #
      # @example
      #   text = "aaa"
      #   line[5]= "b"
      #   => "aaa  b"
      #
      # @api public
      def []=(i, chars)
        edit_mode

        if i.is_a?(Range)
          @text[i] = chars
          @cursor += chars.length
          return
        end

        if i <= 0
          before_text = ""
          after_text = @text.dup
        elsif i > @text.length - 1 # insert outside of line input
          before_text = @text.dup
          after_text = ?\s * (i - @text.length)
          @cursor += after_text.length
        else
          before_text = @text[0..i-1].dup
          after_text  = @text[i..-1].dup
        end

        if i > @text.length - 1
          @text = before_text + after_text + chars
        else
          @text = before_text + chars + after_text
        end

        @cursor = i + chars.length
      end

      # Read character
      #
      # @api public
      def [](i)
        @text[i]
      end

      # Replace current line with new text
      #
      # @param [String] text
      #
      # @api public
      def replace(text)
        @text = text
        @cursor = @text.length # put cursor outside of text
        replace_mode
      end

      # Insert char(s) at cursor position
      #
      # @api public
      def insert(chars)
        self[@cursor] = chars
      end

      # Add char and move cursor
      #
      # @api public
      def <<(char)
        @text << char
        @cursor += 1
      end

      # Remove char from the line at current position
      #
      # @api public
      def delete(n = 1)
        @text.slice!(@cursor, n)
      end

      # Remove char from the line in front of the cursor
      #
      # @param [Integer] n
      #   the number of chars to remove
      #
      # @api public
      def remove(n = 1)
        left(n)
        @text.slice!(@cursor, n)
      end

      # Full line with prompt as string
      #
      # @api public
      def to_s
        "#{@prompt}#{@text}"
      end
      alias inspect to_s

      # Prompt size
      #
      # @api public
      def prompt_size
        p = self.class.sanitize(@prompt).split(/\r?\n/)
        # return the length of each line + screen width for every line past the first
        # which accounts for multi-line prompts
        p.join.length + ((p.length - 1) * TTY::Screen.width )
      end

      # Text size
      #
      # @api public
      def text_size
        self.class.sanitize(@text).size
      end

      # Full line size with prompt
      #
      # @api public
      def size
        prompt_size + text_size
      end
      alias length size
    end # Line
  end # Reader
end # TTY