config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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