#!/usr/bin/env python # ***** BEGIN LICENSE BLOCK ***** # Version: MPL 1.1/GPL 2.0/LGPL 2.1 # # The contents of this file are subject to the Mozilla Public License # Version 1.1 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the # License for the specific language governing rights and limitations # under the License. # # The Original Code is Komodo code. # # The Initial Developer of the Original Code is ActiveState Software Inc. # Portions created by ActiveState Software Inc are Copyright (C) 2000-2007 # ActiveState Software Inc. All Rights Reserved. # # Contributor(s): # ActiveState Software Inc # # Alternatively, the contents of this file may be used under the terms of # either the GNU General Public License Version 2 or later (the "GPL"), or # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), # in which case the provisions of the GPL or the LGPL are applicable instead # of those above. If you wish to allow use of your version of this file only # under the terms of either the GPL or the LGPL, and not to allow others to # use your version of this file under the terms of the MPL, indicate your # decision by deleting the provisions above and replace them with the notice # and other provisions required by the GPL or the LGPL. If you do not delete # the provisions above, a recipient may use your version of this file under # the terms of any one of the MPL, the GPL or the LGPL. # # ***** END LICENSE BLOCK ***** # # Contributors: # Shane Caraveo (ShaneC@ActiveState.com) # Trent Mick (TrentM@ActiveState.com) # Todd Whiteman (ToddW@ActiveState.com) """codeintel support for PHP""" import os from os.path import isdir, join, basename, splitext, exists, dirname import sys import re import logging import time import warnings from cStringIO import StringIO import weakref from glob import glob from SilverCity.ScintillaConstants import (SCE_UDL_SSL_DEFAULT, SCE_UDL_SSL_OPERATOR, SCE_UDL_SSL_IDENTIFIER, SCE_UDL_SSL_WORD, SCE_UDL_SSL_VARIABLE, SCE_UDL_SSL_STRING, SCE_UDL_SSL_NUMBER, SCE_UDL_SSL_COMMENT, SCE_UDL_SSL_COMMENTBLOCK) from codeintel2.parseutil import * from codeintel2.phpdoc import phpdoc_tags from codeintel2.citadel import ImportHandler, CitadelLangIntel from codeintel2.udl import UDLBuffer, UDLLexer, UDLCILEDriver, is_udl_csl_style, XMLParsingBufferMixin from codeintel2.common import * from codeintel2 import util from codeintel2.indexer import PreloadBufLibsRequest, PreloadLibRequest from codeintel2.gencix_utils import * from codeintel2.tree_php import PHPTreeEvaluator from codeintel2.langintel import (ParenStyleCalltipIntelMixin, ProgLangTriggerIntelMixin) from codeintel2.accessor import AccessorCache, KoDocumentAccessor if _xpcom_: from xpcom.server import UnwrapObject #---- global data lang = "PHP" log = logging.getLogger("codeintel.php") #log.setLevel(logging.DEBUG) util.makePerformantLogger(log) #---- language support class PHPLexer(UDLLexer): lang = lang def _walk_php_symbols(elem, _prefix=None): if _prefix: lpath = _prefix + (elem.get("name"), ) else: lpath = (elem.get("name"), ) yield lpath if not (elem.tag == "scope" and elem.get("ilk") == "function"): for child in elem: for child_lpath in _walk_php_symbols(child, lpath): yield child_lpath class PHPLangIntel(CitadelLangIntel, ParenStyleCalltipIntelMixin, ProgLangTriggerIntelMixin): lang = lang # Used by ProgLangTriggerIntelMixin.preceding_trg_from_pos() trg_chars = tuple('$>:(,@"\' \\') calltip_trg_chars = tuple('(') # excluded ' ' for perf (bug 55497) # named styles used by the class whitespace_style = SCE_UDL_SSL_DEFAULT operator_style = SCE_UDL_SSL_OPERATOR identifier_style = SCE_UDL_SSL_IDENTIFIER keyword_style = SCE_UDL_SSL_WORD variable_style = SCE_UDL_SSL_VARIABLE string_style = SCE_UDL_SSL_STRING comment_styles = (SCE_UDL_SSL_COMMENT, SCE_UDL_SSL_COMMENTBLOCK) comment_styles_or_whitespace = comment_styles + (whitespace_style, ) def _functionCalltipTrigger(self, ac, pos, DEBUG=False): # Implicit calltip triggering from an arg separater ",", we trigger a # calltip if we find a function open paren "(" and function identifier # http://bugs.activestate.com/show_bug.cgi?id=70470 if DEBUG: print "Arg separater found, looking for start of function" # Move back to the open paren of the function paren_count = 0 p = pos min_p = max(0, p - 200) # look back max 200 chars while p > min_p: p, c, style = ac.getPrecedingPosCharStyle(ignore_styles=self.comment_styles) if style == self.operator_style: if c == ")": paren_count += 1 elif c == "(": if paren_count == 0: # We found the open brace of the func trg_from_pos = p+1 p, ch, style = ac.getPrevPosCharStyle() if DEBUG: print "Function start found, pos: %d" % (p, ) if style in self.comment_styles_or_whitespace: # Find previous non-ignored style then p, c, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace) if style in (self.identifier_style, self.keyword_style): return Trigger(lang, TRG_FORM_CALLTIP, "call-signature", trg_from_pos, implicit=True) else: paren_count -= 1 elif c in ";{}": # Gone too far and noting was found if DEBUG: print "No function found, hit stop char: %s at p: %d" % (c, p) return None # Did not find the function open paren if DEBUG: print "No function found, ran out of chars to look at, p: %d" % (p,) return None #@util.hotshotit def trg_from_pos(self, buf, pos, implicit=True, DEBUG=False, ac=None): #DEBUG = True if pos < 4: return None #DEBUG = True # Last four chars and styles if ac is None: ac = AccessorCache(buf.accessor, pos, fetchsize=4) last_pos, last_char, last_style = ac.getPrevPosCharStyle() prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle() # Bump up how much text is retrieved when cache runs out ac.setCacheFetchSize(20) if DEBUG: print "\nphp trg_from_pos" print " last_pos: %s" % last_pos print " last_char: %s" % last_char print " last_style: %r" % last_style ac.dump() try: # Note: If a "$" exists by itself, it's styled as whitespace. # Generally we want it to be indicating a variable instead. if last_style == self.whitespace_style and last_char != "$": if DEBUG: print "Whitespace style" WHITESPACE = tuple(" \t\n\r\v\f") if not implicit: # If we're not already at the keyword style, find it if prev_style != self.keyword_style: prev_pos, prev_char, prev_style = ac.getPrecedingPosCharStyle(last_style, self.comment_styles) if DEBUG: print "Explicit: prev_pos: %d, style: %d, ch: %r" % (prev_pos, prev_style, prev_char) else: prev_pos = pos - 2 if last_char in WHITESPACE and \ (prev_style == self.keyword_style or (prev_style == self.operator_style and prev_char == ",")): p = prev_pos style = prev_style ch = prev_char #print "p: %d" % p while p > 0 and style == self.operator_style and ch == ",": p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace) #print "p 1: %d" % p if p > 0 and style == self.identifier_style: # Skip the identifier too p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace) #print "p 2: %d" % p if DEBUG: ac.dump() p, text = ac.getTextBackWithStyle(style, self.comment_styles, max_text_len=len("implements")) if DEBUG: print "ac.getTextBackWithStyle:: pos: %d, text: %r" % (p, text) if text in ("new", "extends"): return Trigger(lang, TRG_FORM_CPLN, "classes", pos, implicit) elif text in ("implements", ): return Trigger(lang, TRG_FORM_CPLN, "interfaces", pos, implicit) elif text in ("use", ): return Trigger(lang, TRG_FORM_CPLN, "use", pos, implicit) elif prev_style == self.operator_style and \ prev_char == "," and implicit: return self._functionCalltipTrigger(ac, prev_pos, DEBUG) elif last_style == self.operator_style: if DEBUG: print " lang_style is operator style" print "Prev char: %r" % (prev_char) ac.dump() if last_char == ":": if not prev_char == ":": return None ac.setCacheFetchSize(10) p, c, style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles) if DEBUG: print "Preceding: %d, %r, %d" % (p, c, style) if style is None: return None elif style == self.keyword_style: # Check if it's a "self::" or "parent::" expression p, text = ac.getTextBackWithStyle(self.keyword_style, # Ensure we don't go too far max_text_len=6) if DEBUG: print "Keyword text: %d, %r" % (p, text) ac.dump() if text not in ("parent", "self", "static"): return None return Trigger(lang, TRG_FORM_CPLN, "static-members", pos, implicit) elif last_char == ">": if prev_char == "-": p, c, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace) if style in (self.variable_style, self.identifier_style) or \ (style == self.operator_style and c == ')'): return Trigger(lang, TRG_FORM_CPLN, "object-members", pos, implicit) elif DEBUG: print "Preceding style is not a variable, pos: %d, style: %d" % (p, style) elif last_char in "(,": # where to trigger from, updated by "," calltip handler if DEBUG: print "Checking for function calltip" # Implicit calltip triggering from an arg separater "," # http://bugs.activestate.com/show_bug.cgi?id=70470 if implicit and last_char == ',': return self._functionCalltipTrigger(ac, prev_pos, DEBUG) if prev_style in self.comment_styles_or_whitespace: # Find previous non-ignored style then p, c, prev_style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles_or_whitespace) if prev_style in (self.identifier_style, self.keyword_style): return Trigger(lang, TRG_FORM_CALLTIP, "call-signature", pos, implicit) elif last_char == "\\": # Ensure does not trigger when defining a new namespace, # i.e., do not trigger for: # namespace foo\<|> style = last_style while style in (self.operator_style, self.identifier_style): p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=30) if style == self.whitespace_style: p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30) if style is None: if DEBUG: print "Triggering namespace completion" return Trigger(lang, TRG_FORM_CPLN, "namespace-members", pos, implicit) prev_text = ac.getTextBackWithStyle(style, max_text_len=15) if DEBUG: print "prev_text: %r" % (prev_text, ) if prev_text[1] == "use": if DEBUG: print "Triggering use-namespace completion" return Trigger(lang, TRG_FORM_CPLN, "use-namespace", pos, implicit) elif prev_text[1] != "namespace": if DEBUG: print "Triggering namespace completion" return Trigger(lang, TRG_FORM_CPLN, "namespace-members", pos, implicit) elif last_style == self.variable_style or \ (not implicit and last_char == "$"): if DEBUG: print "Variable style" # Completion for variables (builtins and user defined variables), # must occur after a "$" character. if not implicit and last_char == '$': # Explicit call, move ahead one for real trigger position pos += 1 if not implicit or prev_char == "$": # Ensure we are not triggering over static class variables. # Do this by checking that the preceding text is not "::" # http://bugs.activestate.com/show_bug.cgi?id=78099 p, c, style = ac.getPrecedingPosCharStyle(last_style, max_look_back=30) if c == ":" and style == self.operator_style and \ ac.getTextBackWithStyle(style, max_text_len=3)[1] == "::": return None return Trigger(lang, TRG_FORM_CPLN, "variables", pos-1, implicit) elif last_style in (self.identifier_style, self.keyword_style): if DEBUG: if last_style == self.identifier_style: print "Identifier style" else: print "Identifier keyword style" # Completion for keywords,function and class names # Works after first 3 characters have been typed #if DEBUG: # print "identifier_style: pos - 4 %s" % (accessor.style_at_pos(pos - 4)) #third_char, third_style = last_four_char_and_styles[2] #fourth_char, fourth_style = last_four_char_and_styles[3] if prev_style == last_style: trig_pos, ch, style = ac.getPrevPosCharStyle() if style == last_style: p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles) # style is None if no change of style (not ignored) was # found in the last x number of chars #if not implicit and style == last_style: # if DEBUG: # print "Checking back further for explicit call" # p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=100) # if p is not None: # trg_pos = p + 3 if style in (None, self.whitespace_style, self.operator_style): # Ensure we are not in another trigger zone, we do # this by checking that the preceeding text is not # one of "->", "::", "new", "function", "class", ... if style == self.whitespace_style: p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30) if style is None: return Trigger(lang, TRG_FORM_CPLN, "functions", trig_pos, implicit) prev_text = ac.getTextBackWithStyle(style, max_text_len=15) if DEBUG: print "prev_text: %r" % (prev_text, ) if (prev_text[1] not in ("new", "function", "use", "class", "interface", "implements", "public", "private", "protected", "final", "abstract", "instanceof",) # For the operator styles, we must use # endswith, as it could follow a "()", # bug 90846. and prev_text[1][-2:] not in ("->", "::",) # Don't trigger when accessing a # namespace - bug 88736. and not prev_text[1].endswith("\\")): return Trigger(lang, TRG_FORM_CPLN, "functions", trig_pos, implicit) # If we want implicit triggering on more than 3 chars #elif style == self.identifier_style: # p, c, style = ac.getPrecedingPosCharStyle(self.identifier_style) # return Trigger(lang, TRG_FORM_CPLN, "functions", # p+1, implicit) elif DEBUG: print "identifier preceeded by an invalid style: " \ "%r, p: %r" % (style, p, ) elif last_char == '_' and prev_char == '_' and \ style == self.whitespace_style: # XXX - Check the php version, magic methods only # appeared in php 5. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles) if style == self.keyword_style and \ ac.getTextBackWithStyle(style, max_text_len=9)[1] == "function": if DEBUG: print "triggered:: complete magic-methods" return Trigger(lang, TRG_FORM_CPLN, "magic-methods", prev_pos, implicit) # PHPDoc completions elif last_char == "@" and last_style in self.comment_styles: # If the preceeding non-whitespace character is a "*" or newline # then we complete for phpdoc tag names p = last_pos - 1 min_p = max(0, p - 50) # Don't look more than 50 chars if DEBUG: print "Checking match for phpdoc completions" accessor = buf.accessor while p >= min_p and \ accessor.style_at_pos(p) in self.comment_styles: ch = accessor.char_at_pos(p) p -= 1 #if DEBUG: # print "Looking at ch: %r" % (ch) if ch in "*\r\n": break elif ch not in " \t\v": # Not whitespace, not a valid tag then return None else: # Nothing found in the specified range if DEBUG: print "trg_from_pos: not a phpdoc" return None if DEBUG: print "Matched trigger for phpdoc completion" return Trigger("PHP", TRG_FORM_CPLN, "phpdoc-tags", pos, implicit) # PHPDoc calltip elif last_char in " \t" and last_style in self.comment_styles: # whitespace in a comment, see if it matches for phpdoc calltip p = last_pos - 1 min_p = max(0, p - 50) # Don't look more than 50 chars if DEBUG: print "Checking match for phpdoc calltip" ch = None ident_found_pos = None accessor = buf.accessor while p >= min_p and \ accessor.style_at_pos(p) in self.comment_styles: ch = accessor.char_at_pos(p) p -= 1 if ident_found_pos is None: #print "phpdoc: Looking for identifier, ch: %r" % (ch) if ch in " \t": pass elif _isident(ch): ident_found_pos = p+1 else: if DEBUG: print "No phpdoc, whitespace not preceeded " \ "by an identifer" return None elif ch == "@": # This is what we've been looking for! phpdoc_field = accessor.text_range(p+2, ident_found_pos+1) if DEBUG: print "Matched trigger for phpdoc calltip: '%s'" % ( phpdoc_field, ) return Trigger("PHP", TRG_FORM_CALLTIP, "phpdoc-tags", ident_found_pos, implicit, phpdoc_field=phpdoc_field) elif not _isident(ch): if DEBUG: print "No phpdoc, identifier not preceeded by '@'" # Not whitespace, not a valid tag then return None # Nothing found in the specified range if DEBUG: print "No phpdoc, ran out of characters to look at." # Array completions elif last_style == self.string_style and last_char in '\'"': if prev_char != '[': if prev_style in self.comment_styles_or_whitespace: # Look back further. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace) if prev_char == '[': # We're good to go. if DEBUG: print "Matched trigger for array completions" return Trigger("PHP", TRG_FORM_CPLN, "array-members", pos, implicit, bracket_pos=prev_pos, trg_char=last_char) # Variable completions inside of comments elif prev_char == "$" and last_style in self.comment_styles: if DEBUG: print "Comment variable style" # Completion for variables (builtins and user defined variables), # must occur after a "$" character. return Trigger(lang, TRG_FORM_CPLN, "comment-variables", pos-1, implicit) elif DEBUG: print "trg_from_pos: no handle for style: %d" % last_style except IndexError: # Not enough chars found, therefore no trigger pass return None #@util.hotshotit def preceding_trg_from_pos(self, buf, pos, curr_pos, preceding_trg_terminators=None, DEBUG=False): #DEBUG = True # Try the default preceding_trg_from_pos handler trg = ProgLangTriggerIntelMixin.preceding_trg_from_pos( self, buf, pos, curr_pos, preceding_trg_terminators, DEBUG=DEBUG) if trg is not None: return trg # Else, let's try to work out some other options accessor = buf.accessor prev_style = accessor.style_at_pos(curr_pos - 1) if prev_style in (self.identifier_style, self.keyword_style): # We don't know what to trigger here... could be one of: # functions: # apache<$><|>_getenv()... # if(get_e<$><|>nv()... # classes: # new Exce<$><|>ption()... # extends Exce<$><|>ption()... # interfaces: # implements apache<$><|>_getenv()... ac = AccessorCache(accessor, curr_pos) pos_before_identifer, ch, prev_style = \ ac.getPrecedingPosCharStyle(prev_style) if DEBUG: print "\nphp preceding_trg_from_pos, first chance for identifer style" print " curr_pos: %d" % (curr_pos) print " pos_before_identifer: %d" % (pos_before_identifer) print " ch: %r" % ch print " prev_style: %d" % prev_style ac.dump() if pos_before_identifer < pos: resetPos = min(pos_before_identifer + 4, accessor.length() - 1) ac.resetToPosition(resetPos) if DEBUG: print "preceding_trg_from_pos:: reset to position: %d, ac now:" % (resetPos) ac.dump() # Trigger on the third identifier character return self.trg_from_pos(buf, resetPos, implicit=False, DEBUG=DEBUG, ac=ac) elif DEBUG: print "Out of scope of the identifier" elif prev_style in self.comment_styles: # Check if there is a PHPDoc to provide a calltip for, example: # /** @param $foo foobar - This is field for <|> if DEBUG: print "\nphp preceding_trg_from_pos::phpdoc: check for calltip" comment = accessor.text_range(max(0, curr_pos-200), curr_pos) at_idx = comment.rfind("@") if at_idx >= 0: if DEBUG: print "\nphp preceding_trg_from_pos::phpdoc: contains '@'" space_idx = comment[at_idx:].find(" ") if space_idx >= 0: # Trigger after the space character. trg_pos = (curr_pos - len(comment)) + at_idx + space_idx + 1 if DEBUG: print "\nphp preceding_trg_from_pos::phpdoc: calltip at %d" % (trg_pos, ) return self.trg_from_pos(buf, trg_pos, implicit=False, DEBUG=DEBUG) _phpdoc_cplns = [ ("variable", t) for t in sorted(phpdoc_tags) ] #@util.hotshotit def async_eval_at_trg(self, buf, trg, ctlr): if _xpcom_: trg = UnwrapObject(trg) ctlr = UnwrapObject(ctlr) pos = trg.pos ctlr.start(buf, trg) #print "trg.type: %r" % (trg.type) # PHPDoc completions if trg.id == ("PHP", TRG_FORM_CPLN, "phpdoc-tags"): #TODO: Would like a "javadoc tag" completion image name. ctlr.set_cplns(self._phpdoc_cplns) ctlr.done("success") return # PHPDoc calltip elif trg.id == ("PHP", TRG_FORM_CALLTIP, "phpdoc-tags"): phpdoc_field = trg.extra.get("phpdoc_field") if phpdoc_field: #print "phpdoc_field: %r" % (phpdoc_field, ) calltip = phpdoc_tags.get(phpdoc_field) if calltip: ctlr.set_calltips([calltip]) ctlr.done("success") return elif trg.type in ("classes", "interfaces"): # Triggers from zero characters, thus calling citdl_expr_from_trg # is no help line = buf.accessor.line_from_pos(pos) evalr = PHPTreeEvaluator(ctlr, buf, trg, "", line) buf.mgr.request_eval(evalr) else: try: citdl_expr = self.citdl_expr_from_trg(buf, trg) except CodeIntelError, ex: ctlr.error(str(ex)) ctlr.done("error") return line = buf.accessor.line_from_pos(pos) evalr = PHPTreeEvaluator(ctlr, buf, trg, citdl_expr, line) buf.mgr.request_eval(evalr) def _citdl_expr_from_pos(self, trg, buf, pos, implicit=True, include_forwards=False, DEBUG=False): #DEBUG = True #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: This style picking is a problem for the LangIntel move. if trg.type == "comment-variables": # Dev note: skip_styles in the other cases below will be a dict. skip_styles = set() elif implicit: skip_styles = buf.implicit_completion_skip_styles else: skip_styles = buf.completion_skip_styles citdl_expr = [] accessor = buf.accessor # Use a cache of characters, easy to keep track this way i = pos ac = AccessorCache(accessor, i) if include_forwards: try: # Move ahead to include forward chars as well lastch_was_whitespace = False while 1: i, ch, style = ac.getNextPosCharStyle() if DEBUG: print "include_forwards:: i now: %d, ch: %r" % (i, ch) if ch in WHITESPACE: lastch_was_whitespace = True continue lastch_was_whitespace = False if ch in STOPOPS: if DEBUG: print "include_forwards:: ch in STOPOPS, i:%d ch:%r" % (i, ch) break elif ch in BLOCKCLOSES: if DEBUG: print "include_forwards:: ch in BLOCKCLOSES, i:%d ch:%r" % (i, ch) break elif lastch_was_whitespace: # Two whitespace separated words if DEBUG: print "include_forwards:: ch separated by whitespace, i:%d ch:%r" % (i, ch) break # Move back to last valid char i -= 1 if DEBUG: if i > pos: print "include_forwards:: Including chars from pos %d up to %d" % (pos, i) else: print "include_forwards:: No valid chars forward from pos %d, i now: %d" % (pos, i) except IndexError: # Nothing forwards, user what we have then i = min(i, accessor.length() - 1) if DEBUG: print "include_forwards:: No more buffer, i now: %d" % (i) ac.resetToPosition(i) ch = None try: while i >= 0: if ch == None and include_forwards: i, ch, style = ac.getCurrentPosCharStyle() else: i, ch, style = ac.getPrevPosCharStyle() if DEBUG: print "i now: %d, ch: %r" % (i, ch) if ch in WHITESPACE: if trg.type in ("use-namespace", "namespace-members"): # Namespaces cannot be split over whitespace. break while ch in WHITESPACE: # drop all whitespace next_char = ch i, ch, style = ac.getPrevPosCharStyle() if ch in WHITESPACE \ or (ch == '\\' and next_char in EOL): if DEBUG: print "drop whitespace: %r" % ch # If there are two whitespace-separated words then this is # (likely or always?) a language keyword or declaration # construct at which we want to stop. E.g. # if foo<|> and ... # def foo<|>(... # if \foo<|>(... # uses a namespace if citdl_expr \ and (_isident(citdl_expr[-1]) or citdl_expr[-1] == '\\') \ and (_isident(ch) or _isdigit(ch)): if DEBUG: print "stop at (likely?) start of keyword or "\ "declaration: %r" % ch break # Not whitespace anymore, move into the main checks below if DEBUG: print "Out of whitespace: i now: %d, ch: %s" % (i, ch) if style in skip_styles: # drop styles to ignore while i >= 0 and style in skip_styles: i, ch, style = ac.getPrevPosCharStyle() if DEBUG: print "drop char of style to ignore: %r" % ch elif ch in ":>" and i > 0: # Next char has to be ":" or "-" respectively prev_pos, prev_ch, prev_style = ac.getPrevPosCharStyle() if (ch == ">" and prev_ch == "-") or \ (ch == ":" and prev_ch == ":"): citdl_expr.append(".") if DEBUG: print "Turning member accessor '%s%s' into '.'" % (prev_ch, ch) i -= 2 else: if DEBUG: print "citdl_expr: %r" % (citdl_expr) print "stop at special stop-operator %d: %r" % (i, ch) break elif (ch in STOPOPS or ch in EXTRA_STOPOPS_PRECEDING_IDENT) and \ (ch != ")" or (citdl_expr and citdl_expr[-1] != ".")): if ch == '$': # This may not be the end of the road, given static # variables are accessed through "Class::$static". prev_pos, prev_ch, prev_style = ac.peekPrevPosCharStyle() if prev_ch == ":": # Continue building up the citdl then. continue if DEBUG: print "citdl_expr: %r" % (citdl_expr) 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: (,