glimmer-cs-gladiator 0.1.1 → 0.1.6

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: 10060190546fe60cbf5c1e130191ed12ff17ea7deee820fd9294ad59b04b8dfa
4
- data.tar.gz: f6ad081752171216f5980a1a5d9508d1c433ed9cd35cdcb5c2f5c022254376c8
3
+ metadata.gz: 8d758e5b444ef3d48c0214ffb763e3dab3f9ca9748961e879b9ebdb626e9c2ea
4
+ data.tar.gz: cb575fbe30017319190278483334256aeb04d277798a5e3351f944c3d3a61481
5
5
  SHA512:
6
- metadata.gz: '092bcecbbcc5fa79cbc4e085546f3b37760e429369498fb24508cffacef919c0e89d1d84c3faa75fe9a38a35bfbae0fb280a273aa6d0b479134b1724145e7f6d'
7
- data.tar.gz: 50893f0dc416e754ca5e589cc82164cb6214c3b56090759bb085e4a8c61cf96c4423002a986740b923c0ffc6861eb8706fa3d44fefe7d1ca298cd179c835e242
6
+ metadata.gz: 4223591b0608f621432a7ee1a1f710ad4a1684aaafb6d1d9152f064b8d707216ba3dc5fece5836764db058a9a88d209d161ee8abc5062d4fb9a4a821cb986c61
7
+ data.tar.gz: 3296674924ae085c139919e9e22a32d1c5a322b163b7fc6954e89bc02af4cb2a7c80e9a49359d9d136f4113206c48ebfcedbbe917e3f97e35529575abed1832a
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Gladiator (Glimmer Editor) 0.1.1 - Glimmer Custom Shell
1
+ # Gladiator (Glimmer Editor) 0.1.6 - Glimmer Custom Shell
2
2
  [![Gem Version](https://badge.fury.io/rb/glimmer-cs-gladiator.svg)](http://badge.fury.io/rb/glimmer-cs-gladiator)
3
3
 
4
4
  ![Gladiator](images/glimmer-gladiator.png)
@@ -9,18 +9,29 @@ Gladiator is also a personal tool for shaping an editor exactly the way I like.
9
9
  I leave building truly professional text editors to software tooling experts who would hopefully use Glimmer one day.
10
10
 
11
11
  Gladiator currently supports the following text editing features:
12
- - File explorer navigation to open file
13
- - File lookup by name
14
- - Find & Replace
12
+ - File explorer navigation to open file, rename, delete, add new file, add new directory, or refresh tree (CMD+T)
13
+ - File lookup by name ignoring slashes, underscores, and dots to ease lookup (CMD+R)
14
+ - Watch open file for external changes to automatically refresh in editor
15
+ - Watch project subdirectories for changes to automatically refresh in file explorer/file lookup
16
+ - Find & Replace (CMD+F)
15
17
  - Show Line Numbers
16
- - Jump to Line
17
- - Remember last opened file, caret position, and top line
18
- - Autosave on focus out/quit/open new file
19
- - Watch open file for external changes to reflect in editor
20
- - Duplicate Line(s)
21
- - Kill Line(s)
22
- - Move up one line
23
- - Move down one line
18
+ - Jump to Line (CMD+L)
19
+ - Multiple tab support (CMD+SHIFT+[ & CMD+SHIFT+] for tab navigation. CMD+1-9 to jump to a specific tab)
20
+ - Remember opened tabs, caret position, top line, window size, and window location
21
+ - Autosave on focus out/quit/open new file
22
+ - Duplicate Line(s)/selection (CMD+D)
23
+ - Kill Line(s)/selection (CMD+K)
24
+ - Move line/selection up (CMD+UP)
25
+ - Move line/selection down (CMD+DOWN)
26
+ - Comment/Uncomment line/selection (CMD+/)
27
+ - Indent/Unindent line/selection (CMD+] & CMD+[)
28
+ - Insert/Prefix New Line (CMD+ENTER & CMD+SHIFT+ENTER)
29
+
30
+ ## Platforms
31
+
32
+ - Mac: Gladiator works best on the Mac.
33
+ - Linux: Gladiator works with handicaps on Linux (performing text editing operations causes scroll jitter)
34
+ - Windows: Not tested on Windows yet, but should theoretically work there too.
24
35
 
25
36
  ## Pre-requisites
26
37
 
@@ -49,7 +60,7 @@ To reuse Gladiator as a Glimmer Custom Shell inside another Glimmer application,
49
60
  following to the application's `Gemfile`:
50
61
 
51
62
  ```
52
- gem 'glimmer-cs-gladiator', '0.1.1'
63
+ gem 'glimmer-cs-gladiator', '0.1.6'
53
64
  ```
54
65
 
55
66
  Run:
@@ -3,4 +3,11 @@
3
3
  require 'glimmer/launcher'
4
4
 
5
5
  gladiator_runner = File.expand_path('../gladiator_runner.rb', __FILE__)
6
- Glimmer::Launcher.new([gladiator_runner, '-J-Xrs'] + ARGV).launch
6
+ launcher = Glimmer::Launcher.new([gladiator_runner, '-J-Xrs'] + ARGV)
7
+ launcher.application_paths.to_a.each do |file|
8
+ if file != gladiator_runner
9
+ launcher.application_paths.delete(file)
10
+ ENV['LOCAL_DIR'] ||= file
11
+ end
12
+ end
13
+ launcher.launch
@@ -15,41 +15,66 @@ module Glimmer
15
15
  @thread = Thread.new(@filewatcher) do |fw|
16
16
  fw.watch do |filename, event|
17
17
  if @last_update.nil? || (Time.now.to_f - @last_update) > REFRESH_DELAY
18
- dir.refresh if filename != dir.selected_child_path
18
+ dir.refresh if !filename.include?('new_file') && !dir.selected_child_path_history.include?(filename) && filename != dir.selected_child_path
19
19
  end
20
20
  @last_update = Time.now.to_f
21
21
  end
22
22
  end
23
23
  end
24
- end
24
+ end
25
25
  end
26
26
 
27
- attr_accessor :selected_child, :filter, :children, :filtered_path_options
28
- attr_reader :path, :display_path
27
+ attr_accessor :selected_child, :filter, :children, :filtered_path_options, :filtered_path, :path, :display_path
28
+ attr_reader :name, :parent
29
+ attr_writer :all_children, :children
29
30
 
30
31
  def initialize(path)
31
- @path = @display_path = path
32
+ @display_path = path
33
+ @path = ::File.expand_path(@display_path)
34
+ @name = ::File.basename(::File.expand_path(path))
32
35
  self.filtered_path_options = []
33
36
  end
37
+
38
+ def name=(the_name)
39
+ self.display_path = display_path.sub(/#{Regexp.escape(@name)}$/, the_name)
40
+ @name = the_name
41
+ new_path = ::File.expand_path(display_path)
42
+ FileUtils.mv(path, new_path)
43
+ self.path = display_path
44
+ end
34
45
 
35
46
  def children
36
47
  @children ||= retrieve_children
37
48
  end
38
-
49
+
39
50
  def retrieve_children
40
- ::Dir.glob(::File.join(@path, '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}.sort_by {|c| c.path.to_s.downcase }.sort_by {|c| c.class.name }
51
+ ::Dir.glob(::File.join(@display_path, '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}.sort_by {|c| c.path.to_s.downcase }.sort_by {|c| c.class.name }
41
52
  end
42
53
 
43
- def refresh
54
+ def selected_child_path_history
55
+ @selected_child_path_history ||= []
56
+ end
57
+
58
+ def pause_refresh
59
+ @refresh_paused = true
60
+ end
61
+
62
+ def resume_refresh
63
+ @refresh_paused = false
64
+ end
65
+
66
+ def refresh(async: true, force: false)
67
+ return if @refresh_paused && !force
44
68
  new_all_children = retrieve_all_children
45
- new_children = retrieve_children
46
- async_exec do
47
- @all_children = new_all_children
48
- @children ||= []
49
- @children.clear
50
- new_children.each do |child|
51
- @children << child
52
- end
69
+ new_children = retrieve_children
70
+ refresh_operation = lambda do
71
+ self.all_children = new_all_children
72
+ self.children = new_children
73
+ end
74
+ if async
75
+ async_exec(&refresh_operation)
76
+ else
77
+ sync_exec(&refresh_operation)
53
78
  end
54
79
  end
55
80
 
@@ -66,7 +91,7 @@ module Glimmer
66
91
  return if filter.nil?
67
92
  all_children_files.select do |child|
68
93
  child.path.downcase.include?(filter.downcase) ||
69
- child.path.downcase.gsub(/[_\/]/, '').include?(filter.downcase)
94
+ child.path.downcase.gsub(/[_\/\.]/, '').include?(filter.downcase)
70
95
  end.sort_by {|c| c.path.to_s.downcase}
71
96
  end
72
97
 
@@ -75,7 +100,7 @@ module Glimmer
75
100
  end
76
101
 
77
102
  def retrieve_all_children
78
- ::Dir.glob(::File.join(@path, '**', '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}
103
+ ::Dir.glob(::File.join(@display_path, '**', '*')).map {|p| ::File.file?(p) ? Gladiator::File.new(p) : Gladiator::Dir.new(p)}
79
104
  end
80
105
 
81
106
  def all_children_files
@@ -83,31 +108,46 @@ module Glimmer
83
108
  end
84
109
 
85
110
  def selected_child_path=(selected_path)
86
- if selected_path && ::File.file?(selected_path)
111
+ return if selected_path.nil? ||
112
+ ::Dir.exist?(selected_path) ||
113
+ (selected_child && ::File.expand_path(selected_child.path) == ::File.expand_path(selected_path))
114
+ if ::File.file?(selected_path)
87
115
  @selected_child&.write_dirty_content
88
116
  new_child = Gladiator::File.new(selected_path)
89
117
  begin
90
118
  unless new_child.dirty_content.nil?
91
119
  self.selected_child&.stop_filewatcher
120
+ selected_child_path_history << new_child.path if new_child && !selected_child_path_history.include?(new_child.path)
92
121
  self.selected_child = new_child
93
122
  self.selected_child.start_filewatcher
94
123
  end
95
124
  rescue
96
125
  # no op
97
126
  end
127
+ else
128
+ refresh
98
129
  end
99
130
  end
100
131
 
101
132
  def selected_child_path
102
133
  @selected_child&.path
103
134
  end
104
-
105
- alias filtered_path selected_child_path
106
- alias filtered_path= selected_child_path=
135
+
136
+ def delete!
137
+ FileUtils.rm_rf(path)
138
+ end
107
139
 
108
140
  def to_s
109
141
  path
110
142
  end
143
+
144
+ def eql?(other)
145
+ self.path.eql?(other&.path)
146
+ end
147
+
148
+ def hash
149
+ self.path.hash
150
+ end
111
151
  end
112
152
  end
113
153
  end
@@ -5,13 +5,17 @@ module Glimmer
5
5
  class File
6
6
  include Glimmer
7
7
 
8
- attr_accessor :dirty_content, :line_numbers_content, :caret_position, :selection_count, :line_number, :find_text, :replace_text, :top_index
9
- attr_reader :path, :display_path
8
+ attr_accessor :line_numbers_content, :selection, :selection_count, :line_number, :find_text, :replace_text, :top_index, :path, :display_path
9
+ attr_reader :name, :dirty_content
10
10
 
11
11
  def initialize(path)
12
12
  raise "Not a file path: #{path}" unless ::File.file?(path)
13
13
  @display_path = path
14
+ @name = ::File.basename(path)
14
15
  @path = ::File.expand_path(path)
16
+ @top_index = 0
17
+ @selection_count = 0
18
+ @selection = Point.new(0, 0 + @selection_count)
15
19
  read_dirty_content = ::File.read(path)
16
20
  begin
17
21
  # test read dirty content
@@ -20,20 +24,47 @@ module Glimmer
20
24
  lines_text_size = lines.size.to_s.size
21
25
  self.line_numbers_content = lines.size.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
22
26
  end
27
+ @line_number = 1
23
28
  self.dirty_content = read_dirty_content
24
- observe(self, :caret_position) do
29
+ observe(self, :selection) do
25
30
  self.line_number = line_index_for_caret_position(caret_position) + 1
26
31
  end
27
32
  observe(self, :line_number) do
28
33
  if line_number
29
- new_caret_position = lines[0...(line_number.to_i - 1)].map(&:size).sum + line_number.to_i - 1
30
- self.caret_position = new_caret_position unless line_index_for_caret_position(new_caret_position) == line_index_for_caret_position(caret_position)
34
+ line_index = line_number - 1
35
+ new_caret_position = caret_position_for_line_index(line_index)
36
+ self.caret_position = new_caret_position unless self.caret_position && line_index_for_caret_position(new_caret_position) == line_index_for_caret_position(caret_position)
31
37
  end
32
38
  end
33
39
  rescue
34
40
  # no op in case of a binary file
35
41
  end
36
42
  end
43
+
44
+ def caret_position=(value)
45
+ self.selection = Point.new(value, value + selection_count.to_i)
46
+ if OS.linux?
47
+ async_exec do
48
+ self.top_index = line_index_for_caret_position(value)
49
+ end
50
+ end
51
+ end
52
+
53
+ def caret_position
54
+ selection.x
55
+ end
56
+
57
+ def name=(the_name)
58
+ self.display_path = display_path.sub(/#{Regexp.escape(@name)}$/, the_name)
59
+ @name = the_name
60
+ new_path = ::File.expand_path(display_path)
61
+ FileUtils.mv(path, new_path)
62
+ self.path = new_path
63
+ end
64
+
65
+ def dirty_content=(the_content)
66
+ @dirty_content = the_content if ::File.exist?(path)
67
+ end
37
68
 
38
69
  def start_filewatcher
39
70
  @filewatcher = Filewatcher.new(@path)
@@ -56,23 +87,55 @@ module Glimmer
56
87
  def stop_filewatcher
57
88
  @filewatcher&.stop
58
89
  end
59
-
60
- def write_dirty_content
90
+
91
+ def format_dirty_content_for_writing!
61
92
  new_dirty_content = "#{dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
62
93
  self.dirty_content = new_dirty_content if new_dirty_content != self.dirty_content
63
- ::File.write(path, dirty_content) if ::File.exists?(path) && dirty_content.to_s.strip.size > 0
94
+ end
95
+
96
+ def write_dirty_content
97
+ return unless ::File.exist?(path)
98
+ format_dirty_content_for_writing!
99
+ ::File.write(path, dirty_content) if ::File.exists?(path)
64
100
  rescue => e
65
101
  puts "Error in writing dirty content for #{path}"
66
102
  puts e.full_message
67
103
  end
68
104
 
69
105
  def write_raw_dirty_content
70
- ::File.write(path, dirty_content) if ::File.exists?(path) && dirty_content.to_s.strip.size > 0
106
+ return unless ::File.exist?(path)
107
+ ::File.write(path, dirty_content) if ::File.exists?(path)
71
108
  rescue => e
72
109
  puts "Error in writing raw dirty content for #{path}"
73
110
  puts e.full_message
74
111
  end
75
-
112
+
113
+ def current_line_indentation
114
+ current_line.to_s.match(/^(\s+)/).to_a[1].to_s
115
+ end
116
+
117
+ def current_line
118
+ lines[line_number - 1]
119
+ end
120
+
121
+ def delete!
122
+ FileUtils.rm(path)
123
+ end
124
+
125
+ def prefix_new_line!
126
+ the_lines = lines
127
+ the_lines[line_number-1...line_number-1] = [current_line_indentation]
128
+ self.dirty_content = the_lines.join("\n")
129
+ self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
130
+ end
131
+
132
+ def insert_new_line!
133
+ the_lines = lines
134
+ the_lines[line_number...line_number] = [current_line_indentation]
135
+ self.dirty_content = the_lines.join("\n")
136
+ self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
137
+ end
138
+
76
139
  def comment_line!
77
140
  old_lines = lines
78
141
  return if old_lines.size < 1
@@ -98,7 +161,7 @@ module Glimmer
98
161
  end
99
162
  end
100
163
  self.dirty_content = new_lines.join("\n")
101
- if old_selection_count > 0
164
+ if old_selection_count.to_i > 0
102
165
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
103
166
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
104
167
  else
@@ -124,7 +187,7 @@ module Glimmer
124
187
  end
125
188
  old_caret_position = self.caret_position
126
189
  self.dirty_content = new_lines.join("\n")
127
- if old_selection_count > 0
190
+ if old_selection_count.to_i > 0
128
191
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
129
192
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
130
193
  else
@@ -144,7 +207,7 @@ module Glimmer
144
207
  delta = 0
145
208
  line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
146
209
  the_line = old_lines[the_line_index]
147
- if the_line.start_with?(' ')
210
+ if the_line.to_s.start_with?(' ')
148
211
  new_lines[the_line_index] = the_line.sub(/ /, '')
149
212
  delta = -2
150
213
  elsif the_line.start_with?(' ')
@@ -153,7 +216,7 @@ module Glimmer
153
216
  end
154
217
  end
155
218
  self.dirty_content = new_lines.join("\n")
156
- if old_selection_count > 0
219
+ if old_selection_count.to_i > 0
157
220
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
158
221
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
159
222
  else
@@ -165,12 +228,15 @@ module Glimmer
165
228
 
166
229
  def kill_line!
167
230
  new_lines = lines
168
- return if new_lines.size < 2
231
+ return if new_lines.size < 1
169
232
  line_indices = line_indices_for_selection(caret_position, selection_count)
170
233
  new_lines = new_lines[0...line_indices.first] + new_lines[(line_indices.last+1)...new_lines.size]
171
234
  old_caret_position = self.caret_position
172
- self.dirty_content = new_lines.join("\n")
173
- self.caret_position = old_caret_position
235
+ old_line_index = self.line_number - 1
236
+ line_position = line_position_for_caret_position(old_caret_position)
237
+ self.dirty_content = "#{new_lines.join("\n")}\n"
238
+ self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
239
+ self.selection_count = 0
174
240
  end
175
241
 
176
242
  def duplicate_line!
@@ -189,7 +255,7 @@ module Glimmer
189
255
  new_lines.insert(the_line_indices.first + i, the_line)
190
256
  end
191
257
  self.dirty_content = new_lines.join("\n")
192
- if old_selection_count > 0
258
+ if old_selection_count.to_i > 0
193
259
  self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
194
260
  self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
195
261
  else
@@ -201,12 +267,21 @@ module Glimmer
201
267
  return if find_text.to_s.empty?
202
268
  all_lines = lines
203
269
  the_line_index = line_index_for_caret_position(caret_position)
204
- all_lines.rotate(the_line_index + 1).each_with_index do |the_line, the_index|
205
- the_index = (the_index + the_line_index + 1)%all_lines.size
206
- if the_line.downcase.include?(find_text.to_s.downcase)
207
- self.caret_position = the_line.downcase.index(find_text.to_s.downcase) + caret_position_for_line_index(the_index)
208
- self.selection_count = find_text.to_s.size
209
- return
270
+ line_position = line_position_for_caret_position(caret_position)
271
+ found =
272
+ 2.times do |i|
273
+ rotation = the_line_index
274
+ all_lines.rotate(rotation).each_with_index do |the_line, the_index|
275
+ the_index = (the_index + rotation)%all_lines.size
276
+ start_position = 0
277
+ start_position = line_position + find_text.to_s.size if i == 0 && the_index == the_line_index && found_text?(caret_position)
278
+ text_to_find_in = the_line.downcase[start_position..-1]
279
+ occurrence_index = text_to_find_in&.index(find_text.to_s.downcase)
280
+ if occurrence_index
281
+ self.caret_position = caret_position_for_line_index(the_index) + start_position + occurrence_index
282
+ self.selection_count = find_text.to_s.size
283
+ return
284
+ end
210
285
  end
211
286
  end
212
287
  end
@@ -215,21 +290,34 @@ module Glimmer
215
290
  return if find_text.to_s.empty?
216
291
  all_lines = lines
217
292
  the_line_index = line_index_for_caret_position(caret_position)
218
- all_lines.rotate(the_line_index).each_with_index.map do |the_line, the_index|
219
- the_index = (the_index + the_line_index)%all_lines.size
220
- [the_line, the_index]
221
- end.reverse.each do |the_line, the_index|
222
- if the_line.downcase.include?(find_text.to_s.downcase)
223
- self.caret_position = the_line.downcase.index(find_text.to_s.downcase) + caret_position_for_line_index(the_index)
224
- self.selection_count = find_text.to_s.size
225
- return
293
+ line_position = line_position_for_caret_position(caret_position)
294
+ 2.times do |i|
295
+ rotation = - the_line_index - 1 + all_lines.size
296
+ all_lines.reverse.rotate(rotation).each_with_index do |the_line, the_index|
297
+ the_index = all_lines.size - 1 - (the_index + rotation)%all_lines.size
298
+ if the_index == the_line_index
299
+ start_position = i > 0 ? 0 : (the_line.size - line_position)
300
+ else
301
+ start_position = 0
302
+ end
303
+ text_to_find_in = the_line.downcase.reverse[start_position...the_line.size].to_s
304
+ occurrence_index = text_to_find_in.index(find_text.to_s.downcase.reverse)
305
+ if occurrence_index
306
+ self.caret_position = caret_position_for_line_index(the_index) + (the_line.size - (start_position + occurrence_index + find_text.to_s.size))
307
+ self.selection_count = find_text.to_s.size
308
+ return
309
+ end
226
310
  end
227
311
  end
228
312
  end
229
313
 
230
314
  def ensure_find_next
231
315
  return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
232
- find_next unless dirty_content[caret_position.to_i, find_text.to_s.size] == find_text
316
+ find_next unless found_text?(self.caret_position)
317
+ end
318
+
319
+ def found_text?(caret_position)
320
+ dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
233
321
  end
234
322
 
235
323
  def replace_next!
@@ -242,10 +330,12 @@ module Glimmer
242
330
  end
243
331
 
244
332
  def page_up
333
+ self.selection_count = 0
245
334
  self.line_number = [(self.line_number - 15), 1].max
246
335
  end
247
336
 
248
337
  def page_down
338
+ self.selection_count = 0
249
339
  self.line_number = [(self.line_number + 15), lines.size].min
250
340
  end
251
341
 
@@ -262,23 +352,18 @@ module Glimmer
262
352
  return if old_lines.size < 2
263
353
  old_selection_count = self.selection_count
264
354
  old_caret_position = self.caret_position
265
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
266
- old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
355
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
356
+ old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
267
357
  old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
268
358
  new_lines = lines
269
359
  the_line_indices = line_indices_for_selection(caret_position, selection_count)
270
360
  the_lines = lines_for_selection(caret_position, selection_count)
271
361
  new_line_index = [the_line_indices.first - 1, 0].max
272
- delta = -1 * (new_lines[new_line_index].size + 1)
273
362
  new_lines[the_line_indices.first..the_line_indices.last] = []
274
363
  new_lines[new_line_index...new_line_index] = the_lines
275
364
  self.dirty_content = new_lines.join("\n")
276
- if old_selection_count > 0
277
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index) + delta
278
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position + delta)
279
- else
280
- self.caret_position = old_caret_position + delta
281
- end
365
+ self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
366
+ self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
282
367
  end
283
368
 
284
369
  def move_down!
@@ -286,23 +371,18 @@ module Glimmer
286
371
  return if old_lines.size < 2
287
372
  old_selection_count = self.selection_count
288
373
  old_caret_position = self.caret_position
289
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
290
- old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
374
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
375
+ old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
291
376
  old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
292
377
  new_lines = lines
293
378
  the_line_indices = line_indices_for_selection(caret_position, selection_count)
294
379
  the_lines = lines_for_selection(caret_position, selection_count)
295
380
  new_line_index = [the_line_indices.first + 1, new_lines.size - 1].min
296
- delta = new_lines[new_line_index].size + 1
297
381
  new_lines[the_line_indices.first..the_line_indices.last] = []
298
382
  new_lines[new_line_index...new_line_index] = the_lines
299
383
  self.dirty_content = new_lines.join("\n")
300
- if old_selection_count > 0
301
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index) + delta
302
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position + delta)
303
- else
304
- self.caret_position = old_caret_position + delta
305
- end
384
+ self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
385
+ self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
306
386
  end
307
387
 
308
388
  def lines
@@ -318,12 +398,21 @@ module Glimmer
318
398
  end
319
399
 
320
400
  def caret_position_for_line_index(line_index)
321
- lines[0...line_index].join("\n").size + 1
401
+ cp = lines[0...line_index].join("\n").size
402
+ cp += 1 if line_index > 0
403
+ cp
322
404
  end
323
405
 
324
406
  def caret_position_for_caret_position_start_of_line(caret_position)
325
407
  caret_position_for_line_index(line_index_for_caret_position(caret_position))
326
408
  end
409
+
410
+ # position within line containing "caret position" (e.g. for caret position 5 in 1st line, they match as 5, for 15 in line 2 with line 1 having 10 characters, line position is 4)
411
+ # TODO consider renaming to line_character_position_for_caret_position
412
+ def line_position_for_caret_position(caret_position)
413
+ caret_position = caret_position.to_i
414
+ caret_position - caret_position_for_caret_position_start_of_line(caret_position)
415
+ end
327
416
 
328
417
  def line_caret_positions_for_selection(caret_position, selection_count)
329
418
  line_indices = line_indices_for_selection(caret_position, selection_count)
@@ -358,6 +447,14 @@ module Glimmer
358
447
  def to_s
359
448
  path
360
449
  end
450
+
451
+ def eql?(other)
452
+ self.path.eql?(other&.path)
453
+ end
454
+
455
+ def hash
456
+ self.path.hash
457
+ end
361
458
  end
362
459
  end
363
460
  end