239 lines
8.9 KiB
EmacsLisp
239 lines
8.9 KiB
EmacsLisp
;;; php-ide.el --- IDE-like UI support for PHP development -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 2023 Friends of Emacs-PHP development
|
|
|
|
;; Author: USAMI Kenta <tadsan@zonu.me>
|
|
;; Keywords: tools, files
|
|
;; URL: https://github.com/emacs-php/php-mode
|
|
;; License: GPL-3.0-or-later
|
|
|
|
;; This program is free software; you can redistribute it and/or modify
|
|
;; it under the terms of the GNU General Public License as published by
|
|
;; the Free Software Foundation, either version 3 of the License, or
|
|
;; (at your option) any later version.
|
|
|
|
;; This program is distributed in the hope that it will be useful,
|
|
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
;; GNU General Public License for more details.
|
|
|
|
;; You should have received a copy of the GNU General Public License
|
|
;; along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
;;; Commentary:
|
|
|
|
;; PHP Mode integrates LSP Mode (lsp-mode), Phpactor (phpactor.el) and IDE-like tools.
|
|
;;
|
|
;; **Note**:
|
|
;; This feature is under development and experimental.
|
|
;; All of these functions, modes and terms are subject to change without notice.
|
|
;;
|
|
;; ## Motivations
|
|
;;
|
|
;; There are some IDE-like features / packages for PHP development.
|
|
;; PHP-IDE bridges projects and their IDE-like features.
|
|
;;
|
|
;; ## IDE Features
|
|
;;
|
|
;; We don't recommend features, but bundle some feature bridges.
|
|
;; They are sorted alphabetically except "none."
|
|
;;
|
|
;; - none
|
|
;; Does not launch any IDE features.
|
|
;; - eglot
|
|
;; https://github.com/joaotavora/eglot
|
|
;; - lsp-bridge
|
|
;; https://github.com/manateelazycat/lsp-bridge
|
|
;; - lsp-mode
|
|
;; https://emacs-lsp.github.io/lsp-mode/
|
|
;; https://github.com/emacs-lsp/lsp-mode
|
|
;; - phpactor
|
|
;; https://phpactor.readthedocs.io/
|
|
;; https://github.com/phpactor/phpactor
|
|
;; https://github.com/emacs-php/phpactor.el
|
|
;;
|
|
;; ## Configuration
|
|
;;
|
|
;; Put follows code into your .emacs (~/.emacs.d/init.el) file:
|
|
;;
|
|
;; (defun init-php-mode-setup ()
|
|
;; (add-hook 'hack-local-variables-hook #'php-ide-mode t t))
|
|
;;
|
|
;; (defun init-php-ide-mode-setup (feature activate)
|
|
;; (pcase feature
|
|
;; (`lsp-bridge
|
|
;; (if activate
|
|
;; (progn (yas-minor-mode +1)
|
|
;; (corfu-mode -1))
|
|
;; (yas-minor-mode -1)
|
|
;; (corfu-mode +1)))))
|
|
;;
|
|
;; (with-eval-after-load 'php-ide
|
|
;; (custom-set-variables
|
|
;; '(php-ide-features . 'eglot) ;; and/or 'none, 'phpactor, 'lsp-mode
|
|
;; '(php-ide-eglot-executable "psalm-language-server") ;; or "intelephense", '("php" "vendor/bin/path/to/server")
|
|
;; ;; If you want to hide php-ide-mode from the mode line, set an empty string
|
|
;; '(php-ide-mode-lighter ""))
|
|
;;
|
|
;; (add-hook 'php-mode-hook #'init-php-mode-setup)
|
|
;; (add-hook 'php-ide-mode-functions #'init-php-ide-mode-setup))
|
|
;;
|
|
;; If you don't enable IDE support by default, set '(php-ide-feature 'none)
|
|
;;
|
|
;; ### For per project configuration
|
|
;;
|
|
;; Put follows code into .dir-locals.el in project directory:
|
|
;;
|
|
;; ((nil (php-project-root . git)
|
|
;; (php-ide-features . (lsp-mode))))
|
|
;;
|
|
;; If you can't put .dir-locals.el in your project directory, consider the sidecar-locals package.
|
|
;; https://melpa.org/#/sidecar-locals
|
|
;; https://codeberg.org/ideasman42/emacs-sidecar-locals
|
|
;;
|
|
|
|
;;; Code:
|
|
(require 'cl-lib)
|
|
(require 'php-project)
|
|
|
|
(eval-when-compile
|
|
(require 'php-ide-phpactor)
|
|
(defvar eglot-server-programs)
|
|
(declare-function lsp-bridge-mode "ext:lsp-bridge" ())
|
|
(declare-function eglot-ensure "ext:eglot" ())
|
|
(declare-function eglot--managed-mode-off "ext:eglot" ())
|
|
(declare-function phpactor--find-executable "ext:phpactor" ()))
|
|
|
|
(defvar php-ide-feature-alist
|
|
'((none :test (lambda () t)
|
|
:activate (lambda () t)
|
|
:deactivate (lambda () t))
|
|
(phpactor :test (lambda () (and (require 'phpactor nil t) (featurep 'phpactor)))
|
|
:activate php-ide-phpactor-activate
|
|
:deactivate php-ide-phpactor-activate)
|
|
(eglot :test (lambda () (and (require 'eglot nil t) (featurep 'eglot)))
|
|
:activate eglot-ensure
|
|
:deactivate eglot--managed-mode-off)
|
|
(lsp-bridge :test (lambda () (and (require 'lsp-bridge nil t) (featurep 'lsp-bridge)))
|
|
:activate (lambda () (lsp-bridge-mode +1))
|
|
:deactivate (lambda () (lsp-bridge-mode -1)))
|
|
(lsp-mode :test (lambda () (and (require 'lsp nil t) (featurep 'lsp)))
|
|
:activate lsp
|
|
:deactivate lsp-workspace-shutdown)))
|
|
|
|
(defvar php-ide-lsp-command-alist
|
|
'((intelephense "intelephense" "--stdio")
|
|
(phpactor . (lambda () (list (if (fboundp 'phpactor--find-executable)
|
|
(phpactor--find-executable)
|
|
"phpactor")
|
|
"language-server")))))
|
|
|
|
(defgroup php-ide nil
|
|
"IDE-like support for PHP developing."
|
|
:tag "PHP-IDE"
|
|
:prefix "php-ide-"
|
|
:group 'php)
|
|
|
|
;;;###autoload
|
|
(defcustom php-ide-features nil
|
|
"A set of PHP-IDE features symbol."
|
|
:tag "PHP-IDE Feature"
|
|
:group 'php-ide
|
|
:type `(set ,@(mapcar (lambda (feature) (list 'const (car feature)))
|
|
php-ide-feature-alist)
|
|
symbol)
|
|
:safe (lambda (v) (cl-loop for feature in (if (listp v) v (list v))
|
|
always (symbolp feature))))
|
|
|
|
;;;###autoload
|
|
(defcustom php-ide-eglot-executable nil
|
|
"Command name or path to the command of Eglot LSP executable."
|
|
:tag "PHP-IDE Eglot Executable"
|
|
:group 'php-ide
|
|
:type '(choice
|
|
(const intelephense)
|
|
(const phpactor)
|
|
string (repeat string))
|
|
:safe (lambda (v) (cond
|
|
((stringp v) (file-exists-p v))
|
|
((listp v) (cl-every #'stringp v))
|
|
((assq v php-ide-lsp-command-alist)))))
|
|
|
|
;;;###autoload
|
|
(defun php-ide-eglot-server-program ()
|
|
"Return a list of command to execute LSP Server."
|
|
(cond
|
|
((stringp php-ide-eglot-executable) (list php-ide-eglot-executable))
|
|
((listp php-ide-eglot-executable) php-ide-eglot-executable)
|
|
((when-let* ((command (assq php-ide-eglot-executable php-ide-lsp-command-alist)))
|
|
(cond
|
|
((functionp command) (funcall command))
|
|
((listp command) command))))))
|
|
|
|
(defcustom php-ide-mode-lighter " PHP-IDE"
|
|
"A symbol of PHP-IDE feature."
|
|
:tag "PHP-IDE Mode Lighter"
|
|
:group 'php-ide
|
|
:type 'string
|
|
:safe #'stringp)
|
|
|
|
;;;###autoload
|
|
(defcustom php-ide-mode-functions nil
|
|
"Hook functions called when before activating or deactivating PHP-IDE.
|
|
Notice that two arguments (FEATURE ACTIVATE) are given.
|
|
|
|
FEATURE: A symbol, like \\='lsp-mode.
|
|
ACTIVATE: T is given when activeting, NIL when deactivating PHP-IDE."
|
|
:tag "PHP-IDE Mode Functions"
|
|
:group 'php-ide
|
|
:type '(repeat function)
|
|
:safe (lambda (functions)
|
|
(and (listp functions) (cl-every #'functionp functions))))
|
|
|
|
;;;###autoload
|
|
(define-minor-mode php-ide-mode
|
|
"Minor mode for integrate IDE-like tools."
|
|
:lighter php-ide-mode-lighter
|
|
(let ((ide-features php-ide-features))
|
|
(when-let* ((unavailable-features (cl-loop for feature in ide-features
|
|
unless (assq feature php-ide-feature-alist)
|
|
collect feature)))
|
|
(user-error "%s includes unavailable PHP-IDE features. (available features are: %s)"
|
|
ide-features
|
|
(mapconcat (lambda (feature) (concat "'" (symbol-name feature)))
|
|
(php-ide--avilable-features) ", ")))
|
|
(cl-loop for feature in ide-features
|
|
for ide-plist = (cdr-safe (assq feature php-ide-feature-alist))
|
|
do (if (null ide-plist)
|
|
(message "Please set `php-ide-feature' variable in .dir-locals.el or custom variable")
|
|
(run-hook-with-args 'php-ide-mode-functions feature php-ide-mode)
|
|
(if php-ide-mode
|
|
(php-ide--activate-buffer feature ide-plist)
|
|
(php-ide--deactivate-buffer ide-plist))))))
|
|
|
|
;;;###autoload
|
|
(defun php-ide-turn-on ()
|
|
"Turn on PHP IDE-FEATURES and execute `php-ide-mode'."
|
|
(unless php-ide-features
|
|
(user-error "No PHP-IDE feature is installed. Install the lsp-mode, lsp-bridge, eglot or phpactor package"))
|
|
(php-ide-mode +1))
|
|
|
|
(defun php-ide--activate-buffer (name ide-plist)
|
|
"Activate php-ide implementation by NAME and IDE-PLIST."
|
|
(unless (funcall (plist-get ide-plist :test))
|
|
(user-error "PHP-IDE feature `%s' is not available" name))
|
|
(funcall (plist-get ide-plist :activate)))
|
|
|
|
(defun php-ide--deactivate-buffer (ide-plist)
|
|
"Deactivate php-ide implementation by IDE-PLIST."
|
|
(funcall (plist-get ide-plist :deactivate)))
|
|
|
|
(defun php-ide--avilable-features ()
|
|
"Return list of available PHP-IDE features."
|
|
(cl-loop for (ide . plist) in php-ide-feature-alist
|
|
if (funcall (plist-get plist :test))
|
|
collect ide))
|
|
|
|
(provide 'php-ide)
|
|
;;; php-ide.el ends here
|