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,10 +1,6 @@
1
1
  class TextEditor
2
2
  class Clipboard
3
3
  class Windows
4
- def clear
5
- raise NotImplementedError
6
- end
7
-
8
4
  def copy(_content)
9
5
  raise NotImplementedError
10
6
  end
@@ -11,10 +11,6 @@ class TextEditor
11
11
  new(editor).run(*args, &block)
12
12
  end
13
13
 
14
- def command
15
- @command ||= Resolver.new(editor)
16
- end
17
-
18
14
  def content
19
15
  buffer.line(line)
20
16
  end
@@ -22,5 +18,9 @@ class TextEditor
22
18
  def run(*)
23
19
  raise NotImplementedError
24
20
  end
21
+
22
+ def scan
23
+ @scan ||= StringScanner.new(content)
24
+ end
25
25
  end
26
26
  end
@@ -0,0 +1,11 @@
1
+ class TextEditor
2
+ class Command
3
+ class AppendLineAndInsert < Command
4
+ def run(*)
5
+ new_buffer = buffer.insert_line(line.next)
6
+ window.update(new_buffer, cursor)
7
+ command.cursor_down.change_mode(:insert)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,9 @@
1
+ class TextEditor
2
+ class Command
3
+ class ChangeMode < Command
4
+ def run(mode, *)
5
+ editor.mode = editor.flavor.modes.fetch(mode)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class TextEditor
2
+ class Command
3
+ class CursorEndAndInsert < Command
4
+ def run(*)
5
+ command.cursor_end.change_mode(:insert)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -2,14 +2,7 @@ class TextEditor
2
2
  class Command
3
3
  class CursorLeft < Command
4
4
  def run(*)
5
- if column.zero? && line.nonzero?
6
- column = buffer.line(line.pred).size
7
- new_cursor = cursor.move(line.pred, column)
8
- else
9
- new_cursor = cursor.left
10
- end
11
-
12
- window.update(buffer, new_cursor)
5
+ window.update(buffer, cursor.left)
13
6
  end
14
7
  end
15
8
  end
@@ -0,0 +1,16 @@
1
+ class TextEditor
2
+ class Command
3
+ class CursorLeftWithWrap < Command
4
+ def run(*)
5
+ if column.zero? && line.nonzero?
6
+ column = buffer.line(line.pred).size
7
+ new_cursor = cursor.move(line.pred, column)
8
+ else
9
+ new_cursor = cursor.left
10
+ end
11
+
12
+ window.update(buffer, new_cursor)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,16 +2,9 @@ class TextEditor
2
2
  class Command
3
3
  class CursorRight < Command
4
4
  def run(*)
5
- columns = content.size
6
5
  lines = buffer.size.pred
7
-
8
- new_cursor =
9
- if column == columns && line != lines
10
- cursor.move(line.next, 0)
11
- else
12
- cursor.right.clamp(lines, columns)
13
- end
14
-
6
+ columns = buffer.line(line).size
7
+ new_cursor = cursor.right.clamp(lines, columns)
15
8
  window.update(buffer, new_cursor)
16
9
  end
17
10
  end
@@ -0,0 +1,9 @@
1
+ class TextEditor
2
+ class Command
3
+ class CursorRightAndInsert < Command
4
+ def run(*)
5
+ command.cursor_right.change_mode(:insert)
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,19 @@
1
+ class TextEditor
2
+ class Command
3
+ class CursorRightWithWrap < Command
4
+ def run(*)
5
+ columns = content.size
6
+ lines = buffer.size.pred
7
+
8
+ new_cursor =
9
+ if column == columns && line != lines
10
+ cursor.move(line.next, 0)
11
+ else
12
+ cursor.right.clamp(lines, columns)
13
+ end
14
+
15
+ window.update(buffer, new_cursor)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ class TextEditor
2
+ class Command
3
+ class CursorStartWithoutWhitespace < Command
4
+ def run(*)
5
+ index = scan.skip(/\s+/) || 0
6
+ new_cursor = cursor.move(line, index)
7
+ window.update(buffer, new_cursor)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -2,7 +2,7 @@ class TextEditor
2
2
  class Command
3
3
  class CursorUp < Command
4
4
  def run(*)
5
- return unless line.zero?
5
+ return if line.zero?
6
6
 
7
7
  columns = buffer.line(line.pred).size
8
8
  new_cursor = cursor.up.clamp(line.pred, columns)
@@ -0,0 +1,16 @@
1
+ class TextEditor
2
+ class Command
3
+ class ForwardWord < Command
4
+ def run(*)
5
+ lines = buffer.size.pred
6
+ index = content.index(/\W/, column.next)
7
+ index ||= column
8
+
9
+ new_cursor = cursor.move(line, index.next)
10
+ new_cursor = new_cursor.clamp(lines, content.size)
11
+
12
+ window.update(buffer, new_cursor)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -2,7 +2,7 @@ class TextEditor
2
2
  class Command
3
3
  class InsertTab < Command
4
4
  def run(*)
5
- command.insert_char(" ")
5
+ command.insert_char(config.tab.to_s)
6
6
  end
7
7
  end
8
8
  end
@@ -0,0 +1,11 @@
1
+ class TextEditor
2
+ class Command
3
+ class PrependLineAndInsert < Command
4
+ def run(*)
5
+ new_buffer = buffer.insert_line(line)
6
+ window.update(new_buffer, cursor)
7
+ command.cursor_start.change_mode(:insert)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ class TextEditor
2
+ class Command
3
+ class ReplaceLine < Command
4
+ def run(*)
5
+ new_buffer = buffer.replace_line(line)
6
+ new_cursor = cursor.move(line, 0)
7
+ window.update(new_buffer, new_cursor)
8
+
9
+ command.change_mode(:insert)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -6,11 +6,12 @@ class TextEditor
6
6
  def method_missing(method, *args, &block)
