1745 lines
67 KiB
EmacsLisp
1745 lines
67 KiB
EmacsLisp
;;; php-mode.el --- Major mode for editing PHP code
|
||
|
||
;; Copyright (C) 2020 Friends of Emacs-PHP development
|
||
;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Turadg Aleahmad
|
||
;; 2008 Aaron S. Hawley
|
||
;; 2011, 2012, 2013, 2014, 2015, 2016, 2017 Eric James Michael Ritz
|
||
|
||
;; Author: Eric James Michael Ritz
|
||
;; Maintainer: USAMI Kenta <tadsan@zonu.me>
|
||
;; URL: https://github.com/emacs-php/php-mode
|
||
;; Keywords: languages php
|
||
;; Version: 1.23.0
|
||
;; Package-Requires: ((emacs "24.3"))
|
||
;; License: GPL-3.0-or-later
|
||
|
||
(defconst php-mode-version-number "1.23.0"
|
||
"PHP Mode version number.")
|
||
|
||
;; This program is free software; you can redistribute it and/or modify
|
||
;; it under the terms of the GNU General Public License as published by
|
||
;; the Free Software Foundation, either version 3 of the License, or
|
||
;; (at your option) any later version.
|
||
|
||
;; This program is distributed in the hope that it will be useful,
|
||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
;; GNU General Public License for more details.
|
||
|
||
;; You should have received a copy of the GNU General Public License
|
||
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||
|
||
;;; Commentary:
|
||
|
||
;; PHP Mode is a major mode for editing PHP script. It's an extension
|
||
;; of CC mode; thus it inherits all C mode's navigation functionality.
|
||
;; But it colors according to the PHP syntax and indents according to the
|
||
;; PSR-2 coding guidelines. It also includes a couple handy IDE-type
|
||
;; features such as documentation search and a source and class browser.
|
||
|
||
;; Please read the manual for setting items compatible with CC Mode.
|
||
;; https://www.gnu.org/software/emacs/manual/html_mono/ccmode.html
|
||
|
||
;; This mode is designed for PHP scripts consisting of a single <?php block.
|
||
;; We recommend the introduction of Web Mode for HTML and Blade templates combined with PHP.
|
||
;; http://web-mode.org/
|
||
|
||
;; Modern PHP Mode can be set on a project basis by .dir-locals.el.
|
||
;; Please read php-project.el for details of directory local variables.
|
||
|
||
;; If you are using a package manager, you do not need (require 'php-mode) in
|
||
;; your ~/.emacs.d/init.el. Read the README for installation instructions.
|
||
;; https://github.com/emacs-php/php-mode
|
||
|
||
;;; Code:
|
||
|
||
(require 'php)
|
||
(require 'php-face)
|
||
(require 'cc-mode)
|
||
(require 'cc-langs)
|
||
|
||
(eval-when-compile
|
||
(require 'cc-fonts))
|
||
|
||
;; Boilerplate from other `cc-mode' derived modes. See
|
||
;; http://cc-mode.sourceforge.net/derived-mode-ex.el for details on how this all
|
||
;; fits together.
|
||
(eval-and-compile
|
||
(c-add-language 'php-mode 'java-mode))
|
||
|
||
(require 'font-lock)
|
||
(require 'custom)
|
||
(require 'etags)
|
||
(require 'speedbar)
|
||
(require 'imenu)
|
||
(require 'nadvice nil t)
|
||
|
||
(require 'cl-lib)
|
||
(require 'mode-local)
|
||
(require 'php-project)
|
||
|
||
(eval-when-compile
|
||
(require 'regexp-opt)
|
||
(defvar add-log-current-defun-header-regexp)
|
||
(defvar add-log-current-defun-function)
|
||
(defvar c-vsemi-status-unknown-p)
|
||
(defvar syntax-propertize-via-font-lock))
|
||
|
||
;; Work around emacs bug#18845, cc-mode expects cl to be loaded
|
||
;; while php-mode only uses cl-lib (without compatibility aliases)
|
||
(eval-and-compile
|
||
(when (and (= emacs-major-version 24) (>= emacs-minor-version 4))
|
||
(require 'cl)))
|
||
|
||
;; Work around https://github.com/emacs-php/php-mode/issues/310.
|
||
;;
|
||
;; In emacs 24.4 and 24.5, lines after functions with a return type
|
||
;; are incorrectly analyzed as member-init-cont.
|
||
;;
|
||
;; Before emacs 24.4, c member initializers are not supported this
|
||
;; way. Starting from emacs 25.1, cc-mode only detects member
|
||
;; initializers when the major mode is c++-mode.
|
||
(eval-and-compile
|
||
(if (and (= emacs-major-version 24) (or (= emacs-minor-version 4)
|
||
(= emacs-minor-version 5)))
|
||
(defun c-back-over-member-initializers ()
|
||
;; Override of cc-engine.el, cc-mode in emacs 24.4 and 24.5 are too
|
||
;; optimistic in recognizing c member initializers. Since we don't
|
||
;; need it in php-mode, just return nil.
|
||
nil)))
|
||
|
||
(autoload 'php-mode-debug "php-mode-debug"
|
||
"Display informations useful for debugging PHP Mode." t)
|
||
|
||
;; Local variables
|
||
|
||
;;;###autoload
|
||
(defgroup php-mode nil
|
||
"Major mode for editing PHP code."
|
||
:tag "PHP Mode"
|
||
:prefix "php-mode-"
|
||
:group 'languages
|
||
:group 'php
|
||
:link '(url-link :tag "Official Site" "https://github.com/emacs-php/php-mode")
|
||
:link '(url-link :tag "PHP Mode Wiki" "https://github.com/emacs-php/php-mode/wiki"))
|
||
|
||
(define-obsolete-variable-alias 'php-default-face 'php-mode-default-face "1.20.0")
|
||
(defcustom php-mode-default-face 'default
|
||
"Default face in `php-mode' buffers."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Default Face"
|
||
:type 'face)
|
||
|
||
(define-obsolete-variable-alias 'php-speedbar-config 'php-mode-speedbar-config "1.20.0")
|
||
(defcustom php-mode-speedbar-config t
|
||
"When set to true automatically configures Speedbar to observe PHP files.
|
||
Ignores php-file patterns option; fixed to expression \"\\.\\(inc\\|php[s345]?\\)\""
|
||
:group 'php-mode
|
||
:tag "PHP Mode Speedbar Config"
|
||
:type 'boolean
|
||
:set (lambda (sym val)
|
||
(set-default sym val)
|
||
(when val
|
||
(speedbar-add-supported-extension
|
||
"\\.\\(inc\\|php[s345]?\\|phtml\\)"))))
|
||
|
||
(defcustom php-mode-speedbar-open nil
|
||
"Normally `php-mode' starts with the speedbar closed.
|
||
Turning this on will open it whenever `php-mode' is loaded."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Speedbar Open"
|
||
:type 'boolean
|
||
:set (lambda (sym val)
|
||
(set-default sym val)
|
||
(when val
|
||
(speedbar 1))))
|
||
|
||
(define-obsolete-variable-alias 'php-template-compatibility 'php-mode-template-compatibility "1.20.0")
|
||
(defcustom php-mode-template-compatibility t
|
||
"Should detect presence of html tags."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Template Compatibility"
|
||
:type 'boolean)
|
||
|
||
(define-obsolete-variable-alias 'php-lineup-cascaded-calls 'php-mode-lineup-cascaded-calls "1.20.0")
|
||
(defcustom php-mode-lineup-cascaded-calls nil
|
||
"Indent chained method calls to the previous line."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Lineup Cascaded Calls"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-mode-page-delimiter
|
||
(eval-when-compile
|
||
(rx symbol-start
|
||
(or "namespace" "function" "class" "trait" "interface")
|
||
symbol-end))
|
||
"Regexp describing line-beginnings that PHP declaration statements."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Page Delimiter"
|
||
:type 'regexp)
|
||
|
||
(define-obsolete-variable-alias 'php-do-not-use-semantic-imenu 'php-mode-do-not-use-semantic-imenu "1.20.0")
|
||
(defcustom php-mode-do-not-use-semantic-imenu t
|
||
"Customize `imenu-create-index-function' for `php-mode'.
|
||
|
||
If using function `semantic-mode' `imenu-create-index-function' will be
|
||
set to `semantic-create-imenu-index' due to `c-mode' being its
|
||
parent. Set this variable to t if you want to use
|
||
`imenu-default-create-index-function' even with `semantic-mode'
|
||
enabled."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Do Not Use Semantic Imenu"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-completion-file ""
|
||
"Path to the file which contains the function names known to PHP."
|
||
:type 'string)
|
||
|
||
(defcustom php-manual-path ""
|
||
"Path to the directory which contains the PHP manual."
|
||
:type 'string)
|
||
|
||
;;;###autoload
|
||
(if (version< emacs-version "24.4")
|
||
(dolist (i '("php" "php5" "php7"))
|
||
(add-to-list 'interpreter-mode-alist (cons i 'php-mode)))
|
||
(add-to-list 'interpreter-mode-alist
|
||
;; Match php, php-3, php5, php7, php5.5, php-7.0.1, etc.
|
||
(cons "php\\(?:-?[3457]\\(?:\\.[0-9]+\\)*\\)?" 'php-mode)))
|
||
|
||
(defcustom php-mode-hook nil
|
||
"List of functions to be executed on entry to `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-pear-hook nil
|
||
"Hook called when a PHP PEAR file is opened with `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Pear Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-drupal-hook nil
|
||
"Hook called when a Drupal file is opened with `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Drupal Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-wordpress-hook nil
|
||
"Hook called when a WordPress file is opened with `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode WordPress Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-symfony2-hook nil
|
||
"Hook called when a Symfony2 file is opened with `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Symfony2 Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-psr2-hook nil
|
||
"Hook called when a PSR-2 file is opened with `php-mode'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode PSR-2 Hook"
|
||
:type 'hook)
|
||
|
||
(defcustom php-mode-force-pear nil
|
||
"Normally PEAR coding rules are enforced only when the filename contains \"PEAR.\"
|
||
Turning this on will force PEAR rules on all PHP files."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Force Pear"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-mode-warn-if-mumamo-off t
|
||
"Warn once per buffer if you try to indent a buffer without
|
||
mumamo-mode turned on. Detects if there are any HTML tags in the
|
||
buffer before warning, but this is is not very smart; e.g. if you
|
||
have any tags inside a PHP string, it will be fooled."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Warn If MuMaMo Off"
|
||
:type '(choice (const :tag "Warn" t) (const "Don't warn" nil)))
|
||
|
||
(defcustom php-mode-coding-style 'pear
|
||
"Select default coding style to use with php-mode.
|
||
This variable can take one of the following symbol values:
|
||
|
||
`Default' - use a reasonable default style for PHP.
|
||
`PSR-2' - use PSR standards (PSR-2, PSR-12).
|
||
`PEAR' - use coding styles preferred for PEAR code and modules.
|
||
`Drupal' - use coding styles preferred for working with Drupal projects.
|
||
`WordPress' - use coding styles preferred for working with WordPress projects.
|
||
`Symfony2' - use coding styles preferred for working with Symfony2 projects."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Coding Style"
|
||
:type '(choice (const :tag "Default" php)
|
||
(const :tag "PEAR" pear)
|
||
(const :tag "Drupal" drupal)
|
||
(const :tag "WordPress" wordpress)
|
||
(const :tag "Symfony2" symfony2)
|
||
(const :tag "PSR-2" psr2))
|
||
:initialize 'custom-initialize-default)
|
||
|
||
;; Since this function has a bad influence on the environment of many users,
|
||
;; temporarily disable it
|
||
(defcustom php-mode-enable-project-coding-style nil
|
||
"When set to true override php-mode-coding-style by php-project-coding-style.
|
||
|
||
If you want to suppress styles from being overwritten by directory / file
|
||
local variables, set NIL."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Enable Project Coding Style"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-mode-enable-backup-style-variables t
|
||
"When set to `T', back up values set by hook and buffer local variables.
|
||
|
||
This function may interfere with other hooks and other behaviors.
|
||
In that case set to `NIL'."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Enable Backup Style Variables"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-mode-disable-c-auto-align-backslashes t
|
||
"When set to non-NIL, override `c-auto-align-backslashes' to NIL."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Disable c-auto-align-backslashes"
|
||
:type 'boolean)
|
||
|
||
(define-obsolete-variable-alias 'php-mode-disable-parent-mode-hooks 'php-mode-disable-c-mode-hook "1.21.0")
|
||
(defcustom php-mode-disable-c-mode-hook t
|
||
"When set to `T', do not run hooks of parent modes (`java-mode', `c-mode')."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Disable C Mode Hook"
|
||
:type 'boolean)
|
||
|
||
(defcustom php-mode-enable-project-local-variable t
|
||
"When set to `T', apply project local variable to buffer local variable."
|
||
:group 'php-mode
|
||
:tag "PHP Mode Enable Project Local Variable"
|
||
:type 'boolean)
|
||
|
||
(defun php-mode-version ()
|
||
"Display string describing the version of PHP Mode."
|
||
(interactive)
|
||
(let ((fmt
|
||
(eval-when-compile
|
||
(let ((id "$Id$"))
|
||
(concat "PHP Mode %s"
|
||
(if (string= id (concat [?$ ?I ?d ?$]))
|
||
""
|
||
(concat " " id)))))))
|
||
(funcall
|
||
(if (called-interactively-p 'interactive) #'message #'format)
|
||
fmt php-mode-version-number)))
|
||
|
||
;;;###autoload
|
||
(define-obsolete-variable-alias 'php-available-project-root-files 'php-project-available-root-files "1.19.0")
|
||
|
||
(defvar php-mode-map
|
||
(let ((map (make-sparse-keymap "PHP Mode")))
|
||
;; Remove menu item for c-mode
|
||
(define-key map [menu-bar C] nil)
|
||
|
||
;; By default PHP Mode binds C-M-h to c-mark-function, which it
|
||
;; inherits from cc-mode. But there are situations where
|
||
;; c-mark-function fails to properly mark a function. For
|
||
;; example, if we use c-mark-function within a method definition
|
||
;; then the region will expand beyond the method and into the
|
||
;; class definition itself.
|
||
;;
|
||
;; Changing the default to mark-defun provides behavior that users
|
||
;; are more likely to expect.
|
||
(define-key map (kbd "C-M-h") 'mark-defun)
|
||
|
||
;; Many packages based on cc-mode provide the 'C-c C-w' binding
|
||
;; to toggle Subword Mode. See the page
|
||
;;
|
||
;; https://www.gnu.org/software/emacs/manual/html_node/ccmode/Subword-Movement.html
|
||
;;
|
||
;; for more information about Subword mode.
|
||
(define-key map (kbd "C-c C-w") 'subword-mode)
|
||
|
||
;; We inherit c-beginning-of-defun and c-end-of-defun from CC Mode
|
||
;; but we have two replacement functions specifically for PHP. We
|
||
;; remap the commands themselves and not their default
|
||
;; key-bindings so that our PHP-specific versions will work even
|
||
;; if the user has reconfigured their keys, e.g. if they rebind
|
||
;; c-end-of-defun to something other than C-M-e.
|
||
(define-key map [remap c-beginning-of-defun] 'php-beginning-of-defun)
|
||
(define-key map [remap c-end-of-defun] 'php-end-of-defun)
|
||
(define-key map [remap c-set-style] 'php-set-style)
|
||
|
||
(define-key map [(control c) (control f)] 'php-search-documentation)
|
||
(define-key map [(meta tab)] 'php-complete-function)
|
||
(define-key map [(control c) (control m)] 'php-browse-manual)
|
||
(define-key map [(control .)] 'php-show-arglist)
|
||
(define-key map [(control c) (control r)] 'php-send-region)
|
||
;; Use the Emacs standard indentation binding. This may upset c-mode
|
||
;; which does not follow this at the moment, but I see no better
|
||
;; choice.
|
||
(define-key map [tab] 'indent-for-tab-command)
|
||
map)
|
||
"Keymap for `php-mode'.")
|
||
|
||
(c-lang-defconst c-mode-menu
|
||
php (append '(["Complete function name" php-complete-function t]
|
||
["Browse manual" php-browse-manual t]
|
||
["Search documentation" php-search-documentation t]
|
||
["----" t])
|
||
(c-lang-const c-mode-menu)))
|
||
|
||
(c-lang-defconst c-at-vsemi-p-fn
|
||
php 'php-c-at-vsemi-p)
|
||
|
||
(c-lang-defconst c-vsemi-status-unknown-p-fn
|
||
php 'php-c-vsemi-status-unknown-p)
|
||
|
||
(c-lang-defconst c-get-state-before-change-functions
|
||
php nil)
|
||
|
||
(c-lang-defconst c-before-font-lock-functions
|
||
php (c-get-lang-constant 'c-before-font-lock-functions nil t))
|
||
|
||
;; Make php-mode recognize opening tags as preprocessor macro's.
|
||
;;
|
||
;; This is a workaround, the tags must be recognized as something
|
||
;; in order for the syntactic guesses of code below the tag
|
||
;; to be correct and as a result not break indentation.
|
||
;;
|
||
;; Note that submatches or \\| here are not expected by cc-mode.
|
||
(c-lang-defconst c-opt-cpp-prefix
|
||
php "\\s-*<\\?")
|
||
|
||
(c-lang-defconst c-anchored-cpp-prefix
|
||
php "\\s-*\\(<\\?(=\\|\\sw+)\\)")
|
||
|
||
(c-lang-defconst c-identifier-ops
|
||
php '(
|
||
(left-assoc "\\" "::" "->")
|
||
(prefix "\\" "::")))
|
||
|
||
(c-lang-defconst c-operators
|
||
php `((prefix "new" "clone")
|
||
,@(c-lang-const c-identifier-ops)
|
||
(postfix "->")
|
||
(postfix "++" "--" "[" "]" "(" ")")
|
||
(right-assoc "**")
|
||
(prefix "++" "--" "+" "-" "~" "(" ")" "@")
|
||
(prefix "instanceof")
|
||
(prefix "!")
|
||
(left-assoc "*" "/" "%")
|
||
(left-assoc "+" "-" ".")
|
||
(left-assoc "<<" ">>")
|
||
(left-assoc "<" ">" "<=" ">=")
|
||
(left-assoc "==" "!=" "===" "!==" "<>" "<=>")
|
||
(left-assoc "&")
|
||
(left-assoc "^")
|
||
(left-assoc "|")
|
||
(left-assoc "&&")
|
||
(left-assoc "||")
|
||
(right-assoc "??")
|
||
(left-assoc "?:")
|
||
(right-assoc-sequence "?" ":")
|
||
(right-assoc ,@(c-lang-const c-assignment-operators))
|
||
(left-assoc "and")
|
||
(left-assoc "xor")
|
||
(left-assoc "or")
|
||
(left-assoc ",")))
|
||
|
||
;; Allow '\' when scanning from open brace back to defining
|
||
;; construct like class
|
||
(c-lang-defconst c-block-prefix-disallowed-chars
|
||
php (cl-set-difference (c-lang-const c-block-prefix-disallowed-chars)
|
||
'(?\\)))
|
||
|
||
;; Allow $ so variables are recognized in cc-mode and remove @. This
|
||
;; makes cc-mode highlight variables and their type hints in arglists.
|
||
(c-lang-defconst c-symbol-start
|
||
php (concat "[" c-alpha "_$]"))
|
||
|
||
;; All string literals can possibly span multiple lines
|
||
(c-lang-defconst c-multiline-string-start-char
|
||
php t)
|
||
|
||
(c-lang-defconst c-assignment-operators
|
||
php '("=" "*=" "/=" "%=" "+=" "-=" ">>=" "<<=" "&=" "^=" "|=" ".=" "??="))
|
||
|
||
(c-lang-defconst beginning-of-defun-function
|
||
php 'php-beginning-of-defun)
|
||
|
||
(c-lang-defconst end-of-defun-function
|
||
php 'php-end-of-defun)
|
||
|
||
(c-lang-defconst c-primitive-type-kwds
|
||
php '("int" "integer" "bool" "boolean" "float" "double" "real"
|
||
"string" "object" "void" "mixed"))
|
||
|
||
(c-lang-defconst c-class-decl-kwds
|
||
"Keywords introducing declarations where the following block (if any)
|
||
contains another declaration level that should be considered a class."
|
||
php '("class" "trait" "interface"))
|
||
|
||
(c-lang-defconst c-brace-list-decl-kwds
|
||
"Keywords introducing declarations where the following block (if
|
||
any) is a brace list.
|
||
|
||
PHP does not have an \"enum\"-like keyword."
|
||
php nil)
|
||
|
||
(c-lang-defconst c-typeless-decl-kwds
|
||
php (append (c-lang-const c-class-decl-kwds) '("function")))
|
||
|
||
(c-lang-defconst c-modifier-kwds
|
||
php '("abstract" "const" "final" "static"))
|
||
|
||
(c-lang-defconst c-protection-kwds
|
||
"Access protection label keywords in classes."
|
||
php '("private" "protected" "public"))
|
||
|
||
(c-lang-defconst c-postfix-decl-spec-kwds
|
||
php '("implements" "extends"))
|
||
|
||
(c-lang-defconst c-type-list-kwds
|
||
php '("@new" ;; @new is *NOT* language construct, it's workaround for coloring.
|
||
"new" "use" "implements" "extends" "namespace" "instanceof" "insteadof"))
|
||
|
||
(c-lang-defconst c-ref-list-kwds
|
||
php nil)
|
||
|
||
(c-lang-defconst c-block-stmt-2-kwds
|
||
php '("catch" "declare" "elseif" "for" "foreach" "if" "switch" "while"))
|
||
|
||
(c-lang-defconst c-simple-stmt-kwds
|
||
php '("break" "continue" "die" "echo" "exit" "goto" "return" "throw"
|
||
"include" "include_once" "print" "require" "require_once"))
|
||
|
||
(c-lang-defconst c-constant-kwds
|
||
php '("true" "false" "null"))
|
||
|
||
(c-lang-defconst c-lambda-kwds
|
||
php '("function" "use"))
|
||
|
||
(c-lang-defconst c-inexpr-block-kwds
|
||
php '("match"))
|
||
|
||
(c-lang-defconst c-other-block-decl-kwds
|
||
php '("namespace"))
|
||
|
||
(c-lang-defconst c-other-kwds
|
||
"Keywords not accounted for by any other `*-kwds' language constant."
|
||
php
|
||
'(
|
||
"__halt_compiler"
|
||
"and"
|
||
"array"
|
||
"as"
|
||
"break"
|
||
"catch"
|
||
"clone"
|
||
"default"
|
||
"empty"
|
||
"enddeclare"
|
||
"endfor"
|
||
"endforeach"
|
||
"endif"
|
||
"endswitch"
|
||
"endwhile"
|
||
"eval"
|
||
"fn" ;; NOT c-lambda-kwds
|
||
"global"
|
||
"isset"
|
||
"list"
|
||
"or"
|
||
"parent"
|
||
"static"
|
||
"unset"
|
||
"var"
|
||
"xor"
|
||
"yield"
|
||
"yield from"
|
||
|
||
;; Below keywords are technically not reserved keywords, but
|
||
;; threated no differently by php-mode from actual reserved
|
||
;; keywords
|
||
;;
|
||
;;; declare directives:
|
||
"encoding"
|
||
"ticks"
|
||
"strict_types"
|
||
|
||
;;; self for static references:
|
||
"self"))
|
||
|
||
;; PHP does not have <> templates/generics
|
||
(c-lang-defconst c-recognize-<>-arglists
|
||
php nil)
|
||
|
||
(c-lang-defconst c-<>-type-kwds
|
||
php nil)
|
||
|
||
(c-lang-defconst c-inside-<>-type-kwds
|
||
php nil)
|
||
|
||
(c-lang-defconst c-enums-contain-decls
|
||
php nil)
|
||
|
||
(c-lang-defconst c-nonlabel-token-key
|
||
"Regexp matching things that can't occur in generic colon labels.
|
||
|
||
This overrides cc-mode `c-nonlabel-token-key' to support switching on
|
||
double quoted strings and true/false/null.
|
||
|
||
Note: this regexp is also applied to goto-labels, a future improvement
|
||
might be to handle switch and goto labels differently."
|
||
php (concat
|
||
;; All keywords except `c-label-kwds' and `c-constant-kwds'.
|
||
(c-make-keywords-re t
|
||
(cl-set-difference (c-lang-const c-keywords)
|
||
(append (c-lang-const c-label-kwds)
|
||
(c-lang-const c-constant-kwds))
|
||
:test 'string-equal))))
|
||
|
||
(c-lang-defconst c-basic-matchers-before
|
||
php (cl-remove-if (lambda (elm) (and (listp elm) (equal (car elm) "\\s|")))
|
||
(c-lang-const c-basic-matchers-before php)))
|
||
|
||
(c-lang-defconst c-basic-matchers-after
|
||
php (cl-remove-if (lambda (elm) (and (listp elm) (memq 'c-annotation-face elm)))
|
||
(c-lang-const c-basic-matchers-after php)))
|
||
|
||
(c-lang-defconst c-opt-<>-sexp-key
|
||
php nil)
|
||
|
||
(defconst php-mode--re-return-typed-closure
|
||
(eval-when-compile
|
||
(rx symbol-start "function" symbol-end
|
||
(* (syntax whitespace))
|
||
"(" (* (not (any "("))) ")"
|
||
(* (syntax whitespace))
|
||
(? symbol-start "use" symbol-end
|
||
(* (syntax whitespace))
|
||
"(" (* (not (any "("))) ")"
|
||
(* (syntax whitespace)))
|
||
":" (+ (not (any "{}")))
|
||
(group "{"))))
|
||
|
||
(defun php-c-lineup-arglist (langelem)
|
||
"Line up the current argument line under the first argument using `c-lineup-arglist' LANGELEM."
|
||
(let (in-return-typed-closure)
|
||
(when (and (consp langelem)
|
||
(eq 'arglist-cont-nonempty (car langelem)))
|
||
(save-excursion
|
||
(save-match-data
|
||
(when (re-search-backward php-mode--re-return-typed-closure (cdr langelem) t)
|
||
(goto-char (match-beginning 1))
|
||
(when (not (php-in-string-or-comment-p))
|
||
(setq in-return-typed-closure t))))))
|
||
(unless in-return-typed-closure
|
||
(c-lineup-arglist langelem))))
|
||
|
||
(defun php-lineup-cascaded-calls (langelem)
|
||
"Line up chained methods using `c-lineup-cascaded-calls',
|
||
but only if the setting is enabled"
|
||
(if php-mode-lineup-cascaded-calls
|
||
(c-lineup-cascaded-calls langelem)
|
||
(save-excursion
|
||
(beginning-of-line)
|
||
(if (looking-at-p "\\s-*->") '+ nil))))
|
||
|
||
(defun php-c-looking-at-or-maybe-in-bracelist (&optional containing-sexp lim)
|
||
"Replace `c-looking-at-or-maybe-in-bracelist'.
|
||
|
||
CONTAINING-SEXP is the position of the brace/paren/bracket enclosing
|
||
POINT, or nil if there is no such position, or we do not know it. LIM is
|
||
a backward search limit."
|
||
(cond
|
||
((looking-at-p "{")
|
||
(save-excursion
|
||
(c-backward-token-2 2 t lim)
|
||
;; PHP 8.0 match expression
|
||
;; echo match ($var) |{
|
||
;; ↑ matches ↑ initial position
|
||
(when (looking-at-p (eval-when-compile (rx symbol-start "match" symbol-end)))
|
||
(cons (point) t))))
|
||
(t nil)))
|
||
|
||
(c-add-style
|
||
"php"
|
||
`((c-basic-offset . 4)
|
||
(c-offsets-alist . ((arglist-close . php-lineup-arglist-close)
|
||
(arglist-cont . (first php-lineup-cascaded-calls 0))
|
||
(arglist-cont-nonempty . (first php-lineup-cascaded-calls php-c-lineup-arglist))
|
||
(arglist-intro . php-lineup-arglist-intro)
|
||
(case-label . +)
|
||
(class-open . 0)
|
||
(comment-intro . 0)
|
||
(inexpr-class . 0)
|
||
(inlambda . 0)
|
||
(inline-open . 0)
|
||
(namespace-open . 0)
|
||
(lambda-intro-cont . +)
|
||
(label . +)
|
||
(statement-cont . (first php-lineup-cascaded-calls php-lineup-string-cont +))
|
||
(substatement-open . 0)
|
||
(topmost-intro-cont . (first php-lineup-cascaded-calls +))))
|
||
(indent-tabs-mode . nil)
|
||
(tab-width . ,(default-value 'tab-width))
|
||
(fill-column . ,(default-value 'fill-column))
|
||
(show-trailing-whitespace . ,(default-value 'show-trailing-whitespace))
|
||
(php-mode-lineup-cascaded-calls . t)
|
||
(php-style-delete-trailing-whitespace . nil)))
|
||
|
||
(defun php-enable-default-coding-style ()
|
||
"Set PHP Mode to use reasonable default formatting."
|
||
(interactive)
|
||
(php-set-style "php"))
|
||
|
||
(c-add-style
|
||
"pear"
|
||
'("php"
|
||
(c-basic-offset . 4)
|
||
(c-offsets-alist . ((case-label . 0)))
|
||
(tab-width . 4)))
|
||
|
||
(defun php-enable-pear-coding-style ()
|
||
"Set up php-mode to use the coding styles preferred for PEAR code and modules."
|
||
(interactive)
|
||
(php-set-style "pear"))
|
||
|
||
(c-add-style
|
||
"drupal"
|
||
'("php"
|
||
(c-basic-offset . 2)
|
||
(tab-width . 2)
|
||
(fill-column . 78)
|
||
(show-trailing-whitespace . t)
|
||
(php-mode-lineup-cascaded-calls . nil)
|
||
(php-style-delete-trailing-whitespace . t)))
|
||
|
||
(defun php-enable-drupal-coding-style ()
|
||
"Make php-mode use coding styles that are preferable for working with Drupal."
|
||
(interactive)
|
||
(php-set-style "drupal"))
|
||
|
||
(c-add-style
|
||
"wordpress"
|
||
'("php"
|
||
(c-basic-offset . 4)
|
||
(c-indent-comments-syntactically-p t)
|
||
(indent-tabs-mode . t)
|
||
(tab-width . 4)
|
||
(fill-column . 78)))
|
||
|
||
(defun php-enable-wordpress-coding-style ()
|
||
"Make php-mode use coding styles that are preferable for working with Wordpress."
|
||
(interactive)
|
||
(php-set-style "wordpress"))
|
||
|
||
(c-add-style
|
||
"symfony2"
|
||
'("php"
|
||
(c-offsets-alist . ((statement-cont . php-lineup-hanging-semicolon)))
|
||
(c-indent-comments-syntactically-p . t)
|
||
(php-mode-lineup-cascaded-calls . nil)
|
||
(fill-column . 78)))
|
||
|
||
(defun php-enable-symfony2-coding-style ()
|
||
"Make php-mode use coding styles that are preferable for working with Symfony2."
|
||
(interactive)
|
||
(php-set-style "symfony2"))
|
||
|
||
(c-add-style
|
||
"psr2" ; PSR-2 / PSR-12
|
||
'("php"
|
||
(c-offsets-alist . ((statement-cont . +)))
|
||
(c-indent-comments-syntactically-p . t)
|
||
(fill-column . 78)
|
||
(show-trailing-whitespace . t)
|
||
(php-mode-lineup-cascaded-calls . nil)
|
||
(php-style-delete-trailing-whitespace . t)))
|
||
|
||
(defun php-enable-psr2-coding-style ()
|
||
"Make php-mode comply to the PSR-2 coding style."
|
||
(interactive)
|
||
(php-set-style "psr2"))
|
||
|
||
(defun php-beginning-of-defun (&optional arg)
|
||
"Move to the beginning of the ARGth PHP function from point.
|
||
Implements PHP version of `beginning-of-defun-function'."
|
||
(interactive "p")
|
||
(let (found-p (arg (or arg 1)))
|
||
(while (> arg 0)
|
||
(setq found-p (re-search-backward php-beginning-of-defun-regexp
|
||
nil 'noerror))
|
||
(setq arg (1- arg)))
|
||
(while (< arg 0)
|
||
(end-of-line 1)
|
||
(let ((opoint (point)))
|
||
(beginning-of-defun 1)
|
||
(forward-list 2)
|
||
(forward-line 1)
|
||
(if (eq opoint (point))
|
||
(setq found-p (re-search-forward php-beginning-of-defun-regexp
|
||
nil 'noerror)))
|
||
(setq arg (1+ arg))))
|
||
(not (null found-p))))
|
||
|
||
(defun php-end-of-defun (&optional arg)
|
||
"Move the end of the ARGth PHP function from point.
|
||
Implements PHP version of `end-of-defun-function'
|
||
|
||
See `php-beginning-of-defun'."
|
||
(interactive "p")
|
||
(php-beginning-of-defun (- (or arg 1))))
|
||
|
||
|
||
(defvar php-warned-bad-indent nil)
|
||
|
||
;; Do it but tell it is not good if html tags in buffer.
|
||
(defun php-check-html-for-indentation ()
|
||
(let ((html-tag-re "^\\s-*</?\\sw+.*?>")
|
||
(here (point)))
|
||
(goto-char (line-beginning-position))
|
||
(if (or (when (boundp 'mumamo-multi-major-mode) mumamo-multi-major-mode)
|
||
;; Fix-me: no idea how to check for mmm or multi-mode
|
||
(save-match-data
|
||
(not (or (re-search-forward html-tag-re (line-end-position) t)
|
||
(re-search-backward html-tag-re (line-beginning-position) t)))))
|
||
(progn
|
||
(goto-char here)
|
||
t)
|
||
(goto-char here)
|
||
(setq php-warned-bad-indent t)
|
||
(let* ((known-multi-libs '(("mumamo" mumamo (lambda () (nxhtml-mumamo)))
|
||
("mmm-mode" mmm-mode (lambda () (mmm-mode 1)))
|
||
("multi-mode" multi-mode (lambda () (multi-mode 1)))
|
||
("web-mode" web-mode (lambda () (web-mode)))))
|
||
(known-names (mapcar (lambda (lib) (car lib)) known-multi-libs))
|
||
(available-multi-libs (delq nil
|
||
(mapcar
|
||
(lambda (lib)
|
||
(when (locate-library (car lib)) lib))
|
||
known-multi-libs)))
|
||
(available-names (mapcar (lambda (lib) (car lib)) available-multi-libs))
|
||
(base-msg
|
||
(concat
|
||
"Indentation fails badly with mixed HTML/PHP in the HTML part in
|
||
plain `php-mode'. To get indentation to work you must use an
|
||
Emacs library that supports 'multiple major modes' in a buffer.
|
||
Parts of the buffer will then be in `php-mode' and parts in for
|
||
example `html-mode'. Known such libraries are:\n\t"
|
||
(mapconcat 'identity known-names ", ")
|
||
"\n"
|
||
(if available-multi-libs
|
||
(concat
|
||
"You have these available in your `load-path':\n\t"
|
||
(mapconcat 'identity available-names ", ")
|
||
"\n\n"
|
||
"Do you want to turn any of those on? ")
|
||
"You do not have any of those in your `load-path'.")))
|
||
(is-using-multi
|
||
(catch 'is-using
|
||
(dolist (lib available-multi-libs)
|
||
(when (and (boundp (cadr lib))
|
||
(symbol-value (cadr lib)))
|
||
(throw 'is-using t))))))
|
||
(unless is-using-multi
|
||
(if available-multi-libs
|
||
(if (not (y-or-n-p base-msg))
|
||
(message "Did not do indentation, but you can try again now if you want")
|
||
(let* ((name
|
||
(if (= 1 (length available-multi-libs))
|
||
(car available-names)
|
||
;; Minibuffer window is more than one line, fix that first:
|
||
(message "")
|
||
(completing-read "Choose multiple major mode support library: "
|
||
available-names nil t
|
||
(car available-names)
|
||
'(available-names . 1)
|
||
)))
|
||
(mode (when name
|
||
(cl-caddr (assoc name available-multi-libs)))))
|
||
(when mode
|
||
;; Minibuffer window is more than one line, fix that first:
|
||
(message "")
|
||
(load name)
|
||
(funcall mode))))
|
||
(lwarn 'php-indent :warning base-msg)))
|
||
nil))))
|
||
|
||
(defun php-cautious-indent-region (start end &optional quiet)
|
||
"Carefully indent region `START' `END' in contexts other than HTML templates.
|
||
|
||
If the optional argument `QUIET' is non-nil then no syntactic errors are
|
||
reported, even if `c-report-syntactic-errors' is non-nil."
|
||
(if (or (not php-mode-warn-if-mumamo-off)
|
||
(not (php-in-poly-php-html-mode))
|
||
php-warned-bad-indent
|
||
(php-check-html-for-indentation))
|
||
(funcall 'c-indent-region start end quiet)))
|
||
|
||
(defun php-cautious-indent-line ()
|
||
"Carefully indent lines in contexts other than HTML templates."
|
||
(if (or (not php-mode-warn-if-mumamo-off)
|
||
(not (php-in-poly-php-html-mode))
|
||
php-warned-bad-indent
|
||
(php-check-html-for-indentation))
|
||
(let ((here (point))
|
||
(c-auto-align-backslashes
|
||
(unless php-mode-disable-c-auto-align-backslashes
|
||
c-auto-align-backslashes))
|
||
doit)
|
||
(move-beginning-of-line nil)
|
||
;; Don't indent heredoc end mark
|
||
(save-match-data
|
||
(unless (and (looking-at "[a-zA-Z0-9_]+;\n")
|
||
(php-in-string-p))
|
||
(setq doit t)))
|
||
(goto-char here)
|
||
(when doit
|
||
(funcall 'c-indent-line)))))
|
||
|
||
(defun php-c-at-vsemi-p (&optional pos)
|
||
"Return T on HTML lines (including php tag) or PHP8 Attribute, otherwise NIL.
|
||
POS is a position on the line in question.
|
||
|
||
This is was done due to the problem reported here:
|
||
|
||
URL `https://answers.launchpad.net/nxhtml/+question/43320'"
|
||
;; If this function could call c-beginning-of-statement-1, change php-c-vsemi-status-unknown-p.
|
||
(save-excursion
|
||
(if pos
|
||
(goto-char pos)
|
||
(setq pos (point)))
|
||
(unless (php-in-string-or-comment-p)
|
||
(or
|
||
;; Detect PHP8 attribute: <<Attribute()>>
|
||
(when (and (< 2 pos) (< 2 (- pos (c-point 'bol))))
|
||
(backward-char 2)
|
||
(looking-at-p ">>\\s-*\\(?:<<\\|$\\)"))
|
||
;; Detect HTML/XML tag and PHP tag (<?php, <?=, ?>)
|
||
(when php-mode-template-compatibility
|
||
(beginning-of-line)
|
||
(looking-at-p
|
||
(eval-when-compile
|
||
(rx (or (: bol (0+ space) "<" (in "a-z\\?"))
|
||
(: (0+ not-newline) (in "a-z\\?") ">" (0+ space) eol))))))))))
|
||
|
||
(defun php-c-vsemi-status-unknown-p ()
|
||
"Always return NIL. See `c-vsemi-status-unknown-p'."
|
||
;; Current implementation of php-c-at-vsemi-p never calls c-beginning-of-statement-1
|
||
nil)
|
||
|
||
(defun php-lineup-string-cont (langelem)
|
||
"Line up string toward equal sign or dot.
|
||
e.g.
|
||
$str = 'some'
|
||
. 'string';
|
||
this ^ lineup"
|
||
(save-excursion
|
||
(goto-char (cdr langelem))
|
||
(let (ret finish)
|
||
(while (and (not finish) (re-search-forward "[=.]" (line-end-position) t))
|
||
(unless (php-in-string-or-comment-p)
|
||
(setq finish t
|
||
ret (vector (1- (current-column))))))
|
||
ret)))
|
||
|
||
(defun php-lineup-arglist-intro (langelem)
|
||
(save-excursion
|
||
(goto-char (cdr langelem))
|
||
(vector (+ (current-column) c-basic-offset))))
|
||
|
||
(defun php-lineup-arglist-close (langelem)
|
||
(save-excursion
|
||
(goto-char (cdr langelem))
|
||
(vector (current-column))))
|
||
|
||
(defun php-lineup-arglist (_langelem)
|
||
(save-excursion
|
||
(beginning-of-line)
|
||
(if (looking-at-p "\\s-*->") '+ 0)))
|
||
|
||
(defun php-lineup-hanging-semicolon (_langelem)
|
||
(save-excursion
|
||
(beginning-of-line)
|
||
(if (looking-at-p "\\s-*;\\s-*$") 0 '+)))
|
||
|
||
(eval-and-compile
|
||
(defconst php-heredoc-start-re
|
||
"<<<\\(?:\\_<.+?\\_>\\|'\\_<.+?\\_>'\\|\"\\_<.+?\\_>\"\\)$"
|
||
"Regular expression for the start of a PHP heredoc."))
|
||
|
||
(defun php-heredoc-end-re (heredoc-start)
|
||
"Build a regular expression for the end of a heredoc started by the string HEREDOC-START."
|
||
;; Extract just the identifier without <<< and quotes.
|
||
(string-match "\\_<.+?\\_>" heredoc-start)
|
||
(concat "^\\s-*\\(" (match-string 0 heredoc-start) "\\)\\W"))
|
||
|
||
(defun php-syntax-propertize-function (start end)
|
||
"Apply propertize rules from START to END."
|
||
(goto-char start)
|
||
(while (and (< (point) end)
|
||
(re-search-forward php-heredoc-start-re end t))
|
||
(php-heredoc-syntax))
|
||
(goto-char start)
|
||
(while (re-search-forward "['\"]" end t)
|
||
(when (php-in-comment-p)
|
||
(c-put-char-property (match-beginning 0)
|
||
'syntax-table (string-to-syntax "_")))))
|
||
|
||
(defun php-heredoc-syntax ()
|
||
"Mark the boundaries of searched heredoc."
|
||
(goto-char (match-beginning 0))
|
||
(c-put-char-property (point) 'syntax-table (string-to-syntax "|"))
|
||
(if (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
|
||
(goto-char (match-end 1))
|
||
;; Did not find the delimiter so go to the end of the buffer.
|
||
(goto-char (point-max)))
|
||
(c-put-char-property (1- (point)) 'syntax-table (string-to-syntax "|")))
|
||
|
||
(defvar-local php-mode--propertize-extend-region-current nil
|
||
"Prevent undesirable recursion in PHP-SYNTAX-PROPERTIZE-EXTEND-REGION")
|
||
|
||
(defun php-syntax-propertize-extend-region (start end)
|
||
"Extend the propertize region if START or END falls inside a PHP heredoc."
|
||
(let ((pair (cons start end)))
|
||
(when (not (member pair php-mode--propertize-extend-region-current))
|
||
;; re-search functions may trigger
|
||
;; syntax-propertize-extend-region-functions to be called again, which in
|
||
;; turn call this to be called again.
|
||
(push pair php-mode--propertize-extend-region-current)
|
||
(unwind-protect
|
||
(let ((new-start)
|
||
(new-end))
|
||
(goto-char start)
|
||
(when (re-search-backward php-heredoc-start-re nil t)
|
||
(let ((maybe (point)))
|
||
(when (and (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
|
||
(> (point) start))
|
||
(setq new-start maybe))))
|
||
(goto-char end)
|
||
(when (re-search-backward php-heredoc-start-re nil t)
|
||
(if (re-search-forward (php-heredoc-end-re (match-string 0)) nil t)
|
||
(when (> (point) end)
|
||
(setq new-end (point)))
|
||
(setq new-end (point-max))))
|
||
(when (or new-start new-end)
|
||
(cons (or new-start start) (or new-end end))))
|
||
;; Cleanup
|
||
(setq php-mode--propertize-extend-region-current
|
||
(delete pair php-mode--propertize-extend-region-current))))))
|
||
|
||
(easy-menu-define php-mode-menu php-mode-map "PHP Mode Commands"
|
||
(cons "PHP" (c-lang-const c-mode-menu php)))
|
||
|
||
(defun php-mode-get-style-alist ()
|
||
"Return an alist consisting of `php' style and styles that inherit it."
|
||
(cl-loop for l in c-style-alist
|
||
if (or (string= (car l) "php")
|
||
(equal (cadr l) "php"))
|
||
collect l))
|
||
|
||
(defvar php-mode-set-style-history nil)
|
||
(defvar-local php-mode--delayed-set-style nil)
|
||
(defvar-local php-style-delete-trailing-whitespace nil)
|
||
|
||
(defun php-set-style (stylename &optional dont-override)
|
||
"Set the current `php-mode' buffer to use the style STYLENAME.
|
||
STYLENAME is one of the names selectable in `php-mode-coding-style'.
|
||
|
||
Borrow the `interactive-form' from `c-set-style' and use the original
|
||
`c-set-style' function to set all declared stylevars.
|
||
For compatibility with `c-set-style' pass DONT-OVERRIDE to it.
|
||
|
||
After setting the stylevars run hooks according to STYLENAME
|
||
|
||
\"pear\" `php-mode-pear-hook'
|
||
\"drupal\" `php-mode-drupal-hook'
|
||
\"wordpress\" `php-mode-wordpress-hook'
|
||
\"symfony2\" `php-mode-symfony2-hook'
|
||
\"psr2\" `php-mode-psr2-hook'"
|
||
(interactive
|
||
(list (let ((completion-ignore-case t)
|
||
(prompt (format "Which %s indentation style? "
|
||
mode-name)))
|
||
(completing-read prompt
|
||
(php-mode-get-style-alist)
|
||
nil t nil
|
||
'php-mode-set-style-history
|
||
c-indentation-style))))
|
||
(php-mode--disable-delay-set-style)
|
||
|
||
;; Back up manually set variables
|
||
(let* (value
|
||
(backup-vars
|
||
(and php-mode-enable-backup-style-variables
|
||
(cl-loop for name in c-style-variables
|
||
do (setq value (symbol-value name))
|
||
if (and value (not (eq 'set-from-style value)))
|
||
collect (cons name value)))))
|
||
(c-set-style stylename dont-override)
|
||
;; Restore variables
|
||
(cl-loop for (name . value) in backup-vars
|
||
do (set (make-local-variable name) value)))
|
||
|
||
(if (eq (symbol-value 'php-style-delete-trailing-whitespace) t)
|
||
(add-hook 'before-save-hook 'delete-trailing-whitespace nil t)
|
||
(remove-hook 'before-save-hook 'delete-trailing-whitespace t))
|
||
(cond ((equal stylename "pear") (run-hooks 'php-mode-pear-hook))
|
||
((equal stylename "drupal") (run-hooks 'php-mode-drupal-hook))
|
||
((equal stylename "wordpress") (run-hooks 'php-mode-wordpress-hook))
|
||
((equal stylename "symfony2") (run-hooks 'php-mode-symfony2-hook))
|
||
((equal stylename "psr2") (run-hooks 'php-mode-psr2-hook))))
|
||
|
||
(defun php-mode--disable-delay-set-style (&rest args)
|
||
"Disable php-mode-set-style-delay on after hook. `ARGS' be ignore."
|
||
(setq php-mode--delayed-set-style nil)
|
||
(when (fboundp 'advice-remove)
|
||
(advice-remove #'php-mode--disable-delay-set-style #'c-set-style)))
|
||
|
||
(defun php-mode-set-style-delay ()
|
||
"Set the current `php-mode' buffer to use the style by custom or local variables."
|
||
(when php-mode--delayed-set-style
|
||
(let ((coding-style (or (and (boundp 'php-project-coding-style) php-project-coding-style)
|
||
php-mode-coding-style)))
|
||
(prog1 (when coding-style
|
||
(php-set-style (symbol-name coding-style)))
|
||
(remove-hook 'hack-local-variables-hook #'php-mode-set-style-delay)))))
|
||
|
||
(defun php-mode-set-local-variable-delay ()
|
||
"Set local variable from php-project."
|
||
(php-project-apply-local-variables)
|
||
(remove-hook 'hack-local-variables-hook #'php-mode-set-local-variable-delay))
|
||
|
||
(defvar php-mode-syntax-table
|
||
(let ((table (make-syntax-table)))
|
||
(c-populate-syntax-table table)
|
||
(modify-syntax-entry ?_ "_" table)
|
||
(modify-syntax-entry ?` "\"" table)
|
||
(modify-syntax-entry ?\" "\"" table)
|
||
(modify-syntax-entry ?# "< b" table)
|
||
(modify-syntax-entry ?\n "> b" table)
|
||
(modify-syntax-entry ?$ "_" table)
|
||
table))
|
||
|
||
;;;###autoload
|
||
(define-derived-mode php-mode c-mode "PHP"
|
||
"Major mode for editing PHP code.
|
||
|
||
\\{php-mode-map}"
|
||
:syntax-table php-mode-syntax-table
|
||
;; :after-hook (c-update-modeline)
|
||
;; (setq abbrev-mode t)
|
||
(when php-mode-disable-c-mode-hook
|
||
(setq-local c-mode-hook nil)
|
||
(setq-local java-mode-hook nil))
|
||
(c-initialize-cc-mode t)
|
||
(c-init-language-vars php-mode)
|
||
(c-common-init 'php-mode)
|
||
|
||
(setq-local comment-start "// ")
|
||
(setq-local comment-start-skip
|
||
(eval-when-compile
|
||
(rx (group (or (: "#")
|
||
(: "/" (+ "/"))
|
||
(: "/*")))
|
||
(* (syntax whitespace)))))
|
||
(setq-local comment-end "")
|
||
(setq-local page-delimiter php-mode-page-delimiter)
|
||
|
||
(setq-local font-lock-string-face 'php-string)
|
||
(setq-local font-lock-keyword-face 'php-keyword)
|
||
(setq-local font-lock-builtin-face 'php-builtin)
|
||
(setq-local c-preprocessor-face-name 'php-php-tag)
|
||
(setq-local font-lock-function-name-face 'php-function-name)
|
||
(setq-local font-lock-variable-name-face 'php-variable-name)
|
||
(setq-local font-lock-constant-face 'php-constant)
|
||
|
||
(setq-local syntax-propertize-function #'php-syntax-propertize-function)
|
||
(add-hook 'syntax-propertize-extend-region-functions
|
||
#'php-syntax-propertize-extend-region t t)
|
||
|
||
(setq imenu-generic-expression php-imenu-generic-expression)
|
||
|
||
;; PHP vars are case-sensitive
|
||
(setq case-fold-search t)
|
||
|
||
(when php-mode-enable-project-local-variable
|
||
(add-hook 'hack-local-variables-hook #'php-mode-set-local-variable-delay t t))
|
||
|
||
;; When php-mode-enable-project-coding-style is set, it is delayed by hook.
|
||
;; Since it depends on the timing at which the file local variable is set.
|
||
;; File local variables are set after initialization of major mode except `run-hook' is complete.
|
||
(if php-mode-enable-project-coding-style
|
||
(progn
|
||
(add-hook 'hack-local-variables-hook #'php-mode-set-style-delay t t)
|
||
(setq php-mode--delayed-set-style t)
|
||
(when (fboundp 'advice-add)
|
||
(advice-add #'c-set-style :after #'php-mode--disable-delay-set-style '(local))))
|
||
(let ((php-mode-enable-backup-style-variables nil))
|
||
(php-set-style (symbol-name php-mode-coding-style))))
|
||
|
||
(when (or php-mode-force-pear
|
||
(and (stringp buffer-file-name)
|
||
(string-match "PEAR\\|pear" buffer-file-name)
|
||
(string-match "\\.php\\'" buffer-file-name)))
|
||
(php-set-style "pear"))
|
||
|
||
(setq indent-line-function 'php-cautious-indent-line)
|
||
(setq indent-region-function 'php-cautious-indent-region)
|
||
(setq c-at-vsemi-p-fn #'php-c-at-vsemi-p)
|
||
(setq c-vsemi-status-unknown-p-fn #'php-c-vsemi-status-unknown-p)
|
||
|
||
;; We map the php-{beginning,end}-of-defun functions so that they
|
||
;; replace the similar commands that we inherit from CC Mode.
|
||
;; Because of our remapping we may not actually need to keep the
|
||
;; following two local variables, but we keep them for now until we
|
||
;; are completely sure their removal will not break any current
|
||
;; behavior or backwards compatibility.
|
||
(setq-local beginning-of-defun-function 'php-beginning-of-defun)
|
||
(setq-local end-of-defun-function 'php-end-of-defun)
|
||
|
||
(setq-local open-paren-in-column-0-is-defun-start nil)
|
||
(setq-local defun-prompt-regexp
|
||
"^\\s-*function\\s-+&?\\s-*\\(\\(\\sw\\|\\s_\\)+\\)\\s-*")
|
||
(setq-local add-log-current-defun-function nil)
|
||
(setq-local add-log-current-defun-header-regexp php-beginning-of-defun-regexp)
|
||
|
||
(when (fboundp #'c-looking-at-or-maybe-in-bracelist)
|
||
(advice-add #'c-looking-at-or-maybe-in-bracelist
|
||
:override 'php-c-looking-at-or-maybe-in-bracelist))
|
||
|
||
(when (>= emacs-major-version 25)
|
||
(with-silent-modifications
|
||
(save-excursion
|
||
(let* ((start (point-min))
|
||
(end (min (point-max)
|
||
(+ start syntax-propertize-chunk-size))))
|
||
(php-syntax-propertize-function start end))))))
|
||
|
||
(declare-function semantic-create-imenu-index "semantic/imenu" (&optional stream))
|
||
|
||
(defvar-mode-local php-mode imenu-create-index-function
|
||
(if php-do-not-use-semantic-imenu
|
||
#'imenu-default-create-index-function
|
||
(require 'semantic/imenu)
|
||
#'semantic-create-imenu-index)
|
||
"Imenu index function for PHP.")
|
||
|
||
|
||
;; Define function name completion function
|
||
(defvar php-completion-table nil
|
||
"Obarray of tag names defined in current tags table and functions known to PHP.")
|
||
|
||
(defun php-complete-function ()
|
||
"Perform function completion on the text around point.
|
||
Completes to the set of names listed in the current tags table
|
||
and the standard php functions.
|
||
The string to complete is chosen in the same way as the default
|
||
for \\[find-tag] (which see)."
|
||
(interactive)
|
||
(let ((pattern (php-get-pattern))
|
||
beg
|
||
completion
|
||
(php-functions (php-completion-table)))
|
||
(if (not pattern) (message "Nothing to complete")
|
||
(if (not (search-backward pattern nil t))
|
||
(message "Can't complete here")
|
||
(setq beg (point))
|
||
(forward-char (length pattern))
|
||
(setq completion (try-completion pattern php-functions nil))
|
||
(cond ((eq completion t))
|
||
((null completion)
|
||
(message "Can't find completion for \"%s\"" pattern)
|
||
(ding))
|
||
((not (string= pattern completion))
|
||
(delete-region beg (point))
|
||
(insert completion))
|
||
(t
|
||
(let ((selected (completing-read
|
||
"Select completion: "
|
||
(all-completions pattern php-functions)
|
||
nil t pattern)))
|
||
(delete-region beg (point))
|
||
(insert selected))))))))
|
||
|
||
(defun php-completion-table ()
|
||
"Build variable `php-completion-table' on demand.
|
||
The table includes the PHP functions and the tags from the
|
||
current `tags-file-name'."
|
||
(or (and tags-file-name
|
||
(save-excursion (tags-verify-table tags-file-name))
|
||
php-completion-table)
|
||
(let ((tags-table
|
||
(when tags-file-name
|
||
(with-current-buffer (get-file-buffer tags-file-name)
|
||
(etags-tags-completion-table))))
|
||
(php-table
|
||
(cond ((and (not (string= "" php-completion-file))
|
||
(file-readable-p php-completion-file))
|
||
(php-build-table-from-file php-completion-file))
|
||
((and (not (string= "" php-manual-path))
|
||
(file-directory-p php-manual-path))
|
||
(php-build-table-from-path php-manual-path))
|
||
(t nil))))
|
||
(unless (or php-table tags-table)
|
||
(error
|
||
(concat "No TAGS file active nor are "
|
||
"`php-completion-file' or `php-manual-path' set")))
|
||
(when tags-table
|
||
;; Combine the tables.
|
||
(if (obarrayp tags-table)
|
||
(mapatoms (lambda (sym) (intern (symbol-name sym) php-table))
|
||
tags-table)
|
||
(setq php-table (append tags-table php-table))))
|
||
(setq php-completion-table php-table))))
|
||
|
||
(defun php-build-table-from-file (filename)
|
||
(let ((table (make-vector 1022 0))
|
||
(buf (find-file-noselect filename)))
|
||
(with-current-buffer buf
|
||
(goto-char (point-min))
|
||
(while (re-search-forward
|
||
"^\\([-a-zA-Z0-9_.]+\\)\n"
|
||
nil t)
|
||
(intern (buffer-substring (match-beginning 1) (match-end 1))
|
||
table)))
|
||
(kill-buffer buf)
|
||
table))
|
||
|
||
(defun php-build-table-from-path (path)
|
||
"Return list of PHP function name from `PATH' directory."
|
||
(cl-loop for file in (directory-files path nil "^function\\..+\\.html$")
|
||
if (string-match "\\.\\([-a-zA-Z_0-9]+\\)\\.html$" file)
|
||
collect (replace-regexp-in-string
|
||
"-" "_" (substring file (match-beginning 1) (match-end 1)) t)))
|
||
|
||
;; Find the pattern we want to complete
|
||
;; find-tag-default from GNU Emacs etags.el
|
||
(defun php-get-pattern ()
|
||
(save-excursion
|
||
(while (looking-at "\\sw\\|\\s_")
|
||
(forward-char 1))
|
||
(if (or (re-search-backward "\\sw\\|\\s_"
|
||
(save-excursion (beginning-of-line) (point))
|
||
t)
|
||
(re-search-forward "\\(\\sw\\|\\s_\\)+"
|
||
(save-excursion (end-of-line) (point))
|
||
t))
|
||
(progn (goto-char (match-end 0))
|
||
(buffer-substring-no-properties
|
||
(point)
|
||
(progn (forward-sexp -1)
|
||
(while (looking-at "\\s'")
|
||
(forward-char 1))
|
||
(point))))
|
||
nil)))
|
||
|
||
(defun php-show-arglist ()
|
||
"Show function arguments at cursor position."
|
||
(interactive)
|
||
(let* ((tagname (php-get-pattern))
|
||
(buf (find-tag-noselect tagname nil nil))
|
||
arglist)
|
||
(with-current-buffer buf
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(when (re-search-forward
|
||
(format "function\\s-+%s\\s-*(\\([^{]*\\))" tagname)
|
||
nil t)
|
||
(setq arglist (buffer-substring-no-properties
|
||
(match-beginning 1) (match-end 1))))))
|
||
(if arglist
|
||
(message "Arglist for %s: %s" tagname arglist)
|
||
(message "Unknown function: %s" tagname))))
|
||
|
||
(defcustom php-search-documentation-browser-function nil
|
||
"Function to display PHP documentation in a WWW browser.
|
||
|
||
If non-nil, this shadows the value of `browse-url-browser-function' when
|
||
calling `php-search-documentation' or `php-search-local-documentation'."
|
||
:group 'php
|
||
:tag "PHP Search Documentation Browser Function"
|
||
:type '(choice (const :tag "default" nil) function)
|
||
:link '(variable-link browse-url-browser-function))
|
||
|
||
(defun php-browse-documentation-url (url)
|
||
"Browse a documentation URL using the configured browser function.
|
||
|
||
See `php-search-documentation-browser-function'."
|
||
(let ((browse-url-browser-function
|
||
(or php-search-documentation-browser-function
|
||
browse-url-browser-function)))
|
||
(browse-url url)))
|
||
|
||
(defvar php-search-local-documentation-types
|
||
(list "function" "control-structures" "class" "book")
|
||
;; "intro" and "ref" also look interesting, but for all practical purposes
|
||
;; their terms are sub-sets of the "book" terms (with the few exceptions
|
||
;; being very unlikely search terms).
|
||
"The set (and priority sequence) of documentation file prefixes
|
||
under which to search for files in the local documentation directory.")
|
||
|
||
(defvar php-search-local-documentation-words-cache nil)
|
||
|
||
(defun php--search-documentation-read-arg ()
|
||
"Obtain interactive argument for searching documentation."
|
||
;; Cache the list of documentation words available for completion,
|
||
;; based on the defined types-of-interest.
|
||
(let ((types-list php-search-local-documentation-types)
|
||
(words-cache php-search-local-documentation-words-cache)
|
||
(local-manual (and (stringp php-manual-path)
|
||
(not (string= php-manual-path "")))))
|
||
(when (and local-manual
|
||
(not (assq types-list words-cache)))
|
||
;; Generate the cache on the first run, or if the types changed.
|
||
;; We read the filenames matching our types list in the local
|
||
;; documentation directory, and extract the 'middle' component
|
||
;; of each. e.g. "function.array-map.html" => "array_map".
|
||
(let* ((types-opt (regexp-opt types-list))
|
||
(pattern (concat "\\`" types-opt "\\.\\(.+\\)\\.html\\'"))
|
||
(collection
|
||
(mapcar (lambda (filename) (subst-char-in-string
|
||
?- ?_ (replace-regexp-in-string
|
||
pattern "\\1" filename)))
|
||
(directory-files php-manual-path nil pattern))))
|
||
;; Replace the entire cache. If the types changed, we don't need
|
||
;; to retain the collection for the previous value.
|
||
(setq words-cache (list (cons types-list collection)))
|
||
(setq php-search-local-documentation-words-cache words-cache)))
|
||
;; By default we search for (current-word) immediately, without prompting.
|
||
;; With a prefix argument, or if there is no (current-word), we perform a
|
||
;; completing read for a word from the cached collection.
|
||
(let* ((default (current-word))
|
||
(prompt (if default
|
||
(format "Search PHP docs (%s): " default)
|
||
"Search PHP docs: "))
|
||
(collection (and local-manual
|
||
(cdr (assq types-list words-cache))))
|
||
(word (if (or current-prefix-arg (not default))
|
||
(completing-read prompt collection nil nil nil nil default)
|
||
default)))
|
||
;; Return interactive argument list.
|
||
(list word))))
|
||
|
||
(defun php-search-local-documentation (word)
|
||
"Search the local PHP documentation (i.e. in `php-manual-path') for
|
||
the word at point. The function returns t if the requested documentation
|
||
exists, and nil otherwise.
|
||
|
||
With a prefix argument, prompt (with completion) for a word to search for."
|
||
(interactive (php--search-documentation-read-arg))
|
||
(let ((file (catch 'found
|
||
(cl-loop for type in php-search-local-documentation-types do
|
||
(let* ((doc-html (format "%s.%s.html"
|
||
type
|
||
(replace-regexp-in-string
|
||
"_" "-" (downcase word))))
|
||
(file (expand-file-name doc-html php-manual-path)))
|
||
(when (file-exists-p file)
|
||
(throw 'found file)))))))
|
||
(when file
|
||
(let ((file-url (if (string-prefix-p "file://" file)
|
||
file
|
||
(concat "file://" file))))
|
||
(php-browse-documentation-url file-url))
|
||
t)))
|
||
|
||
(defsubst php-search-web-documentation (word)
|
||
"Return URL to search PHP manual search by `WORD'."
|
||
(php-browse-documentation-url (concat (or php-search-url php-site-url) word)))
|
||
|
||
;; Define function documentation function
|
||
(defun php-search-documentation (word)
|
||
"Search PHP documentation for the `WORD' at point.
|
||
|
||
If `php-manual-path' has a non-empty string value then the command
|
||
will first try searching the local documentation. If the requested
|
||
documentation does not exist it will fallback to searching the PHP
|
||
website.
|
||
|
||
With a prefix argument, prompt for a documentation word to search
|
||
for. If the local documentation is available, it is used to build
|
||
a completion list."
|
||
(interactive (php--search-documentation-read-arg))
|
||
(if (and (stringp php-manual-path)
|
||
(not (string= php-manual-path "")))
|
||
(or (php-search-local-documentation word)
|
||
(php-search-web-documentation word))
|
||
(php-search-web-documentation word)))
|
||
|
||
;; Define function for browsing manual
|
||
(defun php-browse-manual ()
|
||
"Bring up manual for PHP."
|
||
(interactive)
|
||
(browse-url (if (stringp php-manual-url)
|
||
php-manual-url
|
||
(format "%smanual/%s/" php-site-url php-manual-url))))
|
||
|
||
|
||
;; Font Lock
|
||
(defconst php-phpdoc-type-keywords
|
||
(list "string" "integer" "int" "boolean" "bool" "float"
|
||
"double" "object" "mixed" "array" "resource"
|
||
"void" "null" "false" "true" "self" "static"
|
||
"callable" "iterable" "number"))
|
||
|
||
(defconst php-phpdoc-type-tags
|
||
(list "package" "param" "property" "property-read" "property-write"
|
||
"return" "throws" "var"))
|
||
|
||
(defconst php-phpdoc-font-lock-doc-comments
|
||
`(("{@[-[:alpha:]]+\\s-*\\([^}]*\\)}" ; "{@foo ...}" markup.
|
||
(0 'php-doc-annotation-tag prepend nil)
|
||
(1 'php-string prepend nil))
|
||
(,(rx (group "$") (group (in "A-Za-z_") (* (in "0-9A-Za-z_"))))
|
||
(1 'php-doc-variable-sigil prepend nil)
|
||
(2 'php-variable-name prepend nil))
|
||
("\\(\\$\\)\\(this\\)\\>" (1 'php-doc-$this-sigil prepend nil) (2 'php-doc-$this prepend nil))
|
||
(,(concat "\\s-@" (regexp-opt php-phpdoc-type-tags) "\\s-+"
|
||
"\\(" (rx (+ (? "?") (? "\\") (+ (in "0-9A-Z_a-z")) (? "[]") (? "|"))) "\\)+")
|
||
1 'php-string prepend nil)
|
||
(,(concat "\\(?:|\\|\\?\\|\\s-\\)\\("
|
||
(regexp-opt php-phpdoc-type-keywords 'words)
|
||
"\\)")
|
||
1 font-lock-type-face prepend nil)
|
||
("https?://[^\n\t ]+"
|
||
0 'link prepend nil)
|
||
("^\\(?:/\\*\\)?\\(?:\\s \\|\\*\\)*\\(@[[:alpha:]][-[:alpha:]\\]*\\)" ; "@foo ..." markup.
|
||
1 'php-doc-annotation-tag prepend nil)))
|
||
|
||
(defvar php-phpdoc-font-lock-keywords
|
||
`((,(lambda (limit)
|
||
(c-font-lock-doc-comments "/\\*\\*" limit
|
||
php-phpdoc-font-lock-doc-comments)))))
|
||
|
||
(defconst php-font-lock-keywords-1 (c-lang-const c-matchers-1 php)
|
||
"Basic highlighting for PHP Mode.")
|
||
|
||
(defconst php-font-lock-keywords-2 (c-lang-const c-matchers-2 php)
|
||
"Medium level highlighting for PHP Mode.")
|
||
|
||
(defconst php-font-lock-keywords-3
|
||
(append
|
||
php-phpdoc-font-lock-keywords
|
||
;; php-mode patterns *before* cc-mode:
|
||
;; only add patterns here if you want to prevent cc-mode from applying
|
||
;; a different face.
|
||
`(
|
||
;; Class declaration specification keywords (implements, extends)
|
||
("\\_<\\(?:implements\\|extends\\)\\_>" . 'php-class-declaration-spec)
|
||
;; Namespace declaration
|
||
("\\_<namespace\\_>" . 'php-namespace-declaration)
|
||
;; import statement
|
||
("\\_<use\\_>" . 'php-import-declaration)
|
||
;; Class modifiers (abstract, final)
|
||
("\\_<\\(abstract\\|final\\)\\_>\\s-+\\_<class\\>" 1 'php-class-modifier)
|
||
|
||
;; Highlight variables, e.g. 'var' in '$var' and '$obj->var', but
|
||
;; not in $obj->var()
|
||
("\\(->\\)\\(\\sw+\\)\\s-*(" (1 'php-object-op) (2 'php-method-call))
|
||
("\\<\\(const\\)\\s-+\\(\\_<.+?\\_>\\)" (1 'php-keyword) (2 'php-constant-assign))
|
||
|
||
;; Logical operator (!)
|
||
("\\(!\\)[^=]" 1 'php-logical-op)
|
||
|
||
;; Highlight special variables
|
||
("\\(\\$\\)\\(this\\)\\>" (1 'php-$this-sigil) (2 'php-$this))
|
||
("\\(\\$+\\)\\(\\sw+\\)" (1 'php-variable-sigil) (2 'php-variable-name))
|
||
("\\(->\\)\\([a-zA-Z0-9_]+\\)" (1 'php-object-op) (2 'php-property-name))
|
||
|
||
;; Highlight function/method names
|
||
("\\<function\\s-+&?\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*(" 1 'php-function-name)
|
||
|
||
;; 'array' and 'callable' are keywords, except in the following situations:
|
||
;; - when used as a type hint
|
||
;; - when used as a return type
|
||
("\\b\\(array\\|callable\\)\\s-+&?\\$" 1 font-lock-type-face)
|
||
(")\\s-*:\\s-*\\??\\(array\\|callable\\)\\b" 1 font-lock-type-face)
|
||
;; For 'array', there is an additional situation:
|
||
;; - when used as cast, so that (int) and (array) look the same
|
||
("(\\(array\\))" 1 font-lock-type-face)
|
||
|
||
(,(regexp-opt php-magical-constants 'symbols) (1 'php-magical-constant))
|
||
;; namespaces
|
||
("\\(\\([a-zA-Z0-9_]+\\\\\\)+[a-zA-Z0-9_]+\\|\\(\\\\[a-zA-Z0-9_]+\\)+\\)[^:a-zA-Z0-9_\\\\]" 1 'font-lock-type-face)
|
||
("\\(\\([a-zA-Z0-9_]+\\\\\\)+[a-zA-Z0-9_]+\\|\\(\\\\[a-zA-Z0-9_]+\\)+\\)::" 1 'php-constant)
|
||
(,(eval-when-compile
|
||
(rx bol (* (syntax whitespace))
|
||
(or "private" "protected" "public")
|
||
(+ (syntax whitespace))
|
||
(group (? "?") (+ (or "\\" (syntax word) (syntax symbol))))
|
||
(+ (syntax whitespace))
|
||
(: "$" (+ (or (syntax word) (syntax symbol))))))
|
||
1 'php-class)
|
||
;; Support the ::class constant in PHP5.6
|
||
("\\sw+\\(::\\)\\(class\\)\\b" (1 'php-paamayim-nekudotayim) (2 'php-magical-constant))
|
||
;; Class declaration keywords (class, trait, interface)
|
||
("\\_<\\(class\\|trait\\|interface\\)\\_>" . 'php-class-declaration)
|
||
|
||
;; Highlight static method calls as such. This is necessary for method
|
||
;; names which are identical to keywords to be highlighted correctly.
|
||
("\\sw+::\\(\\sw+\\)(" 1 'php-static-method-call)
|
||
;; Multiple catch (FooException | BarException $e)
|
||
(,(rx symbol-start "catch" symbol-end
|
||
(* (syntax whitespace)) "(" (* (syntax whitespace))
|
||
(group (+ (or (syntax word) (syntax symbol)))))
|
||
(1 font-lock-type-face)
|
||
(,(rx (* (syntax whitespace)) "|" (* (syntax whitespace))
|
||
(group (+ (or (syntax word) (syntax symbol))) symbol-end))
|
||
nil nil (1 font-lock-type-face)))
|
||
;; While c-opt-cpp-* highlights the <?php opening tags, it is not
|
||
;; possible to make it highlight short open tags and closing tags
|
||
;; as well. So we force the correct face on all cases that
|
||
;; c-opt-cpp-* lacks for this purpose.
|
||
;;
|
||
;; Note that starting a file with <% breaks indentation, a
|
||
;; limitation we can/should live with.
|
||
(,(regexp-opt '("<?php" "<?=" "?>"
|
||
"<?" ;; obsolete short open tag
|
||
"<%" "%>" ;; obsolete ASP tag
|
||
;; Obsoleted tags were deleted in PHP 7.
|
||
;; @see http://php.net/manual/language.basic-syntax.phptags.php
|
||
))
|
||
0 'php-php-tag))
|
||
|
||
;; cc-mode patterns
|
||
(c-lang-const c-matchers-3 php)
|
||
|
||
;; php-mode patterns *after* cc-mode:
|
||
;; most patterns should go here, faces will only be applied if not
|
||
;; already fontified by another pattern. Note that using OVERRIDE
|
||
;; is usually overkill.
|
||
`(
|
||
("\\<\\(@\\)" 1 'php-errorcontrol-op)
|
||
;; Highlight function calls
|
||
("\\(\\_<\\(?:\\sw\\|\\s_\\)+?\\_>\\)\\s-*(" 1 'php-function-call)
|
||
;; Highlight all upper-cased symbols as constant
|
||
("\\<\\([A-Z_][A-Z0-9_]+\\)\\>" 1 'php-constant)
|
||
|
||
;; Highlight all statically accessed class names as constant,
|
||
;; another valid option would be using type-face, but using
|
||
;; constant-face because this is how it works in c++-mode.
|
||
("\\(\\sw+\\)\\(::\\)" (1 'php-constant) (2 'php-paamayim-nekudotayim))
|
||
|
||
;; Highlight class name after "use .. as"
|
||
("\\<as\\s-+\\(\\sw+\\)" 1 font-lock-type-face)
|
||
|
||
;; Class names are highlighted by cc-mode as defined in
|
||
;; c-class-decl-kwds, below regexp is a workaround for a bug
|
||
;; where the class names are not highlighted right after opening
|
||
;; a buffer (editing a file corrects it).
|
||
;;
|
||
;; This behaviour is caused by the preceding '<?php', which
|
||
;; cc-mode cannot handle easily. Registering it as a cpp
|
||
;; preprocessor works well (i.e. the next line is not a
|
||
;; statement-cont) but the highlighting glitch remains.
|
||
(,(concat (regexp-opt (c-lang-const c-class-decl-kwds php))
|
||
" \\(\\sw+\\)")
|
||
1 font-lock-type-face)
|
||
|
||
;; Highlight the ? character for nullable return types.
|
||
("function.+:\\s-*\\(\\?\\)\\(?:\\sw\\|\\s_\\|\\\\\\)+" 1 font-lock-type-face)
|
||
(")\\s-*:\\s-*\\(\\?\\)\\(?:\\sw\\|\\s_\\|\\\\\\)+\\s-*\\(?:\{\\|;\\)" 1 font-lock-type-face)
|
||
|
||
;; Highlight the ? character for nullable type hints.
|
||
("\\(\\?\\)\\(:?\\sw\\|\\s_\\|\\\\\\)+\\s-+\\$" 1 font-lock-type-face)
|
||
|
||
;; Class names without a namespace are not highlighted at all when they
|
||
;; are used as nullable type hints or return types (both nullable and
|
||
;; non-nullable). We have to use separate regular expressions, because
|
||
;; we want to capture the class name as well, not just the ? character
|
||
;; like the regexps above.
|
||
("\\?\\(\\(:?\\sw\\|\\s_\\)+\\)\\s-+\\$" 1 font-lock-type-face)
|
||
("function.+:\\s-*\\??\\(\\(?:\\sw\\|\\s_\\)+\\)" 1 font-lock-type-face)
|
||
(")\\s-*:\\s-*\\??\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*\\(?:\{\\|;\\)" 1 font-lock-type-face)
|
||
|
||
;; Assignment operators (=, +=, ...)
|
||
("\\([^=<!>]+?\\([\-+./%]?=\\)[^=<!]+?\\)" 2 'php-assignment-op)
|
||
|
||
;; Comparison operators (==, ===, >=, ...)
|
||
("\\([!=]=\\{1,2\\}[>]?\\|[<>]=?\\)" 1 'php-comparison-op)
|
||
|
||
;; Arithmetic operators (+, -, *, **, /, %)
|
||
("\\(?:[A-Za-z0-9[:blank:]]\\)\\([\-+*/%]\\*?\\)\\(?:[A-Za-z0-9[:blank:]]\\)" 1 'php-arithmetic-op)
|
||
|
||
;; Increment and Decrement operators (++, --)
|
||
("\\(\-\-\\|\+\+\\)\$\\w+" 1 'php-inc-dec-op) ;; pre inc/dec
|
||
("\$\\w+\\(\-\-\\|\+\+\\)" 1 'php-inc-dec-op) ;; post inc/dec
|
||
|
||
;; Logical operators (and, or, &&, ...)
|
||
;; Not operator (!) is defined in "before cc-mode" section above.
|
||
("\\(&&\\|||\\)" 1 'php-logical-op)))
|
||
"Detailed highlighting for PHP Mode.")
|
||
|
||
(defvar php-font-lock-keywords php-font-lock-keywords-3
|
||
"Default expressions to highlight in PHP Mode.")
|
||
|
||
(add-to-list
|
||
(eval-when-compile
|
||
(if (boundp 'flymake-proc-allowed-file-name-masks)
|
||
'flymake-proc-allowed-file-name-masks
|
||
'flymake-allowed-file-name-masks))
|
||
'("\\.php[345s]?\\'" php-flymake-php-init))
|
||
|
||
|
||
(defun php-send-region (start end)
|
||
"Send the region between `START' and `END' to PHP for execution.
|
||
The output will appear in the buffer *PHP*."
|
||
(interactive "r")
|
||
(let ((php-buffer (get-buffer-create "*PHP*"))
|
||
(code (buffer-substring start end)))
|
||
;; Calling 'php -r' will fail if we send it code that starts with
|
||
;; '<?php', which is likely. So we run the code through this
|
||
;; function to check for that prefix and remove it.
|
||
(let ((cleaned-php-code (if (string-prefix-p "<?php" code t)
|
||
(substring code 5)
|
||
code)))
|
||
(call-process php-executable nil php-buffer nil "-r" cleaned-php-code))))
|
||
|
||
|
||
(defconst php-string-interpolated-variable-regexp
|
||
"{\\$[^}\n\\\\]*\\(?:\\\\.[^}\n\\\\]*\\)*}\\|\\${\\sw+}\\|\\$\\sw+")
|
||
|
||
(defun php-string-intepolated-variable-font-lock-find (limit)
|
||
(while (re-search-forward php-string-interpolated-variable-regexp limit t)
|
||
(let ((quoted-stuff (nth 3 (syntax-ppss))))
|
||
(when (and quoted-stuff (member quoted-stuff '(?\" ?`)))
|
||
(put-text-property (match-beginning 0) (match-end 0)
|
||
'face 'php-variable-name))))
|
||
nil)
|
||
|
||
(eval-after-load 'php-mode
|
||
'(progn
|
||
(font-lock-add-keywords
|
||
'php-mode
|
||
`((php-string-intepolated-variable-font-lock-find))
|
||
'append)))
|
||
|
||
|
||
|
||
;;; Correct the behavior of `delete-indentation' by modifying the
|
||
;;; logic of `fixup-whitespace'.
|
||
(defadvice fixup-whitespace (after php-mode-fixup-whitespace)
|
||
"Remove whitespace before certain characters in PHP Mode."
|
||
(let* ((no-behind-space ";\\|,\\|->\\|::")
|
||
(no-front-space "->\\|::"))
|
||
(when (and (eq major-mode 'php-mode)
|
||
(or (looking-at-p (concat " \\(" no-behind-space "\\)"))
|
||
(save-excursion
|
||
(forward-char -2)
|
||
(looking-at-p no-front-space))))
|
||
(delete-char 1))))
|
||
|
||
(ad-activate 'fixup-whitespace)
|
||
|
||
;;;###autoload
|
||
(progn
|
||
(add-to-list 'auto-mode-alist '("/\\.php_cs\\(?:\\.dist\\)?\\'" . php-mode))
|
||
(add-to-list 'auto-mode-alist '("\\.\\(?:php\\.inc\\|stub\\)\\'" . php-mode))
|
||
(add-to-list 'auto-mode-alist '("\\.\\(?:php[s345]?\\|phtml\\)\\'" . php-mode-maybe)))
|
||
|
||
(provide 'php-mode)
|
||
;;; php-mode.el ends here
|