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.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.text_editor.rb +81 -2
  4. data/Gemfile.lock +1 -0
  5. data/README.org +103 -0
  6. data/bin/console +1 -1
  7. data/lib/core_ext/hash.rb +2 -0
  8. data/lib/core_ext/pathname.rb +2 -0
  9. data/lib/text_editor.rb +43 -13
  10. data/lib/text_editor/buffer.rb +3 -0
  11. data/lib/text_editor/clipboard.rb +1 -1
  12. data/lib/text_editor/clipboard/cygwin.rb +0 -4
  13. data/lib/text_editor/clipboard/linux.rb +0 -4
  14. data/lib/text_editor/clipboard/mac.rb +0 -4
  15. data/lib/text_editor/clipboard/unknown.rb +0 -4
  16. data/lib/text_editor/clipboard/windows.rb +0 -4
  17. data/lib/text_editor/command.rb +4 -4
  18. data/lib/text_editor/command/append_line_and_insert.rb +11 -0
  19. data/lib/text_editor/command/change_mode.rb +9 -0
  20. data/lib/text_editor/command/cursor_end_and_insert.rb +9 -0
  21. data/lib/text_editor/command/cursor_left.rb +1 -8
  22. data/lib/text_editor/command/cursor_left_with_wrap.rb +16 -0
  23. data/lib/text_editor/command/cursor_right.rb +2 -9
  24. data/lib/text_editor/command/cursor_right_and_insert.rb +9 -0
  25. data/lib/text_editor/command/cursor_right_with_wrap.rb +19 -0
  26. data/lib/text_editor/command/cursor_start_without_whitespace.rb +11 -0
  27. data/lib/text_editor/command/cursor_up.rb +1 -1
  28. data/lib/text_editor/command/forward_word.rb +16 -0
  29. data/lib/text_editor/command/insert_tab.rb +1 -1
  30. data/lib/text_editor/command/prepend_line_and_insert.rb +11 -0
  31. data/lib/text_editor/command/replace_line.rb +13 -0
  32. data/lib/text_editor/command/resolver.rb +3 -2
  33. data/lib/text_editor/component.rb +1 -1
  34. data/lib/text_editor/configuration.rb +11 -3
  35. data/lib/text_editor/configuration/bootstrap.rb +9 -3
  36. data/lib/text_editor/configuration/flavor.rb +20 -0
  37. data/lib/text_editor/configuration/log.rb +7 -0
  38. data/lib/text_editor/configuration/mode.rb +28 -0
  39. data/lib/text_editor/configuration/operating_system.rb +21 -0
  40. data/lib/text_editor/configuration/resolver.rb +1 -1
  41. data/lib/text_editor/configuration/tab.rb +11 -0
  42. data/lib/text_editor/cursor.rb +2 -0
  43. data/lib/text_editor/keyboard.rb +1 -1
  44. data/lib/text_editor/logger.rb +11 -0
  45. data/lib/text_editor/reactor.rb +5 -3
  46. data/lib/text_editor/terminal.rb +2 -19
  47. data/lib/text_editor/window.rb +34 -5
  48. data/spec/spec_helper.rb +3 -0
  49. data/spec/support/command_helper.rb +43 -0
  50. data/spec/text_editor/clipboard_spec.rb +0 -7
  51. data/spec/text_editor/command/append_line_and_insert_spec.rb +38 -0
  52. data/spec/text_editor/command/backspace_spec.rb +54 -0
  53. data/spec/text_editor/command/change_mode_spec.rb +17 -0
  54. data/spec/text_editor/command/cursor_down_spec.rb +57 -0
  55. data/spec/text_editor/command/cursor_end_and_insert_spec.rb +35 -0
  56. data/spec/text_editor/command/cursor_end_spec.rb +30 -0
  57. data/spec/text_editor/command/cursor_left_spec.rb +46 -0
  58. data/spec/text_editor/command/cursor_left_with_wrap_spec.rb +46 -0
  59. data/spec/text_editor/command/cursor_right_and_insert_spec.rb +58 -0
  60. data/spec/text_editor/command/cursor_right_spec.rb +53 -0
  61. data/spec/text_editor/command/cursor_right_with_wrap_spec.rb +54 -0
  62. data/spec/text_editor/command/cursor_start_spec.rb +33 -0
  63. data/spec/text_editor/command/cursor_start_without_whitespace_spec.rb +46 -0
  64. data/spec/text_editor/command/cursor_up_spec.rb +56 -0
  65. data/spec/text_editor/command/forward_word_spec.rb +8 -0
  66. data/spec/text_editor/command/insert_char_spec.rb +24 -0
  67. data/spec/text_editor/command/insert_line_spec.rb +26 -0
  68. data/spec/text_editor/command/insert_tab_spec.rb +27 -0
  69. data/spec/text_editor/command/prepend_line_and_insert_spec.rb +38 -0
  70. data/spec/text_editor/command/replace_line_spec.rb +34 -0
  71. data/spec/text_editor/command/resolver_spec.rb +2 -2
  72. data/spec/text_editor/command/write_file_spec.rb +25 -0
  73. data/spec/text_editor/command_spec.rb +8 -8
  74. data/spec/text_editor/component_spec.rb +7 -0
  75. data/spec/text_editor/configuration/log_spec.rb +26 -0
  76. data/spec/text_editor/{operating_system_spec.rb → configuration/operating_system_spec.rb} +1 -1
  77. data/spec/text_editor/configuration/tab_spec.rb +41 -0
  78. data/spec/text_editor/configuration_spec.rb +11 -4
  79. data/spec/text_editor/logger_spec.rb +17 -0
  80. data/text_editor.gemspec +2 -1
  81. metadata +84 -7
  82. data/lib/text_editor/mode.rb +0 -23
  83. data/lib/text_editor/mode/nano.rb +0 -30
  84. data/lib/text_editor/operating_system.rb +0 -19
@@ -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
- command = editor.mode.resolve(key)
27
- next unless command
27
+ keybinding, args = editor.mode.resolve(key)
28
+ resolved = command.resolve(keybinding)
29
+ next unless resolved
28
30
 
29
- command.call(editor, key.to_s)
31
+ resolved.call(editor, *args, key.to_s)
30
32
  render
31
33
  end
32
34
  end
@@ -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 write(string)
45
- padded = string.ljust(columns)
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
@@ -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
- terminal.move(index, 0)
21
- terminal.write(line)
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
- terminal.move(index, 0)
30
- terminal.delete_line
29
+ move(index, 0)
30
+ delete_line
31
31
  end
32
32
 
33
- terminal.move(*cursor.position)
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
@@ -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