diff --git a/Cask b/Cask index d992fad3..76702c32 100644 --- a/Cask +++ b/Cask @@ -8,9 +8,13 @@ "lisp/php-face.el" "lisp/php-project.el" "lisp/php-local-manual.el" + "lisp/php-ui-phpactor.el" + "lisp/php-ui.el" "lisp/php-mode-debug.el") (development + ;;(depends-on "lsp-mode") + (depends-on "phpactor") (depends-on "pkg-info") (depends-on "projectile") (depends-on "smart-jump") diff --git a/Makefile b/Makefile index 2bad67e0..650eb44f 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ EMACS ?= emacs CASK ?= cask -ELS = lisp/php.el lisp/php-align.el lisp/php-face.el lisp/php-project.el lisp/php-local-manual.el lisp/php-mode.el lisp/php-mode-debug.el +ELS = lisp/php.el lisp/php-align.el lisp/php-face.el lisp/php-project.el lisp/php-local-manual.el lisp/php-mode.el lisp/php-ui.el lisp/php-ui-phpactor.el lisp/php-mode-debug.el AUTOLOADS = php-mode-autoloads.el ELCS = $(ELS:.el=.elc) %.elc: %.el - $(EMACS) -Q -batch -L lisp/ -f batch-byte-compile $< + $(EMACS) -Q -batch -L lisp/ --eval \ + "(let ((default-directory (expand-file-name \".cask\" default-directory))) \ + (require 'package) \ + (normal-top-level-add-subdirs-to-load-path))" \ + -f package-initialize -f batch-byte-compile $< all: autoloads $(ELCS) authors diff --git a/lisp/php-ui-phpactor.el b/lisp/php-ui-phpactor.el new file mode 100644 index 00000000..944daa6a --- /dev/null +++ b/lisp/php-ui-phpactor.el @@ -0,0 +1,91 @@ +;;; php-ui-phpactor.el --- UI support for PHP developing -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Friends of Emacs-PHP development + +;; Author: USAMI Kenta +;; Keywords: tools, files +;; URL: https://github.com/emacs-php/php-mode +;; Version: 1.24.0 +;; 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 . + +;;; Commentary: + +;; PHP-UI implementation to integrate Phpactor (phpactor.el). + +;;; Code: +(require 'phpactor nil t) +(require 'popup nil t) +(require 'smart-jump nil t) + +(defvar-local php-ui-phpactor-buffer nil) +(defvar-local php-ui-phpactor-hover-last-pos nil) +(defvar-local php-ui-phpactor-hover-last-msg nil) + +(declare-function phpactor--command-argments (&rest arg-keys)) +(declare-function phpactor--parse-json (buffer)) +(declare-function phpactor--rpc-async "phpactor" (action arguments callback)) +(declare-function phpactor-goto-definition "phpactor" ()) +(declare-function popup-tip "popup" (string)) + +(defvar php-ui-phpactor-timer nil + "Timer object for execute Phpactor and display hover message.") + +(defun php-ui-phpactor-hover () + "Show brief information about the symbol underneath the cursor." + (interactive) + (when php-ui-phpactor-buffer + (if (eq (point) php-ui-phpactor-hover-last-pos) + (when php-ui-phpactor-hover-last-msg + (let ((msg php-ui-phpactor-hover-last-msg)) + (setq php-ui-phpactor-hover-last-msg nil) + (popup-tip msg))) + (setq php-ui-phpactor-hover-last-pos (point)) + (let ((main-buffer (current-buffer))) + (phpactor--rpc-async "hover" (phpactor--command-argments :source :offset) + (lambda (proc) + (let* ((response (phpactor--parse-json (process-buffer proc))) + (msg (plist-get (plist-get response :parameters) :message))) + (with-current-buffer main-buffer + (setq php-ui-phpactor-hover-last-msg msg))))))))) + +;;;###autoload +(defun php-ui-phpactor-activate () + "Activate PHP-UI using phpactor.el." + (interactive) + (if (not (fboundp 'smart-jump-go)) + (local-set-key [remap xref-find-definitions] #'phpactor-goto-definition) + (local-set-key [remap xref-find-definitions] #'smart-jump-go) + (local-set-key [remap xref-pop-marker-stack] #'smart-jump-back) + (local-set-key [remap xref-find-references] #'smart-jump-references)) + (unless php-ui-phpactor-timer + (setq php-ui-phpactor-timer (run-with-idle-timer 1.0 1 #'php-ui-phpactor-hover))) + (setq php-ui-phpactor-buffer t)) + +;;;###autoload +(defun php-ui-phpactor-deactivate () + "Dectivate PHP-UI using phpactor.el." + (interactive) + (local-unset-key [remap xref-find-definitions]) + (local-unset-key [remap xref-pop-marker-stack]) + (local-unset-key [remap xref-find-references]) + + (when php-ui-phpactor-timer + (cancel-timer php-ui-phpactor-timer) + (setq php-ui-phpactor-timer nil)) + (setq php-ui-phpactor-buffer nil)) + +(provide 'php-ui-phpactor) +;;; php-ui-phpactor.el ends here diff --git a/lisp/php-ui.el b/lisp/php-ui.el new file mode 100644 index 00000000..530931db --- /dev/null +++ b/lisp/php-ui.el @@ -0,0 +1,196 @@ +;;; php-ui.el --- UI support for PHP development -*- lexical-binding: t; -*- + +;; Copyright (C) 2022 Friends of Emacs-PHP development + +;; Author: USAMI Kenta +;; Keywords: tools, files +;; URL: https://github.com/emacs-php/php-mode +;; Version: 1.24.0 +;; 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 . + +;;; 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-UI 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-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 my-php-mode-setup () +;; (add-hook 'hack-local-variables-hook #'php-ui-mode t t)) +;; +;; (with-eval-after-load 'php-ui +;; (custom-set-variables +;; '(php-ui-feature 'eglot) ;; or 'none, 'phpactor, 'lsp-mode +;; '(php-ui-eglot-executable '("psalm-language-server")) ;; or "intelephense", '("php" "vendor/bin/path/to/server") +;; ;; If you want to hide php-ui-mode from the mode line, set an empty string +;; '(php-ui-mode-lighter "")) +;; +;; ;; Only Eglot users +;; (add-to-list 'php-ui-eglot-executable '(php-mode . php-ui-eglot-server-program)) +;; +;; (add-hook 'php-mode #'my-php-mode-setup)) +;; +;; If you don't enable IDE support by default, set '(php-ui-feature 'none) +;; +;; ### For per project configuration +;; +;; Put follows code into .dir-locals.el in project directory: +;; +;; ((nil (php-project-root . git) +;; (php-ui-eglot-executable . ("psalm-language-server")) +;; ;; or (php-ui-eglot-executable . ("php" "vendor/bin/path/to/server")) +;; (php-ui-feature . 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://gitlab.com/ideasman42/emacs-sidecar-locals +;; + +;;; Code: +(eval-when-compile + (require 'php-ui-phpactor)) + +(defvar php-ui-feature-alist + '((none :test (lambda () t) + :activate (lambda () t) + :deactivate (lambda () t)) + (phpactor :test (lambda () (and (require 'phpactor nil t) (featurep 'phpactor))) + :activate php-ui-phpactor-activate + :deactivate php-ui-phpactor-activate) + (eglot :test (lambda () (and (require 'eglot nil t) (featurep 'eglot))) + :activate eglot-ensure + :deactivate eglot--managed-mode-off) + (lsp-mode :test (lambda () (and (require 'lsp nil t) (featurep 'lsp))) + :activate lsp + :deactivate lsp-workspace-shutdown))) + +(defgroup php-ui nil + "UI support for PHP developing." + :tag "PHP-UI" + :prefix "php-ui-" + :group 'php) + +(defcustom php-ui-eglot-executable nil + "A symbol of PHP-UI feature" + :tag "PHP-UI Eglot Executable" + :group 'php-ui + :type '(repeat string) + :safe (lambda (v) (or (stringp v) (listp v)))) + +(defcustom php-ui-feature nil + "A symbol of PHP-UI feature" + :tag "PHP-UI Feature" + :group 'php-ui + :type 'symbol + :safe #'symbolp) + +(defcustom php-ui-mode-lighter " PHP-UI" + "A symbol of PHP-UI feature" + :tag "PHP-UI Mode Lighter" + :group 'php-ui + :type 'string + :safe #'stringp) + +;;;###autoload +(define-minor-mode php-ui-mode + "Minor mode for integrate IDE-like tools." + :lighter php-ui-mode-lighter + (let ((ui-plist (cdr-safe (assq php-ui-feature php-ui-feature-alist)))) + (if (null ui-plist) + (message "Please set `php-ui-feature' variable in .dir-locals.el or custom variable") + (if php-ui-mode + (php-ui--activate-buffer ui-plist) + (php-ui--deactivate-buffer ui-plist))))) + +;;;###autoload +(defun php-ui (feature) + "Select a PHP-UI feature and execute `php-ui-mode'." + (interactive (list (php-ui--select-feature))) + (unless feature + (user-error "No PHP-UI feature is installed. Install the lsp-mode, eglot or phpactor package")) + (unless (assq feature php-ui-feature-alist) + (user-error "`%s' does not include in available PHP-UI features. (%s)" + feature + (mapconcat #'symbol-name (php-ui--avilable-features) ", "))) + (when (and php-ui-mode feature php-ui-feature + (not (eq php-ui-feature php-ui-feature))) + (php-ui-mode -1)) + (let ((php-ui-feature feature)) + (php-ui-mode +1))) + +;;;###autoload +(defun php-ui-eglot-server-program () + "Return a list of command to execute LSP Server." + (if (stringp php-ui-eglot-executable) + (list php-ui-eglot-executable) + php-ui-eglot-executable)) + +(defun php-ui--eglot-current-server () + (php-project-get-root-dir)) + +(defun php-ui--activate-buffer (ui-plist) + "Activate php-ui implementation by UI-PLIST." + (funcall (plist-get ui-plist :activate))) + +(defun php-ui--deactivate-buffer (ui-plist) + "Deactivate php-ui implementation by UI-PLIST." + (funcall (plist-get ui-plist :deactivate))) + +(defun php-ui--avilable-features () + "Return list of available PHP-UI features." + (cl-loop for (ui . plist) in php-ui-feature-alist + if (funcall (plist-get plist :test)) + collect ui)) + +(defun php-ui--select-feature () + "Choose PHP-UI feature" + (let* ((features (php-ui--avilable-features)) + (count (length features))) + (cond + ((eq count 0) nil) + ((eq count 1) (car features)) + (t + (intern (completing-read "Select PHP-UI feature: " features nil t)))))) + +(provide 'php-ui) +;;; php-ui.el ends here