glimmer-cs-gladiator 0.1.1 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
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