vimamsa 0.1.13 → 0.1.15

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/custom_example.rb +12 -0
  3. data/lib/vimamsa/ack.rb +3 -4
  4. data/lib/vimamsa/actions.rb +1 -2
  5. data/lib/vimamsa/audio.rb +25 -1
  6. data/lib/vimamsa/buffer.rb +116 -591
  7. data/lib/vimamsa/buffer_changetext.rb +272 -0
  8. data/lib/vimamsa/buffer_cursor.rb +303 -0
  9. data/lib/vimamsa/buffer_list.rb +137 -133
  10. data/lib/vimamsa/buffer_manager.rb +15 -15
  11. data/lib/vimamsa/clipboard.rb +36 -0
  12. data/lib/vimamsa/conf.rb +136 -5
  13. data/lib/vimamsa/constants.rb +0 -10
  14. data/lib/vimamsa/debug.rb +9 -8
  15. data/lib/vimamsa/editor.rb +57 -84
  16. data/lib/vimamsa/encrypt.rb +6 -11
  17. data/lib/vimamsa/file_history.rb +0 -8
  18. data/lib/vimamsa/file_manager.rb +142 -10
  19. data/lib/vimamsa/gui.rb +106 -85
  20. data/lib/vimamsa/gui_dialog.rb +113 -0
  21. data/lib/vimamsa/gui_menu.rb +5 -1
  22. data/lib/vimamsa/gui_sourceview.rb +46 -29
  23. data/lib/vimamsa/gui_sourceview_autocomplete.rb +141 -0
  24. data/lib/vimamsa/gui_text.rb +49 -0
  25. data/lib/vimamsa/hyper_plain_text.rb +19 -5
  26. data/lib/vimamsa/key_actions.rb +41 -202
  27. data/lib/vimamsa/key_binding_tree.rb +129 -41
  28. data/lib/vimamsa/key_bindings_vimlike.rb +58 -48
  29. data/lib/vimamsa/langservp.rb +23 -3
  30. data/lib/vimamsa/macro.rb +35 -25
  31. data/lib/vimamsa/main.rb +7 -10
  32. data/lib/vimamsa/rbvma.rb +13 -11
  33. data/lib/vimamsa/search.rb +1 -1
  34. data/lib/vimamsa/search_replace.rb +106 -160
  35. data/lib/vimamsa/terminal.rb +34 -0
  36. data/lib/vimamsa/tests.rb +122 -0
  37. data/lib/vimamsa/util.rb +43 -4
  38. data/lib/vimamsa/version.rb +1 -1
  39. data/vimamsa.gemspec +5 -2
  40. metadata +59 -9
  41. /data/lib/vimamsa/{form_generator.rb → gui_form_generator.rb} +0 -0
