config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

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