351 lines
14 KiB
EmacsLisp
351 lines
14 KiB
EmacsLisp
;;; 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
|