Blob


1 ;;; side-notes.el --- Easy access to a directory notes file -*- lexical-binding: t; -*-
3 ;; Copyright (c) 2019-2024 Paul W. Rankin
5 ;; Author: Paul W. Rankin <rnkn@rnkn.xyz>
6 ;; Keywords: convenience
7 ;; Version: 0.4.1
8 ;; Package-Requires: ((emacs "24.4"))
9 ;; URL: https://github.com/rnkn/side-notes
11 ;; This file is not part of GNU Emacs.
13 ;; This program is free software; you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or (at
16 ;; your option) any later version.
18 ;; This program is distributed in the hope that it will be useful, but
19 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 ;; General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with this program. If not, see <https://www.gnu.org/licenses/>.
26 ;;; Commentary:
28 ;; Side Notes
29 ;; ==========
31 ;; Quickly display your quick side notes in quick side window.
33 ;; Side notes live in a plain text file, called notes.txt by default, in
34 ;; the current directory or any parent directory (i.e. when locating notes,
35 ;; we first look in the current directory, then one directory up, then two
36 ;; directories up, and so forth.)
38 ;; Side notes are displayed in a side window with the command
39 ;; side-notes-toggle-notes.
41 ;; The filename is defined by user option side-notes-file.
43 ;; To really mix things up, there's the user option
44 ;; side-notes-secondary-file, which defaults to notes-2.txt, and will
45 ;; display a separate notes file in a lower side window when the command
46 ;; side-notes-toggle-notes is prefixed with an argument (C-u).
48 ;; By default a notes file found in any parent directory will open that
49 ;; file rather than visit a non-existing file in the current directory, but
50 ;; you can override this by prefixing side-notes-toggle-notes with...
52 ;; - C-u C-u to force visiting side-notes-file within the current
53 ;; directory.
54 ;; - C-u C-u C-u to force visiting side-notes-secondary-file within
55 ;; the current directory.
57 ;; Of course, you can use Markdown or Org Mode or whatever by changing the
58 ;; file extensions of side-notes-file and/or side-notes-secondary-file.
60 ;; For more information on how to change the way the side window is
61 ;; displayed, see (info "(elisp) Side Windows").
64 ;; Installation
65 ;; ------------
67 ;; Install from [MELPA stable][1] then add something like the following to
68 ;; your init file:
70 ;; (define-key (current-global-map) (kbd "M-s n") #'side-notes-toggle-notes)
73 ;; Bugs and Feature Requests
74 ;; -------------------------
76 ;; Send me an email (address in the package header). For bugs, please
77 ;; ensure you can reproduce with:
79 ;; $ emacs -Q -l side-notes.el
81 ;; Known issues are tracked with FIXME comments in the source.
84 ;; [1]: https://stable.melpa.org/#/side-notes
85 ;; [2]: https://melpa.org/#/side-notes
88 ;;; Code:
90 (defgroup side-notes ()
91 "Display a notes file."
92 :group 'convenience)
94 (defcustom side-notes-hook
95 nil
96 "Hook run after showing notes buffer."
97 :type 'hook
98 :group 'side-notes)
100 (defcustom side-notes-file
101 "notes.txt"
102 "Name of the notes file.
104 This file lives in the current directory or any parent directory
105 thereof, which allows you to keep a notes file in the top level
106 of a multi-directory project.
108 If you would like to use a file-specific notes file, specify a
109 string with `add-file-local-variable'. Likewise you can specify a
110 directory-specific notes file with `add-dir-local-variable'."
111 :type 'string
112 :safe 'stringp
113 :group 'side-notes)
114 (make-variable-buffer-local 'side-notes-file)
116 (defcustom side-notes-secondary-file
117 "notes-2.txt"
118 "Name of an optional secondary notes file.
120 Like `side-notes-file' but displayed when `side-notes-toggle-notes'
121 is prefixed with \\[universal-argument].
123 If you would like to use a file-specific notes file, specify a
124 string with `add-file-local-variable'. Likewise you can specify a
125 directory-specific notes file with `add-dir-local-variable'."
126 :type 'string
127 :safe 'stringp
128 :group 'side-notes)
129 (make-variable-buffer-local 'side-notes-secondary-file)
131 (defcustom side-notes-select-window
133 "If non-nil, switch to notes window upon displaying it."
134 :type 'boolean
135 :safe 'booleanp
136 :group 'side-notes)
138 (defcustom side-notes-display-alist
139 '((side . right)
140 (window-width . 35))
141 "Alist used to display notes buffer.
143 n.b. the special symbol `slot' added automatically to ensure that
144 `side-notes-file' is displayed above `side-notes-secondary-file'."
145 :type 'alist
146 :link '(info-link "(elisp) Buffer Display Action Alists")
147 :group 'side-notes)
149 (defface side-notes
150 '((t nil))
151 "Default face for notes buffer."
152 :group 'side-notes)
154 (defvar-local side-notes-buffer-identify
155 nil
156 "Buffer local variable to identify a notes buffer.")
158 (defun side-notes-locate-notes (&optional arg)
159 "Look up directory hierachy for file `side-notes-file'.
161 Return nil if no notes file found."
162 (cond ((and side-notes-secondary-file (= arg 64))
163 (expand-file-name side-notes-secondary-file default-directory))
164 ((= arg 16)
165 (expand-file-name side-notes-file default-directory))
166 ((and side-notes-secondary-file (= arg 4))
167 (expand-file-name side-notes-secondary-file
168 (locate-dominating-file default-directory
169 side-notes-secondary-file)))
170 (t
171 (expand-file-name side-notes-file
172 (locate-dominating-file default-directory
173 side-notes-file)))))
175 ;;;###autoload
176 (defun side-notes-toggle-notes (arg)
177 "Pop up a side window containing `side-notes-file'.
179 When prefixed with...
181 1. \\[universal-argument], locate `side-notes-secondary-file' instead.
182 2. \\[universal-argument] \\[universal-argument], force visiting `side-notes-file' within current directory.
183 3. \\[universal-argument] \\[universal-argument] \\[universal-argument], force visiting `side-notes-secondary-file' within
184 current directory.
186 See `side-notes-display-alist' for options concerning displaying
187 the notes buffer."
188 (interactive "p")
189 (if side-notes-buffer-identify
190 (quit-window)
191 (let ((display-buffer-mark-dedicated t)
192 (buffer (find-file-noselect (side-notes-locate-notes arg))))
193 (if (get-buffer-window buffer (selected-frame))
194 (delete-windows-on buffer (selected-frame))
195 (display-buffer-in-side-window
196 buffer (cons (cons 'slot (if (or (= arg 4) (= arg 64)) 1 -1))
197 side-notes-display-alist))
198 (with-current-buffer buffer
199 (setq side-notes-buffer-identify t)
200 (face-remap-add-relative 'default 'side-notes)
201 (run-hooks 'side-notes-hook))
202 (if side-notes-select-window
203 (select-window (get-buffer-window buffer (selected-frame))))
204 (message "Showing `%s'; %s to hide" buffer
205 (key-description (where-is-internal this-command
206 overriding-local-map t)))))))
208 (provide 'side-notes)
209 ;;; side-notes.el ends here
211 ;; Local Variables:
212 ;; coding: utf-8-unix
213 ;; fill-column: 80
214 ;; require-final-newline: t
215 ;; sentence-end-double-space: nil
216 ;; indent-tabs-mode: nil
217 ;; End: