1548 lines
55 KiB
EmacsLisp
1548 lines
55 KiB
EmacsLisp
;;; php-mode.el --- Major mode for editing PHP code
|
||
|
||
;; Copyright (C) 1999, 2000, 2001, 2003, 2004 Turadg Aleahmad
|
||
;; 2008 Aaron S. Hawley
|
||
;; 2011, 2012, 2013, 2014, 2015 Eric James Michael Ritz
|
||
|
||
;;; Author: Eric James Michael Ritz
|
||
;;; URL: https://github.com/ejmr/php-mode
|
||
;;; Version: 1.17.0
|
||
|
||
(defconst php-mode-version-number "1.17.0"
|
||
"PHP Mode version number.")
|
||
|
||
(defconst php-mode-modified "2015-10-02"
|
||
"PHP Mode build date.")
|
||
|
||
;;; License
|
||
|
||
;; This file 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 file 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 file; if not, write to the Free Software
|
||
;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||
;; 02110-1301, USA.
|
||
|
||
;;; Usage
|
||
|
||
;; Put this file in your Emacs lisp path (eg. site-lisp) and add to
|
||
;; your .emacs file:
|
||
;;
|
||
;; (require 'php-mode)
|
||
|
||
;; To use abbrev-mode, add lines like this:
|
||
;; (add-hook 'php-mode-hook
|
||
;; '(lambda () (define-abbrev php-mode-abbrev-table "ex" "extends")))
|
||
|
||
;; To make php-mode compatible with html-mode, see http://php-mode.sf.net
|
||
|
||
;; Many options available under Help:Customize
|
||
;; Options specific to php-mode are in
|
||
;; Programming/Languages/Php
|
||
;; Since it inherits much functionality from c-mode, look there too
|
||
;; Programming/Languages/C
|
||
|
||
;;; Commentary:
|
||
|
||
;; PHP mode is a major mode for editing PHP source code. It's an
|
||
;; extension of C mode; thus it inherits all C mode's navigation
|
||
;; functionality. But it colors according to the PHP grammar and
|
||
;; indents according to the PEAR coding guidelines. It also includes
|
||
;; a couple handy IDE-type features such as documentation search and a
|
||
;; source and class browser.
|
||
|
||
;;; Code:
|
||
|
||
(require 'cc-mode)
|
||
(eval-when-compile
|
||
(require 'cc-langs)
|
||
(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 'add-log)
|
||
(require 'custom)
|
||
(require 'flymake)
|
||
(require 'etags)
|
||
(require 'speedbar)
|
||
(eval-when-compile
|
||
(unless (require 'cl-lib nil t)
|
||
(require 'cl))
|
||
(require 'regexp-opt)
|
||
(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
|
||
(if (and (= emacs-major-version 24) (>= emacs-minor-version 4))
|
||
(require 'cl)))
|
||
|
||
;; Use the recommended cl functions in php-mode but alias them to the
|
||
;; old names when we detect emacs < 24.3
|
||
(if (and (= emacs-major-version 24) (< emacs-minor-version 3))
|
||
(unless (fboundp 'cl-set-difference)
|
||
(defalias 'cl-set-difference 'set-difference)))
|
||
|
||
|
||
;; Local variables
|
||
;;;###autoload
|
||
(defgroup php nil
|
||
"Major mode `php-mode' for editing PHP code."
|
||
:prefix "php-"
|
||
:group 'languages
|
||
:link '(url-link :tag "Official Site" "https://github.com/ejmr/php-mode")
|
||
:link '(url-link :tag "PHP Mode Wiki" "https://github.com/ejmr/php-mode/wiki"))
|
||
|
||
(defcustom php-executable (or (executable-find "php")
|
||
"/usr/bin/php")
|
||
"The location of the PHP executable."
|
||
:type 'string
|
||
:group 'php)
|
||
|
||
(defcustom php-default-face 'default
|
||
"Default face in `php-mode' buffers."
|
||
:type 'face
|
||
:group 'php)
|
||
|
||
(defcustom php-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]?\\)\""
|
||
:type 'boolean
|
||
:set (lambda (sym val)
|
||
(set-default sym val)
|
||
(when val
|
||
(speedbar-add-supported-extension
|
||
"\\.\\(inc\\|php[s345]?\\|phtml\\)")))
|
||
:group 'php)
|
||
|
||
(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."
|
||
:type 'boolean
|
||
:set (lambda (sym val)
|
||
(set-default sym val)
|
||
(when val
|
||
(speedbar 1)))
|
||
:group 'php)
|
||
|
||
(defcustom php-template-compatibility t
|
||
"Should detect presence of html tags."
|
||
:type 'boolean
|
||
:group 'php)
|
||
|
||
(defun php-mode-extra-constants-create-regexp(kwds)
|
||
"Create regexp for the list of extra constant keywords KWDS."
|
||
(concat "[^_$]?\\<\\("
|
||
(regexp-opt
|
||
(append kwds
|
||
(when (boundp 'web-mode-extra-php-constants) web-mode-extra-php-constants)))
|
||
"\\)\\>[^_]?"))
|
||
|
||
(defun php-mode-extra-constants-set(sym value)
|
||
"Apply the list of extra constant keywords VALUE.
|
||
|
||
This function is called when the custom variable php-extra-constants
|
||
is updated. The web-mode-extra-constants list is appended to the list
|
||
of constants when set."
|
||
;; remove old keywords
|
||
(when (boundp 'php-extra-constants)
|
||
(font-lock-remove-keywords
|
||
'php-mode `((,(php-mode-extra-constants-create-regexp php-extra-constants) 1 font-lock-constant-face))))
|
||
;; add new keywords
|
||
(when value
|
||
(font-lock-add-keywords
|
||
'php-mode `((,(php-mode-extra-constants-create-regexp value) 1 font-lock-constant-face))))
|
||
(set sym value))
|
||
|
||
(defcustom php-lineup-cascaded-calls nil
|
||
"Indent chained method calls to the previous line"
|
||
:type 'boolean
|
||
:group 'php)
|
||
|
||
;;;###autoload
|
||
(defcustom php-extra-constants '()
|
||
"A list of additional strings to treat as PHP constants."
|
||
:type 'list
|
||
:set 'php-mode-extra-constants-set
|
||
:group 'php)
|
||
|
||
(defun php-create-regexp-for-method (visibility)
|
||
"Make a regular expression for methods with the given VISIBILITY.
|
||
|
||
VISIBILITY must be a string that names the visibility for a PHP
|
||
method, e.g. 'public'. The parameter VISIBILITY can itself also
|
||
be a regular expression.
|
||
|
||
The regular expression this function returns will check for other
|
||
keywords that can appear in method signatures, e.g. 'final' and
|
||
'static'. The regular expression will have one capture group
|
||
which will be the name of the method."
|
||
(concat
|
||
;; Initial space with possible 'abstract' or 'final' keywords
|
||
"^\\s-*\\(?:\\(?:abstract\\|final\\)\\s-+\\)?"
|
||
;; 'static' keyword may come either before or after visibility
|
||
"\\(?:" visibility "\\(?:\\s-+static\\)?\\|\\(?:static\\s-+\\)?" visibility "\\)\\s-+"
|
||
;; Make sure 'function' comes next with some space after
|
||
"function\\s-+"
|
||
;; Capture the name as the first group and the regexp and make sure
|
||
;; by the end we see the opening parenthesis for the parameters.
|
||
"\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*("))
|
||
|
||
(defun php-create-regexp-for-classlike (type)
|
||
"Accepts a `type' of a 'classlike' object as a string, such as
|
||
'class' or 'interface', and returns a regexp as a string which
|
||
can be used to match against definitions for that classlike."
|
||
(concat
|
||
;; First see if 'abstract' or 'final' appear, although really these
|
||
;; are not valid for all values of `type' that the function
|
||
;; accepts.
|
||
"^\\s-*\\(?:\\(?:abstract\\|final\\)\\s-+\\)?"
|
||
;; The classlike type
|
||
type
|
||
;; Its name, which is the first captured group in the regexp. We
|
||
;; allow backslashes in the name to handle namespaces, but again
|
||
;; this is not necessarily correct for all values of `type'.
|
||
"\\s-+\\(\\(?:\\sw\\|\\\\\\|\\s_\\)+\\)"))
|
||
|
||
(defvar php-imenu-generic-expression
|
||
`(("Namespaces"
|
||
,(php-create-regexp-for-classlike "namespace") 1)
|
||
("Classes"
|
||
,(php-create-regexp-for-classlike "class") 1)
|
||
("Interfaces"
|
||
,(php-create-regexp-for-classlike "interface") 1)
|
||
("Traits"
|
||
,(php-create-regexp-for-classlike "trait") 1)
|
||
("All Methods"
|
||
,(php-create-regexp-for-method "\\(?:\\sw\\|\\s_\\)+") 1)
|
||
("Private Methods"
|
||
,(php-create-regexp-for-method "private") 1)
|
||
("Protected Methods"
|
||
,(php-create-regexp-for-method "protected") 1)
|
||
("Public Methods"
|
||
,(php-create-regexp-for-method "public") 1)
|
||
("Anonymous Functions"
|
||
"\\<\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*=\\s-*function\\s-*(" 1)
|
||
("Named Functions"
|
||
"^\\s-*function\\s-+\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*(" 1))
|
||
"Imenu generic expression for PHP Mode. See `imenu-generic-expression'.")
|
||
|
||
(defcustom php-manual-url "http://www.php.net/manual/en/"
|
||
"URL at which to find PHP manual.
|
||
You can replace \"en\" with your ISO language code."
|
||
:type 'string
|
||
:group 'php)
|
||
|
||
(defcustom php-search-url "http://www.php.net/"
|
||
"URL at which to search for documentation on a word."
|
||
:type 'string
|
||
:group 'php)
|
||
|
||
(defcustom php-completion-file ""
|
||
"Path to the file which contains the function names known to PHP."
|
||
:type 'string
|
||
:group 'php)
|
||
|
||
(defcustom php-manual-path ""
|
||
"Path to the directory which contains the PHP manual."
|
||
:type 'string
|
||
:group 'php)
|
||
|
||
;;;###autoload
|
||
(add-to-list 'interpreter-mode-alist (cons "php" 'php-mode))
|
||
|
||
(defcustom php-mode-hook nil
|
||
"List of functions to be executed on entry to `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(defcustom php-mode-pear-hook nil
|
||
"Hook called when a PHP PEAR file is opened with `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(defcustom php-mode-drupal-hook nil
|
||
"Hook called when a Drupal file is opened with `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(defcustom php-mode-wordpress-hook nil
|
||
"Hook called when a WordPress file is opened with `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(defcustom php-mode-symfony2-hook nil
|
||
"Hook called when a Symfony2 file is opened with `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(defcustom php-mode-psr2-hook nil
|
||
"Hook called when a PSR-2 file is opened with `php-mode'."
|
||
:type 'hook
|
||
:group 'php)
|
||
|
||
(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."
|
||
:type 'boolean
|
||
:group 'php)
|
||
|
||
(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."
|
||
:type '(choice (const :tag "Warg" t) (const "Don't warn" nil))
|
||
:group 'php)
|
||
|
||
(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.
|
||
|
||
`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.
|
||
|
||
`PSR-2' - use coding styles preferred for working with projects using PSR-2 standards."
|
||
:type '(choice (const :tag "Default" default)
|
||
(const :tag "PEAR" pear)
|
||
(const :tag "Drupal" drupal)
|
||
(const :tag "WordPress" wordpress)
|
||
(const :tag "Symfony2" symfony2)
|
||
(const :tag "PSR-2" psr2))
|
||
:group 'php
|
||
:set 'php-mode-custom-coding-style-set
|
||
:initialize 'custom-initialize-default)
|
||
|
||
(defun php-mode-custom-coding-style-set (sym value)
|
||
(when (eq major-mode 'php-mode)
|
||
(set sym value)
|
||
(set-default sym value)
|
||
(cond ((eq value 'pear)
|
||
(php-enable-pear-coding-style))
|
||
((eq value 'default)
|
||
(php-enable-default-coding-style))
|
||
((eq value 'drupal)
|
||
(php-enable-drupal-coding-style))
|
||
((eq value 'wordpress)
|
||
(php-enable-wordpress-coding-style))
|
||
((eq value 'symfony2)
|
||
(php-enable-symfony2-coding-style))
|
||
((eq value 'psr2)
|
||
(php-enable-psr2-coding-style)))))
|
||
|
||
(defun php-mode-version ()
|
||
"Display string describing the version of PHP mode."
|
||
(interactive)
|
||
(message "PHP mode %s of %s"
|
||
php-mode-version-number php-mode-modified))
|
||
|
||
(defvar php-mode-map
|
||
(let ((map (make-sparse-keymap)))
|
||
;; (define-key map [menu-bar php]
|
||
;; (cons "PHP" (make-sparse-keymap "PHP")))
|
||
|
||
;; (define-key map [menu-bar php complete-function]
|
||
;; '("Complete function name" . php-complete-function))
|
||
;; (define-key map [menu-bar php browse-manual]
|
||
;; '("Browse manual" . php-browse-manual))
|
||
;; (define-key map [menu-bar php search-documentation]
|
||
;; '("Search documentation" . php-search-documentation))
|
||
|
||
;; 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 Submode Word.
|
||
(if (boundp 'subword-mode)
|
||
(if subword-mode
|
||
(subword-mode nil)
|
||
(subword-mode t)))
|
||
|
||
;; 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 [(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)
|
||
|
||
;; 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-identifier-ops
|
||
php '(
|
||
(left-assoc "\\" "::" "->")
|
||
(prefix "\\" "::")))
|
||
|
||
;; 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
|
||
;; falls back to java, so no need to specify the language
|
||
php (append (remove ">>>=" (c-lang-const c-assignment-operators))
|
||
'(".=")))
|
||
|
||
(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"))
|
||
|
||
(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" "native" "static" "strictfp"
|
||
"synchronized" "transient" "volatile"))
|
||
|
||
(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" "use" "implements" "extends" "namespace" "instanceof" "insteadof"))
|
||
|
||
(c-lang-defconst c-ref-list-kwds
|
||
php nil)
|
||
|
||
(c-lang-defconst c-block-stmt-2-kwds
|
||
php (append '("elseif" "foreach" "declare")
|
||
(remove "synchronized" (c-lang-const c-block-stmt-2-kwds))))
|
||
|
||
(c-lang-defconst c-simple-stmt-kwds
|
||
php (append '("include" "include_once" "require" "require_once"
|
||
"echo" "print" "die" "exit")
|
||
(c-lang-const c-simple-stmt-kwds)))
|
||
|
||
(c-lang-defconst c-constant-kwds
|
||
php '("true"
|
||
"false"
|
||
"null"))
|
||
|
||
(c-lang-defconst c-lambda-kwds
|
||
php '("function"
|
||
"use"))
|
||
|
||
(c-lang-defconst c-other-kwds
|
||
"Keywords not accounted for by any other `*-kwds' language constant."
|
||
php '(
|
||
"__halt_compiler"
|
||
"and"
|
||
"array"
|
||
"callable"
|
||
"as"
|
||
"break"
|
||
"catch all"
|
||
"catch"
|
||
"clone"
|
||
"default"
|
||
"empty"
|
||
"enddeclare"
|
||
"endfor"
|
||
"endforeach"
|
||
"endif"
|
||
"endswitch"
|
||
"endwhile"
|
||
"eval"
|
||
"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-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))))
|
||
|
||
(defun php-lineup-cascaded-calls (langelem)
|
||
"Line up chained methods using `c-lineup-cascaded-calls',
|
||
but only if the setting is enabled"
|
||
(if php-lineup-cascaded-calls
|
||
(c-lineup-cascaded-calls langelem)))
|
||
|
||
(c-add-style
|
||
"php"
|
||
'((c-basic-offset . 4)
|
||
(c-doc-comment-style . javadoc)
|
||
(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 c-lineup-arglist))
|
||
(arglist-intro . php-lineup-arglist-intro)
|
||
(case-label . +)
|
||
(class-open . -)
|
||
(comment-intro . 0)
|
||
(inlambda . 0)
|
||
(lambda-intro-cont . +)
|
||
(inline-open . 0)
|
||
(label . +)
|
||
(statement-cont . (first php-lineup-cascaded-calls php-lineup-string-cont +))
|
||
(substatement-open . 0)
|
||
(topmost-intro-cont . (first php-lineup-cascaded-calls +))))))
|
||
|
||
(defun php-enable-default-coding-style ()
|
||
"Set PHP Mode to use reasonable default formatting."
|
||
(interactive)
|
||
(c-set-style "php"))
|
||
|
||
(c-add-style
|
||
"pear"
|
||
'("php"
|
||
(c-basic-offset . 4)
|
||
(c-offsets-alist . ((case-label . 0)))))
|
||
|
||
(defun php-enable-pear-coding-style ()
|
||
"Sets up php-mode to use the coding styles preferred for PEAR
|
||
code and modules."
|
||
(interactive)
|
||
(setq tab-width 4
|
||
indent-tabs-mode nil)
|
||
(c-set-style "pear")
|
||
|
||
;; Undo drupal/PSR-2 coding style whitespace effects
|
||
(set (make-local-variable 'show-trailing-whitespace)
|
||
(default-value 'show-trailing-whitespace)))
|
||
|
||
(c-add-style
|
||
"drupal"
|
||
'("php"
|
||
(c-basic-offset . 2)))
|
||
|
||
(defun php-enable-drupal-coding-style ()
|
||
"Makes php-mode use coding styles that are preferable for
|
||
working with Drupal."
|
||
(interactive)
|
||
(setq tab-width 2
|
||
indent-tabs-mode nil
|
||
fill-column 78)
|
||
(set (make-local-variable 'show-trailing-whitespace) t)
|
||
(add-hook 'before-save-hook 'delete-trailing-whitespace nil t)
|
||
(c-set-style "drupal"))
|
||
|
||
(c-add-style
|
||
"wordpress"
|
||
'("php"
|
||
(c-basic-offset . 4)))
|
||
|
||
(defun php-enable-wordpress-coding-style ()
|
||
"Makes php-mode use coding styles that are preferable for
|
||
working with Wordpress."
|
||
(interactive)
|
||
(setq indent-tabs-mode t
|
||
fill-column 78
|
||
tab-width 4
|
||
c-indent-comments-syntactically-p t)
|
||
(c-set-style "wordpress")
|
||
|
||
;; Undo drupal/PSR-2 coding style whitespace effects
|
||
(set (make-local-variable 'show-trailing-whitespace)
|
||
(default-value 'show-trailing-whitespace)))
|
||
|
||
(c-add-style
|
||
"symfony2"
|
||
'("php"
|
||
(c-offsets-alist . ((statement-cont . php-lineup-hanging-semicolon)))))
|
||
|
||
(defun php-enable-symfony2-coding-style ()
|
||
"Makes php-mode use coding styles that are preferable for
|
||
working with Symfony2."
|
||
(interactive)
|
||
(setq indent-tabs-mode nil
|
||
fill-column 78
|
||
c-indent-comments-syntactically-p t
|
||
require-final-newline t)
|
||
(c-set-style "symfony2")
|
||
|
||
;; Undo drupal/PSR-2 coding style whitespace effects
|
||
(set (make-local-variable 'show-trailing-whitespace)
|
||
(default-value 'show-trailing-whitespace)))
|
||
|
||
(c-add-style
|
||
"psr2"
|
||
'("php"
|
||
(c-offsets-alist . ((statement-cont . +)))))
|
||
|
||
(defun php-enable-psr2-coding-style ()
|
||
"Makes php-mode comply to the PSR-2 coding style"
|
||
(interactive)
|
||
(setq indent-tabs-mode nil
|
||
fill-column 78
|
||
c-indent-comments-syntactically-p t
|
||
require-final-newline t)
|
||
(c-set-style "psr2")
|
||
|
||
;; Apply drupal-like coding style whitespace effects
|
||
(set (make-local-variable 'require-final-newline) t)
|
||
(set (make-local-variable 'show-trailing-whitespace) t)
|
||
(add-hook 'before-save-hook 'delete-trailing-whitespace nil t))
|
||
|
||
(defconst php-beginning-of-defun-regexp
|
||
"^\\s-*\\(?:\\(?:abstract\\|final\\|private\\|protected\\|public\\|static\\)\\s-+\\)*function\\s-+&?\\(\\(?:\\sw\\|\\s_\\)+\\)\\s-*("
|
||
"Regular expression for a PHP function.")
|
||
|
||
(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 ((arg (or arg 1)))
|
||
(while (> arg 0)
|
||
(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))
|
||
(re-search-forward php-beginning-of-defun-regexp
|
||
nil 'noerror))
|
||
(setq arg (1+ arg))))))
|
||
|
||
(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
|
||
(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)
|
||
(if (or (not php-mode-warn-if-mumamo-off)
|
||
php-warned-bad-indent
|
||
(php-check-html-for-indentation))
|
||
(funcall 'c-indent-region start end quiet)))
|
||
|
||
(defun php-cautious-indent-line ()
|
||
(if (or (not php-mode-warn-if-mumamo-off)
|
||
php-warned-bad-indent
|
||
(php-check-html-for-indentation))
|
||
(let ((here (point))
|
||
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 region border), 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 (not php-template-compatibility)
|
||
nil
|
||
(setq pos (or pos (point)))
|
||
(let ((here (point))
|
||
ret)
|
||
(save-match-data
|
||
(goto-char pos)
|
||
(beginning-of-line)
|
||
(setq ret (looking-at
|
||
(rx
|
||
(or (seq
|
||
bol
|
||
(0+ space)
|
||
"<"
|
||
(in "a-z\\?"))
|
||
(seq
|
||
(0+ not-newline)
|
||
(in "a-z\\?")
|
||
">"
|
||
(0+ space)
|
||
eol))))))
|
||
(goto-char here)
|
||
ret)))
|
||
|
||
(defun php-c-vsemi-status-unknown-p ()
|
||
"See `php-c-at-vsemi-p'."
|
||
)
|
||
|
||
(defsubst php-in-string-p ()
|
||
(nth 3 (syntax-ppss)))
|
||
|
||
(defsubst php-in-comment-p ()
|
||
(nth 4 (syntax-ppss)))
|
||
|
||
(defsubst php-in-string-or-comment-p ()
|
||
(nth 8 (syntax-ppss)))
|
||
|
||
(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 '+)))
|
||
|
||
(defconst php-heredoc-start-re
|
||
"<<<\\(?:\\w+\\|'\\w+'\\)$"
|
||
"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 "\\w+" heredoc-start)
|
||
(concat "^\\(" (match-string 0 heredoc-start) "\\)\\W"))
|
||
|
||
(defun php-syntax-propertize-function (start end)
|
||
"Apply propertize rules from START to END."
|
||
;; (defconst php-syntax-propertize-function
|
||
;; (syntax-propertize-rules
|
||
;; (php-heredoc-start-re (0 (ignore (php-heredoc-syntax))))))
|
||
(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 "|")))
|
||
|
||
(defun php-syntax-propertize-extend-region (start end)
|
||
"Extend the propertize region if START or END falls inside a
|
||
PHP heredoc."
|
||
(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)))))
|
||
|
||
(easy-menu-define php-mode-menu php-mode-map "PHP Mode Commands"
|
||
(cons "PHP" (c-lang-const c-mode-menu php)))
|
||
|
||
;;;###autoload
|
||
(define-derived-mode php-mode c-mode "PHP"
|
||
"Major mode for editing PHP code.
|
||
|
||
\\{php-mode-map}"
|
||
|
||
(c-initialize-cc-mode t)
|
||
(c-init-language-vars php-mode)
|
||
(c-common-init 'php-mode)
|
||
|
||
(modify-syntax-entry ?_ "_" php-mode-syntax-table)
|
||
(modify-syntax-entry ?` "\"" php-mode-syntax-table)
|
||
(modify-syntax-entry ?\" "\"" php-mode-syntax-table)
|
||
(modify-syntax-entry ?# "< b" php-mode-syntax-table)
|
||
(modify-syntax-entry ?\n "> b" php-mode-syntax-table)
|
||
|
||
(set (make-local-variable 'syntax-propertize-via-font-lock)
|
||
'(("\\(\"\\)\\(\\\\.\\|[^\"\n\\]\\)*\\(\"\\)" (1 "\"") (3 "\""))
|
||
("\\(\'\\)\\(\\\\.\\|[^\'\n\\]\\)*\\(\'\\)" (1 "\"") (3 "\""))))
|
||
|
||
(when (boundp 'syntax-propertize-function)
|
||
(add-to-list (make-local-variable 'syntax-propertize-extend-region-functions)
|
||
#'php-syntax-propertize-extend-region)
|
||
(set (make-local-variable 'syntax-propertize-function)
|
||
#'php-syntax-propertize-function))
|
||
|
||
(setq imenu-generic-expression php-imenu-generic-expression)
|
||
|
||
;; PHP vars are case-sensitive
|
||
(setq case-fold-search t)
|
||
|
||
;; Do not force newline at end of file. Such newlines can cause
|
||
;; trouble if the PHP file is included in another file before calls
|
||
;; to header() or cookie().
|
||
(set (make-local-variable 'require-final-newline) nil)
|
||
(set (make-local-variable 'next-line-add-newlines) nil)
|
||
|
||
;; PEAR coding standards
|
||
(add-hook 'php-mode-pear-hook 'php-enable-pear-coding-style
|
||
nil t)
|
||
|
||
;; ;; Drupal coding standards
|
||
(add-hook 'php-mode-drupal-hook 'php-enable-drupal-coding-style
|
||
nil t)
|
||
|
||
;; ;; WordPress coding standards
|
||
(add-hook 'php-mode-wordpress-hook 'php-enable-wordpress-coding-style
|
||
nil t)
|
||
|
||
;; ;; Symfony2 coding standards
|
||
(add-hook 'php-mode-symfony2-hook 'php-enable-symfony2-coding-style
|
||
nil t)
|
||
|
||
;; ;; PSR-2 coding standards
|
||
(add-hook 'php-mode-psr2-hook 'php-enable-psr2-coding-style
|
||
nil t)
|
||
|
||
(cond ((eq php-mode-coding-style 'pear)
|
||
(php-enable-pear-coding-style)
|
||
(run-hooks 'php-mode-pear-hook))
|
||
((eq php-mode-coding-style 'drupal)
|
||
(php-enable-drupal-coding-style)
|
||
(run-hooks 'php-mode-drupal-hook))
|
||
((eq php-mode-coding-style 'wordpress)
|
||
(php-enable-wordpress-coding-style)
|
||
(run-hooks 'php-mode-wordpress-hook))
|
||
((eq php-mode-coding-style 'symfony2)
|
||
(php-enable-symfony2-coding-style)
|
||
(run-hooks 'php-mode-symfony2-hook))
|
||
((eq php-mode-coding-style 'psr2)
|
||
(php-enable-psr2-coding-style)
|
||
(run-hooks 'php-mode-psr2-hook)))
|
||
|
||
(if (or php-mode-force-pear
|
||
(and (stringp buffer-file-name)
|
||
(string-match "PEAR\\|pear"
|
||
(buffer-file-name))
|
||
(string-match "\\.php$" (buffer-file-name))))
|
||
(run-hooks 'php-mode-pear-hook))
|
||
|
||
(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 'php-c-vsemi-status-unknown-p)
|
||
|
||
(set (make-local-variable 'syntax-begin-function)
|
||
'c-beginning-of-syntax)
|
||
|
||
;; 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.
|
||
(set (make-local-variable 'beginning-of-defun-function)
|
||
'php-beginning-of-defun)
|
||
(set (make-local-variable 'end-of-defun-function)
|
||
'php-end-of-defun)
|
||
|
||
(set (make-local-variable 'open-paren-in-column-0-is-defun-start)
|
||
nil)
|
||
(set (make-local-variable 'defun-prompt-regexp)
|
||
"^\\s-*function\\s-+&?\\s-*\\(\\(\\sw\\|\\s_\\)+\\)\\s-*")
|
||
(set (make-local-variable 'add-log-current-defun-header-regexp)
|
||
php-beginning-of-defun-regexp))
|
||
|
||
;; 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
|
||
(message "Making completion list...")
|
||
(with-output-to-temp-buffer "*Completions*"
|
||
(display-completion-list
|
||
(all-completions pattern php-functions)))
|
||
(message "Making completion list...%s" "done")))))))
|
||
|
||
(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))
|
||
(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.
|
||
(mapatoms (lambda (sym) (intern (symbol-name sym) php-table))
|
||
tags-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)
|
||
(let ((table (make-vector 1022 0))
|
||
(files (directory-files
|
||
path
|
||
nil
|
||
"^function\\..+\\.html$")))
|
||
(mapc (lambda (file)
|
||
(string-match "\\.\\([-a-zA-Z_0-9]+\\)\\.html$" file)
|
||
(intern
|
||
(replace-regexp-in-string
|
||
"-" "_" (substring file (match-beginning 1) (match-end 1)) t)
|
||
table))
|
||
files)
|
||
table))
|
||
|
||
;; 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 ()
|
||
(interactive)
|
||
(let* ((tagname (php-get-pattern))
|
||
(buf (find-tag-noselect tagname nil nil))
|
||
arglist)
|
||
(with-current-buffer buf
|
||
(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'."
|
||
:type '(choice (const :tag "default" nil) function)
|
||
:link '(variable-link browse-url-browser-function)
|
||
:group 'php)
|
||
|
||
(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
|
||
(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)
|
||
(php-browse-documentation-url (concat php-search-url
|
||
(replace-regexp-in-string "_" "-" (downcase 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 php-manual-url))
|
||
|
||
(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-mode patterns *before* cc-mode:
|
||
;; only add patterns here if you want to prevent cc-mode from applying
|
||
;; a different face.
|
||
'(
|
||
;; Highlight variables, e.g. 'var' in '$var' and '$obj->var', but
|
||
;; not in $obj->var()
|
||
("->\\(\\sw+\\)\\s-*(" 1 'default)
|
||
|
||
;; Highlight special variables
|
||
("\\$\\(this\\|that\\)" 1 font-lock-constant-face)
|
||
("\\(\\$\\|->\\)\\([a-zA-Z0-9_]+\\)" 2 font-lock-variable-name-face)
|
||
|
||
;; Highlight function/method names
|
||
("\\<function\\s-+&?\\(\\sw+\\)\\s-*(" 1 font-lock-function-name-face)
|
||
|
||
;; The dollar sign should not get a variable-name face, below
|
||
;; pattern resets the face to default in case cc-mode sets the
|
||
;; variable-name face (cc-mode does this for variables prefixed
|
||
;; with type, like in arglist)
|
||
("\\(\\$\\)\\(\\sw+\\)" 1 'default)
|
||
|
||
;; Array is a keyword, except when used as cast, so that (int)
|
||
;; and (array) look the same
|
||
("(\\(array\\))" 1 font-lock-type-face)
|
||
|
||
;; Support the ::class constant in PHP5.6
|
||
("\\sw+::\\(class\\)" 1 font-lock-constant-face))
|
||
|
||
;; 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.
|
||
`(
|
||
;; Highlight variables, e.g. 'var' in '$var' and '$obj->var', but
|
||
;; not in $obj->var()
|
||
("->\\(\\sw+\\)\\s-*(" 1 'default)
|
||
|
||
("\\(\\$\\|->\\)\\([a-zA-Z0-9_]+\\)" 2 font-lock-variable-name-face)
|
||
|
||
;; Highlight all upper-cased symbols as constant
|
||
("\\<\\([A-Z_][A-Z0-9_]+\\)\\>" 1 font-lock-constant-face)
|
||
|
||
;; 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 font-lock-constant-face)
|
||
|
||
;; 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 return types in functions and methods.
|
||
("function.+:\\s-?\\(\\sw+\\)" 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 '("?>" "<?" "<%" "%>")) 0 font-lock-preprocessor-face)))
|
||
"Detailed highlighting for PHP mode.")
|
||
|
||
(defvar php-font-lock-keywords php-font-lock-keywords-3
|
||
"Default expressions to highlight in PHP mode.")
|
||
|
||
;;; Provide support for Flymake so that users can see warnings and
|
||
;;; errors in real-time as they write code.
|
||
|
||
(defun flymake-php-init ()
|
||
(let* ((temp-file (flymake-init-create-temp-buffer-copy
|
||
'flymake-create-temp-inplace))
|
||
(local-file (file-relative-name
|
||
temp-file
|
||
(file-name-directory buffer-file-name))))
|
||
(list php-executable (list "-f" local-file "-l"))))
|
||
|
||
(add-to-list 'flymake-allowed-file-name-masks
|
||
'("\\.php[345s]?$"
|
||
flymake-php-init
|
||
flymake-simple-cleanup
|
||
flymake-get-real-file-name))
|
||
|
||
(add-to-list 'flymake-err-line-patterns
|
||
'("\\(Parse\\|Fatal\\) error: \\(.*?\\) in \\(.*?\\) on line \\([0-9]+\\)" 3 4 nil 2))
|
||
|
||
|
||
(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))))
|
||
|
||
|
||
(defface php-annotations-annotation-face '((t . (:inherit font-lock-constant-face)))
|
||
"Face used to highlight annotations.")
|
||
|
||
(defconst php-annotations-re "\\(\\s-\\|{\\)\\(@[[:alpha:]]+\\)")
|
||
|
||
(defmacro php-annotations-inside-comment-p (pos)
|
||
"Return non-nil if POS is inside a comment."
|
||
`(or (eq (get-char-property ,pos 'face) 'font-lock-comment-face)
|
||
(eq (get-char-property ,pos 'face) 'font-lock-comment-delimiter-face)))
|
||
|
||
(defun php-annotations-font-lock-find-annotation (limit)
|
||
(let ((match
|
||
(catch 'match
|
||
(save-match-data
|
||
(while (re-search-forward php-annotations-re limit t)
|
||
(when (php-annotations-inside-comment-p (match-beginning 0))
|
||
(goto-char (match-end 0))
|
||
(throw 'match (match-data))))))))
|
||
(when match
|
||
(set-match-data match)
|
||
t)))
|
||
|
||
(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 'font-lock-variable-name-face))))
|
||
nil)
|
||
|
||
(eval-after-load 'php-mode
|
||
'(progn
|
||
(font-lock-add-keywords
|
||
'php-mode
|
||
'((php-annotations-font-lock-find-annotation (2 'php-annotations-annotation-face t))))
|
||
(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)
|
||
|
||
;; Advice `font-lock-fontify-keywords-region' to support namespace
|
||
;; separators in class names. Use word syntax for backslashes when
|
||
;; doing keyword fontification, but not when doing syntactic
|
||
;; fontification because that breaks \ as escape character in strings.
|
||
;;
|
||
;; Special care is taken to restore the original syntax, because we
|
||
;; want \ not to be word for functions like forward-word.
|
||
(defadvice font-lock-fontify-keywords-region (around backslash-as-word activate)
|
||
"Fontify keywords with backslash as word character"
|
||
(let ((old-syntax (string (char-syntax ?\\))))
|
||
(modify-syntax-entry ?\\ "w")
|
||
ad-do-it
|
||
(modify-syntax-entry ?\\ old-syntax)))
|
||
|
||
|
||
;;;###autoload
|
||
(dolist (pattern '("\\.php[s345t]?\\'" "\\.phtml\\'" "Amkfile" "\\.amk$"))
|
||
(add-to-list 'auto-mode-alist `(,pattern . php-mode) t))
|
||
|
||
(provide 'php-mode)
|
||
|
||
;;; php-mode.el ends here
|
||
|
||
;; Local Variables:
|
||
;; firestarter: ert-run-tests-interactively
|
||
;; End:
|