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.
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