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