summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--breadcrumb.el190
1 files changed, 105 insertions, 85 deletions
diff --git a/breadcrumb.el b/breadcrumb.el
index 9b4466e..15c6828 100644
--- a/breadcrumb.el
+++ b/breadcrumb.el
@@ -1,9 +1,10 @@
;;; breadcrumb.el --- project and imenu-based breadcrumb paths -*- lexical-binding: t; -*-
-;; Copyright (C) 2023 João Távora
+;; Copyright (C) 2023 Free Software Foundation, Inc.
;; Author: João Távora <joaotavora@gmail.com>
-;; Version: 0.0.3beta
+;; Package-Requires: ((emacs "28.1") (project "0.9.8"))
+;; Version: 1.0.0
;; Keywords:
;; This program is free software; you can redistribute it and/or modify
@@ -98,6 +99,48 @@
(require 'imenu)
(require 'project)
+
+;;; Customization options
+
+(defgroup breadcrumb nil
+ "One-liner indication of where you are in the maze."
+ :prefix "breadcrumb-"
+ :group 'convenience)
+
+(defcustom bc-project-max-length 40
+ "Soft cutoff for `breadcrumb-project-crumbs'." :type 'natnum)
+
+(defcustom bc-project-crumb-separator "/"
+ "Separator for `breadcrumb-project-crumbs'." :type 'string)
+
+(defcustom bc-imenu-max-length 40
+ "Soft cutoff for `breadcrumb-imenu-crumbs'." :type 'natnum)
+
+(defcustom bc-imenu-crumb-separator " > "
+ "Separator for `breadcrumb-project-crumbs'." :type 'string)
+
+(defface bc-face '((t (:inherit shadow)))
+ "Base face for all breadcrumb things.")
+
+(defface bc-imenu-crumbs-face '((t (:inherit bc-face)))
+ "Face for imenu crumbs in the breadcrumb imenu path.")
+
+(defface bc-imenu-leaf-face '((t (:inherit (font-lock-function-name-face
+ bc-imenu-crumbs-face))))
+ "Face for imenu leaf crumbs in the breadcrumb imenu path.")
+
+(defface bc-project-crumbs-face '((t (:inherit bc-face)))
+ "Face for project crumbs in the breadcrumb project path.")
+
+(defface bc-project-base-face '((t (:inherit bc-project-crumbs-face)))
+ "Face for project base in the breadcrumb project path.")
+
+(defface bc-project-leaf-face '((t (:inherit (mode-line-buffer-id))))
+ "Face for the project leaf crumb in breadcrumb project path.")
+
+
+;;; "ipath" management logic and imenu interoperation
+;;;
(cl-defun bc--bisect (a x &key (from 0) (to (length a)) key from-end)
"Compute index to insert X in sequence A, keeping it sorted.
If X already in A, the resulting index is the leftmost such
@@ -158,19 +201,6 @@ These structures don't have a `breadcrumb-region' property on."
(bc--ipath-rich index-alist pos)
(bc--ipath-plain index-alist pos)))
-;; FIXME: Why do I need to put these in special variables?
-(defvar bc--header-line-key [header-line mouse-1])
-(defvar bc--mode-line-key [mode-line mouse-1])
-
-(require 'pulse)
-(defun bc--goto (window pos)
- (with-selected-window window
- (with-current-buffer (window-buffer)
- (push-mark)
- (goto-char pos)
- (let ((pulse-delay 0.05) (pulse-flag t))
- (pulse-momentary-highlight-region (line-beginning-position) (line-end-position))))))
-
(defvar bc-idle-time 1
"Control idle time before requesting new breadcrumbs.")
@@ -205,55 +235,28 @@ These structures don't have a `breadcrumb-region' property on."
(force-mode-line-update t)))))))))
imenu--index-alist))
-(defgroup breadcrumb nil
- "One-liner indication of where you are in the maze."
- :prefix "breadcrumb-"
- :group 'convenience)
-
-(defcustom bc-project-max-length 40
- "Soft cutoff for `breadcrumb-project-crumbs'." :type 'natnum)
-
-(defcustom bc-project-crumb-separator "/"
- "Separator for `breadcrumb-project-crumbs'." :type 'string)
-
-(defcustom bc-imenu-max-length 40
- "Soft cutoff for `breadcrumb-imenu-crumbs'." :type 'natnum)
-
-(defcustom bc-imenu-crumb-separator " > "
- "Separator for `breadcrumb-project-crumbs'." :type 'string)
-
-(defface bc-face '((t (:inherit shadow)))
- "Base face for all breadcrumb things.")
-
-(defface bc-imenu-crumbs-face '((t (:inherit bc-face)))
- "Face for imenu crumbs in the breadcrumb imenu path.")
-
-(defface bc-imenu-leaf-face '((t (:inherit (font-lock-function-name-face
- bc-imenu-crumbs-face))))
- "Face for imenu leaf crumbs in the breadcrumb imenu path.")
-
-(defface bc-project-crumbs-face '((t (:inherit bc-face)))
- "Face for project crumbs in the breadcrumb project path.")
-
-(defface bc-project-base-face '((t (:inherit bc-project-crumbs-face)))
- "Face for project base in the breadcrumb project path.")
-
-(defface bc-project-leaf-face '((t (:inherit (mode-line-buffer-id))))
- "Face for the project leaf crumb in breadcrumb project path.")
+
+;;; Higher-level functions
+;; FIXME: Why do I need to put these key definitiosn in special
+;; variables?
+(defvar bc--header-line-key [header-line mouse-1])
+(defvar bc--mode-line-key [mode-line mouse-1])
(defun bc--format-ipath-node (p more)
(let* ((l (lambda (&rest _event)
(interactive)
- ;; FIXME: This is a bit inadequate if the user is
+ ;; TODO: This is a bit inadequate if the user is
;; clicking the mode or header lines, but 'event' seems
- ;; to be missing in these cases.
+ ;; to be missing in these cases. We would to
+ ;; conveniently visit places near the node `p' via the
+ ;; mouse
(breadcrumb-jump))))
(propertize
p 'mouse-face 'header-line-highlight
'face (if more 'bc-imenu-crumbs-face 'bc-imenu-leaf-face)
'bc-dont-shorten (null more)
- 'help-echo "mouse-1: Go places"
+ 'help-echo (format "mouse-1: Go places near %p" p)
'keymap
(let ((m (make-sparse-keymap)))
(define-key m bc--header-line-key l)
@@ -266,10 +269,12 @@ These structures don't have a `breadcrumb-region' property on."
(when-let ((alist (bc--ipath-alist)))
(when (cl-some #'identity alist)
(bc--summarize
- (cl-loop for (p . more) on (bc-ipath alist (point))
- collect (bc--format-ipath-node p more))
+ (cl-loop
+ for (p . more) on (bc-ipath alist (point))
+ collect (bc--format-ipath-node p more))
bc-imenu-max-length
- bc-imenu-crumb-separator))))
+ (propertize bc-imenu-crumb-separator
+ 'face 'bc-face)))))
(defun bc--summarize (crumbs cutoff separator)
"Return a string that summarizes CRUMBS, a list of strings.
@@ -290,17 +295,20 @@ Join the crumbs with SEPARATOR."
collect toadd)))
(string-join (reverse rcrumbs) separator)))
-(defvar-local bc--cached-project-crumbs nil)
-
-(defun bc--format-project-node (p more root upto)
+(defun bc--format-project-node (p more root path)
+ "Helper for `bc--project-crumbs-1'.
+Formats path crumb P given optional MORE nodes. ROOT is the
+default directory of P's project. PATH is the path of P relative
+to ROOT."
(let ((l (lambda (&rest _event)
(interactive)
- (find-file (file-name-directory (expand-file-name upto root))))))
+ ;; TODO: See similar TODO in `bc--format-ipath-node'.
+ (find-file (file-name-directory (expand-file-name path root))))))
(propertize p 'face
(if more 'bc-project-crumbs-face 'bc-project-leaf-face)
'bc-dont-shorten (null more)
'mouse-face 'header-line-highlight
- 'help-echo (format "mouse-1: Go places nearby %s -> %s" root upto)
+ 'help-echo (format "mouse-1: Go places near %s/%s" root path)
'keymap
(let ((m (make-sparse-keymap)))
(define-key m bc--header-line-key l)
@@ -308,34 +316,36 @@ Join the crumbs with SEPARATOR."
m))))
(defun bc--project-crumbs-1 (bfn)
- (cl-loop with project = (project-current)
- with root = (if project (project-root project) default-directory)
- with relname = (file-relative-name (or bfn default-directory)
- root)
- for (s . more) on (split-string relname "/")
- concat s into upto
- when more concat "/" into upto
- collect (bc--format-project-node s more root upto) into retval
- finally
- (cl-return
- (if project
- (cons (propertize (project-name project)
- 'bc-dont-shorten t
- 'face 'bc-project-base-face)
- retval)
- retval))))
+ "Helper for `breadcrumb-project-crumbs'.
+Given BFN, the `buffer-file-name', produce a a list of
+propertized crumbs."
+ (cl-loop
+ with project = (project-current)
+ with root = (if project (project-root project) default-directory)
+ with relname = (file-relative-name (or bfn default-directory)
+ root)
+ for (s . more) on (split-string relname "/")
+ concat s into upto
+ when more concat "/" into upto
+ collect (bc--format-project-node s more root upto) into retval
+ finally
+ (cl-return
+ (if project
+ (cons (propertize (project-name project)
+ 'bc-dont-shorten t
+ 'face 'bc-project-base-face)
+ retval)
+ retval))))
;;;###autoload
(cl-defun breadcrumb-project-crumbs ()
"Describing the current file inside project."
- (or bc--cached-project-crumbs
- (setq bc--cached-project-crumbs
- (bc--summarize
- (if buffer-file-name (bc--project-crumbs-1 buffer-file-name)
- (list (propertize (buffer-name) 'face 'bc-project-leaf-face)))
- bc-project-max-length
- (propertize bc-project-crumb-separator
- 'face 'bc-project-crumbs-face)))))
+ (bc--summarize
+ (if buffer-file-name (bc--project-crumbs-1 buffer-file-name)
+ (list (propertize (buffer-name) 'face 'bc-project-leaf-face)))
+ bc-project-max-length
+ (propertize bc-project-crumb-separator
+ 'face 'bc-project-crumbs-face)))
(defun bc--header-line ()
"Helper for `breadcrumb-headerline-mode'."
@@ -361,6 +371,16 @@ Join the crumbs with SEPARATOR."
(define-globalized-minor-mode breadcrumb-mode bc-local-mode
bc--turn-on-local-mode-on-behalf-of-global-mode)
+(require 'pulse)
+(defun bc--goto (window pos)
+ "Helper for `breadcrumb-jump'."
+ (with-selected-window window
+ (with-current-buffer (window-buffer)
+ (push-mark)
+ (goto-char pos)
+ (let ((pulse-delay 0.05) (pulse-flag t))
+ (pulse-momentary-highlight-region (line-beginning-position) (line-end-position))))))
+
;;;###autoload
(defun breadcrumb-jump ()
"Like \\[execute-extended-command] `imenu', but breadcrumb-powered."