PageRenderTime 53ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/libs/codeintel2/lang_php.py

https://github.com/MinerCrafter/SublimeCodeIntel
Python | 3309 lines | 2996 code | 88 blank | 225 comment | 255 complexity | 552fa497b347d494869fd91c766ef9e6 MD5 | raw file
Possible License(s): 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, KoDocumentAccessor
  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, "use", 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] == "use":
  278. if DEBUG:
  279. print "Triggering use-namespace completion"
  280. return Trigger(lang, TRG_FORM_CPLN, "use-namespace",
  281. pos, implicit)
  282. elif prev_text[1] != "namespace":
  283. if DEBUG:
  284. print "Triggering namespace completion"
  285. return Trigger(lang, TRG_FORM_CPLN, "namespace-members",
  286. pos, implicit)
  287. elif last_style == self.variable_style or \
  288. (not implicit and last_char == "$"):
  289. if DEBUG:
  290. print "Variable style"
  291. # Completion for variables (builtins and user defined variables),
  292. # must occur after a "$" character.
  293. if not implicit and last_char == '$':
  294. # Explicit call, move ahead one for real trigger position
  295. pos += 1
  296. if not implicit or prev_char == "$":
  297. # Ensure we are not triggering over static class variables.
  298. # Do this by checking that the preceding text is not "::"
  299. # http://bugs.activestate.com/show_bug.cgi?id=78099
  300. p, c, style = ac.getPrecedingPosCharStyle(last_style,
  301. max_look_back=30)
  302. if c == ":" and style == self.operator_style and \
  303. ac.getTextBackWithStyle(style, max_text_len=3)[1] == "::":
  304. return None
  305. return Trigger(lang, TRG_FORM_CPLN, "variables",
  306. pos-1, implicit)
  307. elif last_style in (self.identifier_style, self.keyword_style):
  308. if DEBUG:
  309. if last_style == self.identifier_style:
  310. print "Identifier style"
  311. else:
  312. print "Identifier keyword style"
  313. # Completion for keywords,function and class names
  314. # Works after first 3 characters have been typed
  315. #if DEBUG:
  316. # print "identifier_style: pos - 4 %s" % (accessor.style_at_pos(pos - 4))
  317. #third_char, third_style = last_four_char_and_styles[2]
  318. #fourth_char, fourth_style = last_four_char_and_styles[3]
  319. if prev_style == last_style:
  320. trig_pos, ch, style = ac.getPrevPosCharStyle()
  321. if style == last_style:
  322. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  323. # style is None if no change of style (not ignored) was
  324. # found in the last x number of chars
  325. #if not implicit and style == last_style:
  326. # if DEBUG:
  327. # print "Checking back further for explicit call"
  328. # p, c, style = ac.getPrecedingPosCharStyle(style, max_look_back=100)
  329. # if p is not None:
  330. # trg_pos = p + 3
  331. if style in (None, self.whitespace_style,
  332. self.operator_style):
  333. # Ensure we are not in another trigger zone, we do
  334. # this by checking that the preceeding text is not
  335. # one of "->", "::", "new", "function", "class", ...
  336. if style == self.whitespace_style:
  337. p, c, style = ac.getPrecedingPosCharStyle(self.whitespace_style, max_look_back=30)
  338. if style is None:
  339. return Trigger(lang, TRG_FORM_CPLN, "functions",
  340. trig_pos, implicit)
  341. prev_text = ac.getTextBackWithStyle(style, max_text_len=15)
  342. if DEBUG:
  343. print "prev_text: %r" % (prev_text, )
  344. if (prev_text[1] not in ("new", "function", "use",
  345. "class", "interface", "implements",
  346. "public", "private", "protected",
  347. "final", "abstract", "instanceof",)
  348. # For the operator styles, we must use
  349. # endswith, as it could follow a "()",
  350. # bug 90846.
  351. and prev_text[1][-2:] not in ("->", "::",)
  352. # Don't trigger when accessing a
  353. # namespace - bug 88736.
  354. and not prev_text[1].endswith("\\")):
  355. return Trigger(lang, TRG_FORM_CPLN, "functions",
  356. trig_pos, implicit)
  357. # If we want implicit triggering on more than 3 chars
  358. #elif style == self.identifier_style:
  359. # p, c, style = ac.getPrecedingPosCharStyle(self.identifier_style)
  360. # return Trigger(lang, TRG_FORM_CPLN, "functions",
  361. # p+1, implicit)
  362. elif DEBUG:
  363. print "identifier preceeded by an invalid style: " \
  364. "%r, p: %r" % (style, p, )
  365. elif last_char == '_' and prev_char == '_' and \
  366. style == self.whitespace_style:
  367. # XXX - Check the php version, magic methods only
  368. # appeared in php 5.
  369. p, ch, style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles)
  370. if style == self.keyword_style and \
  371. ac.getTextBackWithStyle(style, max_text_len=9)[1] == "function":
  372. if DEBUG:
  373. print "triggered:: complete magic-methods"
  374. return Trigger(lang, TRG_FORM_CPLN, "magic-methods",
  375. prev_pos, implicit)
  376. # PHPDoc completions
  377. elif last_char == "@" and last_style in self.comment_styles:
  378. # If the preceeding non-whitespace character is a "*" or newline
  379. # then we complete for phpdoc tag names
  380. p = last_pos - 1
  381. min_p = max(0, p - 50) # Don't look more than 50 chars
  382. if DEBUG:
  383. print "Checking match for phpdoc completions"
  384. accessor = buf.accessor
  385. while p >= min_p and \
  386. accessor.style_at_pos(p) in self.comment_styles:
  387. ch = accessor.char_at_pos(p)
  388. p -= 1
  389. #if DEBUG:
  390. # print "Looking at ch: %r" % (ch)
  391. if ch in "*\r\n":
  392. break
  393. elif ch not in " \t\v":
  394. # Not whitespace, not a valid tag then
  395. return None
  396. else:
  397. # Nothing found in the specified range
  398. if DEBUG:
  399. print "trg_from_pos: not a phpdoc"
  400. return None
  401. if DEBUG:
  402. print "Matched trigger for phpdoc completion"
  403. return Trigger("PHP", TRG_FORM_CPLN,
  404. "phpdoc-tags", pos, implicit)
  405. # PHPDoc calltip
  406. elif last_char in " \t" and last_style in self.comment_styles:
  407. # whitespace in a comment, see if it matches for phpdoc calltip
  408. p = last_pos - 1
  409. min_p = max(0, p - 50) # Don't look more than 50 chars
  410. if DEBUG:
  411. print "Checking match for phpdoc calltip"
  412. ch = None
  413. ident_found_pos = None
  414. accessor = buf.accessor
  415. while p >= min_p and \
  416. accessor.style_at_pos(p) in self.comment_styles:
  417. ch = accessor.char_at_pos(p)
  418. p -= 1
  419. if ident_found_pos is None:
  420. #print "phpdoc: Looking for identifier, ch: %r" % (ch)
  421. if ch in " \t":
  422. pass
  423. elif _isident(ch):
  424. ident_found_pos = p+1
  425. else:
  426. if DEBUG:
  427. print "No phpdoc, whitespace not preceeded " \
  428. "by an identifer"
  429. return None
  430. elif ch == "@":
  431. # This is what we've been looking for!
  432. phpdoc_field = accessor.text_range(p+2,
  433. ident_found_pos+1)
  434. if DEBUG:
  435. print "Matched trigger for phpdoc calltip: '%s'" % (
  436. phpdoc_field, )
  437. return Trigger("PHP", TRG_FORM_CALLTIP,
  438. "phpdoc-tags", ident_found_pos, implicit,
  439. phpdoc_field=phpdoc_field)
  440. elif not _isident(ch):
  441. if DEBUG:
  442. print "No phpdoc, identifier not preceeded by '@'"
  443. # Not whitespace, not a valid tag then
  444. return None
  445. # Nothing found in the specified range
  446. if DEBUG:
  447. print "No phpdoc, ran out of characters to look at."
  448. # Array completions
  449. elif last_style == self.string_style and last_char in '\'"':
  450. if prev_char != '[':
  451. if prev_style in self.comment_styles_or_whitespace:
  452. # Look back further.
  453. prev_pos, prev_char, prev_style = ac.getPrevPosCharStyle(ignore_styles=self.comment_styles_or_whitespace)
  454. if prev_char == '[':
  455. # We're good to go.
  456. if DEBUG:
  457. print "Matched trigger for array completions"
  458. return Trigger("PHP", TRG_FORM_CPLN,
  459. "array-members", pos, implicit,
  460. bracket_pos=prev_pos,
  461. trg_char=last_char)
  462. # Variable completions inside of comments
  463. elif prev_char == "$" and last_style in self.comment_styles:
  464. if DEBUG:
  465. print "Comment variable style"
  466. # Completion for variables (builtins and user defined variables),
  467. # must occur after a "$" character.
  468. return Trigger(lang, TRG_FORM_CPLN, "comment-variables",
  469. pos-1, implicit)
  470. elif DEBUG:
  471. print "trg_from_pos: no handle for style: %d" % last_style
  472. except IndexError:
  473. # Not enough chars found, therefore no trigger
  474. pass
  475. return None
  476. #@util.hotshotit
  477. def preceding_trg_from_pos(self, buf, pos, curr_pos,
  478. preceding_trg_terminators=None, DEBUG=False):
  479. #DEBUG = True
  480. # Try the default preceding_trg_from_pos handler
  481. trg = ProgLangTriggerIntelMixin.preceding_trg_from_pos(
  482. self, buf, pos, curr_pos, preceding_trg_terminators,
  483. DEBUG=DEBUG)
  484. if trg is not None:
  485. return trg
  486. # Else, let's try to work out some other options
  487. accessor = buf.accessor
  488. prev_style = accessor.style_at_pos(curr_pos - 1)
  489. if prev_style in (self.identifier_style, self.keyword_style):
  490. # We don't know what to trigger here... could be one of:
  491. # functions:
  492. # apache<$><|>_getenv()...
  493. # if(get_e<$><|>nv()...
  494. # classes:
  495. # new Exce<$><|>ption()...
  496. # extends Exce<$><|>ption()...
  497. # interfaces:
  498. # implements apache<$><|>_getenv()...
  499. ac = AccessorCache(accessor, curr_pos)
  500. pos_before_identifer, ch, prev_style = \
  501. ac.getPrecedingPosCharStyle(prev_style)
  502. if DEBUG:
  503. print "\nphp preceding_trg_from_pos, first chance for identifer style"
  504. print " curr_pos: %d" % (curr_pos)
  505. print " pos_before_identifer: %d" % (pos_before_identifer)
  506. print " ch: %r" % ch
  507. print " prev_style: %d" % prev_style
  508. ac.dump()
  509. if pos_before_identifer < pos:
  510. resetPos = min(pos_before_identifer + 4, accessor.length() - 1)
  511. ac.resetToPosition(resetPos)
  512. if DEBUG:
  513. print "preceding_trg_from_pos:: reset to position: %d, ac now:" % (resetPos)
  514. ac.dump()
  515. # Trigger on the third identifier character
  516. return self.trg_from_pos(buf, resetPos,
  517. implicit=False, DEBUG=DEBUG, ac=ac)
  518. elif DEBUG:
  519. print "Out of scope of the identifier"
  520. elif prev_style in self.comment_styles:
  521. # Check if there is a PHPDoc to provide a calltip for, example:
  522. # /** @param $foo foobar - This is field for <|>
  523. if DEBUG:
  524. print "\nphp preceding_trg_from_pos::phpdoc: check for calltip"
  525. comment = accessor.text_range(max(0, curr_pos-200), curr_pos)
  526. at_idx = comment.rfind("@")
  527. if at_idx >= 0:
  528. if DEBUG:
  529. print "\nphp preceding_trg_from_pos::phpdoc: contains '@'"
  530. space_idx = comment[at_idx:].find(" ")
  531. if space_idx >= 0:
  532. # Trigger after the space character.
  533. trg_pos = (curr_pos - len(comment)) + at_idx + space_idx + 1
  534. if DEBUG:
  535. print "\nphp preceding_trg_from_pos::phpdoc: calltip at %d" % (trg_pos, )
  536. return self.trg_from_pos(buf, trg_pos,
  537. implicit=False, DEBUG=DEBUG)
  538. _phpdoc_cplns = [ ("variable", t) for t in sorted(phpdoc_tags) ]
  539. #@util.hotshotit
  540. def async_eval_at_trg(self, buf, trg, ctlr):
  541. if _xpcom_:
  542. trg = UnwrapObject(trg)
  543. ctlr = UnwrapObject(ctlr)
  544. pos = trg.pos
  545. ctlr.start(buf, trg)
  546. #print "trg.type: %r" % (trg.type)
  547. # PHPDoc completions
  548. if trg.id == ("PHP", TRG_FORM_CPLN, "phpdoc-tags"):
  549. #TODO: Would like a "javadoc tag" completion image name.
  550. ctlr.set_cplns(self._phpdoc_cplns)
  551. ctlr.done("success")
  552. return
  553. # PHPDoc calltip
  554. elif trg.id == ("PHP", TRG_FORM_CALLTIP, "phpdoc-tags"):
  555. phpdoc_field = trg.extra.get("phpdoc_field")
  556. if phpdoc_field:
  557. #print "phpdoc_field: %r" % (phpdoc_field, )
  558. calltip = phpdoc_tags.get(phpdoc_field)
  559. if calltip:
  560. ctlr.set_calltips([calltip])
  561. ctlr.done("success")
  562. return
  563. elif trg.type in ("classes", "interfaces"):
  564. # Triggers from zero characters, thus calling citdl_expr_from_trg
  565. # is no help
  566. line = buf.accessor.line_from_pos(pos)
  567. evalr = PHPTreeEvaluator(ctlr, buf, trg, "", line)
  568. buf.mgr.request_eval(evalr)
  569. else:
  570. try:
  571. citdl_expr = self.citdl_expr_from_trg(buf, trg)
  572. except CodeIntelError, ex:
  573. ctlr.error(str(ex))
  574. ctlr.done("error")
  575. return
  576. line = buf.accessor.line_from_pos(pos)
  577. evalr = PHPTreeEvaluator(ctlr, buf, trg, citdl_expr, line)
  578. buf.mgr.request_eval(evalr)
  579. def _citdl_expr_from_pos(self, trg, buf, pos, implicit=True,
  580. include_forwards=False, DEBUG=False):
  581. #DEBUG = True
  582. #PERF: Would dicts be faster for all of these?
  583. WHITESPACE = tuple(" \t\n\r\v\f")
  584. EOL = tuple("\r\n")
  585. BLOCKCLOSES = tuple(")}]")
  586. STOPOPS = tuple("({[,&$+=^|%/<;:->!.@?")
  587. EXTRA_STOPOPS_PRECEDING_IDENT = BLOCKCLOSES # Might be others.
  588. #TODO: This style picking is a problem for the LangIntel move.
  589. if trg.type == "comment-variables":
  590. # Dev note: skip_styles in the other cases below will be a dict.
  591. skip_styles = set()
  592. elif implicit:
  593. skip_styles = buf.implicit_completion_skip_styles
  594. else:
  595. skip_styles = buf.completion_skip_styles
  596. citdl_expr = []
  597. accessor = buf.accessor
  598. # Use a cache of characters, easy to keep track this way
  599. i = pos
  600. ac = AccessorCache(accessor, i)
  601. if include_forwards:
  602. try:
  603. # Move ahead to include forward chars as well
  604. lastch_was_whitespace = False
  605. while 1:
  606. i, ch, style = ac.getNextPosCharStyle()
  607. if DEBUG:
  608. print "include_forwards:: i now: %d, ch: %r" % (i, ch)
  609. if ch in WHITESPACE:
  610. lastch_was_whitespace = True
  611. continue
  612. lastch_was_whitespace = False
  613. if ch in STOPOPS:
  614. if DEBUG:
  615. print "include_forwards:: ch in STOPOPS, i:%d ch:%r" % (i, ch)
  616. break
  617. elif ch in BLOCKCLOSES:
  618. if DEBUG:
  619. print "include_forwards:: ch in BLOCKCLOSES, i:%d ch:%r" % (i, ch)
  620. break
  621. elif lastch_was_whitespace:
  622. # Two whitespace separated words
  623. if DEBUG:
  624. print "include_forwards:: ch separated by whitespace, i:%d ch:%r" % (i, ch)
  625. break
  626. # Move back to last valid char
  627. i -= 1
  628. if DEBUG:
  629. if i > pos:
  630. print "include_forwards:: Including chars from pos %d up to %d" % (pos, i)
  631. else:
  632. print "include_forwards:: No valid chars forward from pos %d, i now: %d" % (pos, i)
  633. except IndexError:
  634. # Nothing forwards, user what we have then
  635. i = min(i, accessor.length() - 1)
  636. if DEBUG:
  637. print "include_forwards:: No more buffer, i now: %d" % (i)
  638. ac.resetToPosition(i)
  639. ch = None
  640. try:
  641. while i >= 0:
  642. if ch == None and include_forwards:
  643. i, ch, style = ac.getCurrentPosCharStyle()
  644. else:
  645. i, ch, style = ac.getPrevPosCharStyle()
  646. if DEBUG:
  647. print "i now: %d, ch: %r" % (i, ch)
  648. if ch in WHITESPACE:
  649. if trg.type in ("use-namespace", "namespace-members"):
  650. # Namespaces cannot be split over whitespace.
  651. break
  652. while ch in WHITESPACE:
  653. # drop all whitespace
  654. next_char = ch
  655. i, ch, style = ac.getPrevPosCharStyle()
  656. if ch in WHITESPACE \
  657. or (ch == '\\' and next_char in EOL):
  658. if DEBUG:
  659. print "drop whitespace: %r" % ch
  660. # If there are two whitespace-separated words then this is
  661. # (likely or always?) a language keyword or declaration
  662. # construct at which we want to stop. E.g.
  663. # if foo<|> and ...
  664. # def foo<|>(...
  665. # if \foo<|>(... # uses a namespace
  666. if citdl_expr \
  667. and (_isident(citdl_expr[-1]) or citdl_expr[-1] == '\\') \
  668. and (_isident(ch) or _isdigit(ch)):
  669. if DEBUG:
  670. print "stop at (likely?) start of keyword or "\
  671. "declaration: %r" % ch
  672. break
  673. # Not whitespace anymore, move into the main checks below
  674. if DEBUG:
  675. print "Out of whitespace: i now: %d, ch: %s" % (i, ch)
  676. if style in skip_styles: # drop styles to ignore
  677. while i >= 0 and style in skip_styles:
  678. i, ch, style = ac.getPrevPosCharStyle()
  679. if DEBUG:
  680. print "drop char of style to ignore: %r" % ch
  681. elif ch in ":>" and i > 0:
  682. # Next char has to be ":" or "-" respectively
  683. prev_pos, prev_ch, prev_style = ac.getPrevPosCharStyle()
  684. if (ch == ">" and prev_ch == "-") or \
  685. (ch == ":" and prev_ch == ":"):
  686. citdl_expr.append(".")
  687. if DEBUG:
  688. print "Turning member accessor '%s%s' into '.'" % (prev_ch, ch)
  689. i -= 2
  690. else:
  691. if DEBUG:
  692. print "citdl_expr: %r" % (citdl_expr)
  693. print "stop at special stop-operator %d: %r" % (i, ch)
  694. break
  695. elif (ch in STOPOPS or ch in EXTRA_STOPOPS_PRECEDING_IDENT) and \
  696. (ch != ")" or (citdl_expr and citdl_expr[-1] != ".")):
  697. if ch == '$':
  698. # This may not be the end of the road, given static
  699. # variables are accessed through "Class::$static".
  700. prev_pos, prev_ch, prev_style = ac.peekPrevPosCharStyle()
  701. if prev_ch == ":":
  702. # Continue building up the citdl then.
  703. continue
  704. if DEBUG:
  705. print "citdl_expr: %r" % (citdl_expr)
  706. print "stop at stop-operator %d: %r" % (i, ch)
  707. break
  708. elif ch in BLOCKCLOSES:
  709. if DEBUG:
  710. print "found block at %d: %r" % (i, ch)
  711. citdl_expr.append(ch)
  712. BLOCKS = { # map block close char to block open char
  713. ')': '(',
  714. ']': '[',
  715. '}': '{',
  716. }
  717. stack = [] # stack of blocks: (<block close char>, <style>)
  718. stack.append( (ch, style, BLOCKS[ch], i) )
  719. while i >= 0:
  720. i, ch, style = ac.getPrevPosCharStyle()
  721. if DEBUG:
  722. print "finding matching brace: ch %r (%s), stack %r"\
  723. % (ch, ', '.join(buf.style_names_from_style_num(style)), stack)
  724. if ch in BLOCKS and style not in skip_styles:
  725. stack.append( (ch, style, BLOCKS[ch]) )
  726. elif ch == stack[-1][2] and style not in skip_styles:
  727. #XXX Replace the second test with the following
  728. # when LexPython+SilverCity styling bugs are fixed
  729. # (spurious 'stderr' problem):
  730. # and style == stack[-1][1]:
  731. stack.pop()
  732. if not stack:
  733. if DEBUG:
  734. print "jump to matching brace at %d: %r" % (i, ch)
  735. citdl_expr.append(ch)
  736. break
  737. else:
  738. # Didn't find the matching brace.
  739. if DEBUG:
  740. print "couldn't find matching brace"
  741. raise EvalError("could not find matching brace for "
  742. "'%s' at position %d"
  743. % (stack[-1][0], stack[-1][3]))
  744. else:
  745. if DEBUG:
  746. style_names = buf.style_names_from_style_num(style)
  747. print "add char: %r (%s)" % (ch, ', '.join(style_names))
  748. citdl_expr.append(ch)
  749. i -= 1
  750. except IndexError:
  751. # Nothing left to consume, return what we have
  752. pass
  753. # Remove any unecessary starting dots
  754. while citdl_expr and citdl_expr[-1] == ".":
  755. citdl_expr.pop()
  756. citdl_expr.reverse()
  757. citdl_expr = ''.join(citdl_expr)
  758. if DEBUG:
  759. print "return: %r" % citdl_expr
  760. print util.banner("done")
  761. return citdl_expr
  762. def citdl_expr_from_trg(self, buf, trg):
  763. """Return a PHP CITDL expression preceding the given trigger.
  764. The expression drops newlines, whitespace, and function call
  765. arguments -- basically any stuff that is not used by the codeintel
  766. database system for determining the resultant object type of the
  767. expression. For example (in which <|> represents the given position):
  768. GIVEN RETURN
  769. ----- ------
  770. foo-<|>> foo
  771. Foo:<|>: Foo
  772. foo(bar-<|>> bar
  773. foo(bar,blam)-<|>> foo()
  774. foo(bar, foo()
  775. blam)-<|>>
  776. foo(arg1, arg2)->bar-<|>> foo().bar
  777. Foo(arg1, arg2)::bar-<|>> Foo().bar
  778. Foo\bar:<|>: Foo\bar
  779. Foo\bar::bam-<|>> Foo\bar.bam
  780. Foo\bar(arg1, arg2)::bam-<|>> Foo\bar().bam
  781. """
  782. #DEBUG = True
  783. DEBUG = False
  784. if DEBUG:
  785. print util.banner("%s citdl_expr_from_trg @ %r" % (buf.lang, trg))
  786. if trg.form == TRG_FORM_CPLN:
  787. # "->" or "::"
  788. if trg.type == "classes":
  789. i = trg.pos + 1
  790. elif trg.type == "functions":
  791. i = trg.pos + 3 # 3-char trigger, skip over it
  792. elif trg.type in ("variables", "comment-variables"):
  793. i = trg.pos + 1 # triggered on the $, skip over it
  794. elif trg.type == "array-members":
  795. i = trg.extra.get("bracket_pos") # triggered on foo['
  796. elif trg.type == "use":
  797. i = trg.pos + 1
  798. elif trg.type == "namespace-members" or \
  799. trg.type == "use-namespace":
  800. i = trg.pos - 1
  801. else:
  802. i = trg.pos - 2 # skip past the trigger char
  803. return self._citdl_expr_from_pos(trg, buf, i, trg.implicit,
  804. DEBUG=DEBUG)
  805. elif trg.form == TRG_FORM_DEFN:
  806. return self.citdl_expr_under_pos(trg, buf, trg.pos, DEBUG)
  807. else: # trg.form == TRG_FORM_CALLTIP:
  808. # (<|>
  809. return self._citdl_expr_from_pos(trg, buf, trg.pos-1, trg.implicit,
  810. DEBUG=DEBUG)
  811. def citdl_expr_under_pos(self, trg, buf, pos, DEBUG=False):
  812. """Return a PHP CITDL expression around the given pos.
  813. Similar to citdl_expr_from_trg(), but looks forward to grab additional
  814. characters.
  815. GIVEN RETURN
  816. ----- ------
  817. foo-<|>> foo
  818. F<|>oo:: Foo
  819. foo->ba<|>r foo.bar
  820. f<|>oo->bar foo
  821. foo(bar-<|>> bar
  822. foo(bar,blam)-<|>> foo()
  823. foo(bar, foo()
  824. blam)-<|>>
  825. foo(arg1, arg2)->bar-<|>> foo().bar
  826. Foo(arg1, arg2)::ba<|>r-> Foo().bar
  827. Fo<|>o(arg1, arg2)::bar-> Foo
  828. """
  829. #DEBUG = True
  830. expr = self._citdl_expr_from_pos(trg, buf, pos-1, implicit=True,
  831. include_forwards=True, DEBUG=DEBUG)
  832. if expr:
  833. # Chop off any trailing "." characters
  834. return expr.rstrip(".")
  835. return expr
  836. def libs_from_buf(self, buf):
  837. env = buf.env
  838. # A buffer's libs depend on its env and the buf itself so
  839. # we cache it on the env and key off the buffer.
  840. if "php-buf-libs" not in env.cache:
  841. env.cache["php-buf-libs"] = weakref.WeakKeyDictionary()
  842. cache = env.cache["php-buf-libs"] # <buf-weak-ref> -> <libs>
  843. if buf not in cache:
  844. # - curdirlib
  845. # Using the dirname of this buffer isn't always right, but
  846. # hopefully is a good first approximation.
  847. cwd = dirname(buf.path)
  848. if cwd == "<Unsaved>":
  849. libs = []
  850. else:
  851. libs = [ self.mgr.db.get_lang_lib("PHP", "curdirlib", [cwd], "PHP")]
  852. libs += self._buf_indep_libs_from_env(env)
  853. cache[buf] = libs
  854. return cache[buf]
  855. def lpaths_from_blob(self, blob):
  856. """Return <lpaths> for this blob
  857. where,
  858. <lpaths> is a set of externally referencable lookup-paths, e.g.
  859. [("MyOwnClass",), ("MyOwnClass", "function1"), ...]
  860. """
  861. return set(lpath for child in blob
  862. for lpath in _walk_php_symbols(child))
  863. def _php_from_env(self, env):
  864. import which
  865. path = [d.strip()
  866. for d in env.get_envvar("PATH", "").split(os.pathsep)
  867. if d.strip()]
  868. for exe_name in ("php", "php4", "php-cgi", "php-cli"):
  869. try:
  870. return which.which(exe_name, path=path)
  871. except which.WhichError:
  872. pass
  873. return None
  874. def _php_info_from_php(self, php, env):
  875. """Call the given PHP and return:
  876. (<version>, <include_path>)
  877. Returns (None, []) if could not determine.
  878. """
  879. import process
  880. import tempfile
  881. # Use a marker to separate the start of output from possible
  882. # leading lines of PHP loading errors/logging.
  883. marker = "--- Start of Good Stuff ---"
  884. info_cmd = (r'<?php '
  885. + r'echo("%s\n");' % marker
  886. + r'echo(phpversion()."\n");'
  887. + r'echo(ini_get("include_path")."\n");'
  888. + r' ?>')
  889. argv = [php]
  890. envvars = env.get_all_envvars()
  891. php_ini_path = env.get_pref("phpConfigFile")
  892. if php_ini_path:
  893. envvars["PHPRC"] = php_ini_path
  894. fd, filepath = tempfile.mkstemp(suffix=".php")
  895. try:
  896. os.write(fd, info_cmd)
  897. os.close(fd)
  898. argv.append(filepath)
  899. p = process.ProcessOpen(argv, env=env.get_all_envvars())
  900. stdout, stderr = p.communicate()
  901. finally:
  902. os.remove(filepath)
  903. stdout_lines = stdout.splitlines(0)
  904. retval = p.returncode
  905. if retval:
  906. log.warn("failed to determine PHP info:\n"
  907. " path: %s\n"
  908. " retval: %s\n"
  909. " stdout:\n%s\n"
  910. " stderr:\n%s\n",
  911. php, retval, util.indent('\n'.join(stdout_lines)),
  912. util.indent(stderr))
  913. return None, []
  914. stdout_lines = stdout_lines[stdout_lines.index(marker)+1:]
  915. php_ver = stdout_lines[0]
  916. include_path = [p.strip() for p in stdout_lines[1].split(os.pathsep)
  917. if p.strip()]
  918. return php_ver, include_path
  919. def _extra_dirs_from_env(self, env):
  920. extra_dirs = set()
  921. include_project = env.get_pref("codeintel_scan_files_in_project", True)
  922. if include_project:
  923. proj_base_dir = env.get_proj_base_dir()
  924. if proj_base_dir is not None:
  925. extra_dirs.add(proj_base_dir) # Bug 68850.
  926. for pref in env.get_all_prefs("phpExtraPaths"):
  927. if not pref: continue
  928. extra_dirs.update(d.strip() for d in pref.split(os.pathsep)
  929. if exists(d.strip()))
  930. if extra_dirs:
  931. log.debug("PHP extra lib dirs: %r", extra_dirs)
  932. max_depth = env.get_pref("codeintel_max_recursive_dir_depth", 10)
  933. php_assocs = env.assoc_patterns_from_lang("PHP")
  934. extra_dirs = tuple(
  935. util.gen_dirs_under_dirs(extra_dirs,
  936. max_depth=max_depth,
  937. interesting_file_patterns=php_assocs)
  938. )
  939. else:
  940. extra_dirs = () # ensure retval is a tuple
  941. return extra_dirs
  942. def _buf_indep_libs_from_env(self, env):
  943. """Create the buffer-independent list of libs."""
  944. cache_key = "php-libs"
  945. if cache_key not in env.cache:
  946. env.add_pref_observer("php", self._invalidate_cache)
  947. env.add_pref_observer("phpExtraPaths",
  948. self._invalidate_cache_and_rescan_extra_dirs)
  949. env.add_pref_observer("phpConfigFile",
  950. self._invalidate_cache)
  951. env.add_pref_observer("codeintel_selected_catalogs",
  952. self._invalidate_cache)
  953. env.add_pref_observer("codeintel_max_recursive_dir_depth",
  954. self._invalidate_cache)
  955. env.add_pref_observer("codeintel_scan_files_in_project",
  956. self._invalidate_cache)
  957. # (Bug 68850) Both of these 'live_*' prefs on the *project*
  958. # prefset can result in a change of project base dir. It is
  959. # possible that we can false positives here if there is ever
  960. # a global pref of this name.
  961. env.add_pref_observer("import_live",
  962. self._invalidate_cache_and_rescan_extra_dirs)
  963. env.add_pref_observer("import_dirname",
  964. self._invalidate_cache_and_rescan_extra_dirs)
  965. db = self.mgr.db
  966. # Gather information about the current php.
  967. php = None
  968. if env.has_pref("php"):
  969. php = env.get_pref("php").strip() or None
  970. if not php or not exists(php):
  971. php = self._php_from_env(env)
  972. if not php:
  973. log.warn("no PHP was found from which to determine the "
  974. "import path")
  975. php_ver, include_path = None, []
  976. else:
  977. php_ver, include_path \
  978. = self._php_info_from_php(php, env)
  979. libs = []
  980. # - extradirslib

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