]> git.bundesbrandschatzamt.de Git - emacsconfig.git/commitdiff
update terraform-mode
authorAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Wed, 24 Dec 2025 14:25:42 +0000 (15:25 +0100)
committerAndreas Gerler <baronNOSPAMbundesbrandschatzamt.de>
Wed, 24 Dec 2025 14:25:42 +0000 (15:25 +0100)
elpa/terraform-mode-20241217.1628/terraform-mode-autoloads.el [deleted file]
elpa/terraform-mode-20241217.1628/terraform-mode-pkg.el [deleted file]
elpa/terraform-mode-20241217.1628/terraform-mode.el [deleted file]
elpa/terraform-mode-20251115.2210/terraform-mode-autoloads.el [new file with mode: 0644]
elpa/terraform-mode-20251115.2210/terraform-mode-pkg.el [new file with mode: 0644]
elpa/terraform-mode-20251115.2210/terraform-mode.el [new file with mode: 0644]

diff --git a/elpa/terraform-mode-20241217.1628/terraform-mode-autoloads.el b/elpa/terraform-mode-20241217.1628/terraform-mode-autoloads.el
deleted file mode 100644 (file)
index f7c3217..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-;;; terraform-mode-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
-;; Generated by the `loaddefs-generate' function.
-
-;; This file is part of GNU Emacs.
-
-;;; Code:
-
-(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
-
-
-\f
-;;; Generated autoloads from terraform-mode.el
-
-(autoload 'terraform-mode "terraform-mode" "\
-Major mode for editing terraform configuration file
-
-(fn)" t)
-(add-to-list 'auto-mode-alist '("\\.t\\(f\\(vars\\)?\\|ofu\\)\\'" . terraform-mode))
-(register-definition-prefixes "terraform-mode" '("terraform-"))
-\f
-;;; End of scraped data
-
-(provide 'terraform-mode-autoloads)
-
-;; Local Variables:
-;; version-control: never
-;; no-byte-compile: t
-;; no-update-autoloads: t
-;; no-native-compile: t
-;; coding: utf-8-emacs-unix
-;; End:
-
-;;; terraform-mode-autoloads.el ends here
diff --git a/elpa/terraform-mode-20241217.1628/terraform-mode-pkg.el b/elpa/terraform-mode-20241217.1628/terraform-mode-pkg.el
deleted file mode 100644 (file)
index 2d4ff5e..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-;; -*- no-byte-compile: t; lexical-binding: nil -*-
-(define-package "terraform-mode" "20241217.1628"
-  "Major mode for terraform configuration file."
-  '((emacs    "24.3")
-    (hcl-mode "0.03")
-    (dash     "2.17.0"))
-  :url "https://github.com/hcl-emacs/terraform-mode"
-  :commit "5bdd734a87f67f6574664f63eb4d02a0bc74c0d0"
-  :revdesc "5bdd734a87f6"
-  :authors '(("Syohei YOSHIDA" . "syohexNOSPAMgmail.com"))
-  :maintainers '(("Syohei YOSHIDA" . "syohexNOSPAMgmail.com")))
diff --git a/elpa/terraform-mode-20241217.1628/terraform-mode.el b/elpa/terraform-mode-20241217.1628/terraform-mode.el
deleted file mode 100644 (file)
index 794f596..0000000
+++ /dev/null
@@ -1,417 +0,0 @@
-;;; terraform-mode.el --- Major mode for terraform configuration file -*- lexical-binding: t -*-
-
-;; Copyright (C) 2017 by Syohei YOSHIDA
-
-;; Author: Syohei YOSHIDA <syohexNOSPAMgmail.com>
-;; URL: https://github.com/syohex/emacs-terraform-mode
-;; Package-Version: 20241217.1628
-;; Package-Revision: 5bdd734a87f6
-;; Package-Requires: ((emacs "24.3") (hcl-mode "0.03") (dash "2.17.0"))
-
-;; 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 <http://www.gnu.org/licenses/>.
-
-;;; Commentary:
-
-;; Major mode of terraform configuration file. terraform-mode provides
-;; syntax highlighting, indentation function and formatting.
-
-;; Format the current buffer with terraform-format-buffer. To always
-;; format terraform buffers when saving, use:
-;;   (setq terraform-format-on-save t)
-
-;;; Code:
-
-(require 'cl-lib)
-(require 'rx)
-(require 'hcl-mode)
-(require 'dash)
-(require 'thingatpt)
-(require 'outline)
-
-(defgroup terraform nil
-  "Major mode of Terraform configuration file."
-  :group 'languages)
-
-(defcustom terraform-indent-level 2
-  "The tab width to use when indenting."
-  :type 'integer)
-
-(defcustom terraform-format-on-save nil
-  "Format buffer on save"
-  :type 'boolean
-  :group 'terraform-mode)
-
-(defcustom terraform-command "terraform"
-  "Command used to invoke terraform"
-  :type 'string
-  :group 'terraform-mode)
-
-(defface terraform-resource-type-face
-  '((t :inherit font-lock-type-face))
-  "Face for resource names."
-  :group 'terraform-mode)
-
-(define-obsolete-face-alias 'terraform--resource-type-face 'terraform-resource-type-face "1.0.0")
-
-(defface terraform-resource-name-face
-  '((t :inherit font-lock-function-name-face))
-  "Face for resource names."
-  :group 'terraform-mode)
-
-(define-obsolete-face-alias 'terraform--resource-name-face 'terraform-resource-name-face "1.0.0")
-
-(defface terraform-builtin-face
-  '((t :inherit font-lock-builtin-face))
-  "Face for builtins."
-  :group 'terraform-mode)
-
-(define-obsolete-face-alias 'terraform--builtin-face 'terraform-builtin-face "1.0.0")
-
-(defface terraform-variable-name-face
-  '((t :inherit font-lock-variable-name-face))
-  "Face for varriables."
-  :group 'terraform-mode)
-
-(defconst terraform--constants-regexp
-  (concat "\\(?:^\\|[^.]\\)" (regexp-opt '("null") 'words)))
-
-(defconst terraform--block-builtins-without-name-or-type-regexp
-  (rx line-start
-      (zero-or-more space)
-      (group-n 1 (or "terraform" "locals" "required_providers" "atlas" "connection"))
-      (or (one-or-more space) "{")))
-
-(defconst terraform--block-builtins-with-type-only
-  (rx (or "backend" "provider" "provisioner")))
-
-(defconst terraform--block-builtins-with-type-only--builtin-highlight-regexp
-  (eval `(rx line-start
-         (zero-or-more space)
-         (group-n 1 (regexp ,(eval terraform--block-builtins-with-type-only)))
-         (one-or-more space))))
-
-(defconst terraform--block-builtins-with-type-only--resource-type-highlight-regexp
-  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-only--builtin-highlight-regexp))
-             (group-n 2 (and (not (any "=")) (+? (not space))))
-             (or (one-or-more space) "{"))))
-
-(defconst terraform--block-builtins-with-name-only
-  (rx (or "variable" "module" "output")))
-
-(defconst terraform--block-builtins-with-name-only--builtin-highlight-regexp
-  (eval `(rx line-start
-         (zero-or-more space)
-         (group-n 1 (regexp ,(eval terraform--block-builtins-with-name-only)))
-         (one-or-more space))))
-
-(defconst terraform--block-builtins-with-name-only--name-highlight-regexp
-  (eval `(rx (regexp ,(eval terraform--block-builtins-with-name-only--builtin-highlight-regexp))
-         (group-n 2 (+? (not space)))
-         (or (one-or-more space) "{"))))
-
-(defconst terraform--block-builtins-with-type-and-name
-  (rx (or "data" "resource")))
-
-(defconst terraform--block-builtins-with-type-and-name--builtin-highlight-regexp
-  (eval `(rx line-start
-         (zero-or-more space)
-         (group-n 1 (regexp ,(eval terraform--block-builtins-with-type-and-name)))
-         (one-or-more space))))
-
-(defconst terraform--block-builtins-with-type-and-name--type-highlight-regexp
-  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-and-name--builtin-highlight-regexp))
-         (group-n 2 "\"" (+? (not space)) "\"")
-         (one-or-more space))))
-
-(defconst terraform--block-builtins-with-type-and-name--name-highlight-regexp
-  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-and-name--type-highlight-regexp))
-         (group-n 3 (+? (not space)))
-         (or (one-or-more space) "{"))))
-
-(defconst terraform--assignment-statement
-  (rx line-start
-      (zero-or-more space)
-      (group-n 1 (minimal-match (one-or-more any)))
-      (zero-or-more space)
-      "="))
-
-(defvar terraform-font-lock-keywords
-  `((,terraform--assignment-statement 1 'terraform-variable-name-face)
-    (,terraform--block-builtins-without-name-or-type-regexp 1 'terraform-builtin-face)
-    (,terraform--block-builtins-with-type-only--builtin-highlight-regexp 1 'terraform-builtin-face)
-    (,terraform--block-builtins-with-type-only--resource-type-highlight-regexp
-     (1 'terraform-builtin-face)
-     (2 'terraform-resource-type-face t))
-    (,terraform--block-builtins-with-name-only--builtin-highlight-regexp 1 'terraform-builtin-face)
-    (,terraform--block-builtins-with-name-only--name-highlight-regexp
-     (1 'terraform-builtin-face)
-     (2 'terraform-resource-name-face t))
-    (,terraform--block-builtins-with-type-and-name--builtin-highlight-regexp 1 'terraform-builtin-face)
-    (,terraform--block-builtins-with-type-and-name--type-highlight-regexp 2 'terraform-resource-type-face t)
-    (,terraform--block-builtins-with-type-and-name--name-highlight-regexp
-     (1 'terraform-builtin-face)
-     (2 'terraform-resource-type-face t)
-     (3 'terraform-resource-name-face t))
-    (,terraform--constants-regexp 1 'font-lock-constant-face)
-    ,@hcl-font-lock-keywords))
-
-(defun terraform-format-buffer ()
-  "Rewrite current buffer in a canonical format using terraform fmt."
-  (interactive)
-  (let ((buf (get-buffer-create "*terraform-fmt*")))
-    (if (zerop (call-process-region (point-min) (point-max)
-                                    terraform-command nil buf nil "fmt" "-no-color" "-"))
-        (let ((point (point))
-              (window-start (window-start)))
-          (erase-buffer)
-          (insert-buffer-substring buf)
-          (when (/= terraform-indent-level 2)
-            (indent-region (point-min) (point-max)))
-          (goto-char point)
-          (set-window-start nil window-start))
-      (message "terraform fmt: %s" (with-current-buffer buf (buffer-string))))
-    (kill-buffer buf)))
-
-(defun terraform-format-region ()
-  "Rewrite current region in a canonical format using terraform fmt."
-  (interactive)
-  (let ((buf (get-buffer-create "*terraform-fmt*")))
-    (when (use-region-p)
-    (if (zerop (call-process-region (region-beginning) (region-end)
-                                    terraform-command nil buf nil "fmt" "-"))
-        (let ((point (region-end))
-              (window-start (region-beginning)))
-          (delete-region window-start point)
-          (insert-buffer-substring buf)
-          (goto-char point)
-          (set-window-start nil window-start))
-      (message "terraform fmt: %s" (with-current-buffer buf (buffer-string))))
-    (kill-buffer buf))))
-
-(define-minor-mode terraform-format-on-save-mode
-  "Run terraform-format-buffer before saving current buffer."
-  :lighter ""
-  (if terraform-format-on-save-mode
-      (add-hook 'before-save-hook #'terraform-format-buffer nil t)
-    (remove-hook 'before-save-hook #'terraform-format-buffer t)))
-
-(defun terraform--generate-imenu ()
-  (let ((search-results (make-hash-table :test #'equal))
-    (menu-list '()))
-    (save-match-data
-      (goto-char (point-min))
-      (while (re-search-forward terraform--block-builtins-with-type-only--resource-type-highlight-regexp nil t)
-        (let ((key (match-string 1))
-          (location (match-beginning 2))
-          (resource-type (replace-regexp-in-string "\"" "" (match-string 2))))
-      (-if-let (matches (gethash key search-results))
-          (puthash key (push `(,resource-type . ,location) matches) search-results)
-        (puthash key `((,resource-type . ,location)) search-results))))
-
-
-      (goto-char (point-min))
-      (while (re-search-forward terraform--block-builtins-with-name-only--name-highlight-regexp nil t)
-        (let ((key (match-string 1))
-          (location (match-beginning 2))
-          (resource-name (replace-regexp-in-string "\"" "" (match-string 2))))
-      (-if-let (matches (gethash key search-results))
-          (puthash key (push `(,resource-name . ,location) matches) search-results)
-        (puthash key `((,resource-name . ,location)) search-results))))
-
-      (goto-char (point-min))
-      (while (re-search-forward terraform--block-builtins-with-type-and-name--name-highlight-regexp nil t)
-        (let* ((key (match-string 1))
-           (location (match-beginning 2))
-               (type (match-string 2))
-               (name (match-string 3))
-           (resource-name (concat (replace-regexp-in-string "\"" "" type)
-                      "/"
-                      (replace-regexp-in-string "\"" "" name))))
-      (-if-let (matches (gethash key search-results))
-          (puthash key (push `(,resource-name . ,location) matches) search-results)
-        (puthash key `((,resource-name . ,location)) search-results))))
-
-      (maphash (lambda (k v) (push `(,k ,@v) menu-list)) search-results)
-      menu-list)))
-
-(defun terraform--extract-provider (resource-name)
-  "Return the provider associated with a RESOURCE-NAME."
-  (car (split-string resource-name "_")))
-
-(defun terraform--extract-resource (resource-name)
-  "Return the resource associated with a RESOURCE-NAME."
-  (mapconcat #'identity (cdr (split-string resource-name "_")) "_"))
-
-(defun terraform--get-resource-provider-namespace (provider)
-  "Return provider namespace for PROVIDER."
-  (let ((provider-info (shell-command-to-string (concat terraform-command " providers"))))
-    (with-temp-buffer
-      (insert provider-info)
-      (goto-char (point-min))
-      (when (re-search-forward (concat "/\\(.*?\\)/" provider "\\]") nil t)
-        (match-string 1)))))
-
-(defun terraform--get-resource-provider-source (provider &optional dir)
-  "Return Terraform provider source for PROVIDER located in DIR.
-Terraform provider source is searched in `required_provider' declaration
-in current buffer or in other Terraform files located in the same directory
-of the file of current buffer.  If still not found, the provider source is
-searched by running command `terraform providers'.
-The DIR parameter is optional and used only for tests."
-  (goto-char (point-min))
-  ;; find current directory if it's not specified in arguments
-  (if (and (not dir) buffer-file-name) (setq dir (file-name-directory buffer-file-name)))
-  (let (tf-files
-        ;; try to find provider source in current buffer
-        (provider-source (terraform--get-resource-provider-source-in-buffer provider)))
-    ;; check if terraform provider-source was found
-    (when (and (= (length provider-source) 0) dir)
-        ;; find all terraform files of this project. One of them
-        ;; should contain required_provider declaration
-        (setq tf-files (directory-files dir nil "^[[:alnum:][:blank:]_.-]+\\.tf$")))
-    ;; iterate on terraform files until a provider source is found
-    (while (and (= (length provider-source) 0) tf-files)
-        (with-temp-buffer
-          (let* ((file (pop tf-files))
-                 (file-path (if dir (concat dir "/" file) file)))
-            (insert-file-contents file-path)
-            ;; look for provider source in a terraform file
-            (setq provider-source (terraform--get-resource-provider-source-in-buffer provider)))))
-    provider-source))
-
-(defun terraform--get-resource-provider-source-in-buffer (provider)
-  "Search and return provider namespace for PROVIDER in current buffer.
-Return nil if not found."
-  (goto-char (point-min))
-  (if (and (re-search-forward "^terraform[[:blank:]]*{" nil t)
-           (re-search-forward "^[[:blank:]]*required_providers[[:blank:]]*{" nil t)
-           (re-search-forward (concat "^[[:blank:]]*" provider "[[:blank:]]*=[[:blank:]]*{") nil t)
-           (re-search-forward "^[[:blank:]]*source[[:blank:]]*=[[:blank:]]*\"\\([a-z/]+\\)\"" nil t))
-      (match-string 1)))
-
-(defun terraform--resource-url (resource doc-dir)
-  "Return the url containing the documentation for RESOURCE using DOC-DIR."
-  (let* ((provider (terraform--extract-provider resource))
-          ;; search provider source in terraform files
-         (provider-source (terraform--get-resource-provider-source provider))
-         (resource-name (terraform--extract-resource resource)))
-    (when (= (length provider-source) 0)
-        ;; fallback to old method with terraform providers command
-      (setq provider-source (concat
-                             (terraform--get-resource-provider-namespace provider)
-                             "/" provider)))
-    (if (> (length provider-source) 0)
-        (format "https://registry.terraform.io/providers/%s/latest/docs/%s/%s"
-                provider-source doc-dir resource-name)
-      (user-error "Can not determine the provider source for %s" provider))))
-
-(defun terraform--resource-url-at-point ()
-  (save-excursion
-    (goto-char (line-beginning-position))
-    (unless (looking-at-p "^resource\\|^data")
-      (re-search-backward "^resource\\|^data" nil t))
-    (let ((doc-dir (if (equal (word-at-point) "data") "data-sources" "resources")))
-      (forward-symbol 2)
-      (terraform--resource-url (thing-at-point 'symbol) doc-dir))))
-
-(defun terraform-open-doc ()
-  "Open a browser at the URL documenting the resource at point."
-  (interactive)
-  (browse-url (terraform--resource-url-at-point)))
-
-(defun terraform-kill-doc-url ()
-  "Kill the URL documenting the resource at point (i.e. copy it to the clipboard)."
-  (interactive)
-  (let* ((url (substring-no-properties (terraform--resource-url-at-point))))
-    (kill-new url)
-    (message "Copied URL: %s" url)))
-
-(defun terraform-insert-doc-in-comment ()
-  "Insert a comment containing an URL documenting the resource at point."
-  (interactive)
-  (let ((doc-url (terraform--resource-url-at-point)))
-    (save-excursion
-      (unless (looking-at-p "^resource\\|^data")
-        (re-search-backward "^resource\\|^data" nil t))
-      (insert (format "# %s\n" doc-url)))))
-
-(defun terraform--outline-level ()
-  "Return the depth to which a statement is nested in the outline.
-
-See also `outline-level'."
-  (or (cdr (assoc (match-string 1) outline-heading-alist))
-      (- (match-end 1) (match-beginning 1))))
-
-(defun terraform--setup-outline-mode ()
-  (set (make-local-variable 'outline-level) #'terraform--outline-level)
-
-  (let ((terraform-keywords
-         (list "terraform" "locals" "required_providers" "atlas" "connection"
-               "backend" "provider" "provisioner"
-               "variable" "module" "output"
-               "data" "resource")))
-    (set (make-local-variable 'outline-regexp)
-         (concat
-          "^"
-          (regexp-opt terraform-keywords 'symbols)
-          "[[:blank:]].*{[[:blank:]]*$"))
-    (set (make-local-variable 'outline-heading-alist)
-         (mapcar
-          (lambda (item) (cons item 2))
-          terraform-keywords))))
-
-(defun terraform-toggle-or-indent (&optional arg)
-  "Toggle visibility of block under point or indent.
-
-If the point is not at the heading, call
-`indent-for-tab-command'."
-  (interactive)
-  (if (and outline-minor-mode (outline-on-heading-p))
-      (outline-toggle-children)
-    (indent-for-tab-command arg)))
-
-(defvar terraform-mode-map
-  (let ((map (make-sparse-keymap)))
-    (define-key map (kbd "C-c C-d C-w") #'terraform-open-doc)
-    (define-key map (kbd "C-c C-d C-c") #'terraform-kill-doc-url)
-    (define-key map (kbd "C-c C-d C-r") #'terraform-insert-doc-in-comment)
-    (define-key map (kbd "C-c C-f") #'outline-toggle-children)
-    map))
-
-;;;###autoload
-(define-derived-mode terraform-mode hcl-mode "Terraform"
-  "Major mode for editing terraform configuration file"
-
-  (setq font-lock-defaults '((terraform-font-lock-keywords)))
-  (when terraform-format-on-save
-    (terraform-format-on-save-mode 1))
-
-  ;; indentation
-  (make-local-variable 'terraform-indent-level)
-  (setq hcl-indent-level terraform-indent-level)
-
-  ;; outline
-  (terraform--setup-outline-mode)
-
-  ;; imenu
-  (setq imenu-sort-function 'imenu--sort-by-name)
-  (setq imenu-create-index-function 'terraform--generate-imenu)
-  (imenu-add-to-menubar "Terraform"))
-
-;;;###autoload
-(add-to-list 'auto-mode-alist '("\\.t\\(f\\(vars\\)?\\|ofu\\)\\'" . terraform-mode))
-
-(provide 'terraform-mode)
-
-;;; terraform-mode.el ends here
diff --git a/elpa/terraform-mode-20251115.2210/terraform-mode-autoloads.el b/elpa/terraform-mode-20251115.2210/terraform-mode-autoloads.el
new file mode 100644 (file)
index 0000000..f7c3217
--- /dev/null
@@ -0,0 +1,33 @@
+;;; terraform-mode-autoloads.el --- automatically extracted autoloads (do not edit)   -*- lexical-binding: t -*-
+;; Generated by the `loaddefs-generate' function.
+
+;; This file is part of GNU Emacs.
+
+;;; Code:
+
+(add-to-list 'load-path (or (and load-file-name (directory-file-name (file-name-directory load-file-name))) (car load-path)))
+
+
+\f
+;;; Generated autoloads from terraform-mode.el
+
+(autoload 'terraform-mode "terraform-mode" "\
+Major mode for editing terraform configuration file
+
+(fn)" t)
+(add-to-list 'auto-mode-alist '("\\.t\\(f\\(vars\\)?\\|ofu\\)\\'" . terraform-mode))
+(register-definition-prefixes "terraform-mode" '("terraform-"))
+\f
+;;; End of scraped data
+
+(provide 'terraform-mode-autoloads)
+
+;; Local Variables:
+;; version-control: never
+;; no-byte-compile: t
+;; no-update-autoloads: t
+;; no-native-compile: t
+;; coding: utf-8-emacs-unix
+;; End:
+
+;;; terraform-mode-autoloads.el ends here
diff --git a/elpa/terraform-mode-20251115.2210/terraform-mode-pkg.el b/elpa/terraform-mode-20251115.2210/terraform-mode-pkg.el
new file mode 100644 (file)
index 0000000..025c3bc
--- /dev/null
@@ -0,0 +1,11 @@
+;; -*- no-byte-compile: t; lexical-binding: nil -*-
+(define-package "terraform-mode" "20251115.2210"
+  "Major mode for terraform configuration file."
+  '((emacs    "24.3")
+    (hcl-mode "0.03")
+    (dash     "2.17.0"))
+  :url "https://github.com/syohex/emacs-terraform-mode"
+  :commit "01635df3625c0cec2bb4613a6f920b8569d41009"
+  :revdesc "01635df3625c"
+  :authors '(("Syohei YOSHIDA" . "syohexNOSPAMgmail.com"))
+  :maintainers '(("Syohei YOSHIDA" . "syohexNOSPAMgmail.com")))
diff --git a/elpa/terraform-mode-20251115.2210/terraform-mode.el b/elpa/terraform-mode-20251115.2210/terraform-mode.el
new file mode 100644 (file)
index 0000000..e12133c
--- /dev/null
@@ -0,0 +1,417 @@
+;;; terraform-mode.el --- Major mode for terraform configuration file -*- lexical-binding: t -*-
+
+;; Copyright (C) 2017 by Syohei YOSHIDA
+
+;; Author: Syohei YOSHIDA <syohexNOSPAMgmail.com>
+;; URL: https://github.com/syohex/emacs-terraform-mode
+;; Package-Version: 20251115.2210
+;; Package-Revision: 01635df3625c
+;; Package-Requires: ((emacs "24.3") (hcl-mode "0.03") (dash "2.17.0"))
+
+;; 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 <http://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Major mode of terraform configuration file. terraform-mode provides
+;; syntax highlighting, indentation function and formatting.
+
+;; Format the current buffer with terraform-format-buffer. To always
+;; format terraform buffers when saving, use:
+;;   (setq terraform-format-on-save t)
+
+;;; Code:
+
+(require 'cl-lib)
+(require 'rx)
+(require 'hcl-mode)
+(require 'dash)
+(require 'thingatpt)
+(require 'outline)
+
+(defgroup terraform nil
+  "Major mode of Terraform configuration file."
+  :group 'languages)
+
+(defcustom terraform-indent-level 2
+  "The tab width to use when indenting."
+  :type 'integer)
+
+(defcustom terraform-format-on-save nil
+  "Format buffer on save"
+  :type 'boolean
+  :group 'terraform-mode)
+
+(defcustom terraform-command "terraform"
+  "Command used to invoke terraform"
+  :type 'string
+  :group 'terraform-mode)
+
+(defface terraform-resource-type-face
+  '((t :inherit font-lock-type-face))
+  "Face for resource names."
+  :group 'terraform-mode)
+
+(define-obsolete-face-alias 'terraform--resource-type-face 'terraform-resource-type-face "1.0.0")
+
+(defface terraform-resource-name-face
+  '((t :inherit font-lock-function-name-face))
+  "Face for resource names."
+  :group 'terraform-mode)
+
+(define-obsolete-face-alias 'terraform--resource-name-face 'terraform-resource-name-face "1.0.0")
+
+(defface terraform-builtin-face
+  '((t :inherit font-lock-builtin-face))
+  "Face for builtins."
+  :group 'terraform-mode)
+
+(define-obsolete-face-alias 'terraform--builtin-face 'terraform-builtin-face "1.0.0")
+
+(defface terraform-variable-name-face
+  '((t :inherit font-lock-variable-name-face))
+  "Face for varriables."
+  :group 'terraform-mode)
+
+(defconst terraform--constants-regexp
+  (concat "\\(?:^\\|[^.]\\)" (regexp-opt '("null") 'words)))
+
+(defconst terraform--block-builtins-without-name-or-type-regexp
+  (rx line-start
+      (zero-or-more space)
+      (group-n 1 (or "terraform" "locals" "required_providers" "atlas" "connection"))
+      (or (one-or-more space) "{")))
+
+(defconst terraform--block-builtins-with-type-only
+  (rx (or "backend" "provider" "provisioner")))
+
+(defconst terraform--block-builtins-with-type-only--builtin-highlight-regexp
+  (eval `(rx line-start
+             (zero-or-more space)
+             (group-n 1 (regexp ,(eval terraform--block-builtins-with-type-only)))
+             (one-or-more space))))
+
+(defconst terraform--block-builtins-with-type-only--resource-type-highlight-regexp
+  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-only--builtin-highlight-regexp))
+             (group-n 2 (and (not (any "=")) (+? (not space))))
+             (or (one-or-more space) "{"))))
+
+(defconst terraform--block-builtins-with-name-only
+  (rx (or "variable" "module" "output")))
+
+(defconst terraform--block-builtins-with-name-only--builtin-highlight-regexp
+  (eval `(rx line-start
+             (zero-or-more space)
+             (group-n 1 (regexp ,(eval terraform--block-builtins-with-name-only)))
+             (one-or-more space))))
+
+(defconst terraform--block-builtins-with-name-only--name-highlight-regexp
+  (eval `(rx (regexp ,(eval terraform--block-builtins-with-name-only--builtin-highlight-regexp))
+             (group-n 2 (+? (not space)))
+             (or (one-or-more space) "{"))))
+
+(defconst terraform--block-builtins-with-type-and-name
+  (rx (or "data" "resource" "ephemeral")))
+
+(defconst terraform--block-builtins-with-type-and-name--builtin-highlight-regexp
+  (eval `(rx line-start
+             (zero-or-more space)
+             (group-n 1 (regexp ,(eval terraform--block-builtins-with-type-and-name)))
+             (one-or-more space))))
+
+(defconst terraform--block-builtins-with-type-and-name--type-highlight-regexp
+  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-and-name--builtin-highlight-regexp))
+             (group-n 2 "\"" (+? (not space)) "\"")
+             (one-or-more space))))
+
+(defconst terraform--block-builtins-with-type-and-name--name-highlight-regexp
+  (eval `(rx (regexp ,(eval terraform--block-builtins-with-type-and-name--type-highlight-regexp))
+             (group-n 3 (+? (not space)))
+             (or (one-or-more space) "{"))))
+
+(defconst terraform--assignment-statement
+  (rx line-start
+      (zero-or-more space)
+      (group-n 1 (minimal-match (one-or-more any)))
+      (zero-or-more space)
+      "="))
+
+(defvar terraform-font-lock-keywords
+  `((,terraform--assignment-statement 1 'terraform-variable-name-face)
+    (,terraform--block-builtins-without-name-or-type-regexp 1 'terraform-builtin-face)
+    (,terraform--block-builtins-with-type-only--builtin-highlight-regexp 1 'terraform-builtin-face)
+    (,terraform--block-builtins-with-type-only--resource-type-highlight-regexp
+     (1 'terraform-builtin-face)
+     (2 'terraform-resource-type-face t))
+    (,terraform--block-builtins-with-name-only--builtin-highlight-regexp 1 'terraform-builtin-face)
+    (,terraform--block-builtins-with-name-only--name-highlight-regexp
+     (1 'terraform-builtin-face)
+     (2 'terraform-resource-name-face t))
+    (,terraform--block-builtins-with-type-and-name--builtin-highlight-regexp 1 'terraform-builtin-face)
+    (,terraform--block-builtins-with-type-and-name--type-highlight-regexp 2 'terraform-resource-type-face t)
+    (,terraform--block-builtins-with-type-and-name--name-highlight-regexp
+     (1 'terraform-builtin-face)
+     (2 'terraform-resource-type-face t)
+     (3 'terraform-resource-name-face t))
+    (,terraform--constants-regexp 1 'font-lock-constant-face)
+    ,@hcl-font-lock-keywords))
+
+(defun terraform-format-buffer ()
+  "Rewrite current buffer in a canonical format using terraform fmt."
+  (interactive)
+  (let ((buf (get-buffer-create "*terraform-fmt*")))
+    (if (zerop (call-process-region (point-min) (point-max)
+                                    terraform-command nil buf nil "fmt" "-no-color" "-"))
+        (let ((point (point))
+              (window-start (window-start)))
+          (erase-buffer)
+          (insert-buffer-substring buf)
+          (when (/= terraform-indent-level 2)
+            (indent-region (point-min) (point-max)))
+          (goto-char point)
+          (set-window-start nil window-start))
+      (message "terraform fmt: %s" (with-current-buffer buf (buffer-string))))
+    (kill-buffer buf)))
+
+(defun terraform-format-region ()
+  "Rewrite current region in a canonical format using terraform fmt."
+  (interactive)
+  (let ((buf (get-buffer-create "*terraform-fmt*")))
+    (when (use-region-p)
+      (if (zerop (call-process-region (region-beginning) (region-end)
+                                      terraform-command nil buf nil "fmt" "-"))
+          (let ((point (region-end))
+                (window-start (region-beginning)))
+            (delete-region window-start point)
+            (insert-buffer-substring buf)
+            (goto-char point)
+            (set-window-start nil window-start))
+        (message "terraform fmt: %s" (with-current-buffer buf (buffer-string))))
+      (kill-buffer buf))))
+
+(define-minor-mode terraform-format-on-save-mode
+  "Run terraform-format-buffer before saving current buffer."
+  :lighter ""
+  (if terraform-format-on-save-mode
+      (add-hook 'before-save-hook #'terraform-format-buffer nil t)
+    (remove-hook 'before-save-hook #'terraform-format-buffer t)))
+
+(defun terraform--generate-imenu ()
+  (let ((search-results (make-hash-table :test #'equal))
+        (menu-list '()))
+    (save-match-data
+      (goto-char (point-min))
+      (while (re-search-forward terraform--block-builtins-with-type-only--resource-type-highlight-regexp nil t)
+        (let ((key (match-string 1))
+              (location (match-beginning 2))
+              (resource-type (replace-regexp-in-string "\"" "" (match-string 2))))
+          (-if-let (matches (gethash key search-results))
+              (puthash key (push `(,resource-type . ,location) matches) search-results)
+            (puthash key `((,resource-type . ,location)) search-results))))
+
+
+      (goto-char (point-min))
+      (while (re-search-forward terraform--block-builtins-with-name-only--name-highlight-regexp nil t)
+        (let ((key (match-string 1))
+              (location (match-beginning 2))
+              (resource-name (replace-regexp-in-string "\"" "" (match-string 2))))
+          (-if-let (matches (gethash key search-results))
+              (puthash key (push `(,resource-name . ,location) matches) search-results)
+            (puthash key `((,resource-name . ,location)) search-results))))
+
+      (goto-char (point-min))
+      (while (re-search-forward terraform--block-builtins-with-type-and-name--name-highlight-regexp nil t)
+        (let* ((key (match-string 1))
+               (location (match-beginning 2))
+               (type (match-string 2))
+               (name (match-string 3))
+               (resource-name (concat (replace-regexp-in-string "\"" "" type)
+                                      "/"
+                                      (replace-regexp-in-string "\"" "" name))))
+          (-if-let (matches (gethash key search-results))
+              (puthash key (push `(,resource-name . ,location) matches) search-results)
+            (puthash key `((,resource-name . ,location)) search-results))))
+
+      (maphash (lambda (k v) (push `(,k ,@v) menu-list)) search-results)
+      menu-list)))
+
+(defun terraform--extract-provider (resource-name)
+  "Return the provider associated with a RESOURCE-NAME."
+  (car (split-string resource-name "_")))
+
+(defun terraform--extract-resource (resource-name)
+  "Return the resource associated with a RESOURCE-NAME."
+  (mapconcat #'identity (cdr (split-string resource-name "_")) "_"))
+
+(defun terraform--get-resource-provider-namespace (provider)
+  "Return provider namespace for PROVIDER."
+  (let ((provider-info (shell-command-to-string (concat terraform-command " providers"))))
+    (with-temp-buffer
+      (insert provider-info)
+      (goto-char (point-min))
+      (when (re-search-forward (concat "/\\(.*?\\)/" provider "\\]") nil t)
+        (match-string 1)))))
+
+(defun terraform--get-resource-provider-source (provider &optional dir)
+  "Return Terraform provider source for PROVIDER located in DIR.
+Terraform provider source is searched in `required_provider' declaration
+in current buffer or in other Terraform files located in the same directory
+of the file of current buffer.  If still not found, the provider source is
+searched by running command `terraform providers'.
+The DIR parameter is optional and used only for tests."
+  (goto-char (point-min))
+  ;; find current directory if it's not specified in arguments
+  (if (and (not dir) buffer-file-name) (setq dir (file-name-directory buffer-file-name)))
+  (let (tf-files
+        ;; try to find provider source in current buffer
+        (provider-source (terraform--get-resource-provider-source-in-buffer provider)))
+    ;; check if terraform provider-source was found
+    (when (and (= (length provider-source) 0) dir)
+      ;; find all terraform files of this project. One of them
+      ;; should contain required_provider declaration
+      (setq tf-files (directory-files dir nil "^[[:alnum:][:blank:]_.-]+\\.tf$")))
+    ;; iterate on terraform files until a provider source is found
+    (while (and (= (length provider-source) 0) tf-files)
+      (with-temp-buffer
+        (let* ((file (pop tf-files))
+               (file-path (if dir (concat dir "/" file) file)))
+          (insert-file-contents file-path)
+          ;; look for provider source in a terraform file
+          (setq provider-source (terraform--get-resource-provider-source-in-buffer provider)))))
+    provider-source))
+
+(defun terraform--get-resource-provider-source-in-buffer (provider)
+  "Search and return provider namespace for PROVIDER in current buffer.
+Return nil if not found."
+  (goto-char (point-min))
+  (if (and (re-search-forward "^terraform[[:blank:]]*{" nil t)
+           (re-search-forward "^[[:blank:]]*required_providers[[:blank:]]*{" nil t)
+           (re-search-forward (concat "^[[:blank:]]*" provider "[[:blank:]]*=[[:blank:]]*{") nil t)
+           (re-search-forward "^[[:blank:]]*source[[:blank:]]*=[[:blank:]]*\"\\([a-z/]+\\)\"" nil t))
+      (match-string 1)))
+
+(defun terraform--resource-url (resource doc-dir)
+  "Return the url containing the documentation for RESOURCE using DOC-DIR."
+  (let* ((provider (terraform--extract-provider resource))
+         ;; search provider source in terraform files
+         (provider-source (terraform--get-resource-provider-source provider))
+         (resource-name (terraform--extract-resource resource)))
+    (when (= (length provider-source) 0)
+      ;; fallback to old method with terraform providers command
+      (setq provider-source (concat
+                             (terraform--get-resource-provider-namespace provider)
+                             "/" provider)))
+    (if (> (length provider-source) 0)
+        (format "https://registry.terraform.io/providers/%s/latest/docs/%s/%s"
+                provider-source doc-dir resource-name)
+      (user-error "Can not determine the provider source for %s" provider))))
+
+(defun terraform--resource-url-at-point ()
+  (save-excursion
+    (goto-char (line-beginning-position))
+    (unless (looking-at-p "^resource\\|^data")
+      (re-search-backward "^resource\\|^data" nil t))
+    (let ((doc-dir (if (equal (word-at-point) "data") "data-sources" "resources")))
+      (forward-symbol 2)
+      (terraform--resource-url (thing-at-point 'symbol) doc-dir))))
+
+(defun terraform-open-doc ()
+  "Open a browser at the URL documenting the resource at point."
+  (interactive)
+  (browse-url (terraform--resource-url-at-point)))
+
+(defun terraform-kill-doc-url ()
+  "Kill the URL documenting the resource at point (i.e. copy it to the clipboard)."
+  (interactive)
+  (let* ((url (substring-no-properties (terraform--resource-url-at-point))))
+    (kill-new url)
+    (message "Copied URL: %s" url)))
+
+(defun terraform-insert-doc-in-comment ()
+  "Insert a comment containing an URL documenting the resource at point."
+  (interactive)
+  (let ((doc-url (terraform--resource-url-at-point)))
+    (save-excursion
+      (unless (looking-at-p "^resource\\|^data")
+        (re-search-backward "^resource\\|^data" nil t))
+      (insert (format "# %s\n" doc-url)))))
+
+(defun terraform--outline-level ()
+  "Return the depth to which a statement is nested in the outline.
+
+See also `outline-level'."
+  (or (cdr (assoc (match-string 1) outline-heading-alist))
+      (- (match-end 1) (match-beginning 1))))
+
+(defun terraform--setup-outline-mode ()
+  (set (make-local-variable 'outline-level) #'terraform--outline-level)
+
+  (let ((terraform-keywords
+         (list "terraform" "locals" "required_providers" "atlas" "connection"
+               "backend" "provider" "provisioner"
+               "variable" "module" "output"
+               "data" "resource")))
+    (set (make-local-variable 'outline-regexp)
+         (concat
+          "^"
+          (regexp-opt terraform-keywords 'symbols)
+          "[[:blank:]].*{[[:blank:]]*$"))
+    (set (make-local-variable 'outline-heading-alist)
+         (mapcar
+          (lambda (item) (cons item 2))
+          terraform-keywords))))
+
+(defun terraform-toggle-or-indent (&optional arg)
+  "Toggle visibility of block under point or indent.
+
+If the point is not at the heading, call
+`indent-for-tab-command'."
+  (interactive)
+  (if (and outline-minor-mode (outline-on-heading-p))
+      (outline-toggle-children)
+    (indent-for-tab-command arg)))
+
+(defvar terraform-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map (kbd "C-c C-d C-w") #'terraform-open-doc)
+    (define-key map (kbd "C-c C-d C-c") #'terraform-kill-doc-url)
+    (define-key map (kbd "C-c C-d C-r") #'terraform-insert-doc-in-comment)
+    (define-key map (kbd "C-c C-f") #'outline-toggle-children)
+    map))
+
+;;;###autoload
+(define-derived-mode terraform-mode hcl-mode "Terraform"
+  "Major mode for editing terraform configuration file"
+
+  (setq font-lock-defaults '((terraform-font-lock-keywords)))
+  (when terraform-format-on-save
+    (terraform-format-on-save-mode 1))
+
+  ;; indentation
+  (make-local-variable 'terraform-indent-level)
+  (setq hcl-indent-level terraform-indent-level)
+
+  ;; outline
+  (terraform--setup-outline-mode)
+
+  ;; imenu
+  (set (make-local-variable 'imenu-sort-function) 'imenu--sort-by-name)
+  (setq imenu-create-index-function 'terraform--generate-imenu)
+  (imenu-add-to-menubar "Terraform"))
+
+;;;###autoload
+(add-to-list 'auto-mode-alist '("\\.t\\(f\\(vars\\)?\\|ofu\\)\\'" . terraform-mode))
+
+(provide 'terraform-mode)
+
+;;; terraform-mode.el ends here