Archived
1
0
Fork 0
This repository has been archived on 2024-10-19. You can view files and clone it, but cannot push or open issues or pull requests.
emacs/code/elpa/lsp-mode-20240319.1043/lsp-kotlin.el

351 lines
14 KiB
EmacsLisp
Raw Permalink Normal View History

2024-03-20 13:57:39 +00:00
;;; lsp-kotlin.el --- description -*- lexical-binding: t; -*-
;; Copyright (C) 2020 emacs-lsp maintainers
;; Author: emacs-lsp maintainers
;; Keywords: lsp, kotlin
;; 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 Clients for the Kotlin Programming Language.
;;; Code:
(require 'lsp-mode)
(require 'cl-lib)
(require 'dash)
(defgroup lsp-kotlin nil
"LSP support for Kotlin, using KotlinLanguageServer."
:group 'lsp-mode
:link '(url-link "https://github.com/fwcd/KotlinLanguageServer"))
(define-obsolete-variable-alias
'lsp-kotlin-language-server-path
'lsp-clients-kotlin-server-executable
"lsp-mode 6.4")
(defcustom lsp-clients-kotlin-server-executable
(if (eq system-type 'windows-nt)
"kotlin-language-server.bat"
"kotlin-language-server")
"The kotlin-language-server executable to use.
Leave as just the executable name to use the default behavior of finding the
executable with `exec-path'."
:type 'string
:group 'lsp-kotlin)
(defcustom lsp-kotlin-trace-server "off"
"Traces the communication between VSCode and the Kotlin language server."
:type '(choice (:tag "off" "messages" "verbose"))
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-compiler-jvm-target "1.8"
"Specifies the JVM target, e.g. \"1.6\" or \"1.8\"."
:type 'string
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-linting-debounce-time 250
"[DEBUG] Specifies the debounce time limit.
Lower to increase responsiveness at the cost of possible stability issues."
:type 'number
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-completion-snippets-enabled t
"Specifies whether code completion should provide snippets (true) or
plain-text items (false)."
:type 'boolean
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-debug-adapter-enabled t
"[Recommended] Specifies whether the debug adapter should be used.
When enabled a debugger for Kotlin will be available."
:type 'boolean)
(defcustom lsp-kotlin-debug-adapter-path ""
"Optionally a custom path to the debug adapter executable."
:type 'string
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-external-sources-use-kls-scheme t
"[Recommended] Specifies whether URIs inside JARs should be represented
using the `kls'-scheme."
:type 'boolean
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-external-sources-auto-convert-to-kotlin t
"Specifies whether decompiled/external classes should be auto-converted
to Kotlin."
:type 'boolean
:group 'lsp-kotlin
:package-version '(lsp-mode . "6.1"))
(defcustom lsp-kotlin-server-download-url
"https://github.com/fwcd/kotlin-language-server/releases/latest/download/server.zip"
"The URL for the language server download."
:type 'string
:group 'lsp-kotlin
:package-version '(lsp-mode . "8.0.1"))
(defcustom lsp-kotlin-workspace-dir (expand-file-name (locate-user-emacs-file "workspace/"))
"LSP kotlin workspace directory."
:group 'lsp-kotlin
:risky t
:type 'directory)
(defcustom lsp-kotlin-workspace-cache-dir (expand-file-name ".cache/" lsp-kotlin-workspace-dir)
"LSP kotlin workspace cache directory."
:group 'lsp-kotlin
:risky t
:type 'directory)
;; cache in this case is the dependency cache. Given as an initialization option.
(defcustom lsp-kotlin-ondisk-cache-path nil
"Path to the ondisk cache if used. If lsp-kotlin-ondisk-cache-enabled is t,
but path is nil, then the project root is used as a default."
:type 'string
:group 'lsp-kotlin)
(defcustom lsp-kotlin-ondisk-cache-enabled nil
"Specifies whether to enable ondisk cache or not. If nil, in-memory cache
will be used."
:type 'boolean
:group 'lsp-kotlin)
(defcustom lsp-kotlin-inlayhints-enable-typehints t
"Specifies whether to enable type hints or not.
Requires lsp-inlay-hints-mode."
:type 'boolean
:group 'lsp-kotlin)
(defcustom lsp-kotlin-inlayhints-enable-parameterhints t
"Specifies whether to enable parameter hints or not.
Requires lsp-inlay-hints-mode."
:type 'boolean
:group 'lsp-kotlin)
(defcustom lsp-kotlin-inlayhints-enable-chainedhints t
"Specifies whether to enable chained hints or not.
Requires lsp-inlay-hints-mode."
:type 'boolean
:group 'lsp-kotlin)
(lsp-register-custom-settings
'(("kotlin.externalSources.autoConvertToKotlin" lsp-kotlin-external-sources-auto-convert-to-kotlin t)
("kotlin.externalSources.useKlsScheme" lsp-kotlin-external-sources-use-kls-scheme t)
("kotlin.debugAdapter.path" lsp-kotlin-debug-adapter-path)
("kotlin.debugAdapter.enabled" lsp-kotlin-debug-adapter-enabled t)
("kotlin.completion.snippets.enabled" lsp-kotlin-completion-snippets-enabled t)
("kotlin.linting.debounceTime" lsp-kotlin-linting-debounce-time)
("kotlin.compiler.jvm.target" lsp-kotlin-compiler-jvm-target)
("kotlin.trace.server" lsp-kotlin-trace-server)
("kotlin.languageServer.path" lsp-clients-kotlin-server-executable)
("kotlin.inlayHints.typeHints" lsp-kotlin-inlayhints-enable-typehints t)
("kotlin.inlayHints.parameterHints" lsp-kotlin-inlayhints-enable-parameterhints t)
("kotlin.inlayHints.chainedHints" lsp-kotlin-inlayhints-enable-chainedhints t)))
(defvar lsp-kotlin--language-server-path
(f-join lsp-server-install-dir
"kotlin" "server" "bin" (if (eq system-type 'windows-nt)
"kotlin-language-server.bat"
"kotlin-language-server"))
"The path to store the language server at if necessary.")
;; Debug and running
(declare-function dap-debug "ext:dap-mode" (template) t)
(defun lsp-kotlin-run-main (main-class project-root debug?)
(require 'dap-kotlin)
(dap-debug (list :type "kotlin"
:request "launch"
:mainClass main-class
:projectRoot project-root
:noDebug (not debug?))))
(defun lsp-kotlin-lens-backend (_modified? callback)
(when lsp-kotlin-debug-adapter-enabled
(lsp-request-async
"kotlin/mainClass"
(list :uri (lsp--buffer-uri))
(lambda (mainInfo)
(let ((main-class (lsp-get mainInfo :mainClass))
(project-root (lsp-get mainInfo :projectRoot))
(range (lsp-get mainInfo :range)))
(funcall callback
(list (lsp-make-code-lens :range range
:command
(lsp-make-command
:title "Run"
:command (lambda ()
(interactive)
(lsp-kotlin-run-main main-class project-root nil))))
(lsp-make-code-lens :range range
:command
(lsp-make-command
:title "Debug"
:command (lambda ()
(interactive)
(lsp-kotlin-run-main main-class project-root t)))))
lsp--cur-version)))
:mode 'tick)))
(defvar lsp-lens-backends)
(declare-function lsp-lens-refresh "lsp-lens" (buffer-modified? &optional buffer))
(define-minor-mode lsp-kotlin-lens-mode
"Toggle run/debug overlays."
:group 'lsp-kotlin
:global nil
:init-value nil
:lighter nil
(cond
(lsp-kotlin-lens-mode
(require 'lsp-lens)
;; set lens backends so they are available is lsp-lens-mode is activated
;; backend does not support lenses, and block our other ones from showing. When backend support lenses again, we can use cl-pushnew to add it to lsp-lens-backends instead of overwriting
(setq-local lsp-lens-backends (list #'lsp-kotlin-lens-backend))
(lsp-lens-refresh t))
(t (setq-local lsp-lens-backends (delete #'lsp-kotlin-lens-backend lsp-lens-backends)))))
;; Stolen from lsp-java:
;; https://github.com/emacs-lsp/lsp-java/blob/a1aff851bcf4f397f2a968557d213db1fede0c8a/lsp-java.el#L1065
(declare-function helm-make-source "ext:helm-source")
(defvar lsp-kotlin--helm-result nil)
(defun lsp-kotlin--completing-read-multiple (message items initial-selection)
(if (functionp 'helm)
(progn
(require 'helm-source)
(helm :sources (helm-make-source
message 'helm-source-sync :candidates items
:action '(("Identity" lambda (_)
(setq lsp-kotlin--helm-result (helm-marked-candidates)))))
:buffer "*lsp-kotlin select*"
:prompt message)
lsp-kotlin--helm-result)
(if (functionp 'ivy-read)
(let (result)
(ivy-read message (mapcar #'car items)
:action (lambda (c) (setq result (list (cdr (assoc c items)))))
:multi-action
(lambda (candidates)
(setq result (mapcar (lambda (c) (cdr (assoc c items))) candidates))))
result)
(let ((deps initial-selection) dep)
(while (setq dep (cl-rest (lsp--completing-read
(if deps
(format "%s (selected %s): " message (length deps))
(concat message ": "))
items
(-lambda ((name . id))
(if (-contains? deps id)
(concat name "")
name)))))
(if (-contains? deps dep)
(setq deps (remove dep deps))
(cl-pushnew dep deps)))
deps))))
(defun lsp-kotlin-implement-member ()
(interactive)
(lsp-request-async
"kotlin/overrideMember"
(list :textDocument (list :uri (lsp--buffer-uri))
:position (lsp--cur-position))
(lambda (member-options)
(-if-let* ((option-items (-map (lambda (x)
(list (lsp-get x :title)
(lsp-get (lsp-get (lsp-get x :edit)
:changes)
(intern (concat ":" (lsp--buffer-uri))))))
member-options))
(selected-members (lsp-kotlin--completing-read-multiple "Select overrides" option-items nil)))
(dolist (edit (-flatten selected-members))
(lsp--apply-text-edits edit))))))
(defun lsp-kotlin--parse-uri (uri)
"Get the path for where we'll store the file, calculating it based on URI."
(or (save-match-data
(when (string-match "kls:file:///\\(.*\\)!/\\(.*\.\\(class\\|java\\|kt\\)\\)?.*" uri)
(let* ((jar-path (match-string 1 uri))
(file-path (match-string 2 uri))
(lib-name (string-join (last (split-string jar-path "/") 2) "."))
(buffer-name (replace-regexp-in-string "/" "." file-path t t))
(file-location (expand-file-name (concat lsp-kotlin-workspace-cache-dir "/" lib-name "/" buffer-name))))
file-location)))
(error "Unable to match %s" uri)))
(defun lsp-kotlin--uri-handler (uri)
"Load a file corresponding to URI executing request to the kotlin server."
(let ((file-location (lsp-kotlin--parse-uri uri)))
(unless (file-readable-p file-location)
(lsp-kotlin--ensure-dir (file-name-directory file-location))
(with-lsp-workspace (lsp-find-workspace 'kotlin-ls nil)
(let ((content (lsp-send-request (lsp-make-request
"kotlin/jarClassContents"
(list :uri uri)))))
(with-temp-file file-location
(insert content)))))
file-location))
(defun lsp-kotlin--ensure-dir (path)
"Ensure that directory PATH exists."
(unless (file-directory-p path)
(make-directory path t)))
(lsp-dependency
'kotlin-language-server
`(:system ,lsp-clients-kotlin-server-executable)
`(:download :url lsp-kotlin-server-download-url
:decompress :zip
:store-path ,(f-join lsp-server-install-dir "kotlin" "kotlin-language-server.zip")
:binary-path lsp-clients-kotlin-server-executable
:set-executable? t))
(lsp-register-client
(make-lsp-client
:new-connection (lsp-stdio-connection (lambda ()
`(,(or (when (f-exists? lsp-kotlin--language-server-path)
lsp-kotlin--language-server-path)
(or (executable-find lsp-clients-kotlin-server-executable)
(lsp-package-path 'kotlin-language-server))
"kotlin-language-server"))))
:major-modes '(kotlin-mode kotlin-ts-mode)
:priority -1
:server-id 'kotlin-ls
:uri-handlers (lsp-ht ("kls" #'lsp-kotlin--uri-handler))
:initialized-fn (lambda (workspace)
(with-lsp-workspace workspace
(lsp--set-configuration (lsp-configuration-section "kotlin"))))
:initialization-options (lambda ()
(when lsp-kotlin-ondisk-cache-enabled
(list :storagePath (or lsp-kotlin-ondisk-cache-path
(lsp-workspace-root)))))
:download-server-fn (lambda (_client callback error-callback _update?)
(lsp-package-ensure 'kotlin-language-server callback error-callback))))
(lsp-consistency-check lsp-kotlin)
(provide 'lsp-kotlin)
;;; lsp-kotlin.el ends here