|
| 1 | +;;; lsp-kubernetes-helm.el --- LSP YAML server integration -*- lexical-binding: t; -*- |
| 2 | + |
| 3 | +;; Copyright (C) 2024 Aaron Gonzales |
| 4 | + |
| 5 | +;; Author: Aaron Gonzales <[email protected]> |
| 6 | +;; Keywords: lsp, kubernetes, helm, yaml |
| 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 | +;; |
| 24 | + |
| 25 | +;;; Code: |
| 26 | + |
| 27 | +(require 'lsp-mode) |
| 28 | +(require 'dash) |
| 29 | + |
| 30 | +(defgroup lsp-kubernetes-helm nil |
| 31 | + "LSP support for YAML, using Helm Language Server (helm-ls)." |
| 32 | + :group 'lsp-mode |
| 33 | + :link '(url-link "https://github.com/mrjosh/helm-ls") |
| 34 | + :package-version '(lsp-mode . "9.0.0")) |
| 35 | + |
| 36 | +(defconst lsp-kubernetes-helm--lsp-configuration-section-name "helm-ls" |
| 37 | + "Key used to grab the lsp configuration section for helm-ls.") |
| 38 | + |
| 39 | +(defcustom lsp-kubernetes-helm-ls-server-path "helm_ls" |
| 40 | + "Path to the Helm Language Server binary." |
| 41 | + :group 'lsp-kubernetes-helm |
| 42 | + :risky t |
| 43 | + :type 'file) |
| 44 | + |
| 45 | +(defcustom lsp-kubernetes-helm-ls-log-level "info" |
| 46 | + "Options for the log level of the Helm Language Server." |
| 47 | + :group 'lsp-kubernetes-helm |
| 48 | + :type '(choice |
| 49 | + (const "trace") |
| 50 | + (const "debug") |
| 51 | + (const "info") |
| 52 | + (const "warning") |
| 53 | + (const "error") |
| 54 | + (const "fatal") |
| 55 | + (const "panic")) |
| 56 | + :package-version '(lsp-mode . "9.0.0")) |
| 57 | + |
| 58 | +(defcustom lsp-kubernetes-helm-ls-main-values-file-path "values.yaml" |
| 59 | + "Path to main values file for Helm Chart." |
| 60 | + :group 'lsp-kubernetes-helm |
| 61 | + :type 'file |
| 62 | + :package-version '(lsp-mode . "9.0.0")) |
| 63 | + |
| 64 | +(defcustom lsp-kubernetes-helm-overlay-values-file-path "values.lint.yaml" |
| 65 | + "Path to values file that may be merged with main values files for Helm Chart." |
| 66 | + :group 'lsp-kubernetes-helm |
| 67 | + :type 'file |
| 68 | + :package-version '(lsp-mode . "9.0.0")) |
| 69 | + |
| 70 | +(defcustom lsp-kubernetes-helm-additional-values-files-pattern "values*.yaml" |
| 71 | + "Pattern for additional values files, which will be shown for completion and hover." |
| 72 | + :group 'lsp-kubernetes-helm |
| 73 | + :type 'string |
| 74 | + :package-version '(lsp-mode . "9.0.0")) |
| 75 | + |
| 76 | +(defcustom lsp-kubernetes-helm-yaml-ls-server-path "yaml-language-server" |
| 77 | + "Path to the Yaml Language Server binary that supports the Helm Language Server." |
| 78 | + :group 'lsp-kubernetes-helm |
| 79 | + :link '(url-link :tag "Yaml Language Server" |
| 80 | + "https://github.com/redhat-developer/yaml-language-server") |
| 81 | + :risky t |
| 82 | + :type 'file) |
| 83 | + |
| 84 | +(defcustom lsp-kubernetes-helm-yaml-ls-enable t |
| 85 | + "Enable/disable default YAML Language Server." |
| 86 | + :group 'lsp-kubernetes-helm |
| 87 | + :type 'boolean |
| 88 | + :package-version '(lsp-mode . "9.0.0")) |
| 89 | + |
| 90 | +(defcustom lsp-kubernetes-helm-yaml-ls-enable-for-globs "*.{yaml,yml}" |
| 91 | + "Enable/disable default YAML Language Server." |
| 92 | + :group 'lsp-kubernetes-helm |
| 93 | + :type 'string |
| 94 | + :package-version '(lsp-mode . "9.0.0")) |
| 95 | + |
| 96 | +(defcustom lsp-kubernetes-helm-yaml-ls-diagnostics-limit 25 |
| 97 | + "Limit the amount of yaml diagnostics to return. |
| 98 | +Should typically be set to a low number when editing helm files." |
| 99 | + :group 'lsp-kubernetes-helm |
| 100 | + :type 'number |
| 101 | + :package-version '(lsp-mode . "9.0.0")) |
| 102 | + |
| 103 | +(defcustom lsp-kubernetes-helm-yaml-ls-schemas '((https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.30.3-standalone-strict/all.json |
| 104 | + . ["*.y*"]) |
| 105 | + (kubernetes . [""])) |
| 106 | + "List used by yaml language server to match schemas to globs. |
| 107 | +This list is prioritized over the schema store schemas. Recommended to set |
| 108 | +kubernetes to an empty string and at the end of the list to override the |
| 109 | +default provided in yaml-language-server." |
| 110 | + :group 'lsp-kubernetes-helm |
| 111 | + :type '(alist :key-type (symbol :tag "schema") :value-type (lsp-repeatable-vector :tag "files (glob)")) |
| 112 | + :package-version '(lsp-mode . "9.0.0")) |
| 113 | + |
| 114 | +(defcustom lsp-kubernetes-helm-yaml-ls-format-enable t |
| 115 | + "Enable/disable default YAML formatter." |
| 116 | + :group 'lsp-kubernetes-helm |
| 117 | + :type 'boolean |
| 118 | + :package-version '(lsp-mode . "9.0.0")) |
| 119 | + |
| 120 | +(defcustom lsp-kubernetes-helm-yaml-ls-single-quote nil |
| 121 | + "Use single quote instead of double quotes." |
| 122 | + :group 'lsp-kubernetes-helm |
| 123 | + :type 'boolean |
| 124 | + :package-version '(lsp-mode . "9.0.0")) |
| 125 | + |
| 126 | +(defcustom lsp-kubernetes-helm-yaml-ls-bracket-spacing t |
| 127 | + "Print spaces between brackets in objects." |
| 128 | + :group 'lsp-kubernetes-helm |
| 129 | + :type 'boolean |
| 130 | + :package-version '(lsp-mode . "9.0.0")) |
| 131 | + |
| 132 | +(defcustom lsp-kubernetes-helm-yaml-ls-prose-wrap "preserve" |
| 133 | + "Options for prose-wrap. |
| 134 | +Always: wrap prose if it exceeds the print width. |
| 135 | +Never: never wrap the prose. |
| 136 | +Preserve: wrap prose as-is." |
| 137 | + :group 'lsp-kubernetes-helm |
| 138 | + :type '(choice |
| 139 | + (const "always") |
| 140 | + (const "never") |
| 141 | + (const "preserve")) |
| 142 | + :package-version '(lsp-mode . "9.0.0")) |
| 143 | + |
| 144 | +(defcustom lsp-kubernetes-helm-yaml-ls-print-width 80 |
| 145 | + "Specify the line length that the printer will wrap on." |
| 146 | + :group 'lsp-kubernetes-helm |
| 147 | + :type 'number |
| 148 | + :package-version '(lsp-mode . "9.0.0")) |
| 149 | + |
| 150 | +(defcustom lsp-kubernetes-helm-yaml-ls-validate t |
| 151 | + "Enable/disable validation feature." |
| 152 | + :group 'lsp-kubernetes-helm |
| 153 | + :type 'boolean |
| 154 | + :package-version '(lsp-mode . "9.0.0")) |
| 155 | + |
| 156 | +(defcustom lsp-kubernetes-helm-yaml-ls-hover t |
| 157 | + "Enable/disable hover feature." |
| 158 | + :group 'lsp-kubernetes-helm |
| 159 | + :type 'boolean |
| 160 | + :package-version '(lsp-mode . "9.0.0")) |
| 161 | + |
| 162 | +(defcustom lsp-kubernetes-helm-yaml-ls-completion t |
| 163 | + "Enable/disable completion feature." |
| 164 | + :group 'lsp-kubernetes-helm |
| 165 | + :type 'boolean |
| 166 | + :package-version '(lsp-mode . "9.0.0")) |
| 167 | + |
| 168 | +(defcustom lsp-kubernetes-helm-yaml-ls-schema-store-extensions '(((name . "Kubernetes v1.30.3") |
| 169 | + (description . "Kubernetes v1.30.3 manifest schema definition") |
| 170 | + (url . "https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/v1.30.3-standalone-strict/all.json"))) |
| 171 | + "Schemas defined by user schemas to files in a glob pattern. |
| 172 | +Used by Yaml Language Server to determine which schema to use for which types of files." |
| 173 | + :group 'lsp-kubernetes-helm |
| 174 | + :type '(list (list (alist |
| 175 | + :key-type (choice |
| 176 | + (const :tag "Name" name) |
| 177 | + (const :tag "Description" description) |
| 178 | + (const :tag "URL" url)) |
| 179 | + :value-type string))) |
| 180 | + :package-version '(lsp-mode . "9.0.0")) |
| 181 | + |
| 182 | +(defcustom lsp-kubernetes-helm-yaml-ls-schema-store-enable nil |
| 183 | + "Enable/disable JSON Schema store. When set to true, available YAML \ |
| 184 | +schemas will be automatically pulled from |
| 185 | +`lsp-kubernetes-helm-yaml-ls-schema-store-uri'." |
| 186 | + :group 'lsp-kubernetes-helm |
| 187 | + :type 'boolean |
| 188 | + :package-version '(lsp-mode . "9.0.0")) |
| 189 | + |
| 190 | +(defcustom lsp-kubernetes-helm-yaml-ls-schema-store-uri "https://www.schemastore.org/api/json/catalog.json" |
| 191 | + "URL of schema store catalog to use." |
| 192 | + :group 'lsp-kubernetes-helm |
| 193 | + :type 'string |
| 194 | + :package-version '(lsp-mode . "9.0.0")) |
| 195 | + |
| 196 | +(defcustom lsp-kubernetes-helm-yaml-ls-schema-store-local-db |
| 197 | + (expand-file-name |
| 198 | + (locate-user-emacs-file |
| 199 | + (f-join ".cache" "lsp" "lsp-kubernetes-helm-schemas.json"))) |
| 200 | + "Cached database of schema store." |
| 201 | + :group 'lsp-kubernetes-helm |
| 202 | + :type 'file |
| 203 | + :package-version '(lsp-mode . "9.0.0")) |
| 204 | + |
| 205 | +(defcustom lsp-kubernetes-helm-yaml-ls-custom-tags nil |
| 206 | + "Custom tags for the parser to use." |
| 207 | + :group 'lsp-kubernetes-helm |
| 208 | + :type '(lsp-repeatable-vector string) |
| 209 | + :package-version '(lsp-mode . "9.0.0")) |
| 210 | + |
| 211 | +(defcustom lsp-kubernetes-helm-yaml-ls-max-items-computed 5000 |
| 212 | + "The maximum number of outline symbols and folding regions computed. |
| 213 | +Limited for performance reasons." |
| 214 | + :group 'lsp-kubernetes-helm |
| 215 | + :type 'number |
| 216 | + :package-version '(lsp-mode . "9.0.0")) |
| 217 | + |
| 218 | +(defcustom lsp-kubernetes-helm-server-arguments '("serve" "--stdio") |
| 219 | + "Command to start helm-ls. Minimally needs serve otherwise the server wont start properly." |
| 220 | + :type '(repeat string) |
| 221 | + :group 'lsp-kubernetes-helm |
| 222 | + :package-version '(lsp-mode . "9.0.0")) |
| 223 | + |
| 224 | +(defvar lsp-kubernetes-helm-yaml-ls--schema-store nil |
| 225 | + "A list of schemas provided by schema store uri.") |
| 226 | + |
| 227 | +(defun lsp-kubernetes-helm-download-or-refresh-schema-store-db (&optional force-download) |
| 228 | + "Download remote schema store at `lsp-yaml-schema-store-uri' into local cache. |
| 229 | +Set FORCE-DOWNLOAD to non-nil to force re-download the database. |
| 230 | +FORCE-DOWNLOADING is set to t by default" |
| 231 | + (interactive "P") |
| 232 | + (let ((local-db-directory (file-name-directory lsp-kubernetes-helm-yaml-ls-schema-store-local-db)) |
| 233 | + (force-download (or force-download t))) |
| 234 | + (when (not (file-exists-p lsp-kubernetes-helm-yaml-ls-schema-store-local-db)) |
| 235 | + (unless (file-directory-p local-db-directory) |
| 236 | + (mkdir local-db-directory t)) |
| 237 | + (url-copy-file lsp-kubernetes-helm-yaml-ls-schema-store-uri lsp-kubernetes-helm-yaml-ls-schema-store-local-db force-download)))) |
| 238 | + |
| 239 | +(defun lsp-kubernetes-helm--get-available-schemas () |
| 240 | + "Get list of supported schemas." |
| 241 | + (when (and lsp-kubernetes-helm-yaml-ls-schema-store-enable |
| 242 | + (not lsp-kubernetes-helm-yaml-ls--schema-store)) |
| 243 | + (lsp-kubernetes-helm-download-or-refresh-schema-store-db nil) |
| 244 | + (setq lsp-kubernetes-helm-yaml-ls--schema-store |
| 245 | + (alist-get 'schemas (json-read-file lsp-kubernetes-helm-yaml-ls-schema-store-local-db)))) |
| 246 | + (seq-concatenate 'list lsp-kubernetes-helm-yaml-ls-schema-store-extensions lsp-kubernetes-helm-yaml-ls--schema-store)) |
| 247 | + |
| 248 | +(defun lsp-kubernetes-helm-set-buffer-schema (schema-uri-string) |
| 249 | + "Set yaml schema for the current buffer to SCHEMA-URI-STRING. |
| 250 | +Remove buffer from all other schema associations." |
| 251 | + (interactive "MURI: ") |
| 252 | + (let* ((schema-uri (intern schema-uri-string)) |
| 253 | + (buffer-file-path (file-relative-name |
| 254 | + (lsp--uri-to-path (lsp--buffer-uri)) |
| 255 | + (lsp-workspace-root (lsp--buffer-uri)))) |
| 256 | + ;; yaml language server can do partial path matching |
| 257 | + (glob (concat "/" buffer-file-path)) |
| 258 | + (current-config (assoc schema-uri lsp-kubernetes-helm-yaml-ls-schemas)) |
| 259 | + (current-patterns (and current-config (cdr current-config)))) |
| 260 | + (if current-config |
| 261 | + (or (member glob (append current-patterns nil)) |
| 262 | + (setq lsp-kubernetes-helm-yaml-ls-schemas |
| 263 | + (cl-acons schema-uri |
| 264 | + (vconcat (vector glob) current-patterns) |
| 265 | + (assq-delete-all schema-uri (mapcar (lambda (x) (lsp-kubernetes-helm--remove-glob-from-all-schemas x glob)) lsp-kubernetes-helm-yaml-ls-schemas))))) |
| 266 | + (setq lsp-kubernetes-helm-yaml-ls-schemas |
| 267 | + (cl-acons schema-uri (vector glob) (mapcar (lambda (x) (lsp-kubernetes-helm--remove-glob-from-all-schemas x glob)) lsp-kubernetes-helm-yaml-ls-schemas)))) |
| 268 | + (lsp--set-configuration (lsp-configuration-section lsp-kubernetes-helm--lsp-configuration-section-name)))) |
| 269 | + |
| 270 | +(defun lsp-kubernetes-helm-select-buffer-schema () |
| 271 | + "Select schema for the current buffer based on the list of supported schemas." |
| 272 | + (interactive) |
| 273 | + (let* ((schema (lsp--completing-read "Select buffer schema: " |
| 274 | + (lsp-kubernetes-helm--get-available-schemas) |
| 275 | + (lambda (schema) |
| 276 | + (format "%s: %s" (alist-get 'name schema)(alist-get 'description schema))) |
| 277 | + nil t)) |
| 278 | + (uri (alist-get 'url schema))) |
| 279 | + (lsp-kubernetes-helm-set-buffer-schema uri))) |
| 280 | + |
| 281 | +(defun lsp-kubernetes-helm--remove-glob-from-all-schemas (schemas glob) |
| 282 | + "Removes GLOB from all keys in SCHEMAS." |
| 283 | + (let ((patterns (cdr schemas))) |
| 284 | + (cons (car schemas) |
| 285 | + (vconcat (-filter (lambda (p) |
| 286 | + (not (equal p glob))) |
| 287 | + (append patterns nil)) nil)))) |
| 288 | + |
| 289 | +(lsp-register-custom-settings |
| 290 | + '(("helm-ls.logLevel" lsp-kubernetes-helm-ls-log-level) |
| 291 | + ("helm-ls.valuesFiles.mainValuesFile" lsp-kubernetes-helm-ls-main-values-file-path) |
| 292 | + ("helm-ls.valuesFiles.lintOverlayValuesFile" lsp-kubernetes-helm-overlay-values-file-path) |
| 293 | + ("helm-ls.valuesFiles.additionalValuesFilesGlobPattern" lsp-kubernetes-helm-additional-values-files-pattern) |
| 294 | + ("helm-ls.yamlls.enabled" lsp-kubernetes-helm-yaml-ls-enable t) |
| 295 | + ("helm-ls.yamlls.enabledForFilesGlob" lsp-kubernetes-helm-yaml-ls-enable-for-globs) |
| 296 | + ("helm-ls.yamlls.diagnosticsLimit" lsp-kubernetes-helm-yaml-ls-diagnostics-limit) |
| 297 | + ("helm-ls.yamlls.path" lsp-kubernetes-helm-yaml-ls-server-path) |
| 298 | + ("helm-ls.yamlls.config.format.enable" lsp-kubernetes-helm-yaml-ls-format-enable t) |
| 299 | + ("helm-ls.yamlls.config.format.singleQuote" lsp-kubernetes-helm-yaml-ls-single-quote t) |
| 300 | + ("helm-ls.yamlls.config.format.bracketSpacing" lsp-kubernetes-helm-yaml-ls-bracket-spacing) |
| 301 | + ("helm-ls.yamlls.config.format.proseWrap" lsp-kubernetes-helm-yaml-ls-prose-wrap) |
| 302 | + ("helm-ls.yamlls.config.format.printWidth" lsp-kubernetes-helm-yaml-ls-print-width) |
| 303 | + ("helm-ls.yamlls.config.validate" lsp-kubernetes-helm-yaml-ls-validate t) |
| 304 | + ("helm-ls.yamlls.config.hover" lsp-kubernetes-helm-yaml-ls-hover t) |
| 305 | + ("helm-ls.yamlls.config.completion" lsp-kubernetes-helm-yaml-ls-completion t) |
| 306 | + ("helm-ls.yamlls.config.schemas" lsp-kubernetes-helm-yaml-ls-schemas) |
| 307 | + ("helm-ls.yamlls.config.schemaStore.enable" lsp-kubernetes-helm-yaml-ls-schema-store-enable nil) |
| 308 | + ("helm-ls.yamlls.config.schemaStore.url" lsp-kubernetes-helm-yaml-ls-schema-store-uri) |
| 309 | + ("helm-ls.yamlls.config.customTags" lsp-kubernetes-helm-yaml-ls-custom-tags) |
| 310 | + ("helm-ls.yamlls.config.maxItemsComputed" lsp-kubernetes-helm-yaml-ls-max-items-computed))) |
| 311 | + |
| 312 | +(lsp-dependency 'kubernetes-helm-language-server |
| 313 | + `(:system ,lsp-kubernetes-helm-ls-server-path) |
| 314 | + `(:system ,lsp-kubernetes-helm-yaml-ls-server-path) |
| 315 | + `(:npm :package "yaml-language-server" |
| 316 | + :path ,lsp-kubernetes-helm-yaml-ls-server-path)) |
| 317 | + |
| 318 | +(lsp-register-client |
| 319 | + (make-lsp-client :new-connection (lsp-stdio-connection |
| 320 | + (lambda () |
| 321 | + `(,(or (executable-find lsp-kubernetes-helm-ls-server-path) |
| 322 | + (lsp-package-path 'kubernetes-helm-language-server)) |
| 323 | + ,@lsp-kubernetes-helm-server-arguments))) |
| 324 | + :activation-fn (lsp-activate-on "helm-ls") |
| 325 | + :priority 0 |
| 326 | + :server-id 'helm-ls |
| 327 | + :initialized-fn (lambda (workspace) |
| 328 | + (with-lsp-workspace workspace |
| 329 | + (lsp--set-configuration |
| 330 | + (lsp-configuration-section lsp-kubernetes-helm--lsp-configuration-section-name)))))) |
| 331 | + |
| 332 | +(lsp-consistency-check lsp-kubernetes-helm) |
| 333 | + |
| 334 | +(provide 'lsp-kubernetes-helm) |
| 335 | +;;; lsp-kubernetes-helm.el ends here |
0 commit comments