Skip to content

Much more powerful gq replacement function #29

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 22 commits into from
Closed
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
bb87bed
Add a start of a gq implementation that does not split strings halfwa…
JelteF Sep 6, 2016
630fdc7
Remove extra cursor call
JelteF Sep 11, 2016
a9ff2d9
Fallback to normal gq when no better solution is found
JelteF Sep 11, 2016
0f0eeb8
Fix trailing whitespace break
JelteF Sep 11, 2016
ed8ce61
Add normal mode to feedkeys
JelteF Sep 11, 2016
1259823
Remove echom calls
JelteF Sep 11, 2016
ea4f96d
Add check to see if space is inside brackets and add a "\" accordingly
JelteF Sep 11, 2016
7b21d10
Add support for breaking inside strings
JelteF Sep 20, 2016
0d22c60
Use custom gq more often
JelteF Sep 20, 2016
c8de03f
Fix breaking in string for double quote strings
JelteF Sep 20, 2016
d4b530f
Fix multiline string detection by using syntax plugin
JelteF Sep 21, 2016
4f6697d
Fix some annoying bugs
JelteF Sep 21, 2016
8801fc9
Fix quote choosing for special strings (r/u/b)
JelteF Sep 21, 2016
d77a6cc
Fix string exclusion to include byte strings
JelteF Sep 21, 2016
99d3c50
Add a bracket intsead of backslash + newline when it can help
JelteF Sep 21, 2016
85d31e2
Try adding newline after bracket instead of using backslash
JelteF Sep 21, 2016
146ba03
Fix unicode support partially
JelteF Sep 21, 2016
58c5347
Fix unicode related bugs by using virtcol
JelteF Sep 21, 2016
5391f8d
Add support for strings with strformat and doctests
JelteF Sep 22, 2016
05464ad
Use exclamation mark functions
JelteF Sep 22, 2016
21ea58a
Remove gq<movement> code, it works fine without it
JelteF Oct 6, 2016
7445a12
Fix bug after multiline removal
JelteF Oct 19, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
224 changes: 224 additions & 0 deletions indent/python.vim
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ setlocal expandtab
setlocal nolisp
setlocal autoindent
setlocal indentexpr=GetPythonPEPIndent(v:lnum)
setlocal formatexpr=GetPythonPEPFormat(v:lnum,v:count)
setlocal indentkeys=!^F,o,O,<:>,0),0],0},=elif,=except
setlocal tabstop=4
setlocal softtabstop=4
Expand Down Expand Up @@ -59,6 +60,8 @@ let s:stop_statement = '^\s*\(break\|continue\|raise\|return\|pass\)\>'
let s:skip_special_chars = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
\ '=~? "\\vstring|comment|jedi\\S"'

let s:skip_string = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
\ '=~? "\\vstr|bytes|doc\\S"'
let s:skip_after_opening_paren = 'synIDattr(synID(line("."), col("."), 0), "name") ' .
\ '=~? "\\vcomment|jedi\\S"'

Expand Down Expand Up @@ -421,3 +424,224 @@ function! GetPythonPEPIndent(lnum)

return s:indent_like_previous_line(a:lnum)
endfunction

function! s:SearchPosWithSkip(pattern, flags, skip, stopline)
"
" Returns true if a match is found for {pattern}, but ignores matches
" where {skip} evaluates to false. This allows you to do nifty things
" like, say, only matching outside comments, only on odd-numbered lines,
" or whatever else you like.
"
" Mimics the built-in search() function, but adds a {skip} expression
" like that available in searchpair() and searchpairpos().
" (See the Vim help on search() for details of the other parameters.)
"
" Note the current position, so that if there are no unskipped
" matches, the cursor can be restored to this location.
"
let l:flags = a:flags
let l:movepos = getpos('.')
let l:firstmatch = []
let l:pos = [0, 0, 0, 0]

" Loop as long as {pattern} continues to be found.
"
while search(a:pattern, l:flags, a:stopline) > 0
if l:firstmatch == []
let l:firstmatch = getpos('.')
let l:flags = substitute(l:flags, 'c', '', '')
elseif l:firstmatch == getpos('.')
break
endif

" If {skip} is true, ignore this match and continue searching.
"
if eval(a:skip)
continue
endif

" If we get here, {pattern} was found and {skip} is false,
" so this is a match we don't want to ignore. Update the
" match position and stop searching.
"
let l:pos = getpos('.')
let l:movepos = getpos('.')
break

endwhile

" Jump to the position of the unskipped match, or to the original
" position if there wasn't one.
"

call setpos('.', l:movepos)
return [l:pos[1], l:pos[2]]

endfunction

function! s:IsInComment(lnum, col)
return synIDattr(synID(a:lnum, a:col, 0), 'name') =~? 'comment'
endfunction

function! s:IsInMultilineString(lnum, col)
return synIDattr(synID(a:lnum, a:col, 0), 'name') =~? 'multiline'
endfunction


function! GetPythonPEPFormat(lnum, count)
let l:tw = &textwidth ? &textwidth : 79
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 78 by default IIRC.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The maximum line length according to PEP8 is 79. https://www.python.org/dev/peps/pep-0008/#maximum-line-length


let l:winview = winsaveview()

let l:count = a:count
let l:first_char = indent(a:lnum) + 1

if mode() ==? 'i' " gq was not pressed, but tw was set
return 1
endif

if virtcol('$') <= l:tw + 1 " No need for gq
return 1
endif

let l:twplus1 = s:VirtcolToCol(a:lnum, l:tw + 1)
let l:twminus1 = s:VirtcolToCol(a:lnum, l:tw - 1)

