def _citdl_expr_from_pos(self, buf, pos, implicit=False, include_forwards=False, DEBUG=False, trg=None, array_as_attr=False): #PERF: Would dicts be faster for all of these? WHITESPACE = tuple(" \t\n\r\v\f") EOL = tuple("\r\n") BLOCKCLOSES = tuple(")}]") STOPOPS = tuple("({[,&+-=!^|%/<>;:#@") EXTRA_STOPOPS_PRECEDING_IDENT = BLOCKCLOSES # Might be others. #TODO: clean this up for LangIntel-usage if implicit: skip_styles = buf.implicit_completion_skip_styles else: skip_styles = buf.completion_skip_styles string_styles = buf.string_styles() comment_styles = buf.comment_styles() #XXX Add sentinel num chars? citdl_expr = [] accessor = buf.accessor i = pos is_udl_buffer = False from codeintel2.udl import UDLBuffer if isinstance(buf, UDLBuffer): # We need to check for udl transition points and not go beyond the # current sub-language, bug 95946. is_udl_buffer = True udl_lang = buf.lang_from_pos(pos) # Move ahead to include forward chars as well # We stop when we go out of the expression or when the expression is # becomes a multiple fragment, i.e. # 'sys.pa<|>th.expanduser' -> 'sys.path' if include_forwards: buf_length = accessor.length() if i < buf_length: max_look_ahead = min(buf_length, i + 100) lastch_was_whitespace = False while i < max_look_ahead: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) if is_udl_buffer and buf.lang_from_style( style) != udl_lang: if DEBUG: print "UDL boundary at pos %d, changed from %r to %r" % ( i, udl_lang, buf.lang_from_style(style)) break if ch in WHITESPACE: lastch_was_whitespace = True elif ch in ".)}]" or ch in STOPOPS: break elif lastch_was_whitespace: break else: lastch_was_whitespace = False i += 1 # Move back to last valid char i -= 1 else: i = buf_length - 1 if DEBUG: if i > pos: print "Including chars from pos %d up to %d" % (pos, i) else: print "No valid chars forward from pos %d, i now: %d" % ( pos, i) # Be careful here, we cannot move from code into a comment, but we # can be in a comment to begin with. first_citdl_expr_style = None first_citdl_expr_style_is_comment = False while i >= 0: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) if is_udl_buffer and buf.lang_from_style(style) != udl_lang: if DEBUG: print "UDL boundary at pos %d, changed from %r to %r" % ( i, udl_lang, buf.lang_from_style(style)) break if ch in WHITESPACE: # drop all whitespace while i >= 0: ch = accessor.char_at_pos(i) if ch in WHITESPACE \ or (ch == '\\' and accessor.char_at_pos(i+1) in EOL): if DEBUG: print "drop whitespace: %r" % accessor.char_at_pos( i) else: break i -= 1 # If there are two whitespace-separated words with no . # in between we're changing expressions: # if foo<|> and ... # def foo<|>(... if i >= 0 and citdl_expr and isident(citdl_expr[-1]) \ and ch != '.': if DEBUG: print "stop at non-dot: %r" % ch break elif style in string_styles: # Convert to string citdl_type = self.citdl_from_literal_type.get("string") if DEBUG: print "found string style, converting to: %s and now " \ "finished" % (citdl_type) if citdl_type: citdl_expr += reversed(citdl_type) break elif style in skip_styles: # drop styles to ignore while i >= 0 and accessor.style_at_pos(i) in skip_styles: if DEBUG: print "drop char of style to ignore: %r"\ % accessor.char_at_pos(i) i -= 1 elif ch in STOPOPS or ( # This check ensures that, for example, we get "foo" instead # of "bar()foo" in the following: # bar() # foo<|>. citdl_expr and citdl_expr[-1] != '.' and ch in EXTRA_STOPOPS_PRECEDING_IDENT): if DEBUG: print "stop at stop-operator %d: %r" % (i, ch) break elif ch in BLOCKCLOSES: if DEBUG: print "found block at %d: %r" % (i, ch) citdl_expr.append(ch) BLOCKS = { # map block close char to block open char ')': '(', ']': '[', '}': '{', } stack = [] # stack of blocks: (<block close char>, <style>) stack.append((ch, style, BLOCKS[ch], i)) i -= 1 num_lines = 0 content_styles = [] while i >= 0: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) content_styles.append(style) if DEBUG: print "finding matching brace: ch %r (%s), stack %r"\ % (ch, ', '.join(buf.style_names_from_style_num(style)), stack) if ch in EOL: num_lines += 1 if num_lines >= 3: if DEBUG: print "stop search for matching brace at 3 line sentinel" break elif ch in BLOCKS and style not in skip_styles: stack.append((ch, style, BLOCKS[ch])) elif ch == stack[-1][2] and style not in skip_styles: #XXX Replace the second test with the following # when LexPython+SilverCity styling bugs are fixed # (spurious 'stderr' problem): # and style == stack[-1][1]: last_frame = stack.pop() if not stack: content_styles.pop() # Drop the thing that matched if array_as_attr and \ all(style in string_styles for style in content_styles): prop = accessor.text_range( i + 1, last_frame[3]) if DEBUG: print "Injecting %s" % (prop, ) citdl_expr.append(prop) if DEBUG: print "jump to matching brace at %d: %r" % (i, ch) citdl_expr.append(ch) if trg and ch == "(": # save the text in params, in case the completion # needs to special-case things depending on the # argument. params = trg.extra.setdefault("_params", []) params.insert( 0, accessor.text_range(i + 1, last_frame[3])) i -= 1 break i -= 1 else: # Didn't find the matching brace. if DEBUG: print "couldn't find matching brace" raise EvalError("could not find matching brace for " "'%s' at position %d" % (stack[-1][0], stack[-1][3])) else: if DEBUG: style_names = buf.style_names_from_style_num(style) print "add char: %r (%s)" % (ch, ', '.join(style_names)) if first_citdl_expr_style is None: # Remember the first citdl style we found first_citdl_expr_style = style first_citdl_expr_style_is_comment = style in comment_styles elif first_citdl_expr_style != style and \ (first_citdl_expr_style_is_comment or style in comment_styles): # We've moved into or out of a comment, let's leave now # Fixes: http://bugs.activestate.com/show_bug.cgi?id=65672 break citdl_expr.append(ch) i -= 1 citdl_expr.reverse() citdl_expr = ''.join(citdl_expr) if DEBUG: print "return: %r" % citdl_expr print banner("done") return citdl_expr
def preceding_trg_from_pos(self, buf, pos, curr_pos, preceding_trg_terminators=None, DEBUG=False): accessor = buf.accessor if preceding_trg_terminators is None: preceding_trg_terminators = self.preceding_trg_terminators if DEBUG: print banner("preceding_trg_from_pos(pos=%r, curr_pos=%r)" % (pos, curr_pos)) print indent( markup_text(accessor.text, pos=curr_pos, start_pos=pos)) print banner(None, '-') # Skip over comments and strings in our checking, unless we are # in one of these styles for the whole range. This is so an explicit # trigger in a comment (or, e.g., a doc string) will work, but # the appearance of small comments or strings in code will not mess # things up. comment_and_string_styles = dict( (s, True) for s in buf.comment_styles() + buf.string_styles()) skip_styles = {} start_style = accessor.style_at_pos(pos - 1) EOL_CHARS = tuple("\n\r") # Limiting simplification: Only backtrack a max of 200 chars. # Can increase that if necessary. The problem is detecting a # statement boundary backwards in langs like Python and Ruby # where you can't rely on ';' (actually # `preceding_trg_terminators'). limit = max(1, pos - 200) # First stage. We only consider autocomplete trigger (i.e. # trg.form==TRG_FORM_COMPLETION) if within range of the # curr_pos. Here "within range" means you don't have to more the # cursor to show the autocomplete UI. first_stage_limit = curr_pos for (char, style) in accessor.gen_char_and_style_back( curr_pos - 1, limit - 1): if not isident(char): break first_stage_limit -= 1 if DEBUG: print "[stage 1] first_stage_limit=%d (prev_ch=%r)"\ % (first_stage_limit, (first_stage_limit > 0 and accessor.char_at_pos(first_stage_limit-1) or None)) p = pos if p >= first_stage_limit: for (prev_ch, prev_style) in accessor.gen_char_and_style_back( p - 1, first_stage_limit - 2): if (not skip_styles and prev_style != start_style # EOLs in comments seem to always be style 0. Don't count # them. and prev_ch not in EOL_CHARS): if DEBUG: print "[stage 1] have seen a style change (%d -> %d), " \ "now skipping strings and comments" \ % (start_style, prev_style) skip_styles = comment_and_string_styles if DEBUG: print "[stage 1] consider pos %2d: prev_ch=%r (%d) --"\ % (p, prev_ch, prev_style), if prev_style in skip_styles: if DEBUG: print "comment or string, skip it" elif self._is_terminating_char(prev_ch, prev_style, preceding_trg_terminators): if DEBUG: print "in `preceding_trg_terminators': break" return None elif prev_ch in self.trg_chars: if DEBUG: print "trigger char, try it" trg = buf.trg_from_pos(p, implicit=False) if trg: if DEBUG: print "[stage 1] %s" % trg return trg p -= 1 break elif DEBUG: print "not a trigger char, skip it" p -= 1 if DEBUG: print "[stage 1] end of possible autocomplete trigger range" # Second stage. We only consider calltip triggers now # (self.calltip_trg_chars). # # As well, ignore enclosed paren sections to make sure we are # in-range. For example, we shouldn't trigger on "bar(" here: # foo(bar("skip", "this", "arg", "list"), <|>) close_paren_count = 0 for (prev_ch, prev_style) in accessor.gen_char_and_style_back(p - 1, limit - 2): if (not skip_styles and prev_style != start_style # EOLs in comments seem to always be style 0. Don't count # them. and prev_ch not in EOL_CHARS): if DEBUG: print "[stage 2] seen a style change (%d -> %d), now " \ "skipping strings and comments" \ % (start_style, prev_style) skip_styles = comment_and_string_styles if DEBUG: print "[stage 2] consider pos %2d: prev_ch=%r (%d) --"\ % (p, prev_ch, prev_style), if prev_style in skip_styles: if DEBUG: print "comment or string, skip it" elif prev_ch == ')': close_paren_count += 1 if DEBUG: print "close paren: count=%d" % close_paren_count elif close_paren_count and prev_ch == '(': close_paren_count -= 1 if DEBUG: print "open paren: count=%d" % close_paren_count elif self._is_terminating_char(prev_ch, prev_style, preceding_trg_terminators): if DEBUG: print "in `preceding_trg_terminators': break" return None elif prev_ch in self.calltip_trg_chars: if DEBUG: print "trigger char, try it" trg = buf.trg_from_pos(p, implicit=False) if trg: if DEBUG: print "[stage 2] %s" % trg return trg elif DEBUG: print "not a trigger char, skip it" p -= 1 return None
def _citdl_expr_from_pos(self, buf, pos, implicit=False, include_forwards=False, DEBUG=False, trg=None): #PERF: Would dicts be faster for all of these? WHITESPACE = tuple(" \t\n\r\v\f") EOL = tuple("\r\n") BLOCKCLOSES = tuple(")}]") STOPOPS = tuple("({[,&+-=!^|%/<>;:#@") EXTRA_STOPOPS_PRECEDING_IDENT = BLOCKCLOSES # Might be others. #TODO: clean this up for LangIntel-usage if implicit: skip_styles = buf.implicit_completion_skip_styles else: skip_styles = buf.completion_skip_styles string_styles = buf.string_styles() comment_styles = buf.comment_styles() #XXX Add sentinel num chars? citdl_expr = [] accessor = buf.accessor i = pos # Move ahead to include forward chars as well # We stop when we go out of the expression or when the expression is # becomes a multiple fragment, i.e. # 'sys.pa<|>th.expanduser' -> 'sys.path' if include_forwards: buf_length = accessor.length() if i < buf_length: max_look_ahead = min(buf_length, i+100) lastch_was_whitespace = False while i < max_look_ahead: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) if ch in WHITESPACE: lastch_was_whitespace = True elif ch in ".)}]" or ch in STOPOPS: break elif lastch_was_whitespace: break else: lastch_was_whitespace = False i += 1 # Move back to last valid char i -= 1 else: i = buf_length - 1 if DEBUG: if i > pos: print "Including chars from pos %d up to %d" % (pos, i) else: print "No valid chars forward from pos %d, i now: %d" % (pos, i) # Be careful here, we cannot move from code into a comment, but we # can be in a comment to begin with. first_citdl_expr_style = None first_citdl_expr_style_is_comment = False while i >= 0: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) if ch in WHITESPACE: # drop all whitespace while i >= 0: ch = accessor.char_at_pos(i) if ch in WHITESPACE \ or (ch == '\\' and accessor.char_at_pos(i+1) in EOL): if DEBUG: print "drop whitespace: %r" % accessor.char_at_pos(i) else: break i -= 1 # If there are two whitespace-separated words with no . # in between we're changing expressions: # if foo<|> and ... # def foo<|>(... if i >= 0 and citdl_expr and isident(citdl_expr[-1]) \ and ch != '.': if DEBUG: print "stop at non-dot: %r" % ch break elif style in string_styles: # Convert to string citdl_type = self.citdl_from_literal_type.get("string") if DEBUG: print "found string style, converting to: %s and now " \ "finished" % (citdl_type) if citdl_type: citdl_expr += reversed(citdl_type) break elif style in skip_styles: # drop styles to ignore while i >= 0 and accessor.style_at_pos(i) in skip_styles: if DEBUG: print "drop char of style to ignore: %r"\ % accessor.char_at_pos(i) i -= 1 elif ch in STOPOPS or ( # This check ensures that, for example, we get "foo" instead # of "bar()foo" in the following: # bar() # foo<|>. citdl_expr and citdl_expr[-1] != '.' and ch in EXTRA_STOPOPS_PRECEDING_IDENT): if DEBUG: print "stop at stop-operator %d: %r" % (i, ch) break elif ch in BLOCKCLOSES: if DEBUG: print "found block at %d: %r" % (i, ch) citdl_expr.append(ch) BLOCKS = { # map block close char to block open char ')': '(', ']': '[', '}': '{', } stack = [] # stack of blocks: (<block close char>, <style>) stack.append( (ch, style, BLOCKS[ch], i) ) i -= 1 num_lines = 0 while i >= 0: ch = accessor.char_at_pos(i) style = accessor.style_at_pos(i) if DEBUG: print "finding matching brace: ch %r (%s), stack %r"\ % (ch, ', '.join(buf.style_names_from_style_num(style)), stack) if ch in EOL: num_lines += 1 if num_lines >= 3: if DEBUG: print "stop search for matching brace at 3 line sentinel" break elif ch in BLOCKS and style not in skip_styles: stack.append( (ch, style, BLOCKS[ch]) ) elif ch == stack[-1][2] and style not in skip_styles: #XXX Replace the second test with the following # when LexPython+SilverCity styling bugs are fixed # (spurious 'stderr' problem): # and style == stack[-1][1]: last_frame = stack.pop() if not stack: if DEBUG: print "jump to matching brace at %d: %r" % (i, ch) citdl_expr.append(ch) if trg: # save the text in params, in case the completion # needs to special-case things depending on the # argument. trg.extra["_params"] = accessor.text_range(i + 1, last_frame[3]) i -= 1 break i -= 1 else: # Didn't find the matching brace. if DEBUG: print "couldn't find matching brace" raise EvalError("could not find matching brace for " "'%s' at position %d" % (stack[-1][0], stack[-1][3])) else: if DEBUG: style_names = buf.style_names_from_style_num(style) print "add char: %r (%s)" % (ch, ', '.join(style_names)) if first_citdl_expr_style is None: # Remember the first citdl style we found first_citdl_expr_style = style first_citdl_expr_style_is_comment = style in comment_styles elif first_citdl_expr_style != style and \ (first_citdl_expr_style_is_comment or style in comment_styles): # We've moved into or out of a comment, let's leave now # Fixes: http://bugs.activestate.com/show_bug.cgi?id=65672 break citdl_expr.append(ch) i -= 1 citdl_expr.reverse() citdl_expr = ''.join(citdl_expr) if DEBUG: print "return: %r" % citdl_expr print banner("done") return citdl_expr
def preceding_trg_from_pos(self, buf, pos, curr_pos, preceding_trg_terminators=None, DEBUG=False): accessor = buf.accessor if preceding_trg_terminators is None: preceding_trg_terminators = self.preceding_trg_terminators if DEBUG: print banner("preceding_trg_from_pos(pos=%r, curr_pos=%r)" % (pos, curr_pos)) print indent(markup_text(accessor.text, pos=curr_pos, start_pos=pos)) print banner(None, '-') # Skip over comments and strings in our checking, unless we are # in one of these styles for the whole range. This is so an explicit # trigger in a comment (or, e.g., a doc string) will work, but # the appearance of small comments or strings in code will not mess # things up. comment_and_string_styles = dict( (s, True) for s in buf.comment_styles() + buf.string_styles()) skip_styles = {} start_style = accessor.style_at_pos(pos-1) EOL_CHARS = tuple("\n\r") # Limiting simplification: Only backtrack a max of 200 chars. # Can increase that if necessary. The problem is detecting a # statement boundary backwards in langs like Python and Ruby # where you can't rely on ';' (actually # `preceding_trg_terminators'). limit = max(1, pos - 200) # First stage. We only consider autocomplete trigger (i.e. # trg.form==TRG_FORM_COMPLETION) if within range of the # curr_pos. Here "within range" means you don't have to more the # cursor to show the autocomplete UI. first_stage_limit = curr_pos for (char, style) in accessor.gen_char_and_style_back(curr_pos-1, limit-1): if not isident(char): break first_stage_limit -= 1 if DEBUG: print "[stage 1] first_stage_limit=%d (prev_ch=%r)"\ % (first_stage_limit, (first_stage_limit > 0 and accessor.char_at_pos(first_stage_limit-1) or None)) p = pos if p >= first_stage_limit: for (prev_ch, prev_style) in accessor.gen_char_and_style_back(p-1, first_stage_limit-2): if (not skip_styles and prev_style != start_style # EOLs in comments seem to always be style 0. Don't count # them. and prev_ch not in EOL_CHARS): if DEBUG: print "[stage 1] have seen a style change (%d -> %d), " \ "now skipping strings and comments" \ % (start_style, prev_style) skip_styles = comment_and_string_styles if DEBUG: print "[stage 1] consider pos %2d: prev_ch=%r (%d) --"\ % (p, prev_ch, prev_style), if prev_style in skip_styles: if DEBUG: print "comment or string, skip it" elif self._is_terminating_char(prev_ch, prev_style, preceding_trg_terminators): if DEBUG: print "in `preceding_trg_terminators': break" return None elif prev_ch in self.trg_chars: if DEBUG: print "trigger char, try it" trg = buf.trg_from_pos(p, implicit=False) if trg: if DEBUG: print "[stage 1] %s" % trg return trg p -= 1 break elif DEBUG: print "not a trigger char, skip it" p -= 1 if DEBUG: print "[stage 1] end of possible autocomplete trigger range" # Second stage. We only consider calltip triggers now # (self.calltip_trg_chars). # # As well, ignore enclosed paren sections to make sure we are # in-range. For example, we shouldn't trigger on "bar(" here: # foo(bar("skip", "this", "arg", "list"), <|>) close_paren_count = 0 for (prev_ch, prev_style) in accessor.gen_char_and_style_back(p-1, limit-2): if (not skip_styles and prev_style != start_style # EOLs in comments seem to always be style 0. Don't count # them. and prev_ch not in EOL_CHARS): if DEBUG: print "[stage 2] seen a style change (%d -> %d), now " \ "skipping strings and comments" \ % (start_style, prev_style) skip_styles = comment_and_string_styles if DEBUG: print "[stage 2] consider pos %2d: prev_ch=%r (%d) --"\ % (p, prev_ch, prev_style), if prev_style in skip_styles: if DEBUG: print "comment or string, skip it" elif prev_ch == ')': close_paren_count += 1 if DEBUG: print "close paren: count=%d" % close_paren_count elif close_paren_count and prev_ch == '(': close_paren_count -= 1 if DEBUG: print "open paren: count=%d" % close_paren_count elif self._is_terminating_char(prev_ch, prev_style, preceding_trg_terminators): if DEBUG: print "in `preceding_trg_terminators': break" return None elif prev_ch in self.calltip_trg_chars: if DEBUG: print "trigger char, try it" trg = buf.trg_from_pos(p, implicit=False) if trg: if DEBUG: print "[stage 2] %s" % trg return trg elif DEBUG: print "not a trigger char, skip it" p -= 1 return None
def trg_from_pos(self, pos, implicit=True): """Python trigger types: python-complete-object-members python-calltip-call-signature python-complete-pythondoc-tags complete-available-imports complete-module-members Not yet implemented: complete-available-classes calltip-base-signature """ DEBUG = False # not using 'logging' system, because want to be fast if DEBUG: print "\n----- Python trg_from_pos(pos=%r, implicit=%r) -----"\ % (pos, implicit) if pos == 0: return None accessor = self.accessor last_pos = pos - 1 last_char = accessor.char_at_pos(last_pos) if DEBUG: print " last_pos: %s" % last_pos print " last_char: %r" % last_char # Quick out if the preceding char isn't a trigger char. if last_char not in " .(@_,": if DEBUG: print "trg_from_pos: no: %r is not in ' .(@'_" % last_char return None style = accessor.style_at_pos(last_pos) if DEBUG: style_names = self.style_names_from_style_num(style) print " style: %s (%s)" % (style, ", ".join(style_names)) if last_char == "@": # Possibly python-complete-pythondoc-tags (the only trigger # on '@'). # # Notes: # - PythonDoc 2.1b6 started allowing pythondoc tags in doc # strings which we are yet supporting here. # - Trigger in comments should only happen if the comment # begins with the "##" pythondoc signifier. We don't # bother checking that (PERF). if style in self.comment_styles(): # Only trigger at start of comment line. WHITESPACE = tuple(" \t") SENTINEL = 20 i = last_pos-1 while i >= max(0, last_pos-SENTINEL): ch = accessor.char_at_pos(i) if ch == "#": return Trigger(self.lang, TRG_FORM_CPLN, "pythondoc-tags", pos, implicit) elif ch in WHITESPACE: pass else: return None i -= 1 return None # Remaing triggers should never trigger in some styles. if (implicit and style in self.implicit_completion_skip_styles and last_char != '_' or style in self.completion_skip_styles): if DEBUG: print "trg_from_pos: no: completion is suppressed "\ "in style at %s: %s (%s)"\ % (last_pos, style, ", ".join(style_names)) return None if last_char == " ": # used for: # * complete-available-imports # * complete-module-members # * complete-available-exceptions # Triggering examples ('_' means a space here): # import_ from_ # Non-triggering examples: # from FOO import_ Ximport_ # Not bothering to support: #; if FOO:import_ FOO;import_ # Typing a space is very common so lets have a quick out before # doing the more correct processing: if last_pos-1 < 0 or accessor.char_at_pos(last_pos-1) not in "tm,": return None working_text = accessor.text_range(max(0,last_pos-200), last_pos) line = self._last_logical_line(working_text).strip() if not line: return None ch = line[-1] line = line.replace('\t', ' ') # from <|> # import <|> if line == "from" or line == "import": return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=()) # is it "from FOO import <|>" ? if line.endswith(" import"): if line.startswith('from '): imp_prefix = tuple(line[len('from '):-len(' import')].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) if line == "except" or line.endswith(" except"): return Trigger(self.lang, TRG_FORM_CPLN, "available-exceptions", pos, implicit) if ch == ',': # is it "from FOO import BAR, <|>" ? if line.startswith('from ') and ' import ' in line: imp_prefix = tuple(line[len('from '):line.index(' import')].strip().split('.')) # Need better checks return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) elif last_char == '.': # must be "complete-object-members" or None # If the first non-whitespace character preceding the '.' in the # same statement is an identifer character then trigger, if it # is a ')', then _maybe_ we should trigger (yes if this is # function call paren). # # Triggering examples: # FOO. FOO . FOO; BAR. # FOO(). FOO.BAR. FOO(BAR, BAZ. # FOO().BAR. FOO("blah();", "blam"). FOO = {BAR. # FOO(BAR. FOO[BAR. # ...more cases showing possible delineation of expression # Non-triggering examples: # FOO.. # FOO[1]. too hard to determine sequence element types # from FOO import (BAR. # Not sure if want to support: # "foo". do we want to support literals? what about # lists? tuples? dicts? working_text = accessor.text_range(max(0,last_pos-200), last_pos) line = self._last_logical_line(working_text).strip() if line: ch = line[-1] if (isident(ch) or isdigit(ch) or ch in '.)'): line = line.replace('\t', ' ') m = _dotted_from_rx.match(line) if m: dots = len(m.group(1).strip()) # magic value for imp_prefix, means "from .<|>" imp_prefix = tuple('' for i in xrange(dots+2)) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) elif line.startswith('from '): if ' import ' in line: # we're in "from FOO import BAR." territory, # which is not a trigger return None # from FOO. imp_prefix = tuple(line[len('from '):].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) elif line.startswith('import '): # import FOO. # figure out the dotted parts of "FOO" above imp_prefix = tuple(line[len('import '):].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) else: return Trigger(self.lang, TRG_FORM_CPLN, "object-members", pos, implicit) elif ch in ("\"'"): return Trigger(self.lang, TRG_FORM_CPLN, "literal-members", pos, implicit, citdl_expr="str") else: ch = None if DEBUG: print "trg_from_pos: no: non-ws char preceding '.' is not "\ "an identifier char or ')': %r" % ch return None elif last_char == "_": # used for: # * complete-magic-symbols # Triggering examples: # def __<|>init__ # if __<|>name__ == '__main__': # __<|>file__ # Ensure double "__". if last_pos-1 < 0 or accessor.char_at_pos(last_pos-1) != "_": return None beforeChar = None beforeStyle = None if last_pos-2 >= 0: beforeChar = accessor.char_at_pos(last_pos-2) beforeStyle = accessor.style_at_pos(last_pos-2) if DEBUG: print "trg_from_pos:: checking magic symbol, beforeChar: %r" % (beforeChar) if beforeChar and beforeChar in "\"'" and beforeStyle in self.string_styles(): if DEBUG: print "trg_from_pos:: magic-symbols - string" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos-1, implicit, symbolstype="string") elif beforeChar == "." and beforeStyle != style: # Turned this off, as it interferes with regular "xxx." object # completions. return None if beforeStyle == style: # No change in styles between the characters -- abort. return None text = accessor.text_range(max(0,last_pos-20), last_pos-1).strip() if beforeChar and beforeChar in " \t": if text.endswith("def"): if DEBUG: print "trg_from_pos:: magic-symbols - def" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos-1, implicit, symbolstype="def") if DEBUG: print "trg_from_pos:: magic-symbols - global" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos-1, implicit, symbolstype="global", text=text) elif last_char == '(': # If the first non-whitespace character preceding the '(' in the # same statement is an identifer character then trigger calltip, # # Triggering examples: # FOO. FOO ( FOO; BAR( # FOO.BAR( FOO(BAR, BAZ( FOO = {BAR( # FOO(BAR( FOO[BAR( # Non-triggering examples: # FOO()( a function call returning a callable that is # immediately called again is too rare to bother # with # def foo( might be a "calltip-base-signature", but this # trigger is not yet implemented # import ( will be handled by complete_members # class Foo( is an "complete-available-classes" trigger, # but this is not yet implemented working_text = accessor.text_range(max(0,last_pos-200), last_pos) line = self._last_logical_line(working_text).rstrip() if line: ch = line[-1] if isident(ch) or isdigit(ch): # If this is: # def foo( # then this might be the (as yet unimplemented) # "calltip-base-signature" trigger or it should not be a # trigger point. # # If this is: # class Foo( # then this should be the (as yet unimplemented) # "complete-available-classes" trigger. line = line.replace('\t', ' ') lstripped = line.lstrip() if lstripped.startswith("def"): if DEBUG: print "trg_from_pos: no: point is function declaration" elif lstripped.startswith("class") and '(' not in lstripped: # Second test is necessary to not exclude: # class Foo(bar(<|> if DEBUG: print "trg_from_pos: no: point is class declaration" elif lstripped.startswith('from ') and ' import' in lstripped: # Need better checks # is it "from FOO import (<|>" ? imp_prefix = tuple(lstripped[len('from '):lstripped.index(' import')].split('.')) if DEBUG: print "trg_from_pos: from FOO import (" return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) else: return Trigger(self.lang, TRG_FORM_CALLTIP, "call-signature", pos, implicit) else: if DEBUG: print "trg_from_pos: no: non-ws char preceding "\ "'(' is not an identifier char: %r" % ch else: if DEBUG: print "trg_from_pos: no: no chars preceding '('" return None elif last_char == ',': working_text = accessor.text_range(max(0, last_pos - 200), last_pos) line = self._last_logical_line(working_text) if line: last_bracket = line.rfind("(") if last_bracket >= 0: pos = (pos - (len(line) - last_bracket)) return Trigger(self.lang, TRG_FORM_CALLTIP, "call-signature", pos, implicit) return None else: return None
def trg_from_pos(self, pos, implicit=True): """Python trigger types: python-complete-object-members python-calltip-call-signature python-complete-pythondoc-tags complete-available-imports complete-module-members Not yet implemented: complete-available-classes calltip-base-signature """ DEBUG = False # not using 'logging' system, because want to be fast if DEBUG: print "\n----- Python trg_from_pos(pos=%r, implicit=%r) -----"\ % (pos, implicit) if pos == 0: return None accessor = self.accessor last_pos = pos - 1 last_char = accessor.char_at_pos(last_pos) if DEBUG: print " last_pos: %s" % last_pos print " last_char: %r" % last_char # Quick out if the preceding char isn't a trigger char. if last_char not in " .(@_,": if DEBUG: print "trg_from_pos: no: %r is not in ' .(@'_" % last_char return None style = accessor.style_at_pos(last_pos) if DEBUG: style_names = self.style_names_from_style_num(style) print " style: %s (%s)" % (style, ", ".join(style_names)) if last_char == "@": # Possibly python-complete-pythondoc-tags (the only trigger # on '@'). # # Notes: # - PythonDoc 2.1b6 started allowing pythondoc tags in doc # strings which we are yet supporting here. # - Trigger in comments should only happen if the comment # begins with the "##" pythondoc signifier. We don't # bother checking that (PERF). if style in self.comment_styles(): # Only trigger at start of comment line. WHITESPACE = tuple(" \t") SENTINEL = 20 i = last_pos - 1 while i >= max(0, last_pos - SENTINEL): ch = accessor.char_at_pos(i) if ch == "#": return Trigger(self.lang, TRG_FORM_CPLN, "pythondoc-tags", pos, implicit) elif ch in WHITESPACE: pass else: return None i -= 1 return None # Remaing triggers should never trigger in some styles. if (implicit and style in self.implicit_completion_skip_styles and last_char != '_' or style in self.completion_skip_styles): if DEBUG: print "trg_from_pos: no: completion is suppressed "\ "in style at %s: %s (%s)"\ % (last_pos, style, ", ".join(style_names)) return None if last_char == " ": # used for: # * complete-available-imports # * complete-module-members # * complete-available-exceptions # Triggering examples ('_' means a space here): # import_ from_ # Non-triggering examples: # from FOO import_ Ximport_ # Not bothering to support: #; if FOO:import_ FOO;import_ # Typing a space is very common so lets have a quick out before # doing the more correct processing: if last_pos - 1 < 0 or accessor.char_at_pos(last_pos - 1) not in "tm,": return None working_text = accessor.text_range(max(0, last_pos - 200), last_pos) line = self._last_logical_line(working_text).strip() if not line: return None ch = line[-1] line = line.replace('\t', ' ') # from <|> # import <|> if line == "from" or line == "import": return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=()) # is it "from FOO import <|>" ? if line.endswith(" import"): if line.startswith('from '): imp_prefix = tuple( line[len('from '):-len(' import')].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) if line == "except" or line.endswith(" except"): return Trigger(self.lang, TRG_FORM_CPLN, "available-exceptions", pos, implicit) if ch == ',': # is it "from FOO import BAR, <|>" ? if line.startswith('from ') and ' import ' in line: imp_prefix = tuple( line[len('from '):line.index(' import')].strip().split( '.')) # Need better checks return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) elif last_char == '.': # must be "complete-object-members" or None # If the first non-whitespace character preceding the '.' in the # same statement is an identifer character then trigger, if it # is a ')', then _maybe_ we should trigger (yes if this is # function call paren). # # Triggering examples: # FOO. FOO . FOO; BAR. # FOO(). FOO.BAR. FOO(BAR, BAZ. # FOO().BAR. FOO("blah();", "blam"). FOO = {BAR. # FOO(BAR. FOO[BAR. # ...more cases showing possible delineation of expression # Non-triggering examples: # FOO.. # FOO[1]. too hard to determine sequence element types # from FOO import (BAR. # Not sure if want to support: # "foo". do we want to support literals? what about # lists? tuples? dicts? working_text = accessor.text_range(max(0, last_pos - 200), last_pos) line = self._last_logical_line(working_text).strip() if line: ch = line[-1] if (isident(ch) or isdigit(ch) or ch in '.)'): line = line.replace('\t', ' ') m = _dotted_from_rx.match(line) if m: dots = len(m.group(1).strip()) # magic value for imp_prefix, means "from .<|>" imp_prefix = tuple('' for i in xrange(dots + 2)) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) elif line.startswith('from '): if ' import ' in line: # we're in "from FOO import BAR." territory, # which is not a trigger return None # from FOO. imp_prefix = tuple( line[len('from '):].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) elif line.startswith('import '): # import FOO. # figure out the dotted parts of "FOO" above imp_prefix = tuple( line[len('import '):].strip().split('.')) return Trigger(self.lang, TRG_FORM_CPLN, "available-imports", pos, implicit, imp_prefix=imp_prefix) else: return Trigger(self.lang, TRG_FORM_CPLN, "object-members", pos, implicit) elif ch in ("\"'"): return Trigger(self.lang, TRG_FORM_CPLN, "literal-members", pos, implicit, citdl_expr="str") else: ch = None if DEBUG: print "trg_from_pos: no: non-ws char preceding '.' is not "\ "an identifier char or ')': %r" % ch return None elif last_char == "_": # used for: # * complete-magic-symbols # Triggering examples: # def __<|>init__ # if __<|>name__ == '__main__': # __<|>file__ # Ensure double "__". if last_pos - 1 < 0 or accessor.char_at_pos(last_pos - 1) != "_": return None beforeChar = None beforeStyle = None if last_pos - 2 >= 0: beforeChar = accessor.char_at_pos(last_pos - 2) beforeStyle = accessor.style_at_pos(last_pos - 2) if DEBUG: print "trg_from_pos:: checking magic symbol, beforeChar: %r" % ( beforeChar) if beforeChar and beforeChar in "\"'" and beforeStyle in self.string_styles( ): if DEBUG: print "trg_from_pos:: magic-symbols - string" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos - 1, implicit, symbolstype="string") elif beforeChar == "." and beforeStyle != style: # Turned this off, as it interferes with regular "xxx." object # completions. return None if beforeStyle == style: # No change in styles between the characters -- abort. return None text = accessor.text_range(max(0, last_pos - 20), last_pos - 1).strip() if beforeChar and beforeChar in " \t": if text.endswith("def"): if DEBUG: print "trg_from_pos:: magic-symbols - def" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos - 1, implicit, symbolstype="def") if DEBUG: print "trg_from_pos:: magic-symbols - global" return Trigger(self.lang, TRG_FORM_CPLN, "magic-symbols", last_pos - 1, implicit, symbolstype="global", text=text) elif last_char == '(': # If the first non-whitespace character preceding the '(' in the # same statement is an identifer character then trigger calltip, # # Triggering examples: # FOO. FOO ( FOO; BAR( # FOO.BAR( FOO(BAR, BAZ( FOO = {BAR( # FOO(BAR( FOO[BAR( # Non-triggering examples: # FOO()( a function call returning a callable that is # immediately called again is too rare to bother # with # def foo( might be a "calltip-base-signature", but this # trigger is not yet implemented # import ( will be handled by complete_members # class Foo( is an "complete-available-classes" trigger, # but this is not yet implemented working_text = accessor.text_range(max(0, last_pos - 200), last_pos) line = self._last_logical_line(working_text).rstrip() if line: ch = line[-1] if isident(ch) or isdigit(ch): # If this is: # def foo( # then this might be the (as yet unimplemented) # "calltip-base-signature" trigger or it should not be a # trigger point. # # If this is: # class Foo( # then this should be the (as yet unimplemented) # "complete-available-classes" trigger. line = line.replace('\t', ' ') lstripped = line.lstrip() if lstripped.startswith("def"): if DEBUG: print "trg_from_pos: no: point is function declaration" elif lstripped.startswith( "class") and '(' not in lstripped: # Second test is necessary to not exclude: # class Foo(bar(<|> if DEBUG: print "trg_from_pos: no: point is class declaration" elif lstripped.startswith( 'from ') and ' import' in lstripped: # Need better checks # is it "from FOO import (<|>" ? imp_prefix = tuple( lstripped[len('from '):lstripped. index(' import')].split('.')) if DEBUG: print "trg_from_pos: from FOO import (" return Trigger(self.lang, TRG_FORM_CPLN, "module-members", pos, implicit, imp_prefix=imp_prefix) else: return Trigger(self.lang, TRG_FORM_CALLTIP, "call-signature", pos, implicit) else: if DEBUG: print "trg_from_pos: no: non-ws char preceding "\ "'(' is not an identifier char: %r" % ch else: if DEBUG: print "trg_from_pos: no: no chars preceding '('" return None elif last_char == ',': working_text = accessor.text_range(max(0, last_pos - 200), last_pos) line = self._last_logical_line(working_text) if line: last_bracket = line.rfind("(") if last_bracket >= 0: pos = (pos - (len(line) - last_bracket)) return Trigger(self.lang, TRG_FORM_CALLTIP, "call-signature", pos, implicit) return None else: return None