vimamsa 0.1.0 → 0.1.5
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 +4 -4
- data/.vma_project +0 -0
- data/README.md +110 -16
- data/exe/vimamsa +40 -0
- data/ext/vmaext/vmaext.c +0 -3
- data/lang/hyperplaintext.lang +129 -0
- data/lib/vimamsa.rb +3 -1
- data/lib/vimamsa/ack.rb +35 -0
- data/lib/vimamsa/actions.rb +125 -0
- data/lib/vimamsa/buffer.rb +1760 -0
- data/lib/vimamsa/buffer_list.rb +207 -0
- data/lib/vimamsa/constants.rb +44 -0
- data/lib/vimamsa/debug.rb +142 -0
- data/lib/vimamsa/default_key_bindings.rb +448 -0
- data/lib/vimamsa/easy_jump.rb +161 -0
- data/lib/vimamsa/editor.rb +667 -0
- data/lib/vimamsa/encrypt.rb +47 -0
- data/lib/vimamsa/file_finder.rb +103 -0
- data/lib/vimamsa/file_history.rb +100 -0
- data/lib/vimamsa/file_manager.rb +144 -0
- data/lib/vimamsa/hook.rb +46 -0
- data/lib/vimamsa/hyper_plain_text.rb +61 -0
- data/lib/vimamsa/key_binding_tree.rb +603 -0
- data/lib/vimamsa/macro.rb +177 -0
- data/lib/vimamsa/main.rb +71 -0
- data/lib/vimamsa/rbvma.rb +1072 -0
- data/lib/vimamsa/search.rb +100 -0
- data/lib/vimamsa/search_replace.rb +333 -0
- data/lib/vimamsa/text_transforms.rb +32 -0
- data/lib/vimamsa/util.rb +101 -0
- data/lib/vimamsa/version.rb +1 -1
- data/styles/134272-molokai.xml +33 -0
- data/styles/dark.xml +152 -0
- data/styles/molokai_edit.xml +49 -0
- data/vimamsa.gemspec +4 -2
- metadata +66 -10
- data/ext/vimamsa/extconf.rb +0 -11
- data/ext/vimamsa/vimamsa.c +0 -174
@@ -0,0 +1,1760 @@
|
|
1
|
+
require "digest"
|
2
|
+
require "tempfile"
|
3
|
+
require "pathname"
|
4
|
+
require "openssl"
|
5
|
+
require "ripl/multi_line"
|
6
|
+
|
7
|
+
$paste_lines = false
|
8
|
+
$buffer_history = [0]
|
9
|
+
|
10
|
+
$update_highlight = false
|
11
|
+
|
12
|
+
class Buffer < String
|
13
|
+
|
14
|
+
#attr_reader (:pos, :cpos, :lpos)
|
15
|
+
|
16
|
+
attr_reader :pos, :lpos, :cpos, :deltas, :edit_history, :fname, :call_func, :pathname, :basename, :update_highlight, :marks, :is_highlighted, :syntax_detect_failed, :id, :lang
|
17
|
+
attr_writer :call_func, :update_highlight
|
18
|
+
attr_accessor :qt_update_highlight, :update_hl_startpos, :update_hl_endpos, :hl_queue, :syntax_parser, :highlights, :qt_reset_highlight, :is_parsing_syntax, :line_ends, :bt, :line_action_handler, :module, :active_kbd_mode, :title, :subtitle
|
19
|
+
|
20
|
+
@@num_buffers = 0
|
21
|
+
|
22
|
+
def initialize(str = "\n", fname = nil)
|
23
|
+
debug "Buffer.rb: def initialize"
|
24
|
+
super(str)
|
25
|
+
|
26
|
+
@lang = nil
|
27
|
+
@id = @@num_buffers
|
28
|
+
@@num_buffers += 1
|
29
|
+
qt_create_buffer(@id)
|
30
|
+
puts "NEW BUFFER fn=#{fname} ID:#{@id}"
|
31
|
+
|
32
|
+
@module = nil
|
33
|
+
|
34
|
+
@crypt = nil
|
35
|
+
@update_highlight = true
|
36
|
+
@syntax_detect_failed = false
|
37
|
+
@is_parsing_syntax = false
|
38
|
+
@last_update = Time.now - 100
|
39
|
+
@highlights = {}
|
40
|
+
if fname != nil
|
41
|
+
@fname = File.expand_path(fname)
|
42
|
+
detect_file_language()
|
43
|
+
else
|
44
|
+
@fname = fname
|
45
|
+
end
|
46
|
+
@hl_queue = []
|
47
|
+
@line_action_handler = nil
|
48
|
+
|
49
|
+
@dirname = nil
|
50
|
+
@title = "*buf-#{@id}*"
|
51
|
+
@subtitle = ""
|
52
|
+
|
53
|
+
if @fname
|
54
|
+
@title = File.basename(@fname)
|
55
|
+
@dirname = File.dirname(@fname)
|
56
|
+
userhome = File.expand_path("~")
|
57
|
+
@subtitle = @dirname.gsub(/^#{userhome}/, "~")
|
58
|
+
end
|
59
|
+
|
60
|
+
t1 = Time.now
|
61
|
+
qt_set_current_buffer(@id)
|
62
|
+
gui_set_window_title(@title, @subtitle)
|
63
|
+
|
64
|
+
set_content(str)
|
65
|
+
debug "init time:#{Time.now - t1}"
|
66
|
+
|
67
|
+
# TODO: add \n when chars are added after last \n
|
68
|
+
self << "\n" if self[-1] != "\n"
|
69
|
+
@current_word = nil
|
70
|
+
@active_kbd_mode = nil
|
71
|
+
end
|
72
|
+
|
73
|
+
def set_active
|
74
|
+
if !@active_kbd_mode.nil?
|
75
|
+
$kbd.set_mode(@active_kbd_mode)
|
76
|
+
else
|
77
|
+
$kbd.set_mode_to_default
|
78
|
+
end
|
79
|
+
# qt_set_current_buffer(@id)
|
80
|
+
end
|
81
|
+
|
82
|
+
def detect_file_language
|
83
|
+
@lang = nil
|
84
|
+
@lang = "c" if @fname.match(/\.(c|h|cpp)$/)
|
85
|
+
@lang = "java" if @fname.match(/\.(java)$/)
|
86
|
+
@lang = "ruby" if @fname.match(/\.(rb)$/)
|
87
|
+
@lang = "hyperplaintext" if @fname.match(/\.(txt)$/)
|
88
|
+
@lang = "php" if @fname.match(/\.(php)$/)
|
89
|
+
|
90
|
+
lm = GtkSource::LanguageManager.new
|
91
|
+
|
92
|
+
lm.set_search_path(lm.search_path << ppath("lang/"))
|
93
|
+
lang = lm.guess_language(@fname)
|
94
|
+
# lang.get_metadata("line-comment-start")
|
95
|
+
# lang.get_metadata("block-comment-start")
|
96
|
+
# lang.get_metadata("block-comment-end")
|
97
|
+
@lang_nfo = lang
|
98
|
+
if !lang.nil? and !lang.id.nil?
|
99
|
+
puts "Guessed LANG: #{lang.id}"
|
100
|
+
@lang = lang.id
|
101
|
+
end
|
102
|
+
|
103
|
+
if @lang
|
104
|
+
gui_set_file_lang(@id, @lang)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def add_image(imgpath, pos)
|
109
|
+
return if !is_legal_pos(pos)
|
110
|
+
# insert_txt_at(" ", pos)
|
111
|
+
qt_process_deltas
|
112
|
+
qt_add_image(imgpath, pos)
|
113
|
+
end
|
114
|
+
|
115
|
+
def is_legal_pos(pos, op = :read)
|
116
|
+
return false if pos < 0
|
117
|
+
if op == :add
|
118
|
+
return false if pos > self.size
|
119
|
+
elsif op == :read
|
120
|
+
return false if pos >= self.size
|
121
|
+
end
|
122
|
+
return true
|
123
|
+
end
|
124
|
+
|
125
|
+
def set_encrypted(password)
|
126
|
+
@crypt = Encrypt.new(password)
|
127
|
+
message("Set buffer encrypted")
|
128
|
+
end
|
129
|
+
|
130
|
+
def set_unencrypted()
|
131
|
+
@crypt = nil
|
132
|
+
end
|
133
|
+
|
134
|
+
def add_new_line(txt)
|
135
|
+
# buf.jump(END_OF_LINE);buf.insert_txt("\n");
|
136
|
+
end
|
137
|
+
|
138
|
+
def insert_image_after_current_line(fname)
|
139
|
+
lr = current_line_range()
|
140
|
+
a = "⟦img:#{fname}⟧\n"
|
141
|
+
b = " \n"
|
142
|
+
txt = a + b
|
143
|
+
insert_txt_at(txt, lr.end + 1)
|
144
|
+
qt_process_deltas
|
145
|
+
imgpos = lr.end + 1 + a.size
|
146
|
+
add_image(fname, imgpos)
|
147
|
+
end
|
148
|
+
|
149
|
+
def handle_drag_and_drop(fname)
|
150
|
+
debug "[buffer] Dropped file: #{fname}"
|
151
|
+
if is_image_file(fname)
|
152
|
+
debug "Dropped image file"
|
153
|
+
insert_image_after_current_line(fname)
|
154
|
+
elsif vma.can_open_extension?(fname)
|
155
|
+
debug "Dropped text file"
|
156
|
+
open_new_file(fname)
|
157
|
+
else
|
158
|
+
debug "Dropped unknown file format"
|
159
|
+
end
|
160
|
+
# add_image(imgpath, pos)
|
161
|
+
end
|
162
|
+
|
163
|
+
def get_file_type()
|
164
|
+
return @lang
|
165
|
+
end
|
166
|
+
|
167
|
+
def revert()
|
168
|
+
return if !@fname
|
169
|
+
return if !File.exists?(@fname)
|
170
|
+
message("Revert buffer #{@fname}")
|
171
|
+
str = read_file("", @fname)
|
172
|
+
self.set_content(str)
|
173
|
+
end
|
174
|
+
|
175
|
+
def decrypt(password)
|
176
|
+
begin
|
177
|
+
@crypt = Encrypt.new(password)
|
178
|
+
str = @crypt.decrypt(@encrypted_str)
|
179
|
+
rescue OpenSSL::Cipher::CipherError => e
|
180
|
+
str = "incorrect password"
|
181
|
+
end
|
182
|
+
self.set_content(str)
|
183
|
+
end
|
184
|
+
|
185
|
+
def sanitycheck_btree()
|
186
|
+
# lines = self.split("\n")
|
187
|
+
|
188
|
+
lines = self.scan(/([^\n]*\n)/).flatten
|
189
|
+
|
190
|
+
i = 0
|
191
|
+
ok = true
|
192
|
+
@bt.each_line { |r|
|
193
|
+
if lines[i] != r #or true
|
194
|
+
puts "NO MATCH FOR LINE:"
|
195
|
+
puts "i=#{i}["
|
196
|
+
# puts "[orig]pos=#{leaf.pos} |#{leaf.data}|"
|
197
|
+
# puts "spos=#{spos} nchar=#{leaf.nchar} epos=#{epos} a[]=\nr=|#{r}|"
|
198
|
+
puts "fromtree:|#{r}|"
|
199
|
+
puts "frombuf:|#{lines[i]}"
|
200
|
+
puts "]"
|
201
|
+
ok = false
|
202
|
+
end
|
203
|
+
i += 1
|
204
|
+
}
|
205
|
+
|
206
|
+
puts "BT: NO ERRORS" if ok
|
207
|
+
puts "BT: ERRORS" if !ok
|
208
|
+
end
|
209
|
+
|
210
|
+
def set_content(str)
|
211
|
+
@encrypted_str = nil
|
212
|
+
@qt_update_highlight = true
|
213
|
+
@ftype = nil
|
214
|
+
if str[0..10] == "VMACRYPT001"
|
215
|
+
@encrypted_str = str[11..-1]
|
216
|
+
callback = proc { |x| decrypt_cur_buffer(x) }
|
217
|
+
gui_one_input_action("Decrypt", "Password:", "decrypt", callback)
|
218
|
+
str = "ENCRYPTED"
|
219
|
+
else
|
220
|
+
# @crypt = nil
|
221
|
+
end
|
222
|
+
|
223
|
+
if (str[-1] != "\n")
|
224
|
+
str << "\n"
|
225
|
+
end
|
226
|
+
|
227
|
+
self.replace(str)
|
228
|
+
@line_ends = scan_indexes(self, /\n/)
|
229
|
+
|
230
|
+
# @bt = BufferTree.new(str)
|
231
|
+
if $experimental
|
232
|
+
@bt = BufferTree.new(self)
|
233
|
+
if $debug
|
234
|
+
sanitycheck_btree()
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
@last_update = Time.now - 10
|
239
|
+
debug("line_ends")
|
240
|
+
@marks = Hash.new
|
241
|
+
@basename = ""
|
242
|
+
@pathname = Pathname.new(fname) if @fname
|
243
|
+
@basename = @pathname.basename if @fname
|
244
|
+
@pos = 0 # Position in whole file
|
245
|
+
@cpos = 0 # Column position on current line
|
246
|
+
@lpos = 0 # Number of current line
|
247
|
+
@edit_version = 0 # +1 for every buffer modification
|
248
|
+
@larger_cpos = 0 # Store @cpos when move up/down to shorter line than @cpos
|
249
|
+
@need_redraw = 1
|
250
|
+
@call_func = nil
|
251
|
+
@deltas = []
|
252
|
+
@edit_history = []
|
253
|
+
@redo_stack = []
|
254
|
+
@edit_pos_history = []
|
255
|
+
@edit_pos_history_i = 0
|
256
|
+
# @highlights = {}
|
257
|
+
@highlights.delete_if { |x| true }
|
258
|
+
|
259
|
+
@syntax_parser = nil
|
260
|
+
|
261
|
+
@is_highlighted = false
|
262
|
+
@update_highlight = true
|
263
|
+
@update_hl_startpos = 0 #TODO
|
264
|
+
@update_hl_endpos = self.size - 1
|
265
|
+
|
266
|
+
qt_set_buffer_contents(@id, self.to_s)
|
267
|
+
|
268
|
+
# add_hl_update(@update_hl_startpos, @update_hl_endpos)
|
269
|
+
end
|
270
|
+
|
271
|
+
def set_filename(filename)
|
272
|
+
@fname = filename
|
273
|
+
@pathname = Pathname.new(fname) if @fname
|
274
|
+
@basename = @pathname.basename if @fname
|
275
|
+
detect_file_language
|
276
|
+
end
|
277
|
+
|
278
|
+
def get_short_path()
|
279
|
+
fpath = self.fname
|
280
|
+
if fpath.size > 50
|
281
|
+
fpath = fpath[-50..-1]
|
282
|
+
end
|
283
|
+
return fpath
|
284
|
+
end
|
285
|
+
|
286
|
+
def line(lpos)
|
287
|
+
if @line_ends.size == 0
|
288
|
+
return self
|
289
|
+
end
|
290
|
+
|
291
|
+
#TODO: implement using line_range()
|
292
|
+
if lpos >= @line_ends.size
|
293
|
+
debug("lpos too large") #TODO
|
294
|
+
return ""
|
295
|
+
elsif lpos == @line_ends.size
|
296
|
+
end
|
297
|
+
start = @line_ends[lpos - 1] + 1 if lpos > 0
|
298
|
+
start = 0 if lpos == 0
|
299
|
+
_end = @line_ends[lpos]
|
300
|
+
debug "start: _#{start}, end: #{_end}"
|
301
|
+
return self[start.._end]
|
302
|
+
end
|
303
|
+
|
304
|
+
def is_delta_ok(delta)
|
305
|
+
ret = true
|
306
|
+
pos = delta[0]
|
307
|
+
if pos < 0
|
308
|
+
ret = false
|
309
|
+
debug "pos=#{pos} < 0"
|
310
|
+
elsif pos > self.size
|
311
|
+
debug "pos=#{pos} > self.size=#{self.size}"
|
312
|
+
ret = false
|
313
|
+
end
|
314
|
+
if ret == false
|
315
|
+
# crash("DELTA OK=#{ret}")
|
316
|
+
end
|
317
|
+
return ret
|
318
|
+
end
|
319
|
+
|
320
|
+
#TODO: change to apply=true as default
|
321
|
+
def add_delta(delta, apply = false, auto_update_cpos = false)
|
322
|
+
return if !is_delta_ok(delta)
|
323
|
+
if delta[1] == DELETE
|
324
|
+
return if delta[0] >= self.size
|
325
|
+
# If go over length of buffer
|
326
|
+
if delta[0] + delta[2] >= self.size
|
327
|
+
delta[2] = self.size - delta[0]
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
@edit_version += 1
|
332
|
+
@redo_stack = []
|
333
|
+
if apply
|
334
|
+
delta = run_delta(delta, auto_update_cpos)
|
335
|
+
else
|
336
|
+
@deltas << delta
|
337
|
+
end
|
338
|
+
@edit_history << delta
|
339
|
+
if self[-1] != "\n"
|
340
|
+
add_delta([self.size, INSERT, 1, "\n"], true)
|
341
|
+
end
|
342
|
+
reset_larger_cpos #TODO: correct here?
|
343
|
+
end
|
344
|
+
|
345
|
+
def add_hl_update(startpos, endpos)
|
346
|
+
return if @is_highlighted == false
|
347
|
+
|
348
|
+
debug "@update_hl_endpos = #{endpos}"
|
349
|
+
@hl_queue << [startpos, endpos]
|
350
|
+
end
|
351
|
+
|
352
|
+
def run_delta(delta, auto_update_cpos = false)
|
353
|
+
# auto_update_cpos: In some cases position of cursor should be updated automatically based on change to buffer (delta). In other cases this is handled by the action that creates the delta.
|
354
|
+
|
355
|
+
if $experimental
|
356
|
+
@bt.handle_delta(Delta.new(delta[0], delta[1], delta[2], delta[3]))
|
357
|
+
end
|
358
|
+
pos = delta[0]
|
359
|
+
if @edit_pos_history.any? and (@edit_pos_history.last - pos).abs <= 2
|
360
|
+
@edit_pos_history.pop
|
361
|
+
end
|
362
|
+
|
363
|
+
lsp = get_line_start(pos)
|
364
|
+
|
365
|
+
if @edit_pos_history[-1] != lsp
|
366
|
+
@edit_pos_history << lsp
|
367
|
+
end
|
368
|
+
@edit_pos_history_i = 0
|
369
|
+
|
370
|
+
if delta[1] == DELETE
|
371
|
+
delta[3] = self.slice!(delta[0], delta[2])
|
372
|
+
@deltas << delta
|
373
|
+
update_index(pos, -delta[2])
|
374
|
+
update_line_ends(pos, -delta[2], delta[3])
|
375
|
+
update_highlights(pos, -delta[2], delta[3])
|
376
|
+
update_cursor_pos(pos, -delta[2]) if auto_update_cpos
|
377
|
+
|
378
|
+
@update_hl_startpos = pos - delta[2]
|
379
|
+
@update_hl_endpos = pos
|
380
|
+
add_hl_update(@update_hl_startpos, @update_hl_endpos)
|
381
|
+
elsif delta[1] == INSERT
|
382
|
+
self.insert(delta[0], delta[3])
|
383
|
+
@deltas << delta
|
384
|
+
debug [pos, +delta[2]].inspect
|
385
|
+
update_index(pos, +delta[2])
|
386
|
+
update_cursor_pos(pos, +delta[2]) if auto_update_cpos
|
387
|
+
update_line_ends(pos, +delta[2], delta[3])
|
388
|
+
update_highlights(pos, +delta[2], delta[3])
|
389
|
+
|
390
|
+
@update_hl_startpos = pos
|
391
|
+
@update_hl_endpos = pos + delta[2]
|
392
|
+
add_hl_update(@update_hl_startpos, @update_hl_endpos)
|
393
|
+
end
|
394
|
+
debug "DELTA=#{delta.inspect}"
|
395
|
+
# sanity_check_line_ends #TODO: enable with debug mode
|
396
|
+
#highlight_c()
|
397
|
+
|
398
|
+
$update_highlight = true
|
399
|
+
@update_highlight = true
|
400
|
+
|
401
|
+
return delta
|
402
|
+
end
|
403
|
+
|
404
|
+
# Update cursor position after change in buffer contents.
|
405
|
+
# e.g. after processing with external command like indenter
|
406
|
+
def update_cursor_pos(pos, changeamount)
|
407
|
+
if @pos > pos + 1 && changeamount > 0
|
408
|
+
# @pos is after addition
|
409
|
+
set_pos(@pos + changeamount)
|
410
|
+
elsif @pos > pos && changeamount < 0 && @pos < pos - changeamount
|
411
|
+
# @pos is between removal
|
412
|
+
set_pos(pos)
|
413
|
+
elsif @pos > pos && changeamount < 0 && @pos >= pos - changeamount
|
414
|
+
# @pos is after removal
|
415
|
+
set_pos(@pos + changeamount)
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
def update_index(pos, changeamount)
|
420
|
+
# puts "pos #{pos}, changeamount #{changeamount}, @pos #{@pos}"
|
421
|
+
@edit_pos_history.collect! { |x| r = x if x <= pos; r = x + changeamount if x > pos; r }
|
422
|
+
# TODO: handle between removal case
|
423
|
+
for k in @marks.keys
|
424
|
+
# puts "change(?): pos=#{pos}, k=#{k}, #{@marks[k]}, #{changeamount}"
|
425
|
+
if @marks[k] > pos
|
426
|
+
@marks[k] = @marks[k] + changeamount
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
|
431
|
+
def jump_to_last_edit()
|
432
|
+
return if @edit_pos_history.empty?
|
433
|
+
@edit_pos_history_i += 1
|
434
|
+
|
435
|
+
if @edit_pos_history_i > @edit_pos_history.size
|
436
|
+
@edit_pos_history_i = 0
|
437
|
+
end
|
438
|
+
|
439
|
+
# if @edit_pos_history.size >= @edit_pos_history_i
|
440
|
+
set_pos(@edit_pos_history[-@edit_pos_history_i])
|
441
|
+
center_on_current_line
|
442
|
+
return true
|
443
|
+
# end
|
444
|
+
end
|
445
|
+
|
446
|
+
def jump_to_next_edit()
|
447
|
+
return if @edit_pos_history.empty?
|
448
|
+
@edit_pos_history_i -= 1
|
449
|
+
@edit_pos_history_i = @edit_pos_history.size - 1 if @edit_pos_history_i < 0
|
450
|
+
# Ripl.start :binding => binding
|
451
|
+
debug "@edit_pos_history_i=#{@edit_pos_history_i}"
|
452
|
+
set_pos(@edit_pos_history[-@edit_pos_history_i])
|
453
|
+
center_on_current_line
|
454
|
+
return true
|
455
|
+
end
|
456
|
+
|
457
|
+
def jump_to_random_pos()
|
458
|
+
set_pos(rand(self.size))
|
459
|
+
end
|
460
|
+
|
461
|
+
def undo()
|
462
|
+
debug @edit_history.inspect
|
463
|
+
return if !@edit_history.any?
|
464
|
+
last_delta = @edit_history.pop
|
465
|
+
@redo_stack << last_delta
|
466
|
+
debug last_delta.inspect
|
467
|
+
if last_delta[1] == DELETE
|
468
|
+
d = [last_delta[0], INSERT, 0, last_delta[3]]
|
469
|
+
run_delta(d)
|
470
|
+
elsif last_delta[1] == INSERT
|
471
|
+
d = [last_delta[0], DELETE, last_delta[3].size]
|
472
|
+
run_delta(d)
|
473
|
+
else
|
474
|
+
return #TODO: assert?
|
475
|
+
end
|
476
|
+
set_pos(last_delta[0])
|
477
|
+
#recalc_line_ends #TODO: optimize?
|
478
|
+
calculate_line_and_column_pos
|
479
|
+
end
|
480
|
+
|
481
|
+
def redo()
|
482
|
+
return if !@redo_stack.any?
|
483
|
+
#last_delta = @edit_history[-1].pop
|
484
|
+
redo_delta = @redo_stack.pop
|
485
|
+
#printf("==== UNDO ====\n")
|
486
|
+
debug redo_delta.inspect
|
487
|
+
run_delta(redo_delta)
|
488
|
+
@edit_history << redo_delta
|
489
|
+
set_pos(redo_delta[0])
|
490
|
+
#recalc_line_ends #TODO: optimize?
|
491
|
+
calculate_line_and_column_pos
|
492
|
+
end
|
493
|
+
|
494
|
+
def current_char()
|
495
|
+
return self[@pos]
|
496
|
+
end
|
497
|
+
|
498
|
+
def current_line()
|
499
|
+
range = line_range(@lpos, 1)
|
500
|
+
return self[range]
|
501
|
+
end
|
502
|
+
|
503
|
+
def get_com_str()
|
504
|
+
# return nil if @syntax_detect_failed
|
505
|
+
|
506
|
+
com_str = nil
|
507
|
+
# if get_file_type() == "c" or get_file_type() == "java"
|
508
|
+
# com_str = "//"
|
509
|
+
# elsif get_file_type() == "ruby"
|
510
|
+
# com_str = "#"
|
511
|
+
# else
|
512
|
+
# com_str = "//"
|
513
|
+
# end
|
514
|
+
|
515
|
+
if !@lang_nfo.nil?
|
516
|
+
com_str = @lang_nfo.get_metadata("line-comment-start")
|
517
|
+
end
|
518
|
+
|
519
|
+
# lang.get_metadata("block-comment-start")
|
520
|
+
# lang.get_metadata("block-comment-end")
|
521
|
+
|
522
|
+
com_str = "//" if com_str.nil?
|
523
|
+
|
524
|
+
return com_str
|
525
|
+
end
|
526
|
+
|
527
|
+
def comment_linerange(r)
|
528
|
+
com_str = get_com_str()
|
529
|
+
#lines = $buffer[r].split(/(\n)/).each_slice(2).map { |x| x[0] }
|
530
|
+
lines = $buffer[r].lines
|
531
|
+
mod = ""
|
532
|
+
lines.each { |line|
|
533
|
+
m = line.match(/^(\s*)(\S.*)/)
|
534
|
+
if m == nil or m[2].size == 0
|
535
|
+
ret = line
|
536
|
+
elsif m[2].size > 0
|
537
|
+
ret = "#{m[1]}#{com_str} #{m[2]}\n"
|
538
|
+
end
|
539
|
+
mod << ret
|
540
|
+
}
|
541
|
+
replace_range(r, mod)
|
542
|
+
end
|
543
|
+
|
544
|
+
def get_line_start(pos)
|
545
|
+
|
546
|
+
# Bsearch: https://www.rubydoc.info/stdlib/core/Array#bsearch-instance_method
|
547
|
+
# In find-minimum mode (this is a good choice for typical use case), the block must return true or false, and there must be an index i (0 <= i <= ary.size) so that:
|
548
|
+
# the block returns false for any element whose index is less than i, and
|
549
|
+
# the block returns true for any element whose index is greater than or equal to i.
|
550
|
+
# This method returns the i-th element. If i is equal to ary.size, it returns nil.
|
551
|
+
|
552
|
+
# (OLD) slower version:
|
553
|
+
# ls = @line_ends.select { |x| x < pos }.max
|
554
|
+
a = @line_ends.bsearch_index { |x| x >= pos }
|
555
|
+
|
556
|
+
a = @line_ends[-1] if a == nil
|
557
|
+
a = 0 if a == nil
|
558
|
+
if a > 0
|
559
|
+
a = a - 1
|
560
|
+
else
|
561
|
+
a = 0
|
562
|
+
end
|
563
|
+
ls = nil
|
564
|
+
ls = @line_ends[a] if a != nil
|
565
|
+
# if a != nil and ls != @line_ends[a]
|
566
|
+
# puts "NO MATCH @line_ends[a]"
|
567
|
+
# Ripl.start :binding => binding
|
568
|
+
# end
|
569
|
+
|
570
|
+
if ls == nil
|
571
|
+
ls = 0
|
572
|
+
else
|
573
|
+
ls = ls + 1
|
574
|
+
end
|
575
|
+
return ls
|
576
|
+
end
|
577
|
+
|
578
|
+
def get_line_end(pos)
|
579
|
+
#Ripl.start :binding => binding
|
580
|
+
return @line_ends.select { |x| x > pos }.min
|
581
|
+
end
|
582
|
+
|
583
|
+
def comment_selection(op = :comment)
|
584
|
+
if visual_mode?
|
585
|
+
(startpos, endpos) = get_visual_mode_range2
|
586
|
+
first = get_line_start(startpos)
|
587
|
+
# last = get_line_end(endpos)
|
588
|
+
last = get_line_end(endpos - 1)
|
589
|
+
if op == :comment
|
590
|
+
comment_linerange(first..last)
|
591
|
+
elsif op == :uncomment
|
592
|
+
uncomment_linerange(first..last)
|
593
|
+
end
|
594
|
+
$buffer.end_visual_mode
|
595
|
+
end
|
596
|
+
end
|
597
|
+
|
598
|
+
def uncomment_linerange(r)
|
599
|
+
com_str = get_com_str()
|
600
|
+
#r=$buffer.line_range($buffer.lpos, 2)
|
601
|
+
lines = $buffer[r].split(/(\n)/).each_slice(2).map { |x| x[0] }
|
602
|
+
mod = lines.collect { |x| x.sub(/^(\s*)(#{com_str}\s?)/, '\1') + "\n" }.join()
|
603
|
+
replace_range(r, mod)
|
604
|
+
end
|
605
|
+
|
606
|
+
def get_repeat_num()
|
607
|
+
$method_handles_repeat = true
|
608
|
+
repeat_num = 1
|
609
|
+
if !$next_command_count.nil? and $next_command_count > 0
|
610
|
+
repeat_num = $next_command_count
|
611
|
+
end
|
612
|
+
return repeat_num
|
613
|
+
end
|
614
|
+
|
615
|
+
def comment_line(op = :comment)
|
616
|
+
num_lines = get_repeat_num()
|
617
|
+
lrange = line_range(@lpos, num_lines)
|
618
|
+
if op == :comment
|
619
|
+
comment_linerange(lrange)
|
620
|
+
elsif op == :uncomment
|
621
|
+
uncomment_linerange(lrange)
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
def replace_range(range, text)
|
626
|
+
delete_range(range.first, range.last)
|
627
|
+
insert_txt_at(text, range.begin)
|
628
|
+
end
|
629
|
+
|
630
|
+
def current_line_range()
|
631
|
+
range = line_range(@lpos, 1)
|
632
|
+
return range
|
633
|
+
end
|
634
|
+
|
635
|
+
def line_range(start_line, num_lines, include_last_nl = true)
|
636
|
+
end_line = start_line + num_lines - 1
|
637
|
+
if end_line >= @line_ends.size
|
638
|
+
debug("lpos too large") #TODO
|
639
|
+
end_line = @line_ends.size - 1
|
640
|
+
end
|
641
|
+
start = @line_ends[start_line - 1] + 1 if start_line > 0
|
642
|
+
start = 0 if start_line == 0
|
643
|
+
if include_last_nl
|
644
|
+
_End = @line_ends[end_line]
|
645
|
+
else
|
646
|
+
_End = @line_ends[end_line] - 1
|
647
|
+
end
|
648
|
+
_End = start if _End < start
|
649
|
+
debug "line range: start=#{start}, end=#{_End}"
|
650
|
+
return start.._End
|
651
|
+
end
|
652
|
+
|
653
|
+
def copy(range_id)
|
654
|
+
$paste_lines = false
|
655
|
+
debug "range_id: #{range_id}"
|
656
|
+
debug range_id.inspect
|
657
|
+
range = get_range(range_id)
|
658
|
+
debug range.inspect
|
659
|
+
set_clipboard(self[range])
|
660
|
+
end
|
661
|
+
|
662
|
+
def recalc_line_ends()
|
663
|
+
t1 = Time.now
|
664
|
+
leo = @line_ends.clone
|
665
|
+
@line_ends = scan_indexes(self, /\n/)
|
666
|
+
if @line_ends == leo
|
667
|
+
debug "No change to line ends"
|
668
|
+
else
|
669
|
+
debug "CHANGES to line ends"
|
670
|
+
end
|
671
|
+
|
672
|
+
debug "Scan line_end time: #{Time.now - t1}"
|
673
|
+
#puts @line_ends
|
674
|
+
end
|
675
|
+
|
676
|
+
def sanity_check_line_ends()
|
677
|
+
leo = @line_ends.clone
|
678
|
+
@line_ends = scan_indexes(self, /\n/)
|
679
|
+
if @line_ends == leo
|
680
|
+
debug "No change to line ends"
|
681
|
+
else
|
682
|
+
debug "CHANGES to line ends"
|
683
|
+
debug leo.inspect
|
684
|
+
debug @line_ends.inspect
|
685
|
+
crash("CHANGES to line ends")
|
686
|
+
end
|
687
|
+
end
|
688
|
+
|
689
|
+
def update_bufpos_on_change(positions, xpos, changeamount)
|
690
|
+
# puts "xpos=#{xpos} changeamount=#{changeamount}"
|
691
|
+
positions.collect { |x|
|
692
|
+
r = nil
|
693
|
+
r = x if x < xpos
|
694
|
+
r = x + changeamount if changeamount < 0 && x + changeamount >= xpos
|
695
|
+
r = x + changeamount if changeamount > 0 && x >= xpos
|
696
|
+
r
|
697
|
+
}
|
698
|
+
end
|
699
|
+
|
700
|
+
def update_highlights(pos, changeamount, changestr)
|
701
|
+
return if !self.is_highlighted # $cnf[:syntax_highlight]
|
702
|
+
lpos, cpos = get_line_and_col_pos(pos)
|
703
|
+
if @highlights and @highlights[lpos]
|
704
|
+
hl = @highlights[lpos]
|
705
|
+
hls = hl.collect { |x| x[0] } # highlight range start
|
706
|
+
hle = hl.collect { |x| x[1] } # highlight range end
|
707
|
+
hls2 = update_bufpos_on_change(hls, cpos, changeamount)
|
708
|
+
hle2 = update_bufpos_on_change(hle, cpos, changeamount)
|
709
|
+
hlnew = []
|
710
|
+
for i in hle.size.times
|
711
|
+
if hls2[i] != nil and hle2[i] != nil
|
712
|
+
hlnew << [hls2[i], hle2[i], hl[i][2]]
|
713
|
+
end
|
714
|
+
end
|
715
|
+
@highlights[lpos] = hlnew
|
716
|
+
end
|
717
|
+
end
|
718
|
+
|
719
|
+
def update_line_ends(pos, changeamount, changestr)
|
720
|
+
if changeamount > -1
|
721
|
+
changeamount = changestr.size
|
722
|
+
i_nl = scan_indexes(changestr, /\n/)
|
723
|
+
i_nl.collect! { |x| x + pos }
|
724
|
+
end
|
725
|
+
# puts "change:#{changeamount}"
|
726
|
+
#TODO: this is the bottle neck in insert_txt action
|
727
|
+
@line_ends.collect! { |x|
|
728
|
+
r = nil
|
729
|
+
r = x if x < pos
|
730
|
+
r = x + changeamount if changeamount < 0 && x + changeamount >= pos
|
731
|
+
r = x + changeamount if changeamount > 0 && x >= pos
|
732
|
+
r
|
733
|
+
}.compact!
|
734
|
+
|
735
|
+
if changeamount > -1 && i_nl.size > 0
|
736
|
+
@line_ends.concat(i_nl)
|
737
|
+
@line_ends.sort!
|
738
|
+
end
|
739
|
+
end
|
740
|
+
|
741
|
+
def at_end_of_line?()
|
742
|
+
return (self[@pos] == "\n" or at_end_of_buffer?)
|
743
|
+
end
|
744
|
+
|
745
|
+
def at_end_of_buffer?()
|
746
|
+
return @pos == self.size
|
747
|
+
end
|
748
|
+
|
749
|
+
def set_pos(new_pos)
|
750
|
+
if new_pos >= self.size
|
751
|
+
@pos = self.size - 1 # TODO:??right side of last char
|
752
|
+
elsif new_pos >= 0
|
753
|
+
@pos = new_pos
|
754
|
+
end
|
755
|
+
qt_set_cursor_pos(@id, @pos)
|
756
|
+
calculate_line_and_column_pos
|
757
|
+
end
|
758
|
+
|
759
|
+
# Get the line number of character position
|
760
|
+
def get_line_pos(pos)
|
761
|
+
lpos = @line_ends.bsearch_index { |x, _| x >= pos }
|
762
|
+
return lpos
|
763
|
+
end
|
764
|
+
|
765
|
+
# Calculate the two dimensional column and line positions based on
|
766
|
+
# (one dimensional) position in the buffer.
|
767
|
+
def get_line_and_col_pos(pos)
|
768
|
+
pos = self.size if pos > self.size
|
769
|
+
pos = 0 if pos < 0
|
770
|
+
|
771
|
+
lpos = get_line_pos(pos)
|
772
|
+
|
773
|
+
lpos = @line_ends.size if lpos == nil
|
774
|
+
cpos = pos
|
775
|
+
cpos -= @line_ends[lpos - 1] + 1 if lpos > 0
|
776
|
+
|
777
|
+
return [lpos, cpos]
|
778
|
+
end
|
779
|
+
|
780
|
+
def calculate_line_and_column_pos(reset = true)
|
781
|
+
@lpos, @cpos = get_line_and_col_pos(@pos)
|
782
|
+
reset_larger_cpos if reset
|
783
|
+
end
|
784
|
+
|
785
|
+
def set_line_and_column_pos(lpos, cpos, _reset_larger_cpos = true)
|
786
|
+
@lpos = lpos if !lpos.nil?
|
787
|
+
@cpos = cpos if !cpos.nil?
|
788
|
+
if @lpos > 0
|
789
|
+
new_pos = @line_ends[@lpos - 1] + 1
|
790
|
+
else
|
791
|
+
new_pos = 0
|
792
|
+
end
|
793
|
+
|
794
|
+
if @cpos > (line(@lpos).size - 1)
|
795
|
+
debug("$cpos too large: #{@cpos} #{@lpos}")
|
796
|
+
if @larger_cpos < @cpos
|
797
|
+
@larger_cpos = @cpos
|
798
|
+
end
|
799
|
+
@cpos = line(@lpos).size - 1
|
800
|
+
end
|
801
|
+
new_pos += @cpos
|
802
|
+
set_pos(new_pos)
|
803
|
+
reset_larger_cpos if _reset_larger_cpos
|
804
|
+
end
|
805
|
+
|
806
|
+
# Calculate the one dimensional array index based on column and line positions
|
807
|
+
def calculate_pos_from_cpos_lpos(reset = true)
|
808
|
+
set_line_and_column_pos(nil, nil)
|
809
|
+
end
|
810
|
+
|
811
|
+
def delete2(range_id)
|
812
|
+
$paste_lines = false
|
813
|
+
range = get_range(range_id)
|
814
|
+
return if range == nil
|
815
|
+
debug "RANGE"
|
816
|
+
debug range.inspect
|
817
|
+
debug range.inspect
|
818
|
+
debug "------"
|
819
|
+
delete_range(range.first, range.last)
|
820
|
+
pos = [range.first, @pos].min
|
821
|
+
set_pos(pos)
|
822
|
+
end
|
823
|
+
|
824
|
+
def delete(op)
|
825
|
+
$paste_lines = false
|
826
|
+
# Delete selection
|
827
|
+
if op == SELECTION && visual_mode?
|
828
|
+
(startpos, endpos) = get_visual_mode_range2
|
829
|
+
delete_range(startpos, endpos)
|
830
|
+
@pos = [@pos, @selection_start].min
|
831
|
+
end_visual_mode
|
832
|
+
#return
|
833
|
+
|
834
|
+
# Delete current char
|
835
|
+
elsif op == CURRENT_CHAR_FORWARD
|
836
|
+
return if @pos >= self.size - 1 # May not delete last '\n'
|
837
|
+
add_delta([@pos, DELETE, 1], true)
|
838
|
+
|
839
|
+
# Delete current char and then move backward
|
840
|
+
elsif op == CURRENT_CHAR_BACKWARD
|
841
|
+
add_delta([@pos, DELETE, 1], true)
|
842
|
+
@pos -= 1
|
843
|
+
|
844
|
+
# Delete the char before current char and move backward
|
845
|
+
elsif op == BACKWARD_CHAR and @pos > 0
|
846
|
+
add_delta([@pos - 1, DELETE, 1], true)
|
847
|
+
@pos -= 1
|
848
|
+
elsif op == FORWARD_CHAR #TODO: ok?
|
849
|
+
add_delta([@pos + 1, DELETE, 1], true)
|
850
|
+
end
|
851
|
+
set_pos(@pos)
|
852
|
+
#recalc_line_ends
|
853
|
+
calculate_line_and_column_pos
|
854
|
+
#need_redraw!
|
855
|
+
end
|
856
|
+
|
857
|
+
def delete_range(startpos, endpos)
|
858
|
+
#s = self.slice!(startpos..endpos)
|
859
|
+
set_clipboard(self[startpos..endpos])
|
860
|
+
add_delta([startpos, DELETE, (endpos - startpos + 1)], true)
|
861
|
+
#recalc_line_ends
|
862
|
+
calculate_line_and_column_pos
|
863
|
+
end
|
864
|
+
|
865
|
+
def get_range(range_id)
|
866
|
+
range = nil
|
867
|
+
if range_id == :to_word_end
|
868
|
+
wmarks = get_word_end_marks(@pos, @pos + 150)
|
869
|
+
if wmarks.any?
|
870
|
+
range = @pos..wmarks[0]
|
871
|
+
end
|
872
|
+
elsif range_id == :to_line_end
|
873
|
+
puts "TO LINE END"
|
874
|
+
range = @pos..(@line_ends[@lpos] - 1)
|
875
|
+
elsif range_id == :to_line_start
|
876
|
+
puts "TO LINE START: #{@lpos}"
|
877
|
+
|
878
|
+
if @cpos == 0
|
879
|
+
range = nil
|
880
|
+
else
|
881
|
+
if @lpos == 0
|
882
|
+
startpos = 0
|
883
|
+
else
|
884
|
+
startpos = @line_ends[@lpos - 1] + 1
|
885
|
+
end
|
886
|
+
endpos = @pos - 1
|
887
|
+
range = startpos..endpos
|
888
|
+
end
|
889
|
+
# range = startpos..(@pos - 1)
|
890
|
+
else
|
891
|
+
crash("INVALID RANGE")
|
892
|
+
end
|
893
|
+
return range if range == nil
|
894
|
+
if range.last < range.first
|
895
|
+
range.last = range.first
|
896
|
+
end
|
897
|
+
if range.first < 0
|
898
|
+
range.first = 0
|
899
|
+
end
|
900
|
+
if range.last >= self.size
|
901
|
+
range.last = self.size - 1
|
902
|
+
end
|
903
|
+
#TODO: sanity check
|
904
|
+
return range
|
905
|
+
end
|
906
|
+
|
907
|
+
def reset_larger_cpos()
|
908
|
+
@larger_cpos = @cpos
|
909
|
+
end
|
910
|
+
|
911
|
+
def move(direction)
|
912
|
+
puts "cpos:#{@cpos} lpos:#{@lpos} @larger_cpos:#{@larger_cpos}"
|
913
|
+
if direction == :forward_page
|
914
|
+
puts "FORWARD PAGE"
|
915
|
+
visible_range = get_visible_area()
|
916
|
+
set_pos(visible_range[1])
|
917
|
+
top_where_cursor()
|
918
|
+
end
|
919
|
+
if direction == :backward_page
|
920
|
+
puts "backward PAGE"
|
921
|
+
visible_range = get_visible_area()
|
922
|
+
set_pos(visible_range[0])
|
923
|
+
bottom_where_cursor()
|
924
|
+
end
|
925
|
+
|
926
|
+
if direction == FORWARD_CHAR
|
927
|
+
return if @pos >= self.size - 1
|
928
|
+
set_pos(@pos + 1)
|
929
|
+
end
|
930
|
+
if direction == BACKWARD_CHAR
|
931
|
+
set_pos(@pos - 1)
|
932
|
+
end
|
933
|
+
if direction == FORWARD_LINE
|
934
|
+
if @lpos >= @line_ends.size - 1 # Cursor is on last line
|
935
|
+
debug("ON LAST LINE")
|
936
|
+
return
|
937
|
+
else
|
938
|
+
@lpos += 1
|
939
|
+
end
|
940
|
+
end
|
941
|
+
if direction == BACKWARD_LINE
|
942
|
+
if @lpos == 0 # Cursor is on first line
|
943
|
+
return
|
944
|
+
else
|
945
|
+
@lpos -= 1
|
946
|
+
end
|
947
|
+
end
|
948
|
+
|
949
|
+
if direction == FORWARD_CHAR or direction == BACKWARD_CHAR
|
950
|
+
reset_larger_cpos
|
951
|
+
end
|
952
|
+
|
953
|
+
if direction == BACKWARD_LINE or direction == FORWARD_LINE
|
954
|
+
if @lpos > 0
|
955
|
+
new_pos = @line_ends[@lpos - 1] - 1
|
956
|
+
else
|
957
|
+
new_pos = 0
|
958
|
+
end
|
959
|
+
|
960
|
+
_line = self.line(@lpos)
|
961
|
+
if @cpos > (_line.size - 1)
|
962
|
+
debug("$cpos too large: #{@cpos} #{@lpos}")
|
963
|
+
if @larger_cpos < @cpos
|
964
|
+
@larger_cpos = @cpos
|
965
|
+
end
|
966
|
+
@cpos = line(@lpos).size - 1
|
967
|
+
end
|
968
|
+
|
969
|
+
if @larger_cpos > @cpos and @larger_cpos < (_line.size)
|
970
|
+
@cpos = @larger_cpos
|
971
|
+
elsif @larger_cpos > @cpos and @larger_cpos >= (_line.size)
|
972
|
+
@cpos = line(@lpos).size - 1
|
973
|
+
end
|
974
|
+
|
975
|
+
#new_pos += @cpos
|
976
|
+
#@pos = new_pos
|
977
|
+
calculate_pos_from_cpos_lpos(false)
|
978
|
+
end
|
979
|
+
end
|
980
|
+
|
981
|
+
def mark_current_position(mark_char)
|
982
|
+
@marks[mark_char] = @pos
|
983
|
+
end
|
984
|
+
|
985
|
+
# Get positions of last characters in words
|
986
|
+
def get_word_end_marks(startpos, endpos)
|
987
|
+
startpos = 0 if startpos < 0
|
988
|
+
endpos = self.size if endpos > self.size
|
989
|
+
search_str = self[(startpos)..(endpos)]
|
990
|
+
return if search_str == nil
|
991
|
+
wsmarks = scan_indexes(search_str, /(?<=\p{Word})[^\p{Word}]/)
|
992
|
+
wsmarks = wsmarks.collect { |x| x + startpos - 1 }
|
993
|
+
return wsmarks
|
994
|
+
end
|
995
|
+
|
996
|
+
# Get positions of first characters in words
|
997
|
+
def get_word_start_marks(startpos, endpos)
|
998
|
+
startpos = 0 if startpos < 0
|
999
|
+
endpos = self.size if endpos > self.size
|
1000
|
+
search_str = self[(startpos)..(endpos)]
|
1001
|
+
return if search_str == nil
|
1002
|
+
wsmarks = scan_indexes(search_str, /(?<=[^\p{Word}])\p{Word}/)
|
1003
|
+
wsmarks = wsmarks.collect { |x| x + startpos }
|
1004
|
+
return wsmarks
|
1005
|
+
end
|
1006
|
+
|
1007
|
+
def scan_marks(startpos, endpos, regstr, offset = 0)
|
1008
|
+
startpos = 0 if startpos < 0
|
1009
|
+
endpos = self.size if endpos > self.size
|
1010
|
+
search_str = self[(startpos)..(endpos)]
|
1011
|
+
return if search_str == nil
|
1012
|
+
marks = scan_indexes(search_str, regstr)
|
1013
|
+
marks = marks.collect { |x| x + startpos + offset }
|
1014
|
+
return marks
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
def handle_word(wnfo)
|
1018
|
+
word = wnfo[0]
|
1019
|
+
wtype = wnfo[1]
|
1020
|
+
if wtype == :url
|
1021
|
+
open_url(word)
|
1022
|
+
elsif wtype == :linepointer
|
1023
|
+
puts word.inspect
|
1024
|
+
jump_to_file(word[0], word[1])
|
1025
|
+
elsif wtype == :textfile
|
1026
|
+
open_existing_file(word)
|
1027
|
+
elsif wtype == :file
|
1028
|
+
open_with_default_program(word)
|
1029
|
+
elsif wtype == :hpt_link
|
1030
|
+
open_existing_file(word)
|
1031
|
+
else
|
1032
|
+
#TODO
|
1033
|
+
end
|
1034
|
+
end
|
1035
|
+
|
1036
|
+
def context_menu_items()
|
1037
|
+
m = []
|
1038
|
+
if @visual_mode
|
1039
|
+
seltxt = get_current_selection
|
1040
|
+
m << ["Copy", self.method("copy_active_selection"), nil]
|
1041
|
+
m << ["Join lines", self.method("convert_selected_text"), :joinlines]
|
1042
|
+
# m << ["Sort", self.method("convert_selected_text"), :sortlines]
|
1043
|
+
m << ["Sort", method("call"), :sortlines]
|
1044
|
+
m << ["Filter: get numbers", method("call"), :getnums_on_lines]
|
1045
|
+
m << ["Delete selection", method("call"), :delete_selection]
|
1046
|
+
|
1047
|
+
# m << ["Search in dictionary", self.method("handle_word"), nil]
|
1048
|
+
# m << ["Search in google", self.method("handle_word"), nil]
|
1049
|
+
m << ["Execute in terminal", method("exec_in_terminal"), seltxt]
|
1050
|
+
else
|
1051
|
+
(word, wtype) = get_cur_nonwhitespace_word()
|
1052
|
+
if wtype == :url
|
1053
|
+
m << ["Open url", self.method("open_url"), word]
|
1054
|
+
elsif wtype == :linepointer
|
1055
|
+
m << ["Jump to line", self.method("handle_word"), [word, wtype]]
|
1056
|
+
elsif wtype == :textfile
|
1057
|
+
m << ["Open text file", self.method("handle_word"), [word, wtype]]
|
1058
|
+
elsif wtype == :file
|
1059
|
+
m << ["Open file (xdg-open)", self.method("handle_word"), [word, wtype]]
|
1060
|
+
elsif wtype == :hpt_link
|
1061
|
+
m << ["Jump to file", self.method("handle_word"), [word, wtype]]
|
1062
|
+
else
|
1063
|
+
# m << ["TODO", self.method("handle_word"), word]
|
1064
|
+
m << ["Paste", method("call"), :paste_after]
|
1065
|
+
end
|
1066
|
+
end
|
1067
|
+
return m
|
1068
|
+
end
|
1069
|
+
|
1070
|
+
# Activated when enter/return pressed
|
1071
|
+
def handle_line_action()
|
1072
|
+
if line_action_handler.class == Proc
|
1073
|
+
# Custom handler
|
1074
|
+
line_action_handler.call(lpos)
|
1075
|
+
else
|
1076
|
+
# Generic default action
|
1077
|
+
cur_nonwhitespace_word_action()
|
1078
|
+
end
|
1079
|
+
end
|
1080
|
+
|
1081
|
+
def get_cur_nonwhitespace_word()
|
1082
|
+
wem = scan_marks(@pos, @pos + 200, /(?<=\S)\s/, -1)
|
1083
|
+
wsm = scan_marks(@pos - 200, @pos, /((?<=\s)\S)|^\S/)
|
1084
|
+
|
1085
|
+
word_start = wsm[-1]
|
1086
|
+
word_end = wem[0]
|
1087
|
+
word_start = pos if word_start == nil
|
1088
|
+
word_end = pos if word_end == nil
|
1089
|
+
word = self[word_start..word_end]
|
1090
|
+
puts "'WORD: #{word}'"
|
1091
|
+
message("'#{word}'")
|
1092
|
+
linep = get_file_line_pointer(word)
|
1093
|
+
puts "linep'#{linep}'"
|
1094
|
+
path = File.expand_path(word)
|
1095
|
+
wtype = nil
|
1096
|
+
if is_url(word)
|
1097
|
+
wtype = :url
|
1098
|
+
elsif is_existing_file(path)
|
1099
|
+
message("PATH:'#{word}'")
|
1100
|
+
if vma.can_open_extension?(path)
|
1101
|
+
wtype = :textfile
|
1102
|
+
else
|
1103
|
+
wtype = :file
|
1104
|
+
end
|
1105
|
+
# elsif hpt_check_cur_word(word) #TODO: check only
|
1106
|
+
# puts word
|
1107
|
+
elsif linep != nil
|
1108
|
+
wtype = :linepointer
|
1109
|
+
word = linep
|
1110
|
+
else
|
1111
|
+
fn = hpt_check_cur_word(word)
|
1112
|
+
if !fn.nil?
|
1113
|
+
return [fn, :hpt_link]
|
1114
|
+
end
|
1115
|
+
end
|
1116
|
+
return [word, wtype]
|
1117
|
+
end
|
1118
|
+
|
1119
|
+
def cur_nonwhitespace_word_action()
|
1120
|
+
|
1121
|
+
# (word, wtype) = get_cur_nonwhitespace_word()
|
1122
|
+
wnfo = get_cur_nonwhitespace_word()
|
1123
|
+
handle_word(wnfo)
|
1124
|
+
end
|
1125
|
+
|
1126
|
+
def get_cur_word()
|
1127
|
+
wem = get_word_end_marks(@pos, @pos + 200)
|
1128
|
+
wsm = get_word_start_marks(@pos - 200, @pos)
|
1129
|
+
word_start = wsm[-1]
|
1130
|
+
word_end = wem[0]
|
1131
|
+
word_start = pos if word_start == nil
|
1132
|
+
word_end = pos if word_end == nil
|
1133
|
+
word = self[word_start..word_end]
|
1134
|
+
puts "'#{word}'"
|
1135
|
+
message("'#{word}'")
|
1136
|
+
#puts wm
|
1137
|
+
end
|
1138
|
+
|
1139
|
+
def jump_to_next_instance_of_word()
|
1140
|
+
if $kbd.last_action == $kbd.cur_action and @current_word != nil
|
1141
|
+
# puts "REPEATING *"
|
1142
|
+
else
|
1143
|
+
start_search = [@pos - 150, 0].max
|
1144
|
+
|
1145
|
+
search_str1 = self[start_search..(@pos)]
|
1146
|
+
wsmarks = scan_indexes(search_str1, /(?<=[^\p{Word}])\p{Word}/)
|
1147
|
+
a = wsmarks[-1]
|
1148
|
+
a = 0 if a == nil
|
1149
|
+
|
1150
|
+
search_str2 = self[(@pos)..(@pos + 150)]
|
1151
|
+
wemarks = scan_indexes(search_str2, /(?<=\p{Word})[^\p{Word}]/)
|
1152
|
+
b = wemarks[0]
|
1153
|
+
word_start = (@pos - search_str1.size + a + 1)
|
1154
|
+
word_start = 0 if !(word_start >= 0)
|
1155
|
+
@current_word = self[word_start..(@pos + b - 1)]
|
1156
|
+
end
|
1157
|
+
|
1158
|
+
#TODO: search for /[^\p{Word}]WORD[^\p{Word}]/
|
1159
|
+
position_of_next_word = self.index(@current_word, @pos + 1)
|
1160
|
+
if position_of_next_word != nil
|
1161
|
+
set_pos(position_of_next_word)
|
1162
|
+
else #Search from beginning
|
1163
|
+
position_of_next_word = self.index(@current_word)
|
1164
|
+
set_pos(position_of_next_word) if position_of_next_word != nil
|
1165
|
+
end
|
1166
|
+
center_on_current_line
|
1167
|
+
return true
|
1168
|
+
end
|
1169
|
+
|
1170
|
+
def jump_word(direction, wordpos)
|
1171
|
+
offset = 0
|
1172
|
+
if direction == FORWARD
|
1173
|
+
debug "POS: #{@pos},"
|
1174
|
+
search_str = self[(@pos)..(@pos + 250)]
|
1175
|
+
return if search_str == nil
|
1176
|
+
if wordpos == WORD_START # vim 'w'
|
1177
|
+
wsmarks = scan_indexes(search_str, /(?<=[^\p{Word}])\p{Word}|\Z/) # \Z = end of string, just before last newline.
|
1178
|
+
wsmarks2 = scan_indexes(search_str, /\n[ \t]*\n/) # "empty" lines that have whitespace
|
1179
|
+
wsmarks2 = wsmarks2.collect { |x| x + 1 }
|
1180
|
+
wsmarks = (wsmarks2 + wsmarks).sort.uniq
|
1181
|
+
offset = 0
|
1182
|
+
if wsmarks.any?
|
1183
|
+
next_pos = @pos + wsmarks[0] + offset
|
1184
|
+
set_pos(next_pos)
|
1185
|
+
end
|
1186
|
+
elsif wordpos == WORD_END
|
1187
|
+
search_str = self[(@pos + 1)..(@pos + 150)]
|
1188
|
+
wsmarks = scan_indexes(search_str, /(?<=\p{Word})[^\p{Word}]/)
|
1189
|
+
offset = -1
|
1190
|
+
if wsmarks.any?
|
1191
|
+
next_pos = @pos + 1 + wsmarks[0] + offset
|
1192
|
+
set_pos(next_pos)
|
1193
|
+
end
|
1194
|
+
end
|
1195
|
+
end
|
1196
|
+
if direction == BACKWARD # vim 'b'
|
1197
|
+
start_search = @pos - 150 #TODO 150 length limit
|
1198
|
+
start_search = 0 if start_search < 0
|
1199
|
+
search_str = self[start_search..(@pos - 1)]
|
1200
|
+
return if search_str == nil
|
1201
|
+
wsmarks = scan_indexes(search_str,
|
1202
|
+
#/(^|(\W)\w|\n)/) #TODO 150 length limit
|
1203
|
+
#/^|(?<=[^\p{Word}])\p{Word}|(?<=\n)\n/) #include empty lines?
|
1204
|
+
/\A|(?<=[^\p{Word}])\p{Word}/) # Start of string or nonword,word.
|
1205
|
+
|
1206
|
+
offset = 0
|
1207
|
+
|
1208
|
+
if wsmarks.any?
|
1209
|
+
next_pos = start_search + wsmarks.last + offset
|
1210
|
+
set_pos(next_pos)
|
1211
|
+
end
|
1212
|
+
end
|
1213
|
+
end
|
1214
|
+
|
1215
|
+
def jump_to_mark(mark_char)
|
1216
|
+
p = @marks[mark_char]
|
1217
|
+
set_pos(p) if p
|
1218
|
+
center_on_current_line
|
1219
|
+
return true
|
1220
|
+
end
|
1221
|
+
|
1222
|
+
def jump(target)
|
1223
|
+
if target == START_OF_BUFFER
|
1224
|
+
set_pos(0)
|
1225
|
+
end
|
1226
|
+
if target == END_OF_BUFFER
|
1227
|
+
set_pos(self.size - 1)
|
1228
|
+
end
|
1229
|
+
if target == BEGINNING_OF_LINE
|
1230
|
+
@cpos = 0
|
1231
|
+
calculate_pos_from_cpos_lpos
|
1232
|
+
end
|
1233
|
+
if target == END_OF_LINE
|
1234
|
+
@cpos = line(@lpos).size - 1
|
1235
|
+
calculate_pos_from_cpos_lpos
|
1236
|
+
end
|
1237
|
+
|
1238
|
+
if target == FIRST_NON_WHITESPACE
|
1239
|
+
l = current_line()
|
1240
|
+
puts l.inspect
|
1241
|
+
@cpos = line(@lpos).size - 1
|
1242
|
+
a = scan_indexes(l, /\S/)
|
1243
|
+
puts a.inspect
|
1244
|
+
if a.any?
|
1245
|
+
@cpos = a[0]
|
1246
|
+
else
|
1247
|
+
@cpos = 0
|
1248
|
+
end
|
1249
|
+
calculate_pos_from_cpos_lpos
|
1250
|
+
end
|
1251
|
+
end
|
1252
|
+
|
1253
|
+
def jump_to_line(line_n = 1)
|
1254
|
+
|
1255
|
+
# $method_handles_repeat = true
|
1256
|
+
# if !$next_command_count.nil? and $next_command_count > 0
|
1257
|
+
# line_n = $next_command_count
|
1258
|
+
# debug "jump to line:#{line_n}"
|
1259
|
+
# end
|
1260
|
+
debug "jump to line:#{line_n}"
|
1261
|
+
line_n = get_repeat_num() if line_n == 1
|
1262
|
+
|
1263
|
+
if line_n > @line_ends.size
|
1264
|
+
debug("lpos too large") #TODO
|
1265
|
+
return
|
1266
|
+
end
|
1267
|
+
if line_n == 1
|
1268
|
+
set_pos(0)
|
1269
|
+
else
|
1270
|
+
set_pos(@line_ends[line_n - 2] + 1)
|
1271
|
+
end
|
1272
|
+
end
|
1273
|
+
|
1274
|
+
def join_lines()
|
1275
|
+
if @lpos >= @line_ends.size - 1 # Cursor is on last line
|
1276
|
+
debug("ON LAST LINE")
|
1277
|
+
return
|
1278
|
+
else
|
1279
|
+
# TODO: replace all whitespace between lines with ' '
|
1280
|
+
jump(END_OF_LINE)
|
1281
|
+
delete(CURRENT_CHAR_FORWARD)
|
1282
|
+
#insert_txt(' ',AFTER)
|
1283
|
+
insert_txt(" ", BEFORE)
|
1284
|
+
end
|
1285
|
+
end
|
1286
|
+
|
1287
|
+
def jump_to_next_instance_of_char(char, direction = FORWARD)
|
1288
|
+
|
1289
|
+
#return if at_end_of_line?
|
1290
|
+
if direction == FORWARD
|
1291
|
+
position_of_next_char = self.index(char, @pos + 1)
|
1292
|
+
if position_of_next_char != nil
|
1293
|
+
@pos = position_of_next_char
|
1294
|
+
end
|
1295
|
+
elsif direction == BACKWARD
|
1296
|
+
start_search = @pos - 250
|
1297
|
+
start_search = 0 if start_search < 0
|
1298
|
+
search_substr = self[start_search..(@pos - 1)]
|
1299
|
+
_pos = search_substr.reverse.index(char)
|
1300
|
+
if _pos != nil
|
1301
|
+
@pos -= (_pos + 1)
|
1302
|
+
end
|
1303
|
+
end
|
1304
|
+
m = method("jump_to_next_instance_of_char")
|
1305
|
+
set_last_command({ method: m, params: [char, direction] })
|
1306
|
+
$last_find_command = { char: char, direction: direction }
|
1307
|
+
set_pos(@pos)
|
1308
|
+
end
|
1309
|
+
|
1310
|
+
def replace_with_char(char)
|
1311
|
+
debug "self_pos:'#{self[@pos]}'"
|
1312
|
+
return if self[@pos] == "\n"
|
1313
|
+
d1 = [@pos, DELETE, 1]
|
1314
|
+
d2 = [@pos, INSERT, 1, char]
|
1315
|
+
add_delta(d1, true)
|
1316
|
+
add_delta(d2, true)
|
1317
|
+
debug "DELTAS:#{$buffer.deltas.inspect} "
|
1318
|
+
end
|
1319
|
+
|
1320
|
+
def insert_txt_at(c, pos)
|
1321
|
+
c = c.force_encoding("UTF-8"); #TODO:correct?
|
1322
|
+
c = "\n" if c == "\r"
|
1323
|
+
add_delta([pos, INSERT, c.size, c], true)
|
1324
|
+
calculate_line_and_column_pos
|
1325
|
+
end
|
1326
|
+
|
1327
|
+
def execute_current_line_in_terminal()
|
1328
|
+
s = get_current_line
|
1329
|
+
exec_in_terminal(s)
|
1330
|
+
end
|
1331
|
+
|
1332
|
+
def insert_new_line()
|
1333
|
+
s = get_current_line
|
1334
|
+
$hook.call(:insert_new_line, s)
|
1335
|
+
insert_txt("\n")
|
1336
|
+
# message("foo")
|
1337
|
+
end
|
1338
|
+
|
1339
|
+
def insert_txt(c, mode = BEFORE)
|
1340
|
+
# start_profiler
|
1341
|
+
#Sometimes we get ASCII-8BIT although actually UTF-8 "incompatible character encodings: UTF-8 and ASCII-8BIT (Encoding::CompatibilityError)"
|
1342
|
+
c = c.force_encoding("UTF-8"); #TODO:correct?
|
1343
|
+
|
1344
|
+
c = "\n" if c == "\r"
|
1345
|
+
if $cnf[:indent_based_on_last_line] and c == "\n" and @lpos > 0
|
1346
|
+
# Indent start of new line based on last line
|
1347
|
+
last_line = line(@lpos)
|
1348
|
+
m = /^( +)([^ ]+|$)/.match(last_line)
|
1349
|
+
debug m.inspect
|
1350
|
+
c = c + " " * m[1].size if m
|
1351
|
+
end
|
1352
|
+
if mode == BEFORE
|
1353
|
+
insert_pos = @pos
|
1354
|
+
@pos += c.size
|
1355
|
+
elsif mode == AFTER
|
1356
|
+
insert_pos = @pos + 1
|
1357
|
+
else
|
1358
|
+
return
|
1359
|
+
end
|
1360
|
+
|
1361
|
+
#self.insert(insert_pos,c)
|
1362
|
+
add_delta([insert_pos, INSERT, c.size, c], true)
|
1363
|
+
#puts("encoding: #{c.encoding}")
|
1364
|
+
#puts "c.size: #{c.size}"
|
1365
|
+
#recalc_line_ends #TODO: optimize?
|
1366
|
+
calculate_line_and_column_pos
|
1367
|
+
#need_redraw!
|
1368
|
+
#@pos += c.size
|
1369
|
+
# end_profiler
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
# Update buffer contents to newstr
|
1373
|
+
# Change by taking diff of old/new content
|
1374
|
+
def update_content(newstr)
|
1375
|
+
diff = Differ.diff_by_char(newstr, self.to_s)
|
1376
|
+
|
1377
|
+
da = diff.get_raw_array
|
1378
|
+
|
1379
|
+
pos = 0
|
1380
|
+
posA = 0
|
1381
|
+
posB = 0
|
1382
|
+
deltas = []
|
1383
|
+
|
1384
|
+
for x in da
|
1385
|
+
if x.class == String
|
1386
|
+
posA += x.size
|
1387
|
+
posB += x.size
|
1388
|
+
elsif x.class == Differ::Change
|
1389
|
+
if x.insert?
|
1390
|
+
deltas << [posB, INSERT, x.insert.size, x.insert.clone]
|
1391
|
+
posB += x.insert.size
|
1392
|
+
elsif x.delete?
|
1393
|
+
posA += x.delete.size
|
1394
|
+
deltas << [posB, DELETE, x.delete.size]
|
1395
|
+
end
|
1396
|
+
end
|
1397
|
+
end
|
1398
|
+
for d in deltas
|
1399
|
+
add_delta(d, true, true)
|
1400
|
+
end
|
1401
|
+
# $buffer.update_content(IO.read('test.txt'))
|
1402
|
+
end
|
1403
|
+
|
1404
|
+
def need_redraw!
|
1405
|
+
@need_redraw = true
|
1406
|
+
end
|
1407
|
+
|
1408
|
+
def need_redraw?
|
1409
|
+
return @need_redraw
|
1410
|
+
end
|
1411
|
+
|
1412
|
+
def set_redrawed
|
1413
|
+
@need_redraw = false
|
1414
|
+
end
|
1415
|
+
|
1416
|
+
# Create a new line after current line and insert text on that line
|
1417
|
+
def put_to_new_next_line(txt)
|
1418
|
+
l = current_line_range()
|
1419
|
+
insert_txt_at(txt, l.end + 1)
|
1420
|
+
set_pos(l.end + 1)
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
def paste(at = AFTER, register = nil)
|
1424
|
+
# Paste after current char. Except if at end of line, paste before end of line.
|
1425
|
+
text = ""
|
1426
|
+
if register.nil?
|
1427
|
+
text = paste_system_clipboard
|
1428
|
+
end
|
1429
|
+
|
1430
|
+
if text == ""
|
1431
|
+
return if !$clipboard.any?
|
1432
|
+
if register == nil
|
1433
|
+
text = $clipboard[-1]
|
1434
|
+
else
|
1435
|
+
text = $register[register]
|
1436
|
+
end
|
1437
|
+
end
|
1438
|
+
puts "PASTE: #{text}"
|
1439
|
+
|
1440
|
+
return if text == ""
|
1441
|
+
|
1442
|
+
if $paste_lines
|
1443
|
+
debug "PASTE LINES"
|
1444
|
+
put_to_new_next_line(text)
|
1445
|
+
else
|
1446
|
+
if at_end_of_buffer? or at_end_of_line? or at == BEFORE
|
1447
|
+
pos = @pos
|
1448
|
+
else
|
1449
|
+
pos = @pos + 1
|
1450
|
+
end
|
1451
|
+
insert_txt_at(text, pos)
|
1452
|
+
set_pos(pos + text.size)
|
1453
|
+
end
|
1454
|
+
set_pos(@pos)
|
1455
|
+
#TODO: AFTER does not work
|
1456
|
+
#insert_txt($clipboard[-1],AFTER)
|
1457
|
+
#recalc_line_ends #TODO: bug when run twice?
|
1458
|
+
end
|
1459
|
+
|
1460
|
+
def delete_line()
|
1461
|
+
$method_handles_repeat = true
|
1462
|
+
num_lines = 1
|
1463
|
+
if !$next_command_count.nil? and $next_command_count > 0
|
1464
|
+
num_lines = $next_command_count
|
1465
|
+
debug "copy num_lines:#{num_lines}"
|
1466
|
+
end
|
1467
|
+
lrange = line_range(@lpos, num_lines)
|
1468
|
+
s = self[lrange]
|
1469
|
+
add_delta([lrange.begin, DELETE, lrange.end - lrange.begin + 1], true)
|
1470
|
+
set_clipboard(s)
|
1471
|
+
update_pos(lrange.begin)
|
1472
|
+
$paste_lines = true
|
1473
|
+
#recalc_line_ends
|
1474
|
+
end
|
1475
|
+
|
1476
|
+
def update_pos(pos)
|
1477
|
+
@pos = pos
|
1478
|
+
calculate_line_and_column_pos
|
1479
|
+
end
|
1480
|
+
|
1481
|
+
def start_visual_mode()
|
1482
|
+
@visual_mode = true
|
1483
|
+
@selection_start = @pos
|
1484
|
+
qt_set_selection_start(@id, selection_start)
|
1485
|
+
$kbd.set_mode(:visual)
|
1486
|
+
end
|
1487
|
+
|
1488
|
+
def copy_active_selection(x = nil)
|
1489
|
+
debug "!COPY SELECTION"
|
1490
|
+
$paste_lines = false
|
1491
|
+
return if !@visual_mode
|
1492
|
+
|
1493
|
+
debug "COPY SELECTION"
|
1494
|
+
set_clipboard(self[get_visual_mode_range])
|
1495
|
+
end_visual_mode
|
1496
|
+
return true
|
1497
|
+
end
|
1498
|
+
|
1499
|
+
def transform_selection(op)
|
1500
|
+
return if !@visual_mode
|
1501
|
+
r = get_visual_mode_range
|
1502
|
+
txt = self[r]
|
1503
|
+
txt.upcase! if op == :upcase
|
1504
|
+
txt.downcase! if op == :downcase
|
1505
|
+
txt.gsub!(/\w+/, &:capitalize) if op == :capitalize
|
1506
|
+
txt.swapcase! if op == :swapcase
|
1507
|
+
txt.reverse! if op == :reverse
|
1508
|
+
|
1509
|
+
replace_range(r, txt)
|
1510
|
+
end_visual_mode
|
1511
|
+
end
|
1512
|
+
|
1513
|
+
def convert_selected_text(converter_id)
|
1514
|
+
return if !@visual_mode
|
1515
|
+
r = get_visual_mode_range
|
1516
|
+
txt = self[r]
|
1517
|
+
txt = $vma.apply_conv(converter_id, txt)
|
1518
|
+
#TODO: Detect if changed?
|
1519
|
+
replace_range(r, txt)
|
1520
|
+
end_visual_mode
|
1521
|
+
end
|
1522
|
+
|
1523
|
+
def style_transform(op)
|
1524
|
+
return if !@visual_mode
|
1525
|
+
r = get_visual_mode_range
|
1526
|
+
#TODO: if txt[-1]=="\n"
|
1527
|
+
txt = self[r]
|
1528
|
+
txt = "⦁" + txt + "⦁" if op == :bold
|
1529
|
+
txt = "⟦" + txt + "⟧" if op == :link
|
1530
|
+
txt = "❙" + txt + "❙" if op == :title
|
1531
|
+
txt.gsub!(/[❙◼⟦⟧⦁]/, "") if op == :clear
|
1532
|
+
|
1533
|
+
replace_range(r, txt)
|
1534
|
+
end_visual_mode
|
1535
|
+
end
|
1536
|
+
|
1537
|
+
def set_line_style(op)
|
1538
|
+
lrange = line_range(@lpos, 1, false)
|
1539
|
+
txt = self[lrange]
|
1540
|
+
# txt = "◼ " + txt if op == :heading
|
1541
|
+
txt = "⦁" + txt + "⦁" if op == :bold
|
1542
|
+
txt = "❙" + txt + "❙" if op == :title
|
1543
|
+
txt.gsub!(/◼ /, "") if op == :clear
|
1544
|
+
txt.gsub!(/[❙◼⟦⟧⦁]/, "") if op == :clear or [:h1, :h2, :h3, :h4].include?(op)
|
1545
|
+
|
1546
|
+
if [:h1, :h2, :h3, :h4].include?(op)
|
1547
|
+
txt.strip!
|
1548
|
+
txt = "◼ " + txt if op == :h1
|
1549
|
+
txt = "◼◼ " + txt if op == :h2
|
1550
|
+
txt = "◼◼◼ " + txt if op == :h3
|
1551
|
+
txt = "◼◼◼◼ " + txt if op == :h4
|
1552
|
+
end
|
1553
|
+
replace_range(lrange, txt)
|
1554
|
+
end
|
1555
|
+
|
1556
|
+
def copy_line()
|
1557
|
+
$method_handles_repeat = true
|
1558
|
+
num_lines = 1
|
1559
|
+
if !$next_command_count.nil? and $next_command_count > 0
|
1560
|
+
num_lines = $next_command_count
|
1561
|
+
debug "copy num_lines:#{num_lines}"
|
1562
|
+
end
|
1563
|
+
set_clipboard(self[line_range(@lpos, num_lines)])
|
1564
|
+
$paste_lines = true
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
def put_file_path_to_clipboard
|
1568
|
+
set_clipboard(self.fname)
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
def delete_active_selection() #TODO: remove this function
|
1572
|
+
return if !@visual_mode #TODO: this should not happen
|
1573
|
+
|
1574
|
+
_start, _end = get_visual_mode_range
|
1575
|
+
set_clipboard(self[_start, _end])
|
1576
|
+
end_visual_mode
|
1577
|
+
end
|
1578
|
+
|
1579
|
+
def end_visual_mode()
|
1580
|
+
debug "End visual mode"
|
1581
|
+
#TODO:take previous mode (insert|command) from stack?
|
1582
|
+
$kbd.set_mode(:command)
|
1583
|
+
@visual_mode = false
|
1584
|
+
return true
|
1585
|
+
end
|
1586
|
+
|
1587
|
+
def get_visual_mode_range2()
|
1588
|
+
r = get_visual_mode_range
|
1589
|
+
return [r.begin, r.end]
|
1590
|
+
end
|
1591
|
+
|
1592
|
+
def get_current_line
|
1593
|
+
s = self[line_range(@lpos, 1)]
|
1594
|
+
return s
|
1595
|
+
end
|
1596
|
+
|
1597
|
+
def get_current_selection()
|
1598
|
+
return "" if !@visual_mode
|
1599
|
+
return self[get_visual_mode_range]
|
1600
|
+
end
|
1601
|
+
|
1602
|
+
def get_visual_mode_range()
|
1603
|
+
_start = @selection_start
|
1604
|
+
_end = @pos
|
1605
|
+
|
1606
|
+
_start, _end = _end, _start if _start > _end
|
1607
|
+
_end = _end + 1 if _start < _end
|
1608
|
+
|
1609
|
+
return _start..(_end - 1)
|
1610
|
+
end
|
1611
|
+
|
1612
|
+
def selection_start()
|
1613
|
+
return -1 if !@visual_mode
|
1614
|
+
return @selection_start if @visual_mode
|
1615
|
+
end
|
1616
|
+
|
1617
|
+
def visual_mode?()
|
1618
|
+
return @visual_mode
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
def value()
|
1622
|
+
return self.to_s
|
1623
|
+
end
|
1624
|
+
|
1625
|
+
def save_as()
|
1626
|
+
debug "save_as"
|
1627
|
+
savepath = ""
|
1628
|
+
|
1629
|
+
# If current file has fname, save to that fname
|
1630
|
+
# Else search for previously open files and save to the directory of
|
1631
|
+
# the last viewed file that has a filename
|
1632
|
+
# $buffers[$buffer_history.reverse[1]].fname
|
1633
|
+
|
1634
|
+
if @fname
|
1635
|
+
savepath = File.dirname(@fname)
|
1636
|
+
else
|
1637
|
+
savepath = buflist.get_last_dir
|
1638
|
+
end
|
1639
|
+
# Ripl.start :binding => binding
|
1640
|
+
qt_file_saveas(savepath)
|
1641
|
+
# calls back to file_saveas
|
1642
|
+
# TODO:?
|
1643
|
+
end
|
1644
|
+
|
1645
|
+
def write_contents_to_file(fpath)
|
1646
|
+
if @crypt != nil
|
1647
|
+
mode = "wb+"
|
1648
|
+
contents = "VMACRYPT001" + @crypt.encrypt(self.to_s)
|
1649
|
+
else
|
1650
|
+
mode = "w+"
|
1651
|
+
contents = self.to_s
|
1652
|
+
end
|
1653
|
+
|
1654
|
+
Thread.new {
|
1655
|
+
File.open(fpath, mode) do |io|
|
1656
|
+
#io.set_encoding(self.encoding)
|
1657
|
+
|
1658
|
+
begin
|
1659
|
+
io.write(contents)
|
1660
|
+
rescue Encoding::UndefinedConversionError => ex
|
1661
|
+
# this might happen when trying to save UTF-8 as US-ASCII
|
1662
|
+
# so just warn, try to save as UTF-8 instead.
|
1663
|
+
warn("Saving as UTF-8 because of: #{ex.class}: #{ex}")
|
1664
|
+
io.rewind
|
1665
|
+
|
1666
|
+
io.set_encoding(Encoding::UTF_8)
|
1667
|
+
io.write(contents)
|
1668
|
+
#self.encoding = Encoding::UTF_8
|
1669
|
+
end
|
1670
|
+
end
|
1671
|
+
sleep 3
|
1672
|
+
}
|
1673
|
+
end
|
1674
|
+
|
1675
|
+
def save()
|
1676
|
+
if !@fname
|
1677
|
+
save_as()
|
1678
|
+
return
|
1679
|
+
end
|
1680
|
+
message("Saving file #{@fname}")
|
1681
|
+
write_contents_to_file(@fname)
|
1682
|
+
end
|
1683
|
+
|
1684
|
+
# Indents whole buffer using external program
|
1685
|
+
def indent()
|
1686
|
+
file = Tempfile.new("out")
|
1687
|
+
infile = Tempfile.new("in")
|
1688
|
+
file.write($buffer.to_s)
|
1689
|
+
file.flush
|
1690
|
+
bufc = "FOO"
|
1691
|
+
|
1692
|
+
tmppos = @pos
|
1693
|
+
|
1694
|
+
message("Auto format #{@fname}")
|
1695
|
+
|
1696
|
+
if ["chdr", "c", "cpp"].include?(get_file_type())
|
1697
|
+
|
1698
|
+
#C/C++/Java/JavaScript/Objective-C/Protobuf code
|
1699
|
+
system("clang-format -style='{BasedOnStyle: LLVM, ColumnLimit: 100, SortIncludes: false}' #{file.path} > #{infile.path}")
|
1700
|
+
bufc = IO.read(infile.path)
|
1701
|
+
elsif get_file_type() == "Javascript"
|
1702
|
+
cmd = "clang-format #{file.path} > #{infile.path}'"
|
1703
|
+
debug cmd
|
1704
|
+
system(cmd)
|
1705
|
+
bufc = IO.read(infile.path)
|
1706
|
+
elsif get_file_type() == "ruby"
|
1707
|
+
cmd = "rufo #{file.path}"
|
1708
|
+
debug cmd
|
1709
|
+
system(cmd)
|
1710
|
+
bufc = IO.read(file.path)
|
1711
|
+
else
|
1712
|
+
return
|
1713
|
+
end
|
1714
|
+
$buffer.update_content(bufc)
|
1715
|
+
center_on_current_line #TODO: needed?
|
1716
|
+
file.close; file.unlink
|
1717
|
+
infile.close; infile.unlink
|
1718
|
+
end
|
1719
|
+
|
1720
|
+
def backup()
|
1721
|
+
fname = @fname
|
1722
|
+
return if !@fname
|
1723
|
+
message("Backup buffer #{fname}")
|
1724
|
+
spfx = fname.gsub("=", "==").gsub("/", "=:")
|
1725
|
+
spath = File.expand_path("~/autosave")
|
1726
|
+
return false if !can_save_to_directory?(spath)
|
1727
|
+
datetime = DateTime.now().strftime("%d%m%Y:%H%M%S")
|
1728
|
+
savepath = "#{spath}/#{spfx}_#{datetime}"
|
1729
|
+
if is_path_writable(savepath)
|
1730
|
+
debug "BACKUP BUFFER TO: #{savepath}"
|
1731
|
+
write_contents_to_file(savepath)
|
1732
|
+
else
|
1733
|
+
message("PATH NOT WRITABLE: #{savepath}")
|
1734
|
+
end
|
1735
|
+
end
|
1736
|
+
end
|
1737
|
+
|
1738
|
+
#TODO
|
1739
|
+
def write_to_file(savepath, s)
|
1740
|
+
if is_path_writable(savepath)
|
1741
|
+
IO.write(savepath, $buffer.to_s)
|
1742
|
+
else
|
1743
|
+
message("PATH NOT WRITABLE: #{savepath}")
|
1744
|
+
end
|
1745
|
+
end
|
1746
|
+
|
1747
|
+
def is_path_writable(fpath)
|
1748
|
+
r = false
|
1749
|
+
if fpath.class == String
|
1750
|
+
r = true if File.writable?(Pathname.new(fpath).dirname)
|
1751
|
+
end
|
1752
|
+
return r
|
1753
|
+
end
|
1754
|
+
|
1755
|
+
def backup_all_buffers()
|
1756
|
+
for buf in $buffers
|
1757
|
+
buf.backup
|
1758
|
+
end
|
1759
|
+
message("Backup all buffers")
|
1760
|
+
end
|