From f10b887f945d057a50a06efccfc9d8c1e6762f0e Mon Sep 17 00:00:00 2001 From: Jim Porter Date: Thu, 30 May 2024 14:31:55 -0700 Subject: [PATCH] Add support for outline-minor-mode in Eshell * lisp/outline.el (outline-back-to-heading): Skip past invisible text. (outline-on-heading-p): Check for invisibility of the character *before* point; that way, this returns non-nil if at the end the first line of a collapsed section. (outline-flag-region): Set the outline button as needed. (outline-end-of-subtree): Allow for stopping at empty lines. * lisp/eshell/em-prompt.el (eshell-outline-search) (eshell-outline-level): New functions... (eshell-prompt-initialize): ... use them. * doc/misc/eshell.texi (Bugs and ideas): Remove implemented idea. * etc/NEWS: Announce this change. --- doc/misc/eshell.texi | 6 ------ etc/NEWS | 3 +++ lisp/eshell/em-prompt.el | 45 +++++++++++++++++++++++++++++++++++++++- lisp/outline.el | 29 ++++++++++++++++++-------- 4 files changed, 67 insertions(+), 16 deletions(-) diff --git a/doc/misc/eshell.texi b/doc/misc/eshell.texi index 873d14aff32..071a2e59191 100644 --- a/doc/misc/eshell.texi +++ b/doc/misc/eshell.texi @@ -2967,12 +2967,6 @@ Bugs and ideas @item Support zsh's ``Parameter Expansion'' syntax, i.e., @samp{$@{@var{name}:-@var{val}@}} -@item Create a mode @code{eshell-browse} - -It would treat the Eshell buffer as an outline. Collapsing the outline -hides all of the output text. Collapsing again would show only the -first command run in each directory - @item Allow other revisions of a file to be referenced using @samp{file@{rev@}} This would be expanded by @code{eshell-expand-file-name} (see above). diff --git a/etc/NEWS b/etc/NEWS index 3c672ffed8f..b5d4c9cfe6e 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -877,6 +877,9 @@ can make it executable like other shell scripts: #!/usr/bin/env -S emacs --batch -f eshell-batch-file +--- +*** Eshell now supports 'outline-minor-mode'. + +++ *** New builtin Eshell command 'compile'. This command runs another command, sending its output to a compilation diff --git a/lisp/eshell/em-prompt.el b/lisp/eshell/em-prompt.el index b6556d29544..2e637acc05f 100644 --- a/lisp/eshell/em-prompt.el +++ b/lisp/eshell/em-prompt.el @@ -107,6 +107,45 @@ eshell-prompt-repeat-map ;;; Functions: +(defun eshell-outline-search (&optional bound move backward looking-at) + "Search for outline headings. See `outline-search-function'." + (let ((value '(prompt command-output))) + (if looking-at + (when (and (memq (get-text-property (point) 'field) value) + (= (field-beginning (point)) (point))) + (set-match-data (list (pos-bol) (pos-eol))) + t) + ;; Go to the end when in the middle of heading. + (when (and (not backward) + (memq (get-text-property (point) 'field) value) + (/= (point) (field-beginning (point)))) + (goto-char (field-end (point)))) + ;; Search for our wanted fields. + (if-let ((prop-match + (funcall (if backward #'text-property-search-backward + #'text-property-search-forward) + 'field value (lambda (list elt) (memq elt list)))) + ;; beg and end should only include the first line of the field. + (beg (prop-match-beginning prop-match)) + (end (save-excursion (goto-char beg) (pos-eol))) + ((or (null bound) (if backward (>= beg bound) (<= end bound)))) + ((save-excursion + (goto-char beg) + (= (field-beginning (point) (pos-bol)))))) + (progn + (goto-char (if backward beg end)) + (set-match-data (list beg end)) + t) + (when move (goto-char (or bound (if backward (point-min) (point-max))))) + nil)))) + +(defun eshell-outline-level () + "Get the outline level at point." + (pcase (get-text-property (point) 'field) + ('prompt 1) + ('command-output 2) + (_ (error "Not at an outline heading")))) + (define-minor-mode eshell-prompt-mode "Minor mode for eshell-prompt module. @@ -117,7 +156,11 @@ eshell-prompt-initialize "Initialize the prompting code." (unless eshell-non-interactive-p (add-hook 'eshell-post-command-hook 'eshell-emit-prompt nil t) - (eshell-prompt-mode))) + (eshell-prompt-mode) + + (setq-local outline-search-function #'eshell-outline-search + outline-level #'eshell-outline-level + outline-minor-mode-use-buttons 'in-margins))) (defun eshell-emit-prompt () "Emit a prompt if eshell is being used interactively." diff --git a/lisp/outline.el b/lisp/outline.el index 40a75701cbf..b79984092e9 100644 --- a/lisp/outline.el +++ b/lisp/outline.el @@ -687,6 +687,11 @@ outline-back-to-heading "Move to previous heading line, or beg of this line if it's a heading. Only visible heading lines are considered, unless INVISIBLE-OK is non-nil." (forward-line 0) + (when (and (not invisible-ok) + (> (point) (point-min)) + (get-char-property (1- (point)) 'invisible)) + ;; Skip backwards past any invisible text. + (goto-char (previous-single-char-property-change (point) 'invisible))) (or (outline-on-heading-p invisible-ok) (let (found) (save-excursion @@ -706,7 +711,9 @@ outline-on-heading-p If INVISIBLE-OK is non-nil, an invisible heading line is ok too." (save-excursion (forward-line 0) - (and (bolp) (or invisible-ok (not (outline-invisible-p))) + (and (bolp) (or invisible-ok + (bobp) + (not (outline-invisible-p (1- (point))))) (if outline-search-function (funcall outline-search-function nil nil nil t) (looking-at outline-regexp))))) @@ -1004,6 +1011,11 @@ outline-flag-region (let ((o (make-overlay from to nil 'front-advance))) (overlay-put o 'evaporate t) (overlay-put o 'invisible 'outline) + (unless (eq outline-minor-mode-use-buttons 'insert) + ;; Set up the button on this overlay too. This ensures that the + ;; button is visible even if the first line of the region is + ;; empty. + (overlay-put o 'before-string (nth 1 outline--button-icons))) (overlay-put o 'isearch-open-invisible (or outline-isearch-open-invisible-function #'outline-isearch-open-invisible)))) @@ -1264,14 +1276,13 @@ outline-end-of-subtree (or first (> (funcall outline-level) level))) (setq first nil) (outline-next-heading)) - (if (and (bolp) (not (eolp))) - ;; We stopped at a nonempty line (the next heading). - (progn - ;; Go to end of line before heading - (forward-char -1) - (if (and outline-blank-line (bolp)) - ;; leave blank line before heading - (forward-char -1)))))) + (when (bolp) + ;; We stopped at the next heading. Go to end of line before + ;; heading. + (forward-char -1) + (when (and outline-blank-line (bolp)) + ;; Leave blank line before heading. + (forward-char -1))))) (defun outline-show-branches () "Show all subheadings of this heading, but not their bodies." -- 2.25.1