Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/lsp copilot #4635

Merged
merged 11 commits into from
Dec 20, 2024
216 changes: 216 additions & 0 deletions clients/lsp-copilot.el
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
;;; 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-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 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."
:type 'boolean
:group 'lsp-copilot)

(defcustom lsp-copilot-version "1.41.0"
"Copilot version."
:type '(choice (const :tag "Latest" nil)
(string :tag "Specific Version"))
:group 'lsp-copilot)

(lsp-dependency 'copilot-ls
`(:system ,lsp-copilot-executable)
'(:npm :package "copilot-node-server"
:path "copilot-node-server"
:version lsp-copilot-version))

(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))
'(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-tramp)))
(-if-let (checkStatusResponse (with-lsp-workspace workspace
(lsp-request "checkStatus" '(:dummy "dummy"))))
(-let* (((&copilot-ls:CheckStatusResponse? :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) '(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))

;; 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)))
((&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")))
((&copilot-ls:CheckStatusResponse? :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-logout ()
"Logout from Copilot."
(interactive)
(-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."))))

(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-package-version))
: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)
;; 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)))

;; Server installed by emacs
(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-applicable-fn
: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 'copilot-ls 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-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
32 changes: 17 additions & 15 deletions lsp-inline-completion.el
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
35 changes: 22 additions & 13 deletions 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 Expand Up @@ -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))
Expand Down Expand Up @@ -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))))
Expand Down Expand Up @@ -7950,12 +7952,13 @@ 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))))

(setf (lsp--workspace-server-capabilities workspace) capabilities
(lsp--workspace-status workspace) 'initialized)
Expand Down Expand Up @@ -8365,7 +8368,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
Expand All @@ -8391,7 +8394,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))

Expand Down Expand Up @@ -9599,15 +9604,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)
Expand Down
7 changes: 6 additions & 1 deletion lsp-protocol.el
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -689,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))
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
Loading