Compare commits
7 commits
4629cbf1a0
...
f735e4c59e
Author | SHA1 | Date | |
---|---|---|---|
|
f735e4c59e | ||
|
820e1853cb | ||
|
595d7db714 | ||
|
3b0e21c128 | ||
|
23efcd2a26 | ||
|
d816725afc | ||
|
2a70d6c344 |
13 changed files with 488 additions and 164 deletions
|
@ -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. I’ve decided my /rules/ will be:
|
||||
- Nothing should automatically appear; that is annoying and distracting.
|
||||
- Spelling in org files (abbrev or hippie expander) and code completion are separate, but I’m 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 don’t find the Emacs completion system obvious, with different interfaces, some distinct, some connected. Here’s the summary as I understand:
|
||||
#+begin_verse
|
||||
=indent-for-tab-command=, which /we can/ call:
|
||||
└─ =completion-at-point=, which calls:
|
||||
└─ =completion-at-point-functions= (capf), which can call:
|
||||
└─ hippie and dabbrev functions
|
||||
#+end_verse
|
||||
I don’t 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. Here’s the summary as I understand (as well as the overriding keybindings I use):
|
||||
|
||||
#+BEGIN_EXAMPLE
|
||||
╭─▷ indent-for-tab-command ╭───────╮
|
||||
│ ╷ ╭──┤ M-TAB │ ╭─────╮
|
||||
│ ╰─▶ completion-at-point ◁──╯ ╰───────╯ │ M-/ │
|
||||
╭──┴──╮ (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
|
||||
(setq tab-always-indent 'complete
|
||||
tab-first-completion 'word-or-paren
|
||||
completion-cycle-threshold nil)
|
||||
completion-cycle-threshold 2)
|
||||
#+end_src
|
||||
|
||||
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
|
||||
The venerable [[help:hippie-expand][hippie-expand]] function does a better job than the default, [[help:dabbrev-expand][dabbrev-expand]], so let’s 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
|
||||
|
@ -445,6 +450,7 @@ The venerable [[help:hippie-expand][hippie-expand]] function does a better job t
|
|||
#+end_src
|
||||
|
||||
Details on its job? We need to update its [[help:hippie-expand-try-functions-list][list of expanders]]. I don’t care much for [[help:try-expand-line][try-expand-line]], so that is not on the list.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq hippie-expand-try-functions-list
|
||||
'(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
|
||||
|
||||
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 I’m in Evil’s =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
|
||||
|
||||
*** 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 don’t 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
|
||||
The default completion system either inserts the first option directly in the text (without cycling, so let’s 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).
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package corfu
|
||||
: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.
|
||||
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package org
|
||||
:config
|
||||
|
|
97
ha-demos.org
97
ha-demos.org
|
@ -2,7 +2,7 @@
|
|||
#+author: Howard X. Abrams
|
||||
#+date: 2024-10-18
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2024-10-19 Sat]
|
||||
#+lastmod: [2024-10-24 Thu]
|
||||
|
||||
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
|
||||
(define-ha-demo ha-simple-demo
|
||||
(:head "New Demonstration" :i 0) (message "Howdy")
|
||||
(:head "New Demonstration" :i 1) (message "Hi there"))
|
||||
(:heading "New Demonstration" :i 0) (message "Howdy")
|
||||
(:heading "New Demonstration" :i 1) (message "Hi there"))
|
||||
|
||||
(global-set-key (kbd "<f6>") 'ha-simple-demo)
|
||||
(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
|
||||
(:buffer \"demonstrations.py\") (message \"In a buffer\")
|
||||
(: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
|
||||
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)
|
||||
(let ((state (list :buffer (buffer-name)
|
||||
:mode major-mode
|
||||
:head (when (eq major-mode 'org-mode)
|
||||
:heading (when (eq major-mode 'org-mode)
|
||||
(org-get-heading)))))
|
||||
(cond
|
||||
,@(seq-map (lambda (tf-pair)
|
||||
|
@ -214,7 +214,7 @@ Let’s test:
|
|||
'(:a 1 :i 5) '((:a 1) (:b 2) (:c 3))))))
|
||||
#+END_SRC
|
||||
|
||||
But can I check if I have triggered a state once before? Let’s 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? Let’s 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
|
||||
(defvar ha-demo-prev-state (make-hash-table :test 'equal)
|
||||
|
@ -239,6 +239,91 @@ Let’s create a function that could accept a list of /triggering keys/, and the
|
|||
(not (seq-difference triggers state)))
|
||||
#+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 Lisp’s =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:
|
||||
Let's =provide= a name so we can =require= this file:
|
||||
|
||||
|
|
73
ha-email.org
73
ha-email.org
|
@ -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-service 587)
|
||||
#+end_src
|
||||
* Sending Mail
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(require 'smtpmail)
|
||||
#+END_SRC
|
||||
|
||||
* Installation and Basic Configuration
|
||||
To begin, we need the code. On Ubuntu, this is:
|
||||
#+begin_src shell :tangle no
|
||||
|
@ -304,9 +309,9 @@ The following option is supported here:
|
|||
exclude_tags=deleted;spam;
|
||||
#+end_src
|
||||
*** 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 |
|
||||
|------+---------------------------------------------|
|
||||
|
@ -325,7 +330,7 @@ The =notmuch new= command will notice flag changes in filenames and update tags,
|
|||
|
||||
That should complete the Notmuch configuration.
|
||||
** =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"
|
||||
# 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
|
||||
# Note: We now tangle this file from ~/src/hamacs/ha-email.org
|
||||
#
|
||||
# Install this by moving this file to <maildir>/.notmuch/hooks/post-new
|
||||
# NOTE: you need to define your maildir in the vardiable nm_maildir (just a few lines below in this script)
|
||||
# Also create empty files for:
|
||||
# 1. thefeed.db (things you want to read every once in a while)
|
||||
# 2. spam.db (things you never want to see)
|
||||
# Create empty files for:
|
||||
# 1. thefeed.db (mail you want to read every once in a while)
|
||||
# 2. spam.db (mail you never want to see)
|
||||
# 3. screened.db (your inbox)
|
||||
# 4. ledger.db (papertrail)
|
||||
# in the hooks folder.
|
||||
|
@ -422,8 +425,6 @@ do
|
|||
done
|
||||
timer_end "Screened"
|
||||
|
||||
# Projects...
|
||||
|
||||
timer_start "Old-Projects"
|
||||
notmuch tag +old-project 'subject:/.*howardabrams\/node-mocks-http/'
|
||||
notmuch tag +old-project 'subject:/.*Pigmice2733/'
|
||||
|
@ -434,12 +435,12 @@ notmuch tag +screened 'subject:[Web]'
|
|||
echo "Completing not-much 'post-new' script"
|
||||
#+end_src
|
||||
* 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
|
||||
|
||||
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
|
||||
(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"
|
||||
:query "tag:screened AND NOT tag:unread"
|
||||
:key "I")
|
||||
(:name "Sent" :query "tag:sent")
|
||||
(: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"
|
||||
:key "s")
|
||||
|
@ -479,7 +481,7 @@ A list of pre-defined searches act like "Folder buttons" at the top to quickly s
|
|||
#+end_src
|
||||
** 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
|
||||
(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))
|
||||
|
||||
(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)
|
||||
(notmuch-search-add-tag '("+deleted"))
|
||||
(notmuch-search-archive-thread))
|
||||
|
@ -526,33 +528,36 @@ 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:
|
||||
|
||||
#+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."
|
||||
(interactive)
|
||||
(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.
|
||||
NO-DISPLAY is sent forward to `notmuch-search'."
|
||||
NO-DISPLAY is sent forward to `notmuch-search'."
|
||||
(interactive)
|
||||
(notmuch-search (concat "from:" (hey-notmuch-search-find-from))
|
||||
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.
|
||||
BEG and END provide the region, but are ignored. They are defined
|
||||
since `notmuch-search-interactive-tag-changes' returns them. If
|
||||
REFRESH is true, refresh the buffer from which we started the
|
||||
search."
|
||||
|
||||
While defined (since `notmuch-search-interactive-tag-changes'
|
||||
returns them), this function ignores BEG and END (for the
|
||||
region).
|
||||
|
||||
If REFRESH is true, refresh the buffer from which we started the
|
||||
search."
|
||||
(interactive (notmuch-search-interactive-tag-changes))
|
||||
(let ((this-buf (current-buffer)))
|
||||
(hey-notmuch-search-by-from t)
|
||||
;; This is a dirty hack since I can't find a way to run a
|
||||
;; temporary hook on `notmuch-search' completion. So instead of
|
||||
;; waiting on the search to complete in the background and then
|
||||
;; making tag-changes on it, I will just sleep for a short amount
|
||||
;; of time. This is generally good enough and works, but is not
|
||||
;; guaranteed to work every time. I'm fine with this.
|
||||
;; making tag-changes on it, I sleep for a short amount of time.
|
||||
;; This is generally good enough and works, but is not guaranteed
|
||||
;; to work every time. I'm fine with this.
|
||||
(sleep-for 0.5)
|
||||
(notmuch-search-tag-all tag-changes)
|
||||
(when refresh
|
||||
|
@ -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.
|
||||
This means:
|
||||
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=.
|
||||
3. All existing email should be removed from the inbox."
|
||||
2. All existing email updated with the tag `thefeed'.
|
||||
3. All existing email removed from the inbox."
|
||||
(interactive)
|
||||
(hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from)
|
||||
(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.
|
||||
This means:
|
||||
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=.
|
||||
3. All existing email should be removed from the inbox."
|
||||
2. All existing email updated with the tag `ledger/TAG-NAME'.
|
||||
3. All existing email removed from the inbox."
|
||||
(interactive "sTag Name: ")
|
||||
(hey-notmuch-add-addr-to-db (format "%s %s"
|
||||
tag-name
|
||||
|
@ -597,8 +602,8 @@ This means:
|
|||
(defun hey-notmuch-move-sender-to-screened ()
|
||||
"For the email at point, move the sender of that email to Screened Emails.
|
||||
This means:
|
||||
1. All new email should be tagged =screened= and show up in the inbox.
|
||||
2. All existing email should be updated to add the tag =screened=."
|
||||
1. All new email tagged `screened' and show up in the inbox.
|
||||
2. All existing email updated to add the tag `screened'."
|
||||
(interactive)
|
||||
(hey-notmuch-add-addr-to-db (hey-notmuch-search-find-from)
|
||||
(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: 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
|
||||
|
||||
# Local Variables:
|
||||
# jinx-local-words: "notmuch"
|
||||
# End:
|
||||
|
|
|
@ -73,12 +73,14 @@ If any program wants to pause the output through the =$PAGER= variable, well, we
|
|||
#+end_src
|
||||
** 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.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package pcmpl-args)
|
||||
#+end_src
|
||||
Note that this will work with =shell-command= as well.
|
||||
** 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.
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun eshell-insert-history ()
|
||||
"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-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
|
||||
ls *.sh(*) # List shell scripts that are executable
|
||||
ls *(/) # List directories (recursively)
|
||||
|
|
34
ha-evil.org
34
ha-evil.org
|
@ -91,31 +91,39 @@ I’m not a long term VI user, and I generally like /easy keys/, e.g. ~w~, have
|
|||
|
||||
#+begin_src emacs-lisp
|
||||
(use-package evil
|
||||
:config
|
||||
(require 'evil-commands)
|
||||
(evil-define-key '(normal visual motion operator) 'global
|
||||
:config (require 'evil-commands)
|
||||
(evil-define-key
|
||||
'(normal visual motion operator) 'global
|
||||
"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)
|
||||
#+END_SRC
|
||||
|
||||
;; This may be an absolute heresy to most VI users,
|
||||
;; 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 bindings seems to need their own call, and I’m not sure why I can’t include it in the ~w~ and ~e~ bindings.
|
||||
|
||||
;; The `b' key seems to need its own configuration setting:
|
||||
(evil-define-key '(normal visual motion operator) 'global
|
||||
#+BEGIN_SRC emacs-lisp
|
||||
(evil-define-key
|
||||
'(normal visual motion operator) 'global
|
||||
"b" 'evil-backward-WORD-begin)
|
||||
(evil-define-key '(normal visual motion operator) 'global
|
||||
"B" 'evil-backward-word-begin))
|
||||
;; Note that evil-backward-word-end is on the `g e':
|
||||
(evil-define-key
|
||||
'(normal visual motion operator) 'global
|
||||
"B" 'evil-backward-word-begin)
|
||||
#+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/:
|
||||
- =word-subword-subword=
|
||||
- =word_subword_subword=
|
||||
|
||||
Note I don’t 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:
|
||||
- ~g ;~ :: [[help:goto-last-change][goto-last-change]]
|
||||
- ~g ,~ :: [[help:goto-last-change-reverse][goto-last-change-reverse]]
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
#+author: Howard Abrams
|
||||
#+date: 2024-07-07
|
||||
#+filetags: emacs hamacs
|
||||
#+lastmod: [2024-07-26 Fri]
|
||||
#+lastmod: [2024-10-27 Sun]
|
||||
|
||||
A literate programming file for literate programming in Emacs Org Files.
|
||||
|
||||
|
@ -47,6 +47,126 @@ If literate programming is so great, why doesn’t 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.
|
||||
|
||||
*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 don’t 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
|
||||
:PROPERTIES:
|
||||
:ID: 3230b1f4-0d2d-47c7-9f3d-fa53083f8c8d
|
||||
|
@ -98,22 +218,22 @@ TODO Screenshot of multiple highlighted blocks.
|
|||
:PROPERTIES:
|
||||
:ID: 188e378c-bed4-463c-98d4-d22be1845bc2
|
||||
: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
|
||||
(defun org-babel-execute-subtree ()
|
||||
(defun org-babel-execute-subtree (prefix)
|
||||
"Execute all Org source blocks in current subtree."
|
||||
(interactive "P")
|
||||
(save-excursion
|
||||
(org-narrow-to-subtree)
|
||||
(org-babel-execute-buffer)
|
||||
(org-babel-execute-buffer prefix)
|
||||
(widen)))
|
||||
#+end_src
|
||||
** Editing a Block
|
||||
:PROPERTIES:
|
||||
:ID: f143bbd6-fb4d-45b8-bcfa-196c7a26ed34
|
||||
: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
|
||||
(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`
|
||||
|
||||
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
|
||||
(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))
|
||||
#+end_src
|
||||
*** Calling ripgrep
|
||||
|
@ -198,9 +318,9 @@ This helper function does the work of calling =ripgrep=, parsing its output, and
|
|||
(project-root (project-current))
|
||||
default-directory))
|
||||
(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
|
||||
(shell-command-to-list)
|
||||
(seq-map 'ha-literate--parse-rg-line)
|
||||
|
@ -224,15 +344,39 @@ 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'."
|
||||
(string-equal "match" (alist-get 'type json-data)))
|
||||
#+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
|
||||
|
||||
Let’s 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
|
||||
As mentioned above, let’s 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
|
||||
(defun ha-literate-definition (symb)
|
||||
"Return list of `xref' objects of SYMB location in org files.
|
||||
The location is based on a regular expression starting with
|
||||
`(defxyz SYMB' where this can be `defun' or `defvar', etc."
|
||||
(ha-literate--ripgrep-matches 'ha-literate--process-rg-line
|
||||
(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:
|
||||
|
@ -240,7 +384,14 @@ As mentioned above, let’s assume we can use =ripgrep= to search for /definiti
|
|||
"def" (1+ (not space))))
|
||||
(one-or-more space)
|
||||
(literal symb)
|
||||
word-boundary)))
|
||||
word-boundary))
|
||||
|
||||
(defun ha-literate-definition (symb)
|
||||
"Return list of `xref' objects of SYMB location in org files.
|
||||
The location is based on a regular expression starting with
|
||||
`(defxyz SYMB' where this can be `defun' or `defvar', etc."
|
||||
(ha-literate--ripgrep-matches 'ha-literate--process-rg-line
|
||||
(ha-literate-definition-rx symb)))
|
||||
#+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=.
|
||||
|
@ -252,20 +403,25 @@ The work of processing a match for the =ha-literate-definition= function. It cal
|
|||
The return data comes from `xref-make' and `xref-make-file-location'."
|
||||
(when rg-data-line
|
||||
(let-alist rg-data-line
|
||||
;; (message "xref-make %s" .data.path.text)
|
||||
(xref-make .data.lines.text
|
||||
(xref-make-file-location .data.path.text
|
||||
(xref-make-file-location
|
||||
;; Relative filename:
|
||||
(ha-literate-make-xref-file .data.path.text)
|
||||
;; Line number:
|
||||
.data.line_number
|
||||
;; Column: Icky to parse:
|
||||
(thread-last
|
||||
(first .data.submatches)
|
||||
(alist-get 'start)))))))
|
||||
#+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-.~:
|
||||
|
||||
#+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))
|
||||
#+end_src
|
||||
*** 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/:
|
||||
|
||||
#+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))
|
||||
#+end_src
|
||||
*** References
|
||||
|
@ -383,7 +539,7 @@ The helper function, =ha-literate--process-in-block= is a /recursive/ function t
|
|||
Let’s connect the plumbing:
|
||||
|
||||
#+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))
|
||||
#+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:
|
||||
|
||||
#+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))
|
||||
#+end_src
|
||||
*** 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.
|
||||
Add this function to `xref-backend-functions' hook. "
|
||||
(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
|
||||
|
||||
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
|
||||
:PROPERTIES:
|
||||
: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")
|
||||
("T" org-babel-detangle "from File"))
|
||||
"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
|
||||
|
||||
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.
|
||||
|
||||
#+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
|
||||
|
||||
#+OPTIONS: num:nil toc:t todo:nil tasks:nil tags:nil date:nil
|
||||
|
|
|
@ -41,7 +41,7 @@ Render my code with my font colors:
|
|||
|
||||
#+begin_src emacs-lisp :results silent
|
||||
(use-package htmlize
|
||||
:conf
|
||||
:config
|
||||
;; But I turn the source code coloring off to use a CSS stylesheet I
|
||||
;; control, as otherwise, switching between dark and light themes
|
||||
;; 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))
|
||||
#+end_src
|
||||
** The Blog
|
||||
:PROPERTIES:
|
||||
:ID: 7c4cb568-93a5-44e8-8758-4f415dccf232
|
||||
:END:
|
||||
My main blog made up a collection of org files:
|
||||
#+begin_src emacs-lisp
|
||||
(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-head-extra
|
||||
,(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"
|
||||
:href "http://fonts.googleapis.com/css?family=Source+Sans+Pro:400,700&subset=latin,latin-ext"))
|
||||
(: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"))
|
||||
(:link (@ :rel "shortcut icon" :href "/img/dragon-head.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 "George Vanecek"))
|
||||
(:meta (@ :http-equiv "X-Clacks-Overhead" :content "Rick Cooper"))
|
||||
|
|
|
@ -59,12 +59,12 @@ The =org-indent-indentation-per-level=, which defaults to =2= doesn’t really w
|
|||
:custom-face (org-indent ((t (:inherit fixed-pitch)))))
|
||||
#+end_src
|
||||
|
||||
Finally, let’s 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
|
||||
(modify-all-frames-parameters
|
||||
'((right-divider-width . 20)
|
||||
(internal-border-width . 20)))
|
||||
'((right-divider-width . 2)
|
||||
(internal-border-width . 10)))
|
||||
|
||||
(dolist (face '(window-divider
|
||||
window-divider-first-pixel
|
||||
|
|
66
ha-org.org
66
ha-org.org
|
@ -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)))
|
||||
#+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. Let’s 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
|
||||
|
||||
Let’s create operating-system functions the command line for searching:
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(defun ha-search-notes--macos (phrase path)
|
||||
|
@ -322,9 +330,12 @@ This function calls the above-mentioned operating-system-specific functions, but
|
|||
#+end_src
|
||||
Let’s see it in action:
|
||||
#+begin_src emacs-lisp :tangle no :results replace
|
||||
(ha-search-notes--files "openstack grafana" '("~/Notes"))
|
||||
(ha-search-notes--files "bread" '("~/personal"))
|
||||
#+end_src
|
||||
|
||||
#+RESULTS:
|
||||
: ':3:common/rclinit.cpp:391::Recoll 1.36.1 + Xapian 1.4.22 [/home/howard/.recoll]'
|
||||
|
||||
Returns this string:
|
||||
#+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'"
|
||||
|
@ -384,14 +395,6 @@ And turn on ALL the languages:
|
|||
(plantuml . t)))
|
||||
#+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
|
||||
A noweb definition, e.g. =<<something-something>>= could /jump/ to the =#name= definition.
|
||||
Since [[https://github.com/BurntSushi/ripgrep][ripgrep]] is pretty fast, I’ll 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 let’s try this:
|
|||
#+end_src
|
||||
|
||||
*** 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
|
||||
#+begin_src emacs-lisp :tangle no
|
||||
(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
|
||||
[[file:ha-org-plantuml-example.png]]
|
||||
*** 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:
|
||||
#+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
|
||||
#+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
|
||||
(use-package pikchr-mode
|
||||
:straight (:local-repo "~/src/pikchr-mode")
|
||||
;; :straight (:host github :repo "kljohann/pikchr-mode")
|
||||
:custom
|
||||
(pikchr-executable "~/bin/pikchr"))
|
||||
#+end_src
|
||||
|
||||
Let’s 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
|
||||
fgcolor = 0xeeeeee
|
||||
line; box "Hello," "World!"; arrow
|
||||
|
@ -592,7 +610,7 @@ At work, I’ve been integrating [[https://mermaidjs.github.io/][Mermaid]] into
|
|||
Assuming we have installed the [[https://github.com/mermaid-js/mermaid-cli][Mermaid CLI]]:
|
||||
|
||||
#+BEGIN_SRC sh
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
npm install -g @mermaid-js/mermaid-cli
|
||||
#+END_SRC
|
||||
|
||||
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
|
||||
(use-package mermaid-mode
|
||||
: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
|
||||
|
||||
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
|
||||
(use-package ob-mermaid
|
||||
:config
|
||||
(setq ob-mermaid-cli-path "/opt/homebrew/bin/mmdc"))
|
||||
(setq ob-mermaid-cli-path mermaid-mmdc-location))
|
||||
#+END_SRC
|
||||
|
||||
#+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)))
|
||||
#+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
|
||||
|
||||
I’m 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]]:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# -*- mode: snippet -*-
|
||||
# name: python-src-block
|
||||
# key: #sp
|
||||
# key: <sp
|
||||
# --
|
||||
#+BEGIN_SRC python
|
||||
$0
|
||||
|
|
7
snippets/org-mode/split-org-block
Normal file
7
snippets/org-mode/split-org-block
Normal 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)`
|
5
snippets/org-mode/surround-org-block
Normal file
5
snippets/org-mode/surround-org-block
Normal 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 |
Loading…
Reference in a new issue