├── .gitignore ├── LICENSE ├── README.md ├── utils └── generate.py └── company-terraform.el /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *.elc 3 | *.tf 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Rafał Cieślak 2 | 3 | This software is provided 'as-is', without any express or implied 4 | warranty. In no event will the authors be held liable for any damages 5 | arising from the use of this software. 6 | 7 | Permission is granted to anyone to use this software for any purpose, 8 | including commercial applications, and to alter it and redistribute it 9 | freely, subject to the following restrictions: 10 | 11 | 1. The origin of this software must not be misrepresented; you must not 12 | claim that you wrote the original software. If you use this software 13 | in a product, an acknowledgement in the product documentation would be 14 | appreciated but is not required. 15 | 2. Altered source versions must be plainly marked as such, and must not be 16 | misrepresented as being the original software. 17 | 3. This notice may not be removed or altered from any source distribution. 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # company-terraform [![MELPA](https://melpa.org/packages/company-terraform-badge.svg)](https://melpa.org/#/company-terraform) [![MELPA Stable](https://stable.melpa.org/packages/company-terraform-badge.svg)](https://stable.melpa.org/#/company-terraform) 2 | 3 | Company backend for terraform files. 4 | 5 | [asciinema](https://asciinema.org/a/132870) 6 | 7 | ## Features 8 | 9 | Autompletes: 10 | 11 | - Arguments and meta-parameters in data and resource blocks 12 | - Resource type in data and resource declarations 13 | - Top-level keywords 14 | - In interpolations: 15 | - Built-in functions 16 | - Resource and data types and names, conveniently limited to those existing in your project 17 | - Resource and data arguments and attributes 18 | - Variable names 19 | - Meta-parameters and keywords 20 | 21 | ## Usage 22 | 23 | To enable `company-terraform`, call `M-x company-terraform-init`, or add 24 | 25 | ``` 26 | (require 'company-terraform) 27 | (company-terraform-init) 28 | ``` 29 | 30 | to your `init.el`. 31 | 32 | Completions will appear when both `company-mode` and `terraform-mode` are active. 33 | 34 | I recommend also enabling [`company-quickhelp`](https://github.com/expez/company-quickhelp), 35 | which conviniently previews documentation string for the selected completion candidate. 36 | 37 | `company-terraform` uses the data provided by the [official Terraform documentation](https://www.terraform.io/docs/). 38 | -------------------------------------------------------------------------------- /utils/generate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | ## This file generates company-terraform-data.el. 4 | 5 | ## It's basically a simple HTTP scraper. It walks through terraform 6 | ## documentation on https://www.terraform.io/docs/ and extracts resources, 7 | ## arguments, attributes and their documentation. 8 | 9 | ## But terraform documentation formatting is quite inconsistent. So the actual 10 | ## logic in this file is a complete mess and a ton of weird edge cases that are 11 | ## implemented only to support some particular formatting mistakes. Oh well. 12 | 13 | ## So I did not bother to write this script clearly or to document it. It will 14 | ## need to be entirely rewritten every few weeks :( 15 | 16 | ## Therefore please do not use this file to teach kids Python. 17 | 18 | import requests 19 | from bs4 import BeautifulSoup 20 | import json 21 | 22 | provider_list = [] 23 | resource_list = [] 24 | functions_list = [] 25 | 26 | header = """ 27 | ;;; company-terraform-data.el --- Terraform documentation as elisp lists and hashes 28 | 29 | ;; THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT. 30 | 31 | ;;; Code: 32 | 33 | (defconst company-terraform-toplevel-keywords '( 34 | ("resource" "Defines a new resource") 35 | ("variable" "Defines a variable or module input") 36 | ("data" "Defines a new data source") 37 | ("output" "Defines an output value or module output") 38 | )) 39 | 40 | (defconst company-terraform-interpolation-extra '( 41 | ("module." "References a module") 42 | ("var." "References a variable") 43 | ("data." "References a data source") 44 | ("count." "Resource index metadata") 45 | )) 46 | 47 | (defconst company-terraform-resource-extra '( 48 | ("count" "count (int) - The number of identical resources to create. This doesn't apply to all resources.") 49 | ("depends_on" "depends_on (list of strings) - Explicit dependencies that this resource has. These dependencies will be created before this resource.") 50 | ("provider" "provider (string) - The name of a specific provider to use for this resource. The name is in the format of TYPE.ALIAS, for example, aws.west. Where west is set using the alias attribute in a provider.") 51 | ("lifecycle" "Customizes the lifecycle behavior of the resource.") 52 | )) 53 | 54 | (defconst company-terraform-data-extra '( 55 | ("count" "count (int) - The number of identical resources to create. This doesn't apply to all resources.") 56 | ("depends_on" "depends_on (list of strings) - Explicit dependencies that this resource has. These dependencies will be created before this resource.") 57 | ("provider" "provider (string) - The name of a specific provider to use for this resource. The name is in the format of TYPE.ALIAS, for example, aws.west. Where west is set using the alias attribute in a provider.") 58 | )) 59 | 60 | (defconst company-terraform-count-extra '( 61 | ("index" "index (int) - Current counted resource index.") 62 | )) 63 | 64 | (defconst company-terraform-resource-arguments-hash 65 | (make-hash-table :test 'equal)) 66 | (defconst company-terraform-data-arguments-hash 67 | (make-hash-table :test 'equal)) 68 | (defconst company-terraform-resource-attributes-hash 69 | (make-hash-table :test 'equal)) 70 | (defconst company-terraform-data-attributes-hash 71 | (make-hash-table :test 'equal)) 72 | """ 73 | 74 | footer = """ 75 | (provide 'company-terraform-data) 76 | ;;; company-terraform-data.el ends here 77 | """ 78 | 79 | def get_sibling(element): 80 | sibling = element.next_sibling 81 | if not sibling: 82 | return sibling 83 | if sibling == "\n": 84 | return get_sibling(sibling) 85 | else: 86 | return sibling 87 | 88 | escaping = str.maketrans({"\"": "\\\"", 89 | "\\": "\\\\", 90 | "\n": "\\n"}) 91 | s = requests.Session() 92 | 93 | def get_providers(): 94 | page = s.get("https://www.terraform.io/docs/providers/index.html") 95 | assert(page.status_code is 200) 96 | fixed = page.content.replace(b"

