[
  {
    "path": ".elpaignore",
    "content": "screenshot.png\nREADME.md"
  },
  {
    "path": ".gitignore",
    "content": "*.elc"
  },
  {
    "path": "README.md",
    "content": "[![GNU ELPA](https://elpa.gnu.org/packages/breadcrumb.svg)](https://elpa.gnu.org/packages/breadcrumb.html)\n# M-x breadcrumb-mode\n\n![screenshot](./screenshot.png)\n\n## Usage\n\nBreadcrumbs are sequences of short strings indicating where you are in\nsome big tree-like maze that is probably your code.  Hopefully the\nscreenshot above clears it up.\n\n* `M-x breadcrumb-mode` is global mode.  Will try to turn itself on\n  conservatively and only if there's a project.\n\n* `M-x breadcrumb-local-mode` is a buffer-local minor mode, if you\n  don't want the default heuristics for turning it on everywhere.\n   \nThere's not much more to it.  Breadcrumb will try to query `imenu.el`\nand `project.el` for the best information.\n\n## Installation\n\nYou can download breadcrumb via GNU Elpa with `M-x package-install RET breadcrumb RET`\n\nOr if you'd like to install it manually you can download `breadcrumb.el` and put it in \nyour `load-path` similar to `(add-to-list 'load-path \"~/path/to/breadcrumb.el\")`\n\n## More usage\n\nIf you want some leet modeline you may also manually put the mode-line\nconstructs\n\n```lisp\n(:eval (breadcrumb-imenu-crumbs))\n```\n\nand\n\n```lisp\n(:eval (breadcrumb-project-crumbs))\n```    \n\nin your settings of the `mode-line-format` or `header-line-format`\nvariables.\n\n## Tweaks\n\nThe shape and size of each breadcrumb groups may be tweaked via\n`breadcrumb-imenu-max-length`, `breadcrumb-project-max-length`,\n`breadcrumb-imenu-crumb-separator`, and\n`breadcrumb-project-crumb-separator`.\n\nThe structure each of the breadcrumbs varies depending on whether\neither `project.el` and `imenu.el` (or both) can do useful things for\nyour buffer.\n\nFor Project breadcrumbs, this depends on whether `project.el`'s\n`project-current` can guess what project the current buffer belongs\nto.\n\nFor Imenu breadcrumbs, this varies.  Depending on the major-mode\nauthor's taste, the Imenu tree (in variable `imenu--index-alist`) may\nhave different structure.  Sometimes, minor mode also tweak the Imenu\ntree in useful ways.  For example, with recent Eglot (I think Eglot\n1.14+), managed buffers get extra region info added to it, which makes\nBreadcrumb show \"richer\" paths.\n"
  },
  {
    "path": "breadcrumb.el",
    "content": ";;; breadcrumb.el --- project and imenu-based breadcrumb paths   -*- lexical-binding: t; -*-\n\n;; Copyright (C) 2023  Free Software Foundation, Inc.\n\n;; Author: João Távora <joaotavora@gmail.com>\n;; Package-Requires: ((emacs \"28.1\") (project \"0.9.8\"))\n;; Version: 1.0.1\n;; Keywords:\n\n;; This program is free software; you can redistribute it and/or modify\n;; it under the terms of the GNU General Public License as published by\n;; the Free Software Foundation, either version 3 of the License, or\n;; (at your option) any later version.\n\n;; This program is distributed in the hope that it will be useful,\n;; but WITHOUT ANY WARRANTY; without even the implied warranty of\n;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n;; GNU General Public License for more details.\n\n;; You should have received a copy of the GNU General Public License\n;; along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\n;;; Commentary:\n;;\n;;;; Usage:\n;;\n;; Breadcrumbs are sequences of short strings indicating where you\n;; are in some big tree-like maze.\n;;\n;; To craft these strings, this library uses the maps provided by\n;; project.el and Imenu, respectively.  Project breadcrumbs shows you\n;; the current buffer's path in a large project.  Imenu breadcrumbs\n;; show the current position of point in the buffer's nested\n;; structure of programming constructs (for example, a specific\n;; functions within multiple C++ nested namespaces).\n;;\n;; To use this library:\n;;\n;; * `M-x breadcrumb-mode` is a global mode.  Will try to turn itself\n;;   on conservatively and only if there's a project.\n;;\n;; * `M-x breadcrumb-local-mode` is a buffer-local minor mode, if you\n;;    don't want the default heuristics for turning it on everywhere.\n;;\n;; * Manually put the mode-line constructs\n;;\n;;     (:eval (breadcrumb-imenu-crumbs))\n;;\n;;   and\n;;\n;;     (:eval (breadcrumb-project-crumbs))\n;;\n;;  in your settings of the `mode-line-format' or\n;;  `header-line-format' variables.\n;;\n;; The shape and size of each breadcrumb groups may be tweaked via\n;; `breadcrumb-imenu-max-length', `breadcrumb-project-max-length',\n;; `breadcrumb-imenu-crumb-separator', and\n;; `breadcrumb-project-crumb-separator'.\n;;\n;; The structure each the breadcrumbs varies depending on whether\n;; either project.el and imenu.el (or both) can do useful things for\n;; your buffer.\n;;\n;; For Project breadcrumbs, this depends on whether project.el's\n;; `project-current' can guess what project the current buffer\n;; belongs to.\n;;\n;; For Imenu breadcrumbs, this varies.  Depending on the major-mode\n;; author's taste, the Imenu tree (in variable `imenu--index-alist')\n;; may have different structure.  Sometimes, minor mode also tweak\n;; the Imenu tree in useful ways.  For example, with recent Eglot (I\n;; think Eglot 1.14+), managed buffers get extra region info added to\n;; it, which makes Breadcrumb show \"richer\" paths.\n;;\n;;;; Implementation notes:\n;;\n;; This _should_ be faster than which-func.el due some caching\n;; strategies.  One of these strategies occurs in `bc--ipath-alist',\n;; which takes care not to over-call `imenu--make-index-alist', which\n;; could be slow (in fact very slow if an external process needs to\n;; be contacted).  The variable `breadcrumb-idle-delay' controls\n;; that.  Another cache occurs in `bc--ipath-plain-cache' second is\n;; just a simple \"space-for-speed\" cache.\n;;\n;; Breadcrumb uses the double-dashed Imenu symbols\n;; `imenu--index-alist' and `imenu--make-index-alist'.  There's\n;; really no official API here.  It's arguable that, despite the\n;; name, these aren't really internal symbols (the much older\n;; which-func.el library makes liberal use of them, for example).\n;;\n;;;; Todo:\n;;\n;; Make more clicky buttons in the headerline to do whatever\n;;\n\n;;; Code:\n(require 'cl-lib)\n(require 'imenu)\n(require 'project)\n\n\f\n;;;; Customization options\n;;\n\n(defgroup breadcrumb nil\n  \"One-liner indication of where you are in the maze.\"\n  :prefix \"breadcrumb-\"\n  :group 'convenience)\n\n(defcustom bc-project-max-length 0.3\n  \"Soft cutoff for `breadcrumb-project-crumbs'.\nIf a fixnum, it's a absolute number of characters.  If a float, a\npercentage of `window-width'.\"\n  :type '(choice (natnum :tag \"Number of characters\")\n                 (float  :tag \"Percent of window's width\")))\n\n(defcustom bc-project-crumb-separator \"/\"\n  \"Separator for `breadcrumb-project-crumbs'.\" :type 'string)\n\n(defcustom bc-imenu-max-length 0.3\n  \"Soft cutoff for `breadcrumb-imenu-crumbs'.\nIf a fixnum, it's a absolute number of characters.  If a float, a\npercentage of `window-width'.\"\n  :type '(choice (natnum :tag \"Number of characters\")\n                 (float  :tag \"Percent of window's width\")))\n\n(defcustom bc-imenu-crumb-separator \" > \"\n  \"Separator for `breadcrumb-project-crumbs'.\" :type 'string)\n\n(defface bc-face '((t (:inherit shadow)))\n  \"Base face for all breadcrumb things.\")\n\n(defface bc-imenu-crumbs-face '((t (:inherit bc-face)))\n  \"Face for imenu crumbs in the breadcrumb imenu path.\")\n\n(defface bc-imenu-leaf-face '((t (:inherit (font-lock-function-name-face\n                                            bc-imenu-crumbs-face))))\n  \"Face for imenu leaf crumbs in the breadcrumb imenu path.\")\n\n(defface bc-project-crumbs-face '((t (:inherit bc-face)))\n  \"Face for project crumbs in the breadcrumb project path.\")\n\n(defface bc-project-base-face '((t (:inherit bc-project-crumbs-face)))\n  \"Face for project base in the breadcrumb project path.\")\n\n(defface bc-project-leaf-face '((t (:inherit (mode-line-buffer-id))))\n  \"Face for the project leaf crumb in breadcrumb project path.\")\n\n\f\n;;;; \"ipath\" management logic and imenu interoperation\n;;\n(cl-defun bc--bisect (a x &key (from 0) (to (length a)) key from-end)\n  \"Compute index to insert X in sequence A, keeping it sorted.\nIf X already in A, the resulting index is the leftmost such\nindex, unless FROM-END is t.  KEY is as usual in other CL land.\"\n  (cl-macrolet ((search (from-end key)\n                  `(cl-loop while (< from to)\n                            for mid = (/ (+ from to) 2)\n                            for p1 = (elt a mid)\n                            for p2 = ,(if key `(funcall key p1) `p1)\n                            if (,(if from-end '< '<=) x p2)\n                            do (setq to mid) else do (setq from (1+ mid))\n                            finally return from)))\n    (if from-end (if key (search t key) (search t nil))\n      (if key (search nil key) (search nil nil)))))\n\n(defun bc--ipath-rich (index-alist pos)\n  \"Compute ipath for rich `imenu--index-alist' structures.\nThese structures have a `breadcrumb-region' property on every\nnode.\"\n  (cl-labels\n      ((search (nodes &optional ipath)\n         (cl-loop\n          for n in nodes\n          for reg = (get-text-property 0 'breadcrumb-region (car n))\n          when (<= (car reg) pos (cdr reg))\n          return (search (cdr n) (cons\n                                  (propertize (car n)\n                                              'breadcrumb-siblings nodes\n                                              'breadcrumb-parent (car ipath))\n                                  ipath))\n          finally (cl-return ipath))))\n    (nreverse (search index-alist))))\n\n(defvar-local bc--ipath-plain-cache nil\n  \"A cache for `breadcrumb--ipath-plain'.\")\n\n(defun bc--ipath-plain (index-alist pos)\n  \"Compute ipath for plain `imenu--index-alist' structures.\nThese structures don't have a `breadcrumb-region' property on.\"\n  (cl-labels ((dfs (n &optional ipath siblings)\n                (setq ipath (cons (car n) ipath))\n                (if (consp (cdr n))\n                    (mapc (lambda (n2) (dfs n2 ipath (cdr n))) (cdr n))\n                  ;; FIXME: we convert markers to points via the `+'\n                  ;; down there.  But for siblings, no such conversion\n                  ;; happens, they might still point to invalid\n                  ;; markers.  Not worth doing another tree traversal\n                  ;; for that IMO, and siblings seems to be unused\n                  ;; anyway (github#22)\n                  (put-text-property 0 1 'breadcrumb-siblings (cdr siblings) (car ipath))\n                  (setq bc--ipath-plain-cache\n                        (vconcat bc--ipath-plain-cache\n                                 `[,(cons\n                                     ;; See github#17 and docstring of\n                                     ;; `imenu--index-alist' for the\n                                     ;; \"overlay\" edge case.\n                                     (cl-etypecase (cdr n)\n                                       (number (cdr n))\n                                       (marker (+ (cdr n) 0))\n                                       (overlay (overlay-start (cdr n))))\n                                     ipath)])))))\n    (unless bc--ipath-plain-cache\n      (mapc (lambda (i) (dfs i nil index-alist)) index-alist)\n      (setq bc--ipath-plain-cache (cl-sort bc--ipath-plain-cache #'< :key #'car)))\n    (unless (< pos (car (aref bc--ipath-plain-cache 0)))\n      (let ((res (bc--bisect bc--ipath-plain-cache pos :key #'car :from-end t)))\n        (unless (zerop res) (reverse (cdr (elt bc--ipath-plain-cache (1- res)))))))))\n\n(defun bc-ipath (index-alist pos)\n  \"Get breadcrumb for position POS given INDEX-ALIST.\"\n  (if (get-text-property 0 'breadcrumb-region (caar index-alist))\n      (bc--ipath-rich index-alist pos)\n    (bc--ipath-plain index-alist pos)))\n\n(defvar bc-idle-time 1\n  \"Control idle time before requesting new breadcrumbs.\")\n\n(defvar-local bc--idle-timer nil\n  \"Timer used by `breadcrumb--ipath-alist'.\")\n\n(defvar-local bc--last-update-tick 0\n  \"Last time `breadcrumb--ipath-alist' asked for an update.\")\n\n(defun bc--ipath-alist ()\n  \"Return `imenu--index-alist', maybe arrange for its update.\"\n  ;; Be mindful of indirect buffers (github#31)\n  (let ((buf (or (buffer-base-buffer) (current-buffer))))\n    (with-current-buffer buf\n      (unless (= (buffer-chars-modified-tick) bc--last-update-tick)\n        (setq bc--last-update-tick (buffer-chars-modified-tick))\n        (when bc--idle-timer (cancel-timer bc--idle-timer))\n        (setq bc--idle-timer\n              (run-with-idle-timer\n               bc-idle-time nil\n               (lambda ()\n                 (when (buffer-live-p buf)\n                   (with-current-buffer buf\n                     (setq bc--last-update-tick (buffer-chars-modified-tick))\n                     (let ((non-essential t)\n                           (imenu-auto-rescan t))\n                       (ignore-errors\n                         (imenu--make-index-alist t))\n                       (setq bc--ipath-plain-cache nil)\n                       ;; no point is taxing the mode-line machinery now\n                       ;; if the buffer isn't showing anywhere.\n                       (when (get-buffer-window buf t)\n                         (force-mode-line-update t)))))))))\n      imenu--index-alist)))\n\n\f\n;;;; Higher-level functions\n;;\n\n;; FIXME: Why do I need to put these key definitiosn in special\n;; variables?\n(defvar bc--header-line-key [header-line mouse-1])\n(defvar bc--mode-line-key [mode-line mouse-1])\n\n(defun bc--length (len)\n  \"Interpret LEN using `window-width' and return a number.\"\n  (cond ((floatp len) (* (window-width) len))\n        (t len)))\n\n(defun bc--format-ipath-node (p more)\n  (let* ((l (lambda (&rest _event)\n              (interactive)\n              ;; TODO: This is a bit inadequate if the user is\n              ;; clicking the mode or header lines, but 'event' seems\n              ;; to be missing in these cases.  We would to\n              ;; conveniently visit places near the node `p' via the\n              ;; mouse\n              (breadcrumb-jump))))\n    (propertize\n     p 'mouse-face 'header-line-highlight\n     'face (if more 'bc-imenu-crumbs-face 'bc-imenu-leaf-face)\n     'bc-dont-shorten (null more)\n     'help-echo (format \"mouse-1: Go places near %s\" p)\n     'keymap\n     (let ((m (make-sparse-keymap)))\n       (define-key m bc--header-line-key l)\n       (define-key m bc--mode-line-key l)\n       m))))\n\n;;;###autoload\n(defun breadcrumb-imenu-crumbs ()\n  \"Describe point inside the Imenu tree of current file.\"\n  (when-let ((alist (bc--ipath-alist)))\n    (when (cl-some #'identity alist)\n      (bc--summarize\n       (cl-loop\n        for (p . more) on (bc-ipath alist (point))\n        collect (bc--format-ipath-node p more))\n       (bc--length bc-imenu-max-length)\n       (propertize bc-imenu-crumb-separator\n                   'face 'bc-face)))))\n\n(defun bc--summarize (crumbs cutoff separator)\n  \"Return a string that summarizes CRUMBS, a list of strings.\n\\\"Summarization\\\" consists of truncating some CRUMBS to 1 character.\nRightmost members of CRUMBS are summarized last.  Members with a non-nil\n`breadcrumb-dont-shorten' property are never truncated.  Aim for a\nreturn string that is at most CUTOFF characters long.  Join the crumbs\nwith SEPARATOR.\"\n  (let ((rcrumbs\n         (cl-loop\n          for available = (- cutoff used)\n          for (c . more) on (reverse crumbs)\n          for seplen = (if more (length separator) 0)\n          for shorten-p = (unless (get-text-property 0 'bc-dont-shorten c)\n                            (> (+ (length c) seplen) available))\n          for toadd = (if shorten-p (substring c 0 1) c)\n          sum (+ (length toadd) seplen) into used\n          collect toadd)))\n    (string-join (reverse rcrumbs) separator)))\n\n(defun bc--format-project-node (p more root path)\n  \"Helper for `bc--project-crumbs-1'.\nFormats path crumb P given optional MORE nodes.  ROOT is the\ndefault directory of P's project.  PATH is the path of P relative\nto ROOT.\"\n  (let ((l (lambda (&rest _event)\n             (interactive)\n             ;; TODO: See similar TODO in `bc--format-ipath-node'.\n             (find-file (file-name-directory (expand-file-name path root))))))\n    (propertize p 'face\n                (if more 'bc-project-crumbs-face 'bc-project-leaf-face)\n                'bc-dont-shorten (null more)\n                'mouse-face 'header-line-highlight\n                'help-echo (format \"mouse-1: Go places near %s%s\" root path)\n                'keymap\n                (let ((m (make-sparse-keymap)))\n                  (define-key m bc--header-line-key l)\n                  (define-key m bc--mode-line-key l)\n                  m))))\n\n(defvar-local bc--cached-project-root nil\n  ;; This is a fairly \"dumb\" cache, but hopefully good enough for most\n  ;; cases.  A better smarter cache that could realize when certain\n  ;; project things happened would live in project.el which has much\n  ;; more project-smarts, but that's apparently very hard, so do it\n  ;; here (github#18)\n  \"Cache the expensive `project-root' call.\")\n\n(defun bc--project-crumbs-1 (bfn)\n  \"Helper for `breadcrumb-project-crumbs'.\nGiven BFN, the `buffer-file-name', produce a list of\npropertized crumbs.\"\n  (cl-loop\n   with project = (project-current)\n   with root = (if project (or bc--cached-project-root\n                               (setq bc--cached-project-root\n                                     (project-root project)))\n                 default-directory)\n   with pname = (if project (project-name project)\n                  (file-name-nondirectory (directory-file-name root)))\n   with relname = (file-relative-name (or bfn default-directory)\n                                      root)\n   for (s . more) on (split-string relname \"/\")\n   concat s into upto\n   when more concat \"/\" into upto\n   collect (bc--format-project-node s more root upto) into retval\n   finally\n   (cl-return\n    (if root\n        (cons (propertize pname\n                          'bc-dont-shorten t\n                          'face 'bc-project-base-face)\n              retval)\n      retval))))\n\n;;;###autoload\n(cl-defun breadcrumb-project-crumbs ()\n  \"Describing the current file inside project.\"\n  (bc--summarize\n   (if buffer-file-name (bc--project-crumbs-1 buffer-file-name)\n     (list (propertize (buffer-name)\n                       'face 'bc-project-leaf-face\n                       'bc-dont-shorten t)))\n   (bc--length bc-project-max-length)\n   (propertize bc-project-crumb-separator\n               'face 'bc-project-crumbs-face)))\n\n(defun bc--header-line ()\n  \"Helper for `breadcrumb-headerline-mode'.\"\n  (let ((x (cl-remove-if\n            #'seq-empty-p (mapcar #'funcall\n                                  '(bc-project-crumbs bc-imenu-crumbs)))))\n    (mapconcat #'identity x (propertize \" : \" 'face 'bc-face))))\n\n;;;###autoload\n(define-minor-mode breadcrumb-local-mode\n  \"Header lines with breadcrumbs.\"\n  :init-value nil\n  (if bc-local-mode (add-to-list 'header-line-format '(:eval (bc--header-line)))\n    (setq header-line-format (delete '(:eval (bc--header-line)) header-line-format))))\n\n(defun bc--turn-on-local-mode-on-behalf-of-global-mode ()\n  (unless (or (minibufferp)\n              (not (buffer-file-name))\n              (null (bc-project-crumbs)))\n    (bc-local-mode 1)))\n\n;;;###autoload\n(define-globalized-minor-mode breadcrumb-mode bc-local-mode\n  bc--turn-on-local-mode-on-behalf-of-global-mode)\n\n(require 'pulse)\n(defun bc--goto (window pos)\n  \"Helper for `breadcrumb-jump'.\"\n  (with-selected-window window\n    (with-current-buffer (window-buffer)\n      (push-mark)\n      (goto-char pos)\n      (let ((pulse-delay 0.05) (pulse-flag t))\n        (pulse-momentary-highlight-region (line-beginning-position) (line-end-position))))))\n\n;;;###autoload\n(defun breadcrumb-jump ()\n  \"Like \\\\[execute-extended-command] `imenu', but breadcrumb-powered.\"\n  (interactive)\n  (let (cands choice)\n    (cl-labels\n        ((fmt (strs)\n           (mapconcat #'identity strs \" > \"))\n         (dfs (nodes &optional ipath)\n           (cl-loop\n            for n in nodes\n            for newpath = (cons (car n) ipath)\n            for pos = (or (car (get-text-property 0 'breadcrumb-region (car n)))\n                          (and (number-or-marker-p (cdr n)) (cdr n)))\n            when pos do (push (cons (fmt (reverse newpath)) pos)\n                              cands)\n            do (dfs (cdr n) newpath))))\n      (imenu--make-index-alist)\n      (dfs imenu--index-alist)\n      (unless cands (user-error \"Sorry, no breadcrumb items to jump to\"))\n      (setq choice (cdr (assoc (completing-read \"Index item? \" cands nil t)\n                               cands #'string=)))\n      (push-mark)\n      (bc--goto (selected-window) choice))))\n\n(provide 'breadcrumb)\n;;;###autoload (register-definition-prefixes \"breadcrumb\" '(\"breadcrumb-\"))\n\n;; Local Variables:\n;; read-symbol-shorthands: ((\"bc-\" . \"breadcrumb-\"))\n;; autoload-compute-prefixes: nil\n;; End:\n\n;;; breadcrumb.el ends here\n"
  }
]