vimamsa 0.1.0 → 0.1.1

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