forked from VimPlug/jedi
Moved jedi files to separate package folder
This commit is contained in:
244
jedi/modules.py
Normal file
244
jedi/modules.py
Normal file
@@ -0,0 +1,244 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
from _compatibility import exec_function
|
||||
|
||||
import re
|
||||
import tokenize
|
||||
import sys
|
||||
import os
|
||||
|
||||
import parsing
|
||||
import builtin
|
||||
import debug
|
||||
import evaluate
|
||||
|
||||
|
||||
class Module(builtin.CachedModule):
|
||||
"""
|
||||
Manages all files, that are parsed and caches them.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param path: The module path of the file.
|
||||
"""
|
||||
def __init__(self, path, source):
|
||||
super(Module, self).__init__(path=path)
|
||||
self.source = source
|
||||
self._line_cache = None
|
||||
|
||||
def _get_source(self):
|
||||
s = self.source
|
||||
del self.source # memory efficiency
|
||||
return s
|
||||
|
||||
|
||||
class ModuleWithCursor(Module):
|
||||
"""
|
||||
Manages all files, that are parsed and caches them.
|
||||
Important are the params source and path, one of them has to
|
||||
be there.
|
||||
|
||||
:param source: The source code of the file.
|
||||
:param path: The module path of the file.
|
||||
:param position: The position, the user is currently in. Only important \
|
||||
for the main file.
|
||||
"""
|
||||
def __init__(self, path, source, position):
|
||||
super(ModuleWithCursor, self).__init__(path, source)
|
||||
self.position = position
|
||||
|
||||
# this two are only used, because there is no nonlocal in Python 2
|
||||
self._line_temp = None
|
||||
self._relevant_temp = None
|
||||
|
||||
# Call the parser already here, because it will be used anyways.
|
||||
# Also, the position is here important (which will not be used by
|
||||
# default), therefore fill the cache here.
|
||||
self._parser = parsing.PyFuzzyParser(source, path, position)
|
||||
|
||||
def get_path_until_cursor(self):
|
||||
""" Get the path under the cursor. """
|
||||
result = self._get_path_until_cursor()
|
||||
self._start_cursor_pos = self._line_temp + 1, self._column_temp
|
||||
return result
|
||||
|
||||
def _get_path_until_cursor(self, start_pos=None):
|
||||
def fetch_line():
|
||||
line = self.get_line(self._line_temp)
|
||||
if self._is_first:
|
||||
self._is_first = False
|
||||
self._line_length = self._column_temp
|
||||
line = line[:self._column_temp]
|
||||
else:
|
||||
self._line_length = len(line)
|
||||
line = line + '\n'
|
||||
# add lines with a backslash at the end
|
||||
while 1:
|
||||
self._line_temp -= 1
|
||||
last_line = self.get_line(self._line_temp)
|
||||
if last_line and last_line[-1] == '\\':
|
||||
line = last_line[:-1] + ' ' + line
|
||||
else:
|
||||
break
|
||||
return line[::-1]
|
||||
|
||||
self._is_first = True
|
||||
if start_pos is None:
|
||||
self._line_temp = self.position[0]
|
||||
self._column_temp = self.position[1]
|
||||
else:
|
||||
self._line_temp, self._column_temp = start_pos
|
||||
|
||||
open_brackets = ['(', '[', '{']
|
||||
close_brackets = [')', ']', '}']
|
||||
|
||||
gen = tokenize.generate_tokens(fetch_line)
|
||||
string = ''
|
||||
level = 0
|
||||
force_point = False
|
||||
try:
|
||||
for token_type, tok, start, end, line in gen:
|
||||
#print 'tok', token_type, tok, force_point
|
||||
if level > 0:
|
||||
if tok in close_brackets:
|
||||
level += 1
|
||||
if tok in open_brackets:
|
||||
level -= 1
|
||||
elif tok == '.':
|
||||
force_point = False
|
||||
elif force_point:
|
||||
# it is reversed, therefore a number is getting recognized
|
||||
# as a floating point number
|
||||
if token_type == tokenize.NUMBER and tok[0] == '.':
|
||||
force_point = False
|
||||
else:
|
||||
break
|
||||
elif tok in close_brackets:
|
||||
level += 1
|
||||
elif token_type in [tokenize.NAME, tokenize.STRING]:
|
||||
force_point = True
|
||||
elif token_type == tokenize.NUMBER:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
|
||||
self._column_temp = self._line_length - end[1]
|
||||
string += tok
|
||||
except tokenize.TokenError:
|
||||
debug.warning("Tokenize couldn't finish", sys.exc_info)
|
||||
|
||||
return string[::-1]
|
||||
|
||||
def get_path_under_cursor(self):
|
||||
"""
|
||||
Return the path under the cursor. If there is a rest of the path left,
|
||||
it will be added to the stuff before it.
|
||||
"""
|
||||
line = self.get_line(self.position[0])
|
||||
after = re.search("[\w\d]*", line[self.position[1]:]).group(0)
|
||||
return self.get_path_until_cursor() + after
|
||||
|
||||
def get_operator_under_cursor(self):
|
||||
line = self.get_line(self.position[0])
|
||||
after = re.match("[^\w\s]+", line[self.position[1]:])
|
||||
before = re.match("[^\w\s]+", line[:self.position[1]][::-1])
|
||||
return (before.group(0) if before is not None else '') \
|
||||
+ (after.group(0) if after is not None else '')
|
||||
|
||||
def get_context(self):
|
||||
pos = self._start_cursor_pos
|
||||
while pos > (1, 0):
|
||||
# remove non important white space
|
||||
line = self.get_line(pos[0])
|
||||
while pos[1] > 0 and line[pos[1] - 1].isspace():
|
||||
pos = pos[0], pos[1] - 1
|
||||
|
||||
try:
|
||||
yield self._get_path_until_cursor(start_pos=pos)
|
||||
except StopIteration:
|
||||
yield ''
|
||||
pos = self._line_temp, self._column_temp
|
||||
|
||||
while True:
|
||||
yield ''
|
||||
|
||||
def get_line(self, line_nr):
|
||||
if not self._line_cache:
|
||||
self._line_cache = self.source.split('\n')
|
||||
|
||||
if line_nr == 0:
|
||||
# This is a fix for the zeroth line. We need a newline there, for
|
||||
# the backwards parser.
|
||||
return ''
|
||||
if line_nr < 0:
|
||||
raise StopIteration()
|
||||
try:
|
||||
return self._line_cache[line_nr - 1]
|
||||
except IndexError:
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
@evaluate.memoize_default([])
|
||||
def sys_path_with_modifications(module):
|
||||
def execute_code(code):
|
||||
c = "import os; from os.path import *; result=%s"
|
||||
variables = {'__file__': module.path}
|
||||
try:
|
||||
exec_function(c % code, variables)
|
||||
except Exception:
|
||||
debug.warning('sys path detected, but failed to evaluate')
|
||||
return None
|
||||
try:
|
||||
return os.path.abspath(variables['result'])
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def check_module(module):
|
||||
try:
|
||||
possible_stmts = module.used_names['path']
|
||||
except KeyError:
|
||||
return builtin.module_find_path
|
||||
|
||||
sys_path = list(builtin.module_find_path) # copy
|
||||
for p in possible_stmts:
|
||||
try:
|
||||
call = p.get_assignment_calls().get_only_subelement()
|
||||
except AttributeError:
|
||||
continue
|
||||
n = call.name
|
||||
if not isinstance(n, parsing.Name) or len(n.names) != 3:
|
||||
continue
|
||||
if n.names[:2] != ('sys', 'path'):
|
||||
continue
|
||||
array_cmd = n.names[2]
|
||||
if call.execution is None:
|
||||
continue
|
||||
exe = call.execution
|
||||
if not (array_cmd == 'insert' and len(exe) == 2 \
|
||||
or array_cmd == 'append' and len(exe) == 1):
|
||||
continue
|
||||
|
||||
if array_cmd == 'insert':
|
||||
exe_type, exe.type = exe.type, parsing.Array.NOARRAY
|
||||
exe_pop = exe.values.pop(0)
|
||||
res = execute_code(exe.get_code())
|
||||
if res is not None:
|
||||
sys_path.insert(0, res)
|
||||
exe.type = exe_type
|
||||
exe.values.insert(0, exe_pop)
|
||||
elif array_cmd == 'append':
|
||||
res = execute_code(exe.get_code())
|
||||
if res is not None:
|
||||
sys_path.append(res)
|
||||
return sys_path
|
||||
|
||||
curdir = os.path.abspath(os.curdir)
|
||||
try:
|
||||
os.chdir(os.path.dirname(module.path))
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
result = check_module(module)
|
||||
|
||||
# cleanup, back to old directory
|
||||
os.chdir(curdir)
|
||||
return result
|
||||
Reference in New Issue
Block a user