/libs/codeintel2/lang_php.py
Python | 1474 lines | 1273 code | 47 blank | 154 comment | 113 complexity | 20188cc678cf7d83a283f17276e15c58 MD5 | raw file
Possible License(s): MIT, MPL-2.0-no-copyleft-exception
- #!/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
- 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, "namespaces", 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] != "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",
- "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 ("->", "::",)):
- 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 ("namespaces", "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: (<block close char>, <style>)
- stack.append( (ch, style, BLOCKS[ch], i) )
- while i >= 0:
- i, ch, style = ac.getPrevPosCharStyle()
- if DEBUG:
- print "finding matching brace: ch %r (%s), stack %r"\
- % (ch, ', '.join(buf.style_names_from_style_num(style)), stack)
- if 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]:
- stack.pop()
- if not stack:
- if DEBUG:
- print "jump to matching brace at %d: %r" % (i, ch)
- citdl_expr.append(ch)
- break
- 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))
- citdl_expr.append(ch)
- i -= 1
- except IndexError:
- # Nothing left to consume, return what we have
- pass
- # Remove any unecessary starting dots
- while citdl_expr and citdl_expr[-1] == ".":
- citdl_expr.pop()
- citdl_expr.reverse()
- citdl_expr = ''.join(citdl_expr)
- if DEBUG:
- print "return: %r" % citdl_expr
- print util.banner("done")
- return citdl_expr
- def citdl_expr_from_trg(self, buf, trg):
- """Return a PHP CITDL expression preceding the given trigger.
- The expression drops newlines, whitespace, and function call
- arguments -- basically any stuff that is not used by the codeintel
- database system for determining the resultant object type of the
- expression. For example (in which <|> represents the given position):
-
- GIVEN RETURN
- ----- ------
- foo-<|>> foo
- Foo:<|>: Foo
- foo(bar-<|>> bar
- foo(bar,blam)-<|>> foo()
- foo(bar, foo()
- blam)-<|>>
- foo(arg1, arg2)->bar-<|>> foo().bar
- Foo(arg1, arg2)::bar-<|>> Foo().bar
- Foo\bar:<|>: Foo\bar
- Foo\bar::bam-<|>> Foo\bar.bam
- Foo\bar(arg1, arg2)::bam-<|>> Foo\bar().bam
- """
- #DEBUG = True
- DEBUG = False
- if DEBUG:
- print util.banner("%s citdl_expr_from_trg @ %r" % (buf.lang, trg))
- if trg.form == TRG_FORM_CPLN:
- # "->" or "::"
- if trg.type == "classes":
- i = trg.pos + 1
- elif trg.type == "functions":
- i = trg.pos + 3 # 3-char trigger, skip over it
- elif trg.type in ("variables", "comment-variables"):
- i = trg.pos + 1 # triggered on the $, skip over it
- elif trg.type == "array-members":
- i = trg.extra.get("bracket_pos") # triggered on foo['
- elif trg.type == "namespaces":
- i = trg.pos + 1
- elif trg.type == "namespace-members":
- i = trg.pos - 1
- else:
- i = trg.pos - 2 # skip past the trigger char
- return self._citdl_expr_from_pos(trg, buf, i, trg.implicit,
- DEBUG=DEBUG)
- elif trg.form == TRG_FORM_DEFN:
- return self.citdl_expr_under_pos(trg, buf, trg.pos, DEBUG)
- else: # trg.form == TRG_FORM_CALLTIP:
- # (<|>
- return self._citdl_expr_from_pos(trg, buf, trg.pos-1, trg.implicit,
- DEBUG=DEBUG)
- def citdl_expr_under_pos(self, trg, buf, pos, DEBUG=False):
- """Return a PHP CITDL expression around the given pos.
- Similar to citdl_expr_from_trg(), but looks forward to grab additional
- characters.
- GIVEN RETURN
- ----- ------
- foo-<|>> foo
- F<|>oo:: Foo
- foo->ba<|>r foo.bar
- f<|>oo->bar foo
- foo(bar-<|>> bar
- foo(bar,blam)-<|>> foo()
- foo(bar, foo()
- blam)-<|>>
- foo(arg1, arg2)->bar-<|>> foo().bar
- Foo(arg1, arg2)::ba<|>r-> Foo().bar
- Fo<|>o(arg1, arg2)::bar-> Foo
- """
- #DEBUG = True
- expr = self._citdl_expr_from_pos(trg, buf, pos-1, implicit=True,
- include_forwards=True, DEBUG=DEBUG)
- if expr:
- # Chop off any trailing "." characters
- return expr.rstrip(".")
- return expr
- def libs_from_buf(self, buf):
- env = buf.env
- # A buffer's libs depend on its env and the buf itself so
- # we cache it on the env and key off the buffer.
- if "php-buf-libs" not in env.cache:
- env.cache["php-buf-libs"] = weakref.WeakKeyDictionary()
- cache = env.cache["php-buf-libs"] # <buf-weak-ref> -> <libs>
- if buf not in cache:
- # - curdirlib
- # Using the dirname of this buffer isn't always right, but
- # hopefully is a good first approximation.
- cwd = dirname(buf.path)
- if cwd == "<Unsaved>":
- libs = []
- else:
- libs = [ self.mgr.db.get_lang_lib("PHP", "curdirlib", [cwd], "PHP")]
- libs += self._buf_indep_libs_from_env(env)
- cache[buf] = libs
- return cache[buf]
- def lpaths_from_blob(self, blob):
- """Return <lpaths> for this blob
- where,
- <lpaths> is a set of externally referencable lookup-paths, e.g.
- [("MyOwnClass",), ("MyOwnClass", "function1"), ...]
- """
- return set(lpath for child in blob
- for lpath in _walk_php_symbols(child))
- def _php_from_env(self, env):
- import which
- path = [d.strip()
- for d in env.get_envvar("PATH", "").split(os.pathsep)
- if d.strip()]
- for exe_name in ("php", "php4", "php-cgi", "php-cli"):
- try:
- return which.which(exe_name, path=path)
- except which.WhichError:
- pass
- return None
- def _php_info_from_php(self, php, env):
- """Call the given PHP and return:
- (<version>, <include_path>)
- Returns (None, []) if could not determine.
- """
- import process
- import tempfile
- # Use a marker to separate the start of output from possible
- # leading lines of PHP loading errors/logging.
- marker = "--- Start of Good Stuff ---"
- info_cmd = (r'<?php '
- + r'echo("%s\n");' % marker
- + r'echo(phpversion()."\n");'
- + r'echo(ini_get("include_path")."\n");'
- + r' ?>')
-
- argv = [php]
- envvars = env.get_all_envvars()
- php_ini_path = env.get_pref("phpConfigFile")
- if php_ini_path:
- envvars["PHPRC"] = php_ini_path
- fd, filepath = tempfile.mkstemp(suffix=".php")
- try:
- os.write(fd, info_cmd)
- os.close(fd)
- argv.append(filepath)
- p = process.ProcessOpen(argv, env=env.get_all_envvars())
- stdout, stderr = p.communicate()
- finally:
- os.remove(filepath)
- stdout_lines = stdout.splitlines(0)
- retval = p.returncode
- if retval:
- log.warn("failed to determine PHP info:\n"
- " path: %s\n"
- " retval: %s\n"
- " stdout:\n%s\n"
- " stderr:\n%s\n",
- php, retval, util.indent('\n'.join(stdout_lines)),
- util.indent(stderr))
- return None, []
- stdout_lines = stdout_lines[stdout_lines.index(marker)+1:]
- php_ver = stdout_lines[0]
- include_path = [p.strip() for p in stdout_lines[1].split(os.pathsep)
- if p.strip()]
- return php_ver, include_path
- def _extra_dirs_from_env(self, env):
- extra_dirs = set()
- include_project = env.get_pref("codeintel_scan_files_in_project", True)
- if include_project:
- proj_base_dir = env.get_proj_base_dir()
- if proj_base_dir is not None:
- extra_dirs.add(proj_base_dir) # Bug 68850.
- for pref in env.get_all_prefs("phpExtraPaths"):
- if not pref: continue
- extra_dirs.update(d.strip() for d in pref.split(os.pathsep)
- if exists(d.strip()))
- if extra_dirs:
- log.debug("PHP extra lib dirs: %r", extra_dirs)
- max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
- php_assocs = env.assoc_patterns_from_lang("PHP")
- extra_dirs = tuple(
- util.gen_dirs_under_dirs(extra_dirs,
- max_depth=max_depth,
- interesting_file_patterns=php_assocs)
- )
- else:
- extra_dirs = () # ensure retval is a tuple
- return extra_dirs
- def _buf_indep_libs_from_env(self, env):
- """Create the buffer-independent list of libs."""
- cache_key = "php-libs"
- if cache_key not in env.cache:
- env.add_pref_observer("php", self._invalidate_cache)
- env.add_pref_observer("phpExtraPaths",
- self._invalidate_cache_and_rescan_extra_dirs)
- env.add_pref_observer("phpConfigFile",
- self._invalidate_cache)
- env.add_pref_observer("codeintel_selected_catalogs",
- self._invalidate_cache)
- env.add_pref_observer("codeintel_max_recursive_dir_depth",
- self._invalidate_cache)
- env.add_pref_observer("codeintel_scan_files_in_project",
- self._invalidate_cache)
- # (Bug 68850) Both of these 'live_*' prefs on the *project*
- # prefset can result in a change of project base dir. It is
- # possible that we can false positives here if there is ever
- # a global pref of this name.
- env.add_pref_observer("import_live",
- self._invalidate_cache_and_rescan_extra_dirs)
- env.add_pref_observer("import_dirname",
- self._invalidate_cache_and_rescan_extra_dirs)
- db = self.mgr.db
- # Gather information about the current php.
- php = None
- if env.has_pref("php"):
- php = env.get_pref("php").strip() or None
- if not php or not exists(php):
- php = self._php_from_env(env)
- if not php:
- log.warn("no PHP was found from which to determine the "
- "import path")
- php_ver, include_path = None, []
- else:
- php_ver, include_path \
- = self._php_info_from_php(php, env)
-
- libs = []
- # - extradirslib
- extra_dirs = self._extra_dirs_from_env(env)
- if extra_dirs:
- libs.append( db.get_lang_lib("PHP", "extradirslib",
- extra_dirs, "PHP") )
- # - inilib (i.e. dirs in the include_path in PHP.ini)
- include_dirs = [d for d in include_path
- if d != '.' # handled separately
- if exists(d)]
- if include_dirs:
- max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
- php_assocs = env.assoc_patterns_from_lang("PHP")
- include_dirs = tuple(
- util.gen_dirs_under_dirs(include_dirs,
- max_depth=max_depth,
- interesting_file_patterns=php_assocs)
- )
- if include_dirs:
- libs.append( db.get_lang_lib("PHP", "inilib",
- include_dirs, "PHP") )
- # Warn the user if there is a huge number of import dirs that
- # might slow down completion.
- num_import_dirs = len(extra_dirs) + len(include_dirs)
- if num_import_dirs > 100:
- db.report_event("This buffer is configured with %d PHP "
- "import dirs: this may result in poor "
- "completion performance" % num_import_dirs)
- # - cataloglib, stdlib
- catalog_selections = env.get_pref("codeintel_selected_catalogs")
- libs += [
- db.get_catalog_lib("PHP", catalog_selections),
- db.get_stdlib("PHP", php_ver)
- ]
- env.cache[cache_key] = libs
- return env.cache[cache_key]
- def _invalidate_cache(self, env, pref_name):
- for key in ("php-buf-libs", "php-libs"):
- if key in env.cache:
- log.debug("invalidate '%s' cache on %r", key, env)
- del env.cache[key]
- def _invalidate_cache_and_rescan_extra_dirs(self, env, pref_name):
- self._invalidate_cache(env, pref_name)
- extra_dirs = self._extra_dirs_from_env(env)
- if extra_dirs:
- extradirslib = self.mgr.db.get_lang_lib(
- "PHP", "extradirslib", extra_dirs, "PHP")
- request = PreloadLibRequest(extradirslib)
- self.mgr.idxr.stage_request(request, 1.0)
- #---- code browser integration
- cb_import_group_title = "Includes and Requires"
- def cb_import_data_from_elem(self, elem):
- alias = elem.get("alias")
- symbol = elem.get("symbol")
- module = elem.get("module")
- if alias is not None:
- if symbol is not None:
- name = "%s (%s\%s)" % (alias, module, symbol)
- detail = "from %(module)s import %(symbol)s as %(alias)s" % locals()
- else:
- name = "%s (%s)" % (alias, module)
- detail = "import %(module)s as %(alias)s" % locals()
- elif symbol is not None:
- if module == "\\":
- name = '\\%s' % (symbol)
- else:
- name = '%s\\%s' % (module, symbol)
- detail = "from %(module)s import %(symbol)s" % locals()
- else:
- name = module
- detail = 'include "%s"' % module
- return {"name": name, "detail": detail}
- def cb_variable_data_from_elem(self, elem):
- """Use the 'constant' image in the Code Browser for a variable constant.
- """
- data = CitadelLangIntel.cb_variable_data_from_elem(self, elem)
- if elem.get("ilk") == "constant":
- data["img"] = "constant"
- return data
- class PHPBuffer(UDLBuffer, XMLParsingBufferMixin):
- lang = lang
- m_lang = "HTML"
- css_lang = "CSS"
- csl_lang = "JavaScript"
- ssl_lang = "PHP"
- cb_show_if_empty = True
- # Fillup chars for PHP: basically, any non-identifier char.
- # - dropped '#' to prevent annoying behavior with $form['#foo']
- # - dropped '@' I could find no common use of "@" following func/variable
- # - dropped '$' It gets in the way of common usage: "$this->$"
- # - dropped '\\' I could find no common use of "\" following func/variable
- # - dropped '?' It gets in the way of common usage: "<?php "
- # - dropped '/', it gets in the way of "</" closing an XML/HTML tag
- # - dropped '!' It gets in the way of "<!" in XML/HTML tag (bug 78632)
- # - dropped '=' It gets in the way of "<a href=" in XML/HTML cpln (bug 78632)
- # - dropped ':' It gets in the way of "<a d:blah=" in XML/HTML cpln
- # - dropped '>' It gets in the way of "<p>asdf</" in XML/HTML tag cpln (bug 80348)
- cpln_fillup_chars = "~`%^&*()-+{}[]|;'\",.< "
- #TODO: c.f. cpln_stop_chars stuff in lang_html.py
- # - dropping '[' because need for "<!<|>" -> "<![CDATA[" cpln
- # - dropping '#' because we need it for $form['#foo']
- # - dropping '$' because: MyClass::$class_var
- # - dropping '-' because causes problem with CSS (bug 78312)
- # - dropping '!' because causes problem with CSS "!important" (bug 78312)
- cpln_stop_chars = "~`@%^&*()=+{}]|\\;:'\",.<>?/ "
- def __init__(self, *args, **kwargs):
- super(PHPBuffer, self).__init__(*args, **kwargs)
-
- # Encourage the database to pre-scan dirs relevant to completion
- # for this buffer -- because of recursive-dir-include-everything
- # semantics for PHP this first-time scan can take a while.
- request = PreloadBufLibsRequest(self)
- self.mgr.idxr.stage_request(request, 1.0)
- @property
- def libs(self):
- return self.langintel.libs_from_buf(self)
- @property
- def stdlib(self):
- return self.libs[-1]
- class PHPImportHandler(ImportHandler):
- sep = '/'
- def setCorePath(self, compiler=None, extra=None):
- #XXX To do this independent of Komodo this would need to do all
- # the garbage that koIPHPInfoEx is doing to determine this. It
- # might also require adding a "rcfile" argument to this method
- # so the proper php.ini file is used in the "_shellOutForPath".
- # This is not crucial now because koCodeIntel._Manager() handles
- # this for us.
- if not self.corePath:
- raise CodeIntelError("Do not know how to determine the core "
- "PHP include path. 'corePath' must be set "
- "manually.")
- def _findScannableFiles(self, (files, searchedDirs), dirname, names):
- if sys.platform.startswith("win"):
- cpath = dirname.lower()
- else:
- cpath = dirname
- if cpath in searchedDirs:
- while names:
- del names[0]
- return
- else:
- searchedDirs[cpath] = 1
- for i in range(len(names)-1, -1, -1): # backward so can del from list
- path = os.path.join(dirname, names[i])
- if os.path.isdir(path):
- pass
- elif os.path.splitext(names[i])[1] in (".php", ".inc",
- ".module", ".tpl"):
- #XXX The list of extensions should be settable on
- # the ImportHandler and Komodo should set whatever is
- # set in prefs. ".module" and ".tpl" are for
- # drupal-nerds until CodeIntel gets this right.
- #XXX This check for files should probably include
- # scripts, which might likely not have the
- # extension: need to grow filetype-from-content smarts.
- files.append(path)
- def genScannableFiles(self, path=None, skipRareImports=False,
- importableOnly=False):
- if path is None:
- path = self._getPath()
- searchedDirs = {}
- for dirname in path:
- if dirname == os.curdir:
- # Do NOT traverse '.' if it is in the include_path. Not sure
- # if this is at all common for PHP.
- continue
- files = []
- os.path.walk(dirname, self._findScannableFiles,
- (files, searchedDirs))
- for file in files:
- yield file
- def find_importables_in_dir(self, dir):
- """See citadel.py::ImportHandler.find_importables_in_dir() for
- details.
- Importables for PHP look like this:
- {"foo.php": ("foo.php", None, False),
- "bar.inc": ("bar.inc", None, False),
- "somedir": (None, None, True)}
- TODO: log the fs-stat'ing a la codeintel.db logging.
- """
- from os.path import join, isdir
- from fnmatch import fnmatch
- if dir == "<Unsaved>":
- #TODO: stop these getting in here.
- return {}
- try:
- names = os.listdir(dir)
- except OSError, ex:
- return {}
- dirs, nondirs = set(), set()
- for name in names:
- try:
- if isdir(join(dir, name)):
- dirs.add(name)
- else:
- nondirs.add(name)
- except UnicodeDecodeError:
- # Hit a filename that cannot be encoded in the default encoding.
- # Just skip it. (Bug 82268)
- pass
- importables = {}
- patterns = self.mgr.env.assoc_patterns_from_lang("PHP")
- for name in nondirs:
- for pattern in patterns:
- if fnmatch(name, pattern):
- break
- else:
- continue
- if name in dirs:
- importables[name] = (name, None, True)
- dirs.remove(name)
- else:
- importables[name] = (name, None, False)
- for name in dirs:
- importables[name] = (None, None, True)
- return importables
- class PHPCILEDriver(UDLCILEDriver):
- lang = lang
- ssl_lang = "PHP"
- csl_lang = "JavaScript"
- def scan_multilang(self, buf, csl_cile_driver=None):
- #try:
- """Scan the given multilang (UDL-based) buffer and return a CIX
- element tree.
- "buf" is the multi-lang Buffer instance (e.g.
- lang_rhtml.RHTMLBuffer for RHTML).
- "csl_cile_driver" (optional) is the CSL (client-side language)
- CILE driver. While scanning, CSL tokens should be gathered and,
- if any, passed to the CSL scanner like this:
- csl_cile_driver.scan_csl_tokens(
- file_elem, blob_name, csl_tokens)
- The CSL scanner will append a CIX <scope ilk="blob"> element
- to the <file> element.
- """
- # Create the CIX tree.
- mtime = "XXX"
- fullpath = buf.path
- cixtree = createCixRoot()
- cixfile = createCixFile(cixtree, fullpath, lang=buf.lang)
- if sys.platform.startswith("win"):
- fullpath = fullpath.replace('\\', '/')
- basepath = os.path.basename(fullpath)
- cixblob = createCixModule(cixfile, basepath, "PHP", src=fullpath)
- phpciler = PHPParser(fullpath, buf.accessor.text, mtime)
- csl_tokens = phpciler.scan_multilang_content(buf.accessor.text)
- phpciler.convertToElementTreeModule(cixblob)
- # Hand off the csl tokens if any
- if csl_cile_driver and csl_tokens:
- csl_cile_driver.scan_csl_tokens(cixfile, basepath, csl_tokens)
- return cixtree
- #except Exception, e:
- # print "\nPHP cile exception"
- # import traceback
- # traceback.print_exc()
- # print
- # raise
- #---- internal routines and classes
- # States used by PHP scanner when parsing information
- S_DEFAULT = 0
- S_IN_ARGS = 1
- S_IN_ASSIGNMENT = 2
- S_IGNORE_SCOPE = 3
- S_OBJECT_ARGUMENT = 4
- S_GET_HEREDOC_MARKER = 5
- S_IN_HEREDOC = 6
- # Special tags for multilang handling (i.e. through UDL)
- S_OPEN_TAG = 10
- S_CHECK_CLOSE_TAG = 11
- S_IN_SCRIPT = 12
- # Types used by JavaScriptScanner when parsing information
- TYPE_NONE = 0
- TYPE_FUNCTION = 1
- TYPE_VARIABLE = 2
- TYPE_GETTER = 3
- TYPE_SETTER = 4
- TYPE_MEMBER = 5
- TYPE_OBJECT = 6
- TYPE_CLASS = 7
- TYPE_PARENT = 8
- def _sortByLineCmp(val1, val2):
- try:
- #if hasattr(val1, "line") and hasattr(val2, "line"):
- return cmp(val1.linestart, val2.linestart)
- except AttributeError:
- return cmp(val1, val2)
- def sortByLine(seq):
- seq.sort(_sortByLineCmp)
- return seq
- class PHPArg:
- def __init__(self, name, citdl=None, signature=None, default=None):
- """Set details for a function argument"""
- self.name = name
- self.citdl = citdl
- if signature:
- self.signature = signature
- else:
- if citdl:
- self.signature = "%s $%s" % (citdl, name)
- else:
- self.signature = "$%s" % (name, )
- self.default = default
- def __repr__(self):
- return self.signature
- def updateCitdl(self, citdl):
- self.citdl = citdl
- if self.signature.startswith("$") or self.signature.startswith("&") or \
- " " not in self.signature:
- self.signature = "%s %s" % (citdl, self.signature)
- else:
- self.signature = "%s %s" % (citdl, self.signature.split(" ", 1)[1])
- def toElementTree(self, cixelement):
- cixarg = addCixArgument(cixelement, self.name, argtype=self.citdl)
- if self.default:
- cixarg.attrib["default"] = self.default
- class PHPVariable:
- # PHPDoc variable type sniffer.
- _re_var = re.compile(r'^\s*@var\s+(\$(?P<variable>\w+)\s+)?(?P<type>\w+)(?:\s+(?P<doc>.*?))?', re.M|re.U)
- _ignored_php_types = ("object", "mixed")
- def __init__(self, name, line, vartype='', attributes='', doc=None,
- fromPHPDoc=False, namespace=None):
- self.name = name
- self.types = [(line, vartype, fromPHPDoc)]
- self.linestart = line
- if attributes:
- if not isinstance(attributes, list):
- attributes = attributes.strip().split()
- self.attributes = ' '.join(attributes)
- else:
- self.attributes = None
- self.doc = doc
- self.created_namespace = None
- if namespace:
- self.created_namespace = namespace.name
- def addType(self, line, type, fromPHPDoc=False):
- self.types.append((line, type, fromPHPDoc))
- def __repr__(self):
- return "var %s line %s type %s attributes %s\n"\
- % (self.name, self.linestart, self.types, self.attributes)
- def toElementTree(self, cixblob):
- # Work out the best vartype
- vartype = None
- doc = None
- if self.doc:
- # We are only storing the doc string for cases where we have an
- # "@var" phpdoc tag, we should actually store the docs anyway, but
- # we don't yet have a clean way to ensure the doc is really meant
- # for this specific variable (i.e. the comment was ten lines before
- # the variable definition).
- if "@var" in self.doc:
- doc = uncommentDocString(self.doc)
- # get the variable citdl type set by "@var"
- all_matches = re.findall(self._re_var, doc)
- if len(all_matches) >= 1:
- #print "all_matches[0]: %r" % (all_matches[0], )
- vartype = all_matches[0][2]
- if vartype and vartype.lower() in self._ignored_php_types:
- # Ignore these PHP types, they don't help codeintel.
- # http://bugs.activestate.com/show_bug.cgi?id=77602
- vartype = None
- if not vartype and self.types:
- d = {}
- max_count = 0
- for line, vtype, fromPHPDoc in self.types:
- if vtype:
- if fromPHPDoc:
- if vtype.lower() in self._ignored_php_types:
- # Ignore these PHP types, they don't help codeintel.
- continue
-