Tidying my Bookmarks, Annotations and Code Notes
Pretty pleased with all the way I can explore a new code base.
This commit is contained in:
parent
58e18451d6
commit
968885e5f3
6 changed files with 391 additions and 38 deletions
|
|
@ -1053,28 +1053,6 @@ I do want to change a couple of bindings, as ~j~ to pull up a =completing-read=
|
|||
(define-key dired-mode-map (kbd "n") 'evil-search-next)
|
||||
(define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
|
||||
#+end_src
|
||||
* Annotations
|
||||
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" and then move to them (yes, serious overlap with bookmarks, which we will return to).
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package annotate
|
||||
:config
|
||||
(ha-leader
|
||||
"t A" '("annotations" . annotate-mode)
|
||||
|
||||
"n" '(:ignore t :which-key "notes")
|
||||
"n a" '("toggle mode" . annotate-mode)
|
||||
"n n" '("annotate" . annotate-annotate)
|
||||
"n d" '("delete" . annotate-delete)
|
||||
"n s" '("summary" . annotate-show-annotation-summary)
|
||||
"n j" '("next" . annotate-goto-next-annotation)
|
||||
"n k" '("prev" . annotate-goto-previous-annotation)
|
||||
|
||||
;; If a shift binding isn't set, it defaults to non-shift version
|
||||
;; Use SPC N N to jump to the next error:
|
||||
"n N" '("next error" . flycheck-next-error)))
|
||||
#+end_src
|
||||
Keep the annotations simple, almost /tag-like/, and then the summary allows you to display them.
|
||||
* Keepass
|
||||
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
|
||||
#+begin_src emacs-lisp
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#+title: Capturing Notes with Org
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2020-09-18
|
||||
#+tags: emacs org
|
||||
#+TITLE: Capturing Notes with Org
|
||||
#+AUTHOR: Howard X. Abrams
|
||||
#+DATE: 2020-09-18
|
||||
#+TAGS: emacs org
|
||||
|
||||
A literate programming file for configuring org for capturing notes.
|
||||
|
||||
|
|
|
|||
354
ha-code-notes.org
Normal file
354
ha-code-notes.org
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
#+TITLE: Taking Notes with Org
|
||||
#+AUTHOR: Howard X. Abrams
|
||||
#+DATE: 2026-07-14
|
||||
#+FILETAGS: emacs hamacs
|
||||
#+LASTMOD: [2026-07-14 Tue]
|
||||
|
||||
A literate programming file for configuring Emacs to take notes.
|
||||
|
||||
#+begin_src emacs-lisp :exports none
|
||||
;;; ha-code-notes --- configuring Emacs to take notes. -*- lexical-binding: t; -*-
|
||||
;;
|
||||
;; © 2026 Howard X. Abrams
|
||||
;; Licensed under a Creative Commons Attribution 4.0 International License.
|
||||
;; See http://creativecommons.org/licenses/by/4.0/
|
||||
;;
|
||||
;; Author: Howard X. Abrams <http://gitlab.com/howardabrams>
|
||||
;; Maintainer: Howard X. Abrams
|
||||
;; Created: July 14, 2026
|
||||
;;
|
||||
;; While obvious, GNU Emacs does not include this file or project.
|
||||
;;
|
||||
;; *NB:* Do not edit this file. Instead, edit the original literate file at:
|
||||
;; /Users/howard.abrams/src/hamacs/ha-code-notes.org
|
||||
;; And tangle the file to recreate this one.
|
||||
;;
|
||||
;;; Code:
|
||||
#+end_src
|
||||
|
||||
* Introduction
|
||||
Seems silly to have a full set of instructions for /taking notes/ using Org-mode, because that is what it does, but this is a bit special.
|
||||
|
||||
When I [[file:ha-capturing-notes.org][capture notes]] with a link to some source code, the /destination/ is a *clocked in* task. Useful, but seems to require a dedicated task and some forethought. The following project pairs a “source code file” (which can be any file, actually) and a “notes file” with headers based on the function in the original file.
|
||||
|
||||
#+begin_src dot :file ha-code-notes-illustration.png :exports file :results file
|
||||
digraph G {
|
||||
rankdir=LR;
|
||||
// Make to icons side-by-side
|
||||
code_file [shape=note, label="", fixedsize=true, width=1, height=1.5];
|
||||
note_file [shape=note, label="", fixedsize=true, width=1, height=1.5];
|
||||
|
||||
// Create a couple of label to live _below_ the icons:
|
||||
code_label [shape=plaintext, label="foobar.py"];
|
||||
note_label [shape=plaintext, label=".foobar-note.org"];
|
||||
{ rank=same; code_file; code_label; }
|
||||
{ rank=same; note_file; note_label; }
|
||||
code_file -> code_label [style=invis];
|
||||
note_file -> note_label [style=invis];
|
||||
|
||||
// Connect the code and note icons:
|
||||
code_file -> note_file;
|
||||
}
|
||||
#+end_src
|
||||
|
||||
#+attr_org: :width 800px
|
||||
[[file:ha-code-notes-illustration.png]]
|
||||
|
||||
This allows me to /wax poetic/ with a parallel, but separate org file.
|
||||
* Helper Functions
|
||||
While this project started simple, I’ve expanded the ideas, and this requires some custom helper functions. For instance, I want to add a buffer-level /property/ to an org-mode file, for instance:
|
||||
|
||||
#+begin_example
|
||||
#+TITLE: The title of the file
|
||||
#+PROPERTY: key1 value1
|
||||
#+end_example
|
||||
|
||||
The tricky bit about this function is that if (in the above example) =key1= exists, we should /replace/ it, not add to it.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-org-set-buffer-property (key value)
|
||||
"Set a buffer-level property at the top of an Org file.
|
||||
If KEY already refers to a #+PROPERTY, replace it.
|
||||
Otherwise, insert it at the end of the Org header lines."
|
||||
(let ((property-line (format "#+PROPERTY: %s %s" key value))
|
||||
(magic-re (rx (group line-start "#+PROPERTY:"
|
||||
space (literal key) space
|
||||
(zero-or-more any)
|
||||
line-end))))
|
||||
(defun process-line ()
|
||||
"Helper function returns non-nil if more to process.
|
||||
This works because `replace-match' and `insert' return nil,
|
||||
while `forward-line' returns a non-nil value."
|
||||
(cond
|
||||
((looking-at magic-re) (replace-match property-line))
|
||||
((looking-at "#") (forward-line))
|
||||
(t (insert property-line ?\n))))
|
||||
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(while (process-line)))))
|
||||
#+END_SRC
|
||||
|
||||
And we need the ability to read it. The Org API doesn’t have a function to read it without parsing the entire structure, so we use the =org-collect-keywords= for the =PROPERTY= value, and then parse the results.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-org-get-buffer-property (key)
|
||||
"Get the value of a buffer-level #+PROPERTY matching KEY."
|
||||
(let* ((properties (thread-last "PROPERTY"
|
||||
(list) ; Requires a list of keywords
|
||||
(org-collect-keywords)
|
||||
(car))) ; A list of lists? Get first entry
|
||||
;; Each entry in properties is a string with the key and value:
|
||||
(entry (seq-find (lambda (k) (s-starts-with? key k)) properties)))
|
||||
(substring entry (1+ (s-index-of " " entry)))))
|
||||
#+END_SRC
|
||||
|
||||
They work like:
|
||||
#+BEGIN_SRC emacs-lisp :tangle no
|
||||
(ha-org-set-buffer-property "foor" "bar")
|
||||
(ha-org-get-buffer-property "foo") ; ⟹ "bar"
|
||||
#+END_SRC
|
||||
|
||||
IMenu has a /mode-agnostic/ approach to jumping to /sections/. This could work whether the file is an formatted in org-mode, markdown, or even source code. This function allows me to jump to a particular header in either Org, Markdown, or other files that define the =defun= interface differently.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun imenu-goto (header)
|
||||
"Jump to the first `imenu' entry whose name contains HEADER.
|
||||
Like `consult-imenu', this matches HEADER as a case-insensitive
|
||||
substring rather than requiring an exact name, and jumps straight
|
||||
to the first hit instead of prompting.
|
||||
|
||||
Note that this won't work if IMenu is stale and requires refreshing."
|
||||
;; Overshadowing imenu's lookup function seems overly sneaky!
|
||||
(let ((imenu-name-lookup-function
|
||||
(lambda (str name)
|
||||
(let ((case-fold-search t))
|
||||
(string-match-p (regexp-quote str) name)))))
|
||||
;; Calling `imenu' programmatically is a pain!
|
||||
(when-let ((item (imenu--in-alist header (imenu--make-index-alist))))
|
||||
(imenu item))))
|
||||
#+END_SRC
|
||||
|
||||
* Code Notes
|
||||
This function defines what the notes filename should look like, loads it in a side window, and adds a /back reference/ in the form of an org-mode =property=. Next it needs to either find the section that matches the function in the code we are writing about, or jumps to the bottom and creates it.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-code-notes ()
|
||||
"Open an Org file based on the current opened file.
|
||||
The pattern for choosing the name of the org file is:
|
||||
|
||||
foobar.py --> .foobar-notes.org
|
||||
|
||||
The Org header will be the name of the function in the original source
|
||||
code file. This means you have one note section per function, which
|
||||
should be fine in practice because functions are small and succinct,
|
||||
right?"
|
||||
(interactive)
|
||||
(let* ((orig-file (buffer-file-name))
|
||||
(line-num (line-number-at-pos))
|
||||
(orig-parent (file-name-directory orig-file))
|
||||
(orig-base (file-name-base orig-file))
|
||||
|
||||
;; Keep in mind the `orig-parent' has a final slash, so the
|
||||
;; initial . here marks it as hidden:
|
||||
(note-file (format "%s.%s-notes.org"
|
||||
orig-parent orig-base))
|
||||
(header (which-function)))
|
||||
|
||||
;; With the above local variables defined, we can open the file in
|
||||
;; a window (pane) to the side:
|
||||
(find-file-other-window note-file)
|
||||
(ha-org-set-buffer-property "XREF" orig-file)
|
||||
|
||||
(goto-char (point-min)) ; jump to start of file
|
||||
|
||||
;; The `condition-case' is Elisp's way of a try..catch:
|
||||
(condition-case nil
|
||||
;; Find the first Org header that matches `header':
|
||||
(re-search-forward (rx line-start
|
||||
(one-or-more "*")
|
||||
(one-or-more space)
|
||||
(optional (or "=" "~"))
|
||||
(literal header)
|
||||
(optional (or "=" "~"))))
|
||||
(error
|
||||
;; We didn't find a header matching the function, so we jump to
|
||||
;; the end of the file and create a new heading:
|
||||
(goto-char (point-max))
|
||||
(org-insert-heading)
|
||||
(insert (format "%s" header))
|
||||
(org-insert-property-drawer)
|
||||
(org-set-property "XREF_LINE" (number-to-string line-num))
|
||||
;; In case we want to change the section header name, we store
|
||||
;; the name of the function that led us here as a property:
|
||||
(org-set-property "XREF_NAME" header)
|
||||
|
||||
(when-let ((buf (find-buffer-visiting orig-file)))
|
||||
(with-current-buffer buf
|
||||
(ha-code--fringe-notes)))))
|
||||
|
||||
;; We are somewhere in the file, so go to the end of the block:
|
||||
(org-end-of-subtree)
|
||||
|
||||
;; If the subtree ends mid-line, insert a newline:
|
||||
(unless (eq (line-beginning-position) (point))
|
||||
(end-of-line)
|
||||
(insert "\n"))))
|
||||
#+END_SRC
|
||||
|
||||
Use the builtin autoinsert feature to inject a basic template at the beginning of the notes file when we first create the notes file:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package autoinsert
|
||||
:config
|
||||
(define-auto-insert
|
||||
(cons (rx "/." (one-or-more (not "/")) "-notes.org" string-end) "Org Notes Template")
|
||||
'("Short description: "
|
||||
"#+TITLE: "
|
||||
(s-titleized-words (s-replace-regexp (rx (any "-" "_")) " "
|
||||
(file-name-base (buffer-file-name))))
|
||||
\n
|
||||
"#+DATE:" (format-time-string "%Y-%m-%d %a")
|
||||
\n
|
||||
"#+LASTMOD:" (format-time-string "[%Y-%m-%d %a]")
|
||||
\n \n)))
|
||||
#+END_SRC
|
||||
|
||||
If we are /inside/ one of these note files, let’s have a quick way to return back to the original “code” file:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-code-notes-return ()
|
||||
"Return to the code referenced in the notes.
|
||||
Essentially pretends we have a backlink without a database."
|
||||
(interactive)
|
||||
(let* ((filename (ha-org-get-buffer-property "XREF"))
|
||||
(line-num (car (org-property-values "XREF_LINE")))
|
||||
(function (or (car (org-property-values "XREF_NAME")) (which-function))))
|
||||
|
||||
;; If the property is set, load that file (which jumps to the
|
||||
;; buffer if it is displayed), otherwise, we assume the previous
|
||||
;; buffer contains it:
|
||||
(if filename
|
||||
(find-file-other-window filename)
|
||||
(switch-to-prev-buffer))
|
||||
(if line-num
|
||||
(goto-line (string-to-number line-num)))
|
||||
|
||||
;; If the line number got out of sync so that the point is no
|
||||
;; longer in the correct function, use `find-function' to
|
||||
;; reposition the point:
|
||||
(unless (equal (which-function) function)
|
||||
(if (derived-mode-p 'prog-mode)
|
||||
(xref-find-definitions function)
|
||||
(imenu-goto function)))))
|
||||
#+END_SRC
|
||||
|
||||
And give us keybinding that will either go to the notes (if we are in some code) or return to the source code (if we are in our notes):
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-code-notes-dwim ()
|
||||
"Open the notes buffer, or return to the code."
|
||||
(interactive)
|
||||
(when (buffer-file-name)
|
||||
(if (string-match (rx "/." ; A hidden file
|
||||
(one-or-more (not "/"))
|
||||
"-notes.org" string-end)
|
||||
(buffer-file-name))
|
||||
(ha-code-notes-return)
|
||||
(ha-code-notes))))
|
||||
|
||||
(ha-leader "n c" '("code notes" . ha-code-notes-dwim))
|
||||
#+END_SRC
|
||||
|
||||
** Fringe Indicators for Notes
|
||||
|
||||
A code file with an associated notes file is easy to forget about. Let's mark, in the fringe, every line that has a note, so we notice it as we scroll past:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(define-fringe-bitmap 'ha-code-notes-bitmap
|
||||
[#b00001100
|
||||
#b00010110
|
||||
#b00010111
|
||||
#b00101110
|
||||
#b00101110
|
||||
#b01011100
|
||||
#b01011100
|
||||
#b10110000
|
||||
#b10010000
|
||||
#b11100000]
|
||||
nil nil 'center)
|
||||
|
||||
(defface ha-code-notes-face
|
||||
'((t :foreground "yellow"))
|
||||
"Face for the fringe marker indicating a line has an associated note.")
|
||||
|
||||
(defvar-local ha-code-notes-fringe-overlays nil
|
||||
"Overlays marking lines in this buffer that have notes in the paired notes file.")
|
||||
|
||||
(defun ha-code-notes--fringe ()
|
||||
"Mark, in the fringe, every line in this buffer that has a note.
|
||||
Notes live in the paired `.BASE-notes.org' file (see
|
||||
`ha-code-notes') as headings whose XREF/XREF_LINE
|
||||
properties point back to a line in this file."
|
||||
(mapc #'delete-overlay ha-code-notes--fringe-overlays)
|
||||
(setq ha-code--notes--fringe-overlays nil)
|
||||
(let* ((orig-file (buffer-file-name))
|
||||
(note-file (and orig-file
|
||||
(format "%s.%s-notes.org"
|
||||
(file-name-directory orig-file)
|
||||
(file-name-base orig-file))))
|
||||
lines)
|
||||
(when (and note-file (file-exists-p note-file))
|
||||
(with-temp-buffer
|
||||
(insert-file-contents note-file)
|
||||
(org-mode)
|
||||
(org-map-entries
|
||||
(lambda ()
|
||||
(when (equal (ha-org-get-buffer-property "XREF") orig-file)
|
||||
(push (string-to-number (org-entry-get nil "XREF_LINE")) lines)))))
|
||||
(dolist (line lines)
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- line))
|
||||
(let ((ov (make-overlay (point) (point))))
|
||||
(overlay-put ov 'before-string
|
||||
(propertize "x" 'display
|
||||
'(left-fringe ha-code-notes-bitmap
|
||||
ha-code-notes-face)))
|
||||
(push ov ha-code-notes--fringe-overlays)))))))
|
||||
|
||||
(add-hook 'find-file-hook #'ha-code-notes--fringe)
|
||||
#+END_SRC
|
||||
|
||||
New notes and edited notes should refresh the markers too, so we hook into saving a notes file, and refresh right after inserting a new XREF property drawer:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(defun ha-code-notes--fringe-notes-refresh-all ()
|
||||
"Refresh fringe note markers in every buffer after saving a notes file."
|
||||
(when (string-match (rx "-notes.org" string-end) (buffer-file-name))
|
||||
(dolist (buf (buffer-list))
|
||||
(with-current-buffer buf
|
||||
(when buffer-file-name
|
||||
(ha-code--fringe-notes))))))
|
||||
|
||||
(add-hook 'after-save-hook #'ha-code-notes--fringe-notes-refresh-all)
|
||||
#+END_SRC
|
||||
|
||||
* Technical Artifacts :noexport:
|
||||
|
||||
Let's =provide= a name so we can =require= this file:
|
||||
|
||||
#+begin_src emacs-lisp :exports none
|
||||
(provide 'ha-code-notes)
|
||||
;;; ha-code-notes.el ends here
|
||||
#+end_src
|
||||
|
||||
#+DESCRIPTION: configuring Emacs to take notes.
|
||||
|
||||
#+PROPERTY: header-args:sh :tangle no
|
||||
#+PROPERTY: header-args:emacs-lisp :tangle yes
|
||||
#+PROPERTY: header-args :results none :eval no-export :comments no mkdirp yes
|
||||
|
||||
#+OPTIONS: num:nil toc:nil todo:nil tasks:nil tags:nil date:nil
|
||||
#+OPTIONS: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||
#+INFOJS_OPT: view:nil toc:nil ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||
|
|
@ -957,16 +957,6 @@ I like the idea of dropping returnable bookmarks, but with /good defaults/ for t
|
|||
(bookmark-set label)))
|
||||
#+END_SRC
|
||||
|
||||
The built-in behavior doesn’t honor either /projects/ or /perspectives/, but I use [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package to make a =project=-specific bookmarks and use that to jump to only bookmarks in the current project.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package bookmark-in-project
|
||||
:bind
|
||||
(("C-x r m" . bookmark-in-project-toggle)
|
||||
("C-x r M" . ha-bookmark-set)))
|
||||
#+END_SRC
|
||||
|
||||
|
||||
** Minor Keybinding Annoys
|
||||
I like ~C-a~ to go to the beginning of the line, but what about getting to the beginning of text on that line? In Evil, you have ~^~ for beginning of line, and ~0~ for first text. Why not have ~C-a~ toggle between them both:
|
||||
|
||||
|
|
|
|||
|
|
@ -360,10 +360,15 @@ And the collection of useful operations:
|
|||
"b C-g" '(keyboard-escape-quit :which-key t))
|
||||
#+end_src
|
||||
* Bookmarks
|
||||
Expand on my [[file:ha-config.org::*Bookmarks][Bookmarks]] with the following key sequences:
|
||||
The built-in behavior of bookmarks doesn’t honor either /projects/ or /perspectives/, but the [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package makes =project=-specific bookmarks.
|
||||
|
||||
This expands on my global [[file:ha-config.org::*Bookmarks][Bookmarks]]:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package bookmark-in-project
|
||||
:bindd
|
||||
(("C-x r m" . bookmark-in-project-toggle)
|
||||
("C-x r M" . ha-bookmark-set))
|
||||
:config
|
||||
(ha-leader
|
||||
;; Set or delete a bookmark associated with project:
|
||||
|
|
@ -375,6 +380,31 @@ Expand on my [[file:ha-config.org::*Bookmarks][Bookmarks]] with the following ke
|
|||
"b <down>" '("next mark" . bookmark-in-project-jump-next)
|
||||
"b <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
||||
#+end_src
|
||||
** Annotations
|
||||
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" displayed /inline/ with the buffer.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package annotate
|
||||
:config
|
||||
(ha-leader
|
||||
"t A" '("annotations" . annotate-mode)
|
||||
|
||||
"n" '(:ignore t :which-key "notes")
|
||||
"n a" '("toggle mode" . annotate-mode)
|
||||
"n n" '("annotate" . annotate-annotate)
|
||||
"n d" '("delete" . annotate-delete-annotation)
|
||||
|
||||
;; Can't seem to get anything useful out of this function:
|
||||
"n s" '("summary" . annotate-show-annotation-summary)
|
||||
|
||||
;; The next and previous only work for the current file:
|
||||
"n j" '("next" . annotate-goto-next-annotation)
|
||||
"n k" '("prev" . annotate-goto-previous-annotation)))
|
||||
#+end_src
|
||||
|
||||
Keep the annotations simple, almost /tag-like/, and then the summary doesn’t wrap. For longer notes, see my [[file:ha-capturing-notes.org::*Code Notes][Code Notes]] section.
|
||||
|
||||
The annotations are stored as s-expressions in the file, =~/.emacs.d/annotations=. Let’s see how well this scales.
|
||||
* Centering
|
||||
After reading [[https://mbork.pl/2024-04-15_Improving_recenter-top-bottom_and_reposition-window][this essay]], I got to thinking that it would be nice to position the text in a buffer /near the top/, but show context based on some specific, textual /things/. My thought is to have a function that prompts for the thing (like the current paragraph, function, etc), but also create thing-specific functions.
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#+date: 2020-09-18
|
||||
#+tags: emacs org
|
||||
#+startup: inlineimages
|
||||
#+lastmod: [2026-04-08 Wed]
|
||||
#+lastmod: [2026-07-15 Wed]
|
||||
|
||||
A literate programming file for configuring org-mode and those files.
|
||||
|
||||
|
|
@ -65,6 +65,7 @@ Begin by initializing these org variables:
|
|||
org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0,
|
||||
; but uses a trick to make it
|
||||
; appear indented.
|
||||
org-use-sub-superscripts '{}
|
||||
|
||||
org-imenu-depth 4
|
||||
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
||||
|
|
|
|||
Loading…
Reference in a new issue