;;; php-mode.el --- Major mode for editing PHP code -*- lexical-binding: t; -*- ;; 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 ;; URL: https://github.com/emacs-php/php-mode ;; Keywords: languages php ;; Version: 1.24.0 ;; Package-Requires: ((emacs "25.2")) ;; License: GPL-3.0-or-later (defconst php-mode-version-number "1.24.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 . ;;; 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 ") (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" "never")) (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" "enum")) (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 C-like \"enum\" 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" "case")) (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-*") (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))) (cond ;; Detect PHP8 attribute: #[Attribute()] ((and (< 1 pos) (< 1 (- pos (c-point 'bol)))) (backward-char 1) (looking-at-p (eval-when-compile (rx "]" (* (syntax whitespace)) (or "#[" line-end))))) ;; Detect HTML/XML tag and PHP tag () (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 (rx "<<<" (* (syntax whitespace)) (or (group (+ (or (syntax word) (syntax symbol)))) (: "\"" (group (+ (or (syntax word) (syntax symbol)))) "\"") (: "'" (group (+ (or (syntax word) (syntax symbol)))) "'")) line-end) "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")) (eval-and-compile (defconst php-syntax-propertize-rules `((php-heredoc-start-re (0 (ignore (php--syntax-propertize-heredoc (match-beginning 0) (or (match-string 1) (match-string 2) (match-string 3)) (null (match-string 3)))))) (,(rx "#[") (0 (ignore (php--syntax-propertize-attributes (match-beginning 0))))) (,(rx (or "'" "\"" )) (0 (ignore (php--syntax-propertize-quotes-in-comment (match-beginning 0))))))) (defmacro php-build-propertize-function () `(syntax-propertize-rules ,@php-syntax-propertize-rules)) (defalias 'php-syntax-propertize-function (php-build-propertize-function))) (defun php--syntax-propertize-heredoc (start id is-heredoc) "Apply propertize Heredoc and Nowdoc from START, with ID and IS-HEREDOC." (let ((terminator (rx-to-string `(: line-start (* (syntax whitespace)) ,id word-boundary)))) (put-text-property start (1+ start) 'syntax-table (string-to-syntax "|")) (re-search-forward terminator nil t) (when (match-string 0) (put-text-property (1- (point)) (point) 'syntax-table (string-to-syntax "|"))))) (defun php--syntax-propertize-quotes-in-comment (pos) "Apply propertize quotes (' and \") from POS." (when (php-in-comment-p) (put-text-property pos (1+ pos) 'syntax-table (string-to-syntax "_")))) (defun php--syntax-propertize-attributes (start) "Apply propertize PHP8 #[Attributes] (without # comment) from START." (unless (php-in-string-p) (put-text-property start (1+ start) '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) (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) (unless (string= php-mode-cc-vertion c-version) (user-error "CC Mode has been updated. %s" (if (package-installed-p 'php-mode) "Please run `M-x package-reinstall php-mode' command." "Please byte recompile PHP Mode files."))) (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 (: "#" (not (any "["))) (: "/" (+ "/")) (: "/*"))) (* (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 (if (symbolp php-imenu-generic-expression) (symbol-value php-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) (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 '(local))) (advice-add #'fixup-whitespace :after #'php-mode--fixup-whitespace-after '(local)) (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.") (autoload 'php-local-manual-complete-function "php-local-manual") (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) (php-local-manual-complete-function)) (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)))) ;; 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 ("\\_" . 'php-namespace-declaration) ;; import statement ("\\_" . 'php-import-declaration) ;; Class modifiers (abstract, final) ("\\_<\\(abstract\\|final\\)\\_>\\s-+\\_" 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 ("\\" . '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 " "" ;; 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" ("\\]+?\\([\-+./%]?=\\)[^==, ...) ("\\([!=]=\\{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) ;; string interpolation ("$var, ${var}, {$var}") (php-mode--string-interpolated-variable-font-lock-find 0 nil))) "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 ;; '\\|::\\)") (save-excursion (forward-char -2) (looking-at-p "->\\|::"))) (delete-char 1))) ;;;###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