doom-modeline-env.el (11800B)
1 ;;; doom-modeline-env.el --- A environment parser for doom-modeline -*- lexical-binding: t -*- 2 3 ;; Copyright (C) 2019-2024 Vincent Zhang, Justin Barclay 4 5 ;; This file is not part of GNU Emacs. 6 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 ;; Parse programming environment. 24 ;; 25 26 ;;; Code: 27 28 (require 'doom-modeline-core) 29 (eval-when-compile 30 (require 'subr-x)) 31 32 33 ;; Externals 34 (defvar python-shell-interpreter) 35 36 37 ;; Customization 38 39 (defgroup doom-modeline-env nil 40 "The environment parser for `doom-modeline'." 41 :group 'doom-modeline 42 :link '(url-link :tag "Homepage" "https://github.com/seagle0128/doom-modeline")) 43 44 (defcustom doom-modeline-env-load-string doom-modeline-ellipsis 45 "What to display as the version while a new one is being loaded." 46 :type 'string 47 :group 'doom-modeline-env) 48 49 (defcustom doom-modeline-before-update-env-hook nil 50 "Hooks that run before the modeline version string is updated." 51 :type 'hook 52 :group 'doom-modeline-env) 53 54 (defcustom doom-modeline-after-update-env-hook nil 55 "Hooks that run after the modeline version string is updated." 56 :type 'hook 57 :group 'doom-modeline-env) 58 59 60 ;; Variables 61 62 ;; Show version string for multi-version managers like rvm, rbenv, pyenv, etc. 63 (defvar-local doom-modeline-env--version nil 64 "The version to display with major-mode in mode-line. 65 Example: \"2.6.0\"") 66 67 (defvar-local doom-modeline-env--command nil 68 "A program that we're looking to extract version information from. 69 Example: \"ruby\"") 70 71 (defvar-local doom-modeline-env--command-args nil 72 "A list of arguments for the command to extract the version from. 73 Example: \\='(\"--version\")") 74 75 (defvar-local doom-modeline-env--parser nil 76 "A function that returns version number from a command --version (or similar). 77 Example: \\='doom-modeline-env--ruby") 78 79 80 ;; Functions & Macros 81 82 (defun doom-modeline-update-env () 83 "Update environment info on mode-line." 84 (when (and doom-modeline-env-version 85 doom-modeline-env--command 86 (executable-find doom-modeline-env--command) 87 doom-modeline-env--command-args 88 doom-modeline-env--parser) 89 (let ((default-directory (doom-modeline-project-root)) 90 (buffer (current-buffer))) 91 (run-hooks 'doom-modeline-before-update-env-hook) 92 (setq doom-modeline-env--version doom-modeline-env-load-string) 93 (doom-modeline-env--get 94 doom-modeline-env--command 95 doom-modeline-env--command-args 96 (lambda (prog-version) 97 (with-current-buffer buffer 98 (setq doom-modeline-env--version 99 (funcall doom-modeline-env--parser prog-version)) 100 (run-hooks 'doom-modeline-after-update-env-hook))))))) 101 102 (add-hook 'find-file-hook #'doom-modeline-update-env) 103 (with-no-warnings 104 (if (boundp 'after-focus-change-function) 105 (add-function 106 :after after-focus-change-function 107 (lambda () 108 (if (frame-focus-state) 109 (doom-modeline-update-env)))) 110 (add-hook 'focus-in-hook #'doom-modeline-update-env))) 111 112 (defun doom-modeline-env--get (prog args callback) 113 "Start a sub process using PROG and apply the ARGS to the sub process. 114 Once it receives information from STDOUT, it closes off the subprocess and 115 passes on the information into the CALLBACK. 116 Example: 117 (doom-modeline-env--get 118 \"ruby\" 119 \\='(\"--version\") 120 (lambda (line) 121 (message (doom-modeline-parser--ruby line)))" 122 (when-let ((proc (ignore-errors 123 (apply 'start-process 124 ;; Flaten process-args into a single list so we can handle 125 ;; variadic length args 126 (append 127 (list "doom-modeline-env" nil prog) 128 args)))) 129 (parser callback)) 130 (set-process-filter proc 131 (lambda (_proc line) 132 (ignore-errors 133 (funcall parser line)))))) 134 135 (cl-defmacro doom-modeline-def-env (name &key hooks command parser) 136 "Define a handler for updating & displaying a version string for a language. 137 138 NAME is an unquoted symbol representing the handler's unique ID. 139 HOOKS is a list of hook symbols where this handler should be triggered. 140 COMMAND should be a function that returns a shell command and its arguments (as 141 a list). It is run on HOOKS. It takes no arguments. 142 PARSER should be a function for parsing COMMAND's output line-by-line, to 143 extract the version string." 144 (declare (indent defun)) 145 (unless (and hooks command parser) 146 (error "'%s' env is missing either :hooks, :command or :parser" name)) 147 (let ((parse-fn (intern (format "doom-modeline-env--%s-parse" name))) 148 (action-fn (intern (format "doom-modeline-env--%s-args" name))) 149 (setup-fn (intern (format "doom-modeline-env-setup-%s" name))) 150 (update-fn (intern (format "doom-modeline-env-update-%s" name))) 151 (enable-var (intern (format "doom-modeline-env-enable-%s" name))) 152 (command-var (intern (format "doom-modeline-env-%s-command" name))) 153 (parser-var (intern (format "doom-modeline-env-%s-parser-fn" name))) 154 (exe-var (intern (format "doom-modeline-env-%s-executable" name)))) 155 (macroexp-progn 156 `((defcustom ,enable-var t 157 ,(format "Whether to display the version string for %s buffers." name) 158 :type 'boolean 159 :group 'doom-modeline-env) 160 (defvar ,command-var ',action-fn 161 ,(concat "A function that returns the shell command and arguments (as a list) to\n" 162 "produce a version string.")) 163 (defvar ,parser-var ',parse-fn 164 ,(format "The function to parse each line of `%s'\'s output." command-var)) 165 (defcustom ,exe-var nil 166 ,(format (concat "What executable to use for the version indicator in %s buffers.\n\n" 167 "If nil, the default binary for this language is used.") 168 name) 169 :type 'string 170 :group 'doom-modeline-env) 171 (defalias ',parse-fn ,parser 172 (format "The line parser for %s buffers.\n\nUsed by `%s'." 173 ',name ',update-fn)) 174 (defalias ',action-fn ,command 175 (format "The command resolver for %s buffers.\n\nUsed by `%s'." 176 ',name ',update-fn)) 177 (defalias ',setup-fn 178 (lambda () 179 (if enable-local-variables 180 (add-hook 'hack-local-variables-hook #',update-fn nil t) 181 (,update-fn))) 182 (format "Prepares the modeline to later display the %s version string." 183 ',name)) 184 (defalias ',update-fn 185 (lambda () 186 (when ,enable-var 187 (when-let* ((command-list (funcall ,command-var)) 188 (exe (executable-find (car command-list)))) 189 (setq doom-modeline-env--command exe 190 doom-modeline-env--command-args (cdr command-list) 191 doom-modeline-env--parser ,parser-var) 192 (doom-modeline-update-env)))) 193 (format "Updates the %s version string in the modeline." ',name)) 194 (let ((hooks ',(eval hooks))) 195 (dolist (hook (if (listp hooks) hooks (list hooks))) 196 (add-hook hook #',setup-fn))))))) 197 198 199 ;; Bootstrap 200 ;; Versions, support Python, Ruby, Perl and Golang, etc. 201 202 ;;;###autoload (autoload 'doom-modeline-env-setup-python "doom-modeline-env") 203 (doom-modeline-def-env python 204 :hooks '(python-mode-hook python-ts-mode-hook) 205 :command (lambda () (cond ((and (executable-find "pipenv") 206 (locate-dominating-file default-directory "Pipfile")) 207 (list "pipenv" "run" 208 (or doom-modeline-env-python-executable 209 python-shell-interpreter 210 "python") 211 "--version")) 212 ((executable-find "pyenv") (list "pyenv" "version-name")) 213 ((and (executable-find "direnv") 214 (locate-dominating-file default-directory ".envrc")) 215 (list "bash" 216 "-c" 217 ;; Direnv unfortunately writes crao on stderr 218 ;; so we need to pipe that to /dev/null 219 (format "direnv exec %s %s --version 2>/dev/null" 220 default-directory 221 (or doom-modeline-env-python-executable 222 python-shell-interpreter 223 "python")))) 224 ((list (or doom-modeline-env-python-executable 225 python-shell-interpreter 226 "python") 227 "--version")))) 228 :parser (lambda (line) (let ((version (split-string line))) 229 (if (length> version 1) 230 (cadr version) 231 (car version))))) 232 233 ;;;###autoload (autoload 'doom-modeline-env-setup-ruby "doom-modeline-env") 234 (doom-modeline-def-env ruby 235 :hooks '(ruby-mode-hook ruby-ts-mode-hook enh-ruby-mode-hook) 236 :command (lambda () (list (or doom-modeline-env-ruby-executable "ruby") "--version")) 237 :parser (lambda (line) 238 (car (split-string 239 (cadr 240 (split-string line)) 241 "p")))) 242 243 ;;;###autoload (autoload 'doom-modeline-env-setup-perl "doom-modeline-env") 244 (doom-modeline-def-env perl 245 :hooks 'perl-mode-hook 246 :command (lambda () (list (or doom-modeline-env-perl-executable "perl") "--version")) 247 :parser (lambda (line) 248 (cadr 249 (split-string 250 (car 251 (split-string 252 (cadr 253 (split-string line "(")) 254 ")")) 255 "v")))) 256 257 ;;;###autoload (autoload 'doom-modeline-env-setup-go "doom-modeline-env") 258 (doom-modeline-def-env go 259 :hooks '(go-mode-hook go-ts-mode-hook) 260 :command (lambda () (list (or doom-modeline-env-go-executable "go") "version")) 261 :parser (lambda (line) 262 (cadr 263 (split-string 264 (cadr 265 (cdr 266 (split-string line))) 267 "go")))) 268 269 ;;;###autoload (autoload 'doom-modeline-env-setup-elixir "doom-modeline-env") 270 (doom-modeline-def-env elixir 271 :hooks '(elixir-mode-hook elixir-ts-mode-hook) 272 :command (lambda () (list (or doom-modeline-env-elixir-executable "elixir") "--version")) 273 :parser (lambda (line) (cadr (split-string line)))) 274 275 ;;;###autoload (autoload 'doom-modeline-env-setup-rust "doom-modeline-env") 276 (doom-modeline-def-env rust 277 :hooks '(rust-mode-hook rust-ts-mode-hook) 278 :command (lambda () (list (or doom-modeline-env-rust-executable "rustc") "--version")) 279 :parser (lambda (line) 280 (car 281 (split-string 282 (cadr 283 (split-string line)) 284 "-")))) 285 286 (provide 'doom-modeline-env) 287 288 ;;; doom-modeline-env.el ends here