lsp-kotlin.el (14622B)
1 ;;; lsp-kotlin.el --- description -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020 emacs-lsp maintainers 4 5 ;; Author: emacs-lsp maintainers 6 ;; Keywords: lsp, kotlin 7 8 ;; This program is free software; you can redistribute it and/or modify 9 ;; it under the terms of the GNU General Public License as published by 10 ;; the Free Software Foundation, either version 3 of the License, or 11 ;; (at your option) any later version. 12 13 ;; This program is distributed in the hope that it will be useful, 14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 ;; GNU General Public License for more details. 17 18 ;; You should have received a copy of the GNU General Public License 19 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 20 21 ;;; Commentary: 22 23 ;; LSP Clients for the Kotlin Programming Language. 24 25 ;;; Code: 26 27 (require 'lsp-mode) 28 (require 'cl-lib) 29 (require 'dash) 30 31 (defgroup lsp-kotlin nil 32 "LSP support for Kotlin, using KotlinLanguageServer." 33 :group 'lsp-mode 34 :link '(url-link "https://github.com/fwcd/KotlinLanguageServer")) 35 36 (define-obsolete-variable-alias 37 'lsp-kotlin-language-server-path 38 'lsp-clients-kotlin-server-executable 39 "lsp-mode 6.4") 40 41 (defcustom lsp-clients-kotlin-server-executable 42 (if (eq system-type 'windows-nt) 43 "kotlin-language-server.bat" 44 "kotlin-language-server") 45 "The kotlin-language-server executable to use. 46 Leave as just the executable name to use the default behavior of finding the 47 executable with `exec-path'." 48 :type 'string 49 :group 'lsp-kotlin) 50 51 (defcustom lsp-kotlin-trace-server "off" 52 "Traces the communication between VSCode and the Kotlin language server." 53 :type '(choice (:tag "off" "messages" "verbose")) 54 :group 'lsp-kotlin 55 :package-version '(lsp-mode . "6.1")) 56 57 (defcustom lsp-kotlin-compiler-jvm-target "1.8" 58 "Specifies the JVM target, e.g. \"1.6\" or \"1.8\"." 59 :type 'string 60 :group 'lsp-kotlin 61 :package-version '(lsp-mode . "6.1")) 62 63 (defcustom lsp-kotlin-linting-debounce-time 250 64 "[DEBUG] Specifies the debounce time limit. 65 Lower to increase responsiveness at the cost of possible stability issues." 66 :type 'number 67 :group 'lsp-kotlin 68 :package-version '(lsp-mode . "6.1")) 69 70 (defcustom lsp-kotlin-completion-snippets-enabled t 71 "Specifies whether code completion should provide snippets (true) or 72 plain-text items (false)." 73 :type 'boolean 74 :group 'lsp-kotlin 75 :package-version '(lsp-mode . "6.1")) 76 77 (defcustom lsp-kotlin-debug-adapter-enabled t 78 "[Recommended] Specifies whether the debug adapter should be used. 79 When enabled a debugger for Kotlin will be available." 80 :type 'boolean) 81 82 (defcustom lsp-kotlin-debug-adapter-path "" 83 "Optionally a custom path to the debug adapter executable." 84 :type 'string 85 :group 'lsp-kotlin 86 :package-version '(lsp-mode . "6.1")) 87 88 (defcustom lsp-kotlin-external-sources-use-kls-scheme t 89 "[Recommended] Specifies whether URIs inside JARs should be represented 90 using the `kls'-scheme." 91 :type 'boolean 92 :group 'lsp-kotlin 93 :package-version '(lsp-mode . "6.1")) 94 95 (defcustom lsp-kotlin-external-sources-auto-convert-to-kotlin t 96 "Specifies whether decompiled/external classes should be auto-converted 97 to Kotlin." 98 :type 'boolean 99 :group 'lsp-kotlin 100 :package-version '(lsp-mode . "6.1")) 101 102 (defcustom lsp-kotlin-server-download-url 103 "https://github.com/fwcd/kotlin-language-server/releases/latest/download/server.zip" 104 "The URL for the language server download." 105 :type 'string 106 :group 'lsp-kotlin 107 :package-version '(lsp-mode . "9.0.0")) 108 109 (defcustom lsp-kotlin-workspace-dir (expand-file-name (locate-user-emacs-file "workspace/")) 110 "LSP kotlin workspace directory." 111 :group 'lsp-kotlin 112 :risky t 113 :type 'directory) 114 115 (defcustom lsp-kotlin-workspace-cache-dir (expand-file-name ".cache/" lsp-kotlin-workspace-dir) 116 "LSP kotlin workspace cache directory." 117 :group 'lsp-kotlin 118 :risky t 119 :type 'directory) 120 121 ;; cache in this case is the dependency cache. Given as an initialization option. 122 (defcustom lsp-kotlin-ondisk-cache-path nil 123 "Path to the ondisk cache if used. If lsp-kotlin-ondisk-cache-enabled is t, 124 but path is nil, then the project root is used as a default." 125 :type 'string 126 :group 'lsp-kotlin) 127 128 (defcustom lsp-kotlin-ondisk-cache-enabled nil 129 "Specifies whether to enable ondisk cache or not. If nil, in-memory cache 130 will be used." 131 :type 'boolean 132 :group 'lsp-kotlin) 133 134 (defcustom lsp-kotlin-inlayhints-enable-typehints t 135 "Specifies whether to enable type hints or not. 136 Requires lsp-inlay-hints-mode." 137 :type 'boolean 138 :group 'lsp-kotlin) 139 140 (defcustom lsp-kotlin-inlayhints-enable-parameterhints t 141 "Specifies whether to enable parameter hints or not. 142 Requires lsp-inlay-hints-mode." 143 :type 'boolean 144 :group 'lsp-kotlin) 145 146 (defcustom lsp-kotlin-inlayhints-enable-chainedhints t 147 "Specifies whether to enable chained hints or not. 148 Requires lsp-inlay-hints-mode." 149 :type 'boolean 150 :group 'lsp-kotlin) 151 152 (lsp-register-custom-settings 153 '(("kotlin.externalSources.autoConvertToKotlin" lsp-kotlin-external-sources-auto-convert-to-kotlin t) 154 ("kotlin.externalSources.useKlsScheme" lsp-kotlin-external-sources-use-kls-scheme t) 155 ("kotlin.debugAdapter.path" lsp-kotlin-debug-adapter-path) 156 ("kotlin.debugAdapter.enabled" lsp-kotlin-debug-adapter-enabled t) 157 ("kotlin.completion.snippets.enabled" lsp-kotlin-completion-snippets-enabled t) 158 ("kotlin.linting.debounceTime" lsp-kotlin-linting-debounce-time) 159 ("kotlin.compiler.jvm.target" lsp-kotlin-compiler-jvm-target) 160 ("kotlin.trace.server" lsp-kotlin-trace-server) 161 ("kotlin.languageServer.path" lsp-clients-kotlin-server-executable) 162 ("kotlin.inlayHints.typeHints" lsp-kotlin-inlayhints-enable-typehints t) 163 ("kotlin.inlayHints.parameterHints" lsp-kotlin-inlayhints-enable-parameterhints t) 164 ("kotlin.inlayHints.chainedHints" lsp-kotlin-inlayhints-enable-chainedhints t))) 165 166 (defvar lsp-kotlin--language-server-path 167 (f-join lsp-server-install-dir 168 "kotlin" "server" "bin" (if (eq system-type 'windows-nt) 169 "kotlin-language-server.bat" 170 "kotlin-language-server")) 171 "The path to store the language server at if necessary.") 172 173 174 ;; Debug and running 175 (declare-function dap-debug "ext:dap-mode" (template) t) 176 177 (defun lsp-kotlin-run-main (main-class project-root debug?) 178 (require 'dap-kotlin) 179 (dap-debug (list :type "kotlin" 180 :request "launch" 181 :mainClass main-class 182 :projectRoot project-root 183 :noDebug (not debug?)))) 184 185 (defun lsp-kotlin-lens-backend (_modified? callback) 186 (when lsp-kotlin-debug-adapter-enabled 187 (lsp-request-async 188 "kotlin/mainClass" 189 (list :uri (lsp--buffer-uri)) 190 (lambda (mainInfo) 191 (let ((main-class (lsp-get mainInfo :mainClass)) 192 (project-root (lsp-get mainInfo :projectRoot)) 193 (range (lsp-get mainInfo :range))) 194 (funcall callback 195 (list (lsp-make-code-lens :range range 196 :command 197 (lsp-make-command 198 :title "Run" 199 :command (lambda () 200 (interactive) 201 (lsp-kotlin-run-main main-class project-root nil)))) 202 (lsp-make-code-lens :range range 203 :command 204 (lsp-make-command 205 :title "Debug" 206 :command (lambda () 207 (interactive) 208 (lsp-kotlin-run-main main-class project-root t))))) 209 lsp--cur-version))) 210 :mode 'tick))) 211 212 (defvar lsp-lens-backends) 213 (declare-function lsp-lens-refresh "lsp-lens" (buffer-modified? &optional buffer)) 214 215 (define-minor-mode lsp-kotlin-lens-mode 216 "Toggle run/debug overlays." 217 :group 'lsp-kotlin 218 :global nil 219 :init-value nil 220 :lighter nil 221 (cond 222 (lsp-kotlin-lens-mode 223 (require 'lsp-lens) 224 ;; set lens backends so they are available is lsp-lens-mode is activated 225 ;; 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 226 (setq-local lsp-lens-backends (list #'lsp-kotlin-lens-backend)) 227 (lsp-lens-refresh t)) 228 (t (setq-local lsp-lens-backends (delete #'lsp-kotlin-lens-backend lsp-lens-backends))))) 229 230 231 ;; Stolen from lsp-java: 232 ;; https://github.com/emacs-lsp/lsp-java/blob/a1aff851bcf4f397f2a968557d213db1fede0c8a/lsp-java.el#L1065 233 (declare-function helm-make-source "ext:helm-source") 234 (defvar lsp-kotlin--helm-result nil) 235 (defun lsp-kotlin--completing-read-multiple (message items initial-selection) 236 (if (functionp 'helm) 237 (progn 238 (require 'helm-source) 239 (helm :sources (helm-make-source 240 message 'helm-source-sync :candidates items 241 :action '(("Identity" lambda (_) 242 (setq lsp-kotlin--helm-result (helm-marked-candidates))))) 243 :buffer "*lsp-kotlin select*" 244 :prompt message) 245 lsp-kotlin--helm-result) 246 (if (functionp 'ivy-read) 247 (let (result) 248 (ivy-read message (mapcar #'car items) 249 :action (lambda (c) (setq result (list (cdr (assoc c items))))) 250 :multi-action 251 (lambda (candidates) 252 (setq result (mapcar (lambda (c) (cdr (assoc c items))) candidates)))) 253 result) 254 (let ((deps initial-selection) dep) 255 (while (setq dep (cl-rest (lsp--completing-read 256 (if deps 257 (format "%s (selected %s): " message (length deps)) 258 (concat message ": ")) 259 items 260 (-lambda ((name . id)) 261 (if (-contains? deps id) 262 (concat name " ✓") 263 name))))) 264 (if (-contains? deps dep) 265 (setq deps (remove dep deps)) 266 (cl-pushnew dep deps))) 267 deps)))) 268 269 (defun lsp-kotlin-implement-member () 270 (interactive) 271 (lsp-request-async 272 "kotlin/overrideMember" 273 (list :textDocument (list :uri (lsp--buffer-uri)) 274 :position (lsp--cur-position)) 275 (lambda (member-options) 276 (-if-let* ((option-items (-map (lambda (x) 277 (list (lsp-get x :title) 278 (lsp-get (lsp-get (lsp-get x :edit) 279 :changes) 280 (intern (concat ":" (lsp--buffer-uri)))))) 281 member-options)) 282 (selected-members (lsp-kotlin--completing-read-multiple "Select overrides" option-items nil))) 283 (dolist (edit (-flatten selected-members)) 284 (lsp--apply-text-edits edit)))))) 285 286 (defun lsp-kotlin--parse-uri (uri) 287 "Get the path for where we'll store the file, calculating it based on URI." 288 (or (save-match-data 289 (when (string-match "kls:file:///\\(.*\\)!/\\(.*\.\\(class\\|java\\|kt\\)\\)?.*" uri) 290 (let* ((jar-path (match-string 1 uri)) 291 (file-path (match-string 2 uri)) 292 (lib-name (string-join (last (split-string jar-path "/") 2) ".")) 293 (buffer-name (replace-regexp-in-string "/" "." file-path t t)) 294 (file-location (expand-file-name (concat lsp-kotlin-workspace-cache-dir "/" lib-name "/" buffer-name)))) 295 file-location))) 296 (error "Unable to match %s" uri))) 297 298 (defun lsp-kotlin--uri-handler (uri) 299 "Load a file corresponding to URI executing request to the kotlin server." 300 (let ((file-location (lsp-kotlin--parse-uri uri))) 301 (unless (file-readable-p file-location) 302 (lsp-kotlin--ensure-dir (file-name-directory file-location)) 303 (with-lsp-workspace (lsp-find-workspace 'kotlin-ls nil) 304 (let ((content (lsp-send-request (lsp-make-request 305 "kotlin/jarClassContents" 306 (list :uri uri))))) 307 (with-temp-file file-location 308 (insert content))))) 309 file-location)) 310 311 (defun lsp-kotlin--ensure-dir (path) 312 "Ensure that directory PATH exists." 313 (unless (file-directory-p path) 314 (make-directory path t))) 315 316 (lsp-dependency 317 'kotlin-language-server 318 `(:system ,lsp-clients-kotlin-server-executable) 319 `(:download :url lsp-kotlin-server-download-url 320 :decompress :zip 321 :store-path ,(f-join lsp-server-install-dir "kotlin" "kotlin-language-server.zip") 322 :binary-path lsp-clients-kotlin-server-executable 323 :set-executable? t)) 324 325 (lsp-register-client 326 (make-lsp-client 327 :new-connection (lsp-stdio-connection (lambda () 328 `(,(or (when (f-exists? lsp-kotlin--language-server-path) 329 lsp-kotlin--language-server-path) 330 (or (executable-find lsp-clients-kotlin-server-executable) 331 (lsp-package-path 'kotlin-language-server)) 332 "kotlin-language-server")))) 333 :major-modes '(kotlin-mode kotlin-ts-mode) 334 :priority -1 335 :server-id 'kotlin-ls 336 :uri-handlers (lsp-ht ("kls" #'lsp-kotlin--uri-handler)) 337 :initialized-fn (lambda (workspace) 338 (with-lsp-workspace workspace 339 (lsp--set-configuration (lsp-configuration-section "kotlin")))) 340 :initialization-options (lambda () 341 (when lsp-kotlin-ondisk-cache-enabled 342 (list :storagePath (or lsp-kotlin-ondisk-cache-path 343 (lsp-workspace-root))))) 344 :download-server-fn (lambda (_client callback error-callback _update?) 345 (lsp-package-ensure 'kotlin-language-server callback error-callback)))) 346 347 (lsp-consistency-check lsp-kotlin) 348 349 (provide 'lsp-kotlin) 350 ;;; lsp-kotlin.el ends here