0: 124 | argumentref = argumentrefs[0] 125 | ul = get_sibling(argumentref) 126 | while ul is not None: 127 | if ul.name == 'ul': 128 | lis = ul.find_all('li', recursive=False) 129 | for li in lis: 130 | codes = li.find_all('code') 131 | if len(codes) > 0: 132 | argname = codes[0].get_text() 133 | else: 134 | aas = li.find_all('a') 135 | if len(aas) is 0: 136 | continue 137 | if aas[0].get('name') is not None: 138 | argname = aas[0]['name'] 139 | else: 140 | argname = li.get_text().split('-')[0].strip() 141 | argdoc = li.get_text().replace("\n", " ").strip() 142 | arglist.append({'name': argname, 'doc': argdoc.translate(escaping)}) 143 | break 144 | if ul.name == 'h2': 145 | break 146 | ul = get_sibling(ul) 147 | return arglist 148 | 149 | 150 | def get_resource_params(name, docpath): 151 | blacklist = [ 152 | 'vault_aws_auth_backend_role_tag' # This resource has a completely 153 | # broken docpage. 154 | ] 155 | if name in blacklist: 156 | return 157 | page = s.get("https://www.terraform.io%s" % docpath) 158 | kind = {'r': 'resource', 'd': 'data', 'external': 'data', 'http': 'data'}[docpath.split('/')[-2]] 159 | assert(page.status_code is 200) 160 | soup = BeautifulSoup(page.content, 'html.parser') 161 | headers = soup.find_all('h1', id=name) 162 | if len(headers) is 0: 163 | # Sometimes docs are bonkers and use a wring header. 164 | headers = soup.find_all('h1') 165 | header = headers[0] 166 | maindoc = "" 167 | p = get_sibling(header) 168 | while p and p.name != 'h2': 169 | maindoc += p.get_text().replace("\n", " ") + "\n\n" 170 | p = get_sibling(p) 171 | maindoc = maindoc.strip() 172 | 173 | arglist = arghelper('argument', soup) 174 | attrlist = arghelper('attributes', soup) 175 | 176 | return { 177 | 'name': name, 178 | 'doc': maindoc.translate(escaping), 179 | 'args': arglist, 180 | 'attrs': attrlist, 181 | 'type': kind 182 | } 183 | 184 | def get_interpolation_functions(): 185 | page = s.get("https://www.terraform.io/docs/configuration/interpolation.html") 186 | soup = BeautifulSoup(page.content, 'html.parser') 187 | header = soup.find_all('h2', id='built-in-functions')[0] 188 | ul = get_sibling(header) 189 | while ul.name != 'ul': 190 | ul = get_sibling(ul) 191 | lis = ul.find_all('li', recursive=False) 192 | for li in lis: 193 | aas = li.find_all('a') 194 | if len(aas) is 0: 195 | continue 196 | funcsig = aas[1].get_text() 197 | funcname = funcsig.split("(")[0] 198 | funcparam = funcsig[len(funcname):] 199 | funcdoc = li.get_text().replace("\n", " ").strip() 200 | functions_list.append({'name': funcname, 'doc': funcdoc.translate(escaping), 'param': funcparam}) 201 | 202 | 203 | def gather_all_by_provider(provider): 204 | global resource_list 205 | print("Gathering resources for %s" % provider) 206 | resources = get_resources_by_provider(provider) 207 | for name, docpath in resources: 208 | print("- " + name) 209 | params = get_resource_params(name, docpath) 210 | if params is not None: 211 | resource_list.append(params) 212 | 213 | 214 | def prepare_file(): 215 | print("Generating file...") 216 | data = "" 217 | 218 | # First, build up bare resource list. 219 | reslist = "" 220 | for resource in resource_list: 221 | if resource['type'] == 'resource': 222 | reslist += " (\"%(name)s\" \"%(doc)s\")\n" % { 223 | 'name': resource['name'], 224 | 'doc': resource['doc']} 225 | data += "(defconst company-terraform-resources-list '(\n" + reslist + " ))\n\n" 226 | 227 | # Then, resource argument hashes. 228 | for resource in resource_list: 229 | if resource['type'] == 'resource': 230 | arglist = "" 231 | for argument in resource['args']: 232 | arglist += " (\"%(name)s\" \"%(doc)s\")\n" % { 233 | 'name': argument['name'], 234 | 'doc': argument['doc']} 235 | data += "(puthash \"%(name)s\" '(\n%(arglist)s\n ) company-terraform-resource-arguments-hash)\n\n" % { 236 | 'name': resource['name'], 237 | 'arglist': arglist} 238 | 239 | # Then, resource attribute hashes. 240 | for resource in resource_list: 241 | if resource['type'] == 'resource': 242 | attrlist = "" 243 | for attribute in resource['attrs']: 244 | attrlist += " (\"%(name)s\" \"%(doc)s\")\n" % { 245 | 'name':attribute['name'], 246 | 'doc': attribute['doc']} 247 | data += "(puthash \"%(name)s\" '(\n%(attrlist)s\n ) company-terraform-resource-attributes-hash)\n\n" % { 248 | 'name': resource['name'], 249 | 'attrlist': attrlist} 250 | 251 | # Data list. 252 | reslist = "" 253 | for resource in resource_list: 254 | if resource['type'] == 'data': 255 | reslist += " (\"%(name)s\" \"%(doc)s\")\n" % { 256 | 'name': resource['name'], 257 | 'doc': resource['doc']} 258 | data += "(defconst company-terraform-data-list '(\n" + reslist + " ))\n\n" 259 | 260 | # Then, data argument hashes. 261 | for resource in resource_list: 262 | if resource['type'] == 'data': 263 | arglist = "" 264 | for argument in resource['args']: 265 | arglist += " (\"%(name)s\" \"%(doc)s\")\n" % { 266 | 'name': argument['name'], 267 | 'doc': argument['doc']} 268 | data += "(puthash \"%(name)s\" '(\n%(arglist)s\n ) company-terraform-data-arguments-hash)\n\n" % { 269 | 'name': resource['name'], 270 | 'arglist': arglist} 271 | 272 | # Then, data attribute hashes. 273 | for resource in resource_list: 274 | if resource['type'] == 'data': 275 | attrlist = "" 276 | for attribute in resource['attrs']: 277 | attrlist += " (\"%(name)s\" \"%(doc)s\")\n" % { 278 | 'name':attribute['name'], 279 | 'doc': attribute['doc']} 280 | data += "(puthash \"%(name)s\" '(\n%(attrlist)s\n ) company-terraform-data-attributes-hash)\n\n" % { 281 | 'name': resource['name'], 282 | 'attrlist': attrlist} 283 | 284 | funclist = "" 285 | for func in functions_list: 286 | funclist += " (\"%(name)s\" \"%(doc)s\")\n" % { 287 | 'name':func['name'], 288 | 'doc': func['doc']} 289 | data += "(defconst company-terraform-interpolation-functions '(\n" + funclist + " ))\n\n" 290 | 291 | with open("company-terraform-data.el", "w") as file: 292 | file.write(header + data + footer) 293 | 294 | get_providers() 295 | for p in provider_list: 296 | gather_all_by_provider(p) 297 | get_interpolation_functions() 298 | prepare_file() 299 | -------------------------------------------------------------------------------- /company-terraform.el: -------------------------------------------------------------------------------- 1 | ;;; company-terraform.el --- A company backend for terraform 2 | 3 | ;; Copyright (C) 2017 Rafał Cieślak 4 | 5 | ;; Author: Rafał Cieślak 6 | ;; Version: 1.0 7 | ;; Package-Requires: ((emacs "24.4") (company "0.8.12") (terraform-mode "0.06")) 8 | ;; Created: 10 August 2017 9 | ;; Keywords: abbrev, convenience, terraform, company 10 | ;; URL: https://github.com/rafalcieslak/emacs-company-terraform 11 | 12 | ;;; Commentary: 13 | 14 | ;; company-terraform provides a company backend for terraform files. It enables 15 | ;; context-aware autocompletion for terraform sources. This includes resource 16 | ;; and data arguments and attributes, both in resource and data blocks as well 17 | ;; as in interpolations, built-in functions and top-level keywords. 18 | 19 | ;;; Code: 20 | 21 | (require 'company) 22 | (require 'cl-lib) 23 | (require 'subr-x) 24 | (require 'terraform-mode) 25 | 26 | (require 'company-terraform-data) 27 | 28 | (defun company-terraform--scan-resources (dir) 29 | "Search .tf files in DIR for resource data and variable blocks." 30 | (let* ((files (directory-files dir t "\\.tf$")) 31 | (datas (make-hash-table :test 'equal)) 32 | (resources (make-hash-table :test 'equal)) 33 | (variables '()) 34 | (outputs '()) 35 | (locals '()) 36 | (modules '()) 37 | (modules-with-dirs (make-hash-table :test 'equal))) 38 | (dolist (file files) 39 | (with-temp-buffer 40 | (if (find-buffer-visiting file) 41 | ;; If this file is being edited, use the current (possibly unsaved) version. 42 | (insert (with-current-buffer (find-buffer-visiting file) (buffer-string))) 43 | ;; Otherwise just open the file from file system. 44 | (ignore-errors (insert-file-contents file))) 45 | (goto-char 1) ; Start by searching for data and resource blocks. 46 | (while (re-search-forward "\\(resource\\|data\\)[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*{" nil t) 47 | (let* ((kind (intern (match-string-no-properties 1))) 48 | (hash-to-use (cl-case kind 49 | ('data datas) 50 | ('resource resources))) 51 | (type (match-string-no-properties 2)) 52 | (name (match-string-no-properties 3))) 53 | (when (eq 'empty (gethash type hash-to-use 'empty)) 54 | (puthash type '() hash-to-use)) 55 | (push name (gethash type hash-to-use)))) 56 | (goto-char 1) ; Then search for variable blocks. 57 | (while (re-search-forward "variable[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*{" nil t) 58 | (push (match-string-no-properties 1) variables)) 59 | (goto-char 1) ; Then search for output blocks. 60 | (while (re-search-forward "output[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*{" nil t) 61 | (push (match-string-no-properties 1) outputs)) 62 | (goto-char 1) ; Then search for locals 63 | (while (re-search-forward "locals[[:space:]\n]*{" nil t) 64 | (let ((end (save-excursion (backward-char) (forward-sexp) (point)))) 65 | ;; TODO: This will also find sub-keys for locals which are nested dicts. 66 | (while (re-search-forward "\n[[:space:]]*\\([^[:space:]\n#]*\\)[[:space:]]*=" end t) 67 | (push (match-string-no-properties 1) locals)) 68 | )) 69 | (goto-char 1) ; Then search for modules 70 | (while (re-search-forward "module[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*{" nil t) 71 | (let ((module-name (match-string-no-properties 1)) 72 | (end (save-excursion (backward-char) (forward-sexp) (point)))) 73 | (push module-name modules) 74 | ;; Search for module source path 75 | (while (re-search-forward "\n[[:space:]]*source[[:space:]]*=[[:space:]]*\"\\([^\"]*\\)\"" end t) 76 | (let* ((module-dir-hash (secure-hash 'md5 (concat "1." module-name ";" (match-string-no-properties 1)))) 77 | (module-dir (concat dir ".terraform/modules/" module-dir-hash))) 78 | ;; If the dir does not exist, use data straight from source dir 79 | (if (file-directory-p module-dir) 80 | (puthash module-name module-dir modules-with-dirs) 81 | (puthash module-name (concat dir (match-string-no-properties 1)) modules-with-dirs)))) 82 | )))) 83 | (list datas resources variables outputs locals modules modules-with-dirs))) 84 | 85 | (defconst company-terraform-perdir-resource-cache 86 | (make-hash-table :test 'equal)) 87 | 88 | (defun company-terraform-get-resource-cache (kind &optional dir) 89 | "Return several dictionaries gathering names used in the project. 90 | KIND specifies the block type requested and mey be 'resource, 91 | 'data or 'variable. Searches for blocks in DIR or buffer's 92 | directory if DIR is nil. If available, uses a cached version 93 | which lasts serval seconds." 94 | (nth (cl-case kind 95 | ('data 0) 96 | ('resource 1) 97 | ('variable 2) 98 | ('output 3) 99 | ('local 4) 100 | ('module 5) 101 | ('module-dir 6)) 102 | (let* ((dir (or dir (file-name-directory (buffer-file-name)))) 103 | (v (gethash dir company-terraform-perdir-resource-cache)) 104 | (cache-time (car v)) 105 | (resource-data (cdr v))) 106 | (if (and v 107 | (< (- (float-time) cache-time) 20)) 108 | resource-data 109 | (progn 110 | (message "Regenerating company-terraform resource cache for %s..." dir) 111 | (let ((resource-data (company-terraform--scan-resources dir))) 112 | (puthash dir (cons (float-time) resource-data) company-terraform-perdir-resource-cache) 113 | resource-data)))))) 114 | 115 | (defun company-terraform-get-context () 116 | "Guess the context in terraform description where point is." 117 | (let ((nest-level (nth 0 (syntax-ppss))) 118 | (curr-ppos (nth 1 (syntax-ppss))) 119 | (string-state (nth 3 (syntax-ppss))) 120 | (string-ppos (nth 8 (syntax-ppss)))) 121 | (cond 122 | ;; Resource/data type 123 | ((and string-state 124 | (save-excursion 125 | (goto-char string-ppos) 126 | (re-search-backward "\\(resource\\|data\\)[[:space:]\n]*\\=" nil t))) 127 | (list 'object-type (intern (match-string-no-properties 1)))) 128 | ((or 129 | ;; String interpolation 130 | (and (> nest-level 0) 131 | string-state 132 | (save-excursion 133 | (re-search-backward "\\${[^\"]*\\=" nil t))) 134 | ;; Assignment expression 135 | (and (> nest-level 0) 136 | (save-excursion 137 | (re-search-backward "=[^\n=\"]*\\=" nil t))) 138 | ) 139 | (list 'interpolation 140 | (buffer-substring 141 | (point) 142 | (save-excursion 143 | (with-syntax-table (make-syntax-table (syntax-table)) 144 | ;; Minus, asterisk and dot characters are part of the object path. 145 | (modify-syntax-entry ?- "w") 146 | (modify-syntax-entry ?. "w") 147 | (modify-syntax-entry ?* "w") 148 | (skip-syntax-backward "w") 149 | (point)))))) 150 | ;; Inside resource/data block 151 | ((and (eq ?{ (char-after curr-ppos)) 152 | (save-excursion 153 | (goto-char curr-ppos) 154 | (re-search-backward "\\(resource\\|data\\|module\\)[[:space:]\n]*\"\\([^\"]*\\)\"[[:space:]\n]*\\(\"[^\"]*\"[[:space:]\n]*\\)?\\=" nil t))) 155 | 156 | (list 'block (intern (match-string-no-properties 1)) (match-string-no-properties 2))) 157 | ;; Top level 158 | ((eq 0 nest-level) 'top-level) 159 | (t 'no-idea)))) 160 | 161 | (defun company-terraform-test-context () 162 | "Echoes a message naming the current context in a terraform file. Useful for diagnostics." 163 | (interactive) 164 | (message "company-terraform-context: %s" (company-terraform-get-context))) 165 | 166 | (defun company-terraform--prefix () 167 | "Return the text before point that is part of a completable symbol. 168 | Check function ‘company-mode’ docs for the details on how this 169 | function's result is interpreted." 170 | (if (eq major-mode 'terraform-mode) 171 | (let ((context (company-terraform-get-context))) 172 | (pcase context 173 | ('no-idea nil) 174 | ('top-level (company-grab-symbol)) 175 | (`(interpolation . ,_) (cons (car (last (split-string (nth 1 context) "\\."))) t)) 176 | (`(object-type . ,_) (company-grab-symbol-cons "\"" 1)) 177 | (`(resource . ,_) (company-grab-symbol)) 178 | (`(data . ,_) (company-grab-symbol)) 179 | (_ (company-grab-symbol)))))) 180 | 181 | (defun company-terraform--make-candidate (candidate) 182 | "Annotates a completion suggestion from a name-doc list CANDIDATE." 183 | (let ((text (nth 0 candidate)) 184 | (doc (nth 1 candidate))) 185 | (propertize text 'doc doc))) 186 | 187 | (defun company-terraform--filterdoc (prefix lists &optional multi) 188 | "Filters candidates for a PREFIX. 189 | The candidates are provided either as a single list of a list of 190 | LISTS if MULTI is non-nil. Each candidate is either a single 191 | string of a pair of string and documentation." 192 | (if (not multi) (setq lists (list lists))) 193 | (cl-loop 194 | for l in lists 195 | append (cl-loop 196 | for item in l 197 | if (and (stringp item) (string-prefix-p prefix item)) 198 | collect item 199 | else if (and (listp item) (string-prefix-p prefix (car item))) 200 | collect (company-terraform--make-candidate item)))) 201 | 202 | (defun company-terraform-is-resource-n (string) 203 | "True iff STRING is an integer or a literal * character." 204 | (if (string-match "\\`\\([0-9]+\\)\\|*\\'" string) t nil)) 205 | 206 | (defun company-terraform-candidates (prefix) 207 | "Prepare a list of autocompletion candidates for the given PREFIX." 208 | (let ((context (company-terraform-get-context))) 209 | (pcase context 210 | ('top-level 211 | (company-terraform--filterdoc prefix company-terraform-toplevel-keywords)) 212 | (`(object-type resource) 213 | (company-terraform--filterdoc prefix company-terraform-resources-list)) 214 | (`(object-type data) 215 | (company-terraform--filterdoc prefix company-terraform-data-list)) 216 | (`(block resource ,type) 217 | (company-terraform--filterdoc prefix 218 | (list (gethash type company-terraform-resource-arguments-hash) 219 | company-terraform-resource-extra) 220 | t)) 221 | (`(block data ,type) 222 | (company-terraform--filterdoc prefix 223 | (list (gethash type company-terraform-data-arguments-hash) 224 | company-terraform-data-extra) 225 | t)) 226 | (`(block module ,module-name) 227 | (company-terraform--filterdoc prefix (company-terraform-get-resource-cache 228 | 'variable 229 | (gethash module-name (company-terraform-get-resource-cache 'module-dir))))) 230 | (`(interpolation ,pathstr) 231 | ;; Within interpolation 232 | (pcase (split-string pathstr "\\.") 233 | (`(,x) 234 | ;; Complete function name or resource type. 235 | (company-terraform--filterdoc x 236 | (list company-terraform-interpolation-functions 237 | (hash-table-keys (company-terraform-get-resource-cache 'resource)) 238 | company-terraform-interpolation-extra) 239 | t)) 240 | (`("count" ,x) 241 | ;; Complete count metadata 242 | (company-terraform--filterdoc x company-terraform-count-extra)) 243 | (`("var" ,x) 244 | ;; Complete variable name. 245 | (company-terraform--filterdoc x (company-terraform-get-resource-cache 'variable))) 246 | (`("local" ,x) 247 | ;; Complete locals name. 248 | (company-terraform--filterdoc x (company-terraform-get-resource-cache 'local))) 249 | (`("module" ,x) 250 | ;; Complete module name. 251 | (company-terraform--filterdoc x (company-terraform-get-resource-cache 'module))) 252 | (`("data" ,x) 253 | ;; Complete data source type. 254 | (company-terraform--filterdoc x (hash-table-keys (company-terraform-get-resource-cache 'data)))) 255 | (`("data" ,data-type ,x) 256 | ;; Complete data name. 257 | (company-terraform--filterdoc x 258 | (gethash data-type (company-terraform-get-resource-cache 'data)))) 259 | (`("data" ,data-type ,data-name . ,(or `(,x) 260 | `(,(pred company-terraform-is-resource-n) ,x))) 261 | ;; Complete data arguments/attributes 262 | (company-terraform--filterdoc x 263 | (list (gethash data-type company-terraform-data-arguments-hash) 264 | (gethash data-type company-terraform-data-attributes-hash)) 265 | t)) 266 | (`("module" ,module-name ,x) 267 | ;; Complete module output 268 | (company-terraform--filterdoc x 269 | (company-terraform-get-resource-cache 270 | 'output 271 | (gethash module-name (company-terraform-get-resource-cache 'module-dir))) 272 | )) 273 | (`(,resource-type ,x) 274 | ;; Complete resource name. 275 | (company-terraform--filterdoc x 276 | (gethash resource-type (company-terraform-get-resource-cache 'resource)))) 277 | (`(,resource-type ,resource-name . ,(or `(,x) 278 | `(,(pred company-terraform-is-resource-n) ,x))) 279 | ;; Complete resource arguments/attributes 280 | (company-terraform--filterdoc x 281 | (list (gethash resource-type company-terraform-resource-arguments-hash) 282 | (gethash resource-type company-terraform-resource-attributes-hash)) 283 | t))))))) 284 | 285 | (defun company-terraform-doc (candidate) 286 | "Return the documentation of a completion CANDIDATE." 287 | (get-text-property 0 'doc candidate)) 288 | 289 | (defun company-terraform-docbuffer (candidate) 290 | "Prepare a temporary buffer with completion CANDIDATE documentation." 291 | (company-doc-buffer (company-terraform-doc candidate))) 292 | 293 | ;;;###autoload 294 | (defun company-terraform (command &optional arg &rest ignored) 295 | "Main entry point for a company backend. 296 | Read `company-mode` function docs for the semantics of this function." 297 | (cl-case command 298 | (interactive (company-begin-backend 'company-test-backend)) 299 | (prefix (company-terraform--prefix)) 300 | (candidates (company-terraform-candidates arg)) 301 | (meta (company-terraform-doc arg)) 302 | (doc-buffer (company-terraform-docbuffer arg)))) 303 | 304 | 305 | ;;;###autoload 306 | (defun company-terraform-init () 307 | "Add terraform to the company backends." 308 | (interactive) 309 | (add-to-list 'company-backends 'company-terraform)) 310 | 311 | (provide 'company-terraform) 312 | 313 | ;;; company-terraform.el ends here 314 | --------------------------------------------------------------------------------