From 039d967c5fae42eacb2a3c84d862588c912daad1 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Thu, 5 Dec 2024 16:40:02 -0300 Subject: [PATCH 01/11] fix: use lsp-error in case of errors during inline completion --- lsp-inline-completion.el | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/lsp-inline-completion.el b/lsp-inline-completion.el index 4394584b08..844352be03 100644 --- a/lsp-inline-completion.el +++ b/lsp-inline-completion.el @@ -372,22 +372,24 @@ text range that was updated by the completion" (unless implicit (lsp--spinner-start) ) - (unwind-protect - (if-let* ((resp (lsp-request-while-no-input "textDocument/inlineCompletion" - (lsp-inline-completion--params implicit))) - (items (lsp-inline-completion--parse-items resp))) - - (progn - (lsp-inline-completion--clear-overlay) - (setq lsp-inline-completion--items items) - (setq lsp-inline-completion--current 0) - (setq lsp-inline-completion--start-point (point)) - (lsp-inline-completion-show-overlay)) + (condition-case err + (unwind-protect + (if-let* ((resp (lsp-request-while-no-input "textDocument/inlineCompletion" + (lsp-inline-completion--params implicit))) + (items (lsp-inline-completion--parse-items resp))) + + (progn + (lsp-inline-completion--clear-overlay) + (setq lsp-inline-completion--items items) + (setq lsp-inline-completion--current 0) + (setq lsp-inline-completion--start-point (point)) + (lsp-inline-completion-show-overlay)) + (unless implicit + (lsp--info "No Suggestions!"))) + ;; Clean up (unless implicit - (lsp--info "No Suggestions!"))) - ;; Clean up - (unless implicit - (lsp--spinner-stop)))) + (lsp--spinner-stop))) + (t (lsp--error "Couldnot fetch completions: %s" err)))) ;; Inline Completion Mode From 321b37c50d94dbd7536baa281e5a400ee315c2b9 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Thu, 5 Dec 2024 16:40:48 -0300 Subject: [PATCH 02/11] feat: lsp-copilot client for copilot-node-server --- clients/lsp-copilot.el | 250 +++++++++++++++++++++++++++++++++++++++++ docs/lsp-clients.json | 8 ++ lsp-mode.el | 2 +- mkdocs.yml | 1 + 4 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 clients/lsp-copilot.el diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el new file mode 100644 index 0000000000..abb1bc6c66 --- /dev/null +++ b/clients/lsp-copilot.el @@ -0,0 +1,250 @@ +;;; lsp-copilot.el --- lsp-mode client for copilot -*- lexical-binding: t -*- + +;; Copyright (C) 2024 Rodrigo Virote Kassick + +;; Author: Rodrigo Virote Kassick +;; Keywords: lsp-mode, generative-ai, code-assistant + +;; This file is not part of GNU Emacs + +;; 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: + +;; LSP client for the copilot node server -- https://www.npmjs.com/package/copilot-node-server + +;; Package-Requires: (lsp-mode secrets s compile dash cl-lib request company) + +;; Code: + +(require 'dash) +(require 'lsp-mode) +(require 's) + +(defgroup lsp-copilot () + "Copilot LSP configuration" + :group 'lsp-mode + :tag "Copilot LSP" + :link '(url-link "https://www.npmjs.com/package/copilot-node-server")) + +(defcustom lsp-copilot-enabled t + "Whether the server should be started to provide completions." + :type 'boolean + :group 'lsp-copilot) + +(defcustom lsp-copilot-langserver-command-args '("--stdio") + "Command to start copilot-langserver." + :type '(repeat string) + :group 'lsp-copilot) + +(defcustom lsp-copilot-executable "copilot-lsp" + "The system-wise executable of lsp-copilot. +When this executable is not found, you can stil use +lsp-install-server to fetch an emacs-local version of the LSP." + :type 'string + :group 'lsp-copilot) + + +(defcustom lsp-copilot-major-modes '(python-mode + python-ts-mode + go-mode + go-ts-mode + js-mode + js-ts-mode + java-mode + java-ts-mode + kotlin-mode + kotlin-ts-mode + ruby-mode + ruby-ts-mode + rust-mode + rust-ts-mode + tsx-ts-mode + typescript-mode + typescript-ts-mode + vue-mode + yaml-mode + yaml-ts-mode) + + "The major modes for which lsp-copilot should be used" + :type '(repeat symbol) + :group 'lsp-copilot) + +(defcustom lsp-copilot-server-disabled-languages nil + "The lanuages for which the server must not be enabled (initialization setup for copilot)" + :type '(repeat string) + :group 'lsp-copilot) + +(defcustom lsp-copilot-server-multi-root t + "Whether the copilot server is started with multi-root" + :type 'boolean + :group 'lsp-copilot) + +(lsp-interface + (CopilotSignInInitiateResponse (:status :userCode :verificationUri :expiresIn :interval :user) nil) + (CopilotSignInConfirmResponse (:status :user)) + (CopilotCheckStatusResponse (:status :user))) + +(lsp-dependency 'lsp-copilot + `(:system ,lsp-copilot-executable) + '(:npm :package "copilot-node-server" + :path "language-server.js")) + + +(defun lsp-copilot--client-active-for-mode-p (_ mode) + (and lsp-copilot-enabled (member mode lsp-copilot-major-modes))) + +(defun lsp-copilot--find-active-workspaces () + "Returns a list of lsp-copilot workspaces" + (-some->> (lsp-session) + (lsp--session-workspaces) + (--filter (member (lsp--client-server-id (lsp--workspace-client it)) + '(lsp-copilot lsp-copilot-remote))))) + +(defun lsp-copilot--authenticated-as () + "Returns nil when not authorized; otherwise, the user name" + (-if-let (workspace (--some (lsp-find-workspace it (buffer-file-name)) + '(lsp-copilot lsp-copilot-remote))) + (-if-let (checkStatusResponse (with-lsp-workspace workspace + (lsp-request "checkStatus" '(:dummy "dummy")))) + (-let* (((&CopilotCheckStatusResponse? :status :user) checkStatusResponse)) + (unless (s-present-p status) + (error "No status in response %S" checkStatusResponse)) + ;; Result: + (when (s-equals-p status "OK") + user)) + (error "No response from the LSP server")) + (error "No lsp-copilot workspace found!"))) + +;;;###autoload +(defun lsp-copilot-check-status () + "Checks the status of the Copilot Server" + (interactive) + + (condition-case err + (progn + (let ((user (lsp-copilot--authenticated-as))) + (if user + (message "Authenticated as %s" user) + (user-error "Not Authenticated")))) + (t (user-error "Error checking status: %s" err)))) + + +;;;###autoload +(defun lsp-copilot-login () + "Log in with copilot. + +This function is automatically called during the client initialization if needed" + (interactive) + + (-when-let (workspace (--some (lsp-find-workspace it) '(lsp-copilot lsp-copilot-remote))) + (with-lsp-workspace workspace + (-when-let* ((response (lsp-request "signInInitiate" '(:dummy "dummy")))) + (-let (((&CopilotSignInInitiateResponse? :status :user-code :verification-uri :expires-in :interval :user) response)) + + ;; Bail if already signed in + (when (s-equals-p status "AlreadySignedIn") + (lsp-message "Copilot :: Already signed in as %s" user)) + + (if (display-graphic-p) + (progn + (gui-set-selection 'CLIPBOARD user-code) + (read-from-minibuffer (format "Your one-time code %s is copied. Press \ +ENTER to open GitHub in your browser. If your browser does not open \ +automatically, browse to %s." user-code verification-uri)) + (browse-url verification-uri) + (read-from-minibuffer "Press ENTER if you finish authorizing.")) + ;; Console: + (read-from-minibuffer (format "First copy your one-time code: %s. Press ENTER to continue." user-code)) + (read-from-minibuffer (format "Please open %s in your browser. Press ENTER if you finish authorizing." verification-uri))) + + (lsp-message "Verifying...") + (-let* ((confirmResponse (lsp-request "signInConfirm" (list :userCode user-code))) + ((&CopilotSignInConfirmResponse? :status :user) confirmResponse)) + (when (s-equals-p status "NotAuthorized") + (user-error "User %s is not authorized" user)) + (lsp-message "User %s is authorized: %s" user status)) + + ;; Do we need to confirm? + (-let* ((checkStatusResponse (lsp-request "checkStatus" '(:dummy "dummy"))) + ((&CopilotCheckStatusResponse? :status :user) checkStatusResponse)) + (when (s-equals-p status "NotAuthorized") + (user-error "User %s is not authorized" user)) + + (lsp-message "Authenticated as %s" user))))))) + + +(defun lsp-copilot--server-initialization-options () + ;; Trying to replicate Copilot.vim initialization here ... + (list :editorInfo (list :name "emacs" :version (symbol-value 'emacs-version)) + :editorPluginInfo (list :name "lsp-copilot" :version "1.38.0") + :editorConfig (list :enableAutoCompletions lsp-copilot-enabled + :disabledLanguages lsp-copilot-server-disabled-languages) + :name "emacs" + :version "0.1.0")) + +(defun lsp-copilot--server-initialized-fn (workspace) + (unless (lsp-copilot--authenticated-as) + (lsp-copilot-login))) + +(defun lsp-copilot--cmdline () + (-if-let (candidates (directory-files-recursively + (f-join lsp-server-install-dir "npm" "copilot-node-server") + "^language-server.js$")) + `("node" ,(car candidates) ,@lsp-copilot-langserver-command-args) + (error "language-server.js not found"))) + +;; Server installed by emacs +(lsp-register-client + (make-lsp-client + :server-id 'lsp-copilot + :new-connection (lsp-stdio-connection #'lsp-copilot--cmdline) + :activation-fn #'lsp-copilot--client-active-for-mode-p + :multi-root lsp-copilot-server-multi-root + :priority -2 + :add-on? t + :completion-in-comments? t + :initialization-options #'lsp-copilot--server-initialization-options + :initialized-fn #'lsp-copilot--server-initialized-fn + :download-server-fn (lambda (_client callback error-callback _update?) + (lsp-package-ensure 'lsp-copilot callback error-callback)) + :notification-handlers (lsp-ht + ("$/progress" (lambda (&rest args) (lsp-message "$/progress with %S" args))) + ("featureFlagsNotification" #'ignore) + ("statusNotification" #'ignore) + ("window/logMessage" #'lsp--window-log-message) + ("conversation/preconditionsNotification" #'ignore)))) + +(lsp-register-client + (make-lsp-client + :server-id 'lsp-copilot-remote + :new-connection (lsp-stdio-connection (lambda () + `(,lsp-copilot-executable ,@lsp-copilot-langserver-command-args))) + :activation-fn #'lsp-copilot--client-active-for-mode-p + :multi-root lsp-copilot-server-multi-root + :priority -2 + :add-on? t + :completion-in-comments? t + :initialization-options #'lsp-copilot--server-initialization-options + :initialized-fn #'lsp-copilot--server-initialized-fn + :notification-handlers (lsp-ht + ("$/progress" (lambda (&rest args) (lsp-message "$/progress with %S" args))) + ("featureFlagsNotification" #'ignore) + ("statusNotification" #'ignore) + ("window/logMessage" #'lsp--window-log-message) + ("conversation/preconditionsNotification" #'ignore)))) + +(lsp-consistency-check lsp-copilot) + +(provide 'lsp-copilot) diff --git a/docs/lsp-clients.json b/docs/lsp-clients.json index 6a3eb70445..3af660efba 100644 --- a/docs/lsp-clients.json +++ b/docs/lsp-clients.json @@ -116,6 +116,14 @@ "lsp-install-server": "camells", "debugger": "Not available" }, + { + "name": "copilot", + "full-name": "Github Copilot", + "server-name": "copilot-node-server", + "server-url": "https://www.npmjs.com/package/copilot-node-server", + "installation-url": "https://www.npmjs.com/package/copilot-node-server", + "debugger": "Not available" + }, { "name": "credo", "full-name": "Credo", diff --git a/lsp-mode.el b/lsp-mode.el index 58b71ea13e..08de53e0a9 100644 --- a/lsp-mode.el +++ b/lsp-mode.el @@ -176,7 +176,7 @@ As defined by the Language Server Protocol 3.16." '( ccls lsp-actionscript lsp-ada lsp-angular lsp-ansible lsp-asm lsp-astro lsp-autotools lsp-awk lsp-bash lsp-beancount lsp-bufls lsp-clangd lsp-clojure lsp-cmake lsp-cobol lsp-credo lsp-crystal lsp-csharp lsp-css - lsp-cucumber lsp-cypher lsp-d lsp-dart lsp-dhall lsp-docker lsp-dockerfile + lsp-copilot lsp-cucumber lsp-cypher lsp-d lsp-dart lsp-dhall lsp-docker lsp-dockerfile lsp-earthly lsp-elixir lsp-elm lsp-emmet lsp-erlang lsp-eslint lsp-fortran lsp-futhark lsp-fsharp lsp-gdscript lsp-gleam lsp-glsl lsp-go lsp-golangci-lint lsp-grammarly lsp-graphql lsp-groovy lsp-hack lsp-haskell lsp-haxe lsp-idris lsp-java diff --git a/mkdocs.yml b/mkdocs.yml index f182a9aa30..d9d89650d7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -84,6 +84,7 @@ nav: - Fortran: page/lsp-fortran.md - Futhark: page/lsp-futhark.md - GDScript: page/lsp-gdscript.md + - Github Copilot: page/lsp-copilot.md - Gleam: page/lsp-gleam.md - GLSL: page/lsp-glsl.md - GNAT Project: page/lsp-gpr.md From 48bcf821ae91f06dc012747a36a1e6e21eef47cc Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Sat, 7 Dec 2024 10:22:05 -0300 Subject: [PATCH 03/11] refactor: move copilot interfaces to lsp-protocol --- clients/lsp-copilot.el | 17 ++++++----------- lsp-protocol.el | 5 +++++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index abb1bc6c66..a6f988c3d4 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -91,11 +91,6 @@ lsp-install-server to fetch an emacs-local version of the LSP." :type 'boolean :group 'lsp-copilot) -(lsp-interface - (CopilotSignInInitiateResponse (:status :userCode :verificationUri :expiresIn :interval :user) nil) - (CopilotSignInConfirmResponse (:status :user)) - (CopilotCheckStatusResponse (:status :user))) - (lsp-dependency 'lsp-copilot `(:system ,lsp-copilot-executable) '(:npm :package "copilot-node-server" @@ -110,15 +105,15 @@ lsp-install-server to fetch an emacs-local version of the LSP." (-some->> (lsp-session) (lsp--session-workspaces) (--filter (member (lsp--client-server-id (lsp--workspace-client it)) - '(lsp-copilot lsp-copilot-remote))))) + '(copilot-ls copilot-ls-remote))))) (defun lsp-copilot--authenticated-as () "Returns nil when not authorized; otherwise, the user name" (-if-let (workspace (--some (lsp-find-workspace it (buffer-file-name)) - '(lsp-copilot lsp-copilot-remote))) + '(copilot-ls copilot-ls-remote))) (-if-let (checkStatusResponse (with-lsp-workspace workspace (lsp-request "checkStatus" '(:dummy "dummy")))) - (-let* (((&CopilotCheckStatusResponse? :status :user) checkStatusResponse)) + (-let* (((&copilot-ls:CheckStatusResponse? :status :user) checkStatusResponse)) (unless (s-present-p status) (error "No status in response %S" checkStatusResponse)) ;; Result: @@ -151,7 +146,7 @@ This function is automatically called during the client initialization if needed (-when-let (workspace (--some (lsp-find-workspace it) '(lsp-copilot lsp-copilot-remote))) (with-lsp-workspace workspace (-when-let* ((response (lsp-request "signInInitiate" '(:dummy "dummy")))) - (-let (((&CopilotSignInInitiateResponse? :status :user-code :verification-uri :expires-in :interval :user) response)) + (-let (((&copilot-ls:SignInInitiateResponse? :status :user-code :verification-uri :expires-in :interval :user) response)) ;; Bail if already signed in (when (s-equals-p status "AlreadySignedIn") @@ -171,14 +166,14 @@ automatically, browse to %s." user-code verification-uri)) (lsp-message "Verifying...") (-let* ((confirmResponse (lsp-request "signInConfirm" (list :userCode user-code))) - ((&CopilotSignInConfirmResponse? :status :user) confirmResponse)) + ((&copilot-ls:SignInConfirmResponse? :status :user) confirmResponse)) (when (s-equals-p status "NotAuthorized") (user-error "User %s is not authorized" user)) (lsp-message "User %s is authorized: %s" user status)) ;; Do we need to confirm? (-let* ((checkStatusResponse (lsp-request "checkStatus" '(:dummy "dummy"))) - ((&CopilotCheckStatusResponse? :status :user) checkStatusResponse)) + ((&copilot-ls:CheckStatusResponse? :status :user) checkStatusResponse)) (when (s-equals-p status "NotAuthorized") (user-error "User %s is not authorized" user)) diff --git a/lsp-protocol.el b/lsp-protocol.el index a56e2b8e51..eb930dcea2 100644 --- a/lsp-protocol.el +++ b/lsp-protocol.el @@ -465,6 +465,11 @@ See `-let' for a description of the destructuring mechanism." (lsp-interface (terraform-ls:Providers (:v :provider_requirements :installed_providers) nil)) (lsp-interface (terraform-ls:module.terraform (:v :required_version :discovered_version))) +(lsp-interface + (copilot-ls:SignInInitiateResponse (:status :userCode :verificationUri :expiresIn :interval :user) nil) + (copilot-ls:SignInConfirmResponse (:status :user)) + (copilot-ls:CheckStatusResponse (:status :user))) + ;; begin autogenerated code From 1752d1ca139a329938eebe1713f90756e268f33e Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Sat, 7 Dec 2024 10:23:57 -0300 Subject: [PATCH 04/11] refactor: use copilot-ls / copilot-ls-remote as server ids --- clients/lsp-copilot.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index a6f988c3d4..0e9c32ea57 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -91,7 +91,7 @@ lsp-install-server to fetch an emacs-local version of the LSP." :type 'boolean :group 'lsp-copilot) -(lsp-dependency 'lsp-copilot +(lsp-dependency 'copilot-ls `(:system ,lsp-copilot-executable) '(:npm :package "copilot-node-server" :path "language-server.js")) @@ -143,7 +143,7 @@ lsp-install-server to fetch an emacs-local version of the LSP." This function is automatically called during the client initialization if needed" (interactive) - (-when-let (workspace (--some (lsp-find-workspace it) '(lsp-copilot lsp-copilot-remote))) + (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-remote))) (with-lsp-workspace workspace (-when-let* ((response (lsp-request "signInInitiate" '(:dummy "dummy")))) (-let (((&copilot-ls:SignInInitiateResponse? :status :user-code :verification-uri :expires-in :interval :user) response)) @@ -203,7 +203,7 @@ automatically, browse to %s." user-code verification-uri)) ;; Server installed by emacs (lsp-register-client (make-lsp-client - :server-id 'lsp-copilot + :server-id 'copilot-ls :new-connection (lsp-stdio-connection #'lsp-copilot--cmdline) :activation-fn #'lsp-copilot--client-active-for-mode-p :multi-root lsp-copilot-server-multi-root @@ -223,12 +223,12 @@ automatically, browse to %s." user-code verification-uri)) (lsp-register-client (make-lsp-client - :server-id 'lsp-copilot-remote + :server-id 'copilot-ls-remote :new-connection (lsp-stdio-connection (lambda () - `(,lsp-copilot-executable ,@lsp-copilot-langserver-command-args))) + `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args))) :activation-fn #'lsp-copilot--client-active-for-mode-p :multi-root lsp-copilot-server-multi-root - :priority -2 + :priority -1 :add-on? t :completion-in-comments? t :initialization-options #'lsp-copilot--server-initialization-options From c529d466fbd3e1cb7618ba9b9c7af8c9c0e1cd67 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Sat, 7 Dec 2024 10:27:11 -0300 Subject: [PATCH 05/11] refactor: remove unused symbols --- clients/lsp-copilot.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index 0e9c32ea57..a24fc7a89d 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -146,7 +146,7 @@ This function is automatically called during the client initialization if needed (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-remote))) (with-lsp-workspace workspace (-when-let* ((response (lsp-request "signInInitiate" '(:dummy "dummy")))) - (-let (((&copilot-ls:SignInInitiateResponse? :status :user-code :verification-uri :expires-in :interval :user) response)) + (-let (((&copilot-ls:SignInInitiateResponse? :status :user-code :verification-uri :user) response)) ;; Bail if already signed in (when (s-equals-p status "AlreadySignedIn") @@ -189,7 +189,7 @@ automatically, browse to %s." user-code verification-uri)) :name "emacs" :version "0.1.0")) -(defun lsp-copilot--server-initialized-fn (workspace) +(defun lsp-copilot--server-initialized-fn (_) (unless (lsp-copilot--authenticated-as) (lsp-copilot-login))) From 977d752962f1dab74257a2e3edbd71ae495547d9 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Mon, 9 Dec 2024 08:22:34 -0300 Subject: [PATCH 06/11] chore: no need for explicit remote server Now the lsp-controlled copilot npm install path contains a bin with a copilot-node-server -- we no longer need to go out of our way to find the language server js and execute it with node --- clients/lsp-copilot.el | 31 +++---------------------------- 1 file changed, 3 insertions(+), 28 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index a24fc7a89d..9ca88f517b 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -94,7 +94,7 @@ lsp-install-server to fetch an emacs-local version of the LSP." (lsp-dependency 'copilot-ls `(:system ,lsp-copilot-executable) '(:npm :package "copilot-node-server" - :path "language-server.js")) + :path "copilot-node-server")) (defun lsp-copilot--client-active-for-mode-p (_ mode) @@ -193,18 +193,12 @@ automatically, browse to %s." user-code verification-uri)) (unless (lsp-copilot--authenticated-as) (lsp-copilot-login))) -(defun lsp-copilot--cmdline () - (-if-let (candidates (directory-files-recursively - (f-join lsp-server-install-dir "npm" "copilot-node-server") - "^language-server.js$")) - `("node" ,(car candidates) ,@lsp-copilot-langserver-command-args) - (error "language-server.js not found"))) - ;; Server installed by emacs (lsp-register-client (make-lsp-client :server-id 'copilot-ls - :new-connection (lsp-stdio-connection #'lsp-copilot--cmdline) + :new-connection (lsp-stdio-connection (lambda () + `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args))) :activation-fn #'lsp-copilot--client-active-for-mode-p :multi-root lsp-copilot-server-multi-root :priority -2 @@ -221,25 +215,6 @@ automatically, browse to %s." user-code verification-uri)) ("window/logMessage" #'lsp--window-log-message) ("conversation/preconditionsNotification" #'ignore)))) -(lsp-register-client - (make-lsp-client - :server-id 'copilot-ls-remote - :new-connection (lsp-stdio-connection (lambda () - `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args))) - :activation-fn #'lsp-copilot--client-active-for-mode-p - :multi-root lsp-copilot-server-multi-root - :priority -1 - :add-on? t - :completion-in-comments? t - :initialization-options #'lsp-copilot--server-initialization-options - :initialized-fn #'lsp-copilot--server-initialized-fn - :notification-handlers (lsp-ht - ("$/progress" (lambda (&rest args) (lsp-message "$/progress with %S" args))) - ("featureFlagsNotification" #'ignore) - ("statusNotification" #'ignore) - ("window/logMessage" #'lsp--window-log-message) - ("conversation/preconditionsNotification" #'ignore)))) - (lsp-consistency-check lsp-copilot) (provide 'lsp-copilot) From cada66aad3eab8cffb5a838df3b6692b7d18f9ce Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Mon, 9 Dec 2024 23:52:28 -0800 Subject: [PATCH 07/11] fix: make copilot-ls works with plist --- clients/lsp-copilot.el | 65 ++++++++++++++++++------------------------ lsp-mode.el | 27 ++++++++++++------ lsp-protocol.el | 2 +- 3 files changed, 47 insertions(+), 47 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index 9ca88f517b..dce8d8398c 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -55,50 +55,32 @@ lsp-install-server to fetch an emacs-local version of the LSP." :type 'string :group 'lsp-copilot) - -(defcustom lsp-copilot-major-modes '(python-mode - python-ts-mode - go-mode - go-ts-mode - js-mode - js-ts-mode - java-mode - java-ts-mode - kotlin-mode - kotlin-ts-mode - ruby-mode - ruby-ts-mode - rust-mode - rust-ts-mode - tsx-ts-mode - typescript-mode - typescript-ts-mode - vue-mode - yaml-mode - yaml-ts-mode) - - "The major modes for which lsp-copilot should be used" - :type '(repeat symbol) +(defcustom lsp-copilot-applicable-fn (-const t) + "A function which returns whether the copilot is applicable for the buffer. +The input are the file name and the major mode of the buffer." + :type 'function :group 'lsp-copilot) (defcustom lsp-copilot-server-disabled-languages nil - "The lanuages for which the server must not be enabled (initialization setup for copilot)" + "The languages for which the server must not be enabled (initialization setup for copilot)" :type '(repeat string) :group 'lsp-copilot) (defcustom lsp-copilot-server-multi-root t - "Whether the copilot server is started with multi-root" + "Whether the copilot server is started with multi-root." :type 'boolean :group 'lsp-copilot) +(defcustom lsp-copilot-version "1.41.0" + "Copilot version." + :type 'string + :group 'lsp-copilot) + (lsp-dependency 'copilot-ls `(:system ,lsp-copilot-executable) '(:npm :package "copilot-node-server" - :path "copilot-node-server")) - - -(defun lsp-copilot--client-active-for-mode-p (_ mode) - (and lsp-copilot-enabled (member mode lsp-copilot-major-modes))) + :path "copilot-node-server" + :version lsp-copilot-version)) (defun lsp-copilot--find-active-workspaces () "Returns a list of lsp-copilot workspaces" @@ -179,11 +161,18 @@ automatically, browse to %s." user-code verification-uri)) (lsp-message "Authenticated as %s" user))))))) +(defun lsp-copilot-logout () + "Logout from Copilot." + (interactive) + (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-remote))) + (with-lsp-workspace workspace + (lsp-request "signOut" '(:dummy "dummy")) + (lsp--info "Logged out.")))) (defun lsp-copilot--server-initialization-options () ;; Trying to replicate Copilot.vim initialization here ... - (list :editorInfo (list :name "emacs" :version (symbol-value 'emacs-version)) - :editorPluginInfo (list :name "lsp-copilot" :version "1.38.0") + (list :editorInfo (list :name "emacs" :version emacs-version) + :editorPluginInfo (list :name "lsp-copilot" :version lsp-copilot-version) :editorConfig (list :enableAutoCompletions lsp-copilot-enabled :disabledLanguages lsp-copilot-server-disabled-languages) :name "emacs" @@ -197,9 +186,11 @@ automatically, browse to %s." user-code verification-uri)) (lsp-register-client (make-lsp-client :server-id 'copilot-ls - :new-connection (lsp-stdio-connection (lambda () - `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args))) - :activation-fn #'lsp-copilot--client-active-for-mode-p + :new-connection (lsp-stdio-connection + ;; #'lsp-copilot--cmdline + (lambda () `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args)) + ) + :activation-fn lsp-copilot-applicable-fn :multi-root lsp-copilot-server-multi-root :priority -2 :add-on? t @@ -207,7 +198,7 @@ automatically, browse to %s." user-code verification-uri)) :initialization-options #'lsp-copilot--server-initialization-options :initialized-fn #'lsp-copilot--server-initialized-fn :download-server-fn (lambda (_client callback error-callback _update?) - (lsp-package-ensure 'lsp-copilot callback error-callback)) + (lsp-package-ensure 'copilot-ls callback error-callback)) :notification-handlers (lsp-ht ("$/progress" (lambda (&rest args) (lsp-message "$/progress with %S" args))) ("featureFlagsNotification" #'ignore) diff --git a/lsp-mode.el b/lsp-mode.el index 08de53e0a9..9a59d1adb6 100644 --- a/lsp-mode.el +++ b/lsp-mode.el @@ -3824,7 +3824,8 @@ disappearing, unset all the variables related to it." (versionSupport . t))) (diagnostic . ((dynamicRegistration . :json-false) (relatedDocumentSupport . :json-false))) - (linkedEditingRange . ((dynamicRegistration . t))))) + (linkedEditingRange . ((dynamicRegistration . t))) + (inlineCompletion . ()))) (window . ((workDoneProgress . t) (showDocument . ((support . t)))))) custom-capabilities)) @@ -5105,6 +5106,7 @@ Applies on type formatting." (-lambda ((mode-or-pattern . language)) (cond ((and (stringp mode-or-pattern) + (buffer-file-name) (s-matches? mode-or-pattern (buffer-file-name))) language) ((eq mode-or-pattern major-mode) language)))) @@ -7950,12 +7952,17 @@ SESSION is the active session." (apply 'vector) (list :workspaceFolders)))) (-lambda ((&InitializeResult :capabilities)) - ;; we know that Rust Analyzer will send {} which will be parsed as null - ;; when using plists - (when (equal 'rust-analyzer server-id) - (-> capabilities - (lsp:server-capabilities-text-document-sync?) - (lsp:set-text-document-sync-options-save? t))) + (pcase server-id + ;; we know that Rust Analyzer will send {} which will be parsed as null + ;; when using plists + ('rust-analyzer + (-> capabilities + (lsp:server-capabilities-text-document-sync?) + (lsp:set-text-document-sync-options-save? t))) + ;; for copilot-ls, the inlineCompletionProvider will be sent as {} + ('copilot-ls + (-> capabilities + (lsp:set-server-capabilities-inline-completion-provider? t)))) (setf (lsp--workspace-server-capabilities workspace) capabilities (lsp--workspace-status workspace) 'initialized) @@ -8365,7 +8372,7 @@ nil." (error "The package %s is not installed. Unable to find %s" package path)) path)) -(cl-defun lsp--npm-dependency-install (callback error-callback &key package &allow-other-keys) +(cl-defun lsp--npm-dependency-install (callback error-callback &key package version &allow-other-keys) (if-let* ((npm-binary (executable-find "npm"))) (progn ;; Explicitly `make-directory' to work around NPM bug in @@ -8391,7 +8398,9 @@ nil." "--prefix" (f-join lsp-server-install-dir "npm" package) "install" - package)) + (if-let* ((version (lsp-resolve-value version))) + (format "%s@%s" package version) + package))) (lsp-log "Unable to install %s via `npm' because it is not present" package) nil)) diff --git a/lsp-protocol.el b/lsp-protocol.el index eb930dcea2..656e81f898 100644 --- a/lsp-protocol.el +++ b/lsp-protocol.el @@ -694,7 +694,7 @@ See `-let' for a description of the destructuring mechanism." (SemanticHighlightingCapabilities nil (:semanticHighlighting)) (SemanticHighlightingInformation (:line) (:tokens)) (SemanticHighlightingServerCapabilities nil (:scopes)) - (ServerCapabilities nil (:callHierarchyProvider :codeActionProvider :codeLensProvider :colorProvider :completionProvider :declarationProvider :definitionProvider :documentFormattingProvider :documentHighlightProvider :documentLinkProvider :documentOnTypeFormattingProvider :documentRangeFormattingProvider :documentSymbolProvider :executeCommandProvider :experimental :foldingRangeProvider :hoverProvider :implementationProvider :referencesProvider :renameProvider :selectionRangeProvider :semanticHighlighting :signatureHelpProvider :textDocumentSync :typeDefinitionProvider :typeHierarchyProvider :workspace :workspaceSymbolProvider :semanticTokensProvider)) + (ServerCapabilities nil (:callHierarchyProvider :codeActionProvider :codeLensProvider :colorProvider :completionProvider :declarationProvider :definitionProvider :documentFormattingProvider :documentHighlightProvider :documentLinkProvider :documentOnTypeFormattingProvider :documentRangeFormattingProvider :documentSymbolProvider :executeCommandProvider :experimental :foldingRangeProvider :hoverProvider :implementationProvider :referencesProvider :renameProvider :selectionRangeProvider :semanticHighlighting :signatureHelpProvider :textDocumentSync :typeDefinitionProvider :typeHierarchyProvider :workspace :workspaceSymbolProvider :semanticTokensProvider :inlineCompletionProvider)) (ServerInfo (:name) (:version)) (SignatureHelp (:signatures) (:activeParameter :activeSignature)) (SignatureHelpCapabilities nil (:contextSupport :dynamicRegistration :signatureInformation)) From b598971492a0ac789eca600f9f76e447b9d9ba06 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Tue, 10 Dec 2024 19:02:37 -0300 Subject: [PATCH 08/11] chore: editor plugin version is package version of lsp-mode --- clients/lsp-copilot.el | 2 +- lsp-mode.el | 10 +++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index dce8d8398c..b14dfc87cd 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -172,7 +172,7 @@ automatically, browse to %s." user-code verification-uri)) (defun lsp-copilot--server-initialization-options () ;; Trying to replicate Copilot.vim initialization here ... (list :editorInfo (list :name "emacs" :version emacs-version) - :editorPluginInfo (list :name "lsp-copilot" :version lsp-copilot-version) + :editorPluginInfo (list :name "lsp-copilot" :version (lsp-package-version)) :editorConfig (list :enableAutoCompletions lsp-copilot-enabled :disabledLanguages lsp-copilot-server-disabled-languages) :name "emacs" diff --git a/lsp-mode.el b/lsp-mode.el index 9a59d1adb6..cb438fb6f8 100644 --- a/lsp-mode.el +++ b/lsp-mode.el @@ -9608,15 +9608,19 @@ This avoids overloading the server with many files when starting Emacs." (declare-function package-desc-version "ext:package") (declare-function package--alist "ext:package") +(defun lsp-package-version () + "Returns a string with the version of the lsp-mode package" + (package-version-join + (package-desc-version + (car (alist-get 'lsp-mode (package--alist)))))) + (defun lsp-version () "Return string describing current version of `lsp-mode'." (interactive) (unless (featurep 'package) (require 'package)) (let ((ver (format "lsp-mode %s, Emacs %s, %s" - (package-version-join - (package-desc-version - (car (alist-get 'lsp-mode (package--alist))))) + (lsp-package-version) emacs-version system-type))) (if (called-interactively-p 'interactive) From e650f67c1994a57e6ca7d8414ec7bef96e9cea04 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Tue, 10 Dec 2024 19:03:01 -0300 Subject: [PATCH 09/11] chore: allow nil in copilot version --- clients/lsp-copilot.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index b14dfc87cd..dc34edd3d9 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -73,7 +73,8 @@ The input are the file name and the major mode of the buffer." (defcustom lsp-copilot-version "1.41.0" "Copilot version." - :type 'string + :type '(choice (const :tag "Latest" nil) + (string :tag "Specific Version")) :group 'lsp-copilot) (lsp-dependency 'copilot-ls From 6ebb2fe936ca359d1c8947abb53e17dd2f3557c8 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Tue, 10 Dec 2024 19:03:23 -0300 Subject: [PATCH 10/11] fix: auto-registered remote variant is called -tramp --- clients/lsp-copilot.el | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index dc34edd3d9..9e390a5a3e 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -88,12 +88,12 @@ The input are the file name and the major mode of the buffer." (-some->> (lsp-session) (lsp--session-workspaces) (--filter (member (lsp--client-server-id (lsp--workspace-client it)) - '(copilot-ls copilot-ls-remote))))) + '(copilot-ls copilot-ls-tramp))))) (defun lsp-copilot--authenticated-as () "Returns nil when not authorized; otherwise, the user name" (-if-let (workspace (--some (lsp-find-workspace it (buffer-file-name)) - '(copilot-ls copilot-ls-remote))) + '(copilot-ls copilot-ls-tramp))) (-if-let (checkStatusResponse (with-lsp-workspace workspace (lsp-request "checkStatus" '(:dummy "dummy")))) (-let* (((&copilot-ls:CheckStatusResponse? :status :user) checkStatusResponse)) @@ -126,7 +126,7 @@ The input are the file name and the major mode of the buffer." This function is automatically called during the client initialization if needed" (interactive) - (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-remote))) + (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-tramp))) (with-lsp-workspace workspace (-when-let* ((response (lsp-request "signInInitiate" '(:dummy "dummy")))) (-let (((&copilot-ls:SignInInitiateResponse? :status :user-code :verification-uri :user) response)) @@ -165,7 +165,7 @@ automatically, browse to %s." user-code verification-uri)) (defun lsp-copilot-logout () "Logout from Copilot." (interactive) - (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-remote))) + (-when-let (workspace (--some (lsp-find-workspace it) '(copilot-ls copilot-ls-tramp))) (with-lsp-workspace workspace (lsp-request "signOut" '(:dummy "dummy")) (lsp--info "Logged out.")))) @@ -188,7 +188,6 @@ automatically, browse to %s." user-code verification-uri)) (make-lsp-client :server-id 'copilot-ls :new-connection (lsp-stdio-connection - ;; #'lsp-copilot--cmdline (lambda () `(,(lsp-package-path 'copilot-ls) ,@lsp-copilot-langserver-command-args)) ) :activation-fn lsp-copilot-applicable-fn From ba7d3b5fc5ed6d6c80e9c72a30afdf4e2ea51585 Mon Sep 17 00:00:00 2001 From: Rodrigo Kassick Date: Wed, 11 Dec 2024 09:59:07 -0300 Subject: [PATCH 11/11] refactor: move copilot-specific capabilities kludge to lsp-copilot.el --- clients/lsp-copilot.el | 7 ++++++- lsp-mode.el | 6 +----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/clients/lsp-copilot.el b/clients/lsp-copilot.el index 9e390a5a3e..9b1555882f 100644 --- a/clients/lsp-copilot.el +++ b/clients/lsp-copilot.el @@ -179,7 +179,12 @@ automatically, browse to %s." user-code verification-uri)) :name "emacs" :version "0.1.0")) -(defun lsp-copilot--server-initialized-fn (_) +(defun lsp-copilot--server-initialized-fn (workspace) + ;; Patch capabilities -- server may respond with an empty dict. In plist, + ;; this would become nil + (let ((caps (lsp--workspace-server-capabilities workspace))) + (lsp:set-server-capabilities-inline-completion-provider? caps t)) + (unless (lsp-copilot--authenticated-as) (lsp-copilot-login))) diff --git a/lsp-mode.el b/lsp-mode.el index cb438fb6f8..d3e798cc48 100644 --- a/lsp-mode.el +++ b/lsp-mode.el @@ -7958,11 +7958,7 @@ SESSION is the active session." ('rust-analyzer (-> capabilities (lsp:server-capabilities-text-document-sync?) - (lsp:set-text-document-sync-options-save? t))) - ;; for copilot-ls, the inlineCompletionProvider will be sent as {} - ('copilot-ls - (-> capabilities - (lsp:set-server-capabilities-inline-completion-provider? t)))) + (lsp:set-text-document-sync-options-save? t)))) (setf (lsp--workspace-server-capabilities workspace) capabilities (lsp--workspace-status workspace) 'initialized)