fir-repl 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/README.md +64 -0
- data/bin/fir +6 -0
- data/lib/fir.rb +59 -0
- data/lib/fir/cursor.rb +46 -0
- data/lib/fir/eraser.rb +23 -0
- data/lib/fir/evaluater.rb +52 -0
- data/lib/fir/history.rb +53 -0
- data/lib/fir/indent.rb +228 -0
- data/lib/fir/key.rb +24 -0
- data/lib/fir/key_command/backspace_command.rb +28 -0
- data/lib/fir/key_command/ctrl_a_command.rb +21 -0
- data/lib/fir/key_command/ctrl_c_command.rb +17 -0
- data/lib/fir/key_command/ctrl_d_command.rb +16 -0
- data/lib/fir/key_command/ctrl_e_command.rb +19 -0
- data/lib/fir/key_command/ctrl_v_command.rb +23 -0
- data/lib/fir/key_command/ctrl_z_command.rb +17 -0
- data/lib/fir/key_command/down_arrow_command.rb +17 -0
- data/lib/fir/key_command/enter_command.rb +20 -0
- data/lib/fir/key_command/key_command.rb +67 -0
- data/lib/fir/key_command/left_arrow_command.rb +19 -0
- data/lib/fir/key_command/right_arrow_command.rb +21 -0
- data/lib/fir/key_command/single_key_command.rb +23 -0
- data/lib/fir/key_command/tab_command.rb +22 -0
- data/lib/fir/key_command/up_arrow_command.rb +17 -0
- data/lib/fir/lines.rb +57 -0
- data/lib/fir/renderer.rb +65 -0
- data/lib/fir/repl_state.rb +102 -0
- data/lib/fir/screen.rb +26 -0
- data/lib/fir/screen_helper.rb +30 -0
- data/lib/fir/suggestion.rb +31 -0
- data/lib/fir/version.rb +6 -0
- metadata +76 -0
data/lib/fir/key.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
class Fir
|
5
|
+
class Key
|
6
|
+
attr_reader :input
|
7
|
+
|
8
|
+
def initialize(input)
|
9
|
+
@input = input
|
10
|
+
end
|
11
|
+
|
12
|
+
def get
|
13
|
+
input.raw do |raw_input|
|
14
|
+
key = raw_input.sysread(1).chr
|
15
|
+
if key == "\e"
|
16
|
+
skt = Thread.new { 2.times { key += raw_input.sysread(1).chr } }
|
17
|
+
skt.join(0.0001)
|
18
|
+
skt.kill
|
19
|
+
end
|
20
|
+
key
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class BackspaceCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\177$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
unless state.blank?
|
14
|
+
if state.cursor.x.positive?
|
15
|
+
new_state.cursor = state.cursor.left(1)
|
16
|
+
new_line = state.current_line.clone
|
17
|
+
new_line.delete_at(state.cursor.x - 1)
|
18
|
+
new_state.current_line = new_line
|
19
|
+
elsif state.cursor.x.zero? && state.cursor.y.positive?
|
20
|
+
new_state.cursor =
|
21
|
+
state.cursor.up.right(state.lines[state.cursor.y - 1].length)
|
22
|
+
new_state.lines = state.lines.remove
|
23
|
+
end
|
24
|
+
end
|
25
|
+
new_state
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class CtrlACommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\x01$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
unless state.blank?
|
14
|
+
if state.cursor.x.positive?
|
15
|
+
new_state.cursor = state.cursor.left(state.cursor.x)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
new_state
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class CtrlCCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\u0003$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
new_state.history.reset
|
14
|
+
new_state.blank
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class CtrlECommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\x05$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
unless state.blank?
|
14
|
+
new_state.cursor = state.cursor.right(state.current_line.length - state.cursor.x)
|
15
|
+
end
|
16
|
+
new_state
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class CtrlVCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\u0016$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
paste_buffer = `pbpaste`
|
14
|
+
new_state.current_line = state.current_line.clone.insert(
|
15
|
+
state.cursor.x,
|
16
|
+
*paste_buffer.split('')
|
17
|
+
).flatten
|
18
|
+
new_state.cursor = state.cursor.right(paste_buffer.length)
|
19
|
+
new_state.history.reset
|
20
|
+
new_state
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class CtrlZCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\u001A$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
`kill -TSTP #{Process.pid}`
|
14
|
+
new_state
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class DownArrowCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
[/^\e\[B$/, /\x0E/]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
new_state.history.down
|
14
|
+
new_state
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class EnterCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\r$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
new_state.commit_current_line_to_history
|
14
|
+
new_state.lines = state.lines.add([])
|
15
|
+
new_state.cursor = state.cursor.down.left(state.cursor.x)
|
16
|
+
new_state.history.reset
|
17
|
+
new_state
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
class Fir
|
5
|
+
class KeyCommand
|
6
|
+
attr_reader :character
|
7
|
+
attr_reader :state
|
8
|
+
|
9
|
+
def self.build(character, state)
|
10
|
+
find(character).new(character, state)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.for(character, state)
|
14
|
+
registry.find do |candidate|
|
15
|
+
candidate.handles?(character)
|
16
|
+
end.new(character, state)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.registry
|
20
|
+
@registry ||= [KeyCommand]
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.register(candidate)
|
24
|
+
registry.unshift(candidate)
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.inherited(candidate)
|
28
|
+
register(candidate)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.handles?(character)
|
32
|
+
Array(character_regex).any? { |re| re.match(character) }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.character_regex
|
36
|
+
/.*/
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(character, state)
|
40
|
+
@character = character
|
41
|
+
@state = state
|
42
|
+
end
|
43
|
+
|
44
|
+
def execute
|
45
|
+
execute_hook(state.clone)
|
46
|
+
end
|
47
|
+
|
48
|
+
def execute_hook(new_state)
|
49
|
+
new_state
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
require_relative './single_key_command'
|
55
|
+
require_relative './enter_command'
|
56
|
+
require_relative './tab_command'
|
57
|
+
require_relative './backspace_command'
|
58
|
+
require_relative './ctrl_c_command'
|
59
|
+
require_relative './ctrl_d_command'
|
60
|
+
require_relative './ctrl_z_command'
|
61
|
+
require_relative './ctrl_v_command'
|
62
|
+
require_relative './ctrl_a_command'
|
63
|
+
require_relative './ctrl_e_command'
|
64
|
+
require_relative './left_arrow_command'
|
65
|
+
require_relative './right_arrow_command'
|
66
|
+
require_relative './up_arrow_command'
|
67
|
+
require_relative './down_arrow_command'
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class LeftArrowCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
[/^\e\[D$/, /^\x02$/]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
unless state.blank?
|
14
|
+
new_state.cursor = state.cursor.left(1) if state.cursor.x.positive?
|
15
|
+
end
|
16
|
+
new_state
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class RightArrowCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
[/^\e\[C$/, /^\x06$/]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
unless state.blank?
|
14
|
+
if state.cursor.x < state.current_line.length
|
15
|
+
new_state.cursor = state.cursor.right(1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
new_state
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class SingleKeyCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
# Matches all printable ASCII characters
|
10
|
+
/[ -~]/
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute_hook(new_state)
|
14
|
+
new_state.current_line = state.lines[-1].clone.insert(
|
15
|
+
state.cursor.x,
|
16
|
+
character
|
17
|
+
)
|
18
|
+
new_state.cursor = state.cursor.right(1)
|
19
|
+
new_state.history.reset
|
20
|
+
new_state
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class TabCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
/^\t$/
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
new_state.current_line = state.current_line.clone.insert(
|
14
|
+
state.cursor.x,
|
15
|
+
*state.suggestion.split('')
|
16
|
+
).flatten
|
17
|
+
new_state.cursor = state.cursor.right(state.suggestion.length)
|
18
|
+
new_state.history.reset
|
19
|
+
new_state
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
require_relative './key_command'
|
5
|
+
|
6
|
+
class Fir
|
7
|
+
class UpArrowCommand < KeyCommand
|
8
|
+
def self.character_regex
|
9
|
+
[/^\e\[A$/, /\x10/]
|
10
|
+
end
|
11
|
+
|
12
|
+
def execute_hook(new_state)
|
13
|
+
new_state.history.up
|
14
|
+
new_state
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
data/lib/fir/lines.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
# encoding: UTF-8
|
3
|
+
|
4
|
+
class Fir
|
5
|
+
class Lines
|
6
|
+
include Enumerable
|
7
|
+
attr_reader :members
|
8
|
+
|
9
|
+
def initialize(*members)
|
10
|
+
@members = members
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.blank
|
14
|
+
new([])
|
15
|
+
end
|
16
|
+
|
17
|
+
def blank?
|
18
|
+
@members == [[]]
|
19
|
+
end
|
20
|
+
|
21
|
+
def clone
|
22
|
+
self.class.new(*@members.clone.map(&:clone))
|
23
|
+
end
|
24
|
+
|
25
|
+
def each(&block)
|
26
|
+
@members.each(&block)
|
27
|
+
end
|
28
|
+
|
29
|
+
def [](key)
|
30
|
+
@members[key].clone
|
31
|
+
end
|
32
|
+
|
33
|
+
def []=(key, value)
|
34
|
+
@members[key] = value
|
35
|
+
end
|
36
|
+
|
37
|
+
def length
|
38
|
+
@members.length
|
39
|
+
end
|
40
|
+
|
41
|
+
def join(chr = nil)
|
42
|
+
map(&:join).join(chr)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ==(other)
|
46
|
+
other.members == members
|
47
|
+
end
|
48
|
+
|
49
|
+
def add(n)
|
50
|
+
self.class.new(*(@members + [n]))
|
51
|
+
end
|
52
|
+
|
53
|
+
def remove
|
54
|
+
self.class.new(*(@members[0...-1]))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|