config

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

lsp-terraform.el (19774B)


      1 ;;; lsp-terraform.el --- Terraform Client settings         -*- lexical-binding: t; -*-
      2 
      3 ;; Copyright (C) 2019 Ross Donaldson, Sibi Prabakaran
      4 
      5 ;; Author: Ross Donaldson, Sibi Prabakaran
      6 ;; Keywords: terraform lsp
      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 client for Terraform
     24 
     25 ;;; Code:
     26 
     27 (require 'lsp-mode)
     28 (require 'lsp-semantic-tokens)
     29 (require 'lsp-protocol)
     30 (require 'dash)
     31 
     32 ;; terraform-lsp
     33 
     34 (defgroup lsp-terraform nil
     35   "LSP support for Terraform, using terraform-lsp."
     36   :group 'lsp-mode
     37   :link '(url-link "https://github.com/juliosueiras/terraform-lsp")
     38   :package-version `(lsp-mode . "6.2"))
     39 
     40 (defcustom lsp-terraform-server "terraform-lsp"
     41   "Path to the `terraform-lsp' binary."
     42   :group 'lsp-terraform
     43   :risky t
     44   :type '(choice
     45           (file :tag "File")
     46           (repeat string))
     47   :package-version `(lsp-mode . "6.2"))
     48 
     49 (defcustom lsp-terraform-enable-logging nil
     50   "If non-nil, enable `terraform-ls''s native logging."
     51   :group 'lsp-terraform
     52   :risky t
     53   :type 'boolean
     54   :package-version `(lsp-mode . "6.2"))
     55 
     56 
     57 (defun lsp-terraform--make-launch-cmd ()
     58   (-let [base (if (stringp lsp-terraform-server)
     59                   `(,lsp-terraform-server)
     60                 lsp-terraform-server)]
     61     (when lsp-terraform-enable-logging
     62       (push "-enable-log-file" base))
     63     base))
     64 
     65 (lsp-register-client
     66  (make-lsp-client :new-connection (lsp-stdio-connection #'lsp-terraform--make-launch-cmd)
     67                   :major-modes '(terraform-mode)
     68                   :priority -1
     69                   :server-id 'tfls))
     70 
     71 
     72 ;; terraform-ls
     73 
     74 (defgroup lsp-terraform-ls nil
     75   "LSP support for Terraform, using terraform-ls from Hashicorp."
     76   :group 'lsp-mode
     77   :link '(url-link "https://github.com/hashicorp/terraform-ls")
     78   :package-version `(lsp-mode . "9.0.0"))
     79 
     80 (defcustom lsp-terraform-ls-server "terraform-ls"
     81   "Path to the `terraform-ls' binary."
     82   :group 'lsp-terraform-ls
     83   :risky t
     84   :type '(choice
     85           (file :tag "File")
     86           (repeat string))
     87   :package-version `(lsp-mode . "9.0.0"))
     88 
     89 (defcustom lsp-terraform-ls-enable-show-reference nil
     90   "Enable reference counts.
     91 
     92 Display reference counts above top level blocks and
     93 attributes.  This is an experimental feature provided by the
     94 language server."
     95   :group 'lsp-terraform-ls
     96   :type 'boolean
     97   :package-version '(lsp-mode . "9.0.0"))
     98 
     99 (defcustom lsp-terraform-ls-validate-on-save nil
    100   "Enable validating the current open file on save.
    101 
    102 This is an experimental feature provided by the language server."
    103   :group 'lsp-terraform-ls
    104   :type 'boolean
    105   :package-version '(lsp-mode . "9.0.0"))
    106 
    107 (defcustom lsp-terraform-ls-prefill-required-fields nil
    108   "Enable completion of required fields.
    109 
    110 Enable autocompletion for required fields when completing
    111 Terraform blocks.  This is an experimental feature provided by the
    112 language server."
    113   :group 'lsp-terraform-ls
    114   :type 'boolean
    115   :package-version '(lsp-mode . "9.0.0"))
    116 
    117 (defcustom lsp-terraform-ls-providers-position-params nil
    118   "The optional providers tree position params.
    119 Defaults to side following treemacs default."
    120   :type 'alist
    121   :group 'lsp-terraform-ls
    122   :package-version '(lsp-mode . "9.0.0"))
    123 
    124 (defcustom lsp-terraform-ls-module-calls-position-params nil
    125   "The optional module calls tree position params.
    126 Defaults to side following treemacs default."
    127   :type 'alist
    128   :group 'lsp-terraform-ls
    129   :package-version '(lsp-mode . "9.0.0"))
    130 
    131 (defun lsp-terraform-ls--make-launch-cmd ()
    132   `(,lsp-terraform-ls-server "serve"))
    133 
    134 (lsp-defun lsp-terraform-ls--show-references ((&Command :arguments?))
    135   "Show references for command with ARGS."
    136   (lsp-show-xrefs
    137      (lsp--locations-to-xref-items
    138       (lsp-request "textDocument/references"
    139                    (lsp--make-reference-params
    140                     (lsp--text-document-position-params nil (elt arguments? 0)))))
    141      t
    142      t))
    143 
    144 (defun lsp-terraform-ls--custom-capabilities ()
    145   "Construct custom capabilities for the language server."
    146   (when lsp-terraform-ls-enable-show-reference
    147     '((experimental . ((showReferencesCommandId . "client.showReferences"))))))
    148 
    149 (defun lsp-terraform-ls--init-options ()
    150   "Construct initialization options for the lanague server."
    151   `((experimentalFeatures . ((validateOnSave . ,(lsp-json-bool lsp-terraform-ls-validate-on-save))
    152                              (prefillRequiredFields . ,(lsp-json-bool lsp-terraform-ls-prefill-required-fields))))))
    153 
    154 (defcustom lsp-terraform-semantic-token-faces
    155   '(("namespace" . lsp-face-semhl-namespace)
    156     ("type" . lsp-face-semhl-type)
    157     ("class" . lsp-face-semhl-class)
    158     ("enum" . lsp-face-semhl-enum)
    159     ("interface" . lsp-face-semhl-interface)
    160     ("struct" . lsp-face-semhl-struct)
    161     ("typeParameter" . lsp-face-semhl-type-parameter)
    162     ("parameter" . lsp-face-semhl-parameter)
    163     ("variable" . lsp-face-semhl-variable)
    164     ("property" . lsp-face-semhl-property)
    165     ("enumMember" . lsp-face-semhl-constant)
    166     ("event" . lsp-face-semhl-event)
    167     ("function" . lsp-face-semhl-function)
    168     ("method" . lsp-face-semhl-method)
    169     ("macro" . lsp-face-semhl-macro)
    170     ("keyword" . lsp-face-semhl-keyword)
    171     ("modifier" . lsp-face-semhl-member)
    172     ("comment" . lsp-face-semhl-comment)
    173     ("string" . lsp-face-semhl-string)
    174     ("number" . lsp-face-semhl-number)
    175     ("regexp" . lsp-face-semhl-regexp)
    176     ("operator" . lsp-face-semhl-operator)
    177     ("hcl-attrName" . lsp-face-semhl-member)
    178     ("hcl-blockType" . lsp-face-semhl-struct)
    179     ("hcl-blockLabel" . lsp-face-semhl-member)
    180     ("hcl-bool" . lsp-face-semhl-constant)
    181     ("hcl-string" . lsp-face-semhl-string)
    182     ("hcl-number" . lsp-face-semhl-number)
    183     ("hcl-objectKey" . lsp-face-semhl-member)
    184     ("hcl-mapKey" . lsp-face-semhl-member)
    185     ("hcl-keyword" . lsp-face-semhl-keyword)
    186     ("hcl-traversalStep" . lsp-face-semhl-member)
    187     ("hcl-typeCapsule" . lsp-face-semhl-type)
    188     ("hcl-typePrimitive" . lsp-face-semhl-type))
    189   "Mapping between terrafom-ls tokens and fonts to apply."
    190   :group 'lsp-terraform
    191   :type '(alist :key-type string :value-type face)
    192   :package-version '(lsp-mode . "8.1"))
    193 
    194 (defcustom lsp-terraform-semantic-token-modifier-faces
    195   '(("declaration" . lsp-face-semhl-class)
    196     ("definition" . lsp-face-semhl-definition)
    197     ("readonly" . lsp-face-semhl-constant)
    198     ("static" . lsp-face-semhl-static)
    199     ("deprecated" . lsp-face-semhl-deprecated)
    200     ("abstract" . lsp-face-semhl-keyword)
    201     ("async" . lsp-face-semhl-macro)
    202     ("modification" . lsp-face-semhl-operator)
    203     ("documentation" . lsp-face-semhl-comment)
    204     ("defaultLibrary" . lsp-face-semhl-default-library)
    205     ("hcl-dependent" . lsp-face-semhl-constant)
    206     ("terraform-data" . lsp-face-semhl-constant)
    207     ("terraform-locals" . lsp-face-semhl-variable)
    208     ("terraform-module" . lsp-face-semhl-namespace)
    209     ("terraform-output" . lsp-face-semhl-constant)
    210     ("terraform-provider" . lsp-face-semhl-class)
    211     ("terraform-resource" . lsp-face-semhl-interface)
    212     ("terraform-provisioner" . lsp-face-semhl-default-library)
    213     ("terraform-connection" . lsp-face-semhl-constant)
    214     ("terraform-variable" . lsp-face-semhl-variable)
    215     ("terraform-terraform" . lsp-face-semhl-constant)
    216     ("terraform-backend" . lsp-face-semhl-definition)
    217     ("terraform-name" . lsp-face-semhl-interface)
    218     ("terraform-type" . lsp-face-semhl-type)
    219     ("terraform-requiredProviders" . lsp-face-semhl-default-library))
    220   "Mapping between terraform-ls modifiers and fonts to apply."
    221   :group 'lsp-terraform
    222   :type '(alist :key-type string :value-type face)
    223   :package-version '(lsp-mode . "8.1"))
    224 
    225 (lsp-register-client
    226  (make-lsp-client :new-connection (lsp-stdio-connection #'lsp-terraform-ls--make-launch-cmd)
    227                   :major-modes '(terraform-mode)
    228                   :priority 1
    229                   :server-id 'tfmls
    230                   :action-handlers (ht ("client.showReferences" #'lsp-terraform-ls--show-references))
    231                   :semantic-tokens-faces-overrides `(:discard-default-modifiers t
    232                                                      :discard-default-types t
    233                                                      :modifiers ,lsp-terraform-semantic-token-modifier-faces
    234                                                      :types ,lsp-terraform-semantic-token-faces)
    235                   :initialization-options (lsp-terraform-ls--init-options)
    236                   :custom-capabilities (lsp-terraform-ls--custom-capabilities)))
    237 
    238 (defun lsp-terraform-ls-validate ()
    239   "Execute terraform validate on project root."
    240   (interactive)
    241   (lsp-request
    242    "workspace/executeCommand"
    243    (list :command "terraform-ls.terraform.validate"
    244          :arguments (vector (format "uri=%s" (lsp--path-to-uri (lsp-workspace-root))))
    245          )
    246    :no-wait t
    247    :no-merge t))
    248 
    249 (defun lsp-terraform-ls-init ()
    250   "Execute terraform init on project root.
    251 
    252 This is a synchronous action."
    253   (interactive)
    254   (lsp-request
    255      "workspace/executeCommand"
    256      (list :command "terraform-ls.terraform.init"
    257            :arguments (vector (format "uri=%s" (lsp--path-to-uri (lsp-workspace-root)))))
    258      :no-wait nil
    259      :no-merge t))
    260 
    261 (defun lsp-terraform-ls-version ()
    262   "Get information about the terraform binary version for the current module."
    263   (interactive)
    264   (let ((terraform-data (lsp-request
    265                          "workspace/executeCommand"
    266                          (list :command "terraform-ls.module.terraform"
    267                                :arguments (vector (format "uri=%s" (lsp--path-to-uri (lsp-workspace-root))))))))
    268     (lsp--info "Required: %s, Current: %s"
    269                (lsp:terraform-ls-module-terraform-required-version terraform-data)
    270                (lsp:terraform-ls-module-terraform-discovered-version terraform-data))))
    271 
    272 (lsp-consistency-check lsp-terraform)
    273 
    274 (defvar treemacs-position)
    275 (defvar treemacs-width)
    276 (declare-function lsp-treemacs-render "ext:lsp-treemacs" (tree title expand-depth &optional buffer-name))
    277 
    278 (defvar-local lsp-terraform-ls--providers-tree-data nil)
    279 (defvar-local lsp-terraform-ls--modules-call-tree-data nil)
    280 (defvar-local lsp-tf--modules-control-buffer nil)
    281 (defconst lsp-terraform-ls--providers-buffer-name "*Terraform Providers*")
    282 (defconst lsp-terraform-ls--modules-buffer-name "*Terraform Modules*")
    283 
    284 (defvar lsp-terraform-modules-mode-map
    285   (let ((m (make-sparse-keymap)))
    286     (define-key m (kbd "g") 'lsp-terraform-ls--modules-refresh)
    287     m)
    288   "Keymap for `lsp-terraform-modules-mode'.")
    289 
    290 (define-minor-mode lsp-terraform-modules-mode "LSP Treemacs mode for terraform modules."
    291   :keymap lsp-terraform-modules-mode-map
    292   :group 'lsp-terraform-ls)
    293 
    294 (cl-defstruct tf-package display-name doc-link installed-version version-constraint)
    295 
    296 (cl-defstruct tf-module name doc-link version source-type dependent-modules)
    297 
    298 (defun construct-tf-package (provider installed-version)
    299   "Construct `TF-PACKAGE' using PROVIDER and INSTALLED-VERSION."
    300   (make-tf-package :display-name (lsp-get provider :display_name)
    301                    :doc-link (lsp-get provider :docs_link)
    302                    :installed-version installed-version
    303                    :version-constraint (lsp-get provider :version_constraint)))
    304 
    305 (lsp-defun construct-tf-module ((&terraform-ls:Module :name :docs-link :version :source-type :dependent-modules))
    306   "Construct `TF-MODULE' using MODULE."
    307   (make-tf-module :name name
    308                   :doc-link docs-link
    309                   :version version
    310                   :source-type source-type
    311                   :dependent-modules dependent-modules))
    312 
    313 (lsp-defun lsp-terraform-ls--providers-to-tf-package ((&terraform-ls:Providers :provider-requirements :installed-providers))
    314   "Convert PROVIDERS-TREE-DATA to list of `tf-package'."
    315   (let* ((provider-requirements-keys (hash-table-keys provider-requirements))
    316          (installed-versions (mapcar (lambda (x) (lsp-get installed-providers (make-symbol (format ":%s" x)))) provider-requirements-keys))
    317          (providers (mapcar (lambda (x) (lsp-get provider-requirements (make-symbol (format ":%s" x)))) provider-requirements-keys))
    318          (tf-packages (-zip-with (lambda (x y) (construct-tf-package x y)) providers installed-versions)))
    319     tf-packages))
    320 
    321 (lsp-defun lsp-terraform-ls--modules-to-tf-module ((&terraform-ls:ModuleCalls :module-calls))
    322   "Convert MODULES-TREE-DATA to list of `TF-MODULE'."
    323   (let* ((modules (-map (lambda (x) (construct-tf-module x)) module-calls)))
    324     modules))
    325 
    326 (defun lsp-terraform-ls--fetch-modules-data (project-root)
    327   "Fetch modules data and set it in `lsp-terraform-ls--modules-call-tree-data'."
    328   (let* ((tree-data (lsp-request
    329                      "workspace/executeCommand"
    330                      (list :command "terraform-ls.module.calls"
    331                            :arguments (vector (format "uri=%s" (lsp--path-to-uri project-root))))
    332                      :no-wait nil
    333                      :no-merge nil))
    334          (modules (lsp-terraform-ls--modules-to-tf-module tree-data)))
    335     (setq-local lsp-terraform-ls--modules-call-tree-data modules)))
    336 
    337 (defun lsp-terraform-ls--fetch-providers ()
    338   "Fetch modules call data and set it in `lsp-terraform-ls--providers-tree-data'."
    339   (let* ((tree-data (lsp-request
    340                      "workspace/executeCommand"
    341                      (list :command "terraform-ls.module.providers"
    342                            :arguments (vector (format "uri=%s" (lsp--path-to-uri (lsp-workspace-root)))))
    343                      :no-wait nil
    344                      :no-merge nil))
    345          (tf-packages (lsp-terraform-ls--providers-to-tf-package tree-data)))
    346     (setq-local lsp-terraform-ls--providers-tree-data tf-packages)))
    347 
    348 (defun lsp-terraform-ls--tf-packages-to-treemacs (tf-packages)
    349   "Convert list of `TF-PACKAGES' to treemacs compatible data."
    350   (mapcar (lambda (package) (list :label (format "%s %s" (tf-package-display-name package) (tf-package-installed-version package))
    351                                   :icon 'package
    352                                   :key (tf-package-display-name package)
    353                                   :children (list (list
    354                                                    :icon 'library
    355                                                    :label (tf-package-version-constraint package)))
    356                                   :ret-action (lambda (&rest _) (browse-url (tf-package-doc-link package))))) tf-packages))
    357 
    358 (defun lsp-terraform-ls--tf-modules-to-treemacs (tf-modules)
    359   "Convert list of `TF-MODULES' to treemacs compatible data."
    360   (mapcar (lambda (module) (list :label (format "%s %s" (tf-module-name module) (tf-module-version module))
    361                                  :icon 'package
    362                                  :key (tf-module-name module)
    363                                  :ret-action (lambda (&rest _) (browse-url (tf-module-doc-link module)))
    364                                  )) tf-modules))
    365 
    366 (defun lsp-terraform-ls--show-providers (ignore-focus?)
    367   "Show terraform providers and focus on it if IGNORE-FOCUS? is nil."
    368   (unless lsp-terraform-ls--providers-tree-data
    369     (lsp-terraform-ls--fetch-providers))
    370   (let* ((lsp-terraform-treemacs
    371           (lsp-terraform-ls--tf-packages-to-treemacs lsp-terraform-ls--providers-tree-data))
    372          (buffer (lsp-treemacs-render lsp-terraform-treemacs
    373                                       lsp-terraform-ls--providers-buffer-name
    374                                       t
    375                                       "Terraform Providers"))
    376          (position-params (or lsp-terraform-ls-providers-position-params
    377                               `((side . ,treemacs-position)
    378                                 (slot . 2)
    379                                 (window-width . ,treemacs-width))))
    380          (window
    381           (display-buffer-in-side-window buffer position-params)))
    382     (unless ignore-focus?
    383       (select-window window)
    384       (set-window-dedicated-p window t))))
    385 
    386 (defun lsp-terraform-ls--show-module-calls (ignore-focus? project-root)
    387   "Show terraform modules and focus on it if IGNORE-FOCUS? is nil."
    388   (unless lsp-terraform-ls--modules-call-tree-data
    389     (lsp-terraform-ls--fetch-modules-data project-root))
    390   (unless lsp-terraform-ls--modules-call-tree-data
    391     (error "Modules data is empty"))
    392   (let* ((lsp-terraform-treemacs
    393           (lsp-terraform-ls--tf-modules-to-treemacs lsp-terraform-ls--modules-call-tree-data))
    394          (buffer (lsp-treemacs-render lsp-terraform-treemacs
    395                                       lsp-terraform-ls--modules-buffer-name
    396                                       t
    397                                       "Terraform Modules"))
    398          (modules-buffer (current-buffer))
    399          (position-params (or lsp-terraform-ls-module-calls-position-params
    400                               `((side . ,treemacs-position)
    401                                 (slot . 1)
    402                                 (window-width . ,treemacs-width))))
    403          (window
    404           (display-buffer-in-side-window buffer position-params)))
    405     (select-window window)
    406     (setq-local lsp-tf--modules-control-buffer modules-buffer)
    407     (lsp-terraform-modules-mode t)
    408     (set-window-dedicated-p window t)
    409     (when ignore-focus?
    410       (select-window (previous-window)))))
    411 
    412 (defun lsp-terraform-ls--refresh-module-calls ()
    413   "Refresh terraform modules."
    414   (lsp-terraform-ls--fetch-modules-data (lsp-workspace-root))
    415   (unless lsp-terraform-ls--modules-call-tree-data
    416     (error "Modules data is empty"))
    417   (let* ((lsp-terraform-treemacs
    418           (lsp-terraform-ls--tf-modules-to-treemacs lsp-terraform-ls--modules-call-tree-data))
    419          (buffer (lsp-treemacs-render lsp-terraform-treemacs
    420                                       lsp-terraform-ls--modules-buffer-name
    421                                       t
    422                                       "Terraform Modules"))
    423          (position-params (or lsp-terraform-ls-module-calls-position-params
    424                               `((side . ,treemacs-position)
    425                                 (slot . 1)
    426                                 (window-width . ,treemacs-width))))
    427          (window
    428           (display-buffer-in-side-window buffer position-params)))
    429     (select-window window)
    430     (lsp-terraform-modules-mode t)
    431     (set-window-dedicated-p window t)
    432     (lsp--info "Refresh completed")))
    433 
    434 (defun lsp-terraform-ls-providers (&optional ignore-focus?)
    435   "Show terraform providers with focus on it if IGNORE-FOCUS? is nil."
    436   (interactive)
    437   (if (require 'lsp-treemacs nil t)
    438       (lsp-terraform-ls--show-providers ignore-focus?)
    439     (error "The package lsp-treemacs is not installed")))
    440 
    441 (defun lsp-terraform-ls-module-calls (&optional ignore-focus?)
    442   "Show terraform modules with focus on it if IGNORE-FOCUS? is nil."
    443   (interactive)
    444   (if (require 'lsp-treemacs nil t)
    445       (lsp-terraform-ls--show-module-calls ignore-focus? (lsp-workspace-root))
    446     (error "The package lsp-treemacs is not installed")))
    447 
    448 (defun lsp-terraform-ls--modules-refresh ()
    449   "Refresh terraform modules data."
    450   (interactive)
    451   (unless (buffer-live-p lsp-tf--modules-control-buffer)
    452     (error "Original buffer not present.  Do M-x lsp-terraform-ls-module-calls"))
    453   (with-current-buffer lsp-tf--modules-control-buffer
    454     (lsp-terraform-ls--refresh-module-calls)))
    455 
    456 (provide 'lsp-terraform)
    457 ;;; lsp-terraform.el ends here