Skip to content

Commit dfd979d

Browse files
committed
Support multiline strings in python
1 parent 9b0c30f commit dfd979d

4 files changed

Lines changed: 183 additions & 0 deletions

File tree

autoload/sj/python.vim

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,104 @@ function! sj#python#JoinTernaryAssignment()
317317
return 1
318318
endfunction
319319

320+
function! sj#python#SplitString()
321+
let char = getline('.')[col('.') - 1]
322+
if char != '"' && char != "'"
323+
return 0
324+
endif
325+
326+
let string_pattern = '\(\%(^\|[^\\]\)\zs\([''"]\)\).\{-}[^\\]\+\2'
327+
let empty_string_pattern = '\%(''''\|""\)'
328+
329+
let lineno = line('.')
330+
331+
let [match_start, match_end] = sj#SearchColsUnderCursor(string_pattern)
332+
if match_start <= 0
333+
let [match_start, match_end] = sj#SearchColsUnderCursor(empty_string_pattern)
334+
if match_start <= 0
335+
return 0
336+
endif
337+
endif
338+
339+
let string = sj#GetCols(match_start, match_end - 1)
340+
let delimiter = string[0]
341+
let body = string[1:-2]
342+
let indent = indent(lineno)
343+
344+
if body =~ '^\(''\|""\)'
345+
" then we're trying to split a string that's already multiline, ignore:
346+
return 0
347+
endif
348+
349+
if delimiter == '"'
350+
if len(body) == 0
351+
call sj#ReplaceMotion('vi"', '"""'.."\n"..'"""')
352+
else
353+
let body = substitute(body, '\\"', '"', 'g')
354+
call sj#ReplaceMotion('vi"', '""'.."\n"..body.."\n".'""')
355+
endif
356+
elseif delimiter == "'"
357+
if len(body) == 0
358+
call sj#ReplaceMotion("vi'", "'''\n'''")
359+
else
360+
let body = substitute(body, "\\''", "'", 'g')
361+
call sj#ReplaceMotion("vi'", "''\n"..body.."\n''")
362+
endif
363+
else
364+
return 0
365+
endif
366+
367+
if len(body) == 0
368+
call sj#SetIndent(lineno + 1, lineno + 1, indent)
369+
else
370+
call sj#SetIndent(lineno + 1, lineno + 1, indent + shiftwidth())
371+
call sj#SetIndent(lineno + 2, lineno + 2, indent)
372+
endif
373+
374+
return 1
375+
endfunction
376+
377+
function! sj#python#JoinMultilineString()
378+
if sj#SearchUnderCursor('\("""\|''''''\)\s*$') <= 0
379+
return 0
380+
endif
381+
382+
let start_lineno = line('.')
383+
let prefix = getline('.')[0 : col('.') - 2]
384+
let delimiter = getline('.')[col('.') - 1 : col('.') + 2]
385+
386+
if search('^\s*'.delimiter, 'W') <= 0
387+
return 0
388+
endif
389+
390+
let end_lineno = line('.')
391+
let suffix = matchstr(getline(end_lineno), '^\s*'.delimiter.'\zs.*\ze')
392+
393+
if end_lineno - start_lineno > 1
394+
let lines = sj#GetLines(start_lineno + 1, end_lineno - 1)
395+
let lines = sj#TrimList(lines)
396+
let body = join(lines, " ")
397+
else
398+
let body = ''
399+
endif
400+
401+
if delimiter == '"""'
402+
let quote = '"'
403+
let body = escape(body, '"')
404+
elseif delimiter == "'''"
405+
let quote = "'"
406+
let body = escape(body, "'")
407+
else
408+
return 0
409+
endif
410+
411+
let replacement = prefix..quote..body..quote..suffix
412+
call sj#ReplaceLines(start_lineno, end_lineno, replacement)
413+
414+
return 1
415+
endfunction
416+
417+
320418
function! s:SplitList(regex, opening_char, closing_char)
321419
let [from, to] = sj#LocateBracesAroundCursor(a:opening_char, a:closing_char, ['pythonString'])
322420
if from < 0 && to < 0

doc/splitjoin.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,19 @@ Note that splitting `a, b = b, a` would not result in an expression that works
963963
the same way, due to the special handling by python of this case to swap two
964964
values.
965965

966+
Strings ~
967+
>
968+
db.execute('UPDATE table SET value = ?', value)
969+
970+
db.execute('''
971+
UPDATE table SET value = ?
972+
''', value)
973+
<
974+
To avoid string-splitting overriding argument-splitting, it only triggers when
975+
the cursor is on the string delimiter. It's recommended to use f'/f"/F'/F" to
976+
position the cursor precisely before splitting.
977+
978+
966979
==============================================================================
967980
R *splitjoin-r*
968981

ftplugin/python/splitjoin.vim

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
if !exists('b:splitjoin_split_callbacks')
22
let b:splitjoin_split_callbacks = [
3+
\ 'sj#python#SplitString',
34
\ 'sj#python#SplitListComprehension',
45
\ 'sj#python#SplitTuple',
56
\ 'sj#python#SplitAssignment',
@@ -13,6 +14,7 @@ endif
1314

1415
if !exists('b:splitjoin_join_callbacks')
1516
let b:splitjoin_join_callbacks = [
17+
\ 'sj#python#JoinMultilineString',
1618
\ 'sj#python#JoinTuple',
1719
\ 'sj#python#JoinDict',
1820
\ 'sj#python#JoinArray',

spec/plugin/python_spec.rb

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,4 +271,74 @@ def example():
271271
result = [x * y for x in range(1, 10) for y in range(10, 20) if x != y]
272272
EOF
273273
end
274+
275+
describe "strings" do
276+
it "joins ''' strings into single-quoted strings" do
277+
set_file_contents <<~EOF
278+
string = '''
279+
something, "anything"
280+
'''
281+
EOF
282+
283+
vim.search "'''"
284+
join
285+
286+
assert_file_contents <<~EOF
287+
string = 'something, "anything"'
288+
EOF
289+
end
290+
291+
it "joins \"\"\" strings into double-quoted strings" do
292+
set_file_contents <<~EOF
293+
string = """
294+
something, 'anything'
295+
"""
296+
EOF
297+
298+
vim.search '"""'
299+
join
300+
301+
assert_file_contents <<~EOF
302+
string = "something, 'anything'"
303+
EOF
304+
end
305+
306+
it "splits normal strings into multiline strings" do
307+
set_file_contents 'string = "\"anything\""'
308+
309+
vim.search '"\"'
310+
split
311+
312+
assert_file_contents <<~EOF
313+
string = """
314+
"anything"
315+
"""
316+
EOF
317+
end
318+
319+
it "splits empty strings into empty multiline strings" do
320+
set_file_contents 'string = ""'
321+
322+
vim.search '"'
323+
split
324+
325+
assert_file_contents <<~EOF
326+
string = """
327+
"""
328+
EOF
329+
end
330+
331+
it "keeps content around the string, only splits with cursor on delimiter" do
332+
set_file_contents 'string = function_call(one, "two", three)'
333+
334+
vim.search '"'
335+
split
336+
337+
assert_file_contents <<~EOF
338+
string = function_call(one, """
339+
two
340+
""", three)
341+
EOF
342+
end
343+
end
274344
end

0 commit comments

Comments
 (0)