mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 06:10:05 +08:00
302 lines
9.5 KiB
VimL
302 lines
9.5 KiB
VimL
function! python_imports#filename2module(filename)
|
|
" Figure out the dotted module name of the given filename
|
|
|
|
" Look at the file name of the module that contains this tag. Find the
|
|
" nearest parent directory that does not have __init__.py. Assume it is
|
|
" directly included in PYTHONPATH.
|
|
let pkg = fnamemodify(a:filename, ":p")
|
|
let root = fnamemodify(pkg, ":h")
|
|
|
|
" normalize paths
|
|
let pythonPathsNorm = []
|
|
for path in g:pythonPaths
|
|
let path_without_slash = substitute(expand(path), "/$", "", "")
|
|
call add(pythonPathsNorm, path_without_slash)
|
|
endfor
|
|
|
|
let found_dir = ""
|
|
let found_path = ""
|
|
while 1
|
|
if index(pythonPathsNorm, root) != -1
|
|
let found_path = root
|
|
break
|
|
endif
|
|
if found_dir == "" && !filereadable(root . "/__init__.py")
|
|
let found_dir = root
|
|
" note: can't break here! PEP 420 implicit namespace packages don't have __init__.py,
|
|
" so we might find the actual package root in a parent directory beyond this one, via pythonPathsNorm
|
|
endif
|
|
let newroot = fnamemodify(root, ":h")
|
|
if newroot == root
|
|
break
|
|
endif
|
|
let root = newroot
|
|
endwhile
|
|
if found_path != ""
|
|
let root = found_path
|
|
else
|
|
let root = found_dir
|
|
endif
|
|
|
|
let pkg = strpart(pkg, strlen(root))
|
|
" Convert the relative path into a Python dotted module name
|
|
let pkg = substitute(pkg, "\\", "/", "g") " Handle Windows paths
|
|
let pkg = substitute(pkg, "[.]py$", "", "")
|
|
let pkg = substitute(pkg, ".__init__$", "", "")
|
|
let pkg = substitute(pkg, "^/", "", "")
|
|
let pkg = substitute(pkg, "^site-packages/", "", "")
|
|
let pkg = substitute(pkg, "/", ".", "g")
|
|
" Get rid of the last module name if it starts with an underscore, e.g.
|
|
" zope.schema._builtinfields -> zope.schema
|
|
let pkg = substitute(pkg, "[.]_[a-zA-Z0-9_]*$", "", "")
|
|
return pkg
|
|
endfunction
|
|
|
|
function! python_imports#filename2package(filename)
|
|
let module = python_imports#filename2module(a:filename)
|
|
let pkg = python_imports#package_of(module)
|
|
return pkg
|
|
endfunction
|
|
|
|
function! python_imports#package_of(module)
|
|
let pkg = substitute(a:module, '[.]\=[^.]\+$', '', '')
|
|
return pkg
|
|
endfunction
|
|
|
|
function! python_imports#is_stdlib_module(name)
|
|
" Does a:name refer to a standard library module?
|
|
|
|
if has_key(g:pythonBuiltinModules, a:name)
|
|
return 1
|
|
elseif g:pythonStdlibPath == ""
|
|
return 0
|
|
elseif filereadable(g:pythonStdlibPath . "/" . a:name . ".py")
|
|
return 1
|
|
elseif filereadable(g:pythonStdlibPath . "/" . a:name . "/__init__.py")
|
|
return 1
|
|
elseif filereadable(g:pythonStdlibPath . "/lib-dynload/" . a:name . ".so")
|
|
return 1
|
|
elseif filereadable(g:pythonStdlibPath . "/lib-dynload/" . a:name . g:pythonExtModuleSuffix)
|
|
return 1
|
|
else
|
|
return 0
|
|
endif
|
|
endfunction
|
|
|
|
function! python_imports#maybe_reload_config()
|
|
if has('python') || has('python3')
|
|
" XXX: wasteful -- I should check if the file's timestamp has changed
|
|
" instead of parsing it every time
|
|
pyx import python_imports
|
|
pyx python_imports.parse_python_imports_cfg()
|
|
endif
|
|
endfunction
|
|
|
|
function! python_imports#find_place_for_import(pkg, name)
|
|
" Find the appropriate place to insert a "from pkg import name" line.
|
|
" Moves the actual cursor in the actual Vim buffer.
|
|
|
|
" Go to the top (use 'normal gg' because I want to set the ' mark)
|
|
normal gg
|
|
if getline(line(".")) =~ '^"""'
|
|
keepjumps silent! /^"""/ " Skip docstring, if it exists
|
|
else
|
|
keepjumps silent! /^"""/;/^"""/ " Skip docstring, if it exists
|
|
endif
|
|
keepjumps silent! /^import\|^from.*import/ " Find the first import statement
|
|
nohlsearch
|
|
if a:pkg == '__future__'
|
|
return
|
|
endif
|
|
" Find the first empty line after that. NOTE: DO NOT put any comments
|
|
" on the line that says `normal`, or you'll get 24 extra spaces here
|
|
if getline(line(".")) =~ 'import\|"""\|^#'
|
|
keepjumps normal }
|
|
endif
|
|
" Try to find an existing import from the same module, and move to
|
|
" the last one of these
|
|
let pkg = a:pkg
|
|
while pkg != ""
|
|
let stmt = "from ".pkg." " " look for an exact match first
|
|
if search('^' . stmt, 'cnw')
|
|
exec "keepjumps silent! /^".stmt."/;/^\\(".stmt."\\)\\@!/"
|
|
nohlsearch
|
|
break
|
|
endif
|
|
let stmt = "from ".pkg."." " try siblings or subpackages
|
|
if search('^' . stmt, 'cnw')
|
|
exec "keepjumps silent! /^".stmt."/;/^\\(".stmt."\\)\\@!/"
|
|
nohlsearch
|
|
break
|
|
endif
|
|
" If not found, look for imports coming from containing packages
|
|
if pkg =~ '[.]'
|
|
let pkg = substitute(pkg, '[.][^.]*$', '', '')
|
|
else
|
|
break
|
|
endif
|
|
endwhile
|
|
endf
|
|
|
|
if v:version >= 801 || v:version == 800 && has("patch-499")
|
|
function! s:taglist(tag, filename)
|
|
return taglist(a:tag, a:filename)
|
|
endf
|
|
else
|
|
function! s:taglist(tag, filename)
|
|
return taglist(a:tag)
|
|
endf
|
|
endif
|
|
|
|
function! python_imports#import_name(name, here, stay)
|
|
" Add an import statement for 'name'. If 'here' is true, adds the statement
|
|
" on the line above the cursor, if 'here' is false, adds the line to the top
|
|
" of the current file. If 'stay' is true, keeps cursor position, otherwise
|
|
" jumps to the line containing the newly added import statement.
|
|
|
|
call python_imports#maybe_reload_config()
|
|
|
|
" If name is empty, pick up the word under cursor
|
|
if a:name == ""
|
|
let name = expand("<cword>")
|
|
else
|
|
let name = a:name
|
|
endif
|
|
|
|
let alias = l:name
|
|
let l:name = get(g:pythonImportAliases, alias, alias)
|
|
|
|
" Look for hardcoded names
|
|
if has_key(g:pythonImports, l:name)
|
|
let pkg = g:pythonImports[l:name]
|
|
elseif python_imports#is_stdlib_module(l:name)
|
|
let pkg = ''
|
|
else
|
|
" Let's see if we have one tag, or multiple tags (in which case we'll
|
|
" let the user decide)
|
|
let tag_rx = "^\\C" . l:name . "\\([.]py\\)\\=$"
|
|
let found = s:taglist(tag_rx, expand("%"))
|
|
if found == []
|
|
" Give up and bail out
|
|
echohl Error | echomsg "Tag not found:" l:name | echohl None
|
|
return
|
|
elseif len(found) == 1
|
|
" Only one name found, we can skip the selection menu and the
|
|
" whole costly procedure of opening split windows.
|
|
let pkg = python_imports#filename2module(found[0].filename)
|
|
if found[0].kind == 'F'
|
|
" importing an entire module
|
|
let pkg = python_imports#package_of(pkg)
|
|
endif
|
|
else
|
|
" Try to jump to the tag in a new window
|
|
let v:errmsg = ""
|
|
let l:errmsg = ""
|
|
let l:oldfile = expand('%')
|
|
let l:oldswb = &switchbuf
|
|
set switchbuf=split
|
|
let l:oldwinnr = winnr()
|
|
try
|
|
exec "stjump /" . tag_rx
|
|
catch
|
|
let l:errmsg = v:errmsg
|
|
finally
|
|
let &switchbuf = l:oldswb
|
|
endtry
|
|
if l:errmsg != ""
|
|
" Something bad happened (maybe the other file is opened in a
|
|
" different vim instance and there's a swap file)
|
|
if l:oldfile != expand('%')
|
|
close
|
|
exec l:oldwinnr "wincmd w"
|
|
endif
|
|
return
|
|
endif
|
|
if l:oldfile == expand('%')
|
|
" Either the user aborted the tag jump, or the tag exists in
|
|
" the same file, and therefore import is pointless
|
|
return
|
|
endif
|
|
" Look at the file name of the module that contains this tag. Find the
|
|
" nearest parent directory that does not have __init__.py. Assume it is
|
|
" directly included in PYTHONPATH.
|
|
let pkg = python_imports#filename2module(expand("%"))
|
|
let nonfile_tags = found->filter({idx, val -> val.kind != 'F'})
|
|
if expand("%:t") == l:name . ".py" && nonfile_tags == []
|
|
let pkg = python_imports#package_of(pkg)
|
|
endif
|
|
" Close the window containing the tag
|
|
close
|
|
" Return to the right window
|
|
exec l:oldwinnr "wincmd w"
|
|
endif
|
|
endif
|
|
|
|
if pkg == ""
|
|
let line_to_insert = 'import ' . l:name
|
|
elseif pkg == "__future__" && l:name == "print"
|
|
let line_to_insert = 'from __future__ import print_function'
|
|
else
|
|
let line_to_insert = 'from ' . pkg . ' import ' . l:name
|
|
endif
|
|
if l:alias != l:name
|
|
let line_to_insert .= ' as ' . l:alias
|
|
endif
|
|
|
|
" Find the place for adding the import statement
|
|
if !a:here
|
|
if search('^' . line_to_insert . '$', 'bcnw')
|
|
" import already exists
|
|
redraw
|
|
echomsg l:name . " is already imported"
|
|
return
|
|
endif
|
|
call python_imports#find_place_for_import(pkg, l:name)
|
|
endif
|
|
" Find out the indentation of the current line
|
|
let indent = matchstr(getline("."), "^[ \t]*\\%(>>> \\)\\=")
|
|
" Check if we're using parenthesized imports already
|
|
let prev_line = getline(line(".")-1)
|
|
let stopline = 0
|
|
if indent != "" && prev_line == 'from ' . pkg . ' import ('
|
|
if l:alias != l:name
|
|
let line_to_insert = l:name . ' as ' . l:alias . ','
|
|
else
|
|
let line_to_insert = l:name . ','
|
|
endif
|
|
let stopline = search(")", "nW")
|
|
elseif indent != "" && prev_line =~ '^from .* import ('
|
|
silent! /)/+1
|
|
nohlsearch
|
|
if line(".") == line("$") && getline(line(".")-1) !~ ')'
|
|
put =''
|
|
endif
|
|
let indent = ""
|
|
endif
|
|
let line_to_insert = indent . line_to_insert
|
|
" Double check with indent / parenthesized form
|
|
if !a:here && search('^' . line_to_insert . '$', 'cnw', stopline)
|
|
" import already exists
|
|
redraw
|
|
echomsg l:name . " is already imported"
|
|
return
|
|
endif
|
|
" Add the import statement
|
|
put! =line_to_insert
|
|
" Adjust import location with isort if possible
|
|
if !a:here && g:pythonImportsUseAleFix && exists(":ALEFix") == 2
|
|
ALEFix isort
|
|
endif
|
|
" Jump back if possible
|
|
if a:stay
|
|
normal ``
|
|
endif
|
|
" Refresh ALE because otherwise it gets all confused for a bit
|
|
if exists(":ALELint") == 2
|
|
if exists(":ALEResetBuffer") == 2
|
|
ALEResetBuffer
|
|
endif
|
|
ALELint
|
|
endif
|
|
endf
|