Personal emacs config
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

453 lines
17 KiB

  1. ;;; php.el --- PHP support for friends -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 2020 Friends of Emacs-PHP development
  3. ;; Author: USAMI Kenta <tadsan@zonu.me>
  4. ;; Created: 5 Dec 2018
  5. ;; Version: 1.23.0
  6. ;; Keywords: languages, php
  7. ;; Homepage: https://github.com/emacs-php/php-mode
  8. ;; Package-Requires: ((emacs "24.3"))
  9. ;; License: GPL-3.0-or-later
  10. ;; This program is free software; you can redistribute it and/or modify
  11. ;; it under the terms of the GNU General Public License as published by
  12. ;; the Free Software Foundation, either version 3 of the License, or
  13. ;; (at your option) any later version.
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. ;; GNU General Public License for more details.
  18. ;; You should have received a copy of the GNU General Public License
  19. ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. ;;; Commentary:
  21. ;; This file provides common variable and functions for PHP packages.
  22. ;;; Code:
  23. (require 'cl-lib)
  24. (require 'flymake)
  25. (require 'php-project)
  26. (require 'rx)
  27. ;;;###autoload
  28. (defgroup php nil
  29. "Language support for PHP."
  30. :tag "PHP"
  31. :group 'languages
  32. :link '(url-link :tag "Official Site" "https://github.com/emacs-php/php-mode")
  33. :link '(url-link :tag "PHP Mode Wiki" "https://github.com/emacs-php/php-mode/wiki"))
  34. (defcustom php-executable (or (executable-find "php") "/usr/bin/php")
  35. "The location of the PHP executable."
  36. :group 'php
  37. :tag "PHP Executable"
  38. :type 'string)
  39. (defcustom php-site-url "https://php.net/"
  40. "Default PHP.net site URL.
  41. The URL to use open PHP manual and search word."
  42. :group 'php
  43. :tag "PHP Site URL"
  44. :type 'string)
  45. (defcustom php-manual-url 'en
  46. "URL at which to find PHP manual.
  47. You can replace \"en\" with your ISO language code."
  48. :group 'php
  49. :tag "PHP Manual URL"
  50. :type '(choice (const :tag "English" 'en)
  51. (const :tag "Brazilian Portuguese" 'pt_BR)
  52. (const :tag "Chinese (Simplified)" 'zh)
  53. (const :tag "French" 'fr)
  54. (const :tag "German" 'de)
  55. (const :tag "Japanese" 'ja)
  56. (const :tag "Romanian" 'ro)
  57. (const :tag "Russian" 'ru)
  58. (const :tag "Spanish" 'es)
  59. (const :tag "Turkish" 'tr)
  60. (string :tag "PHP manual URL")))
  61. (defcustom php-search-url nil
  62. "URL at which to search for documentation on a word."
  63. :group 'php
  64. :tag "PHP Search URL"
  65. :type '(choice (string :tag "URL to search PHP documentation")
  66. (const :tag "Use `php-site-url' variable" nil)))
  67. (defcustom php-class-suffix-when-insert "::"
  68. "Suffix for inserted class."
  69. :group 'php
  70. :type 'string)
  71. (defcustom php-namespace-suffix-when-insert "\\"
  72. "Suffix for inserted namespace."
  73. :group 'php
  74. :type 'string)
  75. (defcustom php-default-major-mode 'php-mode
  76. "Major mode for editing PHP script."
  77. :group 'php
  78. :tag "PHP Default Major Mode"
  79. :type 'function)
  80. (defcustom php-html-template-major-mode 'web-mode
  81. "Major mode for editing PHP-HTML template."
  82. :group 'php
  83. :tag "PHP-HTML Template Major Mode"
  84. :type 'function)
  85. (defcustom php-blade-template-major-mode 'web-mode
  86. "Major mode for editing Blade template."
  87. :group 'php
  88. :tag "PHP Blade Template Major Mode"
  89. :type 'function)
  90. (defcustom php-template-mode-alist
  91. `(("\\.blade" . ,php-blade-template-major-mode)
  92. ("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode php-default-major-mode))
  93. ("\\.phtml\\'" . ,php-html-template-major-mode))
  94. "Automatically use another MAJOR-MODE when open template file."
  95. :group 'php
  96. :tag "PHP Template Mode Alist"
  97. :type '(alist :key-type regexp :value-type function)
  98. :link '(url-link :tag "web-mode" "http://web-mode.org/")
  99. :link '(url-link :tag "phpt-mode" "https://github.com/emacs-php/phpt-mode"))
  100. (defcustom php-mode-maybe-hook nil
  101. "List of functions to be executed on entry to `php-mode-maybe'."
  102. :group 'php
  103. :tag "PHP Mode Maybe Hook"
  104. :type 'hook)
  105. (defcustom php-default-builtin-web-server-port 3939
  106. "Port number of PHP Built-in HTTP server (php -S)."
  107. :group 'php
  108. :tag "PHP Default Built-in Web Server Port"
  109. :type 'integer
  110. :link '(url-link :tag "Built-in web server"
  111. "https://www.php.net/manual/features.commandline.webserver.php"))
  112. ;;; PHP Keywords
  113. (defconst php-magical-constants
  114. (list "__LINE__" "__FILE__" "__FUNCTION__" "__CLASS__" "__TRAIT__" "__METHOD__" "__NAMESPACE__")
  115. "Magical keyword that is expanded at compile time.
  116. These are different from \"constants\" in strict terms.
  117. see https://www.php.net/manual/language.constants.predefined.php")
  118. ;;; Utillity for locate language construction
  119. (defsubst php-in-string-p ()
  120. "Return non-nil if inside a string.
  121. it is the character that will terminate the string, or t if the string should be terminated by a generic string delimiter."
  122. (nth 3 (syntax-ppss)))
  123. (defsubst php-in-comment-p ()
  124. "Return nil if outside a comment, t if inside a non-nestable comment, else an integer (the current comment nesting)."
  125. (nth 4 (syntax-ppss)))
  126. (defsubst php-in-string-or-comment-p ()
  127. "Return character address of start of comment or string; nil if not in one."
  128. (nth 8 (syntax-ppss)))
  129. (defsubst php-in-poly-php-html-mode ()
  130. "Return T if current buffer is in `poly-html-mode'."
  131. (and (boundp 'poly-php-html-mode)
  132. (symbol-value 'poly-php-html-mode)))
  133. (defconst php-beginning-of-defun-regexp
  134. "^\\s-*\\(?:\\(?:abstract\\|final\\|private\\|protected\\|public\\|static\\)\\s-+\\)*function\\s-+&?\\(\\(\\sw\\|\\s_\\)+\\)\\s-*("
  135. "Regular expression for a PHP function.")
  136. (eval-when-compile
  137. (defun php-create-regexp-for-method (&optional visibility)
  138. "Make a regular expression for methods with the given VISIBILITY.
  139. VISIBILITY must be a string that names the visibility for a PHP
  140. method, e.g. 'public'. The parameter VISIBILITY can itself also
  141. be a regular expression.
  142. The regular expression this function returns will check for other
  143. keywords that can appear in method signatures, e.g. 'final' and
  144. 'static'. The regular expression will have one capture group
  145. which will be the name of the method."
  146. (when (stringp visibility)
  147. (setq visibility (list visibility)))
  148. (rx-to-string `(: line-start
  149. (* (syntax whitespace))
  150. ,@(if visibility
  151. `((* (or "abstract" "final" "static")
  152. (+ (syntax whitespace)))
  153. (or ,@visibility)
  154. (+ (syntax whitespace))
  155. (* (or "abstract" "final" "static")
  156. (+ (syntax whitespace))))
  157. '((* (* (or "abstract" "final" "static"
  158. "private" "protected" "public")
  159. (+ (syntax whitespace))))))
  160. "function"
  161. (+ (syntax whitespace))
  162. (? "&" (* (syntax whitespace)))
  163. (group (+ (or (syntax word) (syntax symbol))))
  164. (* (syntax whitespace))
  165. "(")))
  166. (defun php-create-regexp-for-classlike (type)
  167. "Accepts a `TYPE' of a 'classlike' object as a string, such as
  168. 'class' or 'interface', and returns a regexp as a string which
  169. can be used to match against definitions for that classlike."
  170. (concat
  171. ;; First see if 'abstract' or 'final' appear, although really these
  172. ;; are not valid for all values of `type' that the function
  173. ;; accepts.
  174. "^\\s-*\\(?:\\(?:abstract\\|final\\)\\s-+\\)?"
  175. ;; The classlike type
  176. type
  177. ;; Its name, which is the first captured group in the regexp. We
  178. ;; allow backslashes in the name to handle namespaces, but again
  179. ;; this is not necessarily correct for all values of `type'.
  180. "\\s-+\\(\\(?:\\sw\\|\\\\\\|\\s_\\)+\\)")))
  181. (defconst php-imenu-generic-expression
  182. (eval-when-compile
  183. `(("Namespaces"
  184. ,(php-create-regexp-for-classlike "namespace") 1)
  185. ("Classes"
  186. ,(php-create-regexp-for-classlike "class") 1)
  187. ("Interfaces"
  188. ,(php-create-regexp-for-classlike "interface") 1)
  189. ("Traits"
  190. ,(php-create-regexp-for-classlike "trait") 1)
  191. ("All Methods"
  192. ,(php-create-regexp-for-method) 1)
  193. ("Private Methods"
  194. ,(php-create-regexp-for-method '("private")) 1)
  195. ("Protected Methods"
  196. ,(php-create-regexp-for-method '("protected")) 1)
  197. ("Public Methods"
  198. ,(php-create-regexp-for-method '("public")) 1)
  199. ("Anonymous Functions"
  200. "\\<\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*=\\s-*f\\(unctio\\)?n\\s-*(" 1)
  201. ("Named Functions"
  202. "^\\s-*function\\s-+\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*(" 1)))
  203. "Imenu generic expression for PHP Mode. See `imenu-generic-expression'.")
  204. (defconst php--re-namespace-pattern
  205. (eval-when-compile
  206. (php-create-regexp-for-classlike "namespace")))
  207. (defconst php--re-classlike-pattern
  208. (eval-when-compile
  209. (php-create-regexp-for-classlike (regexp-opt '("class" "interface" "trait")))))
  210. (defun php-get-current-element (re-pattern)
  211. "Return backward matched element by RE-PATTERN."
  212. (save-excursion
  213. (save-match-data
  214. (when (re-search-backward re-pattern nil t)
  215. (match-string-no-properties 1)))))
  216. ;;; Provide support for Flymake so that users can see warnings and
  217. ;;; errors in real-time as they write code.
  218. (defun php-flymake-php-init ()
  219. "PHP specific init-cleanup routines.
  220. This is an alternative function of `flymake-php-init'.
  221. Look at the `php-executable' variable instead of the constant \"php\" command."
  222. (let* ((init (funcall (eval-when-compile
  223. (if (fboundp 'flymake-proc-php-init)
  224. 'flymake-proc-php-init
  225. 'flymake-php-init)))))
  226. (list php-executable (cdr init))))
  227. (defconst php-re-detect-html-tag-aggressive
  228. (eval-when-compile
  229. (rx (or (: string-start (* (in space))
  230. "<!"
  231. (or "DOCTYPE" "doctype")
  232. (+ (in space))
  233. (or "HTML" "html"))
  234. (: (or line-start
  235. (: "<" (? "/")
  236. (* (in space)) (+ (in alpha "-")) (* (in space)) ">"))
  237. (: "<" (* (in space)) (+ (in alpha "-")) (* (in space)) ">"))))))
  238. (defconst php-re-detect-html-tag-default
  239. (eval-when-compile
  240. (rx (or (: string-start (* (in space))
  241. "<!"
  242. (or "DOCTYPE" "doctype")
  243. (+ (in space))
  244. (or "HTML" "html"))
  245. (: line-start
  246. (: "<" (* (in space)) (+ (in alpha "-")) (* (in space)) ">"))))))
  247. (defcustom php-re-detect-html-tag 'php-re-detect-html-tag-default
  248. "Regexp pattern variable-name of HTML detection."
  249. :group 'php
  250. :tag "PHP Re Detect HTML Tag"
  251. :type '(choice (const :tag "Default pattern" 'php-re-detect-html-tag-default)
  252. (const :tag "Aggressive pattern" 'php-re-detect-html-tag-aggressive)
  253. (variable :tag "Variable name of RegExp pattern")))
  254. (defsubst php-re-detect-html-tag ()
  255. "Return RegExp pattern for HTML detection."
  256. (if (symbolp php-re-detect-html-tag)
  257. (symbol-value php-re-detect-html-tag)
  258. php-re-detect-html-tag))
  259. (defun php-buffer-has-html-tag ()
  260. "Return position of HTML tag or NIL in current buffer."
  261. (save-excursion
  262. (save-restriction
  263. (widen)
  264. (goto-char (point-min))
  265. (save-match-data
  266. (re-search-forward (php-re-detect-html-tag) nil t)))))
  267. (defun php-derivation-major-mode ()
  268. "Return major mode for PHP file by file-name and its content."
  269. (let ((mode (assoc-default buffer-file-name
  270. php-template-mode-alist
  271. #'string-match-p))
  272. type)
  273. (when (and (null mode) buffer-file-name
  274. php-project-php-file-as-template)
  275. (setq type (php-project-get-file-html-template-type buffer-file-name))
  276. (cond
  277. ((eq t type) (setq mode php-html-template-major-mode))
  278. ((eq 'auto type)
  279. (when (php-buffer-has-html-tag)
  280. (setq mode php-html-template-major-mode)))))
  281. (when (and mode (not (fboundp mode)))
  282. (if (string-match-p "\\.blade\\." buffer-file-name)
  283. (warn "php-mode is NOT support blade template. %s"
  284. "Please install `web-mode' package")
  285. (setq mode nil)))
  286. (or mode php-default-major-mode)))
  287. ;;;###autoload
  288. (defun php-mode-maybe ()
  289. "Select PHP mode or other major mode."
  290. (interactive)
  291. (run-hooks php-mode-maybe-hook)
  292. (funcall (php-derivation-major-mode)))
  293. ;;;###autoload
  294. (defun php-current-class ()
  295. "Insert current class name if cursor in class context."
  296. (interactive)
  297. (let ((matched (php-get-current-element php--re-classlike-pattern)))
  298. (when matched
  299. (insert (concat matched php-class-suffix-when-insert)))))
  300. ;;;###autoload
  301. (defun php-current-namespace ()
  302. "Insert current namespace if cursor in namespace context."
  303. (interactive)
  304. (let ((matched (php-get-current-element php--re-namespace-pattern)))
  305. (when matched
  306. (insert (concat matched php-namespace-suffix-when-insert)))))
  307. ;;;###autoload
  308. (defun php-copyit-fqsen ()
  309. "Copy/kill class/method FQSEN."
  310. (interactive)
  311. (let ((namespace (or (php-get-current-element php--re-namespace-pattern) ""))
  312. (class (or (php-get-current-element php--re-classlike-pattern) ""))
  313. (namedfunc (php-get-current-element php-beginning-of-defun-regexp)))
  314. (kill-new (concat (if (string= namespace "") "" namespace)
  315. (if (string= class "") "" (concat "\\" class "::"))
  316. (if (string= namedfunc "") "" (concat namedfunc "()"))))))
  317. ;;;###autoload
  318. (defun php-run-builtin-web-server (router-or-dir hostname port &optional document-root)
  319. "Run PHP Built-in web server.
  320. `ROUTER-OR-DIR': Path to router PHP script or Document root.
  321. `HOSTNAME': Hostname or IP address of Built-in web server.
  322. `PORT': Port number of Built-in web server.
  323. `DOCUMENT-ROOT': Path to Document root.
  324. When `DOCUMENT-ROOT' is NIL, the document root is obtained from `ROUTER-OR-DIR'."
  325. (interactive
  326. (let ((insert-default-directory t)
  327. (d-o-r (read-file-name "Document root or Script: " default-directory)))
  328. (list
  329. (expand-file-name d-o-r)
  330. (read-string "Hostname: " "0.0.0.0")
  331. (read-number "Port: " php-default-builtin-web-server-port)
  332. (if (file-directory-p d-o-r)
  333. nil
  334. (let ((root-input (read-file-name "Document root: " (directory-file-name d-o-r))))
  335. (file-name-directory
  336. (if (file-directory-p root-input)
  337. root-input
  338. (directory-file-name root-input))))))))
  339. (let* ((default-directory
  340. (or document-root
  341. (if (file-directory-p router-or-dir)
  342. router-or-dir
  343. (directory-file-name router-or-dir))))
  344. (short-dirname (abbreviate-file-name default-directory))
  345. (short-filename (abbreviate-file-name router-or-dir))
  346. (buf-name (format "php -S %s:%s -t %s %s"
  347. hostname
  348. port
  349. short-dirname
  350. (if document-root short-filename "")))
  351. (args (cl-remove-if
  352. #'null
  353. (list "-S"
  354. (format "%s:%d" hostname port)
  355. "-t"
  356. default-directory
  357. (when document-root router-or-dir)))))
  358. (message "Run PHP built-in server: %s" buf-name)
  359. (apply #'make-comint buf-name php-executable nil args)
  360. (funcall
  361. (if (called-interactively-p 'interactive) #'display-buffer #'get-buffer)
  362. (format "*%s*" buf-name))))
  363. (defun php-ini ()
  364. "Get `php --ini' output buffer."
  365. (interactive)
  366. (let ((buffer (get-buffer-create " *php --ini*")))
  367. (with-current-buffer buffer
  368. (view-mode -1)
  369. (read-only-mode -1)
  370. (erase-buffer)
  371. (shell-command (concat php-executable " --ini") buffer)
  372. (view-mode +1))
  373. (if (called-interactively-p 'interactive)
  374. (pop-to-buffer buffer)
  375. buffer)))
  376. ;;;###autoload
  377. (defun php-find-system-php-ini-file (&optional file)
  378. "Find php.ini FILE by `php --ini'."
  379. (interactive
  380. (list
  381. (let* ((default-directory (expand-file-name "~"))
  382. (buffer (php-ini))
  383. (path (with-current-buffer buffer
  384. (goto-char (point-min))
  385. (save-match-data
  386. (when (re-search-forward ": \\(.+?\\)$" nil nil)
  387. (match-string 1))))))
  388. (when (or (null path) (not (file-directory-p path)))
  389. (when (called-interactively-p 'interactive)
  390. (pop-to-buffer buffer))
  391. (user-error "Failed get path to PHP ini files directory"))
  392. (read-file-name "Find php.ini file: "
  393. (concat (expand-file-name path) "/")
  394. nil nil nil
  395. #'file-exists-p))))
  396. (find-file file))
  397. (provide 'php)
  398. ;;; php.el ends here