text_editor 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|