diff --git a/lisp/php.el b/lisp/php.el index 2e072376..b02fb41a 100644 --- a/lisp/php.el +++ b/lisp/php.el @@ -180,6 +180,20 @@ a completion list." :tag "PHP Blade Template Major Mode" :type 'function) +(defcustom php-blade-template-major-mode-fallback '(mhtml-mode html-mode) + "Major modes to fall back on for a Blade template. + +Used when `php-blade-template-major-mode' — `web-mode' by default — is +not installed. The first entry whose function is defined wins; when +none is, `php-default-major-mode' is used. + +A Blade template is mostly HTML plus Blade's own directives, so an HTML +mode reads it far better than `php-mode' does. Set this to nil to opt +out and get `php-default-major-mode' instead." + :group 'php + :tag "PHP Blade Template Major Mode Fallback" + :type '(repeat function)) + (defcustom php-template-mode-alist `(("\\.blade" . ,php-blade-template-major-mode) ("\\.phpt\\'" . ,(if (fboundp 'phpt-mode) 'phpt-mode php-default-major-mode)) @@ -623,6 +637,11 @@ indentation." (setq php--buffer-has-html-tag-cache (cons tick result)) result)))) +(defun php--blade-template-fallback-mode () + "Return the first available mode of `php-blade-template-major-mode-fallback'." + (cl-loop for mode in php-blade-template-major-mode-fallback + thereis (and (fboundp mode) mode))) + (defun php-derivation-major-mode () "Return major mode for PHP file by file-name and its content." (let ((mode (assoc-default buffer-file-name @@ -638,10 +657,19 @@ indentation." (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))) + ;; A Blade template is not PHP, so `php-default-major-mode' cannot + ;; read it at all; degrade to an HTML mode instead. Every other + ;; template is PHP with HTML in it, and keeps falling back on + ;; `php-default-major-mode' below. + (setq mode (when (string-match-p "\\.blade\\." buffer-file-name) + (let ((fallback (php--blade-template-fallback-mode))) + (warn "`%s' is not available for this Blade template; %s. +Install the `web-mode' package for full Blade support." + mode + (if fallback + (format "using `%s' instead" fallback) + (format "falling back on `%s'" php-default-major-mode))) + fallback)))) (or mode php-default-major-mode))) ;;;###autoload diff --git a/tests/php-mode-test.el b/tests/php-mode-test.el index da662856..09a33754 100644 --- a/tests/php-mode-test.el +++ b/tests/php-mode-test.el @@ -734,6 +734,55 @@ those going through `php-mode-maybe' did not: the same buffer became (set-buffer-modified-p nil) (setq buffer-file-name nil)))))) +(defun php-mode-test--derive (file-name &rest body-text) + "Return the mode `php-derivation-major-mode' picks for FILE-NAME. +BODY-TEXT is inserted into the buffer first." + (with-temp-buffer + (setq buffer-file-name (expand-file-name file-name temporary-file-directory)) + (unwind-protect + (progn + (apply #'insert body-text) + (php-derivation-major-mode)) + (set-buffer-modified-p nil) + (setq buffer-file-name nil)))) + +(ert-deftest php-mode-test-blade-template-fallback () + "A Blade template degrades to an HTML mode when `web-mode' is missing. + +A `.blade.php' file is mostly HTML plus Blade directives, so it is not +PHP and `php-default-major-mode' cannot read it. Before the fallback +existed, `php-derivation-major-mode' returned the unavailable +`web-mode' anyway and `php-mode-maybe' then failed with +`void-function'." + (let ((php-blade-template-major-mode 'php-mode-test--absent-web-mode) + (php-template-mode-alist '(("\\.blade" . php-mode-test--absent-web-mode)))) + (should-not (fboundp 'php-mode-test--absent-web-mode)) + ;; The chosen fallback must be usable: this is what `php-mode-maybe' + ;; funcalls, so an unavailable mode would signal `void-function'. + (let ((php-blade-template-major-mode-fallback '(html-mode))) + (should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n")))) + ;; Unavailable entries are skipped. + (let ((php-blade-template-major-mode-fallback '(php-mode-test--absent-web-mode html-mode))) + (should (eq 'html-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n")))) + ;; Opting out falls back on `php-default-major-mode'. + (let ((php-blade-template-major-mode-fallback nil) + (php-default-major-mode 'php-mode)) + (should (eq 'php-mode (php-mode-test--derive "welcome.blade.php" "@extends('x')\n")))))) + +(ert-deftest php-mode-test-html-template-fallback-unchanged () + "A missing `php-html-template-major-mode' still derives to PHP. + +Only Blade degrades to an HTML mode. `php-project-php-file-as-template' +defaults to `auto', so any .php file holding an HTML tag reaches this +path; sending those to an HTML mode would take most PHP files away from +`php-mode' for anyone without `web-mode'." + (let ((php-html-template-major-mode 'php-mode-test--absent-web-mode) + (php-project-php-file-as-template 'auto) + (php-default-major-mode 'php-mode) + (php-template-mode-alist nil)) + (should-not (fboundp 'php-mode-test--absent-web-mode)) + (should (eq 'php-mode (php-mode-test--derive "page.php" "