yap-rawline 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,78 @@
1
+ module RawLine
2
+ class EventLoop
3
+ attr_reader :events
4
+
5
+ def initialize(registry:)
6
+ @registry = registry
7
+ @events = []
8
+ end
9
+
10
+ # event looks like:
11
+ # * name
12
+ # * source
13
+ # * target
14
+ # * payload
15
+ def add_event(**event)
16
+ # if the last event is the same as the incoming then do there is no
17
+ # need to add it again. For example, rendering events that already
18
+ # back can be squashed into a single event.
19
+ if @events.last != event
20
+ @events << event
21
+ end
22
+ end
23
+
24
+ def recur(event:nil, interval_in_ms:, &blk)
25
+ if block_given?
26
+ # TODO: implement
27
+ elsif event
28
+ add_event event.merge(recur: { interval_in_ms: interval_in_ms, recur_at: recur_at(interval_in_ms) })
29
+ else
30
+ raise "Must pass in a block or an event."
31
+ end
32
+ end
33
+
34
+ def start
35
+ loop do
36
+ event = @events.shift
37
+ if event
38
+ recur = event[:recur]
39
+ if recur
40
+ if current_time_in_ms >= recur[:recur_at]
41
+ dispatch_event(event)
42
+ interval_in_ms = recur[:interval_in_ms]
43
+ add_event event.merge(recur: { interval_in_ms: interval_in_ms, recur_at: recur_at(interval_in_ms) } )
44
+ else
45
+ # put it back on the queue
46
+ add_event event
47
+ dispatch_event(default_event)
48
+ end
49
+ else
50
+ dispatch_event(event)
51
+ end
52
+ else
53
+ dispatch_event(default_event)
54
+ end
55
+ end
56
+ end
57
+
58
+ private
59
+
60
+ def current_time_in_ms
61
+ (Time.now.to_f * 1_000).to_i
62
+ end
63
+
64
+ def default_event
65
+ { name: 'default', source: self }
66
+ end
67
+
68
+ def recur_at(interval_in_ms)
69
+ current_time_in_ms + interval_in_ms
70
+ end
71
+
72
+ def dispatch_event(event)
73
+ @registry.subscribers_for_event(event[:name]).each do |subscriber|
74
+ subscriber.call(event)
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,17 @@
1
+ module RawLine
2
+ class EventRegistry
3
+ def initialize(&blk)
4
+ @subscribers = Hash.new{ |h,k| h[k.to_sym] = [] }
5
+ blk.call(self) if block_given?
6
+ end
7
+
8
+ def subscribe(event_name, *subscribers, &blk)
9
+ subscribers << blk if block_given?
10
+ @subscribers[event_name.to_sym].concat(subscribers)
11
+ end
12
+
13
+ def subscribers_for_event(event_name)
14
+ @subscribers[event_name.to_sym]
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,177 @@
1
+ #!usr/bin/env ruby
2
+
3
+ #
4
+ # history_buffer.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+ #
12
+ #
13
+ module RawLine
14
+
15
+ #
16
+ # The HistoryBuffer class is used to hold the editor and line histories, as well
17
+ # as word completion matches.
18
+ #
19
+ class HistoryBuffer < Array
20
+
21
+ attr_reader :position, :size
22
+ attr_accessor :duplicates, :exclude, :cycle
23
+
24
+ #
25
+ # Create an instance of RawLine::HistoryBuffer.
26
+ # This method takes an optional block used to override the
27
+ # following instance attributes:
28
+ # * <tt>@duplicates</tt> - whether or not duplicate items will be stored in the buffer.
29
+ # * <tt>@exclude</tt> - a Proc object defining exclusion rules to prevent items from being added to the buffer.
30
+ # * <tt>@cycle</tt> - Whether or not the buffer is cyclic.
31
+ #
32
+ def initialize(size)
33
+ @duplicates = true
34
+ @exclude = lambda{|a|}
35
+ @cycle = false
36
+ yield self if block_given?
37
+ @size = size
38
+ @position = nil
39
+ end
40
+
41
+ #
42
+ # Clears the current position on the history object. Useful when deciding
43
+ # to cancel/reset history navigation.
44
+ #
45
+ def clear_position
46
+ @position = nil
47
+ end
48
+
49
+ def searching?
50
+ !!@position
51
+ end
52
+
53
+ def supports_partial_text_matching?
54
+ false
55
+ end
56
+
57
+ #
58
+ # Resize the buffer, resetting <tt>@position</tt> to nil.
59
+ #
60
+ def resize(new_size)
61
+ if new_size < @size
62
+ @size-new_size.times { pop }
63
+ end
64
+ @size = new_size
65
+ @position = nil
66
+ end
67
+
68
+ #
69
+ # Clear the content of the buffer and reset <tt>@position</tt> to nil.
70
+ #
71
+ def empty
72
+ @position = nil
73
+ clear
74
+ end
75
+
76
+ #
77
+ # Retrieve a copy of the element at <tt>@position</tt>.
78
+ #
79
+ def get
80
+ return nil unless length > 0
81
+ return nil unless @position
82
+ at(@position).dup
83
+ end
84
+
85
+ #
86
+ # Return true if <tt>@position</tt> is at the end of the buffer.
87
+ #
88
+ def end?
89
+ @position == length-1
90
+ end
91
+
92
+ #
93
+ # Return true if <tt>@position</tt> is at the start of the buffer.
94
+ #
95
+ def start?
96
+ @position == 0
97
+ end
98
+
99
+ #
100
+ # Decrement <tt>@position</tt>. By default the history will become
101
+ # positioned at the previous item.
102
+ #
103
+ # If <tt>@cycle</tt> is set to true then the history will cycle to the end
104
+ # when it finds itself at the beginning. If false calling this when
105
+ # at the beginning will result in the position not changing.
106
+ #
107
+ # If a search strategy is assigned then the method <tt>search_backward</tt> will be
108
+ # called on the search strategy to determine the position. This method is
109
+ # given any passed in <tt>options</tt> as well as a <tt>:history</tt> option. The
110
+ # <tt>:history</tt> option will be a reference to self.
111
+ #
112
+ def back(options={})
113
+ return nil unless length > 0
114
+
115
+ case @position
116
+ when nil then
117
+ @position = length-1
118
+ when 0 then
119
+ @position = length-1 if @cycle
120
+ else
121
+ @position -= 1
122
+ end
123
+ end
124
+
125
+ #
126
+ # Increment <tt>@position</tt>. By default the history will become
127
+ # positioned at the next item.
128
+ #
129
+ # If <tt>@cycle</tt> is set to true then the history will cycle back to the
130
+ # beginning when it finds itself at the end. If false calling this when
131
+ # at the end will result in the position not changing.
132
+ #
133
+ # If a search strategy is assigned then the method <tt>search_forward</tt> will be
134
+ # called on the search strategy to determine the position. This method is
135
+ # given any passed in <tt>options</tt> as well as a <tt>:history</tt> option. The
136
+ # <tt>:history</tt> option will be a reference to self. If <tt>
137
+ #
138
+ def forward(options={})
139
+ return nil unless length > 0
140
+
141
+ case @position
142
+ when nil then
143
+ nil
144
+ when length-1 then
145
+ @position = 0 if @cycle
146
+ else
147
+ @position += 1
148
+ end
149
+ end
150
+
151
+ #
152
+ # Add a new item to the buffer.
153
+ #
154
+ def push(item)
155
+
156
+ if !@duplicates && self[-1] == item
157
+ # skip adding this line
158
+ return
159
+ end
160
+
161
+ unless @exclude.call(item)
162
+ # Remove the oldest element if size is exceeded
163
+ if @size <= length
164
+ reverse!.pop
165
+ reverse!
166
+ end
167
+ # Add the new item and reset the position
168
+ super(item)
169
+ @position = nil
170
+ end
171
+ end
172
+
173
+ alias << push
174
+
175
+ end
176
+
177
+ end
@@ -0,0 +1,49 @@
1
+ module RawLine
2
+ class KeycodeParser
3
+ def initialize(keymap)
4
+ @keymap = keymap
5
+ @escape_code = keymap[:escape]
6
+ end
7
+
8
+ def parse_bytes(bytes)
9
+ i = 0
10
+ results = []
11
+ loop do
12
+ byte = bytes[i]
13
+
14
+ keycode = find_keycode_for_multi_byte_sequence(bytes[i..-1])
15
+ if keycode
16
+ results << keycode
17
+ i += keycode.length
18
+ else
19
+ results << byte.ord
20
+ i += 1
21
+ end
22
+
23
+ break if i >= bytes.length
24
+ end
25
+ results
26
+ end
27
+
28
+ private
29
+
30
+ # {:left_arrow=>[27, 91, 68]}
31
+ # [27, 91, 68]
32
+ def find_keycode_for_multi_byte_sequence(bytes)
33
+ i = 0
34
+ sequence = []
35
+ loop do
36
+ byte = bytes[i]
37
+ if @keymap.values.any?{ |arr| arr[i] == byte }
38
+ sequence << byte
39
+ i += 1
40
+ else
41
+ break
42
+ end
43
+ break if i >= bytes.length
44
+ end
45
+
46
+ sequence.any? ? sequence : nil
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,169 @@
1
+ #!usr/bin/env ruby
2
+
3
+ #
4
+ # line.rb
5
+ #
6
+ # Created by Fabio Cevasco on 2008-03-01.
7
+ # Copyright (c) 2008 Fabio Cevasco. All rights reserved.
8
+ #
9
+ # This is Free Software. See LICENSE for details.
10
+ #
11
+
12
+ module RawLine
13
+
14
+ #
15
+ # The Line class is used to represent the current line being processed and edited
16
+ # by RawLine::Editor. It keeps track of the characters typed, the cursor position,
17
+ # the current word and maintains an internal history to allow undos and redos.
18
+ #
19
+ class Line
20
+
21
+ attr_accessor :text, :position, :history, :prompt, :history_size, :word_separator
22
+ attr_reader :offset
23
+
24
+ include HighLine::SystemExtensions
25
+
26
+ #
27
+ # Create an instance of RawLine::Line.
28
+ # This method takes an optional block used to override the
29
+ # following instance attributes:
30
+ # * <tt>@text</tt> - the line text.
31
+ # * <tt>@history_size</tt> - the size of the line history buffer.
32
+ # * <tt>@position</tt> - the current cursor position within the line.
33
+ # * <tt>@prompt</tt> - a prompt to prepend to the line text.
34
+ #
35
+ def initialize(history_size)
36
+ @text = ANSIString.new("")
37
+ @history_size = history_size
38
+ @position = 0
39
+ @prompt = ""
40
+ @word_separator = ' '
41
+ yield self if block_given?
42
+ @history = RawLine::HistoryBuffer.new(@history_size)
43
+ @history << "" # Add empty line for complete undo...
44
+ @offset = @prompt.length
45
+ end
46
+
47
+ #
48
+ # Return the maximum line length. By default, it corresponds to the terminal's
49
+ # width minus the length of the line prompt.
50
+ #
51
+ def max_length
52
+ terminal_size[0]-@offset
53
+ end
54
+
55
+ #
56
+ # Return information about the current word, as a Hash composed by the following
57
+ # elements:
58
+ # * <tt>:start</tt>: The position in the line corresponding to the word start
59
+ # * <tt>:end</tt>: The position in the line corresponding to the word end
60
+ # * <tt>:text</tt>: The word text.
61
+ def word
62
+ return {:start => bol, :end => eol+1, :text => @text} if @word_separator.to_s == ''
63
+ last = @text.index(@word_separator, @position)
64
+ first = @text.rindex(@word_separator, @position)
65
+ # Trim word separators and handle EOL and BOL
66
+ if first then
67
+ first +=1
68
+ else
69
+ first = bol
70
+ end
71
+ if last then
72
+ last -=1
73
+ else
74
+ last = eol+1 unless last
75
+ end
76
+ # Swap if overlapping
77
+ last, first = first, last if last < first
78
+ text = @text[first..last].to_s
79
+ # Repeat the search if within word separator
80
+ if text.match @word_separator then
81
+ last = first
82
+ first = @text.rindex(@word_separator, first)
83
+ if first then first+=1
84
+ else first = bol
85
+ end
86
+ text = @text[first..last]
87
+ end
88
+ {:start => first, :end => last, :text => text}
89
+ end
90
+
91
+ #
92
+ # Return an array containing the words present in the current line
93
+ #
94
+ def words
95
+ @text.split @word_separator
96
+ end
97
+
98
+ #
99
+ # Return the position corresponding to the beginning of the line.
100
+ #
101
+ def bol
102
+ 0
103
+ end
104
+
105
+ #
106
+ # Return true if the cursor is at the beginning of the line.
107
+ #
108
+ def bol?
109
+ @position<=bol
110
+ end
111
+
112
+ #
113
+ # Return the position corresponding to the end of the line.
114
+ #
115
+ def eol
116
+ @text.length-1
117
+ end
118
+
119
+ #
120
+ # Return true if the cursor is at the end of the line.
121
+ #
122
+ def eol?
123
+ @position>=eol
124
+ end
125
+
126
+ #
127
+ # Decrement the line position by <tt>offset</tt>
128
+ #
129
+ def left(offset=1)
130
+ @position = (@position-offset <= 0) ? 0 : @position-offset
131
+ end
132
+
133
+ #
134
+ # Increment the line position by <tt>offset</tt>
135
+ #
136
+ def right(offset=1)
137
+ @position += offset
138
+ end
139
+
140
+ #
141
+ # Add a character (expressed as a character code) to the line text.
142
+ #
143
+ def <<(char)
144
+ @text << char.chr
145
+ end
146
+
147
+ #
148
+ # Access the line text at <tt>@index</tt>
149
+ #
150
+ def [](index)
151
+ @text[index]
152
+ end
153
+
154
+ #
155
+ # Modify the character(s) in the line text at <tt>@index</tt>
156
+ #
157
+ def []=(index, chars)
158
+ @text[index] = chars
159
+ end
160
+
161
+ #
162
+ # Return the length of the line text.
163
+ #
164
+ def length
165
+ @text.length
166
+ end
167
+
168
+ end
169
+ end