eshell-outline.el (6363B)
1 ;;; eshell-outline.el --- Enhanced outline-mode for Eshell -*- lexical-binding: t; -*- 2 3 ;; Copyright (C) 2020 Jamie Beardslee 4 5 ;; Author: Jamie Beardslee <jdb@jamzattack.xyz> 6 ;; Keywords: unix, eshell, outline, convenience 7 ;; Version: 2020.09.12 8 ;; URL: https://git.jamzattack.xyz/eshell-outline 9 ;; Package-Requires: ((emacs "25.1")) 10 11 ;; This program is free software; you can redistribute it and/or modify 12 ;; it under the terms of the GNU General Public License as published by 13 ;; the Free Software Foundation, either version 3 of the License, or 14 ;; (at your option) any later version. 15 16 ;; This program is distributed in the hope that it will be useful, 17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 19 ;; GNU General Public License for more details. 20 21 ;; You should have received a copy of the GNU General Public License 22 ;; along with this program. If not, see <https://www.gnu.org/licenses/>. 23 24 ;;; Commentary: 25 26 ;; `eshell-outline-mode' defines a few commands to integrate 27 ;; `outline-minor-mode' into `eshell'. Some eshell-specific keys have 28 ;; been rebound so that they have multiple uses. 29 30 ;; Namely, "C-c C-c" and "C-c C-k" will either kill/interrupt the 31 ;; running process, or show/hide the prompt+output at point. 32 33 ;; "C-c M-o" (`eshell-mark-output'/`eshell-outline-narrow') now uses 34 ;; narrowing to clear the buffer as an analogue to 35 ;; `comint-clear-buffer' and replacement for `eshell/clear'. It is 36 ;; also able to narrow to a previous prompt+output. 37 38 ;; "C-c M-m" (undefined/`eshell-outline-mark') marks the prompt+output 39 ;; at point, replacing "C-c M-o" which has been rebound. If point is 40 ;; at an empty prompt at the end of the buffer, this will mark the 41 ;; previous prompt+output instead. 42 43 ;; Because this mode doesn't actually enable `outline-minor-mode', I 44 ;; also bind "C-c @" to `outline-mode-prefix-map'. 45 46 ;; See the docstring of `eshell-outline-mode' for a full list of 47 ;; keybindings. 48 49 ;;; Code: 50 51 (require 'eshell) 52 (require 'outline) 53 54 (defvar eshell-last-input-start) 55 (defvar eshell-prompt-regexp) 56 57 ;;; Internal functions 58 59 (defun eshell-outline--final-prompt-p () 60 "Return t if point is at or after the final prompt." 61 (>= (point) (marker-position eshell-last-input-start))) 62 63 (defun eshell-outline--setup-outline-variables () 64 "Set a couple of outline variables for Eshell." 65 (setq-local outline-regexp eshell-prompt-regexp) 66 (setq-local outline-level (lambda () 1))) 67 68 69 ;;; Commands 70 71 (defun eshell-outline-toggle-or-interrupt (&optional arg) 72 "Interrupt the process or toggle outline children. 73 If prefix ARG is simply \\[universal-argument], always toggle 74 children. If ARG is anything else, or if a process is running 75 and point is beyond the final prompt, attempt to interrupt it. 76 Otherwise, toggle children." 77 (interactive "P") 78 (cond ((eq arg '(4)) 79 (outline-toggle-children)) 80 ((or arg (and eshell-process-list 81 (eshell-outline--final-prompt-p))) 82 (eshell-interrupt-process)) 83 (t 84 (outline-toggle-children)))) 85 86 (defun eshell-outline-toggle-or-kill (&optional arg) 87 "Kill the process or toggle outline children. 88 If prefix ARG is simply \\[universal-argument], always toggle 89 children. If ARG is anything else, or if a process is running 90 and point is beyond the final prompt, kill it. Otherwise, toggle 91 children." 92 (interactive "P") 93 (cond ((eq arg '(4)) 94 (outline-toggle-children)) 95 ((or arg (and eshell-process-list 96 (eshell-outline--final-prompt-p))) 97 (eshell-kill-process)) 98 (t 99 (outline-toggle-children)))) 100 101 (defun eshell-outline-mark () 102 "Mark the current prompt and output. 103 If point is at the end of the buffer, this will mark the previous 104 command's output." 105 (interactive) 106 (if (= (point) (point-max)) 107 (forward-line -1)) 108 (outline-mark-subtree)) 109 110 (defun eshell-outline-narrow (&optional widen) 111 "Narrow to the current prompt and output. 112 With prefix arg, WIDEN instead of narrowing." 113 (interactive "P") 114 (cond (widen 115 (widen)) 116 ((and eshell-process-list 117 (not (eshell-outline--final-prompt-p))) 118 (user-error "Cannot narrow while a process is running")) 119 (t 120 (let ((beg 121 (save-excursion 122 (end-of-line) 123 (re-search-backward eshell-prompt-regexp nil t))) 124 (end 125 (save-excursion 126 (if (re-search-forward eshell-prompt-regexp nil t 1) 127 (progn (forward-line 0) 128 (point)) 129 (point-max))))) 130 (narrow-to-region beg end))))) 131 132 133 ;;; The minor mode 134 135 ;;;###autoload 136 (define-minor-mode eshell-outline-mode 137 "Outline-mode in Eshell. 138 139 \\{eshell-outline-mode-map}" 140 :lighter " $…" 141 :keymap 142 (let ((map (make-sparse-keymap))) 143 ;; eshell-{previous,next}-prompt are the same as 144 ;; outline-{next,previous} -- no need to bind these. 145 (define-key map (kbd "C-c C-c") #'eshell-outline-toggle-or-interrupt) 146 (define-key map (kbd "C-c C-k") #'eshell-outline-toggle-or-kill) 147 (define-key map (kbd "C-c M-m") #'eshell-outline-mark) 148 ;; similar to `comint-clear-buffer' 149 (define-key map (kbd "C-c M-o") #'eshell-outline-narrow) 150 151 ;; From outline.el 152 (define-key map (kbd "C-c C-a") #'outline-show-all) 153 (define-key map (kbd "C-c C-t") #'outline-hide-body) 154 155 ;; Default `outline-minor-mode' keybindings 156 (define-key map (kbd "C-c @") outline-mode-prefix-map) 157 map) 158 159 (if eshell-outline-mode 160 (progn 161 (eshell-outline--setup-outline-variables) 162 (add-to-invisibility-spec '(outline . t)) 163 ;; TODO: how to make minor-mode only available in eshell-mode? 164 (unless (derived-mode-p 'eshell-mode) 165 (eshell-outline-mode -1) 166 (user-error "Only enable this mode in eshell"))) 167 (remove-from-invisibility-spec '(outline . t)) 168 (outline-show-all))) 169 170 ;;;###autoload 171 (defun eshell-outline-view-buffer () ; temporary 172 "Clone the current eshell buffer, and enable `outline-mode'. 173 This will clone the buffer via `clone-indirect-buffer', so all 174 following changes to the original buffer will be transferred. 175 The command `eshell-outline-mode' offers a more interactive 176 version, with specialized keybindings." 177 (interactive) 178 (let ((buffer 179 (clone-indirect-buffer 180 (generate-new-buffer-name "*eshell outline*") 181 nil))) 182 (with-current-buffer buffer 183 (outline-mode) 184 (eshell-outline--setup-outline-variables) 185 (outline-hide-body)) 186 (pop-to-buffer buffer))) 187 188 (provide 'eshell-outline) 189 ;;; eshell-outline.el ends here