Skip to content

Commit 67545b7

Browse files
committed
FuncRef for Surround()
1 parent b626f2e commit 67545b7

File tree

1 file changed

+172
-158
lines changed

1 file changed

+172
-158
lines changed

lib/utils.vim

Lines changed: 172 additions & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ export def DictToListOfDicts(d: dict<any>): list<dict<any>>
2121
return list_of_dicts
2222
enddef
2323

24-
2524
export def ZipLists(l1: list<any>, l2: list<any>): list<list<any>>
2625
# Zip function, like in Python
2726
var min_len = min([len(l1), len(l2)])
@@ -92,31 +91,11 @@ def RemoveSurrounding(a: any, b: any)
9291
echom "TBD"
9392
enddef
9493

95-
export def Surround(open_delimiter: string,
94+
def SurroundSmart(open_delimiter: string,
9695
close_delimiter: string,
9796
open_delimiters_dict: dict<string>,
9897
close_delimiters_dict: dict<string>,
9998
text_object: string = '')
100-
# Usage:
101-
# Select text and hit <leader> + e.g. parenthesis
102-
#
103-
# 'open_delimiter' and 'close_delimiter' are the strings to add to the text.
104-
# They also serves as keys for the dics 'open_delimiters_dict' and
105-
# 'close_delimiters_dict'.
106-
#
107-
# We need dicts because we need the strings to add to the text for
108-
# surrounding purposes, but also a mechanism to search the surrounding
109-
# delimiters in the text.
110-
# We need regex because the delimiters strings may not be disjoint (think
111-
# for example, in the markdown case, you have '*' delimiter which is
112-
# contained in the '**' delimiter) and therefore we cannot find the
113-
# delimiting string as-is.
114-
# Finally, open_delimiters_dict[ii] is zipped with
115-
# close_delimiters_dict[ii], therefore be sure that there is correspondence
116-
# between opening and closing delimiters.
117-
#
118-
# Remember that Visual Selections and Text Objects are cousins.
119-
# Also, remember that a yank set the marks '[ and '].
12099

121100
var open_string = open_delimiter
122101
var open_regex = open_delimiters_dict[open_string]
@@ -125,159 +104,185 @@ export def Surround(open_delimiter: string,
125104
var close_string = close_delimiter
126105
var close_regex = close_delimiters_dict[close_string]
127106
var close_delimiter_dict = {close_string: close_regex}
107+
# Set marks
108+
var A = getcharpos("'<")
109+
var B = getcharpos("'>")
110+
if !empty(text_object)
111+
# GetTextObject is called for setting '[ and '] marks through a yank.
112+
GetTextObject(text_object)
113+
A = getcharpos("'[")
114+
B = getcharpos("']")
115+
endif
128116

129-
if !empty(IsInRange(open_delimiter_dict, close_delimiter_dict))
130-
RemoveSurrounding(open_regex, close_regex)
131-
else
132-
# Set marks
133-
var A = getcharpos("'<")
134-
var B = getcharpos("'>")
135-
if !empty(text_object)
136-
# GetTextObject is called for setting '[ and '] marks through a yank.
137-
GetTextObject(text_object)
138-
A = getcharpos("'[")
139-
B = getcharpos("']")
140-
endif
117+
# marks -> (x,y) coordinates
118+
# line and column
119+
var lA = A[1]
120+
var cA = A[2]
141121

142-
# marks -> (x,y) coordinates
143-
# line and column
144-
var lA = A[1]
145-
var cA = A[2]
122+
# line and column
123+
var lB = B[1]
124+
var cB = B[2]
146125

147-
# line and column
148-
var lB = B[1]
149-
var cB = B[2]
126+
if A == B
127+
return
128+
endif
150129

151-
if A == B
152-
return
153-
endif
130+
# -------- SMART DELIMITERS BEGIN ---------------------------
131+
# We check conditions like the following and we adjust the style
132+
# delimiters
133+
# We assume that the existing style ranges are (C,D) and (E,F) and we want
134+
# to place (A,B) as in the picture
135+
#
136+
# -E-------A------------
137+
# ------------F---------
138+
# ------------C------B--
139+
# --------D-------------
140+
#
141+
# We want to get:
142+
#
143+
# -E------FA------------
144+
# ----------------------
145+
# ------------------BC--
146+
# --------D-------------
147+
#
148+
# so that all the styles are visible
149+
150+
# We need a list-of-dicts [{a: 'foo'}, {b: 'bar'}, {c: 'baz'}]
151+
var open_delimiters_dict_list = DictToListOfDicts(open_delimiters_dict)
152+
var close_delimiters_dict_list = DictToListOfDicts(close_delimiters_dict)
153+
154+
# Check if A falls in an existing interval
155+
var found_delimiters_interval = []
156+
cursor(lA, cA)
157+
var old_right_delimiter = ''
158+
for delim in ZipLists(open_delimiters_dict_list,
159+
close_delimiters_dict_list)
160+
161+
found_delimiters_interval = IsInRange(delim[0], delim[1])
154162

155-
# -------- SMART DELIMITERS BEGIN ---------------------------
156-
# We check conditions like the following and we adjust the style
157-
# delimiters
158-
# We assume that the existing style ranges are (C,D) and (E,F) and we want
159-
# to place (A,B) as in the picture
160-
#
161-
# -E-------A------------
162-
# ------------F---------
163-
# ------------C------B--
164-
# --------D-------------
165-
#
166-
# We want to get:
167-
#
168-
# -E------FA------------
169-
# ----------------------
170-
# ------------------BC--
171-
# --------D-------------
172-
#
173-
# so that all the styles are visible
174-
175-
var open_delim_leftovers = copy(open_delimiters_dict)
176-
var close_delim_leftovers = copy(close_delimiters_dict)
177-
unlet open_delim_leftovers[open_string]
178-
unlet close_delim_leftovers[close_string]
179-
180-
# We need a list-of-dicts [{a: 'foo'}, {b: 'bar'}, {c: 'baz'}]
181-
var open_delim_leftovers_list = DictToListOfDicts(open_delim_leftovers)
182-
var close_delim_leftovers_list = DictToListOfDicts(close_delim_leftovers)
183-
184-
# Check if A falls in an existing interval
185-
var found_delimiters_interval = []
186-
cursor(lA, cA)
187-
var old_right_delimiter = ''
188-
for delim in ZipLists(open_delim_leftovers_list,
189-
close_delim_leftovers_list)
190-
191-
found_delimiters_interval = IsInRange(delim[0], delim[1])
192-
193-
if !empty(found_delimiters_interval)
194-
old_right_delimiter = keys(delim[0])[0]
195-
# Existing blocks shall be disjoint,
196-
# so we can break as soon as we find a delimiter
197-
break
198-
endif
199-
endfor
200-
201-
# Try to preserve overlapping ranges by moving the delimiters.
202-
# For example. If we have the pairs (C, D) and (E,F) as it follows:
203-
# ------C-------D------E------F
204-
# and we want to add (A, B) as it follows
205-
# ------C---A---D-----E--B---F
206-
# then the results becomes a mess. The idea is to move D before A and E
207-
# after E, thus obtaining:
208-
# ------C--DA-----------BE----F
209-
#
210-
# TODO:
211-
# If you don't want to try to automatically adjust existing ranges, then
212-
# remove 'old_right_delimiter' and 'old_left_limiter' from what follows,
213-
# AND don't remove anything between A and B
214-
#
215-
# TODO: the following is specifically designed for markdown, so if you use
216-
# for other languages, you have to modify it!
217-
var toA = ''
218163
if !empty(found_delimiters_interval)
219-
toA = strcharpart(getline(lA), 0, cA - 1)->substitute('\s*$', '', '')
220-
.. $'{old_right_delimiter} {open_string}'
221-
else
222-
toA = strcharpart(getline(lA), 0, cA - 1) .. open_string
164+
old_right_delimiter = keys(delim[0])[0]
165+
# Existing blocks shall be disjoint,
166+
# so we can break as soon as we find a delimiter
167+
break
223168
endif
169+
endfor
170+
171+
# Try to preserve overlapping ranges by moving the delimiters.
172+
# For example. If we have the pairs (C, D) and (E,F) as it follows:
173+
# ------C-------D------E------F
174+
# and we want to add (A, B) as it follows
175+
# ------C---A---D-----E--B---F
176+
# then the results becomes a mess. The idea is to move D before A and E
177+
# after E, thus obtaining:
178+
# ------C--DA-----------BE----F
179+
#
180+
# TODO:
181+
# If you don't want to try to automatically adjust existing ranges, then
182+
# remove 'old_right_delimiter' and 'old_left_limiter' from what follows,
183+
# AND don't remove anything between A and B
184+
#
185+
# TODO: the following is specifically designed for markdown, so if you use
186+
# for other languages, you have to modify it!
187+
var toA = ''
188+
if !empty(found_delimiters_interval)
189+
toA = strcharpart(getline(lA), 0, cA - 1)->substitute('\s*$', '', '')
190+
.. $'{old_right_delimiter} {open_string}'
191+
else
192+
toA = strcharpart(getline(lA), 0, cA - 1) .. open_string
193+
endif
194+
195+
# Check if B falls in an existing interval
196+
cursor(lB, cB)
197+
var old_left_delimiter = ''
198+
found_delimiters_interval = []
199+
for delim in ZipLists(close_delimiters_dict_list,
200+
close_delimiters_dict_list)
201+
202+
found_delimiters_interval = IsInRange(delim[0], delim[0])
224203

225-
# Check if B falls in an existing interval
226-
cursor(lB, cB)
227-
var old_left_delimiter = ''
228-
found_delimiters_interval = []
229-
for delim in ZipLists(close_delim_leftovers_list,
230-
close_delim_leftovers_list)
231-
232-
found_delimiters_interval = IsInRange(delim[0], delim[0])
233-
234-
if !empty(found_delimiters_interval)
235-
echom "delim: " .. string(delim)
236-
old_left_delimiter = keys(delim[0])[0]
237-
echom "foo: " .. old_left_delimiter
238-
# Existing blocks shall be disjoint,
239-
# so we can break as soon as we find a delimiter
240-
break
241-
endif
242-
endfor
243-
244-
var fromB = ''
245204
if !empty(found_delimiters_interval)
246-
fromB = $'{close_string} {old_left_delimiter}'
247-
.. strcharpart(getline(lB), cB)->substitute('^\s*', '', '')
248-
else
249-
# TODO: Non-smart delimiters
250-
fromB = close_string .. strcharpart(getline(lB), cB)
205+
echom "delim: " .. string(delim)
206+
old_left_delimiter = keys(delim[0])[0]
207+
# Existing blocks shall be disjoint,
208+
# so we can break as soon as we find a delimiter
209+
break
251210
endif
211+
endfor
252212

253-
# ------- SMART DELIMITERS PART END -----------
254-
# We have compute the partial strings until A and the partial string that
255-
# leaves B. Existing delimiters are set.
256-
# Next, we have to adjust the text between A and B, by removing all the
257-
# possible delimiters left between them.
213+
var fromB = ''
214+
if !empty(found_delimiters_interval)
215+
fromB = $'{close_string} {old_left_delimiter}'
216+
.. strcharpart(getline(lB), cB)->substitute('^\s*', '', '')
217+
else
218+
# TODO: Non-smart delimiters
219+
fromB = close_string .. strcharpart(getline(lB), cB)
220+
endif
258221

259-
var A_to_B = ''
260-
if lA == lB
261-
# Overwrite everything that is in the middle
262-
var junk_delimiters =
263-
RegexList2RegexOR(values(open_delimiters_dict), true)
222+
# ------- SMART DELIMITERS PART END -----------
223+
# We have compute the partial strings until A and the partial string that
224+
# leaves B. Existing delimiters are set.
225+
# Next, we have to adjust the text between A and B, by removing all the
226+
# possible delimiters left between them.
264227

265-
# TODO: if smart delimiter, don't use substitute
266-
A_to_B = strcharpart(getline(lA), cA - 1, cB - cA + 1)
267-
-> substitute(junk_delimiters, '', 'g')
228+
var A_to_B = ''
229+
if lA == lB
230+
# Overwrite everything that is in the middle
231+
var all_delimiters_regex =
232+
RegexList2RegexOR(values(open_delimiters_dict), true)
268233

269-
setline(lA, toA .. A_to_B .. fromB)
234+
A_to_B = strcharpart(getline(lA), cA - 1, cB - cA + 1)
235+
-> substitute(all_delimiters_regex, '', 'g')
270236

271-
elseif lB - lA == 1
272-
echom "TBD"
273-
else
274-
echom "TBD"
275-
endif
237+
setline(lA, toA .. A_to_B .. fromB)
276238

277-
# echom 'toA: ' .. toA
278-
# echom 'middle: ' .. AAA
279-
# echom 'fromB: ' .. fromB
239+
elseif lB - lA == 1
240+
echom "TBD"
241+
else
242+
echom "TBD"
280243
endif
244+
245+
# echom 'toA: ' .. toA
246+
# echom 'middle: ' .. AAA
247+
# echom 'fromB: ' .. fromB
248+
enddef
249+
250+
export def SurroundToggle(open_delimiter: string,
251+
close_delimiter: string,
252+
open_delimiters_dict: dict<string>,
253+
close_delimiters_dict: dict<string>,
254+
text_object: string = '')
255+
# Usage:
256+
# Select text and hit <leader> + e.g. parenthesis
257+
#
258+
# 'open_delimiter' and 'close_delimiter' are the strings to add to the text.
259+
# They also serves as keys for the dics 'open_delimiters_dict' and
260+
# 'close_delimiters_dict'.
261+
#
262+
# We need dicts because we need the strings to add to the text for
263+
# surrounding purposes, but also a mechanism to search the surrounding
264+
# delimiters in the text.
265+
# We need regex because the delimiters strings may not be disjoint (think
266+
# for example, in the markdown case, you have '*' delimiter which is
267+
# contained in the '**' delimiter) and therefore we cannot find the
268+
# delimiting string as-is.
269+
# Finally, open_delimiters_dict[ii] is zipped with
270+
# close_delimiters_dict[ii], therefore be sure that there is correspondence
271+
# between opening and closing delimiters.
272+
#
273+
# Remember that Visual Selections and Text Objects are cousins.
274+
# Also, remember that a yank set the marks '[ and '].
275+
276+
277+
if !empty(IsInRange(open_delimiter_dict, close_delimiter_dict))
278+
RemoveSurrounding(open_regex, close_regex)
279+
else
280+
Surround(open_delimiter,
281+
close_delimiter,
282+
open_delimiters_dict,
283+
close_delimiters_dict,
284+
text_object
285+
)
281286
enddef
282287

283288
export def GetTextBetweenMarks(A: string, B: string): list<string>
@@ -495,3 +500,12 @@ export def DeleteTextBetweenMarks(A: string, B: string): string
495500
# This to get rid off E1186
496501
return ''
497502
enddef
503+
504+
505+
# TODO
506+
export var Surround = SurroundSmart
507+
if exists('g:markdown_extras_config')
508+
&& has_key(g:markdown_extras_config, 'smart_delimiters')
509+
&& g:markdown_extras_config['smart_delimiters']
510+
Surround = SurroundSimple
511+
endif

0 commit comments

Comments
 (0)