Compare commits
8 commits
c0a1d5c2d7
...
87f330112a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87f330112a | ||
|
|
263140e101 | ||
|
|
1ccef620bc | ||
|
|
e7f9677ee2 | ||
|
|
968885e5f3 | ||
|
|
58e18451d6 | ||
|
|
6b63130e76 | ||
|
|
3c57ea07af |
22 changed files with 603 additions and 464 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -11,3 +11,4 @@
|
||||||
/ha-theme-results.org
|
/ha-theme-results.org
|
||||||
/support/dashboard
|
/support/dashboard
|
||||||
/.emacs.desktop
|
/.emacs.desktop
|
||||||
|
/.*-notes.org
|
||||||
|
|
|
||||||
|
|
@ -113,9 +113,15 @@ I might switch to [[https://github.com/cxa/agent-shell-macext#start-of-content][
|
||||||
*** Agent Skills
|
*** Agent Skills
|
||||||
The following pulls in [[https://github.com/xenodium/emacs-skills][emacs-skills]], a Claude Agent skills for Emacs:
|
The following pulls in [[https://github.com/xenodium/emacs-skills][emacs-skills]], a Claude Agent skills for Emacs:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC sh
|
||||||
(use-package emacs-skills
|
claude plugin marketplace add xenodium/emacs-skills
|
||||||
:straight (:type git :host github :repo "xenodium/emacs-skills"))
|
claude plugin install emacs-skills@xenodium-emacs-skills
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
Often update with:
|
||||||
|
|
||||||
|
#+BEGIN_SRC sh
|
||||||
|
claude plugin marketplace update xenodium-emacs-skills
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
The most helpful is =/open= that you run afterwards to pull the changes into buffers for complete review. Use the =/highlight= variation to see the changes highlighted.
|
The most helpful is =/open= that you run afterwards to pull the changes into buffers for complete review. Use the =/highlight= variation to see the changes highlighted.
|
||||||
|
|
@ -146,7 +152,7 @@ And point Emacs to it:
|
||||||
:config
|
:config
|
||||||
(setq agent-shell-preferred-agent-config (agent-shell-anthropic-make-claude-code-config)
|
(setq agent-shell-preferred-agent-config (agent-shell-anthropic-make-claude-code-config)
|
||||||
agent-shell-anthropic-claude-acp-command
|
agent-shell-anthropic-claude-acp-command
|
||||||
`,(file-expand-wildcards "/opt/homebrew/Cellar/node/*/bin/claude-agent-acp")))
|
`,(file-expand-wildcards "/opt/homebrew/bin/claude-agent-acp")))
|
||||||
;; /opt/homebrew/Cellar/node/26.0.0/bin/claude-agent-acp
|
;; /opt/homebrew/Cellar/node/26.0.0/bin/claude-agent-acp
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
|
|
@ -1047,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 "n") 'evil-search-next)
|
||||||
(define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
|
(define-key dired-mode-map (kbd ",") 'major-mode-hydras/dired-mode/body)
|
||||||
#+end_src
|
#+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
|
* Keepass
|
||||||
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
|
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
|
#+begin_src emacs-lisp
|
||||||
|
|
|
||||||
|
|
@ -146,7 +146,7 @@ Perhaps we can make it more attractive:
|
||||||
** Telega
|
** Telega
|
||||||
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication.
|
I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be better than Bitlbee for Telegram communication.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package telega
|
(use-package telega
|
||||||
:config
|
:config
|
||||||
(setq telega-chat-show-avatars nil
|
(setq telega-chat-show-avatars nil
|
||||||
|
|
@ -167,7 +167,7 @@ I'm thinking the [[https://zevlg.github.io/telega.el/][Telega package]] would be
|
||||||
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
|
For some reason, you need [[https://github.com/Fanael/rainbow-identifiers][rainbow-identifiers]] to work, oh, I guess the docs state this.
|
||||||
|
|
||||||
In the Telega chat’s, let’s turn on non-fixed-width fonts:
|
In the Telega chat’s, let’s turn on non-fixed-width fonts:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package telega
|
(use-package telega
|
||||||
:hook (telega-chat-mode . mixed-pitch-mode))
|
:hook (telega-chat-mode . mixed-pitch-mode))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#+title: Capturing Notes with Org
|
#+TITLE: Capturing Notes with Org
|
||||||
#+author: Howard X. Abrams
|
#+AUTHOR: Howard X. Abrams
|
||||||
#+date: 2020-09-18
|
#+DATE: 2020-09-18
|
||||||
#+tags: emacs org
|
#+TAGS: emacs org
|
||||||
|
|
||||||
A literate programming file for configuring org for capturing notes.
|
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)))
|
(bookmark-set label)))
|
||||||
#+END_SRC
|
#+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
|
** 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:
|
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:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ Let's turn off the menu and other settings:
|
||||||
Let's install and load some of packages from the [[https://github.com/hlissner/doom-emacs][Doom Emacs]] project, like [[https://github.com/seagle0128/doom-modeline][doom-modeline]] and maybe the themes:
|
Let's install and load some of packages from the [[https://github.com/hlissner/doom-emacs][Doom Emacs]] project, like [[https://github.com/seagle0128/doom-modeline][doom-modeline]] and maybe the themes:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package doom-modeline
|
(use-package doom-modeline
|
||||||
:init
|
:config
|
||||||
(setq doom-modeline-minor-modes nil
|
(setq doom-modeline-minor-modes nil
|
||||||
doom-modeline-buffer-encoding nil
|
doom-modeline-buffer-encoding nil
|
||||||
doom-modeline-major-mode-color-icon t
|
doom-modeline-major-mode-color-icon t
|
||||||
|
|
@ -56,8 +56,13 @@ Let's install and load some of packages from the [[https://github.com/hlissner/d
|
||||||
doom-modeline-modal 'evil
|
doom-modeline-modal 'evil
|
||||||
doom-modeline-lsp-icon t
|
doom-modeline-lsp-icon t
|
||||||
doom-modeline-percent-position nil)
|
doom-modeline-percent-position nil)
|
||||||
|
|
||||||
|
;; Weird bug:
|
||||||
|
(defvar helm-ag-show-status-function nil "Workaround")
|
||||||
|
|
||||||
(doom-modeline-mode 1))
|
(doom-modeline-mode 1))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Window Dimmer
|
* Window Dimmer
|
||||||
To make the active window /more noticeable/, we /dim/ the in-active windows with the [[https://github.com/gonewest818/dimmer.el][dimmer project]].
|
To make the active window /more noticeable/, we /dim/ the in-active windows with the [[https://github.com/gonewest818/dimmer.el][dimmer project]].
|
||||||
|
|
||||||
|
|
|
||||||
66
ha-email.org
66
ha-email.org
|
|
@ -36,7 +36,7 @@ Next type ~s~ to view and organize mail I've never seen before. We need to focus
|
||||||
- ~P~ :: receipts go to this *Paper Trail*, which takes a *tag* as the name of the store
|
- ~P~ :: receipts go to this *Paper Trail*, which takes a *tag* as the name of the store
|
||||||
- ~f~ :: mailing lists and other email that might be nice to read go to *The Feed*
|
- ~f~ :: mailing lists and other email that might be nice to read go to *The Feed*
|
||||||
|
|
||||||
** Email Addresses
|
* Email Addresses
|
||||||
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /not/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
|
The configuration files below expect email addresses (I store passwords and other encrypted information elsewhere). These email addresses are /not/ private, but I figured I would annoy any screenscraping spam-inducing crawlers out there, while still allowing others to follow my lead on configuring Emacs and Email.
|
||||||
|
|
||||||
#+name: email-address-1
|
#+name: email-address-1
|
||||||
|
|
@ -54,19 +54,18 @@ The configuration files below expect email addresses (I store passwords and othe
|
||||||
(rot13-string "ubjneq@shmmlgbnfg.pbz")
|
(rot13-string "ubjneq@shmmlgbnfg.pbz")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
To use these, we set the =:noweb yes= (to pull in the /name/ of the code block) but put a pair of parens after the name to have it evaluated. For instance, the configuration for sending mail through the MUA in Emacs:
|
To use these, we set the =:noweb yes= (to pull in the /name/ of the code block) but put a pair of parens after the name to have it evaluated. For instance, the configuration for sending mail through the MUA in Emacs. Note that port 465 requires implicit SSL/TLS (not STARTTLS), so we set =smtpmail-stream-type= to ~ssl~ and let =auth-source= (e.g. =~/.authinfo.gpg=) supply the credentials for =<<email-address-1>>=:
|
||||||
|
|
||||||
#+begin_src emacs-lisp :noweb yes
|
#+begin_src emacs-lisp :noweb yes
|
||||||
(setq send-mail-function 'smtpmail-send-it
|
(setq send-mail-function 'smtpmail-send-it
|
||||||
message-send-mail-function 'smtpmail-send-it
|
message-send-mail-function 'smtpmail-send-it
|
||||||
smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
|
smtpmail-smtp-server "smtp.mailbox.org"
|
||||||
smtpmail-auth-credentials `(("smtp.gmail.com" 587 ,<<email-address-1>> nil))
|
smtpmail-smtp-service 465
|
||||||
smtpmail-default-smtp-server "smtp.gmail.com"
|
smtpmail-stream-type 'ssl)
|
||||||
smtpmail-smtp-server "smtp.gmail.com"
|
|
||||||
smtpmail-smtp-service 587)
|
|
||||||
#+end_src
|
#+end_src
|
||||||
* Sending Mail
|
* Sending Mail
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
(require 'smtpmail)
|
(require 'smtpmail)
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
* Installation and Basic Configuration
|
* Installation and Basic Configuration
|
||||||
|
|
@ -173,6 +172,7 @@ There are global settings:
|
||||||
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
||||||
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
||||||
Create Both
|
Create Both
|
||||||
|
Expunge Both
|
||||||
SyncState *
|
SyncState *
|
||||||
MaxMessages 100
|
MaxMessages 100
|
||||||
Sync All # New ReNew Flags
|
Sync All # New ReNew Flags
|
||||||
|
|
@ -184,37 +184,41 @@ The file generally can have a =Pass= entry for the encrypted passcode, but to sh
|
||||||
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
||||||
# PERSONAL ACCOUNT
|
# PERSONAL ACCOUNT
|
||||||
IMAPAccount personal
|
IMAPAccount personal
|
||||||
Host imap.gmail.com
|
|
||||||
User <<email-address-1()>> # Substitute your own email address here
|
|
||||||
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.mailpass-personal.gpg"
|
|
||||||
SSLType IMAPS
|
|
||||||
AuthMechs LOGIN
|
|
||||||
|
|
||||||
IMAPStore personal-remote
|
IMAPStore personal-remote
|
||||||
Account personal
|
Account personal
|
||||||
|
Host imap.mailbox.org
|
||||||
|
User <<email-address-1()>> # Substitute your own email address here
|
||||||
|
PassCmd "gpg --quiet --for-your-eyes-only --no-tty --decrypt ~/.mailpass-personal.gpg"
|
||||||
|
SSLType STARTTLS
|
||||||
|
|
||||||
MaildirStore personal-local
|
MaildirStore personal-local
|
||||||
Path ~/.mail/personal/
|
Path ~/.mail/personal/
|
||||||
Inbox ~/.mail/personal/INBOX
|
Inbox ~/.mail/personal/INBOX
|
||||||
Flatten .
|
SubFolders Verbatim
|
||||||
|
|
||||||
Channel personal-inbox
|
Channel mailbox
|
||||||
Far :personal-remote:
|
Master :mailbox-remote:
|
||||||
Near :personal-local:
|
Slave :mailbox-local:
|
||||||
Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
Patterns * !Archives
|
||||||
Expunge Both
|
|
||||||
# Patterns "inbox"
|
|
||||||
# ExpireUnread no
|
|
||||||
|
|
||||||
Channel personal-sent
|
## Channel personal-inbox
|
||||||
Far :personal-remote:"[Gmail]/Sent Mail"
|
## Far :personal-remote:
|
||||||
Near :personal-local:sent
|
## Near :personal-local:
|
||||||
ExpireUnread yes
|
## # Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
||||||
|
## Expunge Both
|
||||||
Channel personal-trash
|
## # Patterns "inbox"
|
||||||
Far :personal-remote:"[Gmail]/Trash"
|
## # ExpireUnread no
|
||||||
Near :personal-local:trash
|
##
|
||||||
ExpireUnread yes
|
## Channel personal-sent
|
||||||
|
## # Far :personal-remote:"[Gmail]/Sent Mail"
|
||||||
|
## Near :personal-local:sent
|
||||||
|
## ExpireUnread yes
|
||||||
|
##
|
||||||
|
## Channel personal-trash
|
||||||
|
## # Far :personal-remote:"[Gmail]/Trash"
|
||||||
|
## Near :personal-local:trash
|
||||||
|
## ExpireUnread yes
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
I have other email accounts that could use or ignore.
|
I have other email accounts that could use or ignore.
|
||||||
|
|
@ -743,7 +747,7 @@ The idea of linking org documents to email could be nice, however, the =ol-notmu
|
||||||
To use, read a message and save a link to it with ~SPC o l~. Next, in an org document, create a link with ~, l~. Now, you can return to the message from that document with ~, o~. Regardless, I may need to store a local copy when I upgrade Org.
|
To use, read a message and save a link to it with ~SPC o l~. Next, in an org document, create a link with ~, l~. Now, you can return to the message from that document with ~, o~. Regardless, I may need to store a local copy when I upgrade Org.
|
||||||
* Display Configuration
|
* Display Configuration
|
||||||
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(use-package doom-modeline
|
(use-package doom-modeline
|
||||||
:config
|
:config
|
||||||
(setq doom-modeline-mu4e t))
|
(setq doom-modeline-mu4e t))
|
||||||
|
|
|
||||||
10
ha-evil.org
10
ha-evil.org
|
|
@ -355,6 +355,16 @@ And the keybindings:
|
||||||
|
|
||||||
Why not use ~f~? I’m reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
|
Why not use ~f~? I’m reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet.
|
||||||
* Evil Extensions
|
* Evil Extensions
|
||||||
|
** Anzu
|
||||||
|
[[https://github.com/emacsorphanage/anzu][Anzu]] provides a minor mode which displays current match and total matches information in the mode-line in various search modes.
|
||||||
|
|
||||||
|
#+BEGIN_SRC emacs-lisp
|
||||||
|
(use-package anzu
|
||||||
|
:ensure t
|
||||||
|
:config
|
||||||
|
(global-anzu-mode +1))
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
** Evil Exchange
|
** Evil Exchange
|
||||||
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] project attempts to do something similar, but in a VI-way, and the /objects/ do not need to be adjacent.
|
I often use the Emacs commands, ~M-t~ and whatnot to exchange words and whatnot, but this requires a drop out of normal state mode. The [[https://github.com/Dewdrops/evil-exchange][evil-exchange]] project attempts to do something similar, but in a VI-way, and the /objects/ do not need to be adjacent.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -360,10 +360,15 @@ And the collection of useful operations:
|
||||||
"b C-g" '(keyboard-escape-quit :which-key t))
|
"b C-g" '(keyboard-escape-quit :which-key t))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Bookmarks
|
* 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
|
#+begin_src emacs-lisp
|
||||||
(use-package bookmark-in-project
|
(use-package bookmark-in-project
|
||||||
|
:bindd
|
||||||
|
(("C-x r m" . bookmark-in-project-toggle)
|
||||||
|
("C-x r M" . ha-bookmark-set))
|
||||||
:config
|
:config
|
||||||
(ha-leader
|
(ha-leader
|
||||||
;; Set or delete a bookmark associated with project:
|
;; 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 <down>" '("next mark" . bookmark-in-project-jump-next)
|
||||||
"b <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
"b <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
||||||
#+end_src
|
#+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
|
* 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.
|
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.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ And a quick shortcuts to call it:
|
||||||
#+end_src
|
#+end_src
|
||||||
* Display Configuration
|
* Display Configuration
|
||||||
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp :tangle no
|
||||||
(setq doom-modeline-irc t
|
(setq doom-modeline-irc t
|
||||||
doom-modeline-irc-stylize 'identity)
|
doom-modeline-irc-stylize 'identity)
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
#+author: Howard Abrams
|
#+author: Howard Abrams
|
||||||
#+date: 2024-07-07
|
#+date: 2024-07-07
|
||||||
#+filetags: emacs hamacs
|
#+filetags: emacs hamacs
|
||||||
#+lastmod: [2025-02-20 Thu]
|
#+lastmod: [2026-08-05 Wed]
|
||||||
|
|
||||||
A literate programming file for literate programming in Emacs Org Files.
|
A literate programming file for literate programming in Emacs Org Files.
|
||||||
|
|
||||||
|
|
@ -51,7 +51,7 @@ Since we are using Emacs, the downsides of using a literate approach (even for p
|
||||||
** Surround with Org Block
|
** Surround with Org Block
|
||||||
This simple change allows me to highlight any text, and surround it in an org block. We
|
This simple change allows me to highlight any text, and surround it in an org block. We
|
||||||
|
|
||||||
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/surround-org-block
|
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/surround-org-block :eval none
|
||||||
# name: surround-org-block
|
# name: surround-org-block
|
||||||
# --
|
# --
|
||||||
,#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
|
,#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
|
||||||
|
|
@ -90,7 +90,7 @@ Actually, I probably want all the parameters (if any) to come along for the ride
|
||||||
|
|
||||||
This allows us to now split a block into two parts:
|
This allows us to now split a block into two parts:
|
||||||
|
|
||||||
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block
|
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block :eval none
|
||||||
,# -*- mode: snippet -*-
|
,# -*- mode: snippet -*-
|
||||||
,# name: split-org-block
|
,# name: split-org-block
|
||||||
,# key: #split
|
,# key: #split
|
||||||
|
|
@ -712,7 +712,8 @@ Not every header should be a destination, as many of my org files have duplicate
|
||||||
"Install"
|
"Install"
|
||||||
"Overview"
|
"Overview"
|
||||||
"Summary"
|
"Summary"
|
||||||
"Technical Artifacts"))
|
"Technical Artifacts"
|
||||||
|
"[["))
|
||||||
"Regular expression matching headers to purge.")
|
"Regular expression matching headers to purge.")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
@ -777,12 +778,20 @@ Before we dive into the implementation of this function, let’s write a test to
|
||||||
'("Somefile∷ Parent﹥ Subparent﹥ Deep Heading"
|
'("Somefile∷ Parent﹥ Subparent﹥ Deep Heading"
|
||||||
"ha-somefile.org" 73)))
|
"ha-somefile.org" 73)))
|
||||||
|
|
||||||
|
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent" "Subby"))
|
||||||
|
|
||||||
|
(should (equal
|
||||||
|
(ha-hamacs-edit--process-entry
|
||||||
|
"ha-somefile.org:73:**** Deep Heading")
|
||||||
|
'("Somefile∷ Parent﹥ Subparent﹥ Subby﹥ Deep Heading"
|
||||||
|
"ha-somefile.org" 73)))
|
||||||
|
|
||||||
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent"
|
(setq ha-hamacs-edit-prev-head-list '("Parent" "Subparent"
|
||||||
"Subby" "Deepsubby"))
|
"Subby" "Deepsubby"))
|
||||||
(should (equal
|
(should (equal
|
||||||
(ha-hamacs-edit--process-entry
|
(ha-hamacs-edit--process-entry
|
||||||
"ha-somefile.org:73:***** Deepest Heading")
|
"ha-somefile.org:73:***** Deepest Heading")
|
||||||
'("Somefile∷ ... Deepest Heading"
|
'("Somefile∷ Parent﹥ Subparent﹥ Subby﹥ Deepsubby﹥ Deepest Heading"
|
||||||
"ha-somefile.org" 73))))
|
"ha-somefile.org" 73))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
#+author: Howard X. Abrams
|
#+author: Howard X. Abrams
|
||||||
#+date: 2020-09-25
|
#+date: 2020-09-25
|
||||||
#+tags: emacs work
|
#+tags: emacs work
|
||||||
|
|
||||||
A literate program for configuring org files for work-related notes.
|
A literate program for configuring org files for work-related notes.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :exports none
|
#+begin_src emacs-lisp :exports none
|
||||||
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
|
;;; org-sprint --- Configuring org files for work-related notes. -*- lexical-binding: t; -*-
|
||||||
;;
|
;;
|
||||||
|
|
@ -25,33 +23,51 @@ A literate program for configuring org files for work-related notes.
|
||||||
;;; Code:
|
;;; Code:
|
||||||
#+end_src
|
#+end_src
|
||||||
* Introduction
|
* Introduction
|
||||||
|
|
||||||
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
|
At the beginning of each Sprint, I create a new org file dedicated to it. This workflow/technique strikes a balance between a single ever-growing file, and a thousand little ones. This also gives me a sense of continuity, as the filename of each sprint is date-based.
|
||||||
|
|
||||||
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
|
I want a single keybinding that always displays the current Sprint note file, regardless of the Sprint. This means, I need to have functions that can calculate what this is.
|
||||||
|
|
||||||
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:templates/sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
|
To have the Org Capture features to be able to write to correct locations in the current file, I need each file to follow a particular format. I create a [[file:templates/sprint.org][sprint note template]] that will be automatically expanded with a new sprint.
|
||||||
|
|
||||||
This template needs the following functions:
|
This template needs the following functions:
|
||||||
- =sprint-current-name= to be both the numeric label as well as the nickname
|
- =sprint-current-name= to be both the numeric label as well as the nickname
|
||||||
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
|
- =sprint-date-range= to include a org-formatted date range beginning and ending the sprint
|
||||||
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
|
- =sprint-date-from-start= return a date for pre-scheduled and recurring meetings
|
||||||
|
|
||||||
* Naming Sprints
|
* Naming Sprints
|
||||||
|
|
||||||
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both… for myself.
|
I give each sprint a nickname, based on a /theme/ of some sorts, alphabetized. Since our sprints are every two weeks, this allows me to go through the alphabet once. Yeah, my group likes to boringly /number/ the sprints, so I do both… for myself.
|
||||||
|
|
||||||
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
|
At the beginning of the year, I choose a theme, and make a list for the upcoming sprints. In the org file, this is a list, that gets /tangled/ into an actual Emacs LIsp list. This is pretty cool.
|
||||||
|
#+begin_src emacs-lisp :var sprint-names=sprint-names-2026
|
||||||
#+begin_src emacs-lisp :var sprint-names=sprint-names-2025
|
|
||||||
(defvar sprint-nicknames sprint-names
|
(defvar sprint-nicknames sprint-names
|
||||||
"List of 26 Sprint Nicknames from A to Z.")
|
"List of 26 Sprint Nicknames from A to Z.")
|
||||||
#+end_src
|
#+end_src
|
||||||
** 2026
|
** 2026
|
||||||
Drunk animals? Everyday AI?
|
Migrating to a new team should include something different.
|
||||||
|
#+name: sprint-names-2026
|
||||||
|
- Alchemist’s Amber
|
||||||
|
- Bubblegloop Bogs
|
||||||
|
- Crumpet Crossroads
|
||||||
|
- Dandelion Drifters
|
||||||
|
- Elfin Espresso
|
||||||
|
- Firefly Fandango
|
||||||
|
- Giggling Goblins
|
||||||
|
- Hobgoblin Hideaway
|
||||||
|
- Inkwell Isles
|
||||||
|
- Jester’s Junction
|
||||||
|
- Knapsack Knolls
|
||||||
|
- Lavender Labyrinths
|
||||||
|
- Moonbeam Meadow
|
||||||
|
- Nectar Nibblers
|
||||||
|
- Owl’s Overlook
|
||||||
|
- Pixie Dust Pipeline
|
||||||
|
- Quizzical Quagmires
|
||||||
|
- Runaway Radishes
|
||||||
|
- Starfruit Springs
|
||||||
|
- Thimbleweed Thicket
|
||||||
|
- Unicorn Unwinding
|
||||||
|
- Velvet Valley
|
||||||
|
- Whistling Willows
|
||||||
|
- Xanadu Xylophones
|
||||||
|
- Yawning Yearlings
|
||||||
|
- Zephyr Zigzags
|
||||||
** 2025
|
** 2025
|
||||||
This year is the animals representing corporate slogans:
|
This year is the animals representing corporate slogans:
|
||||||
|
|
||||||
#+name: sprint-names-2025
|
#+name: sprint-names-2025
|
||||||
- Accelerated and Adaptive Ant
|
- Accelerated and Adaptive Ant
|
||||||
- Buy-in Bee and Blue-sky Bull
|
- Buy-in Bee and Blue-sky Bull
|
||||||
|
|
@ -110,7 +126,6 @@ How about Tabaxi names this year … especially since my next character has to b
|
||||||
- Zip in the Wind
|
- Zip in the Wind
|
||||||
** 2023
|
** 2023
|
||||||
How about a list of Ent names?
|
How about a list of Ent names?
|
||||||
|
|
||||||
#+name: sprint-names-2023
|
#+name: sprint-names-2023
|
||||||
- ashskin
|
- ashskin
|
||||||
- birchblossom
|
- birchblossom
|
||||||
|
|
@ -139,9 +154,7 @@ How about a list of Ent names?
|
||||||
- yewbiquitous
|
- yewbiquitous
|
||||||
- zelkova
|
- zelkova
|
||||||
** 2022
|
** 2022
|
||||||
|
|
||||||
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
|
Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://list.fandom.com/wiki/List_of_monsters][this list of monsters]] from mythology and other sources:
|
||||||
|
|
||||||
#+name: sprint-names-2022
|
#+name: sprint-names-2022
|
||||||
- ankheg
|
- ankheg
|
||||||
- beholder
|
- beholder
|
||||||
|
|
@ -171,7 +184,6 @@ Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://lis
|
||||||
- zombie
|
- zombie
|
||||||
** 2021
|
** 2021
|
||||||
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
||||||
|
|
||||||
#+name: sprint-names-2021
|
#+name: sprint-names-2021
|
||||||
- abibliophobia :: The fear of running out of reading materials to read
|
- abibliophobia :: The fear of running out of reading materials to read
|
||||||
- bamboozled :: To trick or confuse someone
|
- bamboozled :: To trick or confuse someone
|
||||||
|
|
@ -200,9 +212,7 @@ Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-wor
|
||||||
- yerk :: Pull or push something with a sudden movement.
|
- yerk :: Pull or push something with a sudden movement.
|
||||||
- zazzy :: Something that is shiny and flashy
|
- zazzy :: Something that is shiny and flashy
|
||||||
** 2020
|
** 2020
|
||||||
|
|
||||||
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
||||||
|
|
||||||
#+name: sprint-names-2020
|
#+name: sprint-names-2020
|
||||||
- ankylosaurus
|
- ankylosaurus
|
||||||
- brontosaurus
|
- brontosaurus
|
||||||
|
|
@ -230,11 +240,8 @@ New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list o
|
||||||
- xiaotingia
|
- xiaotingia
|
||||||
- yi
|
- yi
|
||||||
- zuul
|
- zuul
|
||||||
|
|
||||||
** 2019
|
** 2019
|
||||||
|
|
||||||
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
|
Came up with a list of somewhat well-known cities throughout the world (at least, they had to have a population of 100,000 or more), but I didn't want any real obvious ones.
|
||||||
|
|
||||||
#+name: sprint-names-2019
|
#+name: sprint-names-2019
|
||||||
- achy-aachen
|
- achy-aachen
|
||||||
- bare-bacabal
|
- bare-bacabal
|
||||||
|
|
@ -262,10 +269,8 @@ Came up with a list of somewhat well-known cities throughout the world (at least
|
||||||
- xenophobic-xichang
|
- xenophobic-xichang
|
||||||
- yellow-yamaguchi
|
- yellow-yamaguchi
|
||||||
- zippy-zinder
|
- zippy-zinder
|
||||||
|
|
||||||
* Sprint Boundaries
|
* Sprint Boundaries
|
||||||
Function to help in calculating dates and other features of a two-week sprint that starts on Tuesday and ends on a Monday… how we work at my job.
|
Function to help in calculating dates and other features of a two-week sprint that starts on Tuesday and ends on a Monday… how we work at my job.
|
||||||
|
|
||||||
Emacs have an internal rep of a time.
|
Emacs have an internal rep of a time.
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun get-date-time (date)
|
(defun get-date-time (date)
|
||||||
|
|
@ -279,44 +284,34 @@ Emacs have an internal rep of a time.
|
||||||
(apply 'encode-time))
|
(apply 'encode-time))
|
||||||
date))
|
date))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Sprint Numbering
|
** Sprint Numbering
|
||||||
|
|
||||||
Each year, specify the first day of the first sprint of the year:
|
Each year, specify the first day of the first sprint of the year:
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
#+BEGIN_SRC emacs-lisp
|
||||||
;; CHANGEME Each year as this should update:
|
;; CHANGEME Each year as this should update:
|
||||||
(defvar sprint-start-date (get-date-time "2026-01-06")
|
(defvar sprint-start-date (get-date-time "2026-01-06")
|
||||||
"The date of the first day of the first sprint of the year.
|
"The date of the first day of the first sprint of the year.
|
||||||
See `sprint-range'.")
|
See `sprint-range'.")
|
||||||
#+END_SRC
|
#+END_SRC
|
||||||
|
|
||||||
My company has sprints two weeks long that we can calculate from
|
My company has sprints two weeks long that we can calculate from
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defvar sprint-length (* 14 24 60 60)
|
(defvar sprint-length (* 14 24 60 60)
|
||||||
"The length of a sprint, in seconds. This is 2 weeks long")
|
"The length of a sprint, in seconds. This is 2 weeks long")
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The number of the sprint comes from the number of /bi-weeks/ (14 day increments) from the =sprint-start-date=. We can calculate the number of seconds from this /start date/ and divide it by the =sprint-length=:
|
The number of the sprint comes from the number of /bi-weeks/ (14 day increments) from the =sprint-start-date=. We can calculate the number of seconds from this /start date/ and divide it by the =sprint-length=:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-number (&optional date)
|
(defun sprint-number (&optional date)
|
||||||
"Return the number of 14-day intervals since SPRINT-START-DATE to DATE.
|
"Return the number of 14-day intervals since SPRINT-START-DATE to DATE.
|
||||||
DATE is a string in YYYY-MM-DD format."
|
DATE is a string in YYYY-MM-DD format."
|
||||||
(unless date
|
(unless date
|
||||||
(setq date (format-time-string "%Y-%m-%d")))
|
(setq date (format-time-string "%Y-%m-%d")))
|
||||||
|
|
||||||
(let* ((end-time (get-date-time date))
|
(let* ((end-time (get-date-time date))
|
||||||
(diff-seconds (float-time (time-subtract end-time sprint-start-date))))
|
(diff-seconds (float-time (time-subtract end-time sprint-start-date))))
|
||||||
|
|
||||||
;; After calculating the number of 'bi-weeks' (2 week sprint increments),
|
;; After calculating the number of 'bi-weeks' (2 week sprint increments),
|
||||||
;; we add one to return the correct sprint for the networking team:
|
;; we add one to return the correct sprint for the networking team:
|
||||||
;; (1+ (floor (/ diff-seconds sprint-length)))
|
;; (1+ (floor (/ diff-seconds sprint-length)))
|
||||||
;; Or for the platform storage team:
|
;; Or for the platform storage team:
|
||||||
(1+ (* 2 (floor (/ diff-seconds sprint-length))))))
|
(1+ (* 2 (floor (/ diff-seconds sprint-length))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And some tests to verify that:
|
And some tests to verify that:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-number-test ()
|
(ert-deftest sprint-number-test ()
|
||||||
|
|
@ -335,7 +330,6 @@ I create my org-file notes based on the Sprint number.
|
||||||
(format-time-string "%Y" d)
|
(format-time-string "%Y" d)
|
||||||
(sprint-number d)))))
|
(sprint-number d)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
So given a particular date, I should expect to be able to find the correct Sprint file name:
|
So given a particular date, I should expect to be able to find the correct Sprint file name:
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-current-file-test ()
|
(ert-deftest sprint-current-file-test ()
|
||||||
|
|
@ -344,9 +338,7 @@ So given a particular date, I should expect to be able to find the correct Sprin
|
||||||
(should (s-ends-with? "Sprint-2024-13.org" (sprint-current-file "2024-02-01")))
|
(should (s-ends-with? "Sprint-2024-13.org" (sprint-current-file "2024-02-01")))
|
||||||
(should (s-ends-with? "Sprint-2024-14.org" (sprint-current-file "2024-02-13"))))
|
(should (s-ends-with? "Sprint-2024-14.org" (sprint-current-file "2024-02-13"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-current-find-file (&optional date)
|
(defun sprint-current-find-file (&optional date)
|
||||||
"Load the `org-mode' note associated with my current sprint."
|
"Load the `org-mode' note associated with my current sprint."
|
||||||
|
|
@ -357,33 +349,32 @@ Daily note-taking goes into my sprint file notes, so this interactive function m
|
||||||
(add-to-list 'org-agenda-files filename)
|
(add-to-list 'org-agenda-files filename)
|
||||||
(find-file filename)))
|
(find-file filename)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
|
The /name/ and /nickname/ of the sprint will be used in the =#+TITLE= section, and it looks something like: =Sprint 2019-07 (darling-dadu)=
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-current-name (&optional date)
|
(defun sprint-current-name (&optional date)
|
||||||
"Return the default name of the current sprint (based on DATE)."
|
"Return the default name of the current sprint (based on DATE)."
|
||||||
(let* ((d (get-date-time date))
|
(let* ((d (get-date-time date))
|
||||||
(sprint-num (sprint-number d))
|
(sprint-num (sprint-number d))
|
||||||
(nickname (nth (1- sprint-num) sprint-nicknames)))
|
(nickname (nth (/ (1- sprint-num) 2) sprint-nicknames)))
|
||||||
(format "Sprint %s-%02d :: %s"
|
(format "Sprint %s-%02d :: %s"
|
||||||
(format-time-string "%Y" d)
|
(format-time-string "%Y" d)
|
||||||
(sprint-number d)
|
(sprint-number d)
|
||||||
nickname)))
|
nickname)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
These test won't pass any more, as the nickname of the sprint changes from year to year.
|
Current sprint name:
|
||||||
|
#+BEGIN_SRC emacs-lisp :tangle no
|
||||||
|
(sprint-current-name)
|
||||||
|
#+END_SRC
|
||||||
|
|
||||||
|
These test won't pass any more, as the nickname of the sprint changes from year to year.
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-current-name-test ()
|
(ert-deftest sprint-current-name-test ()
|
||||||
(should (equal "Sprint 2024-01 :: Art of Shadows" (sprint-current-name "2024-01-02")))
|
(should (equal "Sprint 2024-01 :: Art of Shadows" (sprint-current-name "2024-01-02")))
|
||||||
(should (equal "Sprint 2024-04 :: Daydream at Night" (sprint-current-name "2024-02-14"))))
|
(should (equal "Sprint 2024-04 :: Daydream at Night" (sprint-current-name "2024-02-14"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Sprint Start and End
|
** Sprint Start and End
|
||||||
|
|
||||||
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first Tuesday/ of the year, so I need to begin the year changing this value. I should fix this.
|
I want to print the beginning and ending of the sprint, where we have a sprint number or a data, and we can give the dates that bound the sprint. This odd function calculates this based on knowing the date of the /first Tuesday/ of the year, so I need to begin the year changing this value. I should fix this.
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-range (&optional number-or-date)
|
(defun sprint-range (&optional number-or-date)
|
||||||
"Return a list of three entries, start of the current sprint,
|
"Return a list of three entries, start of the current sprint,
|
||||||
|
|
@ -401,9 +392,7 @@ I want to print the beginning and ending of the sprint, where we have a sprint n
|
||||||
(sprint-end (time-add sprint-next (- day-length))))
|
(sprint-end (time-add sprint-next (- day-length))))
|
||||||
(list sprint-start sprint-end sprint-next)))
|
(list sprint-start sprint-end sprint-next)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Format the start and end so that we can insert this directly in the org file:
|
Format the start and end so that we can insert this directly in the org file:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-date-range (&optional number-or-date)
|
(defun sprint-date-range (&optional number-or-date)
|
||||||
"Return `org-mode' formatted date range for a given sprint.
|
"Return `org-mode' formatted date range for a given sprint.
|
||||||
|
|
@ -415,7 +404,6 @@ Format the start and end so that we can insert this directly in the org file:
|
||||||
(end (format-time-string formatter sprint-end)))
|
(end (format-time-string formatter sprint-end)))
|
||||||
(format "[%s]--[%s]" start end))))
|
(format "[%s]--[%s]" start end))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
And validate with a test:
|
And validate with a test:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(ert-deftest sprint-date-range ()
|
(ert-deftest sprint-date-range ()
|
||||||
|
|
@ -430,11 +418,8 @@ And validate with a test:
|
||||||
(should (equal (sprint-date-range 5)
|
(should (equal (sprint-date-range 5)
|
||||||
(sprint-date-range "2024-01-31"))))
|
(sprint-date-range "2024-01-31"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** Pre-scheduled Dates
|
** Pre-scheduled Dates
|
||||||
|
|
||||||
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
|
Due to the regularity of the sprint cadence, I can pre-schedule meetings and other deadlines by /counting/ the number of days from the start of the sprint:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defun sprint-date-from-start (days &optional formatter)
|
(defun sprint-date-from-start (days &optional formatter)
|
||||||
"Return formatted date string from number of DAYS from the start of the sprint."
|
"Return formatted date string from number of DAYS from the start of the sprint."
|
||||||
|
|
@ -445,18 +430,14 @@ Due to the regularity of the sprint cadence, I can pre-schedule meetings and oth
|
||||||
(format-time-string formatter adate)
|
(format-time-string formatter adate)
|
||||||
(format-time-string "%Y-%m-%d %a" adate))))
|
(format-time-string "%Y-%m-%d %a" adate))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Other Date Functions
|
* Other Date Functions
|
||||||
|
|
||||||
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-num-days (time-interval)
|
(defun sprint-num-days (time-interval)
|
||||||
"Converts a TIME-INTERVAL to a number of days."
|
"Converts a TIME-INTERVAL to a number of days."
|
||||||
(let ((day-length (* 3600 24)))
|
(let ((day-length (* 3600 24)))
|
||||||
(round (/ (float-time time-interval) day-length))))
|
(round (/ (float-time time-interval) day-length))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-day-range (&optional date)
|
(defun sprint-day-range (&optional date)
|
||||||
"Returns a list of two values, the number of days from the
|
"Returns a list of two values, the number of days from the
|
||||||
|
|
@ -468,7 +449,6 @@ The following functions /were/ helpful at times. But I'm not sure I will use the
|
||||||
(ending (time-subtract sprint-end now)))
|
(ending (time-subtract sprint-end now)))
|
||||||
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(ert-deftest sprint-day-range ()
|
(ert-deftest sprint-day-range ()
|
||||||
;; This sprint starts on 2/13 and ends on 2/26
|
;; This sprint starts on 2/13 and ends on 2/26
|
||||||
|
|
@ -476,33 +456,25 @@ The following functions /were/ helpful at times. But I'm not sure I will use the
|
||||||
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
|
(should (equal '(-1 12) (sprint-day-range "2020-02-14")))
|
||||||
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
|
(should (equal '(-13 0) (sprint-day-range "2020-02-26"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp :tangle no
|
||||||
(defun sprint-day-start (&optional date)
|
(defun sprint-day-start (&optional date)
|
||||||
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Tuesday, this would return -1."
|
"Return a relative number of days to the start of the current sprint. For instance, if today was Friday, and the sprint started on Tuesday, this would return -1."
|
||||||
(first (sprint-day-range date)))
|
(first (sprint-day-range date)))
|
||||||
|
|
||||||
(defun sprint-day-end (&optional date)
|
(defun sprint-day-end (&optional date)
|
||||||
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Monday, this would return 3."
|
"Return a relative number of days to the end of the current sprint. For instance, if today was Monday, and the sprint will end on Monday, this would return 3."
|
||||||
(second (sprint-day-range date)))
|
(second (sprint-day-range date)))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
#+begin_src emacs-lisp :exports none
|
#+begin_src emacs-lisp :exports none
|
||||||
(provide 'ha-org-sprint)
|
(provide 'ha-org-sprint)
|
||||||
;;; ha-org-sprint.el ends here
|
;;; ha-org-sprint.el ends here
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
Before you can build this on a new system, make sure that you put the cursor over any of these properties, and hit: ~C-c C-c~
|
||||||
|
|
||||||
#+description: A literate program for configuring org files for work-related notes.
|
#+description: A literate program for configuring org files for work-related notes.
|
||||||
|
|
||||||
#+property: header-args:sh :tangle no
|
#+property: header-args:sh :tangle no
|
||||||
#+property: header-args:emacs-lisp :tangle yes
|
#+property: header-args:emacs-lisp :tangle yes
|
||||||
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
#+property: header-args :results none :eval no-export :comments no mkdirp yes
|
||||||
|
|
||||||
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
#+options: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||||||
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
#+options: skip:nil author:nil email:nil creator:nil timestamp:nil
|
||||||
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
#+infojs_opt: view:nil toc:t ltoc:t mouse:underline buttons:0 path:http://orgmode.org/org-info.js
|
||||||
|
|
|
||||||
|
|
@ -338,11 +338,12 @@ The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a
|
||||||
(org-modern-table nil)
|
(org-modern-table nil)
|
||||||
(org-pretty-entities t)
|
(org-pretty-entities t)
|
||||||
(setq org-modern-fold-stars '(("▸" . "▾")
|
(setq org-modern-fold-stars '(("▸" . "▾")
|
||||||
("▹" . "▿")
|
("▹" . "▿")
|
||||||
("▸" . "▾")
|
("▸" . "▾")
|
||||||
("▹" . "▿")
|
("▹" . "▿")
|
||||||
("▸" . "▾")))
|
("▸" . "▾")))
|
||||||
:config
|
:config
|
||||||
|
(set-face-attribute 'org-modern-todo nil :inverse-video nil)
|
||||||
(set-face-attribute 'org-ellipsis nil :inherit 'default :box nil :underline nil)
|
(set-face-attribute 'org-ellipsis nil :inherit 'default :box nil :underline nil)
|
||||||
(set-face-attribute 'org-modern-symbol nil :family "Iosevka"))
|
(set-face-attribute 'org-modern-symbol nil :family "Iosevka"))
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
#+date: 2020-09-18
|
#+date: 2020-09-18
|
||||||
#+tags: emacs org
|
#+tags: emacs org
|
||||||
#+startup: inlineimages
|
#+startup: inlineimages
|
||||||
#+lastmod: [2026-04-08 Wed]
|
#+lastmod: [2026-07-15 Wed]
|
||||||
|
|
||||||
A literate programming file for configuring org-mode and those files.
|
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,
|
org-edit-src-content-indentation 2 ; Doom Emacs sets this to 0,
|
||||||
; but uses a trick to make it
|
; but uses a trick to make it
|
||||||
; appear indented.
|
; appear indented.
|
||||||
|
org-use-sub-superscripts '{}
|
||||||
|
|
||||||
org-imenu-depth 4
|
org-imenu-depth 4
|
||||||
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
||||||
|
|
|
||||||
|
|
@ -318,85 +318,6 @@ This allows me to delete a Python “block” using ~dal~.
|
||||||
("Misc"
|
("Misc"
|
||||||
(("t" python-tests/body "Tests..."))))))
|
(("t" python-tests/body "Tests..."))))))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Elpy
|
|
||||||
The [[https://elpy.readthedocs.io/en/latest/introduction.html][Elpy Project]] expands on the =python-mode=.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package elpy
|
|
||||||
:init
|
|
||||||
(advice-add 'python-mode :before 'elpy-enable)
|
|
||||||
:config
|
|
||||||
;; (elpy-enable)
|
|
||||||
(setq elpy-test-runner 'elpy-test-pytest-runner)
|
|
||||||
(setq elpy-formatter 'black)
|
|
||||||
(setq elpy-shell-echo-input nil)
|
|
||||||
(setq elpy-modules (delq 'elpy-module-flymake elpy-modules)))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Elpy uses its *own* virtual environment for its backend process (jedi, rope, etc.), keeping elpy's own dependencies out of your project venvs. While there is a [[help:elpy-rpc-reinstall-virtualenv][elpy-rpc-reinstall-virtualenv]] function, I find it doesn’t seem to install everything correctly, and I need to do this by hand:
|
|
||||||
|
|
||||||
#+BEGIN_SRC sh
|
|
||||||
source ~/.emacs.d/elpy/rpc-venv/bin/activate
|
|
||||||
pip install jedi black flake8 ipython
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
After we’ve loaded the Company section, we can add jedi to the list of completions:
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp
|
|
||||||
(use-package elpy
|
|
||||||
:after company-mode
|
|
||||||
:config (add-to-list 'company-backends 'company-jedi))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Let’s expand our =major-mode-hydra= with some extras:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package major-mode-hydra
|
|
||||||
:after elpy
|
|
||||||
:config
|
|
||||||
|
|
||||||
(pretty-hydra-define python-evaluate (:color blue :quit-key "q"
|
|
||||||
:title ha-python-eval-title)
|
|
||||||
("Section"
|
|
||||||
(("F" elpy-shell-send-defun "Function")
|
|
||||||
("E" elpy-shell-send-statement "Statement")
|
|
||||||
(";" python-shell-send-string "Expression"))
|
|
||||||
"Entirety"
|
|
||||||
(("B" elpy-shell-send-buffer "Buffer")
|
|
||||||
("r" elpy-shell-send-region-or-buffer "region"))
|
|
||||||
"And Step..."
|
|
||||||
(("f" elpy-shell-send-defun-and-step "Function" :color pink)
|
|
||||||
("e" elpy-shell-send-statement-and-step "Statement" :color pink))))
|
|
||||||
|
|
||||||
(pretty-hydra-define+ python-refactor nil
|
|
||||||
("Elpy"
|
|
||||||
(("r" elpy-refactor-rename "Rename")
|
|
||||||
("i" elpy-refactor-inline "Inline var")
|
|
||||||
("v" elpy-refactor-extract-variable "To variable")
|
|
||||||
("f" elpy-refactor-extract-function "To function")
|
|
||||||
("a" elpy-refactor-mode "All..."))))
|
|
||||||
|
|
||||||
(major-mode-hydra-define+ python-mode (:quit-key "q" :color blue)
|
|
||||||
("Server"
|
|
||||||
(("s" elpy-shell-switch-to-shell "Go to Server")
|
|
||||||
("C" elpy-config "Config Elpy"))
|
|
||||||
"Edit"
|
|
||||||
(("f" elpy-black-fix-code "Fix/format code"))
|
|
||||||
"Docs"
|
|
||||||
(("d" elpy-eldoc-documentation "Describe Symbol")
|
|
||||||
("D" elpy-doc "Docs Symbol")))))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
* Anaconda
|
|
||||||
The [[https://github.com/pythonic-emacs/anaconda-mode][anaconda-mode project]] seems as good as Elpy, but also include Evil keybindings.
|
|
||||||
|
|
||||||
#+BEGIN_SRC emacs-lisp :tangle no
|
|
||||||
(use-mode anaconda-mode
|
|
||||||
:hook ((python-mode . anaconda-mode)
|
|
||||||
(python-mode ../ anaconda-eldoc-mode)))
|
|
||||||
#+END_SRC
|
|
||||||
|
|
||||||
Since we are using
|
|
||||||
* LSP Integration of Python
|
* LSP Integration of Python
|
||||||
** Dependencies
|
** Dependencies
|
||||||
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=):
|
Each Python project's =requirements-dev.txt= file would reference the [[https://pypi.org/project/python-lsp-server/][python-lsp-server]] (not the /unmaintained/ project, =python-language-server=):
|
||||||
|
|
@ -422,100 +343,60 @@ Each Python project's =requirements-dev.txt= file would reference the [[https://
|
||||||
# ...
|
# ...
|
||||||
#+end_src
|
#+end_src
|
||||||
** Pyright
|
** Pyright
|
||||||
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
|
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead, adding it to the global Python scope.
|
||||||
|
|
||||||
#+begin_src conf :tangle no
|
#+begin_src conf :tangle no
|
||||||
pyright
|
pyright
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
|
While the [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP, this isn’t needed, as =eglot= works directly with it.
|
||||||
|
** eglot
|
||||||
|
The built-in Eglot package natively supports [[https://github.com/Microsoft/pyright][pyright]] via =eglot-server-programs= (no extra package needed):
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
#+begin_src emacs-lisp
|
||||||
(use-package lsp-pyright
|
(use-package eglot
|
||||||
:hook (python-mode . (lambda () (require 'lsp-pyright)))
|
:straight nil
|
||||||
:init (when (executable-find "python3")
|
:after python
|
||||||
(setq lsp-pyright-python-executable-cmd "python3")))
|
:defer t
|
||||||
|
:hook (python-mode . eglot-ensure)
|
||||||
|
:config
|
||||||
|
(add-to-list 'eglot-server-programs
|
||||||
|
'((python-mode python-ts-mode) . ("pyright-langserver" "--stdio"))))
|
||||||
#+end_src
|
#+end_src
|
||||||
*** Keybindings
|
|
||||||
Now that the [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP Integration]] is complete, we can stitch the two projects together, by calling =lsp=. I oscillate between automatically turning on LSP mode with every Python file, but I sometimes run into issues when starting, so I conditionally turn it on.
|
Keybindings for the eglot-based LSP sub-menu, wired into the existing =python-mode= hydra:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(defvar ha-python-lsp-title (font-icons 'faicon "python" :title "Python LSP"))
|
(defvar ha-python-lsp-title (font-icons 'faicon "python" :title "Python LSP"))
|
||||||
|
|
||||||
(defun ha-setup-python-lsp ()
|
(use-package eglot
|
||||||
"Configure the keybindings for LSP in Python."
|
:straight nil
|
||||||
(interactive)
|
:after major-mode-hydra
|
||||||
|
:config
|
||||||
(pretty-hydra-define python-lsp (:color blue :quit-key "q"
|
(pretty-hydra-define python-lsp (:color blue :quit-key "q"
|
||||||
:title ha-python-lsp-title)
|
:title ha-python-lsp-title)
|
||||||
("Server"
|
("Server"
|
||||||
(("D" lsp-disconnect "Disconnect")
|
(("D" eglot-shutdown "Disconnect")
|
||||||
("R" lsp-workspace-restart "Restart")
|
("R" eglot-reconnect "Reconnect")
|
||||||
("S" lsp-workspace-shutdown "Shutdown")
|
("?" eglot-show-workspace-configuration "Describe"))
|
||||||
("?" lsp-describe-session "Describe"))
|
|
||||||
"Refactoring"
|
"Refactoring"
|
||||||
(("a" lsp-execute-code-action "Code Actions")
|
(("a" eglot-code-actions "Code Actions")
|
||||||
("o" lsp-organize-imports "Organize Imports")
|
("o" eglot-code-action-organize-imports "Organize Imports")
|
||||||
("l" lsp-avy-lens "Avy Lens"))
|
("r" eglot-rename "Rename"))
|
||||||
"Toggles"
|
"Navigation"
|
||||||
(("b" lsp-headerline-breadcrumb-mode "Breadcrumbs")
|
(("." xref-find-definitions "Definition")
|
||||||
("d" lsp-ui-doc-mode "Documentation Popups")
|
("/" xref-find-references "References")
|
||||||
("m" lsp-modeline-diagnostics-mode "Modeline Diagnostics")
|
("t" eglot-find-typeDefinition "Type Definition")
|
||||||
("s" lsp-ui-sideline-mode "Sideline Mode"))
|
("i" eglot-find-implementation "Implementation"))
|
||||||
""
|
|
||||||
(("t" lsp-toggle-on-type-formatting "Type Formatting")
|
|
||||||
("h" lsp-toggle-symbol-highlight "Symbol Highlighting")
|
|
||||||
("L" lsp-toggle-trace-io "Log I/O"))))
|
|
||||||
|
|
||||||
(pretty-hydra-define+ python-goto (:quit-key "q")
|
|
||||||
("LSP"
|
|
||||||
(("g" lsp-find-definition "Definition")
|
|
||||||
("d" lsp-find-declaration "Declaration")
|
|
||||||
("r" lsp-find-references "References")
|
|
||||||
("t" lsp-find-type-definition "Type Definition"))
|
|
||||||
"Peek"
|
|
||||||
(("D" lsp-ui-peek-find-definitions "Definitions")
|
|
||||||
("I" lsp-ui-peek-find-implementation "Implementations")
|
|
||||||
("R" lsp-ui-peek-find-references "References")
|
|
||||||
("S" lsp-ui-peek-find-workspace-symbol "Symbols"))
|
|
||||||
"LSP+"
|
|
||||||
(("u" lsp-ui-imenu "UI Menu")
|
|
||||||
("i" lsp-find-implementation "Implementations")
|
|
||||||
("h" lsp-treemacs-call-hierarchy "Hierarchy")
|
|
||||||
("E" lsp-treemacs-errors-list "Error List"))))
|
|
||||||
|
|
||||||
(major-mode-hydra-define+ python-mode nil
|
|
||||||
("Server"
|
|
||||||
(("l" python-lsp/body "LSP..."))
|
|
||||||
"Edit"
|
|
||||||
(("=" lsp-format-region "Format"))
|
|
||||||
"Navigate"
|
|
||||||
(("A" lsp-workspace-folders-add "Add Folder")
|
|
||||||
("R" lsp-workspace-folders-remove "Remove Folder"))
|
|
||||||
"Docs"
|
"Docs"
|
||||||
(("D" lsp-describe-thing-at-point "Describe LSP Symbol")
|
(("d" eldoc-doc-buffer "Eldoc Buffer")
|
||||||
("h" lsp-ui-doc-glance "Glance Help")
|
("h" eglot-inlay-hints-mode "Inlay Hints")
|
||||||
("H" lsp-document-highlight "Highlight"))))
|
("=" eglot-format-buffer "Format Buffer"))))
|
||||||
|
|
||||||
(call-interactively 'lsp))
|
|
||||||
|
|
||||||
(use-package lsp-mode
|
|
||||||
:config
|
|
||||||
(major-mode-hydra-define+ python-mode (:quit-key "q")
|
(major-mode-hydra-define+ python-mode (:quit-key "q")
|
||||||
("Server"
|
("Server"
|
||||||
(("L" ha-setup-python-lsp "Start LSP Server")))))
|
( ;; ("L" eglot-ensure "Start eglot") --> Not needed with eglot-ensure
|
||||||
|
("l" python-lsp/body "LSP...")))))
|
||||||
;; ----------------------------------------------------------------------
|
|
||||||
;; Missing Symbols to be integrated?
|
|
||||||
;; "0" '("treemacs" . lsp-treemacs-symbols)
|
|
||||||
;; "/" '("complete" . completion-at-point)
|
|
||||||
;; "k" '("check code" . python-check)
|
|
||||||
;; "Fb" '("un-blacklist folder" . lsp-workspace-blacklist-remove)
|
|
||||||
;; "hs" '("signature help" . lsp-signature-activate)
|
|
||||||
;; "tT" '("toggle treemacs integration" . lsp-treemacs-sync-mode)
|
|
||||||
;; "ta" '("toggle modeline code actions" . lsp-modeline-code-actions-mode)
|
|
||||||
;; "th" '("toggle highlighting" . lsp-toggle-symbol-highlight)
|
|
||||||
;; "tl" '("toggle lenses" . lsp-lens-mode)
|
|
||||||
;; "ts" '("toggle signature" . lsp-toggle-signature-auto-activate)
|
|
||||||
#+end_src
|
#+end_src
|
||||||
* Project Configuration
|
* Project Configuration
|
||||||
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/:
|
I work with a lot of projects with my team where I need to /configure/ the project such that LSP and my Emacs setup works. Let's suppose I could point a function at a project directory, and have it /set it up/:
|
||||||
|
|
@ -541,17 +422,9 @@ I work with a lot of projects with my team where I need to /configure/ the proje
|
||||||
(shell-command "pipconf --local")
|
(shell-command "pipconf --local")
|
||||||
(shell-command "pip install --upgrade pip"))
|
(shell-command "pip install --upgrade pip"))
|
||||||
|
|
||||||
(message "Configuring pip for LSP")
|
(message "Configuring pip for eglot/pyright")
|
||||||
(with-temp-file "requirements-dev.txt"
|
(with-temp-file "requirements-dev.txt"
|
||||||
(insert "python-lsp-server[all]\n")
|
(insert "pyright\n"))
|
||||||
|
|
||||||
;; Let's install these extra packages individually ...
|
|
||||||
(insert "pyls-flake8\n")
|
|
||||||
;; (insert "pylsp-mypy")
|
|
||||||
;; (insert "pyls-isort")
|
|
||||||
;; (insert "python-lsp-black")
|
|
||||||
;; (insert "pyls-memestra")
|
|
||||||
(insert "pylsp-rope\n"))
|
|
||||||
(shell-command "pip install -r requirements-dev.txt")))
|
(shell-command "pip install -r requirements-dev.txt")))
|
||||||
#+end_src
|
#+end_src
|
||||||
* Major Mode Hydra
|
* Major Mode Hydra
|
||||||
|
|
|
||||||
|
|
@ -229,37 +229,6 @@ The [[https://github.com/r0man/ruby-test-mode][ruby-test-mode]] project aims a r
|
||||||
"t A" '("test all" . ruby-test-run)
|
"t A" '("test all" . ruby-test-run)
|
||||||
"t a" '("retest" . ruby-test-rerun)))
|
"t a" '("retest" . ruby-test-rerun)))
|
||||||
#+end_src
|
#+end_src
|
||||||
** Robe
|
|
||||||
The [[https://github.com/dgutov/robe][Robe project]] can be used instead of [[file:ha-programming.org::*Language Server Protocol (LSP) Integration][LSP]].
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package robe
|
|
||||||
|
|
||||||
:config
|
|
||||||
(ha-local-leader 'ruby-mode-map
|
|
||||||
"w" '(:ignore t :which-key "robe")
|
|
||||||
"ws" '("start" . robe-start))
|
|
||||||
|
|
||||||
;; The following leader-like keys, are only available when I have
|
|
||||||
;; started LSP, and is an alternate to Command-m:
|
|
||||||
:general
|
|
||||||
(:states 'normal :keymaps 'robe-mode-map
|
|
||||||
", w r" '("restart" . lsp-reconnect)
|
|
||||||
", w b" '("events" . lsp-events-buffer)
|
|
||||||
", w e" '("errors" . lsp-stderr-buffer)
|
|
||||||
", w q" '("quit" . lsp-shutdown)
|
|
||||||
", w l" '("load file" . ruby-load-file)
|
|
||||||
|
|
||||||
", l r" '("rename" . lsp-rename)
|
|
||||||
", l f" '("format" . lsp-format)
|
|
||||||
", l a" '("actions" . lsp-code-actions)
|
|
||||||
", l i" '("imports" . lsp-code-action-organize-imports)
|
|
||||||
", l d" '("doc" . lsp-lookup-documentation)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Do we want to load Robe /automatically/?
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package robe :hook (ruby-mode . robe-mode))
|
|
||||||
#+end_src
|
|
||||||
** Bundler
|
** Bundler
|
||||||
The [[https://github.com/endofunky/bundler.el][Bundler project]] integrates [[https://bundler.io/][bundler]] to install a projects Gems.
|
The [[https://github.com/endofunky/bundler.el][Bundler project]] integrates [[https://bundler.io/][bundler]] to install a projects Gems.
|
||||||
|
|
||||||
|
|
@ -295,6 +264,34 @@ Seems that to understand and edit Cucumber /feature/ definitions, you need [[htt
|
||||||
** RSpec
|
** RSpec
|
||||||
https://github.com/pezra/rspec-mode
|
https://github.com/pezra/rspec-mode
|
||||||
* LSP
|
* LSP
|
||||||
|
The built-in Eglot package natively supports [[https://github.com/castwide/solargraph][Solargraph]] via =eglot-server-programs= (no extra package needed):
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package eglot
|
||||||
|
:straight nil
|
||||||
|
:after ruby-mode
|
||||||
|
:defer t
|
||||||
|
:hook (ruby-mode . eglot-ensure))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
|
Keybindings for the eglot-based LSP sub-menu, wired into the =ruby-mode= local leader:
|
||||||
|
|
||||||
|
#+begin_src emacs-lisp
|
||||||
|
(use-package eglot
|
||||||
|
:straight nil
|
||||||
|
:after ruby-mode
|
||||||
|
:config
|
||||||
|
(ha-local-leader 'ruby-mode-map
|
||||||
|
"w" '(:ignore t :which-key "eglot")
|
||||||
|
"wD" '("disconnect" . eglot-shutdown)
|
||||||
|
"wR" '("reconnect" . eglot-reconnect)
|
||||||
|
"w?" '("describe" . eglot-show-workspace-configuration)
|
||||||
|
"wa" '("code actions" . eglot-code-actions)
|
||||||
|
"wo" '("organize imports" . eglot-code-action-organize-imports)
|
||||||
|
"wr" '("rename" . eglot-rename)
|
||||||
|
"w=" '("format buffer" . eglot-format-buffer)))
|
||||||
|
#+end_src
|
||||||
|
|
||||||
Need to install [[https://github.com/castwide/solargraph][Solargraph]] for the LSP server experience:
|
Need to install [[https://github.com/castwide/solargraph][Solargraph]] for the LSP server experience:
|
||||||
#+begin_src sh
|
#+begin_src sh
|
||||||
gem install solargraph
|
gem install solargraph
|
||||||
|
|
@ -303,29 +300,7 @@ Or add it to your =Gemfile=:
|
||||||
#+begin_src ruby
|
#+begin_src ruby
|
||||||
gem 'solargraph', group: :development
|
gem 'solargraph', group: :development
|
||||||
#+end_src
|
#+end_src
|
||||||
* XRef Interface with GNU Global
|
Once installed, [[*eglot][eglot]] finds and talks to =solargraph= automatically; no further Emacs configuration is needed.
|
||||||
The [[http://www.gnu.org/software/global/][GNU Global]] has the ability to generate a tags file for large, multi-project Ruby code bases.
|
|
||||||
|
|
||||||
First, issue these two:
|
|
||||||
#+begin_src sh :dir ~/work/gourmet
|
|
||||||
find . -name .git | while read DOTGIT
|
|
||||||
do
|
|
||||||
REPO=$(dirname $DOTGIT)
|
|
||||||
(cd $REPO && git pull origin master)
|
|
||||||
done
|
|
||||||
|
|
||||||
find . -name "*.rb" > gtags.files
|
|
||||||
gtags --gtagslabel=new-ctags --file gtags.files
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
And now we need the GNU Global for Emacs, we are using the most up-to-date version of [[https://github.com/leoliu/ggtags][ggtags]].
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package ggtags
|
|
||||||
:hook ((ruby-mode . #'ggtags-mode)))
|
|
||||||
#+end_src
|
|
||||||
|
|
||||||
Careful observers will note that
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
|
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
|
||||||
|
|
@ -68,24 +68,10 @@ The [[https://github.com/brotzeit/rustic][rustic project]] incorporates [[https:
|
||||||
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package rustic
|
(use-package rustic
|
||||||
:init
|
:config
|
||||||
(setq rustic-lsp-client 'eglot
|
(setq rustic-lsp-client 'eglot
|
||||||
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer"))
|
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer")
|
||||||
;; :bind (:map rustic-mode-map
|
rustic-format-on-save t))
|
||||||
;; ("M-j" . lsp-ui-imenu)
|
|
||||||
;; ("M-?" . lsp-find-references)
|
|
||||||
;; ("C-c C-c l" . flycheck-list-errors)
|
|
||||||
;; ("C-c C-c a" . lsp-execute-code-action)
|
|
||||||
;; ("C-c C-c r" . lsp-rename)
|
|
||||||
;; ("C-c C-c q" . lsp-workspace-restart)
|
|
||||||
;; ("C-c C-c Q" . lsp-workspace-shutdown)
|
|
||||||
;; ("C-c C-c s" . lsp-rust-analyzer-status)
|
|
||||||
;; ("C-c C-c e" . lsp-rust-analyzer-expand-macro)
|
|
||||||
;; ("C-c C-c d" . dap-hydra)
|
|
||||||
;; ("C-c C-c h" . lsp-ui-doc-glance))
|
|
||||||
|
|
||||||
:config
|
|
||||||
(setq rustic-format-on-save t))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
|
|
||||||
** The Playground
|
** The Playground
|
||||||
|
|
@ -102,7 +88,6 @@ The [[https://github.com/dryman/toml-mode.el][toml-mode]] adds syntax highlighti
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package toml-mode)
|
(use-package toml-mode)
|
||||||
#+end_src
|
#+end_src
|
||||||
* LSP
|
|
||||||
* Technical Artifacts :noexport:
|
* Technical Artifacts :noexport:
|
||||||
Let's =provide= a name so we can =require= this file:
|
Let's =provide= a name so we can =require= this file:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -683,86 +683,21 @@ I have two different /jumping/ systems, the [[info:emacs#Xref][Xref interface]]
|
||||||
The [[https://microsoft.github.io/language-server-protocol/][LSP]] is a way to connect /editors/ (like Emacs) to /languages/ (like Lisp)… wait, no. While originally designed for VS Code and probably Python, we can abstract away [[https://github.com/davidhalter/jedi][Jedi]] and the [[http://tkf.github.io/emacs-jedi/latest/][Emacs integration to Jedi]] (and duplicate everything for Ruby, and Clojure, and…).
|
The [[https://microsoft.github.io/language-server-protocol/][LSP]] is a way to connect /editors/ (like Emacs) to /languages/ (like Lisp)… wait, no. While originally designed for VS Code and probably Python, we can abstract away [[https://github.com/davidhalter/jedi][Jedi]] and the [[http://tkf.github.io/emacs-jedi/latest/][Emacs integration to Jedi]] (and duplicate everything for Ruby, and Clojure, and…).
|
||||||
|
|
||||||
Emacs has two LSP projects, and while I have used [[LSP Mode]], but since I don’t have heavy IDE requirements, I am finding that [[eglot]] to be simpler.
|
Emacs has two LSP projects, and while I have used [[LSP Mode]], but since I don’t have heavy IDE requirements, I am finding that [[eglot]] to be simpler.
|
||||||
*** LSP
|
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package lsp-mode
|
(use-package eglot
|
||||||
:commands (lsp lsp-deferred)
|
:straight nil
|
||||||
:init
|
:commands eglot-ensure
|
||||||
;; Let's make lsp-doctor happy with these settings:
|
|
||||||
(setq gc-cons-threshold (* 100 1024 1024)
|
|
||||||
read-process-output-max (* 1024 1024)
|
|
||||||
company-idle-delay 0.0 ; Are thing fast enough to do this?
|
|
||||||
lsp-keymap-prefix "s-m")
|
|
||||||
|
|
||||||
:config
|
:config
|
||||||
(global-set-key (kbd "s-m") 'lsp)
|
(setq eglot-autoshutdown t))
|
||||||
;; (ha-local-leader :keymaps 'prog-mode-map
|
|
||||||
;; "w" '(:ignore t :which-key "lsp")
|
|
||||||
;; "l" '(:ignore t :which-key "lsp")
|
|
||||||
;; "ws" '("start" . lsp))
|
|
||||||
|
|
||||||
;; ;; The following leader-like keys, are only available when I have
|
|
||||||
;; ;; started LSP, and is an alternate to Command-m:
|
|
||||||
;; :general
|
|
||||||
;; (:states 'normal :keymaps 'lsp-mode-map
|
|
||||||
;; ", w r" '("restart" . lsp-reconnect)
|
|
||||||
;; ", w b" '("events" . lsp-events-buffer)
|
|
||||||
;; ", w e" '("errors" . lsp-stderr-buffer)
|
|
||||||
;; ", w q" '("quit" . lsp-shutdown)
|
|
||||||
;; ", w Q" '("quit all" . lsp-shutdown-all)
|
|
||||||
|
|
||||||
;; ", l r" '("rename" . lsp-rename)
|
|
||||||
;; ", l f" '("format" . lsp-format)
|
|
||||||
;; ", l a" '("actions" . lsp-code-actions)
|
|
||||||
;; ", l i" '("imports" . lsp-code-action-organize-imports)
|
|
||||||
;; ", l d" '("doc" . lsp-lookup-documentation))
|
|
||||||
|
|
||||||
:hook ((lsp-mode . lsp-enable-which-key-integration)))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
I will want to start adding commands under my =,= mode-specific key sequence leader, but in the meantime, all LSP-related keybindings are available under ~⌘-m~. See [[https://emacs-lsp.github.io/lsp-mode/page/keybindings/][this page]] for the default keybindings.
|
|
||||||
|
|
||||||
Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
|
The [[https://github.com/flycheck/flycheck-eglot][flycheck-eglot]] package bridges eglot diagnostics into flycheck, so existing checkers and keybindings continue to work unchanged:
|
||||||
#+begin_src emacs-lisp
|
#+begin_src emacs-lisp
|
||||||
(use-package doom-modeline
|
(use-package flycheck-eglot
|
||||||
|
:after (flycheck eglot)
|
||||||
:config
|
:config
|
||||||
(setq doom-modeline-lsp t
|
(global-flycheck-eglot-mode 1))
|
||||||
doom-modeline-env-version t))
|
|
||||||
#+end_src
|
|
||||||
**** UI
|
|
||||||
The [[https://github.com/emacs-lsp/lsp-ui][lsp-ui]] project offers much of the display and interface to LSP. Seems to make the screen cluttered.
|
|
||||||
#+begin_src emacs-lisp
|
|
||||||
(use-package lsp-ui
|
|
||||||
:commands lsp-ui-mode
|
|
||||||
:config
|
|
||||||
(setq lsp-ui-sideline-ignore-duplicate t
|
|
||||||
lsp-ui-sideline-show-hover t
|
|
||||||
lsp-ui-sideline-show-diagnostics t)
|
|
||||||
:hook (lsp-mode . lsp-ui-mode))
|
|
||||||
#+end_src
|
|
||||||
*** Company Completion
|
|
||||||
The [[https://github.com/tigersoldier/company-lsp][company-lsp]] offers a [[http://company-mode.github.io/][company]] completion backend for [[https://github.com/emacs-lsp/lsp-mode][lsp-mode]]:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
|
||||||
(use-package company-lsp
|
|
||||||
:config
|
|
||||||
(push 'company-lsp company-backends))
|
|
||||||
#+end_src
|
|
||||||
To options that might be interesting:
|
|
||||||
- =company-lsp-async=: When set to non-nil, fetch completion candidates asynchronously.
|
|
||||||
- =company-lsp-enable-snippet=: Set it to non-nil if you want to enable snippet expansion on completion. Set it to nil to disable this feature.
|
|
||||||
|
|
||||||
*** LSP iMenu
|
|
||||||
The [[https://github.com/emacs-lsp/lsp-ui/blob/master/lsp-ui-imenu.el][lsp-imenu]] project offers a =lsp-ui-imenu= function for jumping to functions:
|
|
||||||
|
|
||||||
#+begin_src emacs-lisp :tangle no
|
|
||||||
(use-package lsp-ui-imenu
|
|
||||||
:straight nil
|
|
||||||
:after lsp-ui
|
|
||||||
:config
|
|
||||||
(ha-local-leader :keymaps 'prog-mode-map
|
|
||||||
"g" '(:ignore t :which-key "goto")
|
|
||||||
"g m" '("imenu" . lsp-ui-imenu))
|
|
||||||
(add-hook 'lsp-after-open-hook 'lsp-enable-imenu))
|
|
||||||
#+end_src
|
#+end_src
|
||||||
** General Code Editing
|
** General Code Editing
|
||||||
*** iEdit
|
*** iEdit
|
||||||
|
|
|
||||||
|
|
@ -330,6 +330,7 @@ Let’s make a /theme/:
|
||||||
`(mode-line ((t (:background ,active :foreground "white"))))
|
`(mode-line ((t (:background ,active :foreground "white"))))
|
||||||
`(mode-line-active ((t (:background ,active))))
|
`(mode-line-active ((t (:background ,active))))
|
||||||
`(mode-line-inactive ((t (:background ,inactive))))
|
`(mode-line-inactive ((t (:background ,inactive))))
|
||||||
|
`(which-func ((t (:foreground ,orange-lt))))
|
||||||
|
|
||||||
`(tab-bar ((t :foreground ,default-fg :background ,inactive :inherit variable-pitch)))
|
`(tab-bar ((t :foreground ,default-fg :background ,inactive :inherit variable-pitch)))
|
||||||
`(tab-line ((t :foreground ,default-fg :background ,inactive)))
|
`(tab-line ((t :foreground ,default-fg :background ,inactive)))
|
||||||
|
|
|
||||||
15
initialize
15
initialize
|
|
@ -31,7 +31,7 @@ cat > "$HAMACS_DEST/early-init.el" <<EOF
|
||||||
;; ███ █▄ ███ ███ ███ ███ ███ ███ █▄ ███
|
;; ███ █▄ ███ ███ ███ ███ ███ ███ █▄ ███
|
||||||
;; ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄█ ███
|
;; ███ ███ ███ ███ ███ ███ ███ ███ ███ ▄█ ███
|
||||||
;; ██████████ ▀█ ███ █▀ ███ █▀ ████████▀ ▄████████▀
|
;; ██████████ ▀█ ███ █▀ ███ █▀ ████████▀ ▄████████▀
|
||||||
;;
|
;;
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This is my early Emacs configuration file. See init.el for the real
|
;; This is my early Emacs configuration file. See init.el for the real
|
||||||
|
|
@ -85,7 +85,7 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
||||||
;; +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
;; +#+ +#+ +#+ +#+ +#+ +#+ +#+
|
||||||
;; #+# #+# #+# #+# #+# #+# #+# #+# #+#
|
;; #+# #+# #+# #+# #+# #+# #+# #+# #+#
|
||||||
;; ########## ### ### ### ### ######## ########
|
;; ########## ### ### ### ### ######## ########
|
||||||
;;
|
;;
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
;;
|
;;
|
||||||
;; This is my Emacs Bootloader. Simply put, I initialize the package
|
;; This is my Emacs Bootloader. Simply put, I initialize the package
|
||||||
|
|
@ -135,7 +135,16 @@ cat > "$HAMACS_DEST/init.el" <<EOF
|
||||||
;; While that enables the :straight t extension to use-package, let's just have that be the default:
|
;; While that enables the :straight t extension to use-package, let's just have that be the default:
|
||||||
(use-package straight
|
(use-package straight
|
||||||
:custom (straight-use-package-by-default t
|
:custom (straight-use-package-by-default t
|
||||||
straight-default-vc 'git))
|
straight-default-vc 'git)
|
||||||
|
|
||||||
|
:init
|
||||||
|
;; Eglot and its dependencies ship with Emacs. Tell straight to treat them as
|
||||||
|
;; built-in so nothing (e.g. flycheck-eglot's dependency header) re-pulls the
|
||||||
|
;; GNU-ELPA copies, which would shadow the built-ins and trip eglot's
|
||||||
|
;; `require-with-check' ("Feature `project' is now provided by a different file").
|
||||||
|
(setq straight-built-in-pseudo-packages
|
||||||
|
(append '(eglot project flymake xref jsonrpc external-completion eldoc)
|
||||||
|
straight-built-in-pseudo-packages)))
|
||||||
|
|
||||||
;; See the details in https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8
|
;; See the details in https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,4 @@
|
||||||
# name: title
|
# name: title
|
||||||
# key: title
|
# key: title
|
||||||
# --
|
# --
|
||||||
#+title: ${0}
|
#+TITLE: ${0}
|
||||||
Loading…
Reference in a new issue