glimmer-cs-gladiator 0.3.0 → 0.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1111b445263923138023d8a6944c2e9e97acabee157bf51b3ebe6b198a082f1d
4
- data.tar.gz: 9eb85280093b99e302eccfe2d5ca63728d52cf76c823917a5317fc6b0c2ad771
3
+ metadata.gz: 5b1fe5cc493fe6df44d316fe7e4fc9e19eb6e0f463724e5bbf1254fb35379463
4
+ data.tar.gz: eb399a865ec9e5ab6f4f269cf8c318f8931eda1e014ea557516cc30a979bb249
5
5
  SHA512:
6
- metadata.gz: d3439fc60d40194cdf87431cdd886c43c16e75e0855040acd84017127b3f2b3f1d6a89b2b52bf9f69b39c7fc82defc87b499a14cdb1c19608f1765ebe6b12209
7
- data.tar.gz: 4235750d7d134285f32e1b7961041232bfe8f31f9260836fbd067f1cc6fb81752d79f61bc5171bfe6da39d59fe6dd6ff96d65d87e88630156dc46f8ca5e8d699
6
+ metadata.gz: 2c20d228ba4421dc54f55206a9c75418c0fe6f1369f27984864c3b22620b42880670d9da3416d5db65e0d3a8028521bddd4f07747fd6cf9fb3a61aec23f617e1
7
+ data.tar.gz: 30bec26afe87951052c68b1bf2d6a1e4475d715d9de37664bdb3ecc36584ae363bb941152da4f268db8a119360c165e202ba36b998c6c6feb3df9bbf487ddcac
data/README.md CHANGED
@@ -1,17 +1,18 @@
1
- # <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.3.0 - [Ugliest Text Editor Ever](https://www.reddit.com/r/ruby/comments/hgve8k/gladiator_glimmer_editor_ugliest_text_editor_ever/)
1
+ # <img src='https://raw.githubusercontent.com/AndyObtiva/glimmer-cs-gladiator/master/images/glimmer-cs-gladiator-logo.svg' height=85 /> Gladiator 0.5.1 - [Ugliest Text Editor Ever](https://www.reddit.com/r/ruby/comments/hgve8k/gladiator_glimmer_editor_ugliest_text_editor_ever/)
2
2
  ## [<img src="https://raw.githubusercontent.com/AndyObtiva/glimmer/master/images/glimmer-logo-hi-res.png" height=40 /> Glimmer Custom Shell](https://github.com/AndyObtiva/glimmer-dsl-swt#custom-shell-gem)
