Overdr0ne/gumshoe

Emacs Lisp

94

92 commits

updated Feb 25, 2026

See the code

README

[[https://github.com/Overdr0ne/gumshoe/actions/workflows/test.yml][https://github.com/Overdr0ne/gumshoe/actions/workflows/test.yml/badge.svg]]
[[https://melpa.org/#/gumshoe][file:https://melpa.org/packages/gumshoe-badge.svg]]
#+TITLE: Gumshoe: a smart POINT tracker

[[./noir.jpg]]

* Introduction
  Gumshoe is a daemon that quietly keep tabs on your Point movements so you can retrace your steps if you ever need a reminder of where you’ve been. Each mode keeps a log local to some scope.

  [[./peruse-demo.gif]]
  (This demonstrates the non-built-in gumshoe-peruse-globally command described below, because it’s the most visual. I usually just use gumshoe--backtrack-back/forward and its variants.)

  Gumshoe does not keep track of every move you make, rather, only at increments of some minimum Euclidean distance from the last tracked position, like a leash. It will also automatically log a position if you’ve idled there for a configurable amount of time. This package is very similar to Vim’s jump list, just generalized for Emacs.

* Installation
  Gumshoe is available on [[https://melpa.org/#/gumshoe][MELPA]].  I just use straight+use-package like so:

  #+begin_src elisp
    (use-package gumshoe
      :init
      ;; Enabing global-gumshoe-mode will initiate tracking
      (global-gumshoe-mode +1)
      ;; customize peruse slot display if you like
      (setf gumshoe-slot-schema '(time buffer position line))
      ;; personally, I use perspectives
      ;; (setf gumshoe-slot-schema '(perspective time buffer position line))
      ;; disable auto-cancel of backtracking
      (setf gumshoe-auto-cancel-backtracking-p nil)
      )
  #+end_src

* Usage
  - Once *global-gumshoe-mode* is enabled, gumshoe will automatically start recording movements into the backlog.
  - Configure the backlog data structure with *gumshoe-backlog-type*: ~'ring~ (default, circular buffer) or ~'tree~ (branching timeline).
  - Configure Gumshoe's euclidean follow distance by customizing the *gumshoe-follow-distance* variable.
  - Configure Gumshoe's idle time by customizing the *gumshoe-idle-time* variable.
  - Configure the number of locations logged with the *gumshoe-log-len* variable (applies to ring mode).
  - Horizontal distance is scaled down by a factor of 4 by default via *context-horizontal-scale*, since columns are approximately that much narrower than rows.
  - If you would like to disable gumshoe tracking in some contexts, you may use *gumshoe-ignored-minor-modes* or *gumshoe-ignored-major-modes*, to ignore by mode, or more generically just add to *gumshoe-ignore-predicates* any condition where you would like gumshoe to stop tracking, like *minibufferp*, added by default.
** Backtracking
   Backtracking works a bit like isearch or Vim's jump-list. Point will jump sequentially between points in the backlog.
   [[./backtrack-demo.gif]]
   - Use *gumshoe-backtrack* to enter backtracking mode and start navigating the global log. Once in backtracking mode, use ~backward-paragraph~ and ~forward-paragraph~ (usually *{* and *}*) to navigate.
   - There is built-in support for filtered backtracking:
     - *gumshoe-buf-backtrack* - Navigate entries only in the current buffer
     - *gumshoe-win-backtrack* - Navigate entries only in the current window
     - *gumshoe-marker-backtrack* - Navigate only manually dropped markers
   - [[https://github.com/nex3/perspective-el][Perspective.el]] integration available via =persp-gumshoe= (see [[*Extensions]]).
   - Gumshoe activates *global-gumshoe-backtracking-mode* during backtracking. Leave this mode with *keyboard-quit* (usually *C-g*).
   - Additional backtracking commands (while in backtracking mode):
     - *C-r* (*gumshoe-backtrack-restart*) - Reset to the latest (most recent) entry in the backlog while staying in backtracking mode
     - *C-c* (*gumshoe-backtrack-cancel*) - Jump to the latest entry and quit backtracking
     - *C-v* (*gumshoe-backtrack-resume*) - Resume backtracking at the entry where you previously stopped. If never backtracked, starts at latest entry. If the previous entry was cleaned or wrapped, starts at earliest entry with notification
   - Customize the backtracking keymap via *gumshoe-backtracking-mode-map*.
   - By default, Gumshoe marks "footprints" (overlays indicating entry points). Toggle *gumshoe-show-footprints-p* to disable them.
   - By default, backtracking auto-cancels when you type a non-backtracking command. Disable with ~(setf gumshoe-auto-cancel-backtracking-p nil)~.
   - Footprint strategy via *gumshoe-footprint-strategy*:
     - ~'cover-old~ (default) - Only show most recent footprint within *gumshoe-footprint-radius*
     - ~'delete-overlapping~ - Delete old overlapping footprints
     - ~nil~ - Show all footprints
** Perusing
   You can also visually browse the backlog in the minibuffer with the peruse commands:
   - *gumshoe-peruse-globally* - Browse all entries via completing-read
   - *gumshoe-peruse-in-buffer* - Browse entries in current buffer
   - *gumshoe-peruse-in-window* - Browse entries in current window
   - *gumshoe-peruse-markers* - Browse manually dropped markers
   - Customize *gumshoe-slot-schema* to specify which fields are displayed and in what order (default: ~'(time buffer position line)~).
   - Make your own peruse commands by providing a filter predicate to ~gumshoe--peruse~.
** Manual Markers
   You may manually drop a marker at any time with *gumshoe-drop-marker*. These markers can be navigated independently with *gumshoe-marker-backtrack* and *gumshoe-peruse-markers*.

* Change log
** 4.0
   - Extracted optional integrations into separate packages under =extensions/= to avoid conditional loading.  See [[*Extensions]].
** 3.1
   - Abstracted backlog implementation: Users can now choose between ring (circular buffer) and tree (branching timeline) via ~gumshoe-backlog-type~.
   - Unified command interface: Backtracking commands now enter a transient mode (~global-gumshoe-backtracking-mode~) where movement keys navigate the timeline. No more separate -back/-forward commands.
   - Added ~gumshoe-backtrack-resume~ command to resume backtracking at the last position where you stopped. Remembers position across backtracking sessions and handles cleaned/wrapped entries gracefully.
   - Improved EIEIO method dispatch with proper class wrappers for both ring and tree implementations.
   - Fixed cover-old footprint strategy to properly hide overlapping footprints.
   - Moved shared entry creation logic to ~gumshoe--make-entry~ in gumshoe-lib.el for consistency.
** 3.0
   - Turned backtracking into a minor mode. This better isolates backtracking state, allows customizable keybindings via the minor mode map, and lets users move around with footprints still active.
   - Fixed bugs associated with cleaning up dead entries.
** 2.0
   - Gumshoe now uses 'gumshoe--entries' in the backlog instead of marks or bookmarks. I tried to make bookmarks work for me, but found the bookmark-alist far too ingrained into their interface and ended up finding it easier and more extensible to just make my own abstraction. It contains basically all the same metadata as a bookmark, but users/developers may add whatever metadata they want, by inheriting from it. That's how I added the perspective field.
   - peruse: this is my take on `dogears-list`.  It uses completing read to browse through the backlog. The display is customizable, allowing users to specify what and in what order gumshoe--entry fields are selected.
   - Noticing how similar backtracking is to isearch, I upgraded backtracking to also display 'footprints' which visually indicate maked positions while backtracking.
   - Both backtracking and perusing be filtered programmatically by passing in a predicate function.
*** Why the big change? Why all the OO complexity?
    I generally noticed that the thing I was tracking was not so much the point *position*, but user *context*. And context I realized can cover a lot, and arguably, could cover the entire state of Emacs, or your computer, or, well, the universe at the moment that context is recorded. So rather than try to have gumshoe cover all possible definitions of context, I tried to just focus on the interface, and provide a clear path to extension, for me or anyone. So that’s where all the OO stuff came from. It’s not complete, but that’s where I’m going with it. The benefits may not be obvious at the time of this writing, but I hope will be as I add things.

* Similar Packages
  If Gumshoe doesn’t suit you, here are some more Point history tracking packages that may.
  - https://github.com/alphapapa/dogears.el Dogears is particularly similar to gumshoe. They both have automatic spatial and temporal tracking, and allows visual navigation of the log. There may actually be some sort of convergence of these packages at some point. It’s an open issue.
  - https://github.com/blue0513/point-history
  - https://github.com/tcw165/history
  - https://github.com/dgutov/point-stack/
  - https://github.com/rolandwalker/back-button
  - https://github.com/gilbertw1/better-jumper
  So why do we need gumshoe then?
  Gumshoe has:
  - automatic temporal tracking and spatial tracking
  - customizable log filters allow you to see just the information you want.
  - customizable context metadata allows you to tell gumshoe exactly what you want logged.
  - Built-in autocompletion using only completing-read.
  - extensible scoping: if you can make arbitrary variables local to your scope, you can make a gumshoe mode for that scope. Scoped logs work independently, and don’t interfere with the global-mark-ring.
  - works ootb without much configuration or dependency on external packages
  Packages above may have one or two of these features, but changes on the order of a complete rewrite would be required to make them work like gumshoe. But comments are totally welcome if you’d like to open an issue.

* Extensions
  These are available as separate packages on MELPA. See their readmes for installation and usage details.
  - [[file:extensions/persp-gumshoe/readme.org][persp-gumshoe]] - [[https://github.com/nex3/perspective-el][perspective.el]] integration for perspective-aware backtracking and perusal
  - [[file:extensions/consult-gumshoe/readme.org][consult-gumshoe]] - [[https://github.com/minad/consult][consult]] integration with live preview as you browse the backlog

* Migration from gumshoe 3.x

* Architecture
  - *gumshoe.el* - Main entry point and mode definition
  - *context.el* - Context object for storing position metadata
  - *gumshoe-lib.el* - Shared utilities and customization
  - *gumshoe-backtracker.el* - Backtracking state management
  - *gumshoe-ring.el* - Ring buffer backlog implementation
  - *gumshoe-tree.el* - Tree-based backlog implementation (optional)
  - *gumshoe-footprints.el* - Visual overlay management
  - *gumshoe-peruse.el* - Completion-based browsing
  - *etree.el* - Mutable tree library

* Outstanding Issues
** Dynamic Entry Format Changes
- Gumshoe needs to know entry format before tracking begins. Dynamically changing the format requires reinitializing the mode.
- Adding fields from multiple packages requires defining a new class for each combination (to avoid multiple inheritance).
** Common Context Interface
- The ~context~ class is effectively a bookmark replacement. Consider defining a generic interface using ~cl-defgeneric~ that works for bookmarks, contexts, and other navigation targets.
** Alternative Browsing UIs
- Add an 'ibacklog' buffer interface (like ibuffer) as an alternative to peruse's completing-read interface.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Overdr0ne

90 commits

landakram

1 commits

svaante

1 commits

Overdr0ne/gumshoe

Emacs Lisp

94

92 commits

updated Feb 25, 2026

See the code

README

[[https://github.com/Overdr0ne/gumshoe/actions/workflows/test.yml][https://github.com/Overdr0ne/gumshoe/actions/workflows/test.yml/badge.svg]]
[[https://melpa.org/#/gumshoe][file:https://melpa.org/packages/gumshoe-badge.svg]]
#+TITLE: Gumshoe: a smart POINT tracker

[[./noir.jpg]]

* Introduction
  Gumshoe is a daemon that quietly keep tabs on your Point movements so you can retrace your steps if you ever need a reminder of where you’ve been. Each mode keeps a log local to some scope.

  [[./peruse-demo.gif]]
  (This demonstrates the non-built-in gumshoe-peruse-globally command described below, because it’s the most visual. I usually just use gumshoe--backtrack-back/forward and its variants.)

  Gumshoe does not keep track of every move you make, rather, only at increments of some minimum Euclidean distance from the last tracked position, like a leash. It will also automatically log a position if you’ve idled there for a configurable amount of time. This package is very similar to Vim’s jump list, just generalized for Emacs.

* Installation
  Gumshoe is available on [[https://melpa.org/#/gumshoe][MELPA]].  I just use straight+use-package like so:

  #+begin_src elisp
    (use-package gumshoe
      :init
      ;; Enabing global-gumshoe-mode will initiate tracking
      (global-gumshoe-mode +1)
      ;; customize peruse slot display if you like
      (setf gumshoe-slot-schema '(time buffer position line))
      ;; personally, I use perspectives
      ;; (setf gumshoe-slot-schema '(perspective time buffer position line))
      ;; disable auto-cancel of backtracking
      (setf gumshoe-auto-cancel-backtracking-p nil)
      )
  #+end_src

* Usage
  - Once *global-gumshoe-mode* is enabled, gumshoe will automatically start recording movements into the backlog.
  - Configure the backlog data structure with *gumshoe-backlog-type*: ~'ring~ (default, circular buffer) or ~'tree~ (branching timeline).
  - Configure Gumshoe's euclidean follow distance by customizing the *gumshoe-follow-distance* variable.
  - Configure Gumshoe's idle time by customizing the *gumshoe-idle-time* variable.
  - Configure the number of locations logged with the *gumshoe-log-len* variable (applies to ring mode).
  - Horizontal distance is scaled down by a factor of 4 by default via *context-horizontal-scale*, since columns are approximately that much narrower than rows.
  - If you would like to disable gumshoe tracking in some contexts, you may use *gumshoe-ignored-minor-modes* or *gumshoe-ignored-major-modes*, to ignore by mode, or more generically just add to *gumshoe-ignore-predicates* any condition where you would like gumshoe to stop tracking, like *minibufferp*, added by default.
** Backtracking
   Backtracking works a bit like isearch or Vim's jump-list. Point will jump sequentially between points in the backlog.
   [[./backtrack-demo.gif]]
   - Use *gumshoe-backtrack* to enter backtracking mode and start navigating the global log. Once in backtracking mode, use ~backward-paragraph~ and ~forward-paragraph~ (usually *{* and *}*) to navigate.
   - There is built-in support for filtered backtracking:
     - *gumshoe-buf-backtrack* - Navigate entries only in the current buffer
     - *gumshoe-win-backtrack* - Navigate entries only in the current window
     - *gumshoe-marker-backtrack* - Navigate only manually dropped markers
   - [[https://github.com/nex3/perspective-el][Perspective.el]] integration available via =persp-gumshoe= (see [[*Extensions]]).
   - Gumshoe activates *global-gumshoe-backtracking-mode* during backtracking. Leave this mode with *keyboard-quit* (usually *C-g*).
   - Additional backtracking commands (while in backtracking mode):
     - *C-r* (*gumshoe-backtrack-restart*) - Reset to the latest (most recent) entry in the backlog while staying in backtracking mode
     - *C-c* (*gumshoe-backtrack-cancel*) - Jump to the latest entry and quit backtracking
     - *C-v* (*gumshoe-backtrack-resume*) - Resume backtracking at the entry where you previously stopped. If never backtracked, starts at latest entry. If the previous entry was cleaned or wrapped, starts at earliest entry with notification
   - Customize the backtracking keymap via *gumshoe-backtracking-mode-map*.
   - By default, Gumshoe marks "footprints" (overlays indicating entry points). Toggle *gumshoe-show-footprints-p* to disable them.
   - By default, backtracking auto-cancels when you type a non-backtracking command. Disable with ~(setf gumshoe-auto-cancel-backtracking-p nil)~.
   - Footprint strategy via *gumshoe-footprint-strategy*:
     - ~'cover-old~ (default) - Only show most recent footprint within *gumshoe-footprint-radius*
     - ~'delete-overlapping~ - Delete old overlapping footprints
     - ~nil~ - Show all footprints
** Perusing
   You can also visually browse the backlog in the minibuffer with the peruse commands:
   - *gumshoe-peruse-globally* - Browse all entries via completing-read
   - *gumshoe-peruse-in-buffer* - Browse entries in current buffer
   - *gumshoe-peruse-in-window* - Browse entries in current window
   - *gumshoe-peruse-markers* - Browse manually dropped markers
   - Customize *gumshoe-slot-schema* to specify which fields are displayed and in what order (default: ~'(time buffer position line)~).
   - Make your own peruse commands by providing a filter predicate to ~gumshoe--peruse~.
** Manual Markers
   You may manually drop a marker at any time with *gumshoe-drop-marker*. These markers can be navigated independently with *gumshoe-marker-backtrack* and *gumshoe-peruse-markers*.

* Change log
** 4.0
   - Extracted optional integrations into separate packages under =extensions/= to avoid conditional loading.  See [[*Extensions]].
** 3.1
   - Abstracted backlog implementation: Users can now choose between ring (circular buffer) and tree (branching timeline) via ~gumshoe-backlog-type~.
   - Unified command interface: Backtracking commands now enter a transient mode (~global-gumshoe-backtracking-mode~) where movement keys navigate the timeline. No more separate -back/-forward commands.
   - Added ~gumshoe-backtrack-resume~ command to resume backtracking at the last position where you stopped. Remembers position across backtracking sessions and handles cleaned/wrapped entries gracefully.
   - Improved EIEIO method dispatch with proper class wrappers for both ring and tree implementations.
   - Fixed cover-old footprint strategy to properly hide overlapping footprints.
   - Moved shared entry creation logic to ~gumshoe--make-entry~ in gumshoe-lib.el for consistency.
** 3.0
   - Turned backtracking into a minor mode. This better isolates backtracking state, allows customizable keybindings via the minor mode map, and lets users move around with footprints still active.
   - Fixed bugs associated with cleaning up dead entries.
** 2.0
   - Gumshoe now uses 'gumshoe--entries' in the backlog instead of marks or bookmarks. I tried to make bookmarks work for me, but found the bookmark-alist far too ingrained into their interface and ended up finding it easier and more extensible to just make my own abstraction. It contains basically all the same metadata as a bookmark, but users/developers may add whatever metadata they want, by inheriting from it. That's how I added the perspective field.
   - peruse: this is my take on `dogears-list`.  It uses completing read to browse through the backlog. The display is customizable, allowing users to specify what and in what order gumshoe--entry fields are selected.
   - Noticing how similar backtracking is to isearch, I upgraded backtracking to also display 'footprints' which visually indicate maked positions while backtracking.
   - Both backtracking and perusing be filtered programmatically by passing in a predicate function.
*** Why the big change? Why all the OO complexity?
    I generally noticed that the thing I was tracking was not so much the point *position*, but user *context*. And context I realized can cover a lot, and arguably, could cover the entire state of Emacs, or your computer, or, well, the universe at the moment that context is recorded. So rather than try to have gumshoe cover all possible definitions of context, I tried to just focus on the interface, and provide a clear path to extension, for me or anyone. So that’s where all the OO stuff came from. It’s not complete, but that’s where I’m going with it. The benefits may not be obvious at the time of this writing, but I hope will be as I add things.

* Similar Packages
  If Gumshoe doesn’t suit you, here are some more Point history tracking packages that may.
  - https://github.com/alphapapa/dogears.el Dogears is particularly similar to gumshoe. They both have automatic spatial and temporal tracking, and allows visual navigation of the log. There may actually be some sort of convergence of these packages at some point. It’s an open issue.
  - https://github.com/blue0513/point-history
  - https://github.com/tcw165/history
  - https://github.com/dgutov/point-stack/
  - https://github.com/rolandwalker/back-button
  - https://github.com/gilbertw1/better-jumper
  So why do we need gumshoe then?
  Gumshoe has:
  - automatic temporal tracking and spatial tracking
  - customizable log filters allow you to see just the information you want.
  - customizable context metadata allows you to tell gumshoe exactly what you want logged.
  - Built-in autocompletion using only completing-read.
  - extensible scoping: if you can make arbitrary variables local to your scope, you can make a gumshoe mode for that scope. Scoped logs work independently, and don’t interfere with the global-mark-ring.
  - works ootb without much configuration or dependency on external packages
  Packages above may have one or two of these features, but changes on the order of a complete rewrite would be required to make them work like gumshoe. But comments are totally welcome if you’d like to open an issue.

* Extensions
  These are available as separate packages on MELPA. See their readmes for installation and usage details.
  - [[file:extensions/persp-gumshoe/readme.org][persp-gumshoe]] - [[https://github.com/nex3/perspective-el][perspective.el]] integration for perspective-aware backtracking and perusal
  - [[file:extensions/consult-gumshoe/readme.org][consult-gumshoe]] - [[https://github.com/minad/consult][consult]] integration with live preview as you browse the backlog

* Migration from gumshoe 3.x

* Architecture
  - *gumshoe.el* - Main entry point and mode definition
  - *context.el* - Context object for storing position metadata
  - *gumshoe-lib.el* - Shared utilities and customization
  - *gumshoe-backtracker.el* - Backtracking state management
  - *gumshoe-ring.el* - Ring buffer backlog implementation
  - *gumshoe-tree.el* - Tree-based backlog implementation (optional)
  - *gumshoe-footprints.el* - Visual overlay management
  - *gumshoe-peruse.el* - Completion-based browsing
  - *etree.el* - Mutable tree library

* Outstanding Issues
** Dynamic Entry Format Changes
- Gumshoe needs to know entry format before tracking begins. Dynamically changing the format requires reinitializing the mode.
- Adding fields from multiple packages requires defining a new class for each combination (to avoid multiple inheritance).
** Common Context Interface
- The ~context~ class is effectively a bookmark replacement. Consider defining a generic interface using ~cl-defgeneric~ that works for bookmarks, contexts, and other navigation targets.
** Alternative Browsing UIs
- Add an 'ibacklog' buffer interface (like ibuffer) as an alternative to peruse's completing-read interface.

Not written in Markdown, so it's shown here as plain text — view it formatted on GitHub.

Contributors

Overdr0ne

90 commits

landakram

1 commits

svaante

1 commits

Languages

Emacs Lisp

97.6%

Makefile

2.4%