fir-repl 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|