Compare commits

..

No commits in common. "87f330112ad4a55d7b6c812f5489fd4e67cc5ed4" and "c0a1d5c2d7480d43b9d266f703e1c67efe970cf8" have entirely different histories.

22 changed files with 465 additions and 604 deletions

1
.gitignore vendored
View file

@ -11,4 +11,3 @@
/ha-theme-results.org /ha-theme-results.org
/support/dashboard /support/dashboard
/.emacs.desktop /.emacs.desktop
/.*-notes.org

View file

@ -113,15 +113,9 @@ 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 sh #+BEGIN_SRC emacs-lisp
claude plugin marketplace add xenodium/emacs-skills (use-package emacs-skills
claude plugin install emacs-skills@xenodium-emacs-skills :straight (:type git :host github :repo "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.
@ -152,7 +146,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/bin/claude-agent-acp"))) `,(file-expand-wildcards "/opt/homebrew/Cellar/node/*/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
@ -1053,6 +1047,28 @@ 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

View file

@ -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 :tangle no #+begin_src emacs-lisp
(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 chats, lets turn on non-fixed-width fonts: In the Telega chats, lets turn on non-fixed-width fonts:
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp
(use-package telega (use-package telega
:hook (telega-chat-mode . mixed-pitch-mode)) :hook (telega-chat-mode . mixed-pitch-mode))
#+end_src #+end_src

View file

@ -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.

View file

@ -1,354 +0,0 @@
#+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, Ive 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 doesnt 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, lets 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

View file

@ -957,6 +957,16 @@ 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 doesnt 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:

View file

@ -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
:config :init
(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,13 +56,8 @@ 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]].

View file

@ -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,18 +54,19 @@ 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. 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>>=: 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:
#+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-smtp-server "smtp.mailbox.org" smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
smtpmail-smtp-service 465 smtpmail-auth-credentials `(("smtp.gmail.com" 587 ,<<email-address-1>> nil))
smtpmail-stream-type 'ssl) smtpmail-default-smtp-server "smtp.gmail.com"
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
@ -172,7 +173,6 @@ 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,41 +184,37 @@ 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
SubFolders Verbatim Flatten .
Channel mailbox Channel personal-inbox
Master :mailbox-remote: Far :personal-remote:
Slave :mailbox-local: Near :personal-local:
Patterns * !Archives Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
Expunge Both
# Patterns "inbox"
# ExpireUnread no
## Channel personal-inbox Channel personal-sent
## Far :personal-remote: Far :personal-remote:"[Gmail]/Sent Mail"
## Near :personal-local: Near :personal-local:sent
## # Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam" ExpireUnread yes
## Expunge Both
## # Patterns "inbox" Channel personal-trash
## # ExpireUnread no Far :personal-remote:"[Gmail]/Trash"
## Near :personal-local:trash
## Channel personal-sent ExpireUnread yes
## # 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.
@ -747,7 +743,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 :tangle no #+begin_src emacs-lisp
(use-package doom-modeline (use-package doom-modeline
:config :config
(setq doom-modeline-mu4e t)) (setq doom-modeline-mu4e t))

View file

@ -355,16 +355,6 @@ And the keybindings:
Why not use ~f~? Im reserving the ~f~ for a tree-sitter version that is not always available for all modes… yet. Why not use ~f~? Im 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.

View file

@ -360,15 +360,10 @@ 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
The built-in behavior of bookmarks doesnt honor either /projects/ or /perspectives/, but the [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package makes =project=-specific bookmarks. Expand on my [[file:ha-config.org::*Bookmarks][Bookmarks]] with the following key sequences:
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:
@ -380,31 +375,6 @@ This expands on my global [[file:ha-config.org::*Bookmarks][Bookmarks]]:
"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 doesnt 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=. Lets 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.

View file

@ -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 :tangle no #+begin_src emacs-lisp
(setq doom-modeline-irc t (setq doom-modeline-irc t
doom-modeline-irc-stylize 'identity) doom-modeline-irc-stylize 'identity)
#+end_src #+end_src

View file

@ -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: [2026-08-05 Wed] #+lastmod: [2025-02-20 Thu]
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 :eval none #+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/surround-org-block
# 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 :eval none #+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block
,# -*- mode: snippet -*- ,# -*- mode: snippet -*-
,# name: split-org-block ,# name: split-org-block
,# key: #split ,# key: #split
@ -712,8 +712,7 @@ 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
@ -778,20 +777,12 @@ Before we dive into the implementation of this function, lets 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∷ Parent﹥ Subparent﹥ Subby﹥ Deepsubby﹥ Deepest Heading" '("Somefile∷ ... Deepest Heading"
"ha-somefile.org" 73)))) "ha-somefile.org" 73))))
#+end_src #+end_src

View file

@ -2,7 +2,9 @@
#+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; -*-
;; ;;
@ -23,51 +25,33 @@ 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
Migrating to a new team should include something different. Drunk animals? Everyday AI?
#+name: sprint-names-2026
- Alchemists Amber
- Bubblegloop Bogs
- Crumpet Crossroads
- Dandelion Drifters
- Elfin Espresso
- Firefly Fandango
- Giggling Goblins
- Hobgoblin Hideaway
- Inkwell Isles
- Jesters Junction
- Knapsack Knolls
- Lavender Labyrinths
- Moonbeam Meadow
- Nectar Nibblers
- Owls 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
@ -126,6 +110,7 @@ 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
@ -154,7 +139,9 @@ 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
@ -184,6 +171,7 @@ 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
@ -212,7 +200,9 @@ 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
@ -240,8 +230,11 @@ 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
@ -269,8 +262,10 @@ 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)
@ -284,34 +279,44 @@ 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 ()
@ -330,6 +335,7 @@ 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 ()
@ -338,7 +344,9 @@ 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."
@ -349,32 +357,33 @@ 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) 2) sprint-nicknames))) (nickname (nth (1- sprint-num) 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
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. 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,
@ -392,7 +401,9 @@ 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.
@ -404,6 +415,7 @@ 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 ()
@ -418,8 +430,11 @@ 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."
@ -430,14 +445,18 @@ 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
@ -449,6 +468,7 @@ 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
@ -456,25 +476,33 @@ 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

View file

@ -338,12 +338,11 @@ 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

View file

@ -3,7 +3,7 @@
#+date: 2020-09-18 #+date: 2020-09-18
#+tags: emacs org #+tags: emacs org
#+startup: inlineimages #+startup: inlineimages
#+lastmod: [2026-07-15 Wed] #+lastmod: [2026-04-08 Wed]
A literate programming file for configuring org-mode and those files. A literate programming file for configuring org-mode and those files.
@ -65,7 +65,6 @@ 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.

View file

@ -318,6 +318,85 @@ 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 doesnt 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 weve 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
Lets 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=):
@ -343,60 +422,100 @@ Each Python project's =requirements-dev.txt= file would reference the [[https://
# ... # ...
#+end_src #+end_src
** Pyright ** Pyright
Im using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead, adding it to the global Python scope. Im using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
#+begin_src conf :tangle no #+begin_src conf :tangle no
pyright pyright
#+end_src #+end_src
While the [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP, this isnt needed, as =eglot= works directly with it. The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
** 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 #+begin_src emacs-lisp :tangle no
(use-package eglot (use-package lsp-pyright
:straight nil :hook (python-mode . (lambda () (require 'lsp-pyright)))
:after python :init (when (executable-find "python3")
:defer t (setq lsp-pyright-python-executable-cmd "python3")))
: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
Keybindings for the eglot-based LSP sub-menu, wired into the existing =python-mode= hydra: 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.
#+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"))
(use-package eglot (defun ha-setup-python-lsp ()
:straight nil "Configure the keybindings for LSP in Python."
:after major-mode-hydra (interactive)
: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" eglot-shutdown "Disconnect") (("D" lsp-disconnect "Disconnect")
("R" eglot-reconnect "Reconnect") ("R" lsp-workspace-restart "Restart")
("?" eglot-show-workspace-configuration "Describe")) ("S" lsp-workspace-shutdown "Shutdown")
("?" lsp-describe-session "Describe"))
"Refactoring" "Refactoring"
(("a" eglot-code-actions "Code Actions") (("a" lsp-execute-code-action "Code Actions")
("o" eglot-code-action-organize-imports "Organize Imports") ("o" lsp-organize-imports "Organize Imports")
("r" eglot-rename "Rename")) ("l" lsp-avy-lens "Avy Lens"))
"Navigation" "Toggles"
(("." xref-find-definitions "Definition") (("b" lsp-headerline-breadcrumb-mode "Breadcrumbs")
("/" xref-find-references "References") ("d" lsp-ui-doc-mode "Documentation Popups")
("t" eglot-find-typeDefinition "Type Definition") ("m" lsp-modeline-diagnostics-mode "Modeline Diagnostics")
("i" eglot-find-implementation "Implementation")) ("s" lsp-ui-sideline-mode "Sideline Mode"))
"Docs" ""
(("d" eldoc-doc-buffer "Eldoc Buffer") (("t" lsp-toggle-on-type-formatting "Type Formatting")
("h" eglot-inlay-hints-mode "Inlay Hints") ("h" lsp-toggle-symbol-highlight "Symbol Highlighting")
("=" eglot-format-buffer "Format Buffer")))) ("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"
(("D" lsp-describe-thing-at-point "Describe LSP Symbol")
("h" lsp-ui-doc-glance "Glance Help")
("H" lsp-document-highlight "Highlight"))))
(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" eglot-ensure "Start eglot") --> Not needed with eglot-ensure (("L" ha-setup-python-lsp "Start LSP Server")))))
("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/:
@ -422,9 +541,17 @@ 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 eglot/pyright") (message "Configuring pip for LSP")
(with-temp-file "requirements-dev.txt" (with-temp-file "requirements-dev.txt"
(insert "pyright\n")) (insert "python-lsp-server[all]\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

View file

@ -229,6 +229,37 @@ 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.
@ -264,34 +295,6 @@ 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
@ -300,7 +303,29 @@ Or add it to your =Gemfile=:
#+begin_src ruby #+begin_src ruby
gem 'solargraph', group: :development gem 'solargraph', group: :development
#+end_src #+end_src
Once installed, [[*eglot][eglot]] finds and talks to =solargraph= automatically; no further Emacs configuration is needed. * XRef Interface with GNU Global
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:

View file

@ -68,10 +68,24 @@ The [[https://github.com/brotzeit/rustic][rustic project]] incorporates [[https:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package rustic (use-package rustic
:config :init
(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"))
rustic-format-on-save t)) ;; :bind (:map rustic-mode-map
;; ("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
@ -88,6 +102,7 @@ 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:

View file

@ -683,21 +683,86 @@ 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 dont 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 dont have heavy IDE requirements, I am finding that [[eglot]] to be simpler.
*** LSP
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package eglot (use-package lsp-mode
:straight nil :commands (lsp lsp-deferred)
:commands eglot-ensure :init
;; 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
(setq eglot-autoshutdown t)) (global-set-key (kbd "s-m") 'lsp)
;; (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.
The [[https://github.com/flycheck/flycheck-eglot][flycheck-eglot]] package bridges eglot diagnostics into flycheck, so existing checkers and keybindings continue to work unchanged: Using the [[https://github.com/seagle0128/doom-modeline][Doom Modeline]] to add notifications:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package flycheck-eglot (use-package doom-modeline
:after (flycheck eglot)
:config :config
(global-flycheck-eglot-mode 1)) (setq doom-modeline-lsp t
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

View file

@ -330,7 +330,6 @@ Lets 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)))

View file

@ -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,16 +135,7 @@ 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

View file

@ -2,4 +2,4 @@
# name: title # name: title
# key: title # key: title
# -- # --
#+TITLE: ${0} #+title: ${0}