call cursor(a:lnum, l:twplus1)
let l:orig_breakpoint = searchpos(' ', 'bcW', a:lnum)
let l:orig_breakpointview = winsaveview()
" If breaking inside string extra space is needed for the space and quote
call cursor(a:lnum, l:twminus1)
let l:better_orig_breakpoint = searchpos(' ', 'bcW', a:lnum)
let l:better_orig_breakpointview = winsaveview()
call cursor(a:lnum, l:twplus1)
let l:breakpoint = s:SearchPosWithSkip(' ', 'bcW', s:skip_string, a:lnum)
let l:breakpointview = winsaveview()

" No need for special treatment, normal gq handles docstrings fine
if s:IsInMultilineString(l:orig_breakpoint[0], l:orig_breakpoint[1])
\|| s:IsInComment(l:orig_breakpoint[0], l:orig_breakpoint[1])
call winrestview(l:winview)
return 1
endif

" If the match is at the indent level try breaking after string as last
" resort
" if l:breakpoint[1] <= indent(a:lnum)
" call cursor(a:lnum, l:tw + 1)
" "Search for a space that is not trailing whitespace
" let l:breakpoint = s:SearchPosWithSkip(' [^ ]', 'cW', s:skip_string, a:lnum)
" endif


"" Fallback to old behaviour when nothing is found
" if l:breakpoint[1] == 0
" call winrestview(l:winview)
" return 1
" endif
" let l:breakpoint_brackets = s:isBetweenBrackets(l:breakpointview)
" let l:orig_breakpoint_brackets = s:isBetweenBrackets(l:orig_breakpointview)

"echom s:isBetweenPair('(', ')', l:breakpointview, s:skip_string)
"echom s:isBetweenPair('{', '}', l:breakpointview, s:skip_string)
"echom s:isBetweenPair('\[', '\]', l:breakpointview, s:skip_string)
"echom s:isBetweenPair('(', ')', l:orig_breakpointview, s:skip_string)
"echom s:isBetweenPair('{', '}', l:orig_breakpointview, s:skip_string)
"echom s:isBetweenPair('\[', '\]', l:orig_breakpointview, s:skip_string)
"echom 'new'
"echom l:breakpoint[1]
"echom 'orig'
"echom l:orig_breakpoint[1]

"Order of breaking:
" 1. Only break on breakpoints that have actually been found
" 2. Breaking inside brackets is preferred (no backslash needed)
" 3. Breking outside a string is preferred (new breakpoint)
" 4. Possible future: breaking at space is preferred
if l:breakpoint[1] > indent(a:lnum) && s:isBetweenBrackets(l:breakpointview)
"echom 'between brackets'
call winrestview(l:breakpointview)
call feedkeys("r\<CR>", 'n')
else
"echom 'zooooi'
if l:better_orig_breakpoint[1] > indent(a:lnum)
\ && s:isBetweenBrackets(l:better_orig_breakpointview)
" echom 'doing the quotes'
call winrestview(l:better_orig_breakpointview)
let l:pos_start_string =
\ s:SearchPosWithSkip('.', 'bcW', s:skip_string, a:lnum)
call winrestview(l:better_orig_breakpointview)
" Find the type of start quote of the string
" and skip charactars at the start of the string like b/u/r
let l:extra_chars = 0
let l:cur_char = getline(a:lnum)[l:pos_start_string[1]]
while l:cur_char !=# '"' && l:cur_char !=# "'"
let l:extra_chars += 1
let l:cur_char = getline(a:lnum)[l:pos_start_string[1]
\ + l:extra_chars]
endwhile


if l:cur_char ==# '"'
call feedkeys("a\"\<CR>\"\<esc>", 'n')
else
call feedkeys("a'\<CR>'\<esc>", 'n')
endif
elseif l:breakpoint[1] > indent(a:lnum)
call winrestview(l:breakpointview)

let l:next_char = getline(a:lnum)[l:breakpoint[1]]
if l:next_char ==# '{' || l:next_char ==# '(' || l:next_char ==# '['
"Add a newline after the bracket
call feedkeys("la\<CR>\<esc>", 'n')
elseif !s:isBetweenBrackets(l:breakpointview)
"Add a bracket when this is not present yet
call winrestview(l:breakpointview)
call feedkeys("a(\<esc>", 'n')
else
"Otherwise fall back to a backslash
call winrestview(l:breakpointview)
call feedkeys("a\\\<CR>\<esc>", 'n')
endif
else
call cursor(a:lnum, l:twplus1)
"Search for a space that is not trailing whitespace
let l:afterbreakpoint = s:SearchPosWithSkip(' [^ ]', 'cW', s:skip_string, a:lnum)

if l:afterbreakpoint[0] != 0
call feedkeys("r\<CR>", 'n')
else
"echom 'fallling back to old method'
call winrestview(l:winview)
return 1
endif
endif
endif

call feedkeys('gqq', 'n')
endfunction

function! s:isBetweenBrackets(winview)
" Check if match is inside brackets
let l:skip = s:skip_string
return s:isBetweenPair('(', ')', a:winview, l:skip)
\ || s:isBetweenPair('{', '}', a:winview, l:skip)
\ || s:isBetweenPair('\[', '\]', a:winview, l:skip)
endfunction

function! s:isBetweenPair(left, right, winview, skip)
call winrestview(a:winview)
let l:bracket = searchpairpos(a:left, '', a:right, 'bW', a:skip)
return l:bracket[0] != 0
endfunction

function! s:VirtcolToCol(lnum, cnum)
let l:last = virtcol([a:lnum, '$'])
let l:cnum = a:cnum
let l:vcol = virtcol([a:lnum, l:cnum])
while l:vcol <= a:cnum && l:vcol < l:last
let l:cnum += 1
let l:vcol = virtcol([a:lnum, l:cnum])
endwhile
return l:cnum - 1
endfunction