7
7
  command = resolve(method)
8
8
  return super unless command
9
- command.run(*args, &block)
9
+ command.call(editor, *args, &block)
10
10
  self
11
11
  end
12
12
 
13
13
  def resolve(method)
14
+ return method if method.respond_to?(:call)
14
15
  parts = method.to_s.split("_")
15
16
  name = parts.map(&:capitalize).join
16
17
  command(name)
@@ -19,7 +20,7 @@ class TextEditor
19
20
  private
20
21
 
21
22
  def command(name)
22
- namespace.const_get(name).new(editor)
23
+ namespace.const_get(name)
23
24
  rescue NameError
24
25
  nil
25
26
  end
@@ -2,7 +2,7 @@ class TextEditor
2
2
  module Component
3
3
  extend Forwardable
4
4
 
5
- def_delegators :editor, :config, :logger, :terminal
5
+ def_delegators :editor, :command, :config, :logger, :terminal
6
6
 
7
7
  attr_reader :editor
8
8
 
@@ -2,14 +2,22 @@ class TextEditor
2
2
  class Configuration
3
3
  include Component
4
4
 
5
- attr_accessor :files
5
+ attr_accessor :files, :default_flavor
6
+
7
+ def config
8
+ self
9
+ end
10
+
11
+ def flavor(name, &block)
12
+ editor.flavors[name] = Flavor.new(name, &block)
13
+ end
6
14
 
7
15
  def log
8
- @log ||= OpenStruct.new
16
+ @log ||= Log.new
9
17
  end
10
18
 
11
19
  def tab
12
- @tab ||= OpenStruct.new
20
+ @tab ||= Tab.new
13
21
  end
14
22
  end
15
23
  end
@@ -14,15 +14,21 @@ class TextEditor
14
14
  end
15
15
 
16
16
  resolver.files.each do |file|
17
- require file
17
+ load file
18
18
  end
19
19
  end
20
20
 
21
21
  def options
22
22
  @options ||= OptionParser.new do |opts|
23
- opts.on("--log [FILE]") { |log| config.log = log }
23
+ opts.on("--log FILE", "-l") do |log|
24
+ config.log.file = log
25
+ end
26
+
27
+ opts.on("--flavor FLAVOR", "-f") do |flavor|
28
+ config.default_flavor = flavor.to_sym
29
+ end
24
30
 
25
- opts.on("--version") do
31
+ opts.on("--version", "-v") do
26
32
  puts editor.version
27
33
  raise SystemExit
28
34
  end
@@ -0,0 +1,20 @@
1
+ class TextEditor
2
+ class Configuration
3
+ class Flavor
4
+ attr_reader :name
5
+
6
+ def initialize(name, &block)
7
+ @name = name.to_sym
8
+ instance_eval(&block) if block
9
+ end
10
+
11
+ def mode(name, &block)
12
+ modes[name.to_sym] = Mode.new(name, &block)
13
+ end
14
+
15
+ def modes
16
+ @modes ||= {}
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,7 @@
1
+ class TextEditor
2
+ class Configuration
3
+ class Log
4
+ attr_accessor :file, :level, :options
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,28 @@
1
+ class TextEditor
2
+ class Configuration
3
+ class Mode
4
+ attr_reader :name
5
+
6
+ def initialize(name, &block)
7
+ @name = name.to_sym
8
+ instance_eval(&block) if block
9
+ end
10
+
11
+ def bind(key, command = nil, *args, &block)
12
+ keybindings[key] = [command || block, args]
13
+ end
14
+
15
+ def keybindings
16
+ @keybindings ||= {}
17
+ end
18
+
19
+ def resolve(key)
20
+ keybindings.each do |keybinding, command|
21
+ return command if keybinding === key
22
+ end
23
+
24
+ nil
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ class TextEditor
2
+ class Configuration
3
+ class OperatingSystem
4
+ attr_reader :version
5
+
6
+ def initialize(version)
7
+ @version = version
8
+ end
9
+
10
+ def type
11
+ case version
12
+ when /cygwin/ then :Cygwin
13
+ when /bsd|linux/ then :Linux
14
+ when /darwin|mac/ then :Mac
15
+ when /mingw|mswin/ then :Windows
16
+ else :Unknown
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -3,7 +3,7 @@ class TextEditor
3
3
  class Resolver
4
4
  include Component
5
5
 
6
- FILE = ".text_editor.rb"
6
+ FILE = ".text_editor.rb".freeze
7
7
 
8
8
  def files
9
9
  @files ||= configs.select(&:exist?)
@@ -0,0 +1,11 @@
1
+ class TextEditor
2
+ class Configuration
3
+ class Tab
4
+ attr_accessor :soft, :width
5
+
6
+ def to_s
7
+ soft ? " " * width : "\t"
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,6 @@
1
1
  class TextEditor
2
+ # An internal representation of a cursor.
3
+ # Each {Window} has it's own cursor instance.
2
4
  class Cursor
3
5
  attr_reader :line, :column
4
6
 
@@ -16,7 +16,7 @@ class TextEditor
16
16
 
17
17
  def codes
18
18
  @codes ||= {
19
- 0 => [:ctrl, :space],
19
+ 0 => :ctrl_space,
20
20
  9 => :tab,
21
21
  27 => :escape,
22
22
  127 => :backspace,
@@ -0,0 +1,11 @@
1
+ class TextEditor
2
+ class Logger < ::Logger
3
+ def initialize(editor)
4
+ log = editor.config.log
5
+
6
+ super(log.file, *log.options) do |logger|
7
+ logger.level = log.level
8
+ end
9
+ end
10
+ end
11
+ end