@@ -0,0 +1,272 @@
1
+ # Operations that change the content of the buffer
2
+ # e.g. insert, delete
3
+
4
+ class Buffer < String
5
+
6
+ #TODO: change to apply=true as default
7
+ def add_delta(delta, apply = false, auto_update_cpos = false)
8
+ return if !is_delta_ok(delta)
9
+ if delta[1] == DELETE
10
+ return if delta[0] >= self.size
11
+ # If go over length of buffer
12
+ if delta[0] + delta[2] >= self.size
13
+ delta[2] = self.size - delta[0]
14
+ end
15
+ end
16
+
17
+ @edit_version += 1
18
+ @redo_stack = []
19
+ if apply
20
+ delta = run_delta(delta, auto_update_cpos)
21
+ else
22
+ @deltas << delta
23
+ end
24
+ @edit_history << delta
25
+ if self[-1] != "\n"
26
+ add_delta([self.size, INSERT, 1, "\n"], true)
27
+ end
28
+ reset_larger_cpos #TODO: correct here?
29
+ end
30
+
31
+ # TODO: rename ot auto-format. separate module?
32
+ # Indents whole buffer using external program
33
+ def indent()
34
+ file = Tempfile.new("out")
35
+ infile = Tempfile.new("in")
36
+ file.write(self.to_s)
37
+ file.flush
38
+ bufc = "FOO"
39
+
40
+ tmppos = @pos
41
+
42
+ message("Auto format #{@fname}")
43
+
44
+ ftype = get_file_type()
45
+ if ["chdr", "c", "cpp", "cpphdr"].include?(ftype)
46
+
47
+ #C/C++/Java/JavaScript/Objective-C/Protobuf code
48
+ system("clang-format -style='{BasedOnStyle: LLVM, ColumnLimit: 100, SortIncludes: false}' #{file.path} > #{infile.path}")
49
+ bufc = IO.read(infile.path)
50
+ elsif ftype == "Javascript"
51
+ cmd = "clang-format #{file.path} > #{infile.path}'"
52
+ debug cmd
53
+ system(cmd)
54
+ bufc = IO.read(infile.path)
55
+ elsif ftype == "ruby"
56
+ cmd = "rufo #{file.path}"
57
+ debug cmd
58
+ system(cmd)
59
+ bufc = IO.read(file.path)
60
+ else
61
+ message("No auto-format handler for file of type: #{ftype}")
62
+ return
63
+ end
64
+ self.update_content(bufc)
65
+ center_on_current_line #TODO: needed?
66
+ file.close; file.unlink
67
+ infile.close; infile.unlink
68
+ end
69
+
70
+ # Create a new line after current line and insert text on that line
71
+ def put_to_new_next_line(txt)
72
+ l = current_line_range()
73
+ insert_txt_at(txt, l.end + 1)
74
+ set_pos(l.end + 1)
75
+ end
76
+
77
+ # Start asynchronous read of system clipboard
78
+ def paste_start(at, register)
79
+ @clipboard_paste_running = true
80
+ clipboard = vma.gui.window.display.clipboard
81
+ clipboard.read_text_async do |_clipboard, result|
82
+ begin
83
+ text = clipboard.read_text_finish(result)
84
+ rescue Gio::IOError::NotSupported
85
+ # Happens when pasting from KeePassX and clipboard cleared
86
+ debug Gio::IOError::NotSupported
87
+ else
88
+ paste_finish(text, at, register)
89
+ end
90
+ end
91
+ end
92
+
93
+ def paste_finish(text, at, register)
94
+ debug "PASTE: #{text}"
95
+
96
+ # If we did not put this text to clipboard
97
+ if text != vma.clipboard[-1]
98
+ @paste_lines = false
99
+ end
100
+
101
+ text = sanitize_input(text)
102
+
103
+ vma.clipboard << text
104
+
105
+ return if text == ""
106
+
107
+ if @paste_lines
108
+ debug "PASTE LINES"
109
+ put_to_new_next_line(text)
110
+ else
111
+ debug "PASTE !LINES"
112
+ if at_end_of_buffer? or at_end_of_line? or at == BEFORE
113
+ pos = @pos
114
+ else
115
+ pos = @pos + 1
116
+ end
117
+ insert_txt_at(text, pos)
118
+ set_pos(pos + text.size)
119
+ end
120
+ set_pos(@pos)
121
+ @clipboard_paste_running = false
122
+ end
123
+
124
+ def paste(at = AFTER, register = nil)
125
+ # Macro's don't work with asynchronous call using GTK
126
+ # TODO: implement as synchronous?
127
+ # Use internal clipboard
128
+ if vma.macro.running_macro
129
+ text = vma.clipboard.get()
130
+ paste_finish(text, at, register)
131
+ else
132
+ # Get clipboard using GUI
133
+ paste_start(at, register)
134
+ end
135
+ return true
136
+ end
137
+
138
+ def complete_current_word(rep)
139
+ debug "complete_current_word", 2
140
+ p = @pos - 1
141
+ return if !is_legal_pos(p)
142
+ (word, range) = get_word_in_pos(p, boundary: :word)
143
+ debug [word, range].to_s, 2
144
+ endpos = range.begin+rep.size
145
+ replace_range(range, rep)
146
+ set_pos(endpos)
147
+ end
148
+
149
+ def replace_range(range, text)
150
+ delete_range(range.first, range.last)
151
+ insert_txt_at(text, range.begin)
152
+ end
153
+
154
+ def delete2(range_id, mark = nil)
155
+ @paste_lines = false
156
+ range = get_range(range_id, mark: mark)
157
+ return false if range == nil
158
+ debug "RANGE"
159
+ debug range.inspect
160
+ debug range.inspect
161
+ debug "------"
162
+ delete_range(range.first, range.last)
163
+ pos = [range.first, @pos].min
164
+ set_pos(pos)
165
+ return true
166
+ end
167
+
168
+ def delete(op, x = nil)
169
+ @paste_lines = false
170
+ # Delete selection
171
+ if op == SELECTION && visual_mode?
172
+ (startpos, endpos) = get_visual_mode_range2
173
+ delete_range(startpos, endpos, x)
174
+ @pos = [@pos, @selection_start].min
175
+ end_visual_mode
176
+ #return
177
+
178
+ # Delete current char
179
+ elsif op == CURRENT_CHAR_FORWARD
180
+ return if @pos >= self.size - 1 # May not delete last '\n'
181
+ add_delta([@pos, DELETE, 1], true)
182
+
183
+ # Delete current char and then move backward
184
+ elsif op == CURRENT_CHAR_BACKWARD
185
+ add_delta([@pos, DELETE, 1], true)
186
+ @pos -= 1
187
+
188
+ # Delete the char before current char and move backward
189
+ elsif op == BACKWARD_CHAR and @pos > 0
190
+ add_delta([@pos - 1, DELETE, 1], true)
191
+ @pos -= 1
192
+ elsif op == FORWARD_CHAR #TODO: ok?
193
+ add_delta([@pos + 1, DELETE, 1], true)
194
+ end
195
+ set_pos(@pos)
196
+ #recalc_line_ends
197
+ calculate_line_and_column_pos
198
+ #need_redraw!
199
+ end
200
+
201
+ def delete_range(startpos, endpos, x = nil)
202
+ s = self[startpos..endpos]
203
+ if startpos == endpos or s == ""
204
+ return
205
+ end
206
+ if x == :append
207
+ debug "APPEND"
208
+ s += "\n" + vma.clipboard.get()
209
+ end
210
+ vma.clipboard.set(s)
211
+ add_delta([startpos, DELETE, (endpos - startpos + 1)], true)
212
+ #recalc_line_ends
213
+ calculate_line_and_column_pos
214
+ end
215
+
216
+ def is_delta_ok(delta)
217
+ ret = true
218
+ pos = delta[0]
219
+ if pos < 0
220
+ ret = false
221
+ debug "pos=#{pos} < 0"
222
+ elsif pos > self.size
223
+ debug "pos=#{pos} > self.size=#{self.size}"
224
+ ret = false
225
+ end
226
+ if ret == false
227
+ # crash("DELTA OK=#{ret}")
228
+ end
229
+ return ret
230
+ end
231
+
232
+ def delete_line()
233
+ vma.kbd.method_handles_repeat = true
234
+ num_lines = 1
235
+ if !vma.kbd.next_command_count.nil? and vma.kbd.next_command_count > 0
236
+ num_lines = vma.kbd.next_command_count
237
+ debug "copy num_lines:#{num_lines}"
238
+ end
239
+ lrange = line_range(@lpos, num_lines)
240
+ s = self[lrange]
241
+ add_delta([lrange.begin, DELETE, lrange.end - lrange.begin + 1], true)
242
+ vma.clipboard.set(s)
243
+ update_pos(lrange.begin)
244
+ @paste_lines = true
245
+ #recalc_line_ends
246
+ end
247
+
248
+ def insert_tab
249
+ convert = conf(:tab_to_spaces_default)
250
+ convert = true if conf(:tab_to_spaces_languages).include?(@lang)
251
+ convert = false if conf(:tab_to_spaces_not_languages).include?(@lang)
252
+ tw = conf(:tab_width)
253
+ if convert
254
+ indent_to = (@cpos / tw) * tw + tw
255
+ indentdiff = indent_to - @cpos
256
+ insert_txt(" " * indentdiff)
257
+ else
258
+ insert_txt("\t")
259
+ end
260
+ end
261
+
262
+ def insert_image_after_current_line(fname)
263
+ lr = current_line_range()
264
+ a = "⟦img:#{fname}⟧\n"
265
+ b = " \n"
266
+ txt = a + b
267
+ insert_txt_at(txt, lr.end + 1)
268
+ buf.view.handle_deltas
269
+ imgpos = lr.end + 1 + a.size
270
+ add_image(fname, imgpos)
271
+ end
272
+ end
@@ -0,0 +1,303 @@
1
+ # Buffer operations related to cursor position, e.g. moving the cursor (backward, forward, next line etc.)
2
+ class Buffer < String
3
+
4
+ def line(lpos)
5
+ if @line_ends.size == 0
6
+ return self
7
+ end
8
+
9
+ #TODO: implement using line_range()
10
+ if lpos >= @line_ends.size
11
+ debug("lpos too large") #TODO
12
+ return ""
13
+ elsif lpos == @line_ends.size
14
+ end
15
+ start = @line_ends[lpos - 1] + 1 if lpos > 0
16
+ start = 0 if lpos == 0
17
+ _end = @line_ends[lpos]
18
+ debug "start: _#{start}, end: #{_end}"
19
+ return self[start.._end]
20
+ end
21
+
22
+ def at_end_of_line?()
23
+ return (self[@pos] == "\n" or at_end_of_buffer?)
24
+ end
25
+
26
+ def at_end_of_buffer?()
27
+ return @pos == self.size
28
+ end
29
+
30
+ def refresh_cursor
31
+ self.view.set_cursor_pos(@pos)
32
+ end
33
+
34
+ def set_pos(new_pos)
35
+ if new_pos >= self.size
36
+ @pos = self.size - 1 # TODO:??right side of last char
37
+ elsif new_pos >= 0
38
+ @pos = new_pos
39
+ end
40
+ self.view.set_cursor_pos(pos)
41
+ # gui_set_cursor_pos(@id, @pos)
42
+ calculate_line_and_column_pos
43
+
44
+ check_if_modified_outside
45
+ end
46
+
47
+ # Get the line number of character position
48
+ def get_line_pos(pos)
49
+ lpos = @line_ends.bsearch_index { |x, _| x >= pos }
50
+ return lpos
51
+ end
52
+
53
+ # Calculate the two dimensional column and line positions based on
54
+ # (one dimensional) position in the buffer.
55
+ def get_line_and_col_pos(pos)
56
+ pos = self.size if pos > self.size
57
+ pos = 0 if pos < 0
58
+
59
+ lpos = get_line_pos(pos)
60
+
61
+ lpos = @line_ends.size if lpos == nil
62
+ cpos = pos
63
+ cpos -= @line_ends[lpos - 1] + 1 if lpos > 0
64
+
65
+ return [lpos, cpos]
66
+ end
67
+
68
+ def calculate_line_and_column_pos(reset = true)
69
+ @lpos, @cpos = get_line_and_col_pos(@pos)
70
+ reset_larger_cpos if reset
71
+ end
72
+
73
+ def set_line_and_column_pos(lpos, cpos, _reset_larger_cpos = true)
74
+ @lpos = lpos if !lpos.nil?
75
+ @cpos = cpos if !cpos.nil?
76
+ if @lpos > 0
77
+ new_pos = @line_ends[@lpos - 1] + 1
78
+ else
79
+ new_pos = 0
80
+ end
81
+
82
+ if @cpos > (line(@lpos).size - 1)
83
+ debug("$cpos too large: #{@cpos} #{@lpos}")
84
+ if @larger_cpos < @cpos
85
+ @larger_cpos = @cpos
86
+ end
87
+ @cpos = line(@lpos).size - 1
88
+ end
89
+ new_pos += @cpos
90
+ set_pos(new_pos)
91
+ reset_larger_cpos if _reset_larger_cpos
92
+ end
93
+
94
+ # Calculate the one dimensional array index based on column and line positions
95
+ def calculate_pos_from_cpos_lpos(reset = true)
96
+ set_line_and_column_pos(nil, nil)
97
+ end
98
+
99
+ def update_pos(pos)
100
+ @pos = pos
101
+ calculate_line_and_column_pos
102
+ end
103
+
104
+ def is_legal_pos(pos, op = :read)
105
+ return false if pos < 0
106
+ if op == :add
107
+ return false if pos > self.size
108
+ elsif op == :read
109
+ return false if pos >= self.size
110
+ end
111
+ return true
112
+ end
113
+
114
+ def jump_to_last_edit()
115
+ return if @edit_pos_history.empty?
116
+ @edit_pos_history_i += 1
117
+
118
+ if @edit_pos_history_i > @edit_pos_history.size
119
+ @edit_pos_history_i = 0
120
+ end
121
+
122
+ # if @edit_pos_history.size >= @edit_pos_history_i
123
+ set_pos(@edit_pos_history[-@edit_pos_history_i])
124
+ center_on_current_line
125
+ return true
126
+ # end
127
+ end
128
+
129
+ def jump_to_next_edit()
130
+ return if @edit_pos_history.empty?
131
+ @edit_pos_history_i -= 1
132
+ @edit_pos_history_i = @edit_pos_history.size - 1 if @edit_pos_history_i < 0
133
+ debug "@edit_pos_history_i=#{@edit_pos_history_i}"
134
+ set_pos(@edit_pos_history[-@edit_pos_history_i])
135
+ center_on_current_line
136
+ return true
137
+ end
138
+
139
+ def jump_to_random_pos()
140
+ set_pos(rand(self.size))
141
+ end
142
+
143
+ def jump_to_next_instance_of_word()
144
+ if $kbd.last_action == $kbd.cur_action and @current_word != nil
145
+ # debug "REPEATING *"
146
+ else
147
+ start_search = [@pos - 150, 0].max
148
+
149
+ search_str1 = self[start_search..(@pos)]
150
+ wsmarks = scan_indexes(search_str1, /(?<=[^\p{Word}])\p{Word}/)
151
+ a = wsmarks[-1]
152
+ a = 0 if a == nil
153
+
154
+ search_str2 = self[(@pos)..(@pos + 150)]
155
+ wemarks = scan_indexes(search_str2, /(?<=\p{Word})[^\p{Word}]/)
156
+ b = wemarks[0]
157
+ word_start = (@pos - search_str1.size + a + 1)
158
+ word_start = 0 if !(word_start >= 0)
159
+ @current_word = self[word_start..(@pos + b - 1)]
160
+ end
161
+
162
+ #TODO: search for /[^\p{Word}]WORD[^\p{Word}]/
163
+ position_of_next_word = self.index(@current_word, @pos + 1)
164
+ if position_of_next_word != nil
165
+ set_pos(position_of_next_word)
166
+ else #Search from beginning
167
+ position_of_next_word = self.index(@current_word)
168
+ set_pos(position_of_next_word) if position_of_next_word != nil
169
+ end
170
+ center_on_current_line
171
+ return true
172
+ end
173
+
174
+ def jump_word(direction, wordpos)
175
+ offset = 0
176
+ if direction == FORWARD
177
+ debug "POS: #{@pos},"
178
+ search_str = self[(@pos)..(@pos + 250)]
179
+ return if search_str == nil
180
+ if wordpos == WORD_START # vim 'w'
181
+ wsmarks = scan_indexes(search_str, /(?<=[^\p{Word}])\p{Word}|\Z/) # \Z = end of string, just before last newline.
182
+ wsmarks2 = scan_indexes(search_str, /\n[ \t]*\n/) # "empty" lines that have whitespace
183
+ wsmarks2 = wsmarks2.collect { |x| x + 1 }
184
+ wsmarks = (wsmarks2 + wsmarks).sort.uniq
185
+ offset = 0
186
+ if wsmarks.any?
187
+ next_pos = @pos + wsmarks[0] + offset
188
+ set_pos(next_pos)
189
+ end
190
+ elsif wordpos == WORD_END
191
+ search_str = self[(@pos + 1)..(@pos + 150)]
192
+ wsmarks = scan_indexes(search_str, /(?<=\p{Word})[^\p{Word}]/)
193
+ offset = -1
194
+ if wsmarks.any?
195
+ next_pos = @pos + 1 + wsmarks[0] + offset
196
+ set_pos(next_pos)
197
+ end
198
+ end
199
+ end
200
+ if direction == BACKWARD # vim 'b'
201
+ start_search = @pos - 150 #TODO 150 length limit
202
+ start_search = 0 if start_search < 0
203
+ search_str = self[start_search..(@pos - 1)]
204
+ return if search_str == nil
205
+ wsmarks = scan_indexes(search_str,
206
+ #/(^|(\W)\w|\n)/) #TODO 150 length limit
207
+ #/^|(?<=[^\p{Word}])\p{Word}|(?<=\n)\n/) #include empty lines?
208
+ /\A|(?<=[^\p{Word}])\p{Word}/) # Start of string or nonword,word.
209
+
210
+ offset = 0
211
+
212
+ if wsmarks.any?
213
+ next_pos = start_search + wsmarks.last + offset
214
+ set_pos(next_pos)
215
+ end
216
+ end
217
+ end
218
+
219
+ def jump_to_mark(mark_char)
220
+ p = @marks[mark_char]
221
+ set_pos(p) if p
222
+ center_on_current_line
223
+ return true
224
+ end
225
+
226
+ def jump(target)
227
+ if target == START_OF_BUFFER
228
+ set_pos(0)
229
+ end
230
+ if target == END_OF_BUFFER
231
+ set_pos(self.size - 1)
232
+ end
233
+ if target == BEGINNING_OF_LINE
234
+ @cpos = 0
235
+ calculate_pos_from_cpos_lpos
236
+ end
237
+ if target == END_OF_LINE
238
+ @cpos = line(@lpos).size - 1
239
+ calculate_pos_from_cpos_lpos
240
+ end
241
+
242
+ if target == FIRST_NON_WHITESPACE
243
+ l = current_line()
244
+ debug l.inspect
245
+ @cpos = line(@lpos).size - 1
246
+ a = scan_indexes(l, /\S/)
247
+ debug a.inspect
248
+ if a.any?
249
+ @cpos = a[0]
250
+ else
251
+ @cpos = 0
252
+ end
253
+ calculate_pos_from_cpos_lpos
254
+ end
255
+ end
256
+
257
+ def jump_to_line(line_n = 1)
258
+
259
+ # $method_handles_repeat = true
260
+ # if !$next_command_count.nil? and $next_command_count > 0
261
+ # line_n = $next_command_count
262
+ # debug "jump to line:#{line_n}"
263
+ # end
264
+ debug "jump to line:#{line_n}"
265
+ line_n = get_repeat_num() if line_n == 1
266
+
267
+ if line_n > @line_ends.size
268
+ debug("lpos too large") #TODO
269
+ return
270
+ end
271
+ if line_n == 1
272
+ set_pos(0)
273
+ else
274
+ set_pos(@line_ends[line_n - 2] + 1)
275
+ end
276
+ end
277
+
278
+ def jump_to_next_instance_of_char(char, direction = FORWARD)
279
+ if direction == FORWARD
280
+ position_of_next_char = self.index(char, @pos + 1)
281
+ if position_of_next_char != nil
282
+ @pos = position_of_next_char
283
+ end
284
+ elsif direction == BACKWARD
285
+ start_search = @pos - 250
286
+ start_search = 0 if start_search < 0
287
+ search_substr = self[start_search..(@pos - 1)]
288
+ _pos = search_substr.reverse.index(char)
289
+ if _pos != nil
290
+ @pos -= (_pos + 1)
291
+ end
292
+ end
293
+ m = method("jump_to_next_instance_of_char")
294
+ set_last_command({ method: m, params: [char, direction] })
295
+ $last_find_command = { char: char, direction: direction }
296
+ set_pos(@pos)
297
+ return true
298
+ end
299
+
300
+ def jump_to_pos(new_pos)
301
+ set_pos(new_pos)
302
+ end
303
+ end