3
3
  [![Gem Version](https://badge.fury.io/rb/glimmer-cs-gladiator.svg)](http://badge.fury.io/rb/glimmer-cs-gladiator)
4
4
 
5
5
  ![Gladiator](images/glimmer-gladiator.png)
6
6
 
7
- Gladiator (short for Glimmer Editor) is a [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) sample project under on-going development that demonstrates how to build a text editor in [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Library).
7
+ Gladiator (short for Glimmer Editor) is a [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) sample beta project under on-going development that demonstrates how to build a text editor in Ruby using [Glimmer DSL for SWT](https://github.com/AndyObtiva/glimmer-dsl-swt) (JRuby Desktop Development GUI Library).
8
8
  It is not intended to be a full-fledged editor by any means, yet mostly a fun educational exercise in using [Glimmer](https://github.com/AndyObtiva/glimmer).
9
- Gladiator is also a personal tool for shaping an editor exactly the way I like, with all the keyboard shortcuts I prefer.
9
+ Gladiator is also a personal tool for shaping an editor exactly the way I like, with all the keyboard shortcuts I prefer.
10
10
  I leave building truly professional text editors to software tooling experts who would hopefully use [Glimmer](https://github.com/AndyObtiva/glimmer) one day. Otherwise, I have been happily using Gladiator to develop all my [open-source projects](https://github.com/AndyObtiva) since May of 2020.
11
11
 
12
12
  ## Features
13
13
 
14
14
  Gladiator currently supports the following text editing features (including keyboard shortcuts with Mac CMD=CTRL on Windows/Linux):
15
+ - Ruby Syntax Highlighting with colors
15
16
  - File explorer navigation with context menu to open file, rename, delete, add new file, add new directory, or refresh tree (CMD+T)
16
17
  - File lookup by name ignoring slashes, underscores, and dots to ease lookup (CMD+R)
17
18
  - Watch open file for external changes to automatically refresh in editor
@@ -21,7 +22,7 @@ Gladiator currently supports the following text editing features (including keyb
21
22
  - Jump to Line (CMD+L)
22
23
  - Multiple tab support (CMD+SHIFT+[ & CMD+SHIFT+] for tab navigation. CMD+1-9 to jump to a specific tab)
23
24
  - Remember opened tabs, caret position, top line, window size, and window location
24
- - Autosave on focus out/quit/open new file
25
+ - Autosave on focus out/quit/open new file
25
26
  - Duplicate Line(s)/selection (CMD+D)
26
27
  - Kill Line(s)/selection (CMD+K)
27
28
  - Move line/selection up (CMD+UP)
@@ -30,12 +31,15 @@ Gladiator currently supports the following text editing features (including keyb
30
31
  - Indent/Unindent line/selection (CMD+] & CMD+[)
31
32
  - Insert/Prefix New Line (CMD+ENTER & CMD+SHIFT+ENTER)
32
33
  - Drag and Drop Text Editor Split Screen (drag a file from File Tree or File Lookup List, and it splits the screen)
34
+ - Run Ruby code (CMD+SHIFT+R)
35
+ - Scratchpad for running arbitrary Ruby/Glimmer code without saving to disk (CMD+SHIFT+S)
36
+ - Change Split Orientation to Horizontal/Vertical via View Menu (CMD+SHIFT+O)
33
37
 
34
38
  ## Platforms
35
39
 
36
40
  - Mac: Gladiator works best on the Mac. This is the platform it is most used on and receives the most maintenance for.
37
- - Windows: Gladiator works OK on Windows, but has some annoying bugs.
38
- - Linux: Gladiator works with very bad handicaps on Linux (performing text editing operations causes scroll jitter)
41
+ - Windows: Gladiator works OK on Windows, but has some annoying bugs. Contributers could help fix.
42
+ - Linux: Gladiator works with handicaps on Linux (performing some text editing operations causes scroll jitter). Contributers could help fix.
39
43
 
40
44
  ## Pre-requisites
41
45
 
@@ -95,11 +99,11 @@ bin/gladiator relative-or-absolute-path/to/project
95
99
 
96
100
  ### Glimmer Custom Shell Reuse
97
101
 
98
- To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application, add the
102
+ To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application, add the
99
103
  following to the application's `Gemfile`:
100
104
 
101
105
  ```
102
- gem 'glimmer-cs-gladiator', '~> 0.3.0'
106
+ gem 'glimmer-cs-gladiator', '~> 0.5.1'
103
107
  ```
104
108
 
105
109
  Run:
@@ -112,7 +116,7 @@ And, then instantiate the Gladiator [custom shell](https://github.com/AndyObtiva
112
116
 
113
117
  ## Env Var Options
114
118
 
115
- Gladiator opens with the current directory as the root by default.
119
+ Gladiator opens with the current directory as the root by default.
116
120
  If you would like to open another directory, set `LOCAL_DIR` environment variable.
117
121
 
118
122
  Example:
@@ -131,7 +135,9 @@ It currently remembers:
131
135
  - Last opened file
132
136
  - Caret position
133
137
  - Top line position
138
+ - Window size
134
139
  - Opened tabs
140
+ - Split tabs
135
141
  - Ignore Paths
136
142
 
137
143
  ## Gotcha
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.0
1
+ 0.5.1
@@ -2,4 +2,5 @@ require_relative '../lib/glimmer-cs-gladiator'
2
2
 
3
3
  include Glimmer
4
4
 
5
- gladiator.open
5
+ local_dir = ENV['LOCAL_DIR'] || '.'
6
+ gladiator(project_dir_path: local_dir).open
@@ -1,7 +1,9 @@
1
1
  module Glimmer
2
2
  class Gladiator
3
- class Command
3
+ class Command
4
4
  class << self
5
+ include Glimmer
6
+
5
7
  def command_history
6
8
  @command_history ||= {}
7
9
  end
@@ -11,7 +13,7 @@ module Glimmer
11
13
  command_history[file] ||= [Command.new(file)]
12
14
  end
13
15
 
14
- def do(file, method = nil, command: nil)
16
+ def do(file, method = nil, command: nil)
15
17
  command ||= Command.new(file, method)
16
18
  command_history_for(file)&.last&.next_command = command
17
19
  command.do
@@ -20,11 +22,13 @@ module Glimmer
20
22
 
21
23
  def undo(file)
22
24
  return if command_history_for(file).size <= 1
23
- command_history_for(file).pop.undo
25
+ command = command_history_for(file).pop
26
+ command&.undo
24
27
  end
25
28
 
26
29
  def redo(file)
27
- command_history_for(file).last&.redo
30
+ command = command_history_for(file).last
31
+ command&.redo
28
32
  end
29
33
  end
30
34
 
@@ -4,39 +4,40 @@ module Glimmer
4
4
  class Gladiator
5
5
  class Dir
6
6
  include Glimmer
7
+ include Glimmer::DataBinding::ObservableModel
7
8
 
8
9
  REFRESH_DELAY = 7
9
10
 
10
- class << self
11
- def local_dir
12
- unless @local_dir
13
- @local_dir = new(ENV['LOCAL_DIR'] || '.', true)
14
- # @local_dir.refresh
15
- @filewatcher = Filewatcher.new(@local_dir.path)
16
- @thread = Thread.new(@filewatcher) do |fw|
17
- fw.watch do |filename, event|
18
- if @last_update.nil? || (Time.now.to_f - @last_update) > REFRESH_DELAY
19
- @local_dir.refresh if !filename.include?('new_file') && !@local_dir.selected_child_path_history.include?(filename) && filename != @local_dir.selected_child_path
20
- end
21
- @last_update = Time.now.to_f
22
- end
23
- end
24
- end
25
- @local_dir
26
- end
27
- end
28
-
29
11
  attr_accessor :selected_child, :filter, :children, :filtered_path_options, :filtered_path, :display_path, :ignore_paths
30
- attr_reader :name, :parent, :path, :is_local_dir
12
+ attr_reader :name, :parent, :path
31
13
  attr_writer :all_children
32
14
 
33
- def initialize(path, is_local_dir = false)
34
- @is_local_dir = is_local_dir
15
+ def initialize(path, project_dir = nil)
16
+ @project_dir = project_dir
17
+ if is_local_dir
18
+ @filewatcher = Filewatcher.new(path)
19
+ @thread = Thread.new(@filewatcher) do |fw|
20
+ fw.watch do |filename, event|
21
+ if @last_update.nil? || (Time.now.to_f - @last_update) > REFRESH_DELAY
22
+ refresh if !filename.include?('new_file') && !selected_child_path_history.include?(filename) && filename != selected_child_path
23
+ end
24
+ @last_update = Time.now.to_f
25
+ end
26
+ end
27
+ end
35
28
  self.path = ::File.expand_path(path)
36
29
  @name = ::File.basename(::File.expand_path(path))
37
- @ignore_paths = ['packages', 'tmp']
30
+ @ignore_paths = ['.gladiator', '.git', 'coverage', 'packages', 'tmp', 'vendor']
38
31
  self.filtered_path_options = []
39
32
  end
33
+
34
+ def is_local_dir
35
+ @project_dir.nil?
36
+ end
37
+
38
+ def project_dir
39
+ @project_dir || self
40
+ end
40
41
 
41
42
  def path=(the_path)
42
43
  @path = the_path
@@ -44,32 +45,24 @@ module Glimmer
44
45
  end
45
46
 
46
47
  def generate_display_path
47
- is_local_dir ? path : @display_path = @path.sub(Dir.local_dir.path, '').sub(/^\//, '')
48
+ is_local_dir ? path : @display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
48
49
  end
49
50
 
50
- def name=(the_name)
51
- self.display_path = display_path.sub(/#{Regexp.escape(@name)}$/, the_name)
52
- @name = the_name
53
- new_path = ::File.expand_path(display_path)
54
- FileUtils.mv(path, new_path)
55
- self.path = display_path
56
- end
57
-
58
51
  def children
59
52
  @children ||= retrieve_children
60
53
  end
61
54
 
62
55
  def retrieve_children
63
56
  @children = ::Dir.glob(::File.join(@path, '*')).reject do |p|
64
- # TODO make sure to configure ignore_paths in a preferences dialog
65
- Dir.local_dir.ignore_paths.reduce(false) do |result, ignore_path|
57
+ # TODO make sure to configure ignore_paths in a preferences dialog
58
+ project_dir.ignore_paths.reduce(false) do |result, ignore_path|
66
59
  result || p.include?(ignore_path)
67
60
  end
68
- end.map do |p|
69
- ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)
70
- end.sort_by do |c|
71
- c.path.to_s.downcase
72
- end.sort_by do |c|
61
+ end.map do |p|
62
+ ::File.file?(p) ? Gladiator::File.new(p, project_dir) : Gladiator::Dir.new(p, project_dir)
63
+ end.sort_by do |c|
64
+ c.path.to_s.downcase
65
+ end.sort_by do |c|
73
66
  c.class.name
74
67
  end.each do |child|
75
68
  child.retrieve_children if child.is_a?(Dir)
@@ -105,7 +98,7 @@ module Glimmer
105
98
 
106
99
  def filter=(value)
107
100
  if value.to_s.empty?
108
- @filter = nil
101
+ @filter = nil
109
102
  else
110
103
  @filter = value
111
104
  end
@@ -117,8 +110,8 @@ module Glimmer
117
110
  def filtered
118
111
  return if filter.nil?
119
112
  children_files = !@last_filter.to_s.empty? && filter.downcase.start_with?(@last_filter.downcase) ? @last_filtered : all_children_files
120
- children_files.select do |child|
121
- child_path = child.path.to_s.sub(Dir.local_dir.path, '')
113
+ children_files.select do |child|
114
+ child_path = child.path.to_s.sub(project_dir.path, '')
122
115
  child_path.downcase.include?(filter.downcase) ||
123
116
  child_path.downcase.gsub(/[_\/.-]/, '').include?(filter.downcase.gsub(/[_\/.-]/, ''))
124
117
  end.sort_by {|c| c.path.to_s.downcase}
@@ -141,14 +134,19 @@ module Glimmer
141
134
  end
142
135
 
143
136
  def selected_child_path=(selected_path)
144
- full_selected_path = selected_path.include?(Dir.local_dir.path) ? selected_path : ::File.join(Dir.local_dir.path, selected_path)
145
- return if selected_path.nil? ||
146
- ::Dir.exist?(full_selected_path) ||
137
+ return (project_dir.selected_child = nil) if selected_path.nil?
138
+ # scratchpad scenario
139
+ if selected_path.empty? #scratchpad
140
+ @selected_child&.write_dirty_content
141
+ return (self.selected_child = File.new)
142
+ end
143
+ full_selected_path = selected_path.include?(project_dir.path) ? selected_path : ::File.join(project_dir.path, selected_path)
144
+ return if ::Dir.exist?(full_selected_path) ||
147
145
  (selected_child && selected_child.path == full_selected_path)
148
146
  selected_path = full_selected_path
149
147
  if ::File.file?(selected_path)
150
148
  @selected_child&.write_dirty_content
151
- new_child = Gladiator::File.new(selected_path)
149
+ new_child = Gladiator::File.new(selected_path, project_dir)
152
150
  begin
153
151
  unless new_child.dirty_content.nil?
154
152
  self.selected_child&.stop_filewatcher
@@ -168,6 +166,12 @@ module Glimmer
168
166
  @selected_child&.path
169
167
  end
170
168
 
169
+ def selected_child=(new_child)
170
+ file_properties = @selected_child&.backup_properties if @selected_child == new_child
171
+ @selected_child = new_child
172
+ @selected_child.restore_properties(file_properties) if file_properties
173
+ end
174
+
171
175
  def delete!
172
176
  FileUtils.rm_rf(path)
173
177
  end
@@ -183,11 +187,7 @@ module Glimmer
183
187
  def hash
184
188
  self.path.hash
185
189
  end
186
- end
190
+ end
187
191
  end
188
192
  end
189
-
190
- at_exit do
191
- Glimmer::Gladiator::Dir.local_dir.selected_child&.write_raw_dirty_content
192
- end
193
193
 
@@ -1,22 +1,21 @@
1
- require 'models/glimmer/gladiator/dir'
2
-
3
1
  module Glimmer
4
2
  class Gladiator
5
3
  class File
6
4
  include Glimmer
7
-
8
- attr_accessor :dirty_content, :line_numbers_content, :selection, :selection_count, :line_number, :find_text, :replace_text, :top_index, :display_path, :case_sensitive
9
- attr_reader :name, :path
10
-
11
- def initialize(path)
12
- raise "Not a file path: #{path}" unless ::File.file?(path)
5
+
6
+ attr_accessor :dirty_content, :line_numbers_content, :selection, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive
7
+ attr_reader :name, :path, :project_dir
8
+
9
+ def initialize(path='', project_dir=nil)
10
+ raise "Not a file path: #{path}" if path.nil? || (!path.empty? && !::File.file?(path))
11
+ @project_dir = project_dir
13
12
  @command_history = []
14
- @name = ::File.basename(path)
15
- self.path = ::File.expand_path(path)
16
- @top_index = 0
13
+ @name = path.empty? ? 'Scratchpad' : ::File.basename(path)
14
+ self.path = ::File.expand_path(path) unless path.empty?
15
+ @top_pixel = 0
17
16
  @selection_count = 0
18
17
  @selection = Point.new(0, 0 + @selection_count)
19
- read_dirty_content = ::File.read(path)
18
+ read_dirty_content = path.empty? ? '' : ::File.read(path)
20
19
  begin
21
20
  # test read dirty content
22
21
  read_dirty_content.split("\n")
@@ -40,20 +39,41 @@ module Glimmer
40
39
  # no op in case of a binary file
41
40
  end
42
41
  end
43
-
42
+
44
43
  def path=(the_path)
45
44
  @path = the_path
46
45
  generate_display_path
47
46
  end
48
-
47
+
49
48
  def generate_display_path
50
- @display_path = @path.sub(Dir.local_dir.path, '').sub(/^\//, '')
49
+ return if @path.empty?
50
+ @display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
51
51
  end
52
52
 
53
+ def scratchpad?
54
+ path.empty?
55
+ end
56
+
57
+ def backup_properties
58
+ [:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
59
+ hash.merge(property => send(property))
60
+ end
61
+ end
62
+
63
+ def restore_properties(properties_hash)
64
+ return if properties_hash[:caret_position] == 0 && properties_hash[:selection_count] == 0 && properties_hash[:find_text].nil? && properties_hash[:replace_text].nil? && properties_hash[:top_pixel] == 0 && properties_hash[:case_sensitive].nil?
65
+ properties_hash.each do |property, value|
66
+ send("#{property}=", value)
67
+ end
68
+ end
69
+
53
70
  # to use for widget data-binding
54
71
  def content=(value)
55
- Command.do(self) # record a native (OS-widget) operation
56
- self.dirty_content = value
72
+ value = value.gsub("\t", ' ')
73
+ if dirty_content != value
74
+ Command.do(self) # record a native (OS-widget) operation
75
+ self.dirty_content = value
76
+ end
57
77
  end
58
78
 
59
79
  def content
@@ -61,35 +81,32 @@ module Glimmer
61
81
  end
62
82
 
63
83
  def caret_position=(value)
84
+ old_top_pixel = top_pixel
64
85
  self.selection = Point.new(value, value + selection_count.to_i)
65
- if OS.linux?
66
- async_exec do
67
- self.top_index = line_index_for_caret_position(value)
68
- end
69
- end
86
+ self.top_pixel = old_top_pixel
70
87
  end
71
-
88
+
72
89
  def caret_position
73
90
  selection.x
74
91
  end
75
-
76
- def name=(the_name)
77
- new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name)
78
- @name = the_name
79
- if ::File.exists?(path)
80
- FileUtils.mv(path, new_path)
81
- self.path = new_path
82
- end
92
+
93
+ def selection_count
94
+ selection.y - selection.x
83
95
  end
84
-
96
+
97
+ def selection_count=(value)
98
+ self.selection = Point.new(caret_position, caret_position + value.to_i)
99
+ end
100
+
85
101
  def dirty_content=(the_content)
86
- @dirty_content = the_content if ::File.exist?(path)
102
+ @dirty_content = the_content
87
103
  notify_observers(:content)
88
104
  end
89
-
105
+
90
106
  def start_filewatcher
107
+ return if scratchpad?
91
108
  @filewatcher = Filewatcher.new(@path)
92
- @thread = Thread.new(@filewatcher) do |fw|
109
+ @thread = Thread.new(@filewatcher) do |fw|
93
110
  fw.watch do |filename, event|
94
111
  begin
95
112
  read_dirty_content = ::File.read(path)
@@ -104,27 +121,28 @@ module Glimmer
104
121
  end
105
122
  end
106
123
  end
107
-
124
+
108
125
  def stop_filewatcher
109
126
  @filewatcher&.stop
110
127
  end
111
128
 
112
129
  def format_dirty_content_for_writing!
113
- new_dirty_content = "#{dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
130
+ new_dirty_content = dirty_content.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
131
+ new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
114
132
  self.dirty_content = new_dirty_content if new_dirty_content != self.dirty_content
115
133
  end
116
-
134
+
117
135
  def write_dirty_content
118
- return unless ::File.exist?(path)
136
+ return if scratchpad? || !::File.exist?(path)
119
137
  format_dirty_content_for_writing!
120
138
  ::File.write(path, dirty_content) if ::File.exists?(path)
121
139
  rescue => e
122
140
  puts "Error in writing dirty content for #{path}"
123
141
  puts e.full_message
124
142
  end
125
-
143
+
126
144
  def write_raw_dirty_content
127
- return unless ::File.exist?(path)
145
+ return if scratchpad? || !::File.exist?(path)
128
146
  ::File.write(path, dirty_content) if ::File.exists?(path)
129
147
  rescue => e
130
148
  puts "Error in writing raw dirty content for #{path}"
@@ -138,16 +156,17 @@ module Glimmer
138
156
  def current_line
139
157
  lines[line_number - 1]
140
158
  end
141
-
159
+
142
160
  def delete!
143
- FileUtils.rm(path)
161
+ FileUtils.rm(path) unless scratchpad?
144
162
  end
145
-
163
+
146
164
  def prefix_new_line!
147
165
  the_lines = lines
148
166
  the_lines[line_number-1...line_number-1] = [current_line_indentation]
149
167
  self.dirty_content = the_lines.join("\n")
150
168
  self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
169
+ self.selection_count = 0
151
170
  end
152
171
 
153
172
  def insert_new_line!
@@ -155,6 +174,7 @@ module Glimmer
155
174
  the_lines[line_number...line_number] = [current_line_indentation]
156
175
  self.dirty_content = the_lines.join("\n")
157
176
  self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
177
+ self.selection_count = 0
158
178
  end
159
179
 
160
180
  def comment_line!
@@ -182,7 +202,7 @@ module Glimmer
182
202
  delta += 2
183
203
  end
184
204
  end
185
- self.dirty_content = new_lines.join("\n")
205
+ self.dirty_content = new_lines.join("\n")
186
206
  if old_selection_count.to_i > 0
187
207
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
188
208
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
@@ -192,7 +212,7 @@ module Glimmer
192
212
  self.caret_position = new_caret_position
193
213
  end
194
214
  end
195
-
215
+
196
216
  def indent!
197
217
  new_lines = lines
198
218
  old_lines = lines
@@ -208,7 +228,7 @@ module Glimmer
208
228
  new_lines[the_line_index] = " #{the_line}"
209
229
  end
210
230
  old_caret_position = self.caret_position
211
- self.dirty_content = new_lines.join("\n")
231
+ self.dirty_content = new_lines.join("\n")
212
232
  if old_selection_count.to_i > 0
213
233
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
214
234
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
@@ -216,7 +236,7 @@ module Glimmer
216
236
  self.caret_position = old_caret_position + delta
217
237
  end
218
238
  end
219
-
239
+
220
240
  def outdent!
221
241
  new_lines = lines
222
242
  old_lines = lines
@@ -237,7 +257,7 @@ module Glimmer
237
257
  delta = -1
238
258
  end
239
259
  end
240
- self.dirty_content = new_lines.join("\n")
260
+ self.dirty_content = new_lines.join("\n")
241
261
  if old_selection_count.to_i > 0
242
262
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
243
263
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
@@ -247,7 +267,7 @@ module Glimmer
247
267
  self.caret_position = new_caret_position
248
268
  end
249
269
  end
250
-
270
+
251
271
  def kill_line!
252
272
  new_lines = lines
253
273
  return if new_lines.size < 1
@@ -260,7 +280,7 @@ module Glimmer
260
280
  self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
261
281
  self.selection_count = 0
262
282
  end
263
-
283
+
264
284
  def duplicate_line!
265
285
  new_lines = lines
266
286
  old_lines = lines
@@ -284,13 +304,13 @@ module Glimmer
284
304
  self.caret_position = old_caret_position + delta
285
305
  end
286
306
  end
287
-
307
+
288
308
  def find_next
289
- return if find_text.to_s.empty?
309
+ return if find_text.to_s.empty?
290
310
  all_lines = lines
291
311
  the_line_index = line_index_for_caret_position(caret_position)
292
312
  line_position = line_position_for_caret_position(caret_position)
293
- found =
313
+ found = found_text?(caret_position)
294
314
  2.times do |i|
295
315
  rotation = the_line_index
296
316
  all_lines.rotate(rotation).each_with_index do |the_line, the_index|
@@ -307,7 +327,7 @@ module Glimmer
307
327
  end
308
328
  end
309
329
  end
310
-
330
+
311
331
  def find_previous
312
332
  return if find_text.to_s.empty?
313
333
  all_lines = lines
@@ -332,7 +352,7 @@ module Glimmer
332
352
  end
333
353
  end
334
354
  end
335
-
355
+
336
356
  def ensure_find_next
337
357
  return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
338
358
  find_next unless found_text?(self.caret_position)
@@ -341,7 +361,7 @@ module Glimmer
341
361
  def found_text?(caret_position)
342
362
  dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
343
363
  end
344
-
364
+
345
365
  def replace_next!
346
366
  return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
347
367
  ensure_find_next
@@ -349,28 +369,37 @@ module Glimmer
349
369
  new_dirty_content[caret_position, find_text.size] = replace_text.to_s
350
370
  self.dirty_content = new_dirty_content
351
371
  find_next
372
+ find_next if replace_text.to_s.include?(find_text) && !replace_text.to_s.start_with?(find_text)
352
373
  end
353
-
374
+
354
375
  def page_up
355
376
  self.selection_count = 0
356
377
  self.line_number = [(self.line_number - 15), 1].max
357
378
  end
358
-
379
+
359
380
  def page_down
360
381
  self.selection_count = 0
361
382
  self.line_number = [(self.line_number + 15), lines.size].min
362
383
  end
363
-
384
+
364
385
  def home
365
386
  self.selection_count = 0
366
387
  self.line_number = 1
367
388
  end
368
-
389
+
369
390
  def end
370
391
  self.selection_count = 0
371
392
  self.line_number = lines.size
372
393
  end
373
-
394
+
395
+ def start_of_line
396
+ self.caret_position = caret_position_for_line_index(self.line_number - 1)
397
+ end
398
+
399
+ def end_of_line
400
+ self.caret_position = caret_position_for_line_index(self.line_number) - 1
401
+ end
402
+
374
403
  def move_up!
375
404
  old_lines = lines
376
405
  return if old_lines.size < 2
@@ -389,7 +418,7 @@ module Glimmer
389
418
  self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
390
419
  self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
391
420
  end
392
-
421
+
393
422
  def move_down!
394
423
  old_lines = lines
395
424
  return if old_lines.size < 2
@@ -408,25 +437,38 @@ module Glimmer
408
437
  self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
409
438
  self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
410
439
  end
411
-
440
+
441
+ def run
442
+ begin
443
+ if scratchpad?
444
+ eval content
445
+ else
446
+ write_dirty_content
447
+ load path
448
+ end
449
+ rescue SyntaxError, StandardError => e
450
+ puts e.full_message
451
+ end
452
+ end
453
+
412
454
  def lines
413
455
  dirty_content.split("\n")
414
456
  end
415
-
457
+
416
458
  def line_for_caret_position(caret_position)
417
459
  lines[line_index_for_caret_position(caret_position.to_i)]
418
460
  end
419
-
461
+
420
462
  def line_index_for_caret_position(caret_position)
421
463
  dirty_content[0...caret_position.to_i].count("\n")
422
464
  end
423
-
465
+
424
466
  def caret_position_for_line_index(line_index)
425
467
  cp = lines[0...line_index].join("\n").size
426
468
  cp += 1 if line_index > 0
427
469
  cp
428
470
  end
429
-
471
+
430
472
  def caret_position_for_caret_position_start_of_line(caret_position)
431
473
  caret_position_for_line_index(line_index_for_caret_position(caret_position))
432
474
  end
@@ -437,23 +479,23 @@ module Glimmer
437
479
  caret_position = caret_position.to_i
438
480
  caret_position - caret_position_for_caret_position_start_of_line(caret_position)
439
481
  end
440
-
482
+
441
483
  def line_caret_positions_for_selection(caret_position, selection_count)
442
484
  line_indices = line_indices_for_selection(caret_position, selection_count)
443
485
  line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
444
486
  end
445
-
487
+
446
488
  def end_caret_position_line_index(caret_position, selection_count)
447
489
  end_caret_position = caret_position + selection_count.to_i
448
490
  end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
449
491
  end_line_index = line_index_for_caret_position(end_caret_position)
450
492
  end
451
-
493
+
452
494
  def lines_for_selection(caret_position, selection_count)
453
495
  line_indices = line_indices_for_selection(caret_position, selection_count)
454
496
  lines[line_indices.first..line_indices.last]
455
497
  end
456
-
498
+
457
499
  def line_indices_for_selection(caret_position, selection_count)
458
500
  start_line_index = line_index_for_caret_position(caret_position)
459
501
  if selection_count.to_i > 0
@@ -463,22 +505,22 @@ module Glimmer
463
505
  end
464
506
  (start_line_index..end_line_index).to_a
465
507
  end
466
-
508
+
467
509
  def children
468
510
  []
469
511
  end
470
-
512
+
471
513
  def to_s
472
514
  path
473
515
  end
474
-
516
+
475
517
  def eql?(other)
476
518
  self.path.eql?(other&.path)
477
519
  end
478
-
520
+
479
521
  def hash
480
522
  self.path.hash
481
523
  end
482
- end
524
+ end
483
525
  end
484
526
  end