vimamsa 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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