mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-01-24 06:20:05 +08:00
700 lines
26 KiB
VimL
700 lines
26 KiB
VimL
|
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||
|
" File: autoload/pythonsense.vim
|
||
|
" Author: Jeet Sukumaran
|
||
|
"
|
||
|
" Copyright: (C) 2018 Jeet Sukumaran
|
||
|
"
|
||
|
" License: Permission is hereby granted, free of charge, to any person obtaining
|
||
|
" a copy of this software and associated documentation files (the
|
||
|
" "Software"), to deal in the Software without restriction, including
|
||
|
" without limitation the rights to use, copy, modify, merge, publish,
|
||
|
" distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
" permit persons to whom the Software is furnished to do so, subject to
|
||
|
" the following conditions:
|
||
|
"
|
||
|
" The above copyright notice and this permission notice shall be included
|
||
|
" in all copies or substantial portions of the Software.
|
||
|
"
|
||
|
" THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||
|
" OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
" MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||
|
" IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||
|
" CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||
|
" TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||
|
"
|
||
|
" SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
"
|
||
|
" Credits: - pythontextobj.vim by Nat Williams (https://github.com/natw/vim-pythontextobj)
|
||
|
" - chapa.vim by Alfredo Deza (https://github.com/alfredodeza/chapa.vim)
|
||
|
" - indentobj by Austin Taylor (https://github.com/austintaylor/vim-indentobject)
|
||
|
" - Python Docstring Text Objects by gfixler (https://pastebin.com/u/gfixler)
|
||
|
" - vim-indent-object by Michael Smith (http://github.com/michaeljsmith/vim-indent-object)
|
||
|
"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
|
||
|
|
||
|
" Support Functions {{{1
|
||
|
function! pythonsense#trawl_search(pattern, start_line, fwd)
|
||
|
let current_line = a:start_line
|
||
|
let lastline = line('$')
|
||
|
if a:fwd
|
||
|
let stepvalue = 1
|
||
|
else
|
||
|
let stepvalue = -1
|
||
|
endif
|
||
|
while (current_line > 0 && current_line <= lastline)
|
||
|
let line_text = getline(current_line)
|
||
|
let m = match(line_text, a:pattern)
|
||
|
if m >= 0
|
||
|
return current_line
|
||
|
endif
|
||
|
let current_line = current_line + stepvalue
|
||
|
endwhile
|
||
|
return 0
|
||
|
endfunction
|
||
|
" }}}1
|
||
|
|
||
|
" Python (Statement) Text Objects {{{1
|
||
|
" Based on:
|
||
|
" - https://github.com/natw/vim-pythontextobj
|
||
|
" - https://github.com/alfredodeza/chapa.vim
|
||
|
" - https://github.com/austintaylor/vim-indentobject
|
||
|
" - http://github.com/michaeljsmith/vim-indent-object
|
||
|
|
||
|
" Select an object ("class"/"function")
|
||
|
let s:pythonsense_obj_start_line = -1
|
||
|
let s:pythonsense_obj_end_line = -1
|
||
|
function! pythonsense#select_named_object(obj_name, inner, range)
|
||
|
" Is this a new selection?
|
||
|
let new_vis = 0
|
||
|
let new_vis = new_vis || s:pythonsense_obj_start_line != a:range[0]
|
||
|
let new_vis = new_vis || s:pythonsense_obj_end_line != a:range[1]
|
||
|
|
||
|
" store current range
|
||
|
let s:pythonsense_obj_start_line = a:range[0]
|
||
|
let s:pythonsense_obj_end_line = a:range[1]
|
||
|
|
||
|
" Repeatedly increase the scope of the selection.
|
||
|
let cnt = 1
|
||
|
let scan_start_line = s:pythonsense_obj_start_line
|
||
|
while scan_start_line > 0
|
||
|
if getline(scan_start_line) !~ '^\s*$'
|
||
|
break
|
||
|
endif
|
||
|
let scan_start_line -= 1
|
||
|
endwhile
|
||
|
if scan_start_line == 0
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
let obj_max_indent_level = -1
|
||
|
|
||
|
while cnt > 0
|
||
|
let current_line_nr = scan_start_line
|
||
|
|
||
|
let [obj_start_line, obj_end_line] = pythonsense#get_object_line_range(a:obj_name, obj_max_indent_level, current_line_nr, s:pythonsense_obj_end_line, a:inner)
|
||
|
if obj_start_line == -1
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
|
||
|
let is_changed = 0
|
||
|
let is_changed = is_changed || s:pythonsense_obj_start_line != obj_start_line
|
||
|
let is_changed = is_changed || s:pythonsense_obj_end_line != obj_end_line
|
||
|
if new_vis
|
||
|
let is_changed = 1
|
||
|
endif
|
||
|
|
||
|
let s:pythonsense_obj_start_line = obj_start_line
|
||
|
let s:pythonsense_obj_end_line = obj_end_line
|
||
|
|
||
|
" If there was no change, then don't decrement the count (it didn't
|
||
|
" count because it didn't do anything).
|
||
|
if is_changed
|
||
|
let cnt = cnt - 1
|
||
|
else
|
||
|
" no change to selection;
|
||
|
" move to line above selection and try again
|
||
|
if scan_start_line == 0
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
let [min_indent, max_indent] = pythonsense#get_minmax_indent_count('\(class\|def\|async def\)', scan_start_line, obj_end_line)
|
||
|
if min_indent == 0
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
let obj_max_indent_level = min_indent - 1
|
||
|
let scan_start_line -= 1
|
||
|
endif
|
||
|
endwhile
|
||
|
|
||
|
" select range
|
||
|
if obj_end_line >= obj_start_line
|
||
|
exec obj_start_line
|
||
|
execute "normal! V" . obj_end_line . "G"
|
||
|
return [obj_start_line, obj_end_line]
|
||
|
else
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_object_line_range(obj_name, obj_max_indent_level, line_range_start, line_range_end, inner)
|
||
|
" find definition line
|
||
|
let current_line_nr = a:line_range_start
|
||
|
if a:line_range_start == a:line_range_end
|
||
|
let search_past_decorator_last_line = line("$")
|
||
|
else
|
||
|
let search_past_decorator_last_line = a:line_range_end
|
||
|
endif
|
||
|
|
||
|
while current_line_nr <= search_past_decorator_last_line
|
||
|
if getline(current_line_nr) !~ '^\s*@.*$'
|
||
|
break
|
||
|
end
|
||
|
let current_line_nr += 1
|
||
|
endwhile
|
||
|
if current_line_nr > search_past_decorator_last_line
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
if current_line_nr > a:line_range_end
|
||
|
let effective_line_range_end = current_line_nr
|
||
|
else
|
||
|
let effective_line_range_end = a:line_range_end
|
||
|
endif
|
||
|
let obj_start_line = pythonsense#get_named_python_obj_start_line_nr(a:obj_name, a:obj_max_indent_level, current_line_nr, 0)
|
||
|
" no object definition line in file
|
||
|
if (! obj_start_line)
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
let obj_header_line = obj_start_line
|
||
|
let obj_header_indent = pythonsense#get_line_indent_count(obj_header_line)
|
||
|
if obj_header_indent > 0
|
||
|
let obj_header_indent -= 1
|
||
|
endif
|
||
|
|
||
|
let obj_end_line = pythonsense#get_object_end_line_nr(obj_start_line, obj_start_line, a:inner)
|
||
|
|
||
|
" in case of a class definition, the parentheses are optional
|
||
|
if a:obj_name == "def"
|
||
|
let pattern = '^[^#]*)[^#]*:\(\s*$\|\s*#.*$\)'
|
||
|
else
|
||
|
let pattern = '^[^#]*)\?[^#]*:\(\s*$\|\s*#.*$\)'
|
||
|
endif
|
||
|
|
||
|
if (a:inner)
|
||
|
" find class/function body
|
||
|
let inner_start_line = obj_start_line
|
||
|
while inner_start_line <= line('$')
|
||
|
if getline(inner_start_line) =~# pattern
|
||
|
break
|
||
|
endif
|
||
|
let inner_start_line += 1
|
||
|
endwhile
|
||
|
if inner_start_line <= line('$')
|
||
|
let obj_start_line = inner_start_line + 1
|
||
|
endif
|
||
|
else
|
||
|
" include decorators
|
||
|
let dec_line = pythonsense#get_start_decorators_line_nr(obj_start_line)
|
||
|
if dec_line < obj_start_line
|
||
|
let obj_start_line = dec_line
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
" This is an ugly hack to deal with (some) specially indented cases
|
||
|
" (especially when searching for a 'class' while inside a non-class member
|
||
|
" function, or when searching for a 'def' with nothing but class
|
||
|
" definitions above)
|
||
|
" Make sure there is no statement line with a lower indentation than the
|
||
|
" definition line in between the current line and the definition line
|
||
|
if a:obj_name == 'class'
|
||
|
let pattern = 'def\|async def'
|
||
|
else
|
||
|
let pattern = 'class'
|
||
|
endif
|
||
|
let pattern = '^\s*[^#]*\s*\k'
|
||
|
if pythonsense#is_statement_encountered_between_two_lines(pattern, obj_header_indent, obj_start_line, current_line_nr)
|
||
|
return [-1, -1]
|
||
|
endif
|
||
|
|
||
|
return [obj_start_line, obj_end_line]
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_object_end_line_nr(obj_start, search_start, inner)
|
||
|
let obj_indent = pythonsense#get_line_indent_count(a:obj_start)
|
||
|
let obj_end = pythonsense#get_next_indent_line_nr(a:search_start, obj_indent)
|
||
|
if a:inner
|
||
|
let obj_end = prevnonblank(obj_end)
|
||
|
endif
|
||
|
return obj_end
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_next_indent_line_nr(search_start, obj_indent)
|
||
|
let line = a:search_start
|
||
|
|
||
|
" Handle multiline definition
|
||
|
let saved_cursor = getcurpos()
|
||
|
call cursor(line, 0)
|
||
|
normal! f(%
|
||
|
let line = line('.')
|
||
|
call setpos('.', saved_cursor)
|
||
|
|
||
|
let lastline = line('$')
|
||
|
while (line > 0 && line <= lastline)
|
||
|
let line = line + 1
|
||
|
if (pythonsense#get_line_indent_count(line) <= a:obj_indent && getline(line) !~ '^\s*$')
|
||
|
return line - 1
|
||
|
endif
|
||
|
endwhile
|
||
|
return lastline
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_start_decorators_line_nr(start)
|
||
|
" Returns the line of the first decorator line above the starting line,
|
||
|
" counting only decorators with the same level.
|
||
|
let start_line_indent = pythonsense#get_line_indent_count(a:start)
|
||
|
let last_non_blank_line = a:start
|
||
|
let current_line = a:start - 1
|
||
|
while current_line > 0
|
||
|
if getline(current_line) !~ '^\s*$'
|
||
|
if pythonsense#get_line_indent_count(current_line) != start_line_indent
|
||
|
break
|
||
|
endif
|
||
|
if getline(current_line) !~ '^\s*@'
|
||
|
break
|
||
|
endif
|
||
|
let last_non_blank_line = current_line
|
||
|
endif
|
||
|
let current_line -= 1
|
||
|
endwhile
|
||
|
return last_non_blank_line
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_line_indent_count(line_nr)
|
||
|
if b:pythonsense_is_tab_indented
|
||
|
let indent_count = matchstrpos(getline(a:line_nr), '^\t\+')[2]
|
||
|
else
|
||
|
let indent_count = indent(a:line_nr)
|
||
|
endif
|
||
|
return indent_count
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_minmax_indent_count(pattern, line_range_start, line_range_end)
|
||
|
let current_line = a:line_range_start
|
||
|
let min_indent_count = -1
|
||
|
let max_indent_count = -1
|
||
|
while current_line <= a:line_range_end && current_line <= line('$')
|
||
|
if getline(current_line) !~ '^\s*$'
|
||
|
if getline(current_line) =~# a:pattern
|
||
|
let current_indent = pythonsense#get_line_indent_count(current_line)
|
||
|
if min_indent_count < 0 || current_indent < min_indent_count
|
||
|
let min_indent_count = current_indent
|
||
|
endif
|
||
|
if max_indent_count < 0 || current_indent > max_indent_count
|
||
|
let max_indent_count = current_indent
|
||
|
endif
|
||
|
endif
|
||
|
endif
|
||
|
let current_line = current_line + 1
|
||
|
endwhile
|
||
|
return [min_indent_count, max_indent_count]
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#is_statement_encountered_between_two_lines(pattern, max_indent, line_range_start, line_range_end)
|
||
|
let current_line = a:line_range_start
|
||
|
while current_line <= a:line_range_end && current_line <= line('$')
|
||
|
if getline(current_line) !~ '^\s*$'
|
||
|
if getline(current_line) =~# a:pattern
|
||
|
let current_indent = pythonsense#get_line_indent_count(current_line)
|
||
|
if a:max_indent > -1 && current_indent < a:max_indent
|
||
|
return 1
|
||
|
endif
|
||
|
endif
|
||
|
endif
|
||
|
let current_line += 1
|
||
|
endwhile
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_indent_char()
|
||
|
if b:pythonsense_is_tab_indented
|
||
|
let indent_char = '\t'
|
||
|
else
|
||
|
let indent_char = " "
|
||
|
endif
|
||
|
return indent_char
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#get_named_python_obj_start_line_nr(obj_name, obj_max_indent_level, start_line, fwd)
|
||
|
let lastline = line('$')
|
||
|
if a:fwd
|
||
|
let stepvalue = 1
|
||
|
else
|
||
|
let stepvalue = -1
|
||
|
endif
|
||
|
let indent_char = pythonsense#get_indent_char()
|
||
|
|
||
|
let current_line = a:start_line
|
||
|
while (current_line > 0 && current_line <= lastline)
|
||
|
" if getline(current_line) !~ '\(^\s*$\|^\s*[#@].*$\)'
|
||
|
if getline(current_line) !~ '^\s*$'
|
||
|
break
|
||
|
endif
|
||
|
let current_line = current_line + stepvalue
|
||
|
endwhile
|
||
|
if a:obj_max_indent_level > -1
|
||
|
let pattern = '^' . indent_char . '\{0,' . a:obj_max_indent_level . '}' . '\(class\|def\|async def\)'
|
||
|
else
|
||
|
let pattern = '^\s*' . '\(class\|def\|async def\)'
|
||
|
endif
|
||
|
if getline(current_line) =~# pattern
|
||
|
if getline(current_line) =~# a:obj_name
|
||
|
return current_line
|
||
|
endif
|
||
|
endif
|
||
|
|
||
|
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
|
||
|
if target_line_indent < 0
|
||
|
let target_line_indent = 0
|
||
|
endif
|
||
|
if a:obj_max_indent_level > -1 && target_line_indent > a:obj_max_indent_level
|
||
|
let target_line_indent = a:obj_max_indent_level
|
||
|
endif
|
||
|
let max_indent = target_line_indent
|
||
|
while (current_line > 0 && current_line <= lastline)
|
||
|
let pattern = '^' . indent_char . '\{0,' . max_indent . '}' . '\(class\|def\|async def\)'
|
||
|
if getline(current_line) =~# pattern
|
||
|
if getline(current_line) =~# a:obj_name
|
||
|
return current_line
|
||
|
else
|
||
|
if a:obj_name != 'class' && pythonsense#get_line_indent_count(current_line) <= max_indent
|
||
|
" encountered a scope block at a lower indent level before
|
||
|
" encountering object definition
|
||
|
return 0
|
||
|
endif
|
||
|
endif
|
||
|
endif
|
||
|
" let m = match(getline(current_line), pattern)
|
||
|
" if m >= 0
|
||
|
" return current_line
|
||
|
" endif
|
||
|
|
||
|
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
|
||
|
" Special case for multiline argument lines, with the parameter being
|
||
|
" indented one step more than the open def/class and the closing
|
||
|
" parenthesis.
|
||
|
let closing_pattern = '^' . indent_char . '*)'
|
||
|
if target_line_indent > 0 && target_line_indent < max_indent && getline(current_line) !~# closing_pattern
|
||
|
let max_indent = target_line_indent
|
||
|
endif
|
||
|
if a:obj_max_indent_level > -1 && target_line_indent > a:obj_max_indent_level
|
||
|
let target_line_indent = a:obj_max_indent_level
|
||
|
endif
|
||
|
|
||
|
let current_line = current_line + stepvalue
|
||
|
endwhile
|
||
|
return 0
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#python_text_object(obj_name, inner, mode)
|
||
|
if a:mode == "o"
|
||
|
let lnrange = [line("."), line(".")]
|
||
|
else
|
||
|
let lnrange = [line("'<"), line("'>")]
|
||
|
endif
|
||
|
let nreps_left = 1 "v:count1
|
||
|
while nreps_left > 0
|
||
|
let lnrange = pythonsense#select_named_object(a:obj_name, a:inner, lnrange)
|
||
|
if lnrange[0] == -1
|
||
|
break
|
||
|
endif
|
||
|
let s:pythonsense_obj_start_line = lnrange[0]
|
||
|
let s:pythonsense_obj_end_line = lnrange[1]
|
||
|
let nreps_left -= 1
|
||
|
endwhile
|
||
|
if lnrange[0] != -1
|
||
|
if has("folding") && foldclosed(line('.')) != -1
|
||
|
" try
|
||
|
" execute "normal! zO"
|
||
|
" catch /E490/ " no fold found
|
||
|
" endtry
|
||
|
execute "normal! zO"
|
||
|
endif
|
||
|
" let s:pythonsense_obj_start_line = -1
|
||
|
" let s:pythonsense_obj_end_line = -1
|
||
|
" execute "normal! \<ESC>gv"
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#python_function_text_object(inner, mode)
|
||
|
call pythonsense#python_text_object('def', a:inner, a:mode)
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#python_class_text_object(inner, mode)
|
||
|
call pythonsense#python_text_object('class', a:inner, a:mode)
|
||
|
endfunction
|
||
|
|
||
|
" }}}1
|
||
|
|
||
|
" Python Docstring Text Objects {{{1
|
||
|
" From: https://pastebin.com/u/gfixler
|
||
|
function! pythonsense#python_docstring_text_object (inner)
|
||
|
" get current line number
|
||
|
let s = line('.')
|
||
|
" climb up to first def/class line, or first line of buffer
|
||
|
while s > 0 && getline(s) !~# '^\s*\(def\|async def\|class\)'
|
||
|
let s = s - 1
|
||
|
endwhile
|
||
|
" set search start to just after def/class line, or on first buffer line
|
||
|
let s = s + 1
|
||
|
" descend lines obj_end_line end of buffer or def/class line
|
||
|
while s < line('$') && getline(s) !~# '^\s*\(def\|async def\|class\)'
|
||
|
" if line begins with optional whitespace followed by '''
|
||
|
if getline(s) =~ "^\\s*'''" || getline(s) =~ '^\s*"""'
|
||
|
if getline(s) =~ "^\\s*'''"
|
||
|
let close_pattern = "'''\\s*$"
|
||
|
else
|
||
|
let close_pattern = '"""\s*$'
|
||
|
endif
|
||
|
" set search end to just after found start line
|
||
|
let e = s + 1
|
||
|
" descend lines obj_end_line end of buffer or def/class line
|
||
|
while e <= line('$') && getline(e) !~# '^\s*\(def\|async def\|class\)'
|
||
|
" if line ends with ''' followed by optional whitespace
|
||
|
if getline(e) =~ close_pattern
|
||
|
" TODO check first for blank lines above to select instead
|
||
|
" for 'around', extend search end through blank lines
|
||
|
if a:inner
|
||
|
let e -= 1
|
||
|
let s += 1
|
||
|
else
|
||
|
let x = e + 1
|
||
|
while x <= line('$') && getline(x) =~ '^\s*$'
|
||
|
let e = x
|
||
|
let x = x + 1
|
||
|
endwhile
|
||
|
endif
|
||
|
" visual line select from start to end (first cursor move)
|
||
|
exe 'norm '.s.'ggV'.e.'gg'
|
||
|
return
|
||
|
endif
|
||
|
" move search end down a line
|
||
|
let e = e + 1
|
||
|
endwhile
|
||
|
endif
|
||
|
" move search start down a line
|
||
|
let s = s + 1
|
||
|
endwhile
|
||
|
endfunction
|
||
|
" }}}1
|
||
|
|
||
|
" Python Movements {{{1
|
||
|
|
||
|
function! pythonsense#move_to_python_object(obj_name, to_end, fwd, vim_mode) range
|
||
|
if a:fwd
|
||
|
let initial_search_start_line = a:lastline
|
||
|
else
|
||
|
let initial_search_start_line = a:firstline
|
||
|
endif
|
||
|
if a:to_end
|
||
|
let target_line = pythonsense#find_end_of_python_object_to_move_to(a:obj_name, initial_search_start_line, a:fwd, v:count1)
|
||
|
else
|
||
|
let target_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, initial_search_start_line, a:fwd, -1, v:count1)
|
||
|
endif
|
||
|
if target_line < 0 || target_line > line('$')
|
||
|
return
|
||
|
endif
|
||
|
let current_column = col('.')
|
||
|
let preserve_col_pos = get(b:, "pythonsense_preserve_col_pos", get(g:, "pythonsense_preserve_col_pos", 0))
|
||
|
let fold_open = ""
|
||
|
if a:vim_mode == "v"
|
||
|
normal! gv
|
||
|
endif
|
||
|
if has("folding") && foldclosed(line('.')) != -1
|
||
|
let fold_open = "zO"
|
||
|
else
|
||
|
let fold_open = ""
|
||
|
endif
|
||
|
try
|
||
|
if preserve_col_pos
|
||
|
execute "normal! " . target_line . "G" . current_column . "|" . preserve_col_pos . fold_open
|
||
|
else
|
||
|
execute "normal! " . target_line . "G^" . fold_open
|
||
|
endif
|
||
|
catch /E490/ " no fold found
|
||
|
endtry
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#find_end_of_python_object_to_move_to(obj_name, start_line, fwd, nreps)
|
||
|
let initial_search_start_line = a:start_line
|
||
|
let effective_start_line = initial_search_start_line
|
||
|
let niters = 0
|
||
|
while niters < 2
|
||
|
let [start_line, nreps_remaining] = pythonsense#find_start_line_for_end_movement(a:obj_name, effective_start_line, a:fwd, a:nreps)
|
||
|
if start_line <= 0
|
||
|
let start_line = 1
|
||
|
endif
|
||
|
let start_of_object_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, start_line, a:fwd, -1, nreps_remaining)
|
||
|
if start_of_object_line < 0 || start_of_object_line > line('$')
|
||
|
return -1
|
||
|
endif
|
||
|
let target_line = pythonsense#get_object_end_line_nr(start_of_object_line, start_of_object_line, 1)
|
||
|
if target_line > 0
|
||
|
\ && niters == 0
|
||
|
\ && (
|
||
|
\ target_line == initial_search_start_line
|
||
|
\ || (a:fwd && target_line < initial_search_start_line)
|
||
|
\ || (!a:fwd && target_line > initial_search_start_line)
|
||
|
\ )
|
||
|
" no change; possibly because we are already at an end boundary;
|
||
|
" make ONE more attempt at trying again
|
||
|
let niters += 1
|
||
|
if a:fwd
|
||
|
let effective_start_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, target_line, a:fwd, -1, 1)
|
||
|
if effective_start_line <= 0
|
||
|
break
|
||
|
endif
|
||
|
else
|
||
|
let prev_obj_indent = pythonsense#get_line_indent_count(start_of_object_line)
|
||
|
let [new_start_line, nreps_remaining] = pythonsense#find_start_line_for_end_movement(a:obj_name, start_of_object_line, a:fwd, a:nreps)
|
||
|
while new_start_line > 0 && getline(new_start_line) =~ '^\s*$'
|
||
|
let new_start_line -= 1
|
||
|
endwhile
|
||
|
if new_start_line <= 0
|
||
|
break
|
||
|
endif
|
||
|
let start_of_object_line = pythonsense#find_start_of_python_object_to_move_to(a:obj_name, new_start_line, a:fwd, prev_obj_indent, nreps_remaining)
|
||
|
let target_line = pythonsense#get_object_end_line_nr(start_of_object_line, start_of_object_line, 1)
|
||
|
break
|
||
|
endif
|
||
|
else
|
||
|
break
|
||
|
endif
|
||
|
endwhile
|
||
|
if a:fwd && target_line < initial_search_start_line
|
||
|
return -1
|
||
|
elseif !a:fwd && target_line > initial_search_start_line
|
||
|
return -1
|
||
|
else
|
||
|
return target_line
|
||
|
endif
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#find_start_of_python_object_to_move_to(obj_name, start_line, fwd, max_indent, nreps)
|
||
|
let current_line = a:start_line
|
||
|
if a:fwd
|
||
|
let stepvalue = 1
|
||
|
else
|
||
|
let stepvalue = -1
|
||
|
endif
|
||
|
let target_pattern = '^\s*' . a:obj_name . '\s\+'
|
||
|
let nreps_left = a:nreps
|
||
|
let start_line = current_line
|
||
|
let target_line = current_line
|
||
|
let scope_block_indent = a:max_indent
|
||
|
if getline(start_line) =~# target_pattern
|
||
|
let start_line += stepvalue
|
||
|
endif
|
||
|
while nreps_left > 0
|
||
|
while start_line > 0 && start_line <= line("$")
|
||
|
if getline(start_line) =~ '^\s*\(class\|def\|async def\)'
|
||
|
let current_line_indent = pythonsense#get_line_indent_count(start_line)
|
||
|
if getline(start_line) =~# target_pattern
|
||
|
if a:max_indent < 0 || current_line_indent < scope_block_indent
|
||
|
let target_line = start_line
|
||
|
break
|
||
|
endif
|
||
|
endif
|
||
|
if scope_block_indent == -1 || current_line_indent < scope_block_indent
|
||
|
let scope_block_indent = current_line_indent
|
||
|
endif
|
||
|
endif
|
||
|
let start_line += stepvalue
|
||
|
endwhile
|
||
|
if start_line < 1 || start_line > line("$")
|
||
|
break
|
||
|
endif
|
||
|
let start_line += stepvalue
|
||
|
let nreps_left -= 1
|
||
|
endwhile
|
||
|
return target_line
|
||
|
endfunction
|
||
|
|
||
|
function! pythonsense#find_start_line_for_end_movement(obj_name, initial_search_start_line, fwd, nreps_requested)
|
||
|
let start_line = a:initial_search_start_line
|
||
|
let nreps_remaining = a:nreps_requested
|
||
|
let target_pattern = '^\s*' . a:obj_name . '\s\+'
|
||
|
let scope_block_indent = -1
|
||
|
let is_found = 0
|
||
|
while start_line > 0
|
||
|
if getline(start_line) =~ '^\s*\(class\|def\|async def\)'
|
||
|
let current_line_indent = pythonsense#get_line_indent_count(start_line)
|
||
|
if getline(start_line) =~ target_pattern
|
||
|
if scope_block_indent == -1 || current_line_indent < scope_block_indent
|
||
|
let is_found = 1
|
||
|
break
|
||
|
endif
|
||
|
endif
|
||
|
if scope_block_indent == -1 || current_line_indent < scope_block_indent
|
||
|
let scope_block_indent = current_line_indent
|
||
|
endif
|
||
|
endif
|
||
|
let start_line -= 1
|
||
|
endwhile
|
||
|
if is_found
|
||
|
if !a:fwd
|
||
|
let start_line -= 1
|
||
|
else
|
||
|
let nreps_remaining -= 1 " skip finding this block
|
||
|
endif
|
||
|
endif
|
||
|
return [start_line, nreps_remaining]
|
||
|
endfunction
|
||
|
|
||
|
" }}}1
|
||
|
|
||
|
" Python Location Information {{{1
|
||
|
function! pythonsense#echo_python_location()
|
||
|
let indent_char = pythonsense#get_indent_char()
|
||
|
let pyloc = []
|
||
|
let current_line = line('.')
|
||
|
let obj_pattern = '\(class\|def\|async def\)'
|
||
|
while current_line > 0
|
||
|
if getline(current_line) !~ '^\s*$'
|
||
|
break
|
||
|
endif
|
||
|
let current_line = current_line - 1
|
||
|
endwhile
|
||
|
if current_line == 0
|
||
|
return
|
||
|
endif
|
||
|
let target_line_indent = pythonsense#get_line_indent_count(current_line)
|
||
|
if target_line_indent < 0
|
||
|
break
|
||
|
endif
|
||
|
let previous_line = current_line
|
||
|
while current_line > 0
|
||
|
let pattern = '^' . indent_char . '\{0,' . target_line_indent . '}' . obj_pattern
|
||
|
let current_line_text = getline(current_line)
|
||
|
if current_line_text =~# pattern
|
||
|
let obj_name = matchstr(current_line_text, '^\s*\(class\|def\|async def\)\s\+\zs\k\+')
|
||
|
if get(g:, "pythonsense_extended_location_info", 1)
|
||
|
let obj_type = matchstr(current_line_text, '^\s*\zs\(class\|def\|async def\)')
|
||
|
call add(pyloc, "(" . obj_type . ":)" . obj_name)
|
||
|
else
|
||
|
call add(pyloc, obj_name)
|
||
|
endif
|
||
|
let target_line_indent = pythonsense#get_line_indent_count(current_line) - 1
|
||
|
endif
|
||
|
if target_line_indent < 0
|
||
|
break
|
||
|
endif
|
||
|
let previous_line = current_line
|
||
|
let current_line = current_line - 1
|
||
|
endwhile
|
||
|
if get(g:, "pythonsense_extended_location_info", 1)
|
||
|
let joiner = " > "
|
||
|
else
|
||
|
let joiner = "."
|
||
|
endif
|
||
|
echo join(reverse(pyloc), joiner)
|
||
|
return
|
||
|
endfunction
|
||
|
" }}}1
|