/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
Large files files are truncated, but you can click here to view the full file
- #!/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")
- …
Large files files are truncated, but you can click here to view the full file