1- -- mod-version:3
1+ -- mod-version:3.1
22local core = require " core"
33local translate = require " core.doc.translate"
44local config = require " core.config"
@@ -8,24 +8,33 @@ local command = require "core.command"
88local keymap = require " core.keymap"
99
1010
11- config .plugins .autoinsert = common .merge ({ map = {
12- [" [" ] = " ]" ,
13- [" {" ] = " }" ,
14- [" (" ] = " )" ,
15- [' "' ] = ' "' ,
16- [" '" ] = " '" ,
17- [" `" ] = " `" ,
18- } }, config .plugins .autoinsert )
11+ config .plugins .autoinsert = common .merge ({
12+ map = {
13+ [" [" ] = " ]" ,
14+ [" {" ] = " }" ,
15+ [" (" ] = " )" ,
16+ [' "' ] = ' "' ,
17+ [" '" ] = " '" ,
18+ [" `" ] = " `" ,
19+ }
20+ }, config .plugins .autoinsert )
1921
2022
23+ -- @param chr stirng
24+ -- @return boolean
2125local function is_closer (chr )
2226 for _ , v in pairs (config .plugins .autoinsert .map ) do
2327 if v == chr then
2428 return true
2529 end
2630 end
31+ return false
2732end
2833
34+
35+ -- @param text stirng
36+ -- @param chr stirng
37+ -- @return number
2938local function count_char (text , chr )
3039 local count = 0
3140 for _ in text :gmatch (chr ) do
@@ -35,87 +44,178 @@ local function count_char(text, chr)
3544end
3645
3746
38- local on_text_input = DocView .on_text_input
47+ -- @param dv DocView
48+ -- @param idx number
49+ -- @param text string
50+ -- @param mapping string
51+ -- @return boolean
52+ local function on_text_input_cursor (dv , idx , text , mapping )
53+ local l1 , c1 , l2 , c2 = dv .doc :get_selection_idx (idx , true )
54+ local is_selection_empty = not (l1 ~= l2 or c1 ~= c2 )
3955
40- function DocView :on_text_input (text )
56+ -- wrap selection if we have a selection
57+ if mapping and not is_selection_empty then
58+ dv .doc :insert (l2 , c2 , mapping )
59+ dv .doc :insert (l1 , c1 , text )
60+ dv .doc :set_selections (idx , l1 , c1 + 1 , l2 , c2 + 1 , true )
4161
42- -- Don't insert on multiselections
43- if # self .doc .selections > 4 then return on_text_input (self , text ) end
62+ return true
63+ end
64+
65+ -- no selections, check char next to cursor
66+ local chr = dv .doc :get_char (l1 , c1 )
4467
68+ -- skip inserting closing text if already there,
69+ -- instead just move the cursor to the right of the chr
70+ if text == chr and is_closer (chr ) then
71+ dv .doc :move_to_cursor (idx , 1 )
72+ return true
73+ end
74+
75+ -- don't insert closing quote if we have a non-even number on this line
76+ if text == mapping and count_char (dv .doc .lines [l1 ], text ) % 2 == 1 then
77+ return false
78+ end
79+
80+ -- auto insert closing bracket
81+ -- checks that character next to the cursor is:
82+ -- either whitespace (%s) or the mapped closer character.
83+ -- and it's not a double quote character ('"')
84+ if mapping and (chr :find (" %s" ) or is_closer (chr ) and chr ~= ' "' ) then
85+ dv .doc :insert (l1 , c1 , text )
86+ dv .doc :insert (l2 , c2 + 1 , mapping )
87+ -- move inside the bracket pair:
88+ dv .doc :move_to_cursor (idx , 1 )
89+ return true
90+ end
91+
92+ return false
93+ end
94+
95+
96+ -- save the original on_text_input to call it later
97+ local on_text_input = DocView .on_text_input
98+
99+ function DocView :on_text_input (text )
45100 local mapping = config .plugins .autoinsert .map [text ]
46101
47102 -- prevents plugin from operating on `CommandView`
48103 if getmetatable (self ) ~= DocView then
49104 return on_text_input (self , text )
50105 end
51106
52- -- wrap selection if we have a selection
53- if mapping and self .doc :has_selection () then
54- local l1 , c1 , l2 , c2 , swap = self .doc :get_selection (true )
55- self .doc :insert (l2 , c2 , mapping )
56- self .doc :insert (l1 , c1 , text )
57- self .doc :set_selection (l1 , c1 , l2 , c2 + 2 , swap )
107+ -- call auto insert on every selection
108+ for idx in self .doc :get_selections () do
109+ local inserted = on_text_input_cursor (self , idx , text , mapping )
110+
111+ -- operate normally on the cursor when nothing was inserted
112+ if not inserted then
113+ self .doc :text_input (text , idx )
114+ end
115+ end
116+ end
117+
118+ -- this deletes the matching pair when backspacing the opening one
119+ -- @param doc DocView.doc
120+ -- @param idx number
121+ local function delete_matching_pair (doc , idx )
122+ local l1 , c1 , l2 , c2 = doc :get_selection_idx (idx , true )
123+
124+ -- skip backspace if at the beginning of the line
125+ if c1 <= 1 then
58126 return
59127 end
60128
61- -- skip inserting closing text
62- local chr = self .doc :get_char (self .doc :get_selection ())
63- if text == chr and is_closer (chr ) then
64- self .doc :move_to (1 )
129+ -- only do it if there's nothing selected for the cursor
130+ local is_selection_empty = not (l1 ~= l2 or c1 ~= c2 )
131+ if not is_selection_empty then
65132 return
66133 end
67134
68- -- don't insert closing quote if we have a non-even number on this line
69- local line = self .doc :get_selection ()
70- if text == mapping and count_char (self .doc .lines [line ], text ) % 2 == 1 then
71- return on_text_input (self , text )
135+ -- check if the character to the right of the one being deleted
136+ -- is the expected matching pair
137+ local chr = doc :get_char (l1 , c1 )
138+ local mapped = config .plugins .autoinsert .map [doc :get_char (l1 , c1 - 1 )]
139+ if mapped and mapped == chr then
140+ -- delete 1 more character
141+ doc :remove (l1 , c1 , l2 , c2 + 1 )
72142 end
143+ end
73144
74- -- auto insert closing bracket
75- if mapping and (chr :find (" %s" ) or is_closer (chr ) and chr ~= ' "' ) then
76- on_text_input (self , text )
77- on_text_input (self , mapping )
78- self .doc :move_to (- 1 )
79- return
145+ -- @param doc DocView.doc
146+ local function on_backspace (doc )
147+ for idx in doc :get_selections () do
148+ delete_matching_pair (doc , idx )
149+ end
150+ -- execute the backspace normally
151+ command .perform " doc:backspace"
152+ end
153+
154+ -- need this because the doc:backspace already operates on all cursors,
155+ -- we only want to do it on a single cursor
156+ local function perform_backspace_cursor (doc , idx )
157+ local _ , indent_size = doc :get_indent_info ()
158+ local line1 , col1 , line2 , col2 = doc :get_selection (idx , true )
159+ if line1 == line2 and col1 == col2 then
160+ local text = doc :get_text (line1 , 1 , line1 , col1 )
161+ if # text >= indent_size and text :find (" ^ *$" ) then
162+ doc :delete_to_cursor (idx , 0 , - indent_size )
163+ return
164+ end
80165 end
166+ doc :delete_to_cursor (idx , translate .previous_char )
167+ end
81168
82- on_text_input (self , text )
169+ -- @param doc DocView.doc
170+ local function on_delete_to_previous_word_start (doc )
171+ for idx in doc :get_selections () do
172+ local le , ce = translate .previous_word_start (
173+ doc , doc :get_selection_idx (idx , true ))
174+ ce = ce + 1 -- dont over delete
175+ repeat
176+ local l , c = doc :get_selection_idx (idx , true )
177+
178+ -- delete character and matching pair if any
179+ -- we dont call on_backspace because that already operates on every cursor.
180+ delete_matching_pair (doc , idx )
181+ perform_backspace_cursor (doc , idx )
182+ until l <= le and c <= ce
183+ end
83184end
84185
85186
187+ -- @param doc DocView.doc
188+ local function on_delete_to_start_of_line (doc )
189+ for idx in doc :get_selections () do
190+ local le , ce = translate .start_of_line (
191+ doc , doc :get_selection_idx (idx , true ))
192+ ce = ce + 1 -- dont over delete
193+ repeat
194+ local l , c = doc :get_selection_idx (idx , true )
195+
196+ -- delete character and matching pair if any
197+ -- we dont call on_backspace because that already operates on every cursor.
198+ delete_matching_pair (doc , idx )
199+ perform_backspace_cursor (doc , idx )
200+ until l <= le and c <= ce
201+ end
202+ end
203+
86204
87205local function predicate ()
88- return core .active_view :is (DocView )
89- and # core .active_view .doc .selections <= 4 and not core .active_view .doc :has_selection (), core .active_view .doc
206+ return core .active_view :is (DocView ), core .active_view .doc
90207end
91208
209+
92210command .add (predicate , {
93- [" autoinsert:backspace" ] = function (doc )
94- local l , c = doc :get_selection ()
95- if c > 1 then
96- local chr = doc :get_char (l , c )
97- local mapped = config .plugins .autoinsert .map [doc :get_char (l , c - 1 )]
98- if mapped and mapped == chr then
99- doc :delete_to (1 )
100- end
101- end
102- command .perform " doc:backspace"
103- end ,
104-
105- [" autoinsert:delete-to-previous-word-start" ] = function (doc )
106- local le , ce = translate .previous_word_start (doc , doc :get_selection ())
107- while true do
108- local l , c = doc :get_selection ()
109- if l == le and c == ce then
110- break
111- end
112- command .perform " autoinsert:backspace"
113- end
114- end ,
211+ [" autoinsert:backspace" ] = on_backspace ,
212+ [" autoinsert:delete-to-previous-word-start" ] = on_delete_to_previous_word_start ,
213+ [" autoinsert:delete-to-start-of-line" ] = on_delete_to_start_of_line ,
115214})
116215
117216keymap .add {
118217 [" backspace" ] = " autoinsert:backspace" ,
119218 [" ctrl+backspace" ] = " autoinsert:delete-to-previous-word-start" ,
120- [" ctrl+shift+backspace" ] = " autoinsert:delete-to-previous-word-start " ,
219+ [" ctrl+shift+backspace" ] = " autoinsert:delete-to-start-of-line " ,
121220}
221+
0 commit comments