vimamsa 0.1.13 → 0.1.15

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