Compare commits
No commits in common. "87f330112ad4a55d7b6c812f5489fd4e67cc5ed4" and "c0a1d5c2d7480d43b9d266f703e1c67efe970cf8" have entirely different histories.
87f330112a
...
c0a1d5c2d7
22 changed files with 465 additions and 604 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -11,4 +11,3 @@
|
|||
/ha-theme-results.org
|
||||
/support/dashboard
|
||||
/.emacs.desktop
|
||||
/.*-notes.org
|
||||
|
|
|
|||
|
|
@ -113,15 +113,9 @@ I might switch to [[https://github.com/cxa/agent-shell-macext#start-of-content][
|
|||
*** Agent Skills
|
||||
The following pulls in [[https://github.com/xenodium/emacs-skills][emacs-skills]], a Claude Agent skills for Emacs:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
claude plugin marketplace add 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
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package emacs-skills
|
||||
:straight (:type git :host github :repo "xenodium/emacs-skills"))
|
||||
#+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.
|
||||
|
|
@ -152,7 +146,7 @@ And point Emacs to it:
|
|||
:config
|
||||
(setq agent-shell-preferred-agent-config (agent-shell-anthropic-make-claude-code-config)
|
||||
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
|
||||
#+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 ",") 'major-mode-hydras/dired-mode/body)
|
||||
#+end_src
|
||||
* Annotations
|
||||
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" and then move to them (yes, serious overlap with bookmarks, which we will return to).
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package annotate
|
||||
:config
|
||||
(ha-leader
|
||||
"t A" '("annotations" . annotate-mode)
|
||||
|
||||
"n" '(:ignore t :which-key "notes")
|
||||
"n a" '("toggle mode" . annotate-mode)
|
||||
"n n" '("annotate" . annotate-annotate)
|
||||
"n d" '("delete" . annotate-delete)
|
||||
"n s" '("summary" . annotate-show-annotation-summary)
|
||||
"n j" '("next" . annotate-goto-next-annotation)
|
||||
"n k" '("prev" . annotate-goto-previous-annotation)
|
||||
|
||||
;; If a shift binding isn't set, it defaults to non-shift version
|
||||
;; Use SPC N N to jump to the next error:
|
||||
"n N" '("next error" . flycheck-next-error)))
|
||||
#+end_src
|
||||
Keep the annotations simple, almost /tag-like/, and then the summary allows you to display them.
|
||||
* Keepass
|
||||
Use the [[https://github.com/ifosch/keepass-mode][keepass-mode]] to view a /read-only/ version of my Keepass file in Emacs:
|
||||
#+begin_src emacs-lisp
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ Perhaps we can make it more attractive:
|
|||
** Telega
|
||||
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
|
||||
:config
|
||||
(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.
|
||||
|
||||
In the Telega chat’s, let’s turn on non-fixed-width fonts:
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
#+begin_src emacs-lisp
|
||||
(use-package telega
|
||||
:hook (telega-chat-mode . mixed-pitch-mode))
|
||||
#+end_src
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
#+TITLE: Capturing Notes with Org
|
||||
#+AUTHOR: Howard X. Abrams
|
||||
#+DATE: 2020-09-18
|
||||
#+TAGS: emacs org
|
||||
#+title: Capturing Notes with Org
|
||||
#+author: Howard X. Abrams
|
||||
#+date: 2020-09-18
|
||||
#+tags: emacs org
|
||||
|
||||
A literate programming file for configuring org for capturing notes.
|
||||
|
||||
|
|
|
|||
|
|
@ -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, 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,6 +957,16 @@ I like the idea of dropping returnable bookmarks, but with /good defaults/ for t
|
|||
(bookmark-set label)))
|
||||
#+END_SRC
|
||||
|
||||
The built-in behavior doesn’t honor either /projects/ or /perspectives/, but I use [[https://codeberg.org/ideasman42/emacs-bookmark-in-project][bookmark-in-project]] package to make a =project=-specific bookmarks and use that to jump to only bookmarks in the current project.
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(use-package bookmark-in-project
|
||||
:bind
|
||||
(("C-x r m" . bookmark-in-project-toggle)
|
||||
("C-x r M" . ha-bookmark-set)))
|
||||
#+END_SRC
|
||||
|
||||
|
||||
** Minor Keybinding Annoys
|
||||
I like ~C-a~ to go to the beginning of the line, but what about getting to the beginning of text on that line? In Evil, you have ~^~ for beginning of line, and ~0~ for first text. Why not have ~C-a~ toggle between them both:
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
#+begin_src emacs-lisp
|
||||
(use-package doom-modeline
|
||||
:config
|
||||
:init
|
||||
(setq doom-modeline-minor-modes nil
|
||||
doom-modeline-buffer-encoding nil
|
||||
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-lsp-icon t
|
||||
doom-modeline-percent-position nil)
|
||||
|
||||
;; Weird bug:
|
||||
(defvar helm-ag-show-status-function nil "Workaround")
|
||||
|
||||
(doom-modeline-mode 1))
|
||||
#+end_src
|
||||
|
||||
* 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]].
|
||||
|
||||
|
|
|
|||
64
ha-email.org
64
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
|
||||
- ~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.
|
||||
|
||||
#+name: email-address-1
|
||||
|
|
@ -54,14 +54,15 @@ The configuration files below expect email addresses (I store passwords and othe
|
|||
(rot13-string "ubjneq@shmmlgbnfg.pbz")
|
||||
#+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
|
||||
(setq send-mail-function 'smtpmail-send-it
|
||||
message-send-mail-function 'smtpmail-send-it
|
||||
smtpmail-smtp-server "smtp.mailbox.org"
|
||||
smtpmail-smtp-service 465
|
||||
smtpmail-stream-type 'ssl)
|
||||
smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
|
||||
smtpmail-auth-credentials `(("smtp.gmail.com" 587 ,<<email-address-1>> nil))
|
||||
smtpmail-default-smtp-server "smtp.gmail.com"
|
||||
smtpmail-smtp-server "smtp.gmail.com"
|
||||
smtpmail-smtp-service 587)
|
||||
#+end_src
|
||||
* Sending Mail
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
|
|
@ -172,7 +173,6 @@ There are global settings:
|
|||
#+begin_src conf :tangle ~/.mbsyncrc :noweb yes
|
||||
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
||||
Create Both
|
||||
Expunge Both
|
||||
SyncState *
|
||||
MaxMessages 100
|
||||
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
|
||||
# PERSONAL ACCOUNT
|
||||
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
|
||||
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
|
||||
Path ~/.mail/personal/
|
||||
Inbox ~/.mail/personal/INBOX
|
||||
SubFolders Verbatim
|
||||
Flatten .
|
||||
|
||||
Channel mailbox
|
||||
Master :mailbox-remote:
|
||||
Slave :mailbox-local:
|
||||
Patterns * !Archives
|
||||
Channel personal-inbox
|
||||
Far :personal-remote:
|
||||
Near :personal-local:
|
||||
Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
||||
Expunge Both
|
||||
# Patterns "inbox"
|
||||
# ExpireUnread no
|
||||
|
||||
## Channel personal-inbox
|
||||
## Far :personal-remote:
|
||||
## Near :personal-local:
|
||||
## # Patterns * !"[Gmail]/Drafts" !"[Gmail]/Spam"
|
||||
## Expunge Both
|
||||
## # Patterns "inbox"
|
||||
## # ExpireUnread no
|
||||
##
|
||||
## 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
|
||||
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
|
||||
|
||||
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.
|
||||
* Display Configuration
|
||||
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
|
||||
:config
|
||||
(setq doom-modeline-mu4e t))
|
||||
|
|
|
|||
10
ha-evil.org
10
ha-evil.org
|
|
@ -355,16 +355,6 @@ 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.
|
||||
* 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
|
||||
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,15 +360,10 @@ And the collection of useful operations:
|
|||
"b C-g" '(keyboard-escape-quit :which-key t))
|
||||
#+end_src
|
||||
* Bookmarks
|
||||
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]]:
|
||||
Expand on my [[file:ha-config.org::*Bookmarks][Bookmarks]] with the following key sequences:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package bookmark-in-project
|
||||
:bindd
|
||||
(("C-x r m" . bookmark-in-project-toggle)
|
||||
("C-x r M" . ha-bookmark-set))
|
||||
:config
|
||||
(ha-leader
|
||||
;; Set or delete a bookmark associated with project:
|
||||
|
|
@ -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 <up>" '("previous mark" . bookmark-in-project-jump-previous)))
|
||||
#+end_src
|
||||
** Annotations
|
||||
Let's try [[https://github.com/bastibe/annotate.el][annotate-mode]], which allows you to drop "notes" displayed /inline/ with the buffer.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package annotate
|
||||
:config
|
||||
(ha-leader
|
||||
"t A" '("annotations" . annotate-mode)
|
||||
|
||||
"n" '(:ignore t :which-key "notes")
|
||||
"n a" '("toggle mode" . annotate-mode)
|
||||
"n n" '("annotate" . annotate-annotate)
|
||||
"n d" '("delete" . annotate-delete-annotation)
|
||||
|
||||
;; Can't seem to get anything useful out of this function:
|
||||
"n s" '("summary" . annotate-show-annotation-summary)
|
||||
|
||||
;; The next and previous only work for the current file:
|
||||
"n j" '("next" . annotate-goto-next-annotation)
|
||||
"n k" '("prev" . annotate-goto-previous-annotation)))
|
||||
#+end_src
|
||||
|
||||
Keep the annotations simple, almost /tag-like/, and then the summary doesn’t wrap. For longer notes, see my [[file:ha-capturing-notes.org::*Code Notes][Code Notes]] section.
|
||||
|
||||
The annotations are stored as s-expressions in the file, =~/.emacs.d/annotations=. Let’s see how well this scales.
|
||||
* Centering
|
||||
After reading [[https://mbork.pl/2024-04-15_Improving_recenter-top-bottom_and_reposition-window][this essay]], I got to thinking that it would be nice to position the text in a buffer /near the top/, but show context based on some specific, textual /things/. My thought is to have a function that prompts for the thing (like the current paragraph, function, etc), but also create thing-specific functions.
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ And a quick shortcuts to call it:
|
|||
#+end_src
|
||||
* Display Configuration
|
||||
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
|
||||
doom-modeline-irc-stylize 'identity)
|
||||
#+end_src
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
#+author: Howard Abrams
|
||||
#+date: 2024-07-07
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2026-08-05 Wed]
|
||||
#+lastmod: [2025-02-20 Thu]
|
||||
|
||||
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
|
||||
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
|
||||
# --
|
||||
,#+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:
|
||||
|
||||
#+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 -*-
|
||||
,# name: split-org-block
|
||||
,# key: #split
|
||||
|
|
@ -712,8 +712,7 @@ Not every header should be a destination, as many of my org files have duplicate
|
|||
"Install"
|
||||
"Overview"
|
||||
"Summary"
|
||||
"Technical Artifacts"
|
||||
"[["))
|
||||
"Technical Artifacts"))
|
||||
"Regular expression matching headers to purge.")
|
||||
#+end_src
|
||||
|
||||
|
|
@ -778,20 +777,12 @@ Before we dive into the implementation of this function, let’s write a test to
|
|||
'("Somefile∷ Parent﹥ Subparent﹥ Deep Heading"
|
||||
"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"
|
||||
"Subby" "Deepsubby"))
|
||||
(should (equal
|
||||
(ha-hamacs-edit--process-entry
|
||||
"ha-somefile.org:73:***** Deepest Heading")
|
||||
'("Somefile∷ Parent﹥ Subparent﹥ Subby﹥ Deepsubby﹥ Deepest Heading"
|
||||
'("Somefile∷ ... Deepest Heading"
|
||||
"ha-somefile.org" 73))))
|
||||
#+end_src
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@
|
|||
#+author: Howard X. Abrams
|
||||
#+date: 2020-09-25
|
||||
#+tags: emacs work
|
||||
|
||||
A literate program for configuring org files for work-related notes.
|
||||
|
||||
#+begin_src emacs-lisp :exports none
|
||||
;;; 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:
|
||||
#+end_src
|
||||
* 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.
|
||||
|
||||
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.
|
||||
|
||||
This template needs the following functions:
|
||||
- =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-from-start= return a date for pre-scheduled and recurring meetings
|
||||
|
||||
* 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.
|
||||
|
||||
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
|
||||
"List of 26 Sprint Nicknames from A to Z.")
|
||||
#+end_src
|
||||
** 2026
|
||||
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
|
||||
Drunk animals? Everyday AI?
|
||||
** 2025
|
||||
This year is the animals representing corporate slogans:
|
||||
|
||||
#+name: sprint-names-2025
|
||||
- Accelerated and Adaptive Ant
|
||||
- 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
|
||||
** 2023
|
||||
How about a list of Ent names?
|
||||
|
||||
#+name: sprint-names-2023
|
||||
- ashskin
|
||||
- birchblossom
|
||||
|
|
@ -154,7 +139,9 @@ How about a list of Ent names?
|
|||
- yewbiquitous
|
||||
- zelkova
|
||||
** 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:
|
||||
|
||||
#+name: sprint-names-2022
|
||||
- ankheg
|
||||
- beholder
|
||||
|
|
@ -184,6 +171,7 @@ Fun sprint names for 2022 lists my favorite D&D monsters, also see [[https://lis
|
|||
- zombie
|
||||
** 2021
|
||||
Choosing Sprint Names based on [[https://www.imagineforest.com/blog/funniest-words-in-the-english-language/][Funny or Silly Words]]:
|
||||
|
||||
#+name: sprint-names-2021
|
||||
- abibliophobia :: The fear of running out of reading materials to read
|
||||
- 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.
|
||||
- zazzy :: Something that is shiny and flashy
|
||||
** 2020
|
||||
|
||||
New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list of dinosaurs]].
|
||||
|
||||
#+name: sprint-names-2020
|
||||
- ankylosaurus
|
||||
- brontosaurus
|
||||
|
|
@ -240,8 +230,11 @@ New names from [[https://en.m.wikipedia.org/wiki/List_of_dinosaur_genera][list o
|
|||
- xiaotingia
|
||||
- yi
|
||||
- zuul
|
||||
|
||||
** 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.
|
||||
|
||||
#+name: sprint-names-2019
|
||||
- achy-aachen
|
||||
- bare-bacabal
|
||||
|
|
@ -269,8 +262,10 @@ Came up with a list of somewhat well-known cities throughout the world (at least
|
|||
- xenophobic-xichang
|
||||
- yellow-yamaguchi
|
||||
- zippy-zinder
|
||||
|
||||
* 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.
|
||||
|
||||
Emacs have an internal rep of a time.
|
||||
#+begin_src emacs-lisp
|
||||
(defun get-date-time (date)
|
||||
|
|
@ -284,34 +279,44 @@ Emacs have an internal rep of a time.
|
|||
(apply 'encode-time))
|
||||
date))
|
||||
#+end_src
|
||||
|
||||
** Sprint Numbering
|
||||
|
||||
Each year, specify the first day of the first sprint of the year:
|
||||
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
;; CHANGEME Each year as this should update:
|
||||
(defvar sprint-start-date (get-date-time "2026-01-06")
|
||||
"The date of the first day of the first sprint of the year.
|
||||
See `sprint-range'.")
|
||||
#+END_SRC
|
||||
|
||||
My company has sprints two weeks long that we can calculate from
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar sprint-length (* 14 24 60 60)
|
||||
"The length of a sprint, in seconds. This is 2 weeks long")
|
||||
#+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=:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-number (&optional date)
|
||||
"Return the number of 14-day intervals since SPRINT-START-DATE to DATE.
|
||||
DATE is a string in YYYY-MM-DD format."
|
||||
(unless date
|
||||
(setq date (format-time-string "%Y-%m-%d")))
|
||||
|
||||
(let* ((end-time (get-date-time date))
|
||||
(diff-seconds (float-time (time-subtract end-time sprint-start-date))))
|
||||
|
||||
;; After calculating the number of 'bi-weeks' (2 week sprint increments),
|
||||
;; we add one to return the correct sprint for the networking team:
|
||||
;; (1+ (floor (/ diff-seconds sprint-length)))
|
||||
;; Or for the platform storage team:
|
||||
(1+ (* 2 (floor (/ diff-seconds sprint-length))))))
|
||||
#+end_src
|
||||
|
||||
And some tests to verify that:
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(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)
|
||||
(sprint-number d)))))
|
||||
#+end_src
|
||||
|
||||
So given a particular date, I should expect to be able to find the correct Sprint file name:
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(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-14.org" (sprint-current-file "2024-02-13"))))
|
||||
#+end_src
|
||||
|
||||
Daily note-taking goes into my sprint file notes, so this interactive function makes an easy global short-cut key.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-current-find-file (&optional date)
|
||||
"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)
|
||||
(find-file filename)))
|
||||
#+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)=
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-current-name (&optional date)
|
||||
"Return the default name of the current sprint (based on DATE)."
|
||||
(let* ((d (get-date-time date))
|
||||
(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-time-string "%Y" d)
|
||||
(sprint-number d)
|
||||
nickname)))
|
||||
#+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.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(ert-deftest sprint-current-name-test ()
|
||||
(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"))))
|
||||
#+end_src
|
||||
|
||||
** 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.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-range (&optional number-or-date)
|
||||
"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))))
|
||||
(list sprint-start sprint-end sprint-next)))
|
||||
#+end_src
|
||||
|
||||
Format the start and end so that we can insert this directly in the org file:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-date-range (&optional number-or-date)
|
||||
"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)))
|
||||
(format "[%s]--[%s]" start end))))
|
||||
#+end_src
|
||||
|
||||
And validate with a test:
|
||||
#+begin_src emacs-lisp
|
||||
(ert-deftest sprint-date-range ()
|
||||
|
|
@ -418,8 +430,11 @@ And validate with a test:
|
|||
(should (equal (sprint-date-range 5)
|
||||
(sprint-date-range "2024-01-31"))))
|
||||
#+end_src
|
||||
|
||||
** 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:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun sprint-date-from-start (days &optional formatter)
|
||||
"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 "%Y-%m-%d %a" adate))))
|
||||
#+end_src
|
||||
|
||||
* Other Date Functions
|
||||
|
||||
The following functions /were/ helpful at times. But I'm not sure I will use them.
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(defun sprint-num-days (time-interval)
|
||||
"Converts a TIME-INTERVAL to a number of days."
|
||||
(let ((day-length (* 3600 24)))
|
||||
(round (/ (float-time time-interval) day-length))))
|
||||
#+end_src
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(defun sprint-day-range (&optional date)
|
||||
"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)))
|
||||
(list (sprint-num-days starting) (sprint-num-days ending)))))
|
||||
#+end_src
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(ert-deftest sprint-day-range ()
|
||||
;; 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 '(-13 0) (sprint-day-range "2020-02-26"))))
|
||||
#+end_src
|
||||
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(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."
|
||||
(first (sprint-day-range 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."
|
||||
(second (sprint-day-range date)))
|
||||
#+end_src
|
||||
|
||||
* Technical Artifacts :noexport:
|
||||
|
||||
Let's =provide= a name so we can =require= this file:
|
||||
#+begin_src emacs-lisp :exports none
|
||||
(provide 'ha-org-sprint)
|
||||
;;; ha-org-sprint.el ends here
|
||||
#+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~
|
||||
|
||||
#+description: A literate program for configuring org files for work-related 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:t todo:nil tasks:nil tags:nil date: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
|
||||
|
|
|
|||
|
|
@ -343,7 +343,6 @@ The [[https://github.com/minad/org-modern][org-modern]] project attempts to do a
|
|||
("▹" . "▿")
|
||||
("▸" . "▾")))
|
||||
: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-modern-symbol nil :family "Iosevka"))
|
||||
#+end_src
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
#+date: 2020-09-18
|
||||
#+tags: emacs org
|
||||
#+startup: inlineimages
|
||||
#+lastmod: [2026-07-15 Wed]
|
||||
#+lastmod: [2026-04-08 Wed]
|
||||
|
||||
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,
|
||||
; but uses a trick to make it
|
||||
; appear indented.
|
||||
org-use-sub-superscripts '{}
|
||||
|
||||
org-imenu-depth 4
|
||||
sentence-end-double-space nil ; I jump around by sentences, but seldom have two spaces.
|
||||
|
|
|
|||
|
|
@ -318,6 +318,85 @@ This allows me to delete a Python “block” using ~dal~.
|
|||
("Misc"
|
||||
(("t" python-tests/body "Tests..."))))))
|
||||
#+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
|
||||
** 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=):
|
||||
|
|
@ -343,60 +422,100 @@ Each Python project's =requirements-dev.txt= file would reference the [[https://
|
|||
# ...
|
||||
#+end_src
|
||||
** Pyright
|
||||
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead, adding it to the global Python scope.
|
||||
|
||||
I’m using the Microsoft-supported [[https://github.com/Microsoft/pyright][pyright]] package instead. Adding this to my =requirements.txt= files:
|
||||
#+begin_src conf :tangle no
|
||||
pyright
|
||||
#+end_src
|
||||
|
||||
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):
|
||||
The [[https://github.com/emacs-lsp/lsp-pyright][pyright package]] works with LSP.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package eglot
|
||||
:straight nil
|
||||
:after python
|
||||
:defer t
|
||||
:hook (python-mode . eglot-ensure)
|
||||
:config
|
||||
(add-to-list 'eglot-server-programs
|
||||
'((python-mode python-ts-mode) . ("pyright-langserver" "--stdio"))))
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(use-package lsp-pyright
|
||||
:hook (python-mode . (lambda () (require 'lsp-pyright)))
|
||||
:init (when (executable-find "python3")
|
||||
(setq lsp-pyright-python-executable-cmd "python3")))
|
||||
#+end_src
|
||||
|
||||
Keybindings for the eglot-based LSP sub-menu, wired into the existing =python-mode= hydra:
|
||||
*** 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.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defvar ha-python-lsp-title (font-icons 'faicon "python" :title "Python LSP"))
|
||||
|
||||
(use-package eglot
|
||||
:straight nil
|
||||
:after major-mode-hydra
|
||||
:config
|
||||
(defun ha-setup-python-lsp ()
|
||||
"Configure the keybindings for LSP in Python."
|
||||
(interactive)
|
||||
|
||||
(pretty-hydra-define python-lsp (:color blue :quit-key "q"
|
||||
:title ha-python-lsp-title)
|
||||
("Server"
|
||||
(("D" eglot-shutdown "Disconnect")
|
||||
("R" eglot-reconnect "Reconnect")
|
||||
("?" eglot-show-workspace-configuration "Describe"))
|
||||
(("D" lsp-disconnect "Disconnect")
|
||||
("R" lsp-workspace-restart "Restart")
|
||||
("S" lsp-workspace-shutdown "Shutdown")
|
||||
("?" lsp-describe-session "Describe"))
|
||||
"Refactoring"
|
||||
(("a" eglot-code-actions "Code Actions")
|
||||
("o" eglot-code-action-organize-imports "Organize Imports")
|
||||
("r" eglot-rename "Rename"))
|
||||
"Navigation"
|
||||
(("." xref-find-definitions "Definition")
|
||||
("/" xref-find-references "References")
|
||||
("t" eglot-find-typeDefinition "Type Definition")
|
||||
("i" eglot-find-implementation "Implementation"))
|
||||
"Docs"
|
||||
(("d" eldoc-doc-buffer "Eldoc Buffer")
|
||||
("h" eglot-inlay-hints-mode "Inlay Hints")
|
||||
("=" eglot-format-buffer "Format Buffer"))))
|
||||
(("a" lsp-execute-code-action "Code Actions")
|
||||
("o" lsp-organize-imports "Organize Imports")
|
||||
("l" lsp-avy-lens "Avy Lens"))
|
||||
"Toggles"
|
||||
(("b" lsp-headerline-breadcrumb-mode "Breadcrumbs")
|
||||
("d" lsp-ui-doc-mode "Documentation Popups")
|
||||
("m" lsp-modeline-diagnostics-mode "Modeline Diagnostics")
|
||||
("s" lsp-ui-sideline-mode "Sideline Mode"))
|
||||
""
|
||||
(("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"
|
||||
(("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")
|
||||
("Server"
|
||||
( ;; ("L" eglot-ensure "Start eglot") --> Not needed with eglot-ensure
|
||||
("l" python-lsp/body "LSP...")))))
|
||||
(("L" ha-setup-python-lsp "Start LSP Server")))))
|
||||
|
||||
;; ----------------------------------------------------------------------
|
||||
;; 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
|
||||
* 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/:
|
||||
|
|
@ -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 "pip install --upgrade pip"))
|
||||
|
||||
(message "Configuring pip for eglot/pyright")
|
||||
(message "Configuring pip for LSP")
|
||||
(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")))
|
||||
#+end_src
|
||||
* Major Mode Hydra
|
||||
|
|
|
|||
|
|
@ -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" '("retest" . ruby-test-rerun)))
|
||||
#+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
|
||||
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
|
||||
https://github.com/pezra/rspec-mode
|
||||
* 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:
|
||||
#+begin_src sh
|
||||
gem install solargraph
|
||||
|
|
@ -300,7 +303,29 @@ Or add it to your =Gemfile=:
|
|||
#+begin_src ruby
|
||||
gem 'solargraph', group: :development
|
||||
#+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:
|
||||
|
||||
Let's =provide= a name so we can =require= this file:
|
||||
|
|
|
|||
|
|
@ -68,10 +68,24 @@ The [[https://github.com/brotzeit/rustic][rustic project]] incorporates [[https:
|
|||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package rustic
|
||||
:config
|
||||
:init
|
||||
(setq rustic-lsp-client 'eglot
|
||||
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer")
|
||||
rustic-format-on-save t))
|
||||
rustic-analyzer-command '("/opt/homebrew/bin/rust-analyzer"))
|
||||
;; :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
|
||||
|
||||
** The Playground
|
||||
|
|
@ -88,6 +102,7 @@ The [[https://github.com/dryman/toml-mode.el][toml-mode]] adds syntax highlighti
|
|||
#+begin_src emacs-lisp
|
||||
(use-package toml-mode)
|
||||
#+end_src
|
||||
* LSP
|
||||
* Technical Artifacts :noexport:
|
||||
Let's =provide= a name so we can =require= this 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…).
|
||||
|
||||
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
|
||||
(use-package eglot
|
||||
:straight nil
|
||||
:commands eglot-ensure
|
||||
(use-package lsp-mode
|
||||
:commands (lsp lsp-deferred)
|
||||
: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
|
||||
(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
|
||||
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
|
||||
(use-package flycheck-eglot
|
||||
:after (flycheck eglot)
|
||||
(use-package doom-modeline
|
||||
: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
|
||||
** General Code Editing
|
||||
*** iEdit
|
||||
|
|
|
|||
|
|
@ -330,7 +330,6 @@ Let’s make a /theme/:
|
|||
`(mode-line ((t (:background ,active :foreground "white"))))
|
||||
`(mode-line-active ((t (:background ,active))))
|
||||
`(mode-line-inactive ((t (:background ,inactive))))
|
||||
`(which-func ((t (:foreground ,orange-lt))))
|
||||
|
||||
`(tab-bar ((t :foreground ,default-fg :background ,inactive :inherit variable-pitch)))
|
||||
`(tab-line ((t :foreground ,default-fg :background ,inactive)))
|
||||
|
|
|
|||
11
initialize
11
initialize
|
|
@ -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:
|
||||
(use-package straight
|
||||
:custom (straight-use-package-by-default t
|
||||
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)))
|
||||
straight-default-vc 'git))
|
||||
|
||||
;; See the details in https://dev.to/jkreeftmeijer/emacs-package-management-with-straight-el-and-use-package-3oc8
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@
|
|||
# name: title
|
||||
# key: title
|
||||
# --
|
||||
#+TITLE: ${0}
|
||||
#+title: ${0}
|
||||
Loading…
Reference in a new issue