PageRenderTime 63ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/codeintel2/lang_php.py

http://github.com/Kronuz/SublimeCodeIntel
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

  1. #!/usr/bin/env python
  2. # ***** BEGIN LICENSE BLOCK *****
  3. # Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4. #
  5. # The contents of this file are subject to the Mozilla Public License
  6. # Version 1.1 (the "License"); you may not use this file except in
  7. # compliance with the License. You may obtain a copy of the License at
  8. # http://www.mozilla.org/MPL/
  9. #
  10. # Software distributed under the License is distributed on an "AS IS"
  11. # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
  12. # License for the specific language governing rights and limitations
  13. # under the License.
  14. #
  15. # The Original Code is Komodo code.
  16. #
  17. # The Initial Developer of the Original Code is ActiveState Software Inc.
  18. # Portions created by ActiveState Software Inc are Copyright (C) 2000-2007
  19. # ActiveState Software Inc. All Rights Reserved.
  20. #
  21. # Contributor(s):
  22. # ActiveState Software Inc
  23. #
  24. # Alternatively, the contents of this file may be used under the terms of
  25. # either the GNU General Public License Version 2 or later (the "GPL"), or
  26. # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  27. # in which case the provisions of the GPL or the LGPL are applicable instead
  28. # of those above. If you wish to allow use of your version of this file only
  29. # under the terms of either the GPL or the LGPL, and not to allow others to
  30. # use your version of this file under the terms of the MPL, indicate your
  31. # decision by deleting the provisions above and replace them with the notice
  32. # and other provisions required by the GPL or the LGPL. If you do not delete
  33. # the provisions above, a recipient may use your version of this file under
  34. # the terms of any one of the MPL, the GPL or the LGPL.
  35. #
  36. # ***** END LICENSE BLOCK *****
  37. #
  38. # Contributors:
  39. # Shane Caraveo (ShaneC@ActiveState.com)
  40. # Trent Mick (TrentM@ActiveState.com)
  41. # Todd Whiteman (ToddW@ActiveState.com)
  42. """codeintel support for PHP"""
  43. import os
  44. from os.path import isdir, join, basename, splitext, exists, dirname
  45. import sys
  46. import re
  47. import logging
  48. import time
  49. import warnings
  50. from cStringIO import StringIO
  51. import weakref
  52. from glob import glob
  53. from SilverCity.ScintillaConstants import (SCE_UDL_SSL_DEFAULT,
  54. SCE_UDL_SSL_OPERATOR,
  55. SCE_UDL_SSL_IDENTIFIER,
  56. SCE_UDL_SSL_WORD,
  57. SCE_UDL_SSL_VARIABLE,
  58. SCE_UDL_SSL_STRING,
  59. SCE_UDL_SSL_NUMBER,
  60. SCE_UDL_SSL_COMMENT,
  61. SCE_UDL_SSL_COMMENTBLOCK)
  62. from codeintel2.parseutil import *
  63. from codeintel2.phpdoc import phpdoc_tags
  64. from codeintel2.citadel import ImportHandler, CitadelLangIntel
  65. from codeintel2.udl import UDLBuffer, UDLLexer, UDLCILEDriver, is_udl_csl_style, XMLParsingBufferMixin
  66. from codeintel2.common import *
  67. from codeintel2 import util
  68. from codeintel2.indexer import PreloadBufLibsRequest, PreloadLibRequest
  69. from codeintel2.gencix_utils import *
  70. from codeintel2.tree_php import PHPTreeEvaluator
  71. from codeintel2.langintel import (ParenStyleCalltipIntelMixin,
  72. ProgLangTriggerIntelMixin)
  73. from codeintel2.accessor import AccessorCache
  74. if _xpcom_:
  75. from xpcom.server import UnwrapObject
  76. #---- global data
  77. lang = "PHP"
  78. log = logging.getLogger("codeintel.php")
  79. #log.setLevel(logging.DEBUG)
  80. util.makePerformantLogger(log)
  81. #---- language support
  82. class PHPLexer(UDLLexer):
  83. lang = lang
  84. def _walk_php_symbols(elem, _prefix=None):
  85. if _prefix:
  86. lpath = _prefix + (elem.get("name"), )
  87. else:
  88. lpath = (elem.get("name"), )
  89. yield lpath
  90. if not (elem.tag == "scope" and elem.get("ilk") == "function"):
  91. for child in elem:
  92. for child_lpath in _walk_php_symbols(child, lpath):
  93. yield child_lpath
  94. class PHPLangIntel(CitadelLangIntel, ParenStyleCalltipIntelMixin,
  95. ProgLangTriggerIntelMixin):
  96. lang = lang
  97. # Used by ProgLangTriggerIntelMixin.preceding_trg_from_pos()
  98. trg_chars = tuple('$>:(,@"\' \\')
  99. calltip_trg_chars = tuple('(') # excluded ' ' for perf (bug 55497)
  100. # named styles used by the class
  101. whitespace_style = SCE_UDL_SSL_DEFAULT
  102. operator_style = SCE_UDL_SSL_OPERATOR
  103. identifier_style = SCE_UDL_SSL_IDENTIFIER
  104. keyword_style = SCE_UDL_SSL_WORD
  105. variable_style = SCE_UDL_SSL_VARIABLE
  106. string_style = SCE_UDL_SSL_STRING
  107. comment_styles = (SCE_UDL_SSL_COMMENT, SCE_UDL_SSL_COMMENTBLOCK)
  108. comment_styles_or_whitespace = comment_styles + (whitespace_style, )
  109. def _functionCalltipTrigger(self, ac, pos, DEBUG=False):
  110. # Implicit calltip triggering from an arg separater ",", we trigger a
  111. # calltip if we find a function open paren "(" and function identifier
  112. # http://bugs.activestate.com/show_bug.cgi?id=70470
  113. if DEBUG:
  114. print "Arg separater found, looking for start of function"
  115. # Move back to the open paren of the function
  116. paren_count = 0
  117. p = pos
  118. min_p = max(0, p - 200) # look back max 200 chars
  119. while p > min_p:
  120. p, c, style = ac.getPrecedingPosCharStyle(ignore_styles=self.comment_styles)
  121. if style == self.operator_style:
  122. if c == ")":
  123. paren_count += 1
  124. elif c == "(":
  125. if paren_count == 0:
  126. # We found the open brace of the func
  127. trg_from_pos = p+1
  128. p, ch, style = ac.getPrevPosCharStyle()
  129. if DEBUG:
  130. print "Function start found, pos: %d" % (p, )
  131. if style in self.comment_styles_or_whitespace:
  132. # Find previous non-ignored style then
  133. p, c, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  134. if style in (self.identifier_style, self.keyword_style):
  135. return Trigger(lang, TRG_FORM_CALLTIP,
  136. "call-signature",
  137. trg_from_pos, implicit=True)
  138. else:
  139. paren_count -= 1
  140. elif c in ";{}":
  141. # Gone too far and noting was found
  142. if DEBUG:
  143. print "No function found, hit stop char: %s at p: %d" % (c, p)
  144. return None
  145. # Did not find the function open paren
  146. if DEBUG:
  147. print "No function found, ran out of chars to look at, p: %d" % (p,)
  148. return None
  149. #@util.hotshotit
  150. def trg_from_pos(self, buf, pos, implicit=True, DEBUG=False, ac=None):
  151. #DEBUG = True
  152. if pos < 4:
  153. return None
  154. #DEBUG = True
  155. # Last four chars and styles
  156. if ac is None:
  157. ac = AccessorCache(buf.accessor, pos, fetchsize=4)
  158. last_pos, last_char, last_style = ac.getPrevPosCharStyle()
  159. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle()
  160. # Bump up how much text is retrieved when cache runs out
  161. ac.setCacheFetchSize(20)
  162. if DEBUG:
  163. print "\nphp trg_from_pos"
  164. print " last_pos: %s" % last_pos
  165. print " last_char: %s" % last_char
  166. print " last_style: %r" % last_style
  167. ac.dump()
  168. try:
  169. # Note: If a "$" exists by itself, it's styled as whitespace.
  170. # Generally we want it to be indicating a variable instead.
  171. if last_style == self.whitespace_style and last_char != "$":
  172. if DEBUG:
  173. print "Whitespace style"
  174. WHITESPACE = tuple(" \t\n\r\v\f")
  175. if not implicit:
  176. # If we're not already at the keyword style, find it
  177. if prev_style != self.keyword_style:
  178. prev_pos, prev_char, prev_style = ac.getPrecedingPosCharStyle(last_style, self.comment_styles)
  179. if DEBUG:
  180. print "Explicit: prev_pos: %d, style: %d, ch: %r" % (prev_pos, prev_style, prev_char)
  181. else:
  182. prev_pos = pos - 2
  183. if last_char in WHITESPACE and \
  184. (prev_style == self.keyword_style or
  185. (prev_style == self.operator_style and prev_char == ",")):
  186. p = prev_pos
  187. style = prev_style
  188. ch = prev_char
  189. #print "p: %d" % p
  190. while p > 0 and style == self.operator_style and ch == ",":
  191. p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  192. #print "p 1: %d" % p
  193. if p > 0 and style == self.identifier_style:
  194. # Skip the identifier too
  195. p, ch, style = ac.getPrecedingPosCharStyle(style, self.comment_styles_or_whitespace)
  196. #print "p 2: %d" % p
  197. if DEBUG:
  198. ac.dump()
  199. p, text = ac.getTextBackWithStyle(style, self.comment_styles, max_text_len=len("implements"))
  200. if DEBUG:
  201. print "ac.getTextBackWithStyle:: pos: %d, text: %r" % (p, text)
  202. if text in ("new", "extends"):
  203. return Trigger(lang, TRG_FORM_CPLN, "classes", pos, implicit)
  204. elif text in ("implements", ):
  205. return Trigger(lang, TRG_FORM_CPLN, "interfaces", pos, implicit)
  206. elif text in ("use"):
  207. return Trigger(lang, TRG_FORM_CPLN, "namespaces", pos, implicit)
  208. elif prev_style == self.operator_style and \
  209. prev_char == "," and implicit:
  210. return self._functionCalltipTrigger(ac, prev_pos, DEBUG)
  211. elif last_style == self.operator_style:
  212. if DEBUG:
  213. print " lang_style is operator style"
  214. print "Prev char: %r" % (prev_char)
  215. ac.dump()
  216. if last_char == ":":
  217. if not prev_char == ":":
  218. return None
  219. ac.setCacheFetchSize(10)
  220. p, c, style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles)
  221. if DEBUG:
  222. print "Preceding: %d, %r, %d" % (p, c, style)
  223. if style is None:
  224. return None
  225. elif style == self.keyword_style:
  226. # Check if it's a "self::" or "parent::" expression
  227. p, text = ac.getTextBackWithStyle(self.keyword_style,
  228. # Ensure we don't go too far
  229. max_text_len=6)
  230. if DEBUG:
  231. print "Keyword text: %d, %r" % (p, text)
  232. ac.dump()
  233. if text not in ("parent", "self", "static"):
  234. return None
  235. return Trigger(lang, TRG_FORM_CPLN, "static-members",
  236. pos, implicit)
  237. elif last_char == ">":
  238. if prev_char == "-":
  239. p, c, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace)
  240. if style in (self.variable_style, self.identifier_style) or \
  241. (style == self.operator_style and c == ')'):
  242. return Trigger(lang, TRG_FORM_CPLN, "object-members",
  243. pos, implicit)
  244. elif DEBUG:
  245. print "Preceding style is not a variable, pos: %d, style: %d" % (p, style)
  246. elif last_char in "(,":
  247. # where to trigger from, updated by "," calltip handler
  248. if DEBUG:
  249. print "Checking for function calltip"
  250. # Implicit calltip triggering from an arg separater ","
  251. # http://bugs.activestate.com/show_bug.cgi?id=70470
  252. if implicit and last_char == ',':
  253. return self._functionCalltipTrigger(ac, prev_pos, DEBUG)
  254. if prev_style in self.comment_styles_or_whitespace:
  255. # Find previous non-ignored style then
  256. p, c, prev_style = ac.getPrecedingPosCharStyle(prev_style, self.comment_styles_or_whitespace)
  257. if prev_style in (self.identifier_style, self.keyword_style):
  258. return Trigger(lang, TRG_FORM_CALLTIP, "call-signature",
  259. pos, implicit)
  260. elif last_char == "\\":
  261. # Ensure does not trigger when defining a new namespace,
  262. # i.e., do not trigger for:
  263. # namespace foo\<|>
  264. style = last_style
  265. while style in (self.operator_style, self.identifier_style):
  266. p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=30)
  267. if style == self.whitespace_style:
  268. p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30)
  269. if style is None:
  270. if DEBUG:
  271. print "Triggering namespace completion"
  272. return Trigger(lang, TRG_FORM_CPLN, "namespace-members",
  273. pos, implicit)
  274. prev_text = ac.getTextBackWithStyle(style, max_text_len=15)
  275. if DEBUG:
  276. print "prev_text: %r" % (prev_text, )
  277. if prev_text[1] != "namespace":
  278. if DEBUG:
  279. print "Triggering namespace completion"
  280. return Trigger(lang, TRG_FORM_CPLN, "namespace-members", pos, implicit)
  281. elif last_style == self.variable_style or \
  282. (not implicit and last_char == "$"):
  283. if DEBUG:
  284. print "Variable style"
  285. # Completion for variables (builtins and user defined variables),
  286. # must occur after a "$" character.
  287. if not implicit and last_char == '$':
  288. # Explicit call, move ahead one for real trigger position
  289. pos += 1
  290. if not implicit or prev_char == "$":
  291. # Ensure we are not triggering over static class variables.
  292. # Do this by checking that the preceding text is not "::"
  293. # http://bugs.activestate.com/show_bug.cgi?id=78099
  294. p, c, style = ac.getPrecedingPosCharStyle(last_style,
  295. max_look_back=30)
  296. if c == ":" and style == self.operator_style and \
  297. ac.getTextBackWithStyle(style, max_text_len=3)[1] == "::":
  298. return None
  299. return Trigger(lang, TRG_FORM_CPLN, "variables",
  300. pos-1, implicit)
  301. elif last_style in (self.identifier_style, self.keyword_style):
  302. if DEBUG:
  303. if last_style == self.identifier_style:
  304. print "Identifier style"
  305. else:
  306. print "Identifier keyword style"
  307. # Completion for keywords,function and class names
  308. # Works after first 3 characters have been typed
  309. #if DEBUG:
  310. # print "identifier_style: pos - 4 %s" % (accessor.style_at_pos(pos - 4))
  311. #third_char, third_style = last_four_char_and_styles[2]
  312. #fourth_char, fourth_style = last_four_char_and_styles[3]
  313. if prev_style == last_style:
  314. trig_pos, ch, style = ac.getPrevPosCharStyle()
  315. if style == last_style:
  316. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  317. # style is None if no change of style (not ignored) was
  318. # found in the last x number of chars
  319. #if not implicit and style == last_style:
  320. # if DEBUG:
  321. # print "Checking back further for explicit call"
  322. # p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=100)
  323. # if p is not None:
  324. # trg_pos = p + 3
  325. if style in (None, self.whitespace_style,
  326. self.operator_style):
  327. # Ensure we are not in another trigger zone, we do
  328. # this by checking that the preceeding text is not
  329. # one of "->", "::", "new", "function", "class", ...
  330. if style == self.whitespace_style:
  331. p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30)
  332. if style is None:
  333. return Trigger(lang, TRG_FORM_CPLN, "functions",
  334. trig_pos, implicit)
  335. prev_text = ac.getTextBackWithStyle(style, max_text_len=15)
  336. if DEBUG:
  337. print "prev_text: %r" % (prev_text, )
  338. if (prev_text[1] not in ("new", "function",
  339. "class", "interface", "implements",
  340. "public", "private", "protected",
  341. "final", "abstract", "instanceof",)
  342. # For the operator styles, we must use
  343. # endswith, as it could follow a "()",
  344. # bug 90846.
  345. and prev_text[1][-2:] not in ("->", "::",)):
  346. return Trigger(lang, TRG_FORM_CPLN, "functions",
  347. trig_pos, implicit)
  348. # If we want implicit triggering on more than 3 chars
  349. #elif style == self.identifier_style:
  350. # p, c, style = ac.getPrecedingPosCharStyle(self.identifier_style)
  351. # return Trigger(lang, TRG_FORM_CPLN, "functions",
  352. # p+1, implicit)
  353. elif DEBUG:
  354. print "identifier preceeded by an invalid style: " \
  355. "%r, p: %r" % (style, p, )
  356. elif last_char == '_' and prev_char == '_' and \
  357. style == self.whitespace_style:
  358. # XXX - Check the php version, magic methods only
  359. # appeared in php 5.
  360. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  361. if style == self.keyword_style and \
  362. ac.getTextBackWithStyle(style, max_text_len=9)[1] == "function":
  363. if DEBUG:
  364. print "triggered:: complete magic-methods"
  365. return Trigger(lang, TRG_FORM_CPLN, "magic-methods",
  366. prev_pos, implicit)
  367. # PHPDoc completions
  368. elif last_char == "@" and last_style in self.comment_styles:
  369. # If the preceeding non-whitespace character is a "*" or newline
  370. # then we complete for phpdoc tag names
  371. p = last_pos - 1
  372. min_p = max(0, p - 50) # Don't look more than 50 chars
  373. if DEBUG:
  374. print "Checking match for phpdoc completions"
  375. accessor = buf.accessor
  376. while p >= min_p and \
  377. accessor.style_at_pos(p) in self.comment_styles:
  378. ch = accessor.char_at_pos(p)
  379. p -= 1
  380. #if DEBUG:
  381. # print "Looking at ch: %r" % (ch)
  382. if ch in "*\r\n":
  383. break
  384. elif ch not in " \t\v":
  385. # Not whitespace, not a valid tag then
  386. return None
  387. else:
  388. # Nothing found in the specified range
  389. if DEBUG:
  390. print "trg_from_pos: not a phpdoc"
  391. return None
  392. if DEBUG:
  393. print "Matched trigger for phpdoc completion"
  394. return Trigger("PHP", TRG_FORM_CPLN,
  395. "phpdoc-tags", pos, implicit)
  396. # PHPDoc calltip
  397. elif last_char in " \t" and last_style in self.comment_styles:
  398. # whitespace in a comment, see if it matches for phpdoc calltip
  399. p = last_pos - 1
  400. min_p = max(0, p - 50) # Don't look more than 50 chars
  401. if DEBUG:
  402. print "Checking match for phpdoc calltip"
  403. ch = None
  404. ident_found_pos = None
  405. accessor = buf.accessor
  406. while p >= min_p and \
  407. accessor.style_at_pos(p) in self.comment_styles:
  408. ch = accessor.char_at_pos(p)
  409. p -= 1
  410. if ident_found_pos is None:
  411. #print "phpdoc: Looking for identifier, ch: %r" % (ch)
  412. if ch in " \t":
  413. pass
  414. elif _isident(ch):
  415. ident_found_pos = p+1
  416. else:
  417. if DEBUG:
  418. print "No phpdoc, whitespace not preceeded " \
  419. "by an identifer"
  420. return None
  421. elif ch == "@":
  422. # This is what we've been looking for!
  423. phpdoc_field = accessor.text_range(p+2,
  424. ident_found_pos+1)
  425. if DEBUG:
  426. print "Matched trigger for phpdoc calltip: '%s'" % (
  427. phpdoc_field, )
  428. return Trigger("PHP", TRG_FORM_CALLTIP,
  429. "phpdoc-tags", ident_found_pos, implicit,
  430. phpdoc_field=phpdoc_field)
  431. elif not _isident(ch):
  432. if DEBUG:
  433. print "No phpdoc, identifier not preceeded by '@'"
  434. # Not whitespace, not a valid tag then
  435. return None
  436. # Nothing found in the specified range
  437. if DEBUG:
  438. print "No phpdoc, ran out of characters to look at."
  439. # Array completions
  440. elif last_style == self.string_style and last_char in '\'"':
  441. if prev_char != '[':
  442. if prev_style in self.comment_styles_or_whitespace:
  443. # Look back further.
  444. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace)
  445. if prev_char == '[':
  446. # We're good to go.
  447. if DEBUG:
  448. print "Matched trigger for array completions"
  449. return Trigger("PHP", TRG_FORM_CPLN,
  450. "array-members", pos, implicit,
  451. bracket_pos=prev_pos,
  452. trg_char=last_char)
  453. # Variable completions inside of comments
  454. elif prev_char == "$" and last_style in self.comment_styles:
  455. if DEBUG:
  456. print "Comment variable style"
  457. # Completion for variables (builtins and user defined variables),
  458. # must occur after a "$" character.
  459. return Trigger(lang, TRG_FORM_CPLN, "comment-variables",
  460. pos-1, implicit)
  461. elif DEBUG:
  462. print "trg_from_pos: no handle for style: %d" % last_style
  463. except IndexError:
  464. # Not enough chars found, therefore no trigger
  465. pass
  466. return None
  467. #@util.hotshotit
  468. def preceding_trg_from_pos(self, buf, pos, curr_pos,
  469. preceding_trg_terminators=None, DEBUG=False):
  470. #DEBUG = True
  471. # Try the default preceding_trg_from_pos handler
  472. trg = ProgLangTriggerIntelMixin.preceding_trg_from_pos(
  473. self, buf, pos, curr_pos, preceding_trg_terminators,
  474. DEBUG=DEBUG)
  475. if trg is not None:
  476. return trg
  477. # Else, let's try to work out some other options
  478. accessor = buf.accessor
  479. prev_style = accessor.style_at_pos(curr_pos - 1)
  480. if prev_style in (self.identifier_style, self.keyword_style):
  481. # We don't know what to trigger here... could be one of:
  482. # functions:
  483. # apache<$><|>_getenv()...
  484. # if(get_e<$><|>nv()...
  485. # classes:
  486. # new Exce<$><|>ption()...
  487. # extends Exce<$><|>ption()...
  488. # interfaces:
  489. # implements apache<$><|>_getenv()...
  490. ac = AccessorCache(accessor, curr_pos)
  491. pos_before_identifer, ch, prev_style = \
  492. ac.getPrecedingPosCharStyle(prev_style)
  493. if DEBUG:
  494. print "\nphp preceding_trg_from_pos, first chance for identifer style"
  495. print " curr_pos: %d" % (curr_pos)
  496. print " pos_before_identifer: %d" % (pos_before_identifer)
  497. print " ch: %r" % ch
  498. print " prev_style: %d" % prev_style
  499. ac.dump()
  500. if pos_before_identifer < pos:
  501. resetPos = min(pos_before_identifer + 4, accessor.length() - 1)
  502. ac.resetToPosition(resetPos)
  503. if DEBUG:
  504. print "preceding_trg_from_pos:: reset to position: %d, ac now:" % (resetPos)
  505. ac.dump()
  506. # Trigger on the third identifier character
  507. return self.trg_from_pos(buf, resetPos,
  508. implicit=False, DEBUG=DEBUG, ac=ac)
  509. elif DEBUG:
  510. print "Out of scope of the identifier"
  511. elif prev_style in self.comment_styles:
  512. # Check if there is a PHPDoc to provide a calltip for, example:
  513. # /** @param $foo foobar - This is field for <|>
  514. if DEBUG:
  515. print "\nphp preceding_trg_from_pos::phpdoc: check for calltip"
  516. comment = accessor.text_range(max(0, curr_pos-200), curr_pos)
  517. at_idx = comment.rfind("@")
  518. if at_idx >= 0:
  519. if DEBUG:
  520. print "\nphp preceding_trg_from_pos::phpdoc: contains '@'"
  521. space_idx = comment[at_idx:].find(" ")
  522. if space_idx >= 0:
  523. # Trigger after the space character.
  524. trg_pos = (curr_pos - len(comment)) + at_idx + space_idx + 1
  525. if DEBUG:
  526. print "\nphp preceding_trg_from_pos::phpdoc: calltip at %d" % (trg_pos, )
  527. return self.trg_from_pos(buf, trg_pos,
  528. implicit=False, DEBUG=DEBUG)
  529. _phpdoc_cplns = [ ("variable", t) for t in sorted(phpdoc_tags) ]
  530. #@util.hotshotit
  531. def async_eval_at_trg(self, buf, trg, ctlr):
  532. if _xpcom_:
  533. trg = UnwrapObject(trg)
  534. ctlr = UnwrapObject(ctlr)
  535. pos = trg.pos
  536. ctlr.start(buf, trg)
  537. #print "trg.type: %r" % (trg.type)
  538. # PHPDoc completions
  539. if trg.id == ("PHP", TRG_FORM_CPLN, "phpdoc-tags"):
  540. #TODO: Would like a "javadoc tag" completion image name.
  541. ctlr.set_cplns(self._phpdoc_cplns)
  542. ctlr.done("success")
  543. return
  544. # PHPDoc calltip
  545. elif trg.id == ("PHP", TRG_FORM_CALLTIP, "phpdoc-tags"):
  546. phpdoc_field = trg.extra.get("phpdoc_field")
  547. if phpdoc_field:
  548. #print "phpdoc_field: %r" % (phpdoc_field, )
  549. calltip = phpdoc_tags.get(phpdoc_field)
  550. if calltip:
  551. ctlr.set_calltips([calltip])
  552. ctlr.done("success")
  553. return
  554. elif trg.type in ("classes", "interfaces"):
  555. # Triggers from zero characters, thus calling citdl_expr_from_trg
  556. # is no help
  557. line = buf.accessor.line_from_pos(pos)
  558. evalr = PHPTreeEvaluator(ctlr, buf, trg, "", line)
  559. buf.mgr.request_eval(evalr)
  560. else:
  561. try:
  562. citdl_expr = self.citdl_expr_from_trg(buf, trg)
  563. except CodeIntelError, ex:
  564. ctlr.error(str(ex))
  565. ctlr.done("error")
  566. return
  567. line = buf.accessor.line_from_pos(pos)
  568. evalr = PHPTreeEvaluator(ctlr, buf, trg, citdl_expr, line)
  569. buf.mgr.request_eval(evalr)
  570. def _citdl_expr_from_pos(self, trg, buf, pos, implicit=True,
  571. include_forwards=False, DEBUG=False):
  572. #DEBUG = True
  573. #PERF: Would dicts be faster for all of these?
  574. WHITESPACE = tuple(" \t\n\r\v\f")
  575. EOL = tuple("\r\n")
  576. BLOCKCLOSES = tuple(")}]")
  577. STOPOPS = tuple("({[,&$+=^|%/<;:->!.@?")
  578. EXTRA_STOPOPS_PRECEDING_IDENT = BLOCKCLOSES # Might be others.
  579. #TODO: This style picking is a problem for the LangIntel move.
  580. if trg.type == "comment-variables":
  581. # Dev note: skip_styles in the other cases below will be a dict.
  582. skip_styles = set()
  583. elif implicit:
  584. skip_styles = buf.implicit_completion_skip_styles
  585. else:
  586. skip_styles = buf.completion_skip_styles
  587. citdl_expr = []
  588. accessor = buf.accessor
  589. # Use a cache of characters, easy to keep track this way
  590. i = pos
  591. ac = AccessorCache(accessor, i)
  592. if include_forwards:
  593. try:
  594. # Move ahead to include forward chars as well
  595. lastch_was_whitespace = False
  596. while 1:
  597. i, ch, style = ac.getNextPosCharStyle()
  598. if DEBUG:
  599. print "include_forwards:: i now: %d, ch: %r" % (i, ch)
  600. if ch in WHITESPACE:
  601. lastch_was_whitespace = True
  602. continue
  603. lastch_was_whitespace = False
  604. if ch in STOPOPS:
  605. if DEBUG:
  606. print "include_forwards:: ch in STOPOPS, i:%d ch:%r" % (i, ch)
  607. break
  608. elif ch in BLOCKCLOSES:
  609. if DEBUG:
  610. print "include_forwards:: ch in BLOCKCLOSES, i:%d ch:%r" % (i, ch)
  611. break
  612. elif lastch_was_whitespace:
  613. # Two whitespace separated words
  614. if DEBUG:
  615. print "include_forwards:: ch separated by whitespace, i:%d ch:%r" % (i, ch)
  616. break
  617. # Move back to last valid char
  618. i -= 1
  619. if DEBUG:
  620. if i > pos:
  621. print "include_forwards:: Including chars from pos %d up to %d" % (pos, i)
  622. else:
  623. print "include_forwards:: No valid chars forward from pos %d, i now: %d" % (pos, i)
  624. except IndexError:
  625. # Nothing forwards, user what we have then
  626. i = min(i, accessor.length() - 1)
  627. if DEBUG:
  628. print "include_forwards:: No more buffer, i now: %d" % (i)
  629. ac.resetToPosition(i)
  630. ch = None
  631. try:
  632. while i >= 0:
  633. if ch == None and include_forwards:
  634. i, ch, style = ac.getCurrentPosCharStyle()
  635. else:
  636. i, ch, style = ac.getPrevPosCharStyle()
  637. if DEBUG:
  638. print "i now: %d, ch: %r" % (i, ch)
  639. if ch in WHITESPACE:
  640. if trg.type in ("namespaces", "namespace-members"):
  641. # Namespaces cannot be split over whitespace.
  642. break
  643. while ch in WHITESPACE:
  644. # drop all whitespace
  645. next_char = ch
  646. i, ch, style = ac.getPrevPosCharStyle()
  647. if ch in WHITESPACE \
  648. or (ch == '\\' and next_char in EOL):
  649. if DEBUG:
  650. print "drop whitespace: %r" % ch
  651. # If there are two whitespace-separated words then this is
  652. # (likely or always?) a language keyword or declaration
  653. # construct at which we want to stop. E.g.
  654. # if foo<|> and ...
  655. # def foo<|>(...
  656. # if \foo<|>(... # uses a namespace
  657. if citdl_expr \
  658. and (_isident(citdl_expr[-1]) or citdl_expr[-1] == '\\') \
  659. and (_isident(ch) or _isdigit(ch)):
  660. if DEBUG:
  661. print "stop at (likely?) start of keyword or "\
  662. "declaration: %r" % ch
  663. break
  664. # Not whitespace anymore, move into the main checks below
  665. if DEBUG:
  666. print "Out of whitespace: i now: %d, ch: %s" % (i, ch)
  667. if style in skip_styles: # drop styles to ignore
  668. while i >= 0 and style in skip_styles:
  669. i, ch, style = ac.getPrevPosCharStyle()
  670. if DEBUG:
  671. print "drop char of style to ignore: %r" % ch
  672. elif ch in ":>" and i > 0:
  673. # Next char has to be ":" or "-" respectively
  674. prev_pos, prev_ch, prev_style = ac.getPrevPosCharStyle()
  675. if (ch == ">" and prev_ch == "-") or \
  676. (ch == ":" and prev_ch == ":"):
  677. citdl_expr.append(".")
  678. if DEBUG:
  679. print "Turning member accessor '%s%s' into '.'" % (prev_ch, ch)
  680. i -= 2
  681. else:
  682. if DEBUG:
  683. print "citdl_expr: %r" % (citdl_expr)
  684. print "stop at special stop-operator %d: %r" % (i, ch)
  685. break
  686. elif (ch in STOPOPS or ch in EXTRA_STOPOPS_PRECEDING_IDENT) and \
  687. (ch != ")" or (citdl_expr and citdl_expr[-1] != ".")):
  688. if ch == '$':
  689. # This may not be the end of the road, given static
  690. # variables are accessed through "Class::$static".
  691. prev_pos, prev_ch, prev_style = ac.peekPrevPosCharStyle()
  692. if prev_ch == ":":
  693. # Continue building up the citdl then.
  694. continue
  695. if DEBUG:
  696. print "citdl_expr: %r" % (citdl_expr)
  697. print "stop at stop-operator %d: %r" % (i, ch)
  698. break
  699. elif ch in BLOCKCLOSES:
  700. if DEBUG:
  701. print "found block at %d: %r" % (i, ch)
  702. citdl_expr.append(ch)
  703. BLOCKS = { # map block close char to block open char
  704. ')': '(',
  705. ']': '[',
  706. '}': '{',
  707. }
  708. stack = [] # stack of blocks: (<block close char>, <style>)
  709. stack.append( (ch, style, BLOCKS[ch], i) )
  710. while i >= 0:
  711. i, ch, style = ac.getPrevPosCharStyle()
  712. if DEBUG:
  713. print "finding matching brace: ch %r (%s), stack %r"\
  714. % (ch, ', '.join(buf.style_names_from_style_num(style)), stack)
  715. if ch in BLOCKS and style not in skip_styles:
  716. stack.append( (ch, style, BLOCKS[ch]) )
  717. elif ch == stack[-1][2] and style not in skip_styles:
  718. #XXX Replace the second test with the following
  719. # when LexPython+SilverCity styling bugs are fixed
  720. # (spurious 'stderr' problem):
  721. # and style == stack[-1][1]:
  722. stack.pop()
  723. if not stack:
  724. if DEBUG:
  725. print "jump to matching brace at %d: %r" % (i, ch)
  726. citdl_expr.append(ch)
  727. break
  728. else:
  729. # Didn't find the matching brace.
  730. if DEBUG:
  731. print "couldn't find matching brace"
  732. raise EvalError("could not find matching brace for "
  733. "'%s' at position %d"
  734. % (stack[-1][0], stack[-1][3]))
  735. else:
  736. if DEBUG:
  737. style_names = buf.style_names_from_style_num(style)
  738. print "add char: %r (%s)" % (ch, ', '.join(style_names))
  739. citdl_expr.append(ch)
  740. i -= 1
  741. except IndexError:
  742. # Nothing left to consume, return what we have
  743. pass
  744. # Remove any unecessary starting dots
  745. while citdl_expr and citdl_expr[-1] == ".":
  746. citdl_expr.pop()
  747. citdl_expr.reverse()
  748. citdl_expr = ''.join(citdl_expr)
  749. if DEBUG:
  750. print "return: %r" % citdl_expr
  751. print util.banner("done")
  752. return citdl_expr
  753. def citdl_expr_from_trg(self, buf, trg):
  754. """Return a PHP CITDL expression preceding the given trigger.
  755. The expression drops newlines, whitespace, and function call
  756. arguments -- basically any stuff that is not used by the codeintel
  757. database system for determining the resultant object type of the
  758. expression. For example (in which <|> represents the given position):
  759. GIVEN RETURN
  760. ----- ------
  761. foo-<|>> foo
  762. Foo:<|>: Foo
  763. foo(bar-<|>> bar
  764. foo(bar,blam)-<|>> foo()
  765. foo(bar, foo()
  766. blam)-<|>>
  767. foo(arg1, arg2)->bar-<|>> foo().bar
  768. Foo(arg1, arg2)::bar-<|>> Foo().bar
  769. Foo\bar:<|>: Foo\bar
  770. Foo\bar::bam-<|>> Foo\bar.bam
  771. Foo\bar(arg1, arg2)::bam-<|>> Foo\bar().bam
  772. """
  773. #DEBUG = True
  774. DEBUG = False
  775. if DEBUG:
  776. print util.banner("%s citdl_expr_from_trg @ %r" % (buf.lang, trg))
  777. if trg.form == TRG_FORM_CPLN:
  778. # "->" or "::"
  779. if trg.type == "classes":
  780. i = trg.pos + 1
  781. elif trg.type == "functions":
  782. i = trg.pos + 3 # 3-char trigger, skip over it
  783. elif trg.type in ("variables", "comment-variables"):
  784. i = trg.pos + 1 # triggered on the $, skip over it
  785. elif trg.type == "array-members":
  786. i = trg.extra.get("bracket_pos") # triggered on foo['
  787. elif trg.type == "namespaces":
  788. i = trg.pos + 1
  789. elif trg.type == "namespace-members":
  790. i = trg.pos - 1
  791. else:
  792. i = trg.pos - 2 # skip past the trigger char
  793. return self._citdl_expr_from_pos(trg, buf, i, trg.implicit,
  794. DEBUG=DEBUG)
  795. elif trg.form == TRG_FORM_DEFN:
  796. return self.citdl_expr_under_pos(trg, buf, trg.pos, DEBUG)
  797. else: # trg.form == TRG_FORM_CALLTIP:
  798. # (<|>
  799. return self._citdl_expr_from_pos(trg, buf, trg.pos-1, trg.implicit,
  800. DEBUG=DEBUG)
  801. def citdl_expr_under_pos(self, trg, buf, pos, DEBUG=False):
  802. """Return a PHP CITDL expression around the given pos.
  803. Similar to citdl_expr_from_trg(), but looks forward to grab additional
  804. characters.
  805. GIVEN RETURN
  806. ----- ------
  807. foo-<|>> foo
  808. F<|>oo:: Foo
  809. foo->ba<|>r foo.bar
  810. f<|>oo->bar foo
  811. foo(bar-<|>> bar
  812. foo(bar,blam)-<|>> foo()
  813. foo(bar, foo()
  814. blam)-<|>>
  815. foo(arg1, arg2)->bar-<|>> foo().bar
  816. Foo(arg1, arg2)::ba<|>r-> Foo().bar
  817. Fo<|>o(arg1, arg2)::bar-> Foo
  818. """
  819. #DEBUG = True
  820. expr = self._citdl_expr_from_pos(trg, buf, pos-1, implicit=True,
  821. include_forwards=True, DEBUG=DEBUG)
  822. if expr:
  823. # Chop off any trailing "." characters
  824. return expr.rstrip(".")
  825. return expr
  826. def libs_from_buf(self, buf):
  827. env = buf.env
  828. # A buffer's libs depend on its env and the buf itself so
  829. # we cache it on the env and key off the buffer.
  830. if "php-buf-libs" not in env.cache:
  831. env.cache["php-buf-libs"] = weakref.WeakKeyDictionary()
  832. cache = env.cache["php-buf-libs"] # <buf-weak-ref> -> <libs>
  833. if buf not in cache:
  834. # - curdirlib
  835. # Using the dirname of this buffer isn't always right, but
  836. # hopefully is a good first approximation.
  837. cwd = dirname(buf.path)
  838. if cwd == "<Unsaved>":
  839. libs = []
  840. else:
  841. libs = [ self.mgr.db.get_lang_lib("PHP", "curdirlib", [cwd], "PHP")]
  842. libs += self._buf_indep_libs_from_env(env)
  843. cache[buf] = libs
  844. return cache[buf]
  845. def lpaths_from_blob(self, blob):
  846. """Return <lpaths> for this blob
  847. where,
  848. <lpaths> is a set of externally referencable lookup-paths, e.g.
  849. [("MyOwnClass",), ("MyOwnClass", "function1"), ...]
  850. """
  851. return set(lpath for child in blob
  852. for lpath in _walk_php_symbols(child))
  853. def _php_from_env(self, env):
  854. import which
  855. path = [d.strip()
  856. for d in env.get_envvar("PATH", "").split(os.pathsep)
  857. if d.strip()]
  858. for exe_name in ("php", "php4", "php-cgi", "php-cli"):
  859. try:
  860. return which.which(exe_name, path=path)
  861. except which.WhichError:
  862. pass
  863. return None
  864. def _php_info_from_php(self, php, env):
  865. """Call the given PHP and return:
  866. (<version>, <include_path>)
  867. Returns (None, []) if could not determine.
  868. """
  869. import process
  870. import tempfile
  871. # Use a marker to separate the start of output from possible
  872. # leading lines of PHP loading errors/logging.
  873. marker = "--- Start of Good Stuff ---"
  874. info_cmd = (r'<?php '
  875. + r'echo("%s\n");' % marker
  876. + r'echo(phpversion()."\n");'
  877. + r'echo(ini_get("include_path")."\n");'
  878. + r' ?>')
  879. argv = [php]
  880. envvars = env.get_all_envvars()
  881. php_ini_path = env.get_pref("phpConfigFile")
  882. if php_ini_path:
  883. envvars["PHPRC"] = php_ini_path
  884. fd, filepath = tempfile.mkstemp(suffix=".php")
  885. try:
  886. os.write(fd, info_cmd)
  887. os.close(fd)
  888. argv.append(filepath)
  889. p = process.ProcessOpen(argv, env=env.get_all_envvars())
  890. stdout, stderr = p.communicate()
  891. finally:
  892. os.remove(filepath)
  893. stdout_lines = stdout.splitlines(0)
  894. retval = p.returncode
  895. if retval:
  896. log.warn("failed to determine PHP info:\n"
  897. " path: %s\n"
  898. " retval: %s\n"
  899. " stdout:\n%s\n"
  900. " stderr:\n%s\n",
  901. php, retval, util.indent('\n'.join(stdout_lines)),
  902. util.indent(stderr))
  903. return None, []
  904. stdout_lines = stdout_lines[stdout_lines.index(marker)+1:]
  905. php_ver = stdout_lines[0]
  906. include_path = [p.strip() for p in stdout_lines[1].split(os.pathsep)
  907. if p.strip()]
  908. return php_ver, include_path
  909. def _extra_dirs_from_env(self, env):
  910. extra_dirs = set()
  911. include_project = env.get_pref("codeintel_scan_files_in_project", True)
  912. if include_project:
  913. proj_base_dir = env.get_proj_base_dir()
  914. if proj_base_dir is not None:
  915. extra_dirs.add(proj_base_dir) # Bug 68850.
  916. for pref in env.get_all_prefs("phpExtraPaths"):
  917. if not pref: continue
  918. extra_dirs.update(d.strip() for d in pref.split(os.pathsep)
  919. if exists(d.strip()))
  920. if extra_dirs:
  921. log.debug("PHP extra lib dirs: %r", extra_dirs)
  922. max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
  923. php_assocs = env.assoc_patterns_from_lang("PHP")
  924. extra_dirs = tuple(
  925. util.gen_dirs_under_dirs(extra_dirs,
  926. max_depth=max_depth,
  927. interesting_file_patterns=php_assocs)
  928. )
  929. else:
  930. extra_dirs = () # ensure retval is a tuple
  931. return extra_dirs
  932. def _buf_indep_libs_from_env(self, env):
  933. """Create the buffer-independent list of libs."""
  934. cache_key = "php-libs"
  935. if cache_key not in env.cache:
  936. env.add_pref_observer("php", self._invalidate_cache)
  937. env.add_pref_observer("phpExtraPaths",
  938. self._invalidate_cache_and_rescan_extra_dirs)
  939. env.add_pref_observer("phpConfigFile",
  940. self._invalidate_cache)
  941. env.add_pref_observer("codeintel_selected_catalogs",
  942. self._invalidate_cache)
  943. env.add_pref_observer("codeintel_max_recursive_dir_depth",
  944. self._invalidate_cache)
  945. env.add_pref_observer("codeintel_scan_files_in_project",
  946. self._invalidate_cache)
  947. # (Bug 68850) Both of these 'live_*' prefs on the *project*
  948. # prefset can result in a change of project base dir. It is
  949. # possible that we can false positives here if there is ever
  950. # a global pref of this name.
  951. env.add_pref_observer("import_live",
  952. self._invalidate_cache_and_rescan_extra_dirs)
  953. env.add_pref_observer("import_dirname",
  954. self._invalidate_cache_and_rescan_extra_dirs)
  955. db = self.mgr.db
  956. # Gather information about the current php.
  957. php = None
  958. if env.has_pref("php"):
  959. php = env.get_pref("php").strip() or None
  960. if not php or not exists(php):
  961. php = self._php_from_env(env)
  962. if not php:
  963. log.warn("no PHP was found from which to determine the "
  964. "import path")
  965. php_ver, include_path = None, []
  966. else:
  967. php_ver, include_path \
  968. = self._php_info_from_php(php, env)
  969. libs = []
  970. # - extradirslib
  971. extra_dirs = self._extra_dirs_from_env(env)
  972. if extra_dirs:
  973. libs.append( db.get_lang_lib("PHP", "extradirslib",
  974. extra_dirs, "PHP") )
  975. # - inilib (i.e. dirs in the include_path in PHP.ini)
  976. include_dirs = [d for d in include_path
  977. if d != '.' # handled separately
  978. if exists(d)]
  979. if include_dirs:
  980. max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
  981. php_assocs = env.assoc_patterns_from_lang("PHP")

Large files files are truncated, but you can click here to view the full file