Skip to content

Commit

Permalink
feat: lsp-copilot client for copilot-node-server
Browse files Browse the repository at this point in the history
  • Loading branch information
kassick committed Dec 5, 2024
1 parent 44aafbb commit 2df4263
Show file tree
Hide file tree
Showing 4 changed files with 260 additions and 1 deletion.
250 changes: 250 additions & 0 deletions clients/lsp-copilot.el
Original file line number Diff line number Diff line change
@@ -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 <[email protected]>
;; 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 <https://www.gnu.org/licenses/>.

;; 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)
8 changes: 8 additions & 0 deletions docs/lsp-clients.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion lsp-mode.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 2df4263

Please sign in to comment.