diff --git a/Eask b/Eask index cf80173..395c2fa 100644 --- a/Eask +++ b/Eask @@ -17,7 +17,6 @@ (depends-on "dash") (depends-on "editorconfig") (depends-on "jsonrpc") - -(setq package-lint-batch-fail-on-warnings nil) +(depends-on "f") (setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 diff --git a/copilot-balancer.el b/copilot-balancer.el index 0d25595..61a9ff7 100644 --- a/copilot-balancer.el +++ b/copilot-balancer.el @@ -1,14 +1,22 @@ -;; -*- lexical-binding: t -*- +;;; copilot-balancer.el --- Balancer module -*- lexical-binding:t -*- + +;;; Commentary: + +;; Balancer module + +;;; Code: (require 'cl-lib) (require 'pcase) -(require 'dash) (require 'rx) -(defvar copilot-balancer-lisp-modes '(emacs-lisp-mode - lisp-mode - scheme-mode - clojure-mode) +(require 'dash) + +(defvar copilot-balancer-lisp-modes '( emacs-lisp-mode + lisp-mode + lisp-interaction-mode + scheme-mode + clojure-mode) "List of lisp modes to balance.") (defvar copilot-balancer-lisp-pairs @@ -329,3 +337,4 @@ the line (not the actual newline character).") (t (list start end completion))))) (provide 'copilot-balancer) +;;; copilot-balancer.el ends here diff --git a/copilot.el b/copilot.el index 4d59507..b680a06 100644 --- a/copilot.el +++ b/copilot.el @@ -1,17 +1,21 @@ ;;; copilot.el --- An unofficial Copilot plugin for Emacs -*- lexical-binding:t -*- -;; Package-Requires: ((emacs "27.2") (s "1.12.0") (dash "2.19.1") (editorconfig "0.8.2") (jsonrpc "1.0.14")) +;; Package-Requires: ((emacs "27.2") (s "1.12.0") (dash "2.19.1") (editorconfig "0.8.2") (jsonrpc "1.0.14") (f "0.20.0")) ;; Version: 0.0.1 -;;; URL: https://github.com/copilot-emacs/copilot.el +;; URL: https://github.com/copilot-emacs/copilot.el ;;; Commentary: ;; An unofficial Copilot plugin for Emacs ;;; Code: + (require 'cl-lib) +(require 'compile) (require 'json) (require 'jsonrpc) + +(require 'f) (require 's) (require 'dash) (require 'editorconfig) @@ -54,10 +58,7 @@ Enabling event logging may slightly affect performance." :group 'copilot :type 'integer) -(defcustom copilot-node-executable - (if (eq system-type 'windows-nt) - "node.exe" - "node") +(defcustom copilot-node-executable (executable-find "node") "Node executable path." :group 'copilot :type 'string) @@ -91,14 +92,30 @@ indentation offset." :type '(alist :key-type symbol :value-type (choice integer symbol)) :group 'copilot) -(defconst copilot--base-dir - (file-name-directory - (or load-file-name - (buffer-file-name))) - "Directory containing this file.") +(defconst copilot-server-package-name "copilot-node-server" + "The name of the package to install copilot server.") + +(defcustom copilot-install-dir (expand-file-name + (locate-user-emacs-file (f-join ".cache" "copilot"))) + "Directory in which the servers will be installed." + :risky t + :type 'directory + :group 'copilot) + +(defconst copilot--server-executable + (if (eq system-type 'windows-nt) + (f-join copilot-install-dir "node_modules" "copilot-node-server" "copilot" + "bin" "copilot-node-server") + (f-join copilot-install-dir "bin" "copilot-node-server")) + "The dist directory containing agent.js file.") + +(defcustom copilot-version "1.14.0" + "Copilot version. -(defconst copilot-version "0.10.0" - "Copilot version.") +The default value is the preferred version and ensures functionality. +You may adjust this variable at your own risk." + :type 'string + :group 'copilot) (defvar-local copilot--overlay nil "Overlay for Copilot completion.") @@ -175,7 +192,7 @@ indentation offset." :notification-dispatcher #'copilot--handle-notification :process (make-process :name "copilot agent" :command (list copilot-node-executable - (concat copilot--base-dir "/dist/agent.js")) + copilot--server-executable) :coding 'utf-8-emacs-unix :connection-type 'pipe :stderr (get-buffer-create "*copilot stderr*") @@ -188,8 +205,15 @@ indentation offset." (defun copilot--start-agent () "Start the copilot agent process in local." - (if (not (locate-file copilot-node-executable exec-path)) - (user-error "Could not find node executable") + (cond + ((null copilot-node-executable) + (user-error "Could not find node executable")) + ((not (file-exists-p copilot-install-dir)) + (user-error "Server is not installed, please install via `M-x copilot-install-server`")) + (t + (unless (equal (copilot-installed-version) copilot-version) + (warn "Newer versions of the Copilot server are available for installation. +Please upgrade the server via `M-x copilot-reinstall-server`")) (let ((node-version (->> (with-output-to-string (call-process copilot-node-executable nil standard-output nil "--version")) (s-trim) @@ -203,9 +227,9 @@ indentation offset." (copilot--request 'initialize '(:capabilities (:workspace (:workspaceFolders t)))) (copilot--async-request 'setEditorInfo `(:editorInfo (:name "Emacs" :version ,emacs-version) - :editorPluginInfo (:name "copilot.el" :version ,copilot-version) - ,@(when copilot-network-proxy - `(:networkProxy ,copilot-network-proxy))))))))) + :editorPluginInfo (:name "copilot.el" :version ,copilot-version) + ,@(when copilot-network-proxy + `(:networkProxy ,copilot-network-proxy)))))))))) ;; ;; login / logout @@ -664,8 +688,8 @@ Use TRANSFORM-FN to transform completion if provided." (when (copilot--satisfy-display-predicates) (copilot--dbind (:text :uuid :docVersion doc-version - :range (:start (:line :character start-char) - :end (:character end-char))) + :range (:start (:line :character start-char) + :end (:character end-char))) completion-data (when (= doc-version copilot--doc-version) (save-excursion @@ -873,10 +897,10 @@ Use this for custom bindings in `copilot-mode'.") (cancel-timer copilot--post-command-timer)) (when (numberp copilot-idle-delay) (setq copilot--post-command-timer - (run-with-idle-timer copilot-idle-delay - nil - #'copilot--post-command-debounce - (current-buffer)))))) + (run-with-idle-timer copilot-idle-delay + nil + #'copilot--post-command-debounce + (current-buffer)))))) (defun copilot--self-insert (command) "Handle the case where the char just inserted is the start of the completion. @@ -902,5 +926,83 @@ command that triggered `post-command-hook'." (copilot--satisfy-trigger-predicates)) (copilot-complete))) +;; +;;; Installation + +(defun copilot-installed-version () + "Return the version number of currently installed `copilot-node-server'." + (when-let* + ((package-json + (if (eq system-type 'windows-nt) + (f-join copilot-install-dir "node_modules" "copilot-node-server" "package.json") + (f-join copilot-install-dir "lib" "node_modules" "copilot-node-server" "package.json"))) + ((file-exists-p package-json))) + (with-temp-buffer + (insert-file-contents package-json) + (save-match-data + (when (re-search-forward "\"version\": \"\\([0-9]+\.[0-9]+\.[0-9]+\\)") + (match-string 1)))))) + +;; XXX: This function is modified from `lsp-mode'; see `lsp-async-start-process' +;; function for more information. +(defun copilot-async-start-process (callback error-callback &rest command) + "Start async process COMMAND with CALLBACK and ERROR-CALLBACK." + (let ((name (cl-first command))) + (with-current-buffer + (compilation-start + (mapconcat + #'shell-quote-argument + (-filter + (lambda (cmd) + (not (null cmd))) + command) " ") + t + (lambda (&rest _) + (generate-new-buffer-name "*copilot-install-server*"))) + (view-mode +1) + (add-hook + 'compilation-finish-functions + (lambda (_buf status) + (if (string= "finished\n" status) + (when callback + (condition-case err + (funcall callback) + (error + (funcall error-callback (error-message-string err))))) + (when error-callback + (funcall error-callback (s-trim-right status))))) + nil t)))) + +;;;###autoload +(defun copilot-install-server () + "Interactively install server." + (interactive) + (if-let ((npm-binary (executable-find "npm"))) + (progn + (make-directory copilot-install-dir 'parents) + (copilot-async-start-process + nil nil + npm-binary + "-g" "--prefix" copilot-install-dir + "install" (format "%s@%s" copilot-server-package-name copilot-version))) + (message "Unable to install %s via `npm' because it is not present" package) + nil)) + +;;;###autoload +(defun copilot-reinstall-server () + "Interactively re-install server." + (interactive) + (copilot-uninstall-server) + (copilot-install-server)) + +;;;###autoload +(defun copilot-uninstall-server () + "Delete a Copilot server from `copilot-install-dir'." + (interactive) + (unless (file-directory-p copilot-install-dir) + (user-error "Couldn't find %s directory" copilot-install-dir)) + (delete-directory copilot-install-dir 'recursive) + (message "Server `%s' uninstalled." (file-name-nondirectory (directory-file-name copilot-install-dir)))) + (provide 'copilot) ;;; copilot.el ends here diff --git a/readme.md b/readme.md index 55d63bf..2c24777 100644 --- a/readme.md +++ b/readme.md @@ -6,17 +6,17 @@ Copilot.el is an Emacs plugin for GitHub Copilot. **Warning:** This plugin is unofficial and based on binaries provided by [copilot.vim](https://github.com/github/copilot.vim). -**Note:** You need access to [GitHub Copilot](https://github.com/features/copilot) to use this plugin. +**Note:** You need access to [GitHub Copilot][] to use this plugin. -Current maintainer: [@emil-vdw](https://github.com/emil-vdw), [@rakotomandimby](https://github.com/rakotomandimby). +Current maintainer: [@emil-vdw][], [@jcs090218][], [@rakotomandimby][]. -Retired maintainer: [@zerolfx](https://github.com/zerolfx). +Retired maintainer: [@zerolfx][]. ## Installation 0. Ensure your Emacs version is at least 27, the dependency package `editorconfig` ([melpa](https://melpa.org/#/editorconfig)) and `jsonrpc` ([elpa](https://elpa.gnu.org/packages/jsonrpc.html), >= 1.0.24) are both installed. -1. Install [Node.js](https://nodejs.org/en/download/) v18+. (You can specify the path to `node` executable by setting `copilot-node-executable`.) +1. Install [Node.js][] v18+. (You can specify the path to `node` executable by setting `copilot-node-executable`.) 2. Setup `copilot.el` as described in the next section. @@ -156,7 +156,7 @@ Please make sure you have these dependencies installed (available in ELPA/MELPA) After installing those, clone this repository then insert the below snippet into your config file. -``` +```elisp (add-to-list 'load-path "/path/to/copilot.el") (require 'copilot) ``` @@ -232,13 +232,13 @@ Cycle through the completion list. #### copilot-logout -Logout from GitHub. +Log out from GitHub. ## Customization #### copilot-node-executable -The executable path of Node.js. +The executable path of [Node.js][]. #### copilot-idle-delay @@ -302,3 +302,14 @@ These projects helped me a lot: + https://github.com/TommyX12/company-tabnine/ + https://github.com/cryptobadger/flight-attendant.el + https://github.com/github/copilot.vim + + + + +[@emil-vdw]: https://github.com/emil-vdw +[@jcs090218]: https://github.com/jcs090218 +[@rakotomandimby]: https://github.com/rakotomandimby +[@zerolfx]: https://github.com/zerolfx + +[GitHub Copilot]: https://github.com/features/copilot +[Node.js]: https://nodejs.org/en/download/