📄 common.py
字号:
from __future__ import generators
from bike.globals import *
from bike.parsing.fastparserast import getRoot, Function, Class, Module, getModule
from bike.parsing.parserutils import generateLogicalLines, makeLineParseable, UnbalancedBracesException, generateLogicalLinesAndLineNumbers
from bike.parsing.newstuff import getSourceNodesContainingRegex
from bike.parsing import visitor
from bike import log
import compiler
from compiler.ast import Getattr, Name
import re
class Match:
def __repr__(self):
return ",".join([self.filename, str(self.lineno), str(self.colno),
str(self.confidence)])
def __eq__(self,other):
if self is None or other is None:
return False
return self.filename == other.filename and \
self.lineno == other.lineno and \
self.colno == other.colno
def getScopeForLine(sourceNode, lineno):
scope = None
childnodes = sourceNode.getFlattenedListOfFastParserASTNodes()
if childnodes == []:
return sourceNode.fastparseroot #module node
scope = sourceNode.fastparseroot
for node in childnodes:
if node.linenum > lineno: break
scope = node
if scope.getStartLine() != scope.getEndLine(): # is inline
while scope.getEndLine() <= lineno:
scope = scope.getParent()
return scope
# global from the perspective of 'contextFilename'
def globalScanForMatches(contextFilename, matchFinder, targetname):
for sourcenode in getSourceNodesContainingRegex(targetname, contextFilename):
print >> log.progress, "Scanning", sourcenode.filename
searchscope = sourcenode.fastparseroot
for match in scanScopeForMatches(sourcenode,searchscope,
matchFinder,targetname):
yield match
def scanScopeForMatches(sourcenode,scope,matchFinder,targetname):
lineno = scope.getStartLine()
for line in generateLogicalLines(scope.getMaskedLines()):
if line.find(targetname) != -1:
doctoredline = makeLineParseable(line)
try:
ast = compiler.parse(doctoredline)
except :
print >> log.warning , 'Error parsing: %s' % doctoredline
print >> log.warning , 'Params: \n--%s\n--%s\n--%s\n--%s' % (sourcenode,scope,matchFinder,targetname)
raise
scope = getScopeForLine(sourcenode, lineno)
matchFinder.reset(line)
matchFinder.setScope(scope)
matches = visitor.walk(ast, matchFinder).getMatches()
for index, confidence in matches:
match = Match()
match.filename = sourcenode.filename
match.sourcenode = sourcenode
x, y = indexToCoordinates(line, index)
match.lineno = lineno+y
match.colno = x
match.colend = match.colno+len(targetname)
match.confidence = confidence
yield match
lineno+=line.count("\n")
def walkLinesContainingStrings(scope,astWalker,targetnames):
lineno = scope.getStartLine()
for line in generateLogicalLines(scope.getMaskedLines()):
if lineContainsOneOf(line,targetnames):
doctoredline = makeLineParseable(line)
ast = compiler.parse(doctoredline)
astWalker.lineno = lineno
matches = visitor.walk(ast, astWalker)
lineno+=line.count("\n")
def lineContainsOneOf(line,targetnames):
for name in targetnames:
if line.find(name) != -1:
return True
return False
# translates an idx in a logical line into physical line coordinates
# returns x and y coords
def indexToCoordinates(src, index):
y = src[: index].count("\n")
startOfLineIdx = src.rfind("\n", 0, index)+1
x = index-startOfLineIdx
return x, y
# interface for MatchFinder classes
# implement the visit methods
class MatchFinder:
def setScope(self, scope):
self.scope = scope
def reset(self, line):
self.matches = []
self.words = re.split("(\w+)", line) # every other one is a non word
self.positions = []
i = 0
for word in self.words:
self.positions.append(i)
#if '\n' in word: # handle newlines
# i = len(word[word.index('\n')+1:])
#else:
i+=len(word)
self.index = 0
def getMatches(self):
return self.matches
# need to visit childnodes in same order as they appear
def visitPrintnl(self,node):
if node.dest:
self.visit(node.dest)
for n in node.nodes:
self.visit(n)
def visitName(self, node):
self.popWordsUpTo(node.name)
def visitClass(self, node):
self.popWordsUpTo(node.name)
for base in node.bases:
self.visit(base)
def zipArgs(self, argnames, defaults):
"""Takes a list of argument names and (possibly a shorter) list of
default values and zips them into a list of pairs (argname, default).
Defaults are aligned so that the last len(defaults) arguments have
them, and the first len(argnames) - len(defaults) pairs have None as a
default.
"""
fixed_args = len(argnames) - len(defaults)
defaults = [None] * fixed_args + list(defaults)
return zip(argnames, defaults)
def visitFunction(self, node):
self.popWordsUpTo(node.name)
for arg, default in self.zipArgs(node.argnames, node.defaults):
self.popWordsUpTo(arg)
if default is not None:
self.visit(default)
self.visit(node.code)
def visitGetattr(self,node):
self.visit(node.expr)
self.popWordsUpTo(node.attrname)
def visitAssName(self, node):
self.popWordsUpTo(node.name)
def visitAssAttr(self, node):
self.visit(node.expr)
self.popWordsUpTo(node.attrname)
def visitImport(self, node):
for name, alias in node.names:
for nameelem in name.split("."):
self.popWordsUpTo(nameelem)
if alias is not None:
self.popWordsUpTo(alias)
def visitFrom(self, node):
for elem in node.modname.split("."):
self.popWordsUpTo(elem)
for name, alias in node.names:
self.popWordsUpTo(name)
if alias is not None:
self.popWordsUpTo(alias)
def visitLambda(self, node):
for arg, default in self.zipArgs(node.argnames, node.defaults):
self.popWordsUpTo(arg)
if default is not None:
self.visit(default)
self.visit(node.code)
def visitGlobal(self, node):
for name in node.names:
self.popWordsUpTo(name)
def popWordsUpTo(self, word):
if word == "*":
return # won't be able to find this
try:
posInWords = self.words.index(word)
except ValueError:
print >> log.warning , 'ValueError raised (communicate to bicycle repair man plugin).'
print >> log.warning , 'code that raised error (commom.py): posInWords = self.words.index(word)'
try:
print >> log.warning , 'WORD: %s'%word
except TypeError:
print >> log.warning , 'Unable to get word.'
print >> log.warning , 'SELF.WORDS: %s'%self.words
return
idx = self.positions[posInWords]
self.words = self.words[posInWords+1:]
self.positions = self.positions[posInWords+1:]
def appendMatch(self,name,confidence=100):
idx = self.getNextIndexOfWord(name)
self.matches.append((idx, confidence))
def getNextIndexOfWord(self,name):
return self.positions[self.words.index(name)]
class CouldNotLocateNodeException(Exception): pass
def translateSourceCoordsIntoASTNode(filename,lineno,col):
module = getModule(filename)
maskedlines = module.getMaskedModuleLines()
lline,backtrackchars = getLogicalLine(module, lineno)
doctoredline = makeLineParseable(lline)
ast = compiler.parse(doctoredline)
idx = backtrackchars+col
nodefinder = ASTNodeFinder(lline,idx)
node = compiler.walk(ast, nodefinder).node
if node is None:
raise CouldNotLocateNodeException("Could not translate editor coordinates into source node")
return node
def getLogicalLine(module, lineno):
# we know that the scope is the start of a logical line, so
# we search from there
scope = getScopeForLine(module.getSourceNode(), lineno)
linegenerator = \
module.generateLinesWithLineNumbers(scope.getStartLine())
for lline,llinenum in \
generateLogicalLinesAndLineNumbers(linegenerator):
if llinenum > lineno:
break
prevline = lline
prevlinenum = llinenum
backtrackchars = 0
for i in range(prevlinenum,lineno):
backtrackchars += len(module.getSourceNode().getLines()[i-1])
return prevline, backtrackchars
class ASTNodeFinder(MatchFinder):
# line is a masked line of text
# lineno and col are coords
def __init__(self,line,col):
self.line = line
self.col = col
self.reset(line)
self.node = None
def visitName(self,node):
if self.checkIfNameMatchesColumn(node.name):
self.node = node
self.popWordsUpTo(node.name)
def visitGetattr(self,node):
self.visit(node.expr)
if self.checkIfNameMatchesColumn(node.attrname):
self.node = node
self.popWordsUpTo(node.attrname)
def visitFunction(self, node):
if self.checkIfNameMatchesColumn(node.name):
self.node = node
self.popWordsUpTo(node.name)
for arg, default in self.zipArgs(node.argnames, node.defaults):
if self.checkIfNameMatchesColumn(arg):
self.node = Name(arg)
self.popWordsUpTo(arg)
if default is not None:
self.visit(default)
self.visit(node.code)
visitAssName = visitName
visitAssAttr = visitGetattr
def visitClass(self, node):
if self.checkIfNameMatchesColumn(node.name):
self.node = node
self.popWordsUpTo(node.name)
for base in node.bases:
self.visit(base)
def checkIfNameMatchesColumn(self,name):
idx = self.getNextIndexOfWord(name)
#print "name",name,"idx",idx,"self.col",self.col
if idx <= self.col and idx+len(name) > self.col:
return 1
return 0
def visitFrom(self, node):
for elem in node.modname.split("."):
self.popWordsUpTo(elem)
for name, alias in node.names:
if self.checkIfNameMatchesColumn(name):
self.node = self._manufactureASTNodeFromFQN(name)
return
self.popWordsUpTo(name)
if alias is not None:
self.popWordsUpTo(alias)
# gets round the fact that imports etc dont contain nested getattr
# nodes for fqns (e.g. import a.b.bah) by converting the fqn
# string into a getattr instance
def _manufactureASTNodeFromFQN(self,fqn):
if "." in fqn:
assert 0, "getattr not supported yet"
else:
return Name(fqn)
def isAMethod(scope,node):
return isinstance(node,compiler.ast.Function) and \
isinstance(scope,Class)
def convertNodeToMatchObject(node,confidence=100):
m = Match()
m.sourcenode = node.module.getSourceNode()
m.filename = node.filename
if isinstance(node,Module):
m.lineno = 1
m.colno = 0
elif isinstance(node,Class) or isinstance(node,Function):
m.lineno = node.getStartLine()
m.colno = node.getColumnOfName()
m.confidence = confidence
return m
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -