Compare commits

...

7 commits

Author SHA1 Message Date
Howard Abrams
f735e4c59e Fix spelling 2024-10-27 22:52:36 -07:00
Howard Abrams
820e1853cb Add link to RSS in the header of the blogs 2024-10-27 22:52:02 -07:00
Howard Abrams
595d7db714 Fix two bugs in literate programming functions
First, file references need to be /relative/ to the buffer calling
them. Odd, but sure.

Second, we need to have our 'org backend come /before/ the dumb-jump,
otherwise, dumb-jump says it can't find it, and that is it.
2024-10-27 22:50:06 -07:00
Howard Abrams
3b0e21c128 Expand a literate prog work with extra snippets
These should be a lovely demonstration to solve a thorn I run into
year after year.
2024-10-25 22:07:34 -07:00
Howard Abrams
23efcd2a26 Minor fixes to work better with demonstrating hamacs 2024-10-25 22:05:22 -07:00
Howard Abrams
d816725afc Fix some issues and expand notes on Hippie and the Cape 2024-10-25 21:59:49 -07:00
Howard Abrams
2a70d6c344 Ignoring my theme for my website and writing the style myself.
Plus a bug fix for mail.
2024-10-25 21:59:44 -07:00
13 changed files with 488 additions and 164 deletions

View file

@ -418,25 +418,30 @@ Scattered throughout my configuration, I use =major-mode-hydra-define= where I
The following defines my use of the Emacs completion system. Ive decided my /rules/ will be: The following defines my use of the Emacs completion system. Ive decided my /rules/ will be:
- Nothing should automatically appear; that is annoying and distracting. - Nothing should automatically appear; that is annoying and distracting.
- Spelling in org files (abbrev or hippie expander) and code completion are separate, but Im not sure if I can split them - Spelling in org files (abbrev or hippie expander) and code completion are separate, but Im not sure if I can split them
- IDEs overuse the ~TAB~ binding, and I should re-think the bindings. - IDEs overuse the ~TAB~ binding, and may I should re-think the bindings.
I dont find the Emacs completion system obvious, with different interfaces, some distinct, some connected. Heres the summary as I understand: I dont find the Emacs completion system obvious, with different interfaces, some distinct, some connected. As ~TAB~ is often overloaded. Emacs can have a cascade of functions. Heres the summary as I understand (as well as the overriding keybindings I use):
#+begin_verse
=indent-for-tab-command=, which /we can/ call: #+BEGIN_EXAMPLE
└─ =completion-at-point=, which calls: ╭─▷ indent-for-tab-command ╭───────╮
└─ =completion-at-point-functions= (capf), which can call: │ ╷ ╭──┤ M-TAB │ ╭─────╮
└─ hippie and dabbrev functions │ ╰─▶ completion-at-point ◁──╯ ╰───────╯ │ M-/ │
#+end_verse ╭──┴──╮ (completion-at-point-functions) ╰──┬──╯
│ TAB │ ╷ │
╰─────╯ ╰─▶ hippie and dabbrev ◁──────────────╯
#+END_EXAMPLE
In =org-mode=, ~TAB~ calls [[help:org-cycle][org-cycle]], which is even more overload and context-specific. In the context of typing text, calls the binding for ~TAB~, which is the [[help:indent-for-tab-command][indent-for-tab-command]]. If the line is /indented/, it then completes the word:
In =org-mode=, ~TAB~ calls [[help:org-cycle][org-cycle]], which, in the context of typing text, calls the binding for ~TAB~, which is the [[help:indent-for-tab-command][indent-for-tab-command]]. If the line is /indented/, I can complete the word:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq tab-always-indent 'complete (setq tab-always-indent 'complete
tab-first-completion 'word-or-paren tab-first-completion 'word-or-paren
completion-cycle-threshold nil) completion-cycle-threshold 2)
#+end_src #+end_src
Note that no matter the setting for =tab-first-completion=, hitting ~TAB~ twice, results in completion. Note that no matter the setting for =tab-first-completion=, hitting ~TAB~ twice, results in completion.
This calls [[help:completion-at-point][completion-at-point]]. This code (from mini-buffer) doubles with the other [[Vertico][completing processes]] (like [[help:completing-read][completing-read]]) and presents choices based on a series of functions (see [[https://with-emacs.com/posts/tutorials/customize-completion-at-point/][this essay]] for details). This will call into the CAPF function list (see the variable, =completion-at-point-functions= and the [[file:ha-programming.org::*Cape][Cape]] section for details). This calls [[help:completion-at-point][completion-at-point]]. This code (from mini-buffer) doubles with the other [[Vertico][completing processes]] (like [[help:completing-read][completing-read]]) and presents choices based on a series of functions (see [[https://with-emacs.com/posts/tutorials/customize-completion-at-point/][this essay]] for details). This will call into the CAPF function list (see the variable, =completion-at-point-functions= and the [[*Cape][Cape]] section for details).
*** Hippie Expand *** Hippie Expand
The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so lets swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand][essay]] by Mickey Petersen) with its default key of ~M-/~ (easy to type on the laptop) as well as ~C-Tab~ (easier on mechanical keyboards): The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so lets swap it out (see this [[https://www.masteringemacs.org/article/text-expansion-hippie-expand][essay]] by Mickey Petersen) with its default key of ~M-/~ (easy to type on the laptop) as well as ~C-Tab~ (easier on mechanical keyboards):
#+begin_src emacs-lisp #+begin_src emacs-lisp
@ -445,6 +450,7 @@ The venerable [[help:hippie-expand][hippie-expand]] function does a better job t
#+end_src #+end_src
Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I dont care much for [[help:try-expand-line][try-expand-line]], so that is not on the list. Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I dont care much for [[help:try-expand-line][try-expand-line]], so that is not on the list.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(setq hippie-expand-try-functions-list (setq hippie-expand-try-functions-list
'(try-complete-file-name-partially ; complete filenames, start with / '(try-complete-file-name-partially ; complete filenames, start with /
@ -461,13 +467,42 @@ Details on its job? We need to update its [[help:hippie-expand-try-functions-lis
#+end_src #+end_src
In the shell, IDEs and other systems, the key binding is typically ~TAB~. In modes other than =org-mode=, ~TAB~ re-indents the line with [[help:indent-for-tab-command][indent-for-tab-command]], but I find that I want that feature when Im in Evils =normal state= and hit the ~=~ key, so changing this sounds good. But why not /have both/? In the shell, IDEs and other systems, the key binding is typically ~TAB~. In modes other than =org-mode=, ~TAB~ re-indents the line with [[help:indent-for-tab-command][indent-for-tab-command]], but I find that I want that feature when Im in Evils =normal state= and hit the ~=~ key, so changing this sounds good. But why not /have both/?
#+begin_src emacs-lisp :tangle no
(advice-add #'indent-for-tab-command :after #'hippie-expand) This screws up the [[file:ha-programming-elisp.org::*Lispyville][lispyville]], so I want automatic expansion limited to text files, like Org and Markdown:
#+begin_src emacs-lisp
(defun hippie-expand-in-org (&rest ignored)
"Calls `hippie-expand', limited to text buffers."
(when (derived-mode-p 'text-mode)
(call-interactively #'hippie-expand)))
(advice-add #'indent-for-tab-command :after #'hippie-expand-in-org)
#+end_src #+end_src
*** Cape
The [[https://github.com/minad/cape][Cape project]] deliver particular [[help:completion-at-point][completion-at-point]] functions that can be /hooked/ in. Since I already have the hippie working as I like, I dont need =cape-dabbrev= or =cape-dict= hooked in, but for /general modes/ I will choose the emoji capf:
#+BEGIN_SRC emacs-lisp :tangle no
(use-package cape
:straight (:host github :repo "minad/cape")
:init
(setq completion-at-point-functions (list #'cape-emoji)))
#+END_SRC
Each programming environment might need some particular love. For instance:
#+BEGIN_SRC emacs-lisp
(add-hook 'emacs-lisp-mode :after
(lambda ()
(setq-local completion-at-point-functions
(list #'cape-elisp-symbol))))
#+END_SRC
*** Corfu *** Corfu
The default completion system either inserts the first option directly in the text (without cycling, so lets hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion). The default completion system either inserts the first option directly in the text (without cycling, so lets hope it gets it right the first time), or presents choices in another buffer (who wants to hop to it to select an expansion).
After using [[http://company-mode.github.io/][company]] for my completion back-end, I switched to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation). After using [[http://company-mode.github.io/][company]] for my completion back-end, I switched to [[https://github.com/minad/corfu][corfu]] as it works with the variable-spaced font of my org files (also see [[https://takeonrules.com/2022/01/17/switching-from-company-to-corfu-for-emacs-completion/][this essay]] for my initial motivation).
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package corfu (use-package corfu
:custom :custom
@ -978,7 +1013,6 @@ Since I already (at this point in my file) have Org installed and running, the f
To temporarily read an encrypted part, and call =M-x org-decrypt-entry= when the cursor is inside that section. Saving the file, will re-encrypt it. To temporarily read an encrypted part, and call =M-x org-decrypt-entry= when the cursor is inside that section. Saving the file, will re-encrypt it.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package org (use-package org
:config :config

View file

@ -2,7 +2,7 @@
#+author: Howard X. Abrams #+author: Howard X. Abrams
#+date: 2024-10-18 #+date: 2024-10-18
#+filetags: emacs hamacs #+filetags: emacs hamacs
#+lastmod: [2024-10-19 Sat] #+lastmod: [2024-10-24 Thu]
A literate programming file for creating and running demonstrations A literate programming file for creating and running demonstrations
@ -114,8 +114,8 @@ Instead of executing a sequence of demonstration steps, demonstrations key on
#+BEGIN_SRC emacs-lisp :tangle no #+BEGIN_SRC emacs-lisp :tangle no
(define-ha-demo ha-simple-demo (define-ha-demo ha-simple-demo
(:head "New Demonstration" :i 0) (message "Howdy") (:heading "New Demonstration" :i 0) (message "Howdy")
(:head "New Demonstration" :i 1) (message "Hi there")) (:heading "New Demonstration" :i 1) (message "Hi there"))
(global-set-key (kbd "<f6>") 'ha-simple-demo) (global-set-key (kbd "<f6>") 'ha-simple-demo)
(global-set-key (kbd "<f5>") 'org-present-next) (global-set-key (kbd "<f5>") 'org-present-next)
@ -136,7 +136,7 @@ To make the contents of the expression easier to write, the =define-ha-demo= as
(define-demo demo1 (define-demo demo1
(:buffer \"demonstrations.py\") (message \"In a buffer\") (:buffer \"demonstrations.py\") (message \"In a buffer\")
(:mode 'dired-mode) (message \"In a dired\") (:mode 'dired-mode) (message \"In a dired\")
(:head \"Raven Civilizations\" (message \"In an org file\"))) (:heading \"Raven Civilizations\" (message \"In an org file\")))
Calling `(demo1)' displays a message based on position of the Calling `(demo1)' displays a message based on position of the
point in a particular buffer or place in a heading in an Org file. point in a particular buffer or place in a heading in an Org file.
@ -151,7 +151,7 @@ To make the contents of the expression easier to write, the =define-ha-demo= as
(interactive) (interactive)
(let ((state (list :buffer (buffer-name) (let ((state (list :buffer (buffer-name)
:mode major-mode :mode major-mode
:head (when (eq major-mode 'org-mode) :heading (when (eq major-mode 'org-mode)
(org-get-heading))))) (org-get-heading)))))
(cond (cond
,@(seq-map (lambda (tf-pair) ,@(seq-map (lambda (tf-pair)
@ -214,7 +214,7 @@ Lets test:
'(:a 1 :i 5) '((:a 1) (:b 2) (:c 3)))))) '(:a 1 :i 5) '((:a 1) (:b 2) (:c 3))))))
#+END_SRC #+END_SRC
But can I check if I have triggered a state once before? Lets keep track of the /states/ that have returned true before, in a hash table where the key is the /state/ (a list of =:buffer=, =:mode=, =:head=, etc.) and the /value/ is the number of times triggered at that state: But can I check if I have triggered a state once before? Lets keep track of the /states/ that have returned true before, in a hash table where the key is the /state/ (a list of =:buffer=, =:mode=, =:heading=, etc.) and the /value/ is the number of times triggered at that state:
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(defvar ha-demo-prev-state (make-hash-table :test 'equal) (defvar ha-demo-prev-state (make-hash-table :test 'equal)
@ -239,6 +239,91 @@ Lets create a function that could accept a list of /triggering keys/, and the
(not (seq-difference triggers state))) (not (seq-difference triggers state)))
#+END_SRC #+END_SRC
* Demonstration Support
What sort of functions will I often be doing?
** Display File
Displaying a File with:
- On the side or covering the entire frame
- Larger font size
- Modeline or no modeline
- Going to a particular text or line
- Moving the cursor to the top or middle of the buffer window
All options? Should I use Common Lisps =cl-defun= for the keyword parameters?
#+BEGIN_SRC emacs-lisp
(cl-defun ha-demo-show-file (filename &key position size modeline
line heading shift commands)
"Show a file, FILENAME, in a buffer based on keyed parameters.
POSITION can be 'full 'right or 'below and positions the window.
SIZE is an integer for the font size based on the default size.
MODELINE is shown if non-line, default is to hide it.
LINE is either a line number or a regular expression to match.
SHIFT is the number of lines above the point to show, in case
the LINE shouldn't be at the top of the window.
COMMANDS is a lambda expression that can contain any other
instructions to happen to the buffer display."
(unless position
(setq position :right))
;; Step 1: Create a window
(pcase position
('full )
('right (progn (split-window-horizontally) (other-window 1)))
('below (progn (split-window-vertically) (other-window 1))))
;; We could do :left and :top by not doing the other window bit...
;; Step 2: Load the file
(find-file filename)
(goto-char (point-min))
;; Step 3: Increase the font size
(when size
(text-scale-set size))
(when line
(if (integerp line)
(goto-line line)
(re-search-forward line nil t)))
(when heading
(re-search-forward (rx bol (one-or-more "*") (one-or-more space)
(literal heading))
nil t))
;; If SHIFT is positive integer, left that many line above point,
;; otherwise don't do anything to leave it in the middle.
;; If SHIFT is null, move it to the top of the buffer window:
(if shift
(when (integerp shift)
(recenter-top-bottom shift))
(recenter-top-bottom 0))
(unless modeline
(setq-local mode-line-format nil))
(when commands (funcall commands))
)
(funcall (lambda () (message "Hello")))
#+END_SRC
Let try it all together:
#+BEGIN_SRC emacs-lisp :tangle no
(ha-demo-show-file "ha-config.org" :position 'right :size 1 :modeline nil :line 418 :shift 4)
#+END_SRC
Or:
#+BEGIN_SRC emacs-lisp :tangle no
(ha-demo-show-file "ha-config.org" :modeline t
:heading "Text Expanders"
:commands (lambda () (jinx-mode -1)))
#+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:

View file

@ -64,6 +64,11 @@ To use these, we set the =:noweb yes= (to pull in the /name/ of the code block)
smtpmail-smtp-server "smtp.gmail.com" smtpmail-smtp-server "smtp.gmail.com"
smtpmail-smtp-service 587) smtpmail-smtp-service 587)
#+end_src #+end_src
* Sending Mail
#+BEGIN_SRC emacs-lisp
(require 'smtpmail)
#+END_SRC
* Installation and Basic Configuration * Installation and Basic Configuration
To begin, we need the code. On Ubuntu, this is: To begin, we need the code. On Ubuntu, this is:
#+begin_src shell :tangle no #+begin_src shell :tangle no
@ -304,17 +309,17 @@ The following option is supported here:
exclude_tags=deleted;spam; exclude_tags=deleted;spam;
#+end_src #+end_src
*** Maildir compatibility configuration *** Maildir compatibility configuration
The following option is supported here: This section support the following option:
- =synchronize_flags= :: Valid values are true and false. If true, then the following maildir flags (in message filenames) will be synchronized with the corresponding notmuch tags: - =synchronize_flags= :: Valid values are true and false. If true, then notmuch synchronizing the following maildir flags (in message filenames) with the corresponding notmuch tags:
| Flag | Tag | | Flag | Tag |
|------+---------------------------------------------| |------+---------------------------------------------|
| D | draft | | D | draft |
| F | flagged | | F | flagged |
| P | passed | | P | passed |
| R | replied | | R | replied |
| S | unread (added when 'S' flag is not present) | | S | unread (added when 'S' flag is not present) |
The =notmuch new= command will notice flag changes in filenames and update tags, while the =notmuch tag= and =notmuch restore= commands will notice tag changes and update flags in filenames. The =notmuch new= command will notice flag changes in filenames and update tags, while the =notmuch tag= and =notmuch restore= commands will notice tag changes and update flags in filenames.
@ -325,7 +330,7 @@ The =notmuch new= command will notice flag changes in filenames and update tags,
That should complete the Notmuch configuration. That should complete the Notmuch configuration.
** =pre-new= ** =pre-new=
Then we need a shell script called when beginning a retrieval, =pre-new= that simply calls =mbsync= to download all the messages: We call this shell script when beginning a retrieval, =pre-new= that calls =mbsync= to download all the messages:
#+begin_src shell :tangle ~/.mail/.notmuch/hooks/pre-new :shebang "#!/bin/bash" #+begin_src shell :tangle ~/.mail/.notmuch/hooks/pre-new :shebang "#!/bin/bash"
# More info about hooks: https://notmuchmail.org/manpages/notmuch-hooks-5/ # More info about hooks: https://notmuchmail.org/manpages/notmuch-hooks-5/
@ -344,11 +349,9 @@ And a =post-new= hook based on a filtering scheme that mimics the Hey.com workfl
# Based On: https://gist.githubusercontent.com/frozencemetery/5042526/raw/57195ba748e336de80c27519fe66e428e5003ab8/post-new # Based On: https://gist.githubusercontent.com/frozencemetery/5042526/raw/57195ba748e336de80c27519fe66e428e5003ab8/post-new
# Note: We now tangle this file from ~/src/hamacs/ha-email.org # Note: We now tangle this file from ~/src/hamacs/ha-email.org
# #
# Install this by moving this file to <maildir>/.notmuch/hooks/post-new # Create empty files for:
# NOTE: you need to define your maildir in the vardiable nm_maildir (just a few lines below in this script) # 1. thefeed.db (mail you want to read every once in a while)
# Also create empty files for: # 2. spam.db (mail you never want to see)
# 1. thefeed.db (things you want to read every once in a while)
# 2. spam.db (things you never want to see)
# 3. screened.db (your inbox) # 3. screened.db (your inbox)
# 4. ledger.db (papertrail) # 4. ledger.db (papertrail)
# in the hooks folder. # in the hooks folder.
@ -422,8 +425,6 @@ do
done done
timer_end "Screened" timer_end "Screened"
# Projects...
timer_start "Old-Projects" timer_start "Old-Projects"
notmuch tag +old-project 'subject:/.*howardabrams\/node-mocks-http/' notmuch tag +old-project 'subject:/.*howardabrams\/node-mocks-http/'
notmuch tag +old-project 'subject:/.*Pigmice2733/' notmuch tag +old-project 'subject:/.*Pigmice2733/'
@ -434,12 +435,12 @@ notmuch tag +screened 'subject:[Web]'
echo "Completing not-much 'post-new' script" echo "Completing not-much 'post-new' script"
#+end_src #+end_src
* Hey * Hey
I originally took the following configuration from [[https://youtu.be/wuSPssykPtE][Vedang Manerikar's video]], along with [[https://gist.github.com/vedang/26a94c459c46e45bc3a9ec935457c80f][the code]]. The ideas brought out were to mimic the hey.com email workflow, and while not bad, I thought that maybe I could improve upon it slowly over time. I originally took the following configuration from [[https://youtu.be/wuSPssykPtE][Vedang Manerikar's video]], along with [[https://gist.github.com/vedang/26a94c459c46e45bc3a9ec935457c80f][the code]]. The ideas brought out were to mimic the hey.com email workflow, and while not bad, I thought I could improve it over time.
To allow me to keep Vedang's and my code side-by-side in the same Emacs variable state, I have renamed the prefix to =hey-=, however, if you are looking to steal my code, you may want to revisit the source. To allow me to keep Vedang's and my code side-by-side in the same Emacs variable state, I have renamed the prefix to =hey-=, buf, if you are looking to steal my code, you may want to revisit the original source.
** Default Searches ** Default Searches
A list of pre-defined searches act like "Folder buttons" at the top to quickly see files that match those /buckets/: A list of pre-defined searches act like "Folder buttons" at the top to see files that match those /buckets/:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package notmuch (use-package notmuch
@ -451,6 +452,7 @@ A list of pre-defined searches act like "Folder buttons" at the top to quickly s
(:name "Previously Seen" (:name "Previously Seen"
:query "tag:screened AND NOT tag:unread" :query "tag:screened AND NOT tag:unread"
:key "I") :key "I")
(:name "Sent" :query "tag:sent")
(:name "Unscreened" (:name "Unscreened"
:query "tag:inbox AND tag:unread AND NOT tag:screened AND NOT date:..14d AND NOT tag:thefeed AND NOT tag:/ledger/ AND NOT tag:old-project" :query "tag:inbox AND tag:unread AND NOT tag:screened AND NOT date:..14d AND NOT tag:thefeed AND NOT tag:/ledger/ AND NOT tag:old-project"
:key "s") :key "s")
@ -479,7 +481,7 @@ A list of pre-defined searches act like "Folder buttons" at the top to quickly s
#+end_src #+end_src
** Helper Functions ** Helper Functions
With good bucket definitions, we should be able to scan the mail quickly and deal with the entire lot of them: With good bucket definitions, we should be able to scan the mail and deal with the entire lot:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun hey-notmuch-archive-all () (defun hey-notmuch-archive-all ()
@ -495,7 +497,7 @@ With good bucket definitions, we should be able to scan the mail quickly and dea
(hey-notmuch-archive-all)) (hey-notmuch-archive-all))
(defun hey-notmuch-search-delete-and-archive-thread () (defun hey-notmuch-search-delete-and-archive-thread ()
"Archive the currently selected thread. Add the deleted tag as well." "Archive the selected thread. Add the deleted tag as well."
(interactive) (interactive)
(notmuch-search-add-tag '("+deleted")) (notmuch-search-add-tag '("+deleted"))
(notmuch-search-archive-thread)) (notmuch-search-archive-thread))
@ -526,38 +528,41 @@ A key point in organizing emails with the Hey model, is looking at the "from" ad
And we can create a filter, /search/ and tagging based on this "from" function: And we can create a filter, /search/ and tagging based on this "from" function:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun hey-notmuch-filter-by-from () (defun hey-notmuch-filter-by-from ()
"Filter the current search view to show all emails sent from the sender of the current thread." "Filter the current search view to show all emails sent from the sender of the current thread."
(interactive) (interactive)
(notmuch-search-filter (concat "from:" (hey-notmuch-search-find-from)))) (notmuch-search-filter (concat "from:" (hey-notmuch-search-find-from))))
(defun hey-notmuch-search-by-from (&optional no-display) (defun hey-notmuch-search-by-from (&optional no-display)
"Show all emails sent from the sender of the current thread. "Show all emails sent from the sender of the current thread.
NO-DISPLAY is sent forward to `notmuch-search'." NO-DISPLAY is sent forward to `notmuch-search'."
(interactive) (interactive)
(notmuch-search (concat "from:" (hey-notmuch-search-find-from)) (notmuch-search (concat "from:" (hey-notmuch-search-find-from))
notmuch-search-oldest-first nil nil no-display)) notmuch-search-oldest-first nil nil no-display))
(defun hey-notmuch-tag-by-from (tag-changes &optional beg end refresh) (defun hey-notmuch-tag-by-from (tag-changes &optional beg end refresh)
"Apply TAG-CHANGES to all emails from the sender of the current thread. "Apply TAG-CHANGES to all emails from the sender of the current thread.
BEG and END provide the region, but are ignored. They are defined
since `notmuch-search-interactive-tag-changes' returns them. If While defined (since `notmuch-search-interactive-tag-changes'
REFRESH is true, refresh the buffer from which we started the returns them), this function ignores BEG and END (for the
search." region).
(interactive (notmuch-search-interactive-tag-changes))
(let ((this-buf (current-buffer))) If REFRESH is true, refresh the buffer from which we started the
(hey-notmuch-search-by-from t) search."
;; This is a dirty hack since I can't find a way to run a (interactive (notmuch-search-interactive-tag-changes))
;; temporary hook on `notmuch-search' completion. So instead of (let ((this-buf (current-buffer)))
;; waiting on the search to complete in the background and then (hey-notmuch-search-by-from t)
;; making tag-changes on it, I will just sleep for a short amount ;; This is a dirty hack since I can't find a way to run a
;; of time. This is generally good enough and works, but is not ;; temporary hook on `notmuch-search' completion. So instead of
;; guaranteed to work every time. I'm fine with this. ;; waiting on the search to complete in the background and then
(sleep-for 0.5) ;; making tag-changes on it, I sleep for a short amount of time.
(notmuch-search-tag-all tag-changes) ;; This is generally good enough and works, but is not guaranteed
(when refresh ;; to work every time. I'm fine with this.
(set-buffer this-buf) (sleep-for 0.5)
(notmuch-refresh-this-buffer)))) (notmuch-search-tag-all tag-changes)
(when refresh
(set-buffer this-buf)
(notmuch-refresh-this-buffer))))
#+end_src #+end_src
** Moving Mail to Buckets ** Moving Mail to Buckets
@ -573,8 +578,8 @@ We based the Hey buckets on notmuch databases, we combine the =hey-notmuch-add-a
"For the email at point, move the sender of that email to the feed. "For the email at point, move the sender of that email to the feed.
This means: This means:
1. All new email should go to the feed and skip the inbox altogether. 1. All new email should go to the feed and skip the inbox altogether.
2. All existing email should be updated with the tag =thefeed=. 2. All existing email updated with the tag `thefeed'.
3. All existing email should be removed from the inbox." 3. All existing email removed from the inbox."
(interactive) (interactive)
(hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from) (hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from)
(format "%s/thefeed.db" notmuch-hooks-dir)) (format "%s/thefeed.db" notmuch-hooks-dir))
@ -584,8 +589,8 @@ This means:
"For the email at point, move the sender of that email to the papertrail. "For the email at point, move the sender of that email to the papertrail.
This means: This means:
1. All new email should go to the papertrail and skip the inbox altogether. 1. All new email should go to the papertrail and skip the inbox altogether.
2. All existing email should be updated with the tag =ledger/TAG-NAME=. 2. All existing email updated with the tag `ledger/TAG-NAME'.
3. All existing email should be removed from the inbox." 3. All existing email removed from the inbox."
(interactive "sTag Name: ") (interactive "sTag Name: ")
(hey-notmuch-add-addr-to-db (format "%s %s" (hey-notmuch-add-addr-to-db (format "%s %s"
tag-name tag-name
@ -597,8 +602,8 @@ This means:
(defun hey-notmuch-move-sender-to-screened () (defun hey-notmuch-move-sender-to-screened ()
"For the email at point, move the sender of that email to Screened Emails. "For the email at point, move the sender of that email to Screened Emails.
This means: This means:
1. All new email should be tagged =screened= and show up in the inbox. 1. All new email tagged `screened' and show up in the inbox.
2. All existing email should be updated to add the tag =screened=." 2. All existing email updated to add the tag `screened'."
(interactive) (interactive)
(hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from) (hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from)
(format "%s/screened.db" notmuch-hooks-dir)) (format "%s/screened.db" notmuch-hooks-dir))
@ -759,3 +764,7 @@ Let's =provide= a name so we can =require= this file:
#+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
# Local Variables:
# jinx-local-words: "notmuch"
# End:

View file

@ -73,12 +73,14 @@ If any program wants to pause the output through the =$PAGER= variable, well, we
#+end_src #+end_src
** Argument Completion ** Argument Completion
Shell completion uses the flexible =pcomplete= mechanism internally, which allows you to program the completions per shell command. To know more, check out this [[https://www.masteringemacs.org/article/pcomplete-context-sensitive-completion-emacs][blog post]], about how to configure =pcomplete= for git commands. The [[https://github.com/JonWaltman/pcmpl-args.el][pcmpl-args]] package extends =pcomplete= with completion support for more commands, like the Fish and other modern shells. I love how a package can gives benefits without requiring learning anything. Shell completion uses the flexible =pcomplete= mechanism internally, which allows you to program the completions per shell command. To know more, check out this [[https://www.masteringemacs.org/article/pcomplete-context-sensitive-completion-emacs][blog post]], about how to configure =pcomplete= for git commands. The [[https://github.com/JonWaltman/pcmpl-args.el][pcmpl-args]] package extends =pcomplete= with completion support for more commands, like the Fish and other modern shells. I love how a package can gives benefits without requiring learning anything.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package pcmpl-args) (use-package pcmpl-args)
#+end_src #+end_src
Note that this will work with =shell-command= as well. Note that this will work with =shell-command= as well.
** Better Command Line History ** Better Command Line History
On [[http://www.reddit.com/r/emacs/comments/1zkj2d/advanced_usage_of_eshell/][this discussion]] a little gem for using IDO to search back through the history, instead of =M-R= to prompt for the history. On [[http://www.reddit.com/r/emacs/comments/1zkj2d/advanced_usage_of_eshell/][this discussion]] a little gem for using IDO to search back through the history, instead of =M-R= to prompt for the history.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun eshell-insert-history () (defun eshell-insert-history ()
"Displays the eshell history to select and insert back into your eshell." "Displays the eshell history to select and insert back into your eshell."
@ -92,7 +94,7 @@ This is a cool and under-rated feature of Eshell. Type the following commands in
- ~eshell-display-modifier-help~ - ~eshell-display-modifier-help~
- ~eshell-display-predicate-help~ - ~eshell-display-predicate-help~
In short, use parens to limit the files, for instance: In short, use parenthesis to limit the files, for instance:
#+begin_src sh #+begin_src sh
ls *.sh(*) # List shell scripts that are executable ls *.sh(*) # List shell scripts that are executable
ls *(/) # List directories (recursively) ls *(/) # List directories (recursively)

View file

@ -91,31 +91,39 @@ Im not a long term VI user, and I generally like /easy keys/, e.g. ~w~, have
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package evil (use-package evil
:config :config (require 'evil-commands)
(require 'evil-commands) (evil-define-key
(evil-define-key '(normal visual motion operator) 'global '(normal visual motion operator) 'global
"w" 'evil-forward-WORD-begin "w" 'evil-forward-WORD-begin
"W" 'evil-forward-word-begin "W" 'evil-forward-word-begin
"e" 'evil-forward-WORD-end "e" 'evil-forward-WORD-end
"E" 'evil-forward-word-end "E" 'evil-forward-word-end)
#+END_SRC
;; This may be an absolute heresy to most VI users, The ~b~ key bindings seems to need their own call, and Im not sure why I cant include it in the ~w~ and ~e~ bindings.
;; but I'm Evil and really, I use M-x and SPC instead.
;; Besides, I don't know any : colon commands...
":" 'evil-repeat-find-char-reverse)
;; The `b' key seems to need its own configuration setting: #+BEGIN_SRC emacs-lisp
(evil-define-key '(normal visual motion operator) 'global (evil-define-key
"b" 'evil-backward-WORD-begin) '(normal visual motion operator) 'global
(evil-define-key '(normal visual motion operator) 'global "b" 'evil-backward-WORD-begin)
"B" 'evil-backward-word-begin)) (evil-define-key
;; Note that evil-backward-word-end is on the `g e': '(normal visual motion operator) 'global
"B" 'evil-backward-word-begin)
#+end_src #+end_src
In other words, with the above settings in place, ~w~ and ~e~ should jump from front to back of the entire line, but ~W~ and ~E~ should stop as /subword/: In other words, with the above settings in place, ~w~ and ~e~ should jump from front to back of the entire line, but ~W~ and ~E~ should stop as /subword/:
- =word-subword-subword= - =word-subword-subword=
- =word_subword_subword= - =word_subword_subword=
Note I dont bind =evil-backward-word-end= with a single key, but bind it to ~g e~ below.
While an absolute heresy to most VI users, I'm Evil and use ~M-x~ and ~SPC~ instead of ~:~ for running commands. I bind the ~:~ as a reverse of the ~;~ which continues the search from ~f~ and ~t~:
#+BEGIN_SRC emacs-lisp
(evil-define-key
'(normal visual motion operator) 'global
":" 'evil-repeat-find-char-reverse))
#+END_SRC
This clever hack from [[https://manueluberti.eu//emacs/2022/10/16/back-last-edit/][Manuel Uberti]] got me finding these useful bindings: This clever hack from [[https://manueluberti.eu//emacs/2022/10/16/back-last-edit/][Manuel Uberti]] got me finding these useful bindings:
- ~g ;~ :: [[help:goto-last-change][goto-last-change]] - ~g ;~ :: [[help:goto-last-change][goto-last-change]]
- ~g ,~ :: [[help:goto-last-change-reverse][goto-last-change-reverse]] - ~g ,~ :: [[help:goto-last-change-reverse][goto-last-change-reverse]]

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: [2024-07-26 Fri] #+lastmod: [2024-10-27 Sun]
A literate programming file for literate programming in Emacs Org Files. A literate programming file for literate programming in Emacs Org Files.
@ -47,6 +47,126 @@ If literate programming is so great, why doesnt everyone do it? Most of the /
Since we are using Emacs, the downsides of using a literate approach (even for personal projects) can be minimized. Since we are using Emacs, the downsides of using a literate approach (even for personal projects) can be minimized.
*Note:* What follows is advanced LP usage. I would recommend checking out my [[https://www.howardism.org/Technical/Emacs/literate-programming-tutorial.html][Introduction to LP in Org]] before venturing down this essay. *Note:* What follows is advanced LP usage. I would recommend checking out my [[https://www.howardism.org/Technical/Emacs/literate-programming-tutorial.html][Introduction to LP in Org]] before venturing down this essay.
* Snippets
** 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
# name: surround-org-block
# --
,#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
`yas-selected-text`
,#+END_$1
#+END_SRC
Without a key (that is intentional), I can call the snippet with ~C-c & C-s~ (ugh).
** Split an Org Block
What is the /language/ or /major-mode/ associated with the “current” block?
#+BEGIN_SRC emacs-lisp
(defun ha-org-block-language ()
"Return the language associated with the current block."
(save-excursion
(re-search-backward (rx bol (zero-or-more space)
"#+begin_src"
(one-or-more space)
(group (one-or-more (not space)))))
(match-string 1)))
#+END_SRC
Actually, I probably want all the parameters (if any) to come along for the ride:
#+BEGIN_SRC emacs-lisp
(defun ha-org-block-language-and ()
"Return language and parameters of the current block."
(save-excursion
(re-search-backward (rx bol (zero-or-more space)
"#+begin_src"
(one-or-more space)
(group (one-or-more any))))
(match-string 1)))
#+END_SRC
This allows us to now split a block into two parts:
#+BEGIN_SRC snippet :tangle ~/.emacs.d/snippets/org-mode/split-org-block
,# -*- mode: snippet -*-
,# name: split-org-block
,# key: #split
,# --
,#+END_SRC
`yas-selected-text`
,#+BEGIN_SRC `(ha-org-block-language-and)`
#+END_SRC
* Visit Tangled File
After tangling a file, you might want to take a look at it. But a single org file can tangle out to more than one file, so how to choose? Each block could have its one file, as would the particular language-specific blocks in a section or the entire file. I dont care to be too pendantic here, because I often know the name of the file.
Perhaps a little function to map a /language/ to a file name:
#+BEGIN_SRC emacs-lisp
(defun ha-org-babel-tangle-default (language)
"Return the default tangled file based on LANGUAGE."
(format "%s.%s"
(file-name-sans-extension (buffer-file-name))
(pcase language
("emacs-lisp" "el")
("elisp" "el")
("python" "py")
("ruby" "rb")
;; ...
("bash" "sh")
("sh" "sh")
("shell" "sh"))))
#+END_SRC
This function returns the name of the tangled file based on either the block or a default name based on the file name:
#+BEGIN_SRC emacs-lisp
(defun ha-org-babel-tangle-file-name ()
"Return _potential_ file name or Org source.
If nearest block has a `:tangle' entry, return that.
If block doesn't have `:tangle' (or set to `yes'),
return the filename based on the block's language."
(save-excursion
(org-previous-block 1)
(when (looking-at
(rx "#+begin_src"
(one-or-more space)
(group (one-or-more (not space)))
(one-or-more space) (zero-or-more any)
(optional
(group ":tangle" (one-or-more space)
(optional "\"")
(group (one-or-more any))
(optional "\"")))))
(let ((language (match-string 1))
(filename (match-string 3)))
(if (or (null filename) (equal filename "yes") (equal filename "true"))
(ha-org-babel-tangle-default language)
filename)))))
#+END_SRC
And this function calls =find-file= on the tangled file (if found):
#+BEGIN_SRC emacs-lisp
(defun ha-org-babel-tangle-visit-file ()
"Attempt to call `find-file' on Org's tangled file."
(interactive)
(let ((tangled-file (ha-org-babel-tangle-file-name)))
(if (file-exists-p tangled-file)
(find-file tangled-file)
(message "Looking for %s, which doesn't exist." tangled-file))))
#+END_SRC
*Note:* You can /return/ to the original Org file with =org-babel-tangle-jump-to-org=, if you set the tangle =comments= to =link=, as in this global property:
#+begin_example
,#+PROPERTY: header-args:emacs-lisp :tangle yes :comments link
#+end_example
* Navigating Code Blocks * Navigating Code Blocks
:PROPERTIES: :PROPERTIES:
:ID: 3230b1f4-0d2d-47c7-9f3d-fa53083f8c8d :ID: 3230b1f4-0d2d-47c7-9f3d-fa53083f8c8d
@ -98,22 +218,22 @@ TODO Screenshot of multiple highlighted blocks.
:PROPERTIES: :PROPERTIES:
:ID: 188e378c-bed4-463c-98d4-d22be1845bc2 :ID: 188e378c-bed4-463c-98d4-d22be1845bc2
:END: :END:
A trick to =org-babel-tangle=, is that it tangles /what is shown/, that is, it will only tangle code blocks that are visible after narrowing to the current org section. This means, we can call =org-narrow-to-subtree= to temporary hide everything in the org file except the current heading, evaluate all blocks in the “now visible” buffer, and then widen: A trick to =org-babel-tangle=, is that it tangles /what Emacs shows/, that is, it tangles /visible/ code blocks after narrowing to the current org section. This means, we can call =org-narrow-to-subtree= to temporary hide everything in the org file except the current heading, evaluate all blocks in the “now visible” buffer, and then widen:
#+begin_src emacs-lisp :results silent #+begin_src emacs-lisp :results silent
(defun org-babel-execute-subtree () (defun org-babel-execute-subtree (prefix)
"Execute all Org source blocks in current subtree." "Execute all Org source blocks in current subtree."
(interactive "P") (interactive "P")
(save-excursion (save-excursion
(org-narrow-to-subtree) (org-narrow-to-subtree)
(org-babel-execute-buffer) (org-babel-execute-buffer prefix)
(widen))) (widen)))
#+end_src #+end_src
** Editing a Block ** Editing a Block
:PROPERTIES: :PROPERTIES:
:ID: f143bbd6-fb4d-45b8-bcfa-196c7a26ed34 :ID: f143bbd6-fb4d-45b8-bcfa-196c7a26ed34
:END: :END:
Why navigate to a block, just to focus on that block in a dedicated buffer, when we can take advantage of the =avy-jump= and edit any visible block? Why navigate to a block, solely to focus on that block in a dedicated buffer, when we can take advantage of the =avy-jump= and edit any visible block?
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun org-babel-edit-src-block-at-point (&optional point) (defun org-babel-edit-src-block-at-point (&optional point)
@ -180,10 +300,10 @@ Examples of references in an Org file that should work:
- “ha-literate-symbol-at-point” - “ha-literate-symbol-at-point”
- `ha-literate-symbol-at-point` - `ha-literate-symbol-at-point`
This magical incantation connects our function to Xref with an =org-babel= backend: This magical incantation connects our function to Xref with an =org= backend:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(cl-defmethod xref-backend-identifier-at-point ((_backend (eql org-babel))) (cl-defmethod xref-backend-identifier-at-point ((_backend (eql org)))
(ha-literate-symbol-at-point)) (ha-literate-symbol-at-point))
#+end_src #+end_src
*** Calling ripgrep *** Calling ripgrep
@ -198,9 +318,9 @@ This helper function does the work of calling =ripgrep=, parsing its output, and
(project-root (project-current)) (project-root (project-current))
default-directory)) default-directory))
(search-str (rxt-elisp-to-pcre regex)) (search-str (rxt-elisp-to-pcre regex))
(command (format "rg --json '%s' *.org" search-str))) (command (format "rg --json --type org '%s'" search-str)))
(message "Calling %s" command) (message "Literate xref Calling: %s" command)
(thread-last command (thread-last command
(shell-command-to-list) (shell-command-to-list)
(seq-map 'ha-literate--parse-rg-line) (seq-map 'ha-literate--parse-rg-line)
@ -224,23 +344,54 @@ The output from =ripgrep= goes through a couple of transformation functions list
"Return non-nil if JSON-DATA is an alist with key `type' and value `match'." "Return non-nil if JSON-DATA is an alist with key `type' and value `match'."
(string-equal "match" (alist-get 'type json-data))) (string-equal "match" (alist-get 'type json-data)))
#+end_src #+end_src
TODO Relative Filenames
Since our =ripgrep= searches from the /project root/, but xref wants to make file references relative to the buffer that is calling it, we need to make some changes:
#+BEGIN_SRC emacs-lisp
(defun ha-literate-make-xref-file (filepath)
"Return FILEPATH relative to current buffer's file."
(let ((abspath (expand-file-name filepath
(if (project-current)
(project-root (project-current))
default-directory)))
(relative-to (file-name-parent-directory (buffer-file-name))))
(file-relative-name abspath relative-to))))
#+END_SRC
Lets test this function:
#+BEGIN_SRC emacs-lisp :tangle no
(ert-deftest ha-literate-make-xref-file-test ()
;; Current directory
(should (equal (ha-literate-make-xref-file "ha-display.org")
"ha-display.org"))
;; Subdirectory
(should (equal (ha-literate-make-xref-file "elisp/beep.el")
"elisp/beep.el"))
(should (equal (ha-literate-make-xref-file "~/foo/bar.org")
"../../foo/bar.org")))
#+END_SRC
*** Definitions *** Definitions
As mentioned above, lets assume we can use =ripgrep= to search for /definitions/ in Lisp. I choose that because most of my literate programming is in Emacs Lisp. This regular expression should work with things like =defun= and =defvar=, etc. as well as =use-package=, allowing me to search for the /definition/ of an Emacs package: As mentioned above, lets assume we can use =ripgrep= to search for /definitions/ in Lisp. I choose that because most of my literate programming is in Emacs Lisp. This regular expression should work with things like =defun= and =defvar=, etc. as well as =use-package=, allowing me to search for the /definition/ of an Emacs package:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-literate-definition-rx (symb)
"Return a regular expression to search for definition of SYMB."
(rx "("
(or "use-package"
(seq ; Match both defun and cl-defun:
(optional "cl-")
"def" (1+ (not space))))
(one-or-more space)
(literal symb)
word-boundary))
(defun ha-literate-definition (symb) (defun ha-literate-definition (symb)
"Return list of `xref' objects of SYMB location in org files. "Return list of `xref' objects of SYMB location in org files.
The location is based on a regular expression starting with The location is based on a regular expression starting with
`(defxyz SYMB' where this can be `defun' or `defvar', etc." `(defxyz SYMB' where this can be `defun' or `defvar', etc."
(ha-literate--ripgrep-matches 'ha-literate--process-rg-line (ha-literate--ripgrep-matches 'ha-literate--process-rg-line
(rx "(" (ha-literate-definition-rx symb)))
(or "use-package"
(seq ; Match both defun and cl-defun:
(optional "cl-")
"def" (1+ (not space))))
(one-or-more space)
(literal symb)
word-boundary)))
#+end_src #+end_src
The work of processing a match for the =ha-literate-definition= function. It calls =xref-make= to create an object for the Xref system. This takes two parameters, the text and the location. We create a location with =xref-make-file-location=. The work of processing a match for the =ha-literate-definition= function. It calls =xref-make= to create an object for the Xref system. This takes two parameters, the text and the location. We create a location with =xref-make-file-location=.
@ -248,24 +399,29 @@ The work of processing a match for the =ha-literate-definition= function. It cal
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-literate--process-rg-line (rg-data-line) (defun ha-literate--process-rg-line (rg-data-line)
"Return an `xref' structure based on the contents of RG-DATA-LINE. "Return an `xref' structure based on the contents of RG-DATA-LINE.
The RG-DATA-LINE is a convert JSON data object from ripgrep. The RG-DATA-LINE is a convert JSON data object from ripgrep.
The return data comes from `xref-make' and `xref-make-file-location'." The return data comes from `xref-make' and `xref-make-file-location'."
(when rg-data-line (when rg-data-line
(let-alist rg-data-line (let-alist rg-data-line
;; (message "xref-make %s" .data.path.text)
(xref-make .data.lines.text (xref-make .data.lines.text
(xref-make-file-location .data.path.text (xref-make-file-location
.data.line_number ;; Relative filename:
(thread-last (ha-literate-make-xref-file .data.path.text)
(first .data.submatches) ;; Line number:
(alist-get 'start))))))) .data.line_number
;; Column: Icky to parse:
(thread-last
(first .data.submatches)
(alist-get 'start)))))))
#+end_src #+end_src
I really like the use of =let-alist= where the output from JSON can be parsed into a data structure that can then be accessible via /variables/, like =.data.path.text=. I like the use of =let-alist= where I can access the /parsed/ output from JSON via /variables/, like =.data.path.text=.
We connect this function to the =xref-backend-definitions= list, so that it can be called when we type something like ~M-.~: We connect this function to the =xref-backend-definitions= list, so that it can be called when we type something like ~M-.~:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(cl-defmethod xref-backend-definitions ((_backend (eql org-babel)) symbol) (cl-defmethod xref-backend-definitions ((_backend (eql org)) symbol)
(ha-literate-definition symbol)) (ha-literate-definition symbol))
#+end_src #+end_src
*** Apropos *** Apropos
@ -285,7 +441,7 @@ The /apropos/ approach is anything, so the regular expression here is just the s
And this to /hook it up/: And this to /hook it up/:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(cl-defmethod xref-backend-apropos ((_backend (eql org-babel)) symbol) (cl-defmethod xref-backend-apropos ((_backend (eql org)) symbol)
(ha-literate-apropos symbol)) (ha-literate-apropos symbol))
#+end_src #+end_src
*** References *** References
@ -383,7 +539,7 @@ The helper function, =ha-literate--process-in-block= is a /recursive/ function t
Lets connect the plumbing: Lets connect the plumbing:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(cl-defmethod xref-backend-references ((_backend (eql org-babel)) symbol) (cl-defmethod xref-backend-references ((_backend (eql org)) symbol)
(ha-literate-references symbol)) (ha-literate-references symbol))
#+end_src #+end_src
@ -398,7 +554,7 @@ Need the completion table before we can find the references. It actually doesn
Now we /hook this up/ to the rest of the system: Now we /hook this up/ to the rest of the system:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(cl-defmethod xref-backend-identifier-completion-table ((_backend (eql org-babel))) (cl-defmethod xref-backend-identifier-completion-table ((_backend (eql org)))
(ha-literate-completion-table)) (ha-literate-completion-table))
#+end_src #+end_src
*** Activation of my Literate Searching *** Activation of my Literate Searching
@ -409,14 +565,16 @@ To finish the connections, we need to create a /hook/ that I only allow to turn
"Function to activate org-based literate backend. "Function to activate org-based literate backend.
Add this function to `xref-backend-functions' hook. " Add this function to `xref-backend-functions' hook. "
(when (eq major-mode 'org-mode) (when (eq major-mode 'org-mode)
'org-babel)) 'org))
(add-hook 'xref-backend-functions #'ha-literate-xref-activate) ;; Add this hook to the beginning, as we want to call our
;; backend reference before dumb-jump:
(add-hook 'xref-backend-functions #'ha-literate-xref-activate -100)
#+end_src #+end_src
At this point, we can jump to functions and variables that I define in my org file, or even references to standard symbols like =xref-make= or =xref-backend-functions=. At this point, we can jump to functions and variables that I define in my org file, or even references to standard symbols like =xref-make= or =xref-backend-functions=.
This is seriously cool to be able to jump around my literate code as if it were =.el= files. I may want to think about expanding the definitions to figure out the language of the destination. I can jump around my literate code as if they were =.el= files. I may want to think about expanding the definitions to figure out the language of the destination.
** Searching by Header ** Searching by Header
:PROPERTIES: :PROPERTIES:
:ID: de536693-f0b0-48d0-9b13-c29d7a8caa62 :ID: de536693-f0b0-48d0-9b13-c29d7a8caa62
@ -715,7 +873,8 @@ With a lovely collection of functions, we need to have a way to easily call them
("f" org-babel-tangle-file "choose File") ("f" org-babel-tangle-file "choose File")
("T" org-babel-detangle "from File")) ("T" org-babel-detangle "from File"))
"Misc" "Misc"
(("e" avy-org-babel-edit-src-block "Edit Block ")))) (("e" avy-org-babel-edit-src-block "Edit Block ")
("v" ha-org-babel-tangle-visit-file "Visit Tangled"))))
#+end_src #+end_src
And tie this hydra into the existing leader system: And tie this hydra into the existing leader system:
@ -733,7 +892,7 @@ Let's =provide= a name so we can =require= this file:
#+DESCRIPTION: literate programming in Emacs Org Files. #+DESCRIPTION: literate programming in Emacs Org Files.
#+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 :comments link
#+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

View file

@ -41,7 +41,7 @@ Render my code with my font colors:
#+begin_src emacs-lisp :results silent #+begin_src emacs-lisp :results silent
(use-package htmlize (use-package htmlize
:conf :config
;; But I turn the source code coloring off to use a CSS stylesheet I ;; But I turn the source code coloring off to use a CSS stylesheet I
;; control, as otherwise, switching between dark and light themes ;; control, as otherwise, switching between dark and light themes
;; make the code difficult to read: ;; make the code difficult to read:
@ -85,6 +85,9 @@ Years of having two domain names can be confusing, but I have to keep them going
:publishing-directory ,ha-publishing-howardabrams)) :publishing-directory ,ha-publishing-howardabrams))
#+end_src #+end_src
** The Blog ** The Blog
:PROPERTIES:
:ID: 7c4cb568-93a5-44e8-8758-4f415dccf232
:END:
My main blog made up a collection of org files: My main blog made up a collection of org files:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(add-to-list 'org-publish-project-alist (add-to-list 'org-publish-project-alist
@ -118,7 +121,11 @@ My main blog made up a collection of org files:
;; :html-postamble "<hr><div id='comments'></div>" ;; :html-postamble "<hr><div id='comments'></div>"
:html-head-extra :html-head-extra
,(jack-html ,(jack-html
'((:link (@ :rel "stylesheet" '((:link (@ :rel "alternate"
:type "application/rss+xml"
:title "RSS"
:href "https://www.howardism.org/index.xml"))
(:link (@ :rel "stylesheet"
:type "text/css" :type "text/css"
:href "http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700&subset=latin,latin-ext")) :href "http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700&subset=latin,latin-ext"))
(:link (@ :rel "stylesheet" (:link (@ :rel "stylesheet"
@ -136,7 +143,7 @@ My main blog made up a collection of org files:
(:meta (@ :name "viewport" :content "width=device-width, initial-scale=1")) (:meta (@ :name "viewport" :content "width=device-width, initial-scale=1"))
(:link (@ :rel "shortcut icon" :href "/img/dragon-head.svg")) (:link (@ :rel "shortcut icon" :href "/img/dragon-head.svg"))
(:link (@ :rel "icon" :href "/img/dragon.svg")) (:link (@ :rel "icon" :href "/img/dragon.svg"))
(:link (@ :rel "me" :href "https://emacs.ch/@howard")) (:link (@ :rel "me" :href "https://pdx.sh/@howard"))
(:meta (@ :http-equiv "X-Clacks-Overhead" :content "Darol Allen")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "Darol Allen"))
(:meta (@ :http-equiv "X-Clacks-Overhead" :content "George Vanecek")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "George Vanecek"))
(:meta (@ :http-equiv "X-Clacks-Overhead" :content "Rick Cooper")) (:meta (@ :http-equiv "X-Clacks-Overhead" :content "Rick Cooper"))

View file

@ -59,12 +59,12 @@ The =org-indent-indentation-per-level=, which defaults to =2= doesnt really w
:custom-face (org-indent ((t (:inherit fixed-pitch))))) :custom-face (org-indent ((t (:inherit fixed-pitch)))))
#+end_src #+end_src
Finally, lets add frame borders and window dividers: The following adds frame borders and window dividers to give space between window buffers. Do I need this when my Org files indent the text? Dunno, but it makes the view more pleasant.
#+begin_src emacs-lisp #+begin_src emacs-lisp
(modify-all-frames-parameters (modify-all-frames-parameters
'((right-divider-width . 20) '((right-divider-width . 2)
(internal-border-width . 20))) (internal-border-width . 10)))
(dolist (face '(window-divider (dolist (face '(window-divider
window-divider-first-pixel window-divider-first-pixel
@ -385,7 +385,7 @@ However, I'm just going to have to write a function to clean this.
(replace-match (match-string 1) nil :no-error))) (replace-match (match-string 1) nil :no-error)))
#+end_src #+end_src
Now that is some complicated regular expressions. Now that is some complicated regular expressions.
* Technical Artifacts :noexport: * Technical Artifacts :noexport:
Note, according to [[https://www.reddit.com/r/emacs/comments/vahsao/orgmode_use_capitalized_property_keywords_or/][this discussion]] (and especially [[https://scripter.co/org-keywords-lower-case/][this essay]]), Im switching over to lower-case version of org properties. Using this helper function: Note, according to [[https://www.reddit.com/r/emacs/comments/vahsao/orgmode_use_capitalized_property_keywords_or/][this discussion]] (and especially [[https://scripter.co/org-keywords-lower-case/][this essay]]), Im switching over to lower-case version of org properties. Using this helper function:
Let's provide a name so we can =require= this file: Let's provide a name so we can =require= this file:

View file

@ -283,7 +283,15 @@ Came up with a great way to search a project for Org-specific files, and wrote [
(ha-leader "f o" '("load org" . org-find-file))) (ha-leader "f o" '("load org" . org-find-file)))
#+end_src #+end_src
Now that my paragraphs in an org file are on a single line, I could use =rg= (or some other =grep= program), but being able to use an /indexed search system/, like [[https://ss64.com/osx/mdfind.html][mdfind]] on Macos, or [[https://www.lesbonscomptes.com/recoll/][recoll]] on Linux, gives better results than line-oriented search systems. Lets create operating-system functions the command line for searching: Now that my paragraphs in an org file are on a single line, I could use =rg= (or some other =grep= program), but being able to use an /indexed search system/, like [[https://ss64.com/osx/mdfind.html][mdfind]] on Macos, or [[https://www.lesbonscomptes.com/recoll/][recoll]] on Linux, gives better results than line-oriented search systems.
While =mdfind= is builtin to MacOS, we need to install =recoll=:
#+BEGIN_SRC sh
sudo apt install -y recoll
#+END_SRC
Lets create operating-system functions the command line for searching:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(defun ha-search-notes--macos (phrase path) (defun ha-search-notes--macos (phrase path)
@ -322,9 +330,12 @@ This function calls the above-mentioned operating-system-specific functions, but
#+end_src #+end_src
Lets see it in action: Lets see it in action:
#+begin_src emacs-lisp :tangle no :results replace #+begin_src emacs-lisp :tangle no :results replace
(ha-search-notes--files "openstack grafana" '("~/Notes")) (ha-search-notes--files "bread" '("~/personal"))
#+end_src #+end_src
#+RESULTS:
: ':3:common/rclinit.cpp:391::Recoll 1.36.1 + Xapian 1.4.22 [/home/howard/.recoll]'
Returns this string: Returns this string:
#+begin_example #+begin_example
"'/Users/howard.abrams/Notes/Sprint-2022-25.org' '/Users/howard.abrams/Notes/Sprint-2022-03.org' '/Users/howard.abrams/Notes/Sprint-2020-45.org' '/Users/howard.abrams/Notes/Sprint-2022-09.org' '/Users/howard.abrams/Notes/Sprint-2022-05.org' '/Users/howard.abrams/Notes/Sprint-2022-01.org' '/Users/howard.abrams/Notes/Sprint-2022-19.org'" "'/Users/howard.abrams/Notes/Sprint-2022-25.org' '/Users/howard.abrams/Notes/Sprint-2022-03.org' '/Users/howard.abrams/Notes/Sprint-2020-45.org' '/Users/howard.abrams/Notes/Sprint-2022-09.org' '/Users/howard.abrams/Notes/Sprint-2022-05.org' '/Users/howard.abrams/Notes/Sprint-2022-01.org' '/Users/howard.abrams/Notes/Sprint-2022-19.org'"
@ -384,14 +395,6 @@ And turn on ALL the languages:
(plantuml . t))) (plantuml . t)))
#+end_src #+end_src
The [[https://github.com/isamert/corg.el][corg project]] does completing feature for all the block header values. To do this, type ~M-Tab~ (not just regular ~Tab~).
#+begin_src emacs-lisp :results list :hlines yes
(use-package corg
:straight (:host github :repo "isamert/corg.el")
:hook (org-mode . 'corg-setup))
#+end_src
*** Searching Literate Files *** Searching Literate Files
A noweb definition, e.g. =<<something-something>>= could /jump/ to the =#name= definition. A noweb definition, e.g. =<<something-something>>= could /jump/ to the =#name= definition.
Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, Ill call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse. Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, Ill call it instead of attempting to build a [[https://stackoverflow.com/questions/41933837/understanding-the-ctags-file-format][CTAGS]] table. Oooh, the =rg= takes a =—json= option, which makes it easier to parse.
@ -470,7 +473,21 @@ And lets try this:
#+end_src #+end_src
*** Graphviz *** Graphviz
The [[https://graphviz.org/][graphviz project]] can be written in org blocks, and then rendered as an image: Using the [[https://graphviz.org/][graphviz project]], create charts with /textual instructions/ (code), and then rendered as an image. First setup Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode][graphviz-dot-mode]]:
#+begin_src emacs-lisp
(use-package graphviz-dot-mode
:mode "\\.dot\\'"
:init
(setq tab-width 4
graphviz-dot-indent-width 2
graphviz-dot-auto-indent-on-newline t
graphviz-dot-auto-indent-on-braces t
graphviz-dot-auto-indent-on-semi t))
#+end_src
Next, add it to org-babel:
#+name: ob-graphviz #+name: ob-graphviz
#+begin_src emacs-lisp :tangle no #+begin_src emacs-lisp :tangle no
(add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot")) (add-to-list 'org-src-lang-modes '("dot" . "graphviz-dot"))
@ -547,7 +564,7 @@ Here is a sequence diagram example to show how is looks/works:
#+attr_org: :width 800px #+attr_org: :width 800px
[[file:ha-org-plantuml-example.png]] [[file:ha-org-plantuml-example.png]]
*** Pikchr *** Pikchr
No, not Pikachu, but close. The [[https://pikchr.org/home/doc/trunk/homepage.md][Pikchr project]] is similar to Graphviz and Plantuml, but makes the boxes more positional and really allows one to place things more precisely. Yet another steep learning curve. No, not Pikachu, but close. Unlike Graphviz and Plantuml, the [[https://pikchr.org/home/doc/trunk/homepage.md][Pikchr project]] makes boxes more positional and allows one to place the parts more precisely. Yet another steep learning curve.
Not sure if anyone has made a /package/, so we need to download and build locally: Not sure if anyone has made a /package/, so we need to download and build locally:
#+begin_src sh :dir ~/bin #+begin_src sh :dir ~/bin
@ -556,17 +573,18 @@ Not sure if anyone has made a /package/, so we need to download and build locall
gcc -DPIKCHR_SHELL -o ~/bin/pikchr ~/bin/pikchr.c -lm # to build the pikchr command-line tool gcc -DPIKCHR_SHELL -o ~/bin/pikchr ~/bin/pikchr.c -lm # to build the pikchr command-line tool
#+end_src #+end_src
Of course, since we are dealing with Emacs, any good idea will be assimilated. Johann Klähn created [[https://github.com/kljohann/pikchr-mode][pikchr-mode]]: Of course, since we are dealing with Emacs, where we assimilate any good idea. Johann Klähn created [[https://github.com/kljohann/pikchr-mode][pikchr-mode]]:
#+begin_src emacs-lisp #+begin_src emacs-lisp
(use-package pikchr-mode (use-package pikchr-mode
:straight (:local-repo "~/src/pikchr-mode") :straight (:local-repo "~/src/pikchr-mode")
;; :straight (:host github :repo "kljohann/pikchr-mode")
:custom :custom
(pikchr-executable "~/bin/pikchr")) (pikchr-executable "~/bin/pikchr"))
#+end_src #+end_src
Lets see this in action: Lets see this in action:
#+begin_src pikchr :file ha-org-pikchr-01.svg :results file :exports both #+begin_src pikchr :file ha-org-pikchr-01.png :results file :exports both
bgcolor = 0x1d2021 bgcolor = 0x1d2021
fgcolor = 0xeeeeee fgcolor = 0xeeeeee
line; box "Hello," "World!"; arrow line; box "Hello," "World!"; arrow
@ -592,7 +610,7 @@ At work, Ive been integrating [[https://mermaidjs.github.io/][Mermaid]] into
Assuming we have installed the [[https://github.com/mermaid-js/mermaid-cli][Mermaid CLI]]: Assuming we have installed the [[https://github.com/mermaid-js/mermaid-cli][Mermaid CLI]]:
#+BEGIN_SRC sh #+BEGIN_SRC sh
npm install -g @mermaid-js/mermaid-cli npm install -g @mermaid-js/mermaid-cli
#+END_SRC #+END_SRC
We edit the code with [[https://github.com/abrochard/mermaid-mode][mermaid-mode]]: We edit the code with [[https://github.com/abrochard/mermaid-mode][mermaid-mode]]:
@ -600,7 +618,9 @@ We edit the code with [[https://github.com/abrochard/mermaid-mode][mermaid-mode]
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package mermaid-mode (use-package mermaid-mode
:config :config
(setq mermaid-mmdc-location "/opt/homebrew/bin/mmdc")) (setq mermaid-mmdc-location (if (file-exists-p "/opt/homebrew")
"/opt/homebrew/bin/mmdc"
"/usr/local/bin/mmdc")))
#+END_SRC #+END_SRC
We can make Mermaid diagrams in Emacs Org files using [[https://github.com/arnm/ob-mermaid][ob-mermaid]]: We can make Mermaid diagrams in Emacs Org files using [[https://github.com/arnm/ob-mermaid][ob-mermaid]]:
@ -608,7 +628,7 @@ We can make Mermaid diagrams in Emacs Org files using [[https://github.com/arnm/
#+BEGIN_SRC emacs-lisp #+BEGIN_SRC emacs-lisp
(use-package ob-mermaid (use-package ob-mermaid
:config :config
(setq ob-mermaid-cli-path "/opt/homebrew/bin/mmdc")) (setq ob-mermaid-cli-path mermaid-mmdc-location))
#+END_SRC #+END_SRC
#+BEGIN_SRC mermaid :file ha-org-mermaid.png :theme dark :background-color transparent #+BEGIN_SRC mermaid :file ha-org-mermaid.png :theme dark :background-color transparent
@ -752,18 +772,6 @@ I have a special version of tweaked [[file:elisp/ox-confluence.el][Confluence ex
"o E" '("to confluence" . ox-export-to-confluence))) "o E" '("to confluence" . ox-export-to-confluence)))
#+end_src #+end_src
And Graphviz configuration using [[https://github.com/ppareit/graphviz-dot-mode][graphviz-dot-mode]]:
#+begin_src emacs-lisp
(use-package graphviz-dot-mode
:mode "\\.dot\\'"
:init
(setq tab-width 4
graphviz-dot-indent-width 2
graphviz-dot-auto-indent-on-newline t
graphviz-dot-auto-indent-on-braces t
graphviz-dot-auto-indent-on-semi t))
#+end_src
*** HTML Style *** HTML Style
Im not afraid of HTML, but I like the idea of doing my HTML work in a Lisp-like way using the [[https://github.com/tonyaldon/jack][jack-html project]]: Im not afraid of HTML, but I like the idea of doing my HTML work in a Lisp-like way using the [[https://github.com/tonyaldon/jack][jack-html project]]:

View file

@ -1,6 +1,6 @@
# -*- mode: snippet -*- # -*- mode: snippet -*-
# name: python-src-block # name: python-src-block
# key: #sp # key: <sp
# -- # --
#+BEGIN_SRC python #+BEGIN_SRC python
$0 $0

View file

@ -0,0 +1,7 @@
,# -*- mode: snippet -*-
,# name: split-org-block
,# key: #split
,# --
#+END_SRC
`yas-selected-text`
#+BEGIN_SRC `(ha-org-block-language-and)`

View file

@ -0,0 +1,5 @@
# name: surround-org-block
# --
#+BEGIN_${1:SRC} ${2:emacs-lisp} $0
`yas-selected-text`
#+END_$1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB