diff options
| author | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2025-10-12 11:47:26 -0500 |
| commit | 092304d9e0ccc37cc0ddaa9b136457e56a1cac20 (patch) | |
| tree | ea81999b8442246c978b364dd90e8c752af50db5 /modules/jumper.el | |
changing repositories
Diffstat (limited to 'modules/jumper.el')
| -rw-r--r-- | modules/jumper.el | 177 |
1 files changed, 177 insertions, 0 deletions
diff --git a/modules/jumper.el b/modules/jumper.el new file mode 100644 index 00000000..787a4973 --- /dev/null +++ b/modules/jumper.el @@ -0,0 +1,177 @@ +;;; jumper.el --- Quick jump between locations using registers -*- lexical-binding: t -*- + +;; Author: Craig Jennings +;; Version: 0.1 +;; Package-Requires: ((emacs "25.1")) +;; Keywords: convenience +;; URL: https://github.com/cjennings/jumper + +;;; Commentary: + +;; Jumper provides a simple way to store and jump between locations +;; in your codebase without needing to remember register assignments. + +;;; Code: + +(defgroup jumper nil + "Quick navigation between stored locations." + :group 'convenience) + +(defcustom jumper-prefix-key "M-SPC" + "Prefix key for jumper commands. + +Note that using M-SPC will override the default binding to just-one-space." + :type 'string + :group 'jumper) + +(defcustom jumper-max-locations 10 + "Maximum number of locations to store." + :type 'integer + :group 'jumper) + +;; Internal variables +(defvar jumper--registers (make-vector jumper-max-locations nil) + "Vector of used registers.") + +(defvar jumper--next-index 0 + "Next available index in the jumper--registers vector.") + +(defvar jumper--last-location-register ?z + "Register used to store the last location.") + +(defun jumper--location-key () + "Generate a key to identify the current location." + (format "%s:%d:%d" + (or (buffer-file-name) (buffer-name)) + (line-number-at-pos) + (current-column))) + +(defun jumper--location-exists-p () + "Check if current location is already stored." + (let ((key (jumper--location-key)) + (found nil)) + (dotimes (i + jumper--next-index found) + (let* ((reg (aref jumper--registers i)) + (pos (get-register reg)) + (marker (and pos (registerv-data pos)))) + (when marker + (save-current-buffer + (set-buffer (marker-buffer marker)) + (save-excursion + (goto-char marker) + (when (string= key (jumper--location-key)) + (setq found t))))))))) + +(defun jumper--register-available-p () + "Check if there are registers available." + (< jumper--next-index jumper-max-locations)) + +(defun jumper--format-location (index) + "Format location at INDEX for display." + (let* ((reg (aref jumper--registers index)) + (pos (get-register reg)) + (marker (and pos (registerv-data pos)))) + (when marker + (save-current-buffer + (set-buffer (marker-buffer marker)) + (save-excursion + (goto-char marker) + (format "[%d] %s:%d - %s" + index + (buffer-name) + (line-number-at-pos) + (buffer-substring-no-properties + (line-beginning-position) + (min (+ (line-beginning-position) 40) + (line-end-position))))))))) + +(defun jumper-store-location () + "Store current location in the next free register." + (interactive) + (if (jumper--location-exists-p) + (message "Location already stored") + (if (jumper--register-available-p) + (let ((reg (+ ?0 jumper--next-index))) + (point-to-register reg) + (aset jumper--registers jumper--next-index reg) + (setq jumper--next-index (1+ jumper--next-index)) + (message "Location stored in register %c" reg)) + (message "Sorry - all jump locations are filled!")))) + +(defun jumper-jump-to-location () + "Jump to a stored location." + (interactive) + (if (= jumper--next-index 0) + (message "No locations stored") + (if (= jumper--next-index 1) + ;; Special case for one location - toggle behavior + (let ((reg (aref jumper--registers 0))) + (if (jumper--location-exists-p) + (message "You're already at the stored location") + (point-to-register jumper--last-location-register) + (jump-to-register reg) + (message "Jumped to location"))) + ;; Multiple locations - use completing-read + (let* ((locations + (cl-loop for i from 0 below jumper--next-index + for fmt = (jumper--format-location i) + when fmt collect (cons fmt i))) + ;; Add last location if available + (last-pos (get-register jumper--last-location-register)) + (locations (if last-pos + (cons (cons "[z] Last location" -1) locations) + locations)) + (choice (completing-read "Jump to: " locations nil t)) + (idx (cdr (assoc choice locations)))) + (point-to-register jumper--last-location-register) + (if (= idx -1) + (jump-to-register jumper--last-location-register) + (jump-to-register (aref jumper--registers idx))) + (message "Jumped to location"))))) + +(defun jumper--reorder-registers (removed-idx) + "Reorder registers after removing the one at REMOVED-IDX." + (when (< removed-idx (1- jumper--next-index)) + ;; Shift all higher registers down + (cl-loop for i from removed-idx below (1- jumper--next-index) + do (let ((next-reg (aref jumper--registers (1+ i)))) + (aset jumper--registers i next-reg)))) + (setq jumper--next-index (1- jumper--next-index))) + +(defun jumper-remove-location () + "Remove a stored location." + (interactive) + (if (= jumper--next-index 0) + (message "No locations stored") + (let* ((locations + (cl-loop for i from 0 below jumper--next-index + for fmt = (jumper--format-location i) + when fmt collect (cons fmt i))) + (locations (cons (cons "Cancel" -1) locations)) + (choice (completing-read "Remove location: " locations nil t)) + (idx (cdr (assoc choice locations)))) + (if (= idx -1) + (message "Operation cancelled") + (jumper--reorder-registers idx) + (message "Location removed"))))) + +(defvar jumper-map + (let ((map (make-sparse-keymap))) + (define-key map (kbd "SPC") #'jumper-store-location) + (define-key map (kbd "j") #'jumper-jump-to-location) + (define-key map (kbd "d") #'jumper-remove-location) + map) + "Keymap for jumper commands.") + +;;;###autoload +(defun jumper-setup-keys () + "Setup default keybindings for jumper." + (interactive) + (global-set-key (kbd jumper-prefix-key) jumper-map)) + +;; Call jumper-setup-keys when the package is loaded +(jumper-setup-keys) + +(provide 'jumper) +;;; jumper.el ends here. |
