;;; lsp-clients.el --- lightweight clients                       -*- lexical-binding: t; -*-

;; Copyright (C) 2018 Ivan Yonchovski

;; Author: Ivan Yonchovski <ivan.yonchovski@tick42.com>
;; Keywords: languages

;; 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:

;; Contains definitions for the simple clients.

;;; Code:

(require 'lsp-mode)
(require 'dash)
(require 'dash-functional)
(require 'rx)
(require 'cl-lib)
(require 'em-glob)

;;; Ada
(defgroup lsp-ada nil
  "Settings for Ada Language Server."
  :group 'tools
  :tag "Language Server"
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-ada-project-file "default.gpr"
  "Set the project file full path to configure the language server with.
  The ~ prefix (for the user home directory) is supported.
  See https://github.com/AdaCore/ada_language_server for a per-project
  configuration example."
  :type 'string
  :group 'lsp-ada
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-ada-option-charset "UTF-8"
  "The charset to use by the Ada Language server. Defaults to 'UTF-8'."
  :type 'string
  :group 'lsp-ada
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-ada-enable-diagnostics t
  "A boolean to disable diagnostics. Defaults to true."
  :type 'boolean
  :group 'lsp-ada
  :package-version '(lsp-mode . "6.2"))

(lsp-register-custom-settings
 '(("ada.projectFile" lsp-ada-project-file)
   ("ada.enableDiagnostics" lsp-ada-enable-diagnostics)
   ("ada.defaultCharset" lsp-ada-option-charset)))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection '("ada_language_server"))
                  :major-modes '(ada-mode)
                  :priority -1
                  :initialized-fn (lambda (workspace)
                                    (with-lsp-workspace workspace
                                      (lsp--set-configuration
                                       (lsp-configuration-section "ada"))))
                  :server-id 'ada-ls))

;;; Bash
(defgroup lsp-bash nil
  "Settings for the Bash Language Server."
  :group 'tools
  :tag "Language Server"
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-bash-explainshell-endpoint nil
  "The endpoint to use explainshell.com to answer 'onHover' queries.
See instructions at https://marketplace.visualstudio.com/items?itemName=mads-hartmann.bash-ide-vscode"
  :type 'string
  :risky t
  :group 'lsp-bash
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-bash-highlight-parsing-errors nil
  "Consider parsing errors in scripts as 'problems'."
  :type 'boolean
  :group 'lsp-bash
  :package-version '(lsp-mode . "6.2"))

(defcustom lsp-bash-glob-pattern nil
  "Glob pattern used to find shell script files to parse."
  :type 'string
  :group 'lsp-bash
  :package-version '(lsp-mode . "6.3"))

(defun lsp-bash--bash-ls-server-command ()
  "Startup command for Bash language server."
  (list (lsp-package-path 'bash-language-server) "start"))

(lsp-dependency 'bash-language-server
                '(:system "bash-language-server")
                '(:npm :package "bash-language-server"
                       :path "bash-language-server"))

(lsp-register-client
 (make-lsp-client
  :new-connection (lsp-stdio-connection #'lsp-bash--bash-ls-server-command)
  :major-modes '(sh-mode)
  :priority -1
  :environment-fn (lambda ()
                    '(("EXPLAINSHELL_ENDPOINT" . lsp-bash-explainshell-endpoint)
                      ("HIGHLIGHT_PARSING_ERRORS" . lsp-bash-highlight-parsing-errors)
                      ("GLOB_PATTERN" . lsp-bash-glob-pattern)))
  :server-id 'bash-ls
  :download-server-fn (lambda (_client callback error-callback _update?)
                        (lsp-package-ensure 'bash-language-server callback error-callback))))


;;; Groovy
(defgroup lsp-groovy nil
  "LSP support for Groovy, using groovy-language-server"
  :group 'lsp-mode
  :link '(url-link "https://github.com/prominic/groovy-language-server"))

(defcustom lsp-groovy-server-file
  (locate-user-emacs-file "groovy-language-server/groovy-language-server-all.jar")
  "JAR file path for groovy-language-server-all.jar."
  :group 'lsp-groovy
  :risky t
  :type 'file)

(defun lsp-groovy--lsp-command ()
  "Generate LSP startup command."
  `("java" "-jar" ,(expand-file-name lsp-groovy-server-file)))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection 'lsp-groovy--lsp-command)
                  :major-modes '(groovy-mode)
                  :priority -1
                  :server-id 'groovy-ls))

;;; TypeScript/JavaScript

(lsp-dependency 'javascript-typescript-langserver
                '(:system "javascript-typescript-stdio")
                '(:npm :package "javascript-typescript-langserver"
                       :path "javascript-typescript-stdio"))

(defgroup lsp-typescript-javascript nil
  "Support for TypeScript/JavaScript, using Sourcegraph's JavaScript/TypeScript language server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/sourcegraph/javascript-typescript-langserver"))

(defcustom lsp-clients-typescript-javascript-server-args '()
  "Extra arguments for the typescript-language-server language server."
  :group 'lsp-typescript-javascript
  :risky t
  :type '(repeat string))

(defun lsp-typescript-javascript-tsx-jsx-activate-p (filename &optional _)
  "Check if the javascript-typescript language server should be enabled based on FILENAME."
  (or (string-match-p (rx (one-or-more anything) "." (or "ts" "js") (opt "x") string-end) filename)
      (and (derived-mode-p 'js-mode 'js2-mode 'typescript-mode)
           (not (derived-mode-p 'json-mode)))))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection (lambda ()
                                                          (cons (lsp-package-path 'javascript-typescript-langserver)
                                                                lsp-clients-typescript-javascript-server-args)))
                  :activation-fn 'lsp-typescript-javascript-tsx-jsx-activate-p
                  :priority -3
                  :completion-in-comments? t
                  :server-id 'jsts-ls
                  :download-server-fn (lambda (_client callback error-callback _update?)
                                        (lsp-package-ensure
                                         'javascript-typescript-langserver
                                         callback
                                         error-callback))))


;;; TypeScript
(defgroup lsp-typescript nil
  "LSP support for TypeScript, using Theia/Typefox's TypeScript Language Server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/theia-ide/typescript-language-server"))

(defcustom lsp-clients-typescript-server-args '("--stdio")
  "Extra arguments for the typescript-language-server language server."
  :group 'lsp-typescript
  :risky t
  :type '(repeat string))

(defcustom lsp-clients-typescript-log-verbosity "info"
  "The server log verbosity."
  :group 'lsp-typescript
  :type 'string)

(defcustom lsp-clients-typescript-plugins (vector)
  "The list of plugins to load.
It should be a vector of plist with keys `:location' and `:name'
where `:name' is the name of the package and `:location' is the
directory containing the package. Example:
\(vector
   \(list :name \"@vsintellicode/typescript-intellicode-plugin\"
         :location \"<path>.vscode/extensions/visualstudioexptteam.vscodeintellicode-1.1.9/\"))"
  :group 'lsp-typescript
  :type  '(restricted-sexp :tag "Vector"
                           :match-alternatives
                           (lambda (xs)
                             (and (vectorp xs) (seq-every-p
                                                (-lambda ((&plist :name :location))
                                                  (and name location))
                                                xs)))))

(lsp-dependency 'typescript-language-server
                '(:system "typescript-language-server")
                '(:npm :package "typescript-language-server"
                       :path "typescript-language-server"))

(lsp-dependency 'typescript
                '(:system "tsserver")
                '  (:npm :package "typescript"
                         :path "tsserver"))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection (lambda ()
                                                          `(,(lsp-package-path 'typescript-language-server)
                                                            "--tsserver-path"
                                                            ,(lsp-package-path 'typescript)
                                                            ,@lsp-clients-typescript-server-args)))
                  :activation-fn 'lsp-typescript-javascript-tsx-jsx-activate-p
                  :priority -2
                  :completion-in-comments? t
                  :initialization-options (lambda ()
                                            (list :plugins lsp-clients-typescript-plugins
                                                  :logVerbosity lsp-clients-typescript-log-verbosity
                                                  :tsServerPath (lsp-package-path 'typescript)))
                  :ignore-messages '("readFile .*? requested by TypeScript but content not available")
                  :server-id 'ts-ls
                  :download-server-fn (lambda (_client callback error-callback _update?)
                                        (lsp-package-ensure
                                         'typescript
                                         (-partial #'lsp-package-ensure
                                                   'typescript-language-server
                                                   callback
                                                   error-callback)
                                         error-callback))))



;;; JavaScript Flow
(defgroup lsp-flow nil
  "LSP support for the Flow Javascript type checker."
  :group 'lsp-mode
  :link '(url-link "https://flow.org/"))

(defcustom lsp-clients-flow-server "flow"
  "The Flow executable to use.
Leave as just the executable name to use the default behavior of
finding the executable with variable `exec-path'."
  :group 'lsp-flow
  :risky t
  :type 'file)

(defcustom lsp-clients-flow-server-args '("lsp")
  "Extra arguments for starting the Flow language server."
  :group 'lsp-flow
  :risky t
  :type '(repeat string))

(defun lsp-clients-flow-tag-file-present-p (file-name)
  "Check if the '// @flow' or `/* @flow */' tag is present in
the contents of FILE-NAME."
  (if-let ((buffer (find-buffer-visiting file-name)))
      (with-current-buffer buffer
        (lsp-clients-flow-tag-string-present-p))
    (with-temp-buffer
      (insert-file-contents file-name)
      (lsp-clients-flow-tag-string-present-p))))

(defun lsp-clients-flow-tag-string-present-p ()
  "Helper for `lsp-clients-flow-tag-file-present-p' that works
with the file contents."
  (save-excursion
    (goto-char (point-min))
    (let (stop found)
      (while (not stop)
        (when (not (re-search-forward "[^\n[:space:]]" nil t))
          (setq stop t))
        (if (equal (point) (point-min))
            (setq stop t)
          (backward-char))
        (cond ((or (looking-at "//+[ ]*@flow")
                   (looking-at "/\\**[ ]*@flow")
                   (looking-at "[ ]*\\*[ ]*@flow"))
               (setq found t)
               (setq stop t))
              ((looking-at "//")
               (forward-line))
              ((looking-at "*")
               (forward-line))
              ((looking-at "/\\*")
               (save-excursion
                 (when (not (re-search-forward "*/" nil t))
                   (setq stop t)))
               (forward-line))
              (t (setq stop t))))
      found)))

(defun lsp-clients-flow-project-p (file-name)
  "Check if FILE-NAME is part of a Flow project, that is, if
there is a .flowconfig file in the folder hierarchy."
  (locate-dominating-file file-name ".flowconfig"))

(defun lsp-clients-flow-activate-p (file-name _mode)
  "Check if the Flow language server should be enabled for a
particular FILE-NAME and MODE."
  (and (derived-mode-p 'js-mode 'web-mode 'js2-mode 'flow-js2-mode 'rjsx-mode)
       (or (lsp-clients-flow-project-p file-name)
	   (lsp-clients-flow-tag-file-present-p file-name))))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection (lambda ()
                                                          (cons lsp-clients-flow-server
                                                                lsp-clients-flow-server-args)))
                  :priority -1
                  :activation-fn 'lsp-clients-flow-activate-p
                  :server-id 'flow-ls))


;; PHP
(defgroup lsp-php nil
  "LSP support for PHP, using php-language-server."
  :link '(url-link "https://github.com/felixfbecker/php-language-server")
  :group 'lsp-mode)

(defcustom lsp-clients-php-server-command
  `("php" ,(expand-file-name "~/.composer/vendor/felixfbecker/language-server/bin/php-language-server.php"))
  "Install directory for php-language-server."
  :group 'lsp-php
  :type '(repeat string))

(defun lsp-php--create-connection ()
  "Create lsp connection."
  (lsp-stdio-connection
   (lambda () lsp-clients-php-server-command)
   (lambda ()
     (if (and (cdr lsp-clients-php-server-command)
              (eq (string-match-p "php[0-9.]*\\'" (car lsp-clients-php-server-command)) 0))
         ;; Start with the php command and the list has more elems. Test the existence of the PHP script.
         (let ((php-file (nth 1 lsp-clients-php-server-command)))
           (or (file-exists-p php-file)
               (progn
                 (lsp-log "%s is not present." php-file)
                 nil)))
       t))))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-php--create-connection)
                  :major-modes '(php-mode)
                  :priority -3
                  :server-id 'php-ls))



(defgroup lsp-ocaml nil
  "LSP support for OCaml, using ocaml-language-server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/freebroccolo/ocaml-language-server"))

(define-obsolete-variable-alias
  'lsp-ocaml-ocaml-lang-server-command
  'lsp-ocaml-lang-server-command
  "lsp-mode 6.1")

(defcustom lsp-ocaml-lang-server-command
  '("ocaml-language-server" "--stdio")
  "Command to start ocaml-language-server."
  :group 'lsp-ocaml
  :type '(choice
          (string :tag "Single string value")
          (repeat :tag "List of string values"
                  string)))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection
                                   (lambda () lsp-ocaml-lang-server-command))
                  :major-modes '(reason-mode caml-mode tuareg-mode)
                  :priority -1
                  :server-id 'ocaml-ls))

(defgroup lsp-ocaml-lsp-server nil
  "LSP support for OCaml, using ocaml-lsp-server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/ocaml/ocaml-lsp"))

(define-obsolete-variable-alias 'lsp-merlin 'lsp-ocaml-lsp-server)
(define-obsolete-variable-alias 'lsp-merlin-command 'lsp-ocaml-lsp-server-command)

(defcustom lsp-ocaml-lsp-server-command
  '("ocamllsp")
  "Command to start ocaml-language-server."
  :group 'lsp-ocaml
  :type '(choice
          (string :tag "Single string value")
          (repeat :tag "List of string values"
                  string)))

(lsp-register-client
 (make-lsp-client
  :new-connection
  (lsp-stdio-connection (lambda () lsp-ocaml-lsp-server-command))
  :major-modes '(caml-mode tuareg-mode)
  :priority 0
  :server-id 'ocaml-lsp-server))


;; C-family (C, C++, Objective-C, Objective-C++)

(defgroup lsp-clangd nil
  "LSP support for C-family languages (C, C++, Objective-C, Objective-C++), using clangd."
  :group 'lsp-mode
  :link '(url-link "https://clang.llvm.org/extra/clangd/"))

(defcustom lsp-clients-clangd-executable "clangd"
  "The clangd executable to use.
Leave as just the executable name to use the default behavior of
finding the executable with `exec-path'."
  :group 'lsp-clangd
  :risky t
  :type 'file)

(defcustom lsp-clients-clangd-args '()
  "Extra arguments for the clangd executable."
  :group 'lsp-clangd
  :risky t
  :type '(repeat string))

(defun lsp-clients--clangd-command ()
  "Generate the language server startup command."
  `(,lsp-clients-clangd-executable ,@lsp-clients-clangd-args))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection
                                   'lsp-clients--clangd-command)
                  :major-modes '(c-mode c++-mode objc-mode)
                  :priority -1
                  :server-id 'clangd))

(cl-defmethod lsp-clients-extract-signature-on-hover (contents (_server-id (eql clangd)))
  "Extract a representative line from clangd's CONTENTS, to show in the echo area.
This function tries to extract the type signature from CONTENTS,
or the first line if it cannot do so. A single line is always
returned to avoid that the echo area grows uncomfortably."
  (with-temp-buffer
    (-let [value (lsp:markup-content-value contents)]
      (insert value)
      (goto-char (point-min))
      (if (re-search-forward (rx (seq "```cpp\n"
                                      (opt (group "//"
                                                  (zero-or-more nonl)
                                                  "\n"))
                                      (group
                                       (one-or-more
                                        (not (any "`")))
                                       "\n")
                                      "```")) nil t nil)
          (progn (narrow-to-region (match-beginning 2) (match-end 2))
                 (lsp--render-element (lsp-join-region (point-min) (point-max))))
        (car (s-lines (lsp--render-element contents)))))))

;; Elixir
(defgroup lsp-elixir nil
  "LSP support for Elixir, using elixir-ls."
  :group 'lsp-mode
  :link '(url-link "https://github.com/elixir-lsp/elixir-ls"))

(defcustom lsp-clients-elixir-server-executable
  (if (equal system-type 'windows-nt)
      "language_server.bat"
    "language_server.sh")
  "The elixir-language-server executable to use.
Leave as just the executable name to use the default behavior of
finding the executable with `exec-path'."
  :group 'lsp-elixir
  :type 'file)

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection (lambda ()
                                                          `(,lsp-clients-elixir-server-executable)))
                  :major-modes '(elixir-mode)
                  :priority -1
                  :server-id 'elixir-ls
                  :initialized-fn (lambda (workspace)
                                    (puthash
                                     "textDocumentSync"
                                     (ht ("save" t)
                                         ("change" 2))
                                     (lsp--workspace-server-capabilities workspace)))))

;; Fortran
(defgroup lsp-fortran nil
  "LSP support for Fortran, using the Fortran Language Server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/hansec/fortran-language-server"))

(defcustom lsp-clients-fortls-executable "fortls"
  "The fortls executable to use.
Leave as just the executable name to use the default behavior of
finding the executable with `exec-path'."
  :group 'lsp-fortran
  :risky t
  :type 'file)

(defcustom lsp-clients-fortls-args '()
  "Extra arguments for the fortls executable"
  :group 'lsp-fortran
  :risky t
  :type '(repeat string))

(defun lsp-clients--fortls-command ()
  "Generate the language server startup command."
  `(,lsp-clients-fortls-executable,@lsp-clients-fortls-args))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection 'lsp-clients--fortls-command)
                  :major-modes '(f90-mode fortran-mode)
                  :priority -1
                  :server-id 'fortls))



;; Kotlin
(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 "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"))

(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)))

(lsp-register-client
 (make-lsp-client
  :new-connection (lsp-stdio-connection lsp-clients-kotlin-server-executable)
  :major-modes '(kotlin-mode)
  :priority -1
  :server-id 'kotlin-ls
  :initialized-fn (lambda (workspace)
                    (with-lsp-workspace workspace
                      (lsp--set-configuration (lsp-configuration-section "kotlin"))))))


;; Hack
(defgroup lsp-hack nil
  "LSP support for Hack, using HHVM."
  :group 'lsp-mode
  :link '(url-link "https://docs.hhvm.com/hhvm/"))

(defcustom lsp-clients-hack-command '("hh_client" "lsp" "--from" "emacs")
  "Command to start hh_client."
  :group 'lsp-hack
  :risky t
  :type '(repeat string))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection (lambda () lsp-clients-hack-command))
                  :major-modes '(hack-mode)
                  :priority -1
                  :server-id 'hack
                  ;; ignore some unsupported messages from Nuclide
                  :notification-handlers (lsp-ht ("telemetry/event" 'ignore)
                                                 ("$/cancelRequest" 'ignore))
                  :request-handlers (lsp-ht ("window/showStatus" 'ignore))))


;;; Dockerfile
(defcustom lsp-dockerfile-language-server-command
  '("docker-langserver" "--stdio")
  "The command that starts the docker language server."
  :group 'lsp-dockerfile
  :type '(choice
          (string :tag "Single string value")
          (repeat :tag "List of string values"
                  string)))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection
                                   (-const lsp-dockerfile-language-server-command))
                  :major-modes '(dockerfile-mode)
                  :priority -1
                  :server-id 'dockerfile-ls))


;;; Angular
(defcustom lsp-clients-angular-language-server-command
  '("node"
    "/usr/lib/node_modules/@angular/language-server"
    "--ngProbeLocations"
    "/usr/lib/node_modules"
    "--tsProbeLocations"
    "/usr/lib/node_modules"
    "--stdio")
  "The command that starts the angular language server."
  :group 'lsp-clients-angular
  :type '(choice
          (string :tag "Single string value")
          (repeat :tag "List of string values"
                  string)))

(defun lsp-client--angular-start-loading (_workspace params)
  (lsp--info "Started loading project %s" params))

(defun lsp-client--angular-finished-loading (_workspace params)
  (lsp--info "Finished loading project %s" params))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection
                                   (lambda () lsp-clients-angular-language-server-command))
                  :activation-fn (lambda (&rest _args)
                                   (and (string-match-p "\\.html\\'" (buffer-file-name))
                                        (lsp-workspace-root)
                                        (file-exists-p (f-join (lsp-workspace-root) "angular.json"))))
                  :priority -1
                  :notification-handlers (ht ("angular-language-service/projectLoadingStart" #'lsp-client--angular-start-loading)
                                             ("angular-language-service/projectLoadingFinish" #'lsp-client--angular-finished-loading))
                  :add-on? t
                  :server-id 'angular-ls))


;; TeX
(defgroup lsp-tex nil
  "LSP support for TeX and friends, using Digestif and texlab."
  :group 'lsp-mode
  :link '(url-link "https://github.com/astoff/digestif/")
  :link '(url-link "https://github.com/latex-lsp/texlab"))

(defcustom lsp-tex-server 'texlab
  "Choose LSP tex server."
  :type '(choice (const :tag "texlab" texlab)
                 (const :tag "digestif" digestif))
  :group 'lsp-tex)

(defcustom lsp-clients-digestif-executable "digestif"
  "Command to start the Digestif language server."
  :group 'lsp-tex
  :risky t
  :type 'file)

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection lsp-clients-digestif-executable)
                  :major-modes '(plain-tex-mode latex-mode)
                  :priority (if (eq lsp-tex-server 'digestif) 1 -1)
                  :server-id 'digestif))

(defcustom lsp-clients-texlab-executable "texlab"
  "Command to start the texlab language server."
  :group 'lsp-tex
  :risky t
  :type 'file)

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection lsp-clients-texlab-executable)
                  :major-modes '(plain-tex-mode latex-mode)
                  :priority (if (eq lsp-tex-server 'texlab) 1 -1)
                  :server-id 'texlab))


;; Vim script
(defgroup lsp-vim nil
  "LSP support for viml using vim-language-server"
  :group 'lsp-mode)

(defcustom lsp-clients-vim-executable '("vim-language-server" "--stdio")
  "Command to start the vim language server."
  :group 'lsp-vim
  :risky t
  :type 'file)

(defcustom lsp-clients-vim-initialization-options '((iskeyword . "vim iskeyword option")
                                                    (vimruntime . "/usr/bin/vim")
                                                    (runtimepath . "/usr/bin/vim")
                                                    (diagnostic . ((enable . t)))
                                                    (indexes . ((runtimepath . t)
                                                                (gap . 100)
                                                                (count . 3)))
                                                    (suggest . ((fromVimruntime . t)
                                                                (fromRuntimepath . :json-false))))
  "Initialization options for vim language server."
  :group 'lsp-vim
  :type 'alist)

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection lsp-clients-vim-executable)
                  :major-modes '(vimrc-mode)
                  :priority -1
                  :server-id 'vimls
                  :initialization-options (lambda ()
                                            lsp-clients-vim-initialization-options)))


;; LUA
(defgroup lsp-emmy-lua nil
  "LSP support for emmy-lua."
  :group 'lsp-mode
  :link '(url-link "https://github.com/EmmyLua/EmmyLua-LanguageServer"))

(defcustom lsp-clients-emmy-lua-java-path "java"
  "Path to java which will be used for running emmy-lua language server."
  :group 'lsp-emmy-lua
  :risky t
  :type 'file)

(defcustom lsp-clients-emmy-lua-jar-path (f-expand "~/.emacs.d/EmmyLua-LS-all.jar")
  "Path to jar which will be used for running EmmyLua language server."
  :group 'lsp-emmy-lua
  :risky t
  :type 'file)

(defun lsp-clients-emmy-lua--create-connection ()
  "Create connection to emmy lua language server."
  (lsp-stdio-connection
   (lambda ()
     (list lsp-clients-emmy-lua-java-path "-jar" lsp-clients-emmy-lua-jar-path))
   (lambda ()
     (f-exists? lsp-clients-emmy-lua-jar-path))))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-clients-emmy-lua--create-connection)
                  :major-modes '(lua-mode)
                  :priority -1
                  :server-id 'emmy-lua
                  :notification-handlers (lsp-ht ("emmy/progressReport" #'ignore))))


;; R
(defgroup lsp-r nil
  "LSP support for R."
  :group 'lsp-mode
  :link '(url-link "https://github.com/REditorSupport/languageserver"))

(defcustom lsp-clients-r-server-command '("R" "--slave" "-e" "languageserver::run()")
  "Command to start the R language server."
  :group 'lsp-r
  :risky t
  :type '(repeat string))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection lsp-clients-r-server-command)
                  :major-modes '(ess-r-mode)
                  :server-id 'lsp-r))


;; Crystal
(defgroup lsp-crystal nil
  "LSP support for Crystal via scry."
  :group 'lsp-mode
  :link '(url-link "https://github.com/crystal-lang-tools/scry"))

(defcustom lsp-clients-crystal-executable '("scry" "--stdio")
  "Command to start the scry language server."
  :group 'lsp-crystal
  :risky t
  :type 'file)

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection lsp-clients-crystal-executable)
                  :major-modes '(crystal-mode)
                  :server-id 'scry))


;; Nim
(defgroup lsp-nim nil
  "LSP support for Nim, using nimlsp."
  :group 'lsp-mode
  :link '(url-link "https://github.com/PMunch/nimlsp"))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection "nimlsp")
                  :major-modes '(nim-mode)
                  :priority -1
                  :server-id 'nimls))

;; Dhall
(defgroup lsp-dhall nil
  "LSP support for Dhall, using dhall-lsp-server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/dhall-lang/dhall-haskell"))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection "dhall-lsp-server")
                  :major-modes '(dhall-mode)
                  :priority -1
                  :server-id 'dhallls))

;; CMake
(defgroup lsp-cmake nil
  "LSP support for CMake, using cmake-language-server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/regen100/cmake-language-server"))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection "cmake-language-server")
                  :major-modes '(cmake-mode)
                  :priority -1
                  :server-id 'cmakels))

;; PureScript
(defgroup lsp-purescript nil
  "LSP support for PureScript, using purescript-language-server."
  :group 'lsp-mode
  :link '(url-link "https://github.com/nwolverson/purescript-language-server"))

(defcustom lsp-purescript-server-executable nil
  "Path to server executable."
  :type 'string
  :risky t
  :group 'lsp-purescript)

(defcustom lsp-purescript-server-args
  '("--stdio")
  "Arguments to pass to the server."
  :type '(repeat string)
  :risky t
  :group 'lsp-purescript)

(defun lsp-purescript--server-command ()
  "Generate LSP startup command for purescript-language-server."
  (cons (or lsp-purescript-server-executable
            (lsp-package-path 'purescript-language-server))
        lsp-purescript-server-args))

(lsp-dependency 'purescript-language-server
                '(:system "purescript-language-server")
                '(:npm :package "purescript-language-server"
                       :path "purescript-language-server"))

(lsp-register-client
 (make-lsp-client
  :new-connection (lsp-stdio-connection
                   #'lsp-purescript--server-command)
  :major-modes '(purescript-mode)
  :priority -1
  :server-id 'pursls
  :download-server-fn (lambda (_client callback error-callback _update?)
                        (lsp-package-ensure 'purescript-language-server callback error-callback))))

;;; Rf
(defgroup lsp-rf nil
  "Settings for Robot Framework Language Server."
  :group 'lsp-mode
  :tag "Language Server"
  :link '(url-link "https://github.com/tomi/vscode-rf-language-server.git"))

(defcustom lsp-rf-language-server-start-command '("~/.nvm/versions/node/v9.11.2/bin/node" "~/.vscode/extensions/tomiturtiainen.rf-intellisense-2.8.0/server/server.js")
  "Path to the server.js file of the rf-intellisense server. Accepts a list of strings (path/to/interpreter path/to/server.js)"
  :type 'list
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-include-paths []
  "An array of files that should be included by the parser. Glob patterns as strings are accepted (eg. *.robot between double quotes)"
  :type 'lsp-string-vector
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-exclude-paths []
  "An array of files that should be ignored by the parser. Glob patterns as strings are accepted (eg. *bad.robot between double quotes)"
  :type 'lsp-string-vector
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-dir "~/.vscode/extensions/tomiturtiainen.rf-intellisense-2.8.0/server/library-docs/"
  "Libraries directory for libraries in lsp-rf-language-server-libraries"
  :type 'string
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-libraries ["BuiltIn-3.1.1" "Collections-3.0.4"]
  "Libraries whose keywords are suggested with auto-complete"
  :type '(repeat string)
  ;; :type 'lsp-string-vector
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-log-level "debug"
  "What language server log messages are printed"
  :type 'string
  ;; :type '(choice (:tag "off" "errors" "info" "debug"))
  :group 'lsp-rf)

(defcustom lsp-rf-language-server-trace-server "verbose"
  "Traces the communication between VSCode and the rfLanguageServer service."
  :type 'string
  ;; :type '(choice (:tag "off" "messages" "verbose"))
  :group 'lsp-rf)

(defun parse-rf-language-server-library-dirs (dirs)
  (vconcat (mapcar
   (lambda (x)
     (concat
      (expand-file-name
       lsp-rf-language-server-dir)
      x
      ".json"))
   dirs)))

(defun expand-start-command ()
  (mapcar 'expand-file-name lsp-rf-language-server-start-command))

(defun parse-rf-language-server-globs-to-regex (vector)
  "Converts vector with globs to regex"
    (concat "\\("(mapconcat 'eshell-glob-regexp vector "\\|") "\\)"))

(defun parse-rf-language-server-include-path-regex (vector)
  "Creates regexp to select files from workspace directory"
  (let ((globs (if (eq vector [])
                        ["*.robot" "*.resource"]
                      vector)))
    (parse-rf-language-server-globs-to-regex globs)))

(defun parse-rf-language-server-exclude-paths (seq)
  "Creates regexp to select files from workspace directory"
  (if (eq lsp-rf-language-server-exclude-paths [])
      seq
  (cl-delete-if (lambda (x) (string-match-p
                             (parse-rf-language-server-globs-to-regex
                              lsp-rf-language-server-exclude-paths)
                             x))
                seq)))

(lsp-register-custom-settings
 '(
   ("rfLanguageServer.trace.server" lsp-rf-language-server-trace-server)
   ("rfLanguageServer.logLevel" lsp-rf-language-server-log-level)
   ("rfLanguageServer.libraries" lsp-rf-language-server-libraries)
   ("rfLanguageServer.excludePaths" lsp-rf-language-server-exclude-paths)
   ("rfLanguageServer.includePaths" lsp-rf-language-server-include-paths)))

(lsp-register-client
 (make-lsp-client :new-connection (lsp-stdio-connection
                                   (expand-start-command))
                  :major-modes '(robot-mode)
                  :server-id 'rf-intellisense
                  ;; :library-folders-fn (lambda (_workspace)
                  ;;                        lsp-rf-language-server-libraries)
                  :library-folders-fn (lambda (_workspace)
                                         (parse-rf-language-server-library-dirs
                                         lsp-rf-language-server-libraries))
                  :initialized-fn (lambda (workspace)
                                    (with-lsp-workspace workspace
                                      (lsp--set-configuration
                                       (lsp-configuration-section "rfLanguageServer"))
                                      (lsp-request "buildFromFiles"
                                                   (list :files
                                                         (vconcat
                                                          (parse-rf-language-server-exclude-paths
                                                           (directory-files-recursively
                                                            (lsp--workspace-root workspace)
                                                            (parse-rf-language-server-include-path-regex
                                                             lsp-rf-language-server-include-paths))))))))))

(provide 'lsp-clients)
;;; lsp-clients.el ends here
