glimmer-cs-gladiator 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,30 +1,30 @@
1
- $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
2
-
3
- require 'glimmer-dsl-swt'
4
- require 'filewatcher'
5
- require 'clipboard'
6
- require 'puts_debuggerer'
7
- require 'views/glimmer/gladiator'
8
-
9
- # Custom Composite Initializer (avoid default margins)
10
- Glimmer::SWT::WidgetProxy::DEFAULT_INITIALIZERS['composite'] = lambda do |composite|
11
- if composite.get_layout.nil?
12
- layout = GridLayout.new
13
- composite.layout = layout
14
- end
15
- end
16
-
17
- # Custom LayoutProxy initialize method (avoid default margins)
18
- module Glimmer
19
- module SWT
20
- class LayoutProxy
21
- def initialize(underscored_layout_name, widget_proxy, args)
22
- @underscored_layout_name = underscored_layout_name
23
- @widget_proxy = widget_proxy
24
- args = SWTProxy.constantify_args(args)
25
- @swt_layout = self.class.swt_layout_class_for(underscored_layout_name).new(*args)
26
- @widget_proxy.swt_widget.setLayout(@swt_layout)
27
- end
28
- end
29
- end
30
- end
1
+ $LOAD_PATH.unshift(File.expand_path('..', __FILE__))
2
+
3
+ require 'glimmer-dsl-swt'
4
+ require 'filewatcher'
5
+ require 'clipboard'
6
+ require 'puts_debuggerer'
7
+ require 'views/glimmer/gladiator'
8
+
9
+ # Custom Composite Initializer (avoid default margins)
10
+ Glimmer::SWT::WidgetProxy::DEFAULT_INITIALIZERS['composite'] = lambda do |composite|
11
+ if composite.get_layout.nil?
12
+ layout = GridLayout.new
13
+ composite.layout = layout
14
+ end
15
+ end
16
+
17
+ # Custom LayoutProxy initialize method (avoid default margins)
18
+ module Glimmer
19
+ module SWT
20
+ class LayoutProxy
21
+ def initialize(underscored_layout_name, widget_proxy, args)
22
+ @underscored_layout_name = underscored_layout_name
23
+ @widget_proxy = widget_proxy
24
+ args = SWTProxy.constantify_args(args)
25
+ @swt_layout = self.class.swt_layout_class_for(underscored_layout_name).new(*args)
26
+ @widget_proxy.swt_widget.setLayout(@swt_layout)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -1,6 +1,6 @@
1
- require_relative '../glimmer-cs-gladiator'
2
-
3
- include Glimmer
4
-
5
- local_dir = ENV['LOCAL_DIR'] || '.'
6
- gladiator(project_dir_path: local_dir).open
1
+ require_relative '../glimmer-cs-gladiator'
2
+
3
+ include Glimmer
4
+
5
+ local_dir = ENV['LOCAL_DIR'] || '.'
6
+ gladiator(project_dir_path: local_dir).open
@@ -1,112 +1,112 @@
1
- module Glimmer
2
- class Gladiator
3
- class Command
4
- include Glimmer
5
-
6
- class << self
7
- include Glimmer
8
-
9
- def command_history
10
- @command_history ||= {}
11
- end
12
-
13
- def command_history_for(file)
14
- # keeping a first command to make redo support work by remembering next command after undoing all
15
- command_history[file] ||= [Command.new(file)]
16
- end
17
-
18
- def do(file, method = nil, *args, command: nil)
19
- if command.nil?
20
- command ||= Command.new(file, method, *args)
21
- command.previous_command = command_history_for(file).last
22
- unless command_history_for(file).last.method == :change_content! && method == :change_content!
23
- command_history_for(file).last.next_command = command
24
- end
25
- command.do
26
- command_history_for(file) << command unless command_history_for(file).last.method == :change_content! && method == :change_content!
27
- else
28
- command_history_for(file) << command
29
- end
30
- end
31
-
32
- def undo(file)
33
- return if command_history_for(file).size <= 1
34
- command = command_history_for(file).pop
35
- command&.undo
36
- end
37
-
38
- def redo(file)
39
- command = command_history_for(file).last
40
- command&.redo
41
- end
42
-
43
- def clear(file)
44
- command_history[file] = [Command.new(file)]
45
- end
46
- end
47
-
48
- attr_accessor :file, :method, :args, :previous_command, :next_command,
49
- :file_dirty_content, :file_caret_position, :file_selection_count, :previous_file_dirty_content, :previous_file_caret_position, :previous_file_selection_count
50
-
51
- def initialize(file, method = nil, *args)
52
- @file = file
53
- @method = method
54
- @args = args
55
- end
56
-
57
-
58
- def native?
59
- @method.nil?
60
- end
61
-
62
- def do
63
- return if native?
64
- backup
65
- execute
66
- end
67
-
68
- def undo
69
- return if native?
70
- restore
71
- end
72
-
73
- def redo
74
- return if next_command.nil?# || next_command.native?
75
- @file.dirty_content = next_command.file_dirty_content.clone
76
- @file.caret_position = next_command.file_caret_position
77
- @file.selection_count = next_command.file_selection_count
78
- Command.do(next_command.file, command: next_command)
79
- end
80
-
81
- def backup
82
- @previous_file_dirty_content = @file.dirty_content.clone
83
- @previous_file_caret_position = @file.caret_position
84
- @previous_file_selection_count = @file.selection_count
85
- if @method == :change_content!
86
- @previous_file_caret_position = @file.last_caret_position
87
- @previous_file_selection_count = @file.last_selection_count
88
- end
89
- end
90
-
91
- def restore
92
- @file.dirty_content = @previous_file_dirty_content.clone
93
- @file.caret_position = @previous_file_caret_position
94
- @file.selection_count = @previous_file_selection_count
95
- end
96
-
97
- def execute
98
- @file.start_command
99
- @file.send(@method, *@args)
100
- @file.end_command
101
- @file_dirty_content = @file.dirty_content.clone
102
- @file_caret_position = @file.caret_position
103
- @file_selection_count = @file.selection_count
104
- if previous_command.method == :change_content! && @method == :change_content!
105
- previous_command.file_dirty_content = @file_dirty_content
106
- previous_command.file_caret_position = @file_caret_position
107
- previous_command.file_selection_count = @file_selection_count
108
- end
109
- end
110
- end
111
- end
112
- end
1
+ module Glimmer
2
+ class Gladiator
3
+ class Command
4
+ include Glimmer
5
+
6
+ class << self
7
+ include Glimmer
8
+
9
+ def command_history
10
+ @command_history ||= {}
11
+ end
12
+
13
+ def command_history_for(file)
14
+ # keeping a first command to make redo support work by remembering next command after undoing all
15
+ command_history[file] ||= [Command.new(file)]
16
+ end
17
+
18
+ def do(file, method = nil, *args, command: nil)
19
+ if command.nil?
20
+ command ||= Command.new(file, method, *args)
21
+ command.previous_command = command_history_for(file).last
22
+ unless command_history_for(file).last.method == :change_content! && method == :change_content!
23
+ command_history_for(file).last.next_command = command
24
+ end
25
+ command.do
26
+ command_history_for(file) << command unless command_history_for(file).last.method == :change_content! && method == :change_content!
27
+ else
28
+ command_history_for(file) << command
29
+ end
30
+ end
31
+
32
+ def undo(file)
33
+ return if command_history_for(file).size <= 1
34
+ command = command_history_for(file).pop
35
+ command&.undo
36
+ end
37
+
38
+ def redo(file)
39
+ command = command_history_for(file).last
40
+ command&.redo
41
+ end
42
+
43
+ def clear(file)
44
+ command_history[file] = [Command.new(file)]
45
+ end
46
+ end
47
+
48
+ attr_accessor :file, :method, :args, :previous_command, :next_command,
49
+ :file_dirty_content, :file_caret_position, :file_selection_count, :previous_file_dirty_content, :previous_file_caret_position, :previous_file_selection_count
50
+
51
+ def initialize(file, method = nil, *args)
52
+ @file = file
53
+ @method = method
54
+ @args = args
55
+ end
56
+
57
+
58
+ def native?
59
+ @method.nil?
60
+ end
61
+
62
+ def do
63
+ return if native?
64
+ backup
65
+ execute
66
+ end
67
+
68
+ def undo
69
+ return if native?
70
+ restore
71
+ end
72
+
73
+ def redo
74
+ return if next_command.nil?# || next_command.native?
75
+ @file.dirty_content = next_command.file_dirty_content.clone
76
+ @file.caret_position = next_command.file_caret_position
77
+ @file.selection_count = next_command.file_selection_count
78
+ Command.do(next_command.file, command: next_command)
79
+ end
80
+
81
+ def backup
82
+ @previous_file_dirty_content = @file.dirty_content.clone
83
+ @previous_file_caret_position = @file.caret_position
84
+ @previous_file_selection_count = @file.selection_count
85
+ if @method == :change_content!
86
+ @previous_file_caret_position = @file.last_caret_position
87
+ @previous_file_selection_count = @file.last_selection_count
88
+ end
89
+ end
90
+
91
+ def restore
92
+ @file.dirty_content = @previous_file_dirty_content.clone
93
+ @file.caret_position = @previous_file_caret_position
94
+ @file.selection_count = @previous_file_selection_count
95
+ end
96
+
97
+ def execute
98
+ @file.start_command
99
+ @file.send(@method, *@args)
100
+ @file.end_command
101
+ @file_dirty_content = @file.dirty_content.clone
102
+ @file_caret_position = @file.caret_position
103
+ @file_selection_count = @file.selection_count
104
+ if previous_command.method == :change_content! && @method == :change_content!
105
+ previous_command.file_dirty_content = @file_dirty_content
106
+ previous_command.file_caret_position = @file_caret_position
107
+ previous_command.file_selection_count = @file_selection_count
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -1,662 +1,662 @@
1
- module Glimmer
2
- class Gladiator
3
- class File
4
- include Glimmer
5
-
6
- attr_accessor :line_numbers_content, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive, :caret_position, :selection_count, :last_caret_position, :last_selection_count, :line_position
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
12
- @command_history = []
13
- @name = path.empty? ? 'Scratchpad' : ::File.basename(path)
14
- self.path = ::File.expand_path(path) unless path.empty?
15
- @top_pixel = 0
16
- @caret_position = 0
17
- @selection_count = 0
18
- @last_selection_count = 0
19
- @line_number = 1
20
- @init = nil
21
- end
22
-
23
- def language
24
- # TODO consider using Rouge::Lexer.guess_by_filename instead and perhaps guess_by_source when it fails
25
- extension = path.split('.').last if path.to_s.include?('.')
26
- return 'ruby' if scratchpad?
27
- return 'ruby' if path.to_s.end_with?('Gemfile') || path.to_s.end_with?('Rakefile')
28
- return 'ruby' if dirty_content.start_with?('#!/usr/bin/env ruby') || dirty_content.start_with?('#!/usr/bin/env jruby')
29
- return 'yaml' if path.to_s.end_with?('Gemfile.lock')
30
- return 'shell' if extension.nil? && path.to_s.include?('/bin/')
31
- case extension
32
- # TODO extract case statement to an external config file
33
- when 'rb'
34
- 'ruby'
35
- when 'md', 'markdown'
36
- 'markdown'
37
- when 'js', 'es6'
38
- 'javascript'
39
- when 'json'
40
- 'json'
41
- when 'yaml'
42
- 'yaml'
43
- when 'html'
44
- 'html'
45
- when 'h', 'c'
46
- 'c'
47
- when 'hs'
48
- 'haskell'
49
- when 'gradle'
50
- 'gradle'
51
- when 'cpp'
52
- 'cpp'
53
- when 'css'
54
- 'css'
55
- when 'java'
56
- 'java'
57
- when 'jsp'
58
- 'jsp'
59
- when 'plist'
60
- 'plist'
61
- when 'haml'
62
- 'haml'
63
- when 'xml'
64
- 'xml'
65
- when 'ini'
66
- 'ini'
67
- when 'pl'
68
- 'perl'
69
- when 'tcl'
70
- 'tcl'
71
- when 'sass'
72
- 'sass'
73
- when 'scss'
74
- 'scss'
75
- when 'sql'
76
- 'sql'
77
- when 'sh'
78
- 'shell'
79
- when 'vue'
80
- 'vue'
81
- when 'txt', nil
82
- 'plain_text'
83
- end
84
- end
85
-
86
- def init_content
87
- unless @init
88
- @init = true
89
- begin
90
- # test read dirty content
91
- observe(self, :dirty_content) do
92
- line_count = lines.empty? ? 1 : lines.size
93
- lines_text_size = [line_count.to_s.size, 4].max
94
- old_top_pixel = top_pixel
95
- self.line_numbers_content = line_count.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
96
- self.top_pixel = old_top_pixel
97
- end
98
- the_dirty_content = read_dirty_content
99
- the_dirty_content.split("\n") # test that it is not a binary file (crashes to rescue block otherwise)
100
- self.dirty_content = the_dirty_content
101
- observe(self, :caret_position) do |new_caret_position|
102
- update_line_number_from_caret_position(new_caret_position)
103
- end
104
- observe(self, :line_number) do |new_line_number|
105
- line_index = line_number - 1
106
- new_caret_position = caret_position_for_line_index(line_index)
107
- current_caret_position = caret_position
108
- line_index_for_new_caret_position = line_index_for_caret_position(new_caret_position)
109
- line_index_for_current_caret_position = line_index_for_caret_position(current_caret_position)
110
- self.caret_position = new_caret_position unless (current_caret_position && line_index_for_new_caret_position == line_index_for_current_caret_position)
111
- end
112
- rescue # in case of a binary file
113
- stop_filewatcher
114
- end
115
- end
116
- end
117
-
118
- def update_line_number_from_caret_position(new_caret_position)
119
- new_line_number = line_index_for_caret_position(caret_position) + 1
120
- current_line_number = line_number
121
- unless (current_line_number && current_line_number == new_line_number)
122
- self.line_number = new_line_number
123
- # TODO check if the following line is needed
124
- self.line_position = caret_position - caret_position_for_line_index(line_number - 1) + 1
125
- end
126
- end
127
-
128
- def path=(the_path)
129
- @path = the_path
130
- generate_display_path
131
- end
132
-
133
- def generate_display_path
134
- return if @path.empty?
135
- @display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
136
- end
137
-
138
- def name=(the_name)
139
- new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name) unless scratchpad?
140
- @name = the_name
141
- if !scratchpad? && ::File.exist?(path)
142
- FileUtils.mv(path, new_path)
143
- self.path = new_path
144
- end
145
- end
146
-
147
- def scratchpad?
148
- path.to_s.empty?
149
- end
150
-
151
- def backup_properties
152
- [:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
153
- hash.merge(property => send(property))
154
- end
155
- end
156
-
157
- def restore_properties(properties_hash)
158
- 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?
159
- properties_hash.each do |property, value|
160
- send("#{property}=", value)
161
- end
162
- end
163
-
164
- def caret_position=(value)
165
- @last_caret_position = @caret_position
166
- @caret_position = value
167
- end
168
-
169
- def selection_count=(value)
170
- #@last_selection_count = @selection_count
171
- @selection_count = value
172
- @last_selection_count = @selection_count
173
- end
174
-
175
- def dirty_content
176
- init_content
177
- @dirty_content
178
- end
179
-
180
- def dirty_content=(the_content)
181
- # TODO set partial dirty content by line(s) for enhanced performance
182
- @dirty_content = the_content
183
- old_caret_position = caret_position
184
- old_top_pixel = top_pixel
185
-
186
- notify_observers(:content)
187
- if @formatting_dirty_content_for_writing
188
- self.caret_position = old_caret_position
189
- self.top_pixel = old_top_pixel
190
- end
191
- end
192
-
193
- def content
194
- dirty_content
195
- end
196
-
197
- # to use for widget data-binding
198
- def content=(value)
199
- value = value.gsub("\t", ' ')
200
- if dirty_content != value
201
- Command.do(self, :change_content!, value)
202
- end
203
- end
204
-
205
- def change_content!(value)
206
- self.dirty_content = value
207
- update_line_number_from_caret_position(caret_position)
208
- end
209
-
210
- def start_command
211
- @commmand_in_progress = true
212
- end
213
-
214
- def end_command
215
- @commmand_in_progress = false
216
- end
217
-
218
- def command_in_progress?
219
- @commmand_in_progress
220
- end
221
-
222
- def close
223
- stop_filewatcher
224
- remove_all_observers
225
- initialize(path, project_dir)
226
- Command.clear(self)
227
- end
228
-
229
- def read_dirty_content
230
- path.empty? ? '' : ::File.read(path)
231
- end
232
-
233
- def start_filewatcher
234
- return if scratchpad?
235
- @filewatcher = Filewatcher.new(@path)
236
- @thread = Thread.new(@filewatcher) do |fw|
237
- fw.watch do |filename, event|
238
- async_exec do
239
- begin
240
- self.dirty_content = read_dirty_content if read_dirty_content != dirty_content
241
- rescue StandardError, Errno::ENOENT
242
- # in case of a binary file
243
- stop_filewatcher
244
- end
245
- end
246
- end
247
- end
248
- end
249
-
250
- def stop_filewatcher
251
- @filewatcher&.stop
252
- end
253
-
254
- def write_dirty_content
255
- # TODO write partial dirty content by line(s) for enhanced performance
256
- return if scratchpad? || !::File.exist?(path) || !::File.exists?(path) || read_dirty_content == dirty_content
257
- format_dirty_content_for_writing!
258
- ::File.write(path, dirty_content)
259
- rescue StandardError, ArgumentError => e
260
- puts "Error in writing dirty content for #{path}"
261
- puts e.full_message
262
- end
263
-
264
- def format_dirty_content_for_writing!
265
- return if @commmand_in_progress
266
- # TODO f ix c ar e t pos it ion after formatting dirty content (diff?)
267
- new_dirty_content = dirty_content.to_s.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
268
- new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
269
- if new_dirty_content != self.dirty_content
270
- @formatting_dirty_content_for_writing = true
271
- self.dirty_content = new_dirty_content
272
- @formatting_dirty_content_for_writing = false
273
- end
274
- end
275
-
276
- def write_raw_dirty_content
277
- return if scratchpad? || !::File.exist?(path)
278
- ::File.write(path, dirty_content) if ::File.exists?(path)
279
- rescue => e
280
- puts "Error in writing raw dirty content for #{path}"
281
- puts e.full_message
282
- end
283
-
284
- def current_line_indentation
285
- current_line.to_s.match(/^(\s+)/).to_a[1].to_s
286
- end
287
-
288
- def current_line
289
- lines[line_number - 1]
290
- end
291
-
292
- def delete!
293
- FileUtils.rm(path) unless scratchpad?
294
- end
295
-
296
- def prefix_new_line!
297
- the_lines = lines
298
- the_lines[line_number-1...line_number-1] = [current_line_indentation]
299
- self.dirty_content = the_lines.join("\n")
300
- self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
301
- self.selection_count = 0
302
- end
303
-
304
- def insert_new_line!
305
- the_lines = lines
306
- the_lines[line_number...line_number] = [current_line_indentation]
307
- self.dirty_content = the_lines.join("\n")
308
- self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
309
- self.selection_count = 0
310
- end
311
-
312
- def comment_line!
313
- old_lines = lines
314
- return if old_lines.size < 1
315
- old_selection_count = self.selection_count
316
- old_caret_position = self.caret_position
317
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
318
- old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
319
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
320
- new_lines = lines
321
- delta = 0
322
- line_indices_for_selection(caret_position, selection_count).reverse.each do | the_line_index |
323
- delta = 0
324
- the_line = old_lines[the_line_index]
325
- return if the_line.nil?
326
- if the_line.strip.start_with?('# ')
327
- new_lines[the_line_index] = the_line.sub(/# /, '')
328
- delta -= 2
329
- elsif the_line.strip.start_with?('#')
330
- new_lines[the_line_index] = the_line.sub(/#/, '')
331
- delta -= 1
332
- else
333
- new_lines[the_line_index] = "# #{the_line}"
334
- delta += 2
335
- end
336
- end
337
- self.dirty_content = new_lines.join("\n")
338
- if old_selection_count.to_i > 0
339
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
340
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
341
- else
342
- new_caret_position = old_caret_position + delta
343
- new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
344
- self.caret_position = new_caret_position
345
- self.selection_count = 0
346
- end
347
- end
348
-
349
- def indent!
350
- new_lines = lines
351
- old_lines = lines
352
- return if old_lines.size < 1
353
- old_selection_count = self.selection_count
354
- old_caret_position = self.caret_position
355
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
356
- old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
357
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
358
- delta = 2
359
- line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
360
- the_line = old_lines[the_line_index]
361
- new_lines[the_line_index] = " #{the_line}"
362
- end
363
- old_caret_position = self.caret_position
364
- self.dirty_content = new_lines.join("\n")
365
- if old_selection_count.to_i > 0
366
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
367
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
368
- else
369
- self.caret_position = old_caret_position + delta
370
- self.selection_count = 0
371
- end
372
- end
373
-
374
- def outdent!
375
- new_lines = lines
376
- old_lines = lines
377
- return if old_lines.size < 1
378
- old_selection_count = self.selection_count
379
- old_caret_position = self.caret_position
380
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
381
- old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
382
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
383
- delta = 0
384
- line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
385
- the_line = old_lines[the_line_index]
386
- if the_line.to_s.start_with?(' ')
387
- new_lines[the_line_index] = the_line.sub(/ /, '')
388
- delta = -2
389
- elsif the_line&.start_with?(' ')
390
- new_lines[the_line_index] = the_line.sub(/ /, '')
391
- delta = -1
392
- end
393
- end
394
- self.dirty_content = new_lines.join("\n")
395
- if old_selection_count.to_i > 0
396
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
397
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
398
- else
399
- new_caret_position = old_caret_position + delta
400
- new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
401
- self.caret_position = new_caret_position
402
- self.selection_count = 0
403
- end
404
- end
405
-
406
- def kill_line!
407
- new_lines = lines
408
- return if new_lines.size < 1
409
- line_indices = line_indices_for_selection(caret_position, selection_count)
410
- new_lines = new_lines[0...line_indices.first] + new_lines[(line_indices.last+1)...new_lines.size]
411
- old_caret_position = self.caret_position
412
- old_line_index = self.line_number - 1
413
- line_position = line_position_for_caret_position(old_caret_position)
414
- self.dirty_content = "#{new_lines.join("\n")}\n"
415
- self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
416
- self.selection_count = 0
417
- end
418
-
419
- def duplicate_line!
420
- new_lines = lines
421
- old_lines = lines
422
- return if old_lines.size < 1
423
- old_selection_count = self.selection_count
424
- old_caret_position = self.caret_position
425
- old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
426
- old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
427
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
428
- the_line_indices = line_indices_for_selection(caret_position, selection_count)
429
- the_lines = lines_for_selection(caret_position, selection_count)
430
- delta = the_lines.join("\n").size + 1
431
- the_lines.each_with_index do |the_line, i|
432
- new_lines.insert(the_line_indices.first + i, the_line)
433
- end
434
- self.dirty_content = new_lines.join("\n")
435
- if old_selection_count.to_i > 0
436
- self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
437
- self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
438
- else
439
- self.caret_position = old_caret_position + delta
440
- self.selection_count = 0
441
- end
442
- end
443
-
444
- def find_next
445
- return if find_text.to_s.empty?
446
- all_lines = lines
447
- the_line_index = line_index_for_caret_position(caret_position)
448
- line_position = line_position_for_caret_position(caret_position)
449
- found = found_text?(caret_position)
450
- 2.times do |i|
451
- rotation = the_line_index
452
- all_lines.rotate(rotation).each_with_index do |the_line, the_index|
453
- the_index = (the_index + rotation)%all_lines.size
454
- start_position = 0
455
- start_position = line_position + find_text.to_s.size if i == 0 && the_index == the_line_index && found_text?(caret_position)
456
- text_to_find_in = the_line[start_position..-1]
457
- occurrence_index = case_sensitive ? text_to_find_in&.index(find_text.to_s) : text_to_find_in&.downcase&.index(find_text.to_s.downcase)
458
- if occurrence_index
459
- self.caret_position = caret_position_for_line_index(the_index) + start_position + occurrence_index
460
- self.selection_count = find_text.to_s.size
461
- return
462
- end
463
- end
464
- end
465
- end
466
-
467
- def find_previous
468
- return if find_text.to_s.empty?
469
- all_lines = lines
470
- the_line_index = line_index_for_caret_position(caret_position)
471
- line_position = line_position_for_caret_position(caret_position)
472
- 2.times do |i|
473
- rotation = - the_line_index - 1 + all_lines.size
474
- all_lines.reverse.rotate(rotation).each_with_index do |the_line, the_index|
475
- the_index = all_lines.size - 1 - (the_index + rotation)%all_lines.size
476
- if the_index == the_line_index
477
- start_position = i > 0 ? 0 : (the_line.size - line_position)
478
- else
479
- start_position = 0
480
- end
481
- text_to_find_in = the_line.downcase.reverse[start_position...the_line.size].to_s
482
- occurrence_index = text_to_find_in.index(find_text.to_s.downcase.reverse)
483
- if occurrence_index
484
- self.caret_position = caret_position_for_line_index(the_index) + (the_line.size - (start_position + occurrence_index + find_text.to_s.size))
485
- self.selection_count = find_text.to_s.size
486
- return
487
- end
488
- end
489
- end
490
- end
491
-
492
- def ensure_find_next
493
- return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
494
- find_next unless found_text?(self.caret_position)
495
- end
496
-
497
- def found_text?(caret_position)
498
- dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
499
- end
500
-
501
- def replace_next!
502
- return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
503
- ensure_find_next
504
- new_dirty_content = dirty_content
505
- new_dirty_content[caret_position, find_text.size] = replace_text.to_s
506
- self.dirty_content = new_dirty_content
507
- find_next
508
- find_next if replace_text.to_s.include?(find_text) && !replace_text.to_s.start_with?(find_text)
509
- end
510
-
511
- def page_up
512
- self.selection_count = 0
513
- self.line_number = [(self.line_number - 15), 1].max
514
- end
515
-
516
- def page_down
517
- self.selection_count = 0
518
- self.line_number = [(self.line_number + 15), lines.size].min
519
- end
520
-
521
- def home
522
- self.selection_count = 0
523
- self.line_number = 1
524
- end
525
-
526
- def end
527
- self.selection_count = 0
528
- self.line_number = lines.size
529
- end
530
-
531
- def start_of_line
532
- self.caret_position = caret_position_for_line_index(self.line_number - 1)
533
- end
534
-
535
- def end_of_line
536
- self.caret_position = caret_position_for_line_index(self.line_number) - 1
537
- end
538
-
539
- def move_up!
540
- old_lines = lines
541
- return if old_lines.size < 2
542
- old_selection_count = self.selection_count
543
- old_caret_position = self.caret_position
544
- old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
545
- old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
546
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
547
- new_lines = lines
548
- the_line_indices = line_indices_for_selection(caret_position, selection_count)
549
- the_lines = lines_for_selection(caret_position, selection_count)
550
- new_line_index = [the_line_indices.first - 1, 0].max
551
- new_lines[the_line_indices.first..the_line_indices.last] = []
552
- new_lines[new_line_index...new_line_index] = the_lines
553
- self.dirty_content = new_lines.join("\n")
554
- self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
555
- self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
556
- end
557
-
558
- def move_down!
559
- old_lines = lines
560
- return if old_lines.size < 2
561
- old_selection_count = self.selection_count
562
- old_caret_position = self.caret_position
563
- old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
564
- old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
565
- old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
566
- new_lines = lines
567
- the_line_indices = line_indices_for_selection(caret_position, selection_count)
568
- the_lines = lines_for_selection(caret_position, selection_count)
569
- new_line_index = [the_line_indices.first + 1, new_lines.size - 1].min
570
- new_lines[the_line_indices.first..the_line_indices.last] = []
571
- new_lines[new_line_index...new_line_index] = the_lines
572
- self.dirty_content = new_lines.join("\n")
573
- self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
574
- self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
575
- end
576
-
577
- def run
578
- if scratchpad?
579
- eval content
580
- else
581
- write_dirty_content
582
- load path
583
- end
584
- end
585
-
586
- def lines
587
- need_padding = dirty_content.to_s.end_with?("\n")
588
- splittable_content = need_padding ? "#{dirty_content} " : dirty_content
589
- the_lines = splittable_content.split("\n")
590
- the_lines[-1] = the_lines[-1].strip if need_padding
591
- the_lines
592
- end
593
-
594
- def line_for_caret_position(caret_position)
595
- lines[line_index_for_caret_position(caret_position.to_i)]
596
- end
597
-
598
- def line_index_for_caret_position(caret_position)
599
- dirty_content[0...caret_position.to_i].count("\n")
600
- end
601
-
602
- def caret_position_for_line_index(line_index)
603
- cp = lines[0...line_index].join("\n").size
604
- cp += 1 if line_index > 0
605
- cp
606
- end
607
-
608
- def caret_position_for_caret_position_start_of_line(caret_position)
609
- caret_position_for_line_index(line_index_for_caret_position(caret_position))
610
- end
611
-
612
- # 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)
613
- # TODO consider renaming to line_character_position_for_caret_position
614
- def line_position_for_caret_position(caret_position)
615
- caret_position = caret_position.to_i
616
- caret_position - caret_position_for_caret_position_start_of_line(caret_position)
617
- end
618
-
619
- def line_caret_positions_for_selection(caret_position, selection_count)
620
- line_indices = line_indices_for_selection(caret_position, selection_count)
621
- line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
622
- end
623
-
624
- def end_caret_position_line_index(caret_position, selection_count)
625
- end_caret_position = caret_position + selection_count.to_i
626
- end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
627
- end_line_index = line_index_for_caret_position(end_caret_position)
628
- end
629
-
630
- def lines_for_selection(caret_position, selection_count)
631
- line_indices = line_indices_for_selection(caret_position, selection_count)
632
- lines[line_indices.first..line_indices.last]
633
- end
634
-
635
- def line_indices_for_selection(caret_position, selection_count)
636
- start_line_index = line_index_for_caret_position(caret_position)
637
- if selection_count.to_i > 0
638
- end_line_index = end_caret_position_line_index(caret_position, selection_count)
639
- else
640
- end_line_index = start_line_index
641
- end
642
- (start_line_index..end_line_index).to_a
643
- end
644
-
645
- def children
646
- []
647
- end
648
-
649
- def to_s
650
- path
651
- end
652
-
653
- def eql?(other)
654
- self.path.eql?(other&.path)
655
- end
656
-
657
- def hash
658
- self.path.hash
659
- end
660
- end
661
- end
662
- end
1
+ module Glimmer
2
+ class Gladiator
3
+ class File
4
+ include Glimmer
5
+
6
+ attr_accessor :line_numbers_content, :line_number, :find_text, :replace_text, :top_pixel, :display_path, :case_sensitive, :caret_position, :selection_count, :last_caret_position, :last_selection_count, :line_position
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
12
+ @command_history = []
13
+ @name = path.empty? ? 'Scratchpad' : ::File.basename(path)
14
+ self.path = ::File.expand_path(path) unless path.empty?
15
+ @top_pixel = 0
16
+ @caret_position = 0
17
+ @selection_count = 0
18
+ @last_selection_count = 0
19
+ @line_number = 1
20
+ @init = nil
21
+ end
22
+
23
+ def language
24
+ # TODO consider using Rouge::Lexer.guess_by_filename instead and perhaps guess_by_source when it fails
25
+ extension = path.split('.').last if path.to_s.include?('.')
26
+ return 'ruby' if scratchpad?
27
+ return 'ruby' if path.to_s.end_with?('Gemfile') || path.to_s.end_with?('Rakefile')
28
+ return 'ruby' if dirty_content.start_with?('#!/usr/bin/env ruby') || dirty_content.start_with?('#!/usr/bin/env jruby')
29
+ return 'yaml' if path.to_s.end_with?('Gemfile.lock')
30
+ return 'shell' if extension.nil? && path.to_s.include?('/bin/')
31
+ case extension
32
+ # TODO extract case statement to an external config file
33
+ when 'rb'
34
+ 'ruby'
35
+ when 'md', 'markdown'
36
+ 'markdown'
37
+ when 'js', 'es6'
38
+ 'javascript'
39
+ when 'json'
40
+ 'json'
41
+ when 'yaml'
42
+ 'yaml'
43
+ when 'html'
44
+ 'html'
45
+ when 'h', 'c'
46
+ 'c'
47
+ when 'hs'
48
+ 'haskell'
49
+ when 'gradle'
50
+ 'gradle'
51
+ when 'cpp'
52
+ 'cpp'
53
+ when 'css'
54
+ 'css'
55
+ when 'java'
56
+ 'java'
57
+ when 'jsp'
58
+ 'jsp'
59
+ when 'plist'
60
+ 'plist'
61
+ when 'haml'
62
+ 'haml'
63
+ when 'xml'
64
+ 'xml'
65
+ when 'ini'
66
+ 'ini'
67
+ when 'pl'
68
+ 'perl'
69
+ when 'tcl'
70
+ 'tcl'
71
+ when 'sass'
72
+ 'sass'
73
+ when 'scss'
74
+ 'scss'
75
+ when 'sql'
76
+ 'sql'
77
+ when 'sh'
78
+ 'shell'
79
+ when 'vue'
80
+ 'vue'
81
+ when 'txt', nil
82
+ 'plain_text'
83
+ end
84
+ end
85
+
86
+ def init_content
87
+ unless @init
88
+ @init = true
89
+ begin
90
+ # test read dirty content
91
+ observe(self, :dirty_content) do
92
+ line_count = lines.empty? ? 1 : lines.size
93
+ lines_text_size = [line_count.to_s.size, 4].max
94
+ old_top_pixel = top_pixel
95
+ self.line_numbers_content = line_count.times.map {|n| (' ' * (lines_text_size - (n+1).to_s.size)) + (n+1).to_s }.join("\n")
96
+ self.top_pixel = old_top_pixel
97
+ end
98
+ the_dirty_content = read_dirty_content
99
+ the_dirty_content.split("\n") # test that it is not a binary file (crashes to rescue block otherwise)
100
+ self.dirty_content = the_dirty_content
101
+ observe(self, :caret_position) do |new_caret_position|
102
+ update_line_number_from_caret_position(new_caret_position)
103
+ end
104
+ observe(self, :line_number) do |new_line_number|
105
+ line_index = line_number - 1
106
+ new_caret_position = caret_position_for_line_index(line_index)
107
+ current_caret_position = caret_position
108
+ line_index_for_new_caret_position = line_index_for_caret_position(new_caret_position)
109
+ line_index_for_current_caret_position = line_index_for_caret_position(current_caret_position)
110
+ self.caret_position = new_caret_position unless (current_caret_position && line_index_for_new_caret_position == line_index_for_current_caret_position)
111
+ end
112
+ rescue # in case of a binary file
113
+ stop_filewatcher
114
+ end
115
+ end
116
+ end
117
+
118
+ def update_line_number_from_caret_position(new_caret_position)
119
+ new_line_number = line_index_for_caret_position(caret_position) + 1
120
+ current_line_number = line_number
121
+ unless (current_line_number && current_line_number == new_line_number)
122
+ self.line_number = new_line_number
123
+ # TODO check if the following line is needed
124
+ self.line_position = caret_position - caret_position_for_line_index(line_number - 1) + 1
125
+ end
126
+ end
127
+
128
+ def path=(the_path)
129
+ @path = the_path
130
+ generate_display_path
131
+ end
132
+
133
+ def generate_display_path
134
+ return if @path.empty?
135
+ @display_path = @path.sub(project_dir.path, '').sub(/^\//, '')
136
+ end
137
+
138
+ def name=(the_name)
139
+ new_path = path.sub(/#{Regexp.escape(@name)}$/, the_name) unless scratchpad?
140
+ @name = the_name
141
+ if !scratchpad? && ::File.exist?(path)
142
+ FileUtils.mv(path, new_path)
143
+ self.path = new_path
144
+ end
145
+ end
146
+
147
+ def scratchpad?
148
+ path.to_s.empty?
149
+ end
150
+
151
+ def backup_properties
152
+ [:find_text, :replace_text, :case_sensitive, :top_pixel, :caret_position, :selection_count].reduce({}) do |hash, property|
153
+ hash.merge(property => send(property))
154
+ end
155
+ end
156
+
157
+ def restore_properties(properties_hash)
158
+ 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?
159
+ properties_hash.each do |property, value|
160
+ send("#{property}=", value)
161
+ end
162
+ end
163
+
164
+ def caret_position=(value)
165
+ @last_caret_position = @caret_position
166
+ @caret_position = value
167
+ end
168
+
169
+ def selection_count=(value)
170
+ #@last_selection_count = @selection_count
171
+ @selection_count = value
172
+ @last_selection_count = @selection_count
173
+ end
174
+
175
+ def dirty_content
176
+ init_content
177
+ @dirty_content
178
+ end
179
+
180
+ def dirty_content=(the_content)
181
+ # TODO set partial dirty content by line(s) for enhanced performance
182
+ @dirty_content = the_content
183
+ old_caret_position = caret_position
184
+ old_top_pixel = top_pixel
185
+
186
+ notify_observers(:content)
187
+ if @formatting_dirty_content_for_writing
188
+ self.caret_position = old_caret_position
189
+ self.top_pixel = old_top_pixel
190
+ end
191
+ end
192
+
193
+ def content
194
+ dirty_content
195
+ end
196
+
197
+ # to use for widget data-binding
198
+ def content=(value)
199
+ value = value.gsub("\t", ' ')
200
+ if dirty_content != value
201
+ Command.do(self, :change_content!, value)
202
+ end
203
+ end
204
+
205
+ def change_content!(value)
206
+ self.dirty_content = value
207
+ update_line_number_from_caret_position(caret_position)
208
+ end
209
+
210
+ def start_command
211
+ @commmand_in_progress = true
212
+ end
213
+
214
+ def end_command
215
+ @commmand_in_progress = false
216
+ end
217
+
218
+ def command_in_progress?
219
+ @commmand_in_progress
220
+ end
221
+
222
+ def close
223
+ stop_filewatcher
224
+ remove_all_observers
225
+ initialize(path, project_dir)
226
+ Command.clear(self)
227
+ end
228
+
229
+ def read_dirty_content
230
+ path.empty? ? '' : ::File.read(path)
231
+ end
232
+
233
+ def start_filewatcher
234
+ return if scratchpad?
235
+ @filewatcher = Filewatcher.new(@path)
236
+ @thread = Thread.new(@filewatcher) do |fw|
237
+ fw.watch do |filename, event|
238
+ async_exec do
239
+ begin
240
+ self.dirty_content = read_dirty_content if read_dirty_content != dirty_content
241
+ rescue StandardError, Errno::ENOENT
242
+ # in case of a binary file
243
+ stop_filewatcher
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ def stop_filewatcher
251
+ @filewatcher&.stop
252
+ end
253
+
254
+ def write_dirty_content
255
+ # TODO write partial dirty content by line(s) for enhanced performance
256
+ return if scratchpad? || !::File.exist?(path) || !::File.exists?(path) || read_dirty_content == dirty_content
257
+ format_dirty_content_for_writing!
258
+ ::File.write(path, dirty_content)
259
+ rescue StandardError, ArgumentError => e
260
+ puts "Error in writing dirty content for #{path}"
261
+ puts e.full_message
262
+ end
263
+
264
+ def format_dirty_content_for_writing!
265
+ return if @commmand_in_progress
266
+ # TODO f ix c ar e t pos it ion after formatting dirty content (diff?)
267
+ new_dirty_content = dirty_content.to_s.split("\n").map {|line| line.strip.empty? ? line : line.rstrip }.join("\n")
268
+ new_dirty_content = "#{new_dirty_content.gsub("\r\n", "\n").gsub("\r", "\n").sub(/\n+\z/, '')}\n"
269
+ if new_dirty_content != self.dirty_content
270
+ @formatting_dirty_content_for_writing = true
271
+ self.dirty_content = new_dirty_content
272
+ @formatting_dirty_content_for_writing = false
273
+ end
274
+ end
275
+
276
+ def write_raw_dirty_content
277
+ return if scratchpad? || !::File.exist?(path)
278
+ ::File.write(path, dirty_content) if ::File.exists?(path)
279
+ rescue => e
280
+ puts "Error in writing raw dirty content for #{path}"
281
+ puts e.full_message
282
+ end
283
+
284
+ def current_line_indentation
285
+ current_line.to_s.match(/^(\s+)/).to_a[1].to_s
286
+ end
287
+
288
+ def current_line
289
+ lines[line_number - 1]
290
+ end
291
+
292
+ def delete!
293
+ FileUtils.rm(path) unless scratchpad?
294
+ end
295
+
296
+ def prefix_new_line!
297
+ the_lines = lines
298
+ the_lines[line_number-1...line_number-1] = [current_line_indentation]
299
+ self.dirty_content = the_lines.join("\n")
300
+ self.caret_position = caret_position_for_line_index(line_number-1) + current_line_indentation.size
301
+ self.selection_count = 0
302
+ end
303
+
304
+ def insert_new_line!
305
+ the_lines = lines
306
+ the_lines[line_number...line_number] = [current_line_indentation]
307
+ self.dirty_content = the_lines.join("\n")
308
+ self.caret_position = caret_position_for_line_index(line_number) + current_line_indentation.size
309
+ self.selection_count = 0
310
+ end
311
+
312
+ def comment_line!
313
+ old_lines = lines
314
+ return if old_lines.size < 1
315
+ old_selection_count = self.selection_count
316
+ old_caret_position = self.caret_position
317
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
318
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
319
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
320
+ new_lines = lines
321
+ delta = 0
322
+ line_indices_for_selection(caret_position, selection_count).reverse.each do | the_line_index |
323
+ delta = 0
324
+ the_line = old_lines[the_line_index]
325
+ return if the_line.nil?
326
+ if the_line.strip.start_with?('# ')
327
+ new_lines[the_line_index] = the_line.sub(/# /, '')
328
+ delta -= 2
329
+ elsif the_line.strip.start_with?('#')
330
+ new_lines[the_line_index] = the_line.sub(/#/, '')
331
+ delta -= 1
332
+ else
333
+ new_lines[the_line_index] = "# #{the_line}"
334
+ delta += 2
335
+ end
336
+ end
337
+ self.dirty_content = new_lines.join("\n")
338
+ if old_selection_count.to_i > 0
339
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
340
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
341
+ else
342
+ new_caret_position = old_caret_position + delta
343
+ new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
344
+ self.caret_position = new_caret_position
345
+ self.selection_count = 0
346
+ end
347
+ end
348
+
349
+ def indent!
350
+ new_lines = lines
351
+ old_lines = lines
352
+ return if old_lines.size < 1
353
+ old_selection_count = self.selection_count
354
+ old_caret_position = self.caret_position
355
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
356
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
357
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
358
+ delta = 2
359
+ line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
360
+ the_line = old_lines[the_line_index]
361
+ new_lines[the_line_index] = " #{the_line}"
362
+ end
363
+ old_caret_position = self.caret_position
364
+ self.dirty_content = new_lines.join("\n")
365
+ if old_selection_count.to_i > 0
366
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
367
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
368
+ else
369
+ self.caret_position = old_caret_position + delta
370
+ self.selection_count = 0
371
+ end
372
+ end
373
+
374
+ def outdent!
375
+ new_lines = lines
376
+ old_lines = lines
377
+ return if old_lines.size < 1
378
+ old_selection_count = self.selection_count
379
+ old_caret_position = self.caret_position
380
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
381
+ old_caret_position_line_caret_position = caret_position_for_line_index(old_caret_position_line_index)
382
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
383
+ delta = 0
384
+ line_indices_for_selection(caret_position, selection_count).each do |the_line_index|
385
+ the_line = old_lines[the_line_index]
386
+ if the_line.to_s.start_with?(' ')
387
+ new_lines[the_line_index] = the_line.sub(/ /, '')
388
+ delta = -2
389
+ elsif the_line&.start_with?(' ')
390
+ new_lines[the_line_index] = the_line.sub(/ /, '')
391
+ delta = -1
392
+ end
393
+ end
394
+ self.dirty_content = new_lines.join("\n")
395
+ if old_selection_count.to_i > 0
396
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
397
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
398
+ else
399
+ new_caret_position = old_caret_position + delta
400
+ new_caret_position = [new_caret_position, old_caret_position_line_caret_position].max
401
+ self.caret_position = new_caret_position
402
+ self.selection_count = 0
403
+ end
404
+ end
405
+
406
+ def kill_line!
407
+ new_lines = lines
408
+ return if new_lines.size < 1
409
+ line_indices = line_indices_for_selection(caret_position, selection_count)
410
+ new_lines = new_lines[0...line_indices.first] + new_lines[(line_indices.last+1)...new_lines.size]
411
+ old_caret_position = self.caret_position
412
+ old_line_index = self.line_number - 1
413
+ line_position = line_position_for_caret_position(old_caret_position)
414
+ self.dirty_content = "#{new_lines.join("\n")}\n"
415
+ self.caret_position = caret_position_for_line_index(old_line_index) + [line_position, lines[old_line_index].to_s.size].min
416
+ self.selection_count = 0
417
+ end
418
+
419
+ def duplicate_line!
420
+ new_lines = lines
421
+ old_lines = lines
422
+ return if old_lines.size < 1
423
+ old_selection_count = self.selection_count
424
+ old_caret_position = self.caret_position
425
+ old_caret_position_line_index = line_index_for_caret_position(old_caret_position)
426
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position_line_index)
427
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
428
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
429
+ the_lines = lines_for_selection(caret_position, selection_count)
430
+ delta = the_lines.join("\n").size + 1
431
+ the_lines.each_with_index do |the_line, i|
432
+ new_lines.insert(the_line_indices.first + i, the_line)
433
+ end
434
+ self.dirty_content = new_lines.join("\n")
435
+ if old_selection_count.to_i > 0
436
+ self.caret_position = caret_position_for_line_index(old_caret_position_line_index)
437
+ self.selection_count = (caret_position_for_line_index(old_end_caret_line_index + 1) - self.caret_position)
438
+ else
439
+ self.caret_position = old_caret_position + delta
440
+ self.selection_count = 0
441
+ end
442
+ end
443
+
444
+ def find_next
445
+ return if find_text.to_s.empty?
446
+ all_lines = lines
447
+ the_line_index = line_index_for_caret_position(caret_position)
448
+ line_position = line_position_for_caret_position(caret_position)
449
+ found = found_text?(caret_position)
450
+ 2.times do |i|
451
+ rotation = the_line_index
452
+ all_lines.rotate(rotation).each_with_index do |the_line, the_index|
453
+ the_index = (the_index + rotation)%all_lines.size
454
+ start_position = 0
455
+ start_position = line_position + find_text.to_s.size if i == 0 && the_index == the_line_index && found_text?(caret_position)
456
+ text_to_find_in = the_line[start_position..-1]
457
+ occurrence_index = case_sensitive ? text_to_find_in&.index(find_text.to_s) : text_to_find_in&.downcase&.index(find_text.to_s.downcase)
458
+ if occurrence_index
459
+ self.caret_position = caret_position_for_line_index(the_index) + start_position + occurrence_index
460
+ self.selection_count = find_text.to_s.size
461
+ return
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ def find_previous
468
+ return if find_text.to_s.empty?
469
+ all_lines = lines
470
+ the_line_index = line_index_for_caret_position(caret_position)
471
+ line_position = line_position_for_caret_position(caret_position)
472
+ 2.times do |i|
473
+ rotation = - the_line_index - 1 + all_lines.size
474
+ all_lines.reverse.rotate(rotation).each_with_index do |the_line, the_index|
475
+ the_index = all_lines.size - 1 - (the_index + rotation)%all_lines.size
476
+ if the_index == the_line_index
477
+ start_position = i > 0 ? 0 : (the_line.size - line_position)
478
+ else
479
+ start_position = 0
480
+ end
481
+ text_to_find_in = the_line.downcase.reverse[start_position...the_line.size].to_s
482
+ occurrence_index = text_to_find_in.index(find_text.to_s.downcase.reverse)
483
+ if occurrence_index
484
+ self.caret_position = caret_position_for_line_index(the_index) + (the_line.size - (start_position + occurrence_index + find_text.to_s.size))
485
+ self.selection_count = find_text.to_s.size
486
+ return
487
+ end
488
+ end
489
+ end
490
+ end
491
+
492
+ def ensure_find_next
493
+ return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
494
+ find_next unless found_text?(self.caret_position)
495
+ end
496
+
497
+ def found_text?(caret_position)
498
+ dirty_content[caret_position.to_i, find_text.to_s.size].to_s.downcase == find_text.to_s.downcase
499
+ end
500
+
501
+ def replace_next!
502
+ return if find_text.to_s.empty? || dirty_content.to_s.strip.size < 1
503
+ ensure_find_next
504
+ new_dirty_content = dirty_content
505
+ new_dirty_content[caret_position, find_text.size] = replace_text.to_s
506
+ self.dirty_content = new_dirty_content
507
+ find_next
508
+ find_next if replace_text.to_s.include?(find_text) && !replace_text.to_s.start_with?(find_text)
509
+ end
510
+
511
+ def page_up
512
+ self.selection_count = 0
513
+ self.line_number = [(self.line_number - 15), 1].max
514
+ end
515
+
516
+ def page_down
517
+ self.selection_count = 0
518
+ self.line_number = [(self.line_number + 15), lines.size].min
519
+ end
520
+
521
+ def home
522
+ self.selection_count = 0
523
+ self.line_number = 1
524
+ end
525
+
526
+ def end
527
+ self.selection_count = 0
528
+ self.line_number = lines.size
529
+ end
530
+
531
+ def start_of_line
532
+ self.caret_position = caret_position_for_line_index(self.line_number - 1)
533
+ end
534
+
535
+ def end_of_line
536
+ self.caret_position = caret_position_for_line_index(self.line_number) - 1
537
+ end
538
+
539
+ def move_up!
540
+ old_lines = lines
541
+ return if old_lines.size < 2
542
+ old_selection_count = self.selection_count
543
+ old_caret_position = self.caret_position
544
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
545
+ old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
546
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
547
+ new_lines = lines
548
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
549
+ the_lines = lines_for_selection(caret_position, selection_count)
550
+ new_line_index = [the_line_indices.first - 1, 0].max
551
+ new_lines[the_line_indices.first..the_line_indices.last] = []
552
+ new_lines[new_line_index...new_line_index] = the_lines
553
+ self.dirty_content = new_lines.join("\n")
554
+ self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
555
+ self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
556
+ end
557
+
558
+ def move_down!
559
+ old_lines = lines
560
+ return if old_lines.size < 2
561
+ old_selection_count = self.selection_count
562
+ old_caret_position = self.caret_position
563
+ old_caret_position_line_caret_position = caret_position_for_caret_position_start_of_line(old_caret_position)
564
+ old_caret_position_line_position = old_caret_position - old_caret_position_line_caret_position
565
+ old_end_caret_line_index = end_caret_position_line_index(caret_position, selection_count)
566
+ new_lines = lines
567
+ the_line_indices = line_indices_for_selection(caret_position, selection_count)
568
+ the_lines = lines_for_selection(caret_position, selection_count)
569
+ new_line_index = [the_line_indices.first + 1, new_lines.size - 1].min
570
+ new_lines[the_line_indices.first..the_line_indices.last] = []
571
+ new_lines[new_line_index...new_line_index] = the_lines
572
+ self.dirty_content = new_lines.join("\n")
573
+ self.caret_position = caret_position_for_line_index(new_line_index) + [old_caret_position_line_position, new_lines[new_line_index].size].min
574
+ self.selection_count = old_selection_count.to_i if old_selection_count.to_i > 0
575
+ end
576
+
577
+ def run
578
+ if scratchpad?
579
+ eval content
580
+ else
581
+ write_dirty_content
582
+ load path
583
+ end
584
+ end
585
+
586
+ def lines
587
+ need_padding = dirty_content.to_s.end_with?("\n")
588
+ splittable_content = need_padding ? "#{dirty_content} " : dirty_content
589
+ the_lines = splittable_content.split("\n")
590
+ the_lines[-1] = the_lines[-1].strip if need_padding
591
+ the_lines
592
+ end
593
+
594
+ def line_for_caret_position(caret_position)
595
+ lines[line_index_for_caret_position(caret_position.to_i)]
596
+ end
597
+
598
+ def line_index_for_caret_position(caret_position)
599
+ dirty_content[0...caret_position.to_i].count("\n")
600
+ end
601
+
602
+ def caret_position_for_line_index(line_index)
603
+ cp = lines[0...line_index].join("\n").size
604
+ cp += 1 if line_index > 0
605
+ cp
606
+ end
607
+
608
+ def caret_position_for_caret_position_start_of_line(caret_position)
609
+ caret_position_for_line_index(line_index_for_caret_position(caret_position))
610
+ end
611
+
612
+ # 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)
613
+ # TODO consider renaming to line_character_position_for_caret_position
614
+ def line_position_for_caret_position(caret_position)
615
+ caret_position = caret_position.to_i
616
+ caret_position - caret_position_for_caret_position_start_of_line(caret_position)
617
+ end
618
+
619
+ def line_caret_positions_for_selection(caret_position, selection_count)
620
+ line_indices = line_indices_for_selection(caret_position, selection_count)
621
+ line_caret_positions = line_indices.map { |line_index| caret_position_for_line_index(line_index) }.to_a
622
+ end
623
+
624
+ def end_caret_position_line_index(caret_position, selection_count)
625
+ end_caret_position = caret_position + selection_count.to_i
626
+ end_caret_position -= 1 if dirty_content[end_caret_position - 1] == "\n"
627
+ end_line_index = line_index_for_caret_position(end_caret_position)
628
+ end
629
+
630
+ def lines_for_selection(caret_position, selection_count)
631
+ line_indices = line_indices_for_selection(caret_position, selection_count)
632
+ lines[line_indices.first..line_indices.last]
633
+ end
634
+
635
+ def line_indices_for_selection(caret_position, selection_count)
636
+ start_line_index = line_index_for_caret_position(caret_position)
637
+ if selection_count.to_i > 0
638
+ end_line_index = end_caret_position_line_index(caret_position, selection_count)
639
+ else
640
+ end_line_index = start_line_index
641
+ end
642
+ (start_line_index..end_line_index).to_a
643
+ end
644
+
645
+ def children
646
+ []
647
+ end
648
+
649
+ def to_s
650
+ path
651
+ end
652
+
653
+ def eql?(other)
654
+ self.path.eql?(other&.path)
655
+ end
656
+
657
+ def hash
658
+ self.path.hash
659
+ end
660
+ end
661
+ end
662
+ end