eshell-outline

Enhanced outline-mode for Eshell
git clone https://git.jamzattack.xyz/eshell-outline
Log | Files | Refs | LICENSE

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