diff --git a/php-mode-test.el b/php-mode-test.el index 2c0b5015..1a9177d6 100644 --- a/php-mode-test.el +++ b/php-mode-test.el @@ -271,7 +271,8 @@ an error." style from Drupal." (dolist (mode '(pear wordpress symfony2)) ;; the file written to has no significance, only the buffer - (let ((tmp-filename (concat (make-temp-name temporary-file-directory) ".php"))) + (let ((tmp-filename (concat (make-temp-name temporary-file-directory) ".php")) + (auto-mode-alist '(("\\.php\\'" . php-mode)))) (with-php-mode-test ("issue-53.php") (search-forward "return $this->bar;") (should (equal (list "before-write-file" mode nil) diff --git a/php-mode.el b/php-mode.el index 8485d97d..09d762b7 100644 --- a/php-mode.el +++ b/php-mode.el @@ -1600,23 +1600,9 @@ The output will appear in the buffer *PHP*." (ad-activate 'fixup-whitespace) ;;;###autoload -(add-to-list 'auto-mode-alist - (cons - (eval-when-compile - (rx (or - ;; File name extensions (ex. "*.php", "*.phtml") - (: "." - (or (: "php" (? (in "s345t"))) - "amk" - "phtml")) - ;; Full file names (ex. "/Makefile", "/Amkfile") - (: "/" - (or "Amkfile" - ".php_cs" - ".php_cs.dist"))) - string-end)) - 'php-mode) - t) +(progn + (add-to-list 'auto-mode-alist '("/\\.php_cs\\(?:\\.dist\\)?\\'" . php-mode)) + (add-to-list 'auto-mode-alist '("\\.\\(?:php[s345]?\\|phtml\\)\\'" . php-mode-maybe))) (provide 'php-mode) ;;; php-mode.el ends here diff --git a/php-project.el b/php-project.el index 11221d35..7ab003e2 100644 --- a/php-project.el +++ b/php-project.el @@ -102,85 +102,90 @@ STRING If the string is an actual directory path, it is set as the absolute path of the root directory, not the marker.") (put 'php-project-root 'safe-local-variable - #'(lambda (v) (or (stringp v) (assq v php-project-available-root-files))))) + #'(lambda (v) (or (stringp v) (assq v php-project-available-root-files)))) -;;;###autoload -(progn (defvar-local php-project-bootstrap-scripts nil "List of path to bootstrap php script file. The ideal bootstrap file is silent, it only includes dependent files, defines constants, and sets the class loaders.") - (put 'php-project-bootstrap-scripts 'safe-local-variable #'php-project--eval-bootstrap-scripts)) + (put 'php-project-bootstrap-scripts 'safe-local-variable #'php-project--eval-bootstrap-scripts) -;;;###autoload -(progn (defvar-local php-project-php-executable nil "Path to php executable file.") (put 'php-project-php-executable 'safe-local-variable - #'(lambda (v) (and (stringp v) (file-executable-p v))))) + #'(lambda (v) (and (stringp v) (file-executable-p v)))) -;;;###autoload -(progn (defvar-local php-project-phan-executable nil "Path to phan executable file.") - (put 'php-project-phan-executable 'safe-local-variable #'php-project--eval-bootstrap-scripts)) + (put 'php-project-phan-executable 'safe-local-variable #'php-project--eval-bootstrap-scripts) -;;;###autoload -(progn (defvar-local php-project-coding-style nil "Symbol value of the coding style of the project that PHP major mode refers to. Typically it is `pear', `drupal', `wordpress', `symfony2' and `psr2'.") - (put 'php-project-coding-style 'safe-local-variable #'symbolp)) + (put 'php-project-coding-style 'safe-local-variable #'symbolp) -;;;###autoload -(progn - (defvar php-project-repl nil + (defvar-local php-project-php-file-as-template 'auto + " +`auto' (default) + Automatically switch to mode for template when HTML tag detected in file. + +`t' + Switch all PHP files in that directory to mode for HTML template. + +`nil' + Any .php in that directory is just a PHP script. + +\(\(PATTERN . SYMBOL)) + Alist of file name pattern regular expressions and the above symbol pairs. + PATTERN is regexp pattern. +") + (put 'php-project-php-file-as-template 'safe-local-variable #'php-project--validate-php-file-as-template) + + (defvar-local php-project-repl nil "Function name or path to REPL (interactive shell) script.") - (make-variable-buffer-local 'php-project-repl) (put 'php-project-repl 'safe-local-variable #'(lambda (v) (or (functionp v) - (php-project--eval-bootstrap-scripts v))))) + (php-project--eval-bootstrap-scripts v)))) -;;;###autoload -(progn - (defvar php-project-unit-test nil + (defvar-local php-project-unit-test nil "Function name or path to unit test script.") - (make-variable-buffer-local 'php-project-unit-test) (put 'php-project-unit-test 'safe-local-variable #'(lambda (v) (or (functionp v) - (php-project--eval-bootstrap-scripts v))))) + (php-project--eval-bootstrap-scripts v)))) -;;;###autoload -(progn - (defvar php-project-deploy nil + (defvar-local php-project-deploy nil "Function name or path to deploy script.") - (make-variable-buffer-local 'php-project-deploy) (put 'php-project-deploy 'safe-local-variable #'(lambda (v) (or (functionp v) - (php-project--eval-bootstrap-scripts v))))) + (php-project--eval-bootstrap-scripts v)))) -;;;###autoload -(progn - (defvar php-project-build nil + (defvar-local php-project-build nil "Function name or path to build script.") - (make-variable-buffer-local 'php-project-build) (put 'php-project-build 'safe-local-variable #'(lambda (v) (or (functionp v) - (php-project--eval-bootstrap-scripts v))))) + (php-project--eval-bootstrap-scripts v)))) -;;;###autoload -(progn - (defvar php-project-server-start nil + (defvar-local php-project-server-start nil "Function name or path to server-start script.") - (make-variable-buffer-local 'php-project-server-start) (put 'php-project-server-start 'safe-local-variable #'(lambda (v) (or (functionp v) (php-project--eval-bootstrap-scripts v))))) ;; Functions +(defun php-project--validate-php-file-as-template (val) + "Return T when `VAL' is valid list of safe ." + (cond + ((null val) t) + ((memq val '(t auto)) t) + ((listp val) + (cl-loop for v in val + always (and (consp v) + (stringp (car v)) + (php-project--validate-php-file-as-template (cdr v))))) + (t nil))) (defun php-project--eval-bootstrap-scripts (val) "Return T when `VAL' is valid list of safe bootstrap php script." @@ -213,6 +218,17 @@ Typically it is `pear', `drupal', `wordpress', `symfony2' and `psr2'.") (cons 'root "vendor/bin/phan")))) (executable-find "phan"))) +(defun php-project-get-file-html-template-type (filename) + "Return symbol T, NIL or `auto' by `FILENAME'." + (cond + ((not php-project-php-file-as-template) nil) + ((eq t php-project-php-file-as-template) t) + ((eq 'auto php-project-php-file-as-template) 'auto) + ((listp php-project-php-file-as-template) + (assoc-default filename php-project-php-file-as-template #'string-match-p)) + (t (prog1 nil + (warn "php-project-php-file-as-template is unexpected format"))))) + ;;;###autoload (defun php-project-get-bootstrap-scripts () "Return list of bootstrap script." diff --git a/php.el b/php.el index febf70db..13f2cef1 100644 --- a/php.el +++ b/php.el @@ -29,6 +29,7 @@ ;;; Code: (require 'flymake) +(require 'php-project) ;;;###autoload (defgroup php nil @@ -85,6 +86,41 @@ You can replace \"en\" with your ISO language code." "Suffix for inserted namespace." :group 'php :type 'string) + +(defcustom php-default-major-mode 'php-mode + "Major mode for editing PHP script." + :group 'php + :tag "PHP Default Major Mode" + :type 'function) + +(defcustom php-html-template-major-mode 'web-mode + "Major mode for editing PHP-HTML template." + :group 'php + :tag "PHP-HTML Template Major Mode" + :type 'function) + +(defcustom php-blade-template-major-mode 'web-mode + "Major mode for editing Blade template." + :group 'php + :tag "PHP Blade Template Major Mode" + :type 'function) + +(defcustom php-template-mode-alist + `(("\\.blade" . ,php-blade-template-major-mode) + ("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode php-html-template-major-mode)) + ("\\.phtml\\'" . ,php-html-template-major-mode)) + "Automatically use another MAJOR-MODE when open template file." + :group 'php + :tag "PHP Template Mode Alist" + :type '(alist :key-type regexp :value-type function) + :link '(url-link :tag "web-mode" "http://web-mode.org/") + :link '(url-link :tag "phpt-mode" "https://github.com/emacs-php/phpt-mode")) + +(defcustom php-mode-maybe-hook nil + "List of functions to be executed on entry to `php-mode-maybe'." + :group 'php + :tag "PHP Mode Maybe Hook" + :type 'hook) ;;; PHP Keywords (defconst php-magical-constants @@ -199,6 +235,54 @@ Look at the `php-executable' variable instead of the constant \"php\" command." 'flymake-php-init))))) (list php-executable (cdr init)))) +(defconst php-re-detect-html-tag + (eval-when-compile + (rx (or (: string-start (* (in space)) + "")) + (: "<" (* (in space)) (+ (in alpha "-")) (* (in space)) ">")))))) + +(defun php-buffer-has-html-tag () + "Return position of HTML tag or NIL in current buffer." + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (re-search-forward php-re-detect-html-tag nil t)))) + +(defun php-derivation-major-mode () + "Return major mode for PHP file by file-name and its content." + (let ((mode (assoc-default buffer-file-name + php-template-mode-alist + #'string-match-p)) + type) + (when (and (null mode) buffer-file-name + php-project-php-file-as-template) + (setq type (php-project-get-file-html-template-type buffer-file-name)) + (cond + ((eq t type) (setq mode php-html-template-major-mode)) + ((eq 'auto type) + (when (php-buffer-has-html-tag) + (setq mode php-html-template-major-mode))))) + (when (and mode (not (fboundp mode))) + (if (string-match-p "\\.blade\\." buffer-file-name) + (warn "php-mode is NOT support blade template. %s" + "Please install `web-mode' package") + (setq mode nil))) + (or mode php-default-major-mode))) + +;;;###autoload +(defun php-mode-maybe () + "Select PHP mode or other major mode." + (interactive) + (run-hooks php-mode-maybe-hook) + (funcall (php-derivation-major-mode))) + ;;;###autoload (defun php-current-class () "Insert current class name if cursor in class context."