text_editor 0.0.0 → 0.0.1
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 +4 -4
- data/.rubocop.yml +3 -0
- data/.text_editor.rb +81 -2
- data/Gemfile.lock +1 -0
- data/README.org +103 -0
- data/bin/console +1 -1
- data/lib/core_ext/hash.rb +2 -0
- data/lib/core_ext/pathname.rb +2 -0
- data/lib/text_editor.rb +43 -13
- data/lib/text_editor/buffer.rb +3 -0
- data/lib/text_editor/clipboard.rb +1 -1
- data/lib/text_editor/clipboard/cygwin.rb +0 -4
- data/lib/text_editor/clipboard/linux.rb +0 -4
- data/lib/text_editor/clipboard/mac.rb +0 -4
- data/lib/text_editor/clipboard/unknown.rb +0 -4
- data/lib/text_editor/clipboard/windows.rb +0 -4
- data/lib/text_editor/command.rb +4 -4
- data/lib/text_editor/command/append_line_and_insert.rb +11 -0
- data/lib/text_editor/command/change_mode.rb +9 -0
- data/lib/text_editor/command/cursor_end_and_insert.rb +9 -0
- data/lib/text_editor/command/cursor_left.rb +1 -8
- data/lib/text_editor/command/cursor_left_with_wrap.rb +16 -0
- data/lib/text_editor/command/cursor_right.rb +2 -9
- data/lib/text_editor/command/cursor_right_and_insert.rb +9 -0
- data/lib/text_editor/command/cursor_right_with_wrap.rb +19 -0
- data/lib/text_editor/command/cursor_start_without_whitespace.rb +11 -0
- data/lib/text_editor/command/cursor_up.rb +1 -1
- data/lib/text_editor/command/forward_word.rb +16 -0
- data/lib/text_editor/command/insert_tab.rb +1 -1
- data/lib/text_editor/command/prepend_line_and_insert.rb +11 -0
- data/lib/text_editor/command/replace_line.rb +13 -0
- data/lib/text_editor/command/resolver.rb +3 -2
- data/lib/text_editor/component.rb +1 -1
- data/lib/text_editor/configuration.rb +11 -3
- data/lib/text_editor/configuration/bootstrap.rb +9 -3
- data/lib/text_editor/configuration/flavor.rb +20 -0
- data/lib/text_editor/configuration/log.rb +7 -0
- data/lib/text_editor/configuration/mode.rb +28 -0
- data/lib/text_editor/configuration/operating_system.rb +21 -0
- data/lib/text_editor/configuration/resolver.rb +1 -1
- data/lib/text_editor/configuration/tab.rb +11 -0
- data/lib/text_editor/cursor.rb +2 -0
- data/lib/text_editor/keyboard.rb +1 -1
- data/lib/text_editor/logger.rb +11 -0
- data/lib/text_editor/reactor.rb +5 -3
- data/lib/text_editor/terminal.rb +2 -19
- data/lib/text_editor/window.rb +34 -5
- data/spec/spec_helper.rb +3 -0
- data/spec/support/command_helper.rb +43 -0
- data/spec/text_editor/clipboard_spec.rb +0 -7
- data/spec/text_editor/command/append_line_and_insert_spec.rb +38 -0
- data/spec/text_editor/command/backspace_spec.rb +54 -0
- data/spec/text_editor/command/change_mode_spec.rb +17 -0
- data/spec/text_editor/command/cursor_down_spec.rb +57 -0
- data/spec/text_editor/command/cursor_end_and_insert_spec.rb +35 -0
- data/spec/text_editor/command/cursor_end_spec.rb +30 -0
- data/spec/text_editor/command/cursor_left_spec.rb +46 -0
- data/spec/text_editor/command/cursor_left_with_wrap_spec.rb +46 -0
- data/spec/text_editor/command/cursor_right_and_insert_spec.rb +58 -0
- data/spec/text_editor/command/cursor_right_spec.rb +53 -0
- data/spec/text_editor/command/cursor_right_with_wrap_spec.rb +54 -0
- data/spec/text_editor/command/cursor_start_spec.rb +33 -0
- data/spec/text_editor/command/cursor_start_without_whitespace_spec.rb +46 -0
- data/spec/text_editor/command/cursor_up_spec.rb +56 -0
- data/spec/text_editor/command/forward_word_spec.rb +8 -0
- data/spec/text_editor/command/insert_char_spec.rb +24 -0
- data/spec/text_editor/command/insert_line_spec.rb +26 -0
- data/spec/text_editor/command/insert_tab_spec.rb +27 -0
- data/spec/text_editor/command/prepend_line_and_insert_spec.rb +38 -0
- data/spec/text_editor/command/replace_line_spec.rb +34 -0
- data/spec/text_editor/command/resolver_spec.rb +2 -2
- data/spec/text_editor/command/write_file_spec.rb +25 -0
- data/spec/text_editor/command_spec.rb +8 -8
- data/spec/text_editor/component_spec.rb +7 -0
- data/spec/text_editor/configuration/log_spec.rb +26 -0
- data/spec/text_editor/{operating_system_spec.rb → configuration/operating_system_spec.rb} +1 -1
- data/spec/text_editor/configuration/tab_spec.rb +41 -0
- data/spec/text_editor/configuration_spec.rb +11 -4
- data/spec/text_editor/logger_spec.rb +17 -0
- data/text_editor.gemspec +2 -1
- metadata +84 -7
- data/lib/text_editor/mode.rb +0 -23
- data/lib/text_editor/mode/nano.rb +0 -30
- data/lib/text_editor/operating_system.rb +0 -19
data/lib/text_editor/reactor.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
class TextEditor
|
2
|
+
# The main event loop for the text editor.
|
2
3
|
class Reactor
|
3
4
|
include Component
|
4
5
|
|
@@ -23,10 +24,11 @@ class TextEditor
|
|
23
24
|
key = editor.keyboard.listen
|
24
25
|
next unless key
|
25
26
|
|
26
|
-
|
27
|
-
|
27
|
+
keybinding, args = editor.mode.resolve(key)
|
28
|
+
resolved = command.resolve(keybinding)
|
29
|
+
next unless resolved
|
28
30
|
|
29
|
-
|
31
|
+
resolved.call(editor, *args, key.to_s)
|
30
32
|
render
|
31
33
|
end
|
32
34
|
end
|
data/lib/text_editor/terminal.rb
CHANGED
@@ -9,10 +9,6 @@ class TextEditor
|
|
9
9
|
cols
|
10
10
|
end
|
11
11
|
|
12
|
-
def delete_line
|
13
|
-
deleteln
|
14
|
-
end
|
15
|
-
|
16
12
|
def keys
|
17
13
|
self.class.constants.sort.grep(/^KEY/).reduce({}) do |hash, constant|
|
18
14
|
value = self.class.const_get(constant)
|
@@ -24,10 +20,6 @@ class TextEditor
|
|
24
20
|
getch
|
25
21
|
end
|
26
22
|
|
27
|
-
def move(*coords)
|
28
|
-
setpos(*coords)
|
29
|
-
end
|
30
|
-
|
31
23
|
def render
|
32
24
|
noecho # hide typed characters
|
33
25
|
nonl # disable newline translation
|
@@ -41,17 +33,8 @@ class TextEditor
|
|
41
33
|
close_screen
|
42
34
|
end
|
43
35
|
|
44
|
-
def
|
45
|
-
|
46
|
-
addstr(padded)
|
47
|
-
end
|
48
|
-
|
49
|
-
def x
|
50
|
-
stdscr.curx
|
51
|
-
end
|
52
|
-
|
53
|
-
def y
|
54
|
-
stdscr.cury
|
36
|
+
def window(lines, columns, y, x)
|
37
|
+
stdscr.subwin(lines, columns, y, x)
|
55
38
|
end
|
56
39
|
end
|
57
40
|
end
|
data/lib/text_editor/window.rb
CHANGED
@@ -17,8 +17,8 @@ class TextEditor
|
|
17
17
|
buffer.each_line.with_index do |line, index|
|
18
18
|
if cache[index] != line
|
19
19
|
cache[index] = line
|
20
|
-
|
21
|
-
|
20
|
+
move(index, 0)
|
21
|
+
write(line)
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -26,11 +26,20 @@ class TextEditor
|
|
26
26
|
|
27
27
|
deleted_lines.each do |index|
|
28
28
|
cache.delete(index)
|
29
|
-
|
30
|
-
|
29
|
+
move(index, 0)
|
30
|
+
delete_line
|
31
31
|
end
|
32
32
|
|
33
|
-
|
33
|
+
line = cursor.line
|
34
|
+
content = buffer.line(line)
|
35
|
+
search = content[0..cursor.column]
|
36
|
+
|
37
|
+
tabs = search.scan("\t").size
|
38
|
+
width = config.tab.width - 1
|
39
|
+
column = cursor.column + (tabs * width)
|
40
|
+
|
41
|
+
move(line, column)
|
42
|
+
window.refresh
|
34
43
|
end
|
35
44
|
|
36
45
|
def state
|
@@ -40,5 +49,25 @@ class TextEditor
|
|
40
49
|
def update(buffer, cursor)
|
41
50
|
@state = State.new(buffer, cursor)
|
42
51
|
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def delete_line
|
56
|
+
window.deleteln
|
57
|
+
end
|
58
|
+
|
59
|
+
def move(*coords)
|
60
|
+
window.setpos(*coords)
|
61
|
+
end
|
62
|
+
|
63
|
+
def window
|
64
|
+
@window ||= terminal.window(terminal.lines, terminal.columns, 0, 0)
|
65
|
+
end
|
66
|
+
|
67
|
+
def write(string)
|
68
|
+
tabbed = string.gsub("\t", config.tab.to_s)
|
69
|
+
padded = tabbed.ljust(terminal.columns)
|
70
|
+
window.addstr(padded)
|
71
|
+
end
|
43
72
|
end
|
44
73
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -8,6 +8,9 @@ end
|
|
8
8
|
|
9
9
|
require File.expand_path("../../lib/text_editor", __FILE__)
|
10
10
|
|
11
|
+
support = Pathname.new(__FILE__).dirname.join("support")
|
12
|
+
support.children.each { |file| require file }
|
13
|
+
|
11
14
|
RSpec.configure do |config|
|
12
15
|
config.filter_run :focus
|
13
16
|
config.raise_errors_for_deprecations!
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module CommandHelper
|
2
|
+
extend Forwardable
|
3
|
+
|
4
|
+
def_delegators :subject, :buffer, :column, :cursor, :line, :window
|
5
|
+
|
6
|
+
def self.included(spec)
|
7
|
+
spec.class_eval do
|
8
|
+
subject { described_class.new(editor) }
|
9
|
+
let(:editor) { TextEditor.new(args).tap(&:bootstrap) }
|
10
|
+
let(:args) { [] }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def expect_buffer(content)
|
15
|
+
expect(buffer.to_s).to eq(content)
|
16
|
+
end
|
17
|
+
|
18
|
+
def expect_cursor(line, column)
|
19
|
+
expect(cursor.position).to eq([line, column])
|
20
|
+
end
|
21
|
+
|
22
|
+
def expect_mode(mode)
|
23
|
+
expect(editor.mode.name).to eq(mode)
|
24
|
+
end
|
25
|
+
|
26
|
+
def update_buffer(content)
|
27
|
+
new_buffer = buffer
|
28
|
+
|
29
|
+
content.each_line.map(&:chomp).each.with_index do |line, index|
|
30
|
+
method = index.zero? ? :replace_line : :insert_line
|
31
|
+
new_buffer = new_buffer.send(method, index, line)
|
32
|
+
end
|
33
|
+
|
34
|
+
window.update(new_buffer, cursor)
|
35
|
+
expect_buffer(content)
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_cursor(line, column)
|
39
|
+
new_cursor = cursor.move(line, column)
|
40
|
+
window.update(buffer, new_cursor)
|
41
|
+
expect_cursor(line, column)
|
42
|
+
end
|
43
|
+
end
|
@@ -5,13 +5,6 @@ RSpec.describe TextEditor::Clipboard do
|
|
5
5
|
|
6
6
|
let(:editor) { double("editor") }
|
7
7
|
|
8
|
-
describe "#clear" do
|
9
|
-
it "delegates to adapter.clear" do
|
10
|
-
expect(subject).to receive_message_chain(:adapter, :clear)
|
11
|
-
subject.clear
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
15
8
|
describe "#copy" do
|
16
9
|
it "delegates to adapter.copy" do
|
17
10
|
expect(subject).to receive_message_chain(:adapter, :copy)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::AppendLineAndInsert do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
let(:args) { %w(--flavor vim) }
|
7
|
+
|
8
|
+
before { expect_mode(:normal) }
|
9
|
+
after { expect_mode(:insert) }
|
10
|
+
|
11
|
+
describe "#run" do
|
12
|
+
context "with an empty buffer" do
|
13
|
+
before do
|
14
|
+
expect_buffer("")
|
15
|
+
expect_cursor(0, 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "adds newline and moves cursor" do
|
19
|
+
subject.run
|
20
|
+
expect_buffer("\n")
|
21
|
+
expect_cursor(1, 0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with a non-empty buffer" do
|
26
|
+
before do
|
27
|
+
update_buffer("123")
|
28
|
+
update_cursor(0, 2)
|
29
|
+
end
|
30
|
+
|
31
|
+
it "adds newline and moves cursor" do
|
32
|
+
subject.run
|
33
|
+
expect_buffer("123\n")
|
34
|
+
expect_cursor(1, 0)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::Backspace do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
context "with an empty buffer" do
|
8
|
+
before do
|
9
|
+
expect_buffer("")
|
10
|
+
expect_cursor(0, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "does not change buffer or cursor" do
|
14
|
+
subject.run
|
15
|
+
expect_buffer("")
|
16
|
+
expect_cursor(0, 0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with a one line buffer" do
|
21
|
+
before { update_buffer("ab") }
|
22
|
+
|
23
|
+
it "deletes a character and moves cursor" do
|
24
|
+
update_cursor(0, 2)
|
25
|
+
|
26
|
+
subject.run
|
27
|
+
expect_cursor(0, 1)
|
28
|
+
expect_buffer("a")
|
29
|
+
|
30
|
+
subject.run
|
31
|
+
expect_cursor(0, 0)
|
32
|
+
expect_buffer("")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "does not change buffer if cursor is at start of line" do
|
36
|
+
expect_cursor(0, 0)
|
37
|
+
subject.run
|
38
|
+
expect_cursor(0, 0)
|
39
|
+
expect_buffer("ab")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context "with a multi line buffer" do
|
44
|
+
before { update_buffer("ab\ncd") }
|
45
|
+
|
46
|
+
it "backspaces to previous line" do
|
47
|
+
update_cursor(1, 0)
|
48
|
+
subject.run
|
49
|
+
expect_cursor(0, 2)
|
50
|
+
expect_buffer("abcd")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::ChangeMode do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
before { editor.flavor.modes[:test] = :example }
|
8
|
+
|
9
|
+
it "only switches to a valid mode" do
|
10
|
+
expect(editor.mode).not_to eq(:example)
|
11
|
+
subject.run(:test)
|
12
|
+
expect(editor.mode).to eq(:example)
|
13
|
+
|
14
|
+
expect { subject.run(:invalid) }.to raise_error(KeyError)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::CursorDown do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
context "with an empty buffer" do
|
8
|
+
before do
|
9
|
+
expect_buffer("")
|
10
|
+
expect_cursor(0, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "does not move cursor" do
|
14
|
+
subject.run
|
15
|
+
expect_buffer("")
|
16
|
+
expect_cursor(0, 0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with a one line buffer" do
|
21
|
+
before { update_buffer("ab") }
|
22
|
+
|
23
|
+
it "does not move cursor" do
|
24
|
+
expect_cursor(0, 0)
|
25
|
+
subject.run
|
26
|
+
expect_cursor(0, 0)
|
27
|
+
expect_buffer("ab")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "with a buffer containing multiple lines" do
|
32
|
+
before do
|
33
|
+
update_buffer("ab\ncd")
|
34
|
+
update_cursor(0, 1)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "moves down one line" do
|
38
|
+
subject.run
|
39
|
+
expect_buffer("ab\ncd")
|
40
|
+
expect_cursor(1, 1)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context "with a buffer containing different length lines" do
|
45
|
+
before do
|
46
|
+
update_buffer("abc\nd")
|
47
|
+
update_cursor(0, 2)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "moves down one line and clamps to the end" do
|
51
|
+
subject.run
|
52
|
+
expect_buffer("abc\nd")
|
53
|
+
expect_cursor(1, 1)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::CursorEndAndInsert do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
let(:args) { %w(--flavor vim) }
|
7
|
+
|
8
|
+
before { expect_mode(:normal) }
|
9
|
+
after { expect_mode(:insert) }
|
10
|
+
|
11
|
+
describe "#run" do
|
12
|
+
context "with an empty buffer" do
|
13
|
+
before do
|
14
|
+
expect_buffer("")
|
15
|
+
expect_cursor(0, 0)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "does not move cursor" do
|
19
|
+
subject.run
|
20
|
+
expect_buffer("")
|
21
|
+
expect_cursor(0, 0)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context "with an non-empty buffer" do
|
26
|
+
before { update_buffer("ab cd") }
|
27
|
+
|
28
|
+
it "moves cursor one column past the end of the line" do
|
29
|
+
subject.run
|
30
|
+
expect_buffer("ab cd")
|
31
|
+
expect_cursor(0, 5)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::CursorEnd do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
context "with an empty buffer" do
|
8
|
+
before do
|
9
|
+
expect_buffer("")
|
10
|
+
expect_cursor(0, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "does not move cursor" do
|
14
|
+
subject.run
|
15
|
+
expect_buffer("")
|
16
|
+
expect_cursor(0, 0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with an non-empty buffer" do
|
21
|
+
before { update_buffer("ab cd") }
|
22
|
+
|
23
|
+
it "moves cursor one column past the end of the line" do
|
24
|
+
subject.run
|
25
|
+
expect_buffer("ab cd")
|
26
|
+
expect_cursor(0, 5)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe TextEditor::Command::CursorLeft do
|
4
|
+
include CommandHelper
|
5
|
+
|
6
|
+
describe "#run" do
|
7
|
+
context "with an empty buffer" do
|
8
|
+
before do
|
9
|
+
expect_buffer("")
|
10
|
+
expect_cursor(0, 0)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "does not move cursor" do
|
14
|
+
subject.run
|
15
|
+
expect_buffer("")
|
16
|
+
expect_cursor(0, 0)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "with an non-empty buffer" do
|
21
|
+
before do
|
22
|
+
update_buffer("ab")
|
23
|
+
update_cursor(0, 1)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "moves cursor to the left" do
|
27
|
+
subject.run
|
28
|
+
expect_buffer("ab")
|
29
|
+
expect_cursor(0, 0)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context "with a buffer containing multiple lines" do
|
34
|
+
before do
|
35
|
+
update_buffer("ab\ncd")
|
36
|
+
update_cursor(1, 0)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "does not wrap to previous line" do
|
40
|
+
subject.run
|
41
|
+
expect_buffer("ab\ncd")
|
42
|
+
expect_cursor(1, 0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|