Álvaro Ramírez
Deleting from Emacs sequence vars
Adding hooks and setting variables is core to customizing Emacs. Take a major mode like
emacs-lisp-mode
as an example. To customize its behaviour, one may add a hook function to emacs-lisp-mode-hook
, or if you're a little lazy while experimenting, you may even use a lambda.
(add-hook 'emacs-lisp-mode-hook #'my/emacs-lisp-mode-config) (add-hook 'emacs-lisp-mode-hook (lambda () (message "I woz ere")))
emacs-lisp-mode-hook
's content would subsequently look as follows:
'(my/emacs-lisp-mode-config (lambda nil (message "I woz ere")) ert--activate-font-lock-keywords easy-escape-minor-mode lisp-extra-font-lock-global-mode)
Maybe my/emacs-lisp-mode-config
didn't work out for us and we'd like to remove it. We can use remove-hook
for that and evaluate something like:
(remove-hook 'emacs-lisp-mode-hook #'my/emacs-lisp-mode-config)
The lambda can be removed too, but you ought to be careful in using the same lambda body.
(remove-hook 'emacs-lisp-mode-hook (lambda () (message "I woz tere")))
There are other ways to remove the lambdas, but we're digressing here… We typically have to write these throwaway snippets to undo our experiments. What if we just had a handy helper always available to remove items from sequences (edit: we do, remove-hook
is already interactive, see Update 2 below)? After all, hooks are just lists (sequences).
While the interactive command can likely be simplified further, I tried to optimize for ergonomic usage. For example, completing-read
gives us a way narrow down whichever variable we'd like to modify as well as the item we'd like to remove. seqp
is also handy, as we filter out noise by automatically removing any variable that's not a sequence.
(defun ar/remove-from-list-variable () (interactive) (let* ((var (intern (completing-read "From variable: " (let (symbols) (mapatoms (lambda (sym) (when (and (boundp sym) (seqp (symbol-value sym))) (push sym symbols)))) symbols) nil t))) (values (mapcar (lambda (item) (setq item (prin1-to-string item)) (concat (truncate-string-to-width (nth 0 (split-string item "\n")) (window-body-width)) (propertize item 'invisible t))) (symbol-value var))) (index (progn (when (seq-empty-p values) (error "Already empty")) (seq-position values (completing-read "Delete: " values nil t))))) (unless index (error "Eeek. Something's up.")) (set var (append (seq-take (symbol-value var) index) (seq-drop (symbol-value var) (1+ index)))) (message "Deleted: %s" (truncate-string-to-width (seq-elt values index) (- (window-body-width) 9)))))
Hooks are just an example of lists we can delete from. I recently used the same command on display-buffer-alist
.
While this has been a fun exercise, I can't help but think that I'm likely re-inventing the wheel here. Is there something already built-in that I'm missing?
Update 1
alphapapa suggested some generalizations that would provide an editing buffer of sorts. This is a neat idea, using familiar key bindigs C-c C-c
to save and C-c C-k
to bail.
Beware, I haven't tested the code with a diverse set of list items, so there's a chance of corrupting the variable content. Improvements to the code are totally welcome.
;;; -*- lexical-binding: t; -*- (defun ar/edit-list-variable () (interactive) (let* ((var (intern (completing-read "From variable: " (let (symbols) (mapatoms (lambda (sym) (when (and (boundp sym) (seqp (symbol-value sym))) (push sym symbols)))) symbols) nil t))) (values (string-join (mapcar #'prin1-to-string (symbol-value var)) "\n"))) (with-current-buffer (get-buffer-create "*eval elisp*") (emacs-lisp-mode) (local-set-key (kbd "C-c C-c") (lambda () (interactive) (eval-buffer) (kill-this-buffer) (message "Saved: %s" var))) (local-set-key (kbd "C-c C-k") 'kill-this-buffer) (erase-buffer) (insert (format "(setq %s\n `(%s))" var values)) (mark-whole-buffer) (indent-region (point-min) (point-max)) (deactivate-mark) (switch-to-buffer (current-buffer)))))
Update 2
So hunch was right…
"While this has been a fun exercise, I can't help but think that I'm likely re-inventing the wheel here. Is there something already built-in that I'm missing?"
juicecelery's Reddit commit confirmed it. Thank you! remove-hook is already interactive 🤦♂️. TIL 😁
juicecelery was kind enough to point out an improvement in the custom function:
"but I see your improvements, for instance that non list items are removed from the selection."
Sprinkle me logs
At times, basic prints/logs are just about the right debugging strategy. Sure, we have debuggers and REPLs which are super useful, but sometimes you just know that sprinkling your code with a handful of temporary prints/logs will get you enough info to fix an issue.
I must confess, my temporary print statements are fairly uninspiring. Sometimes I log the name of the method/function, but I also resort to less creative options like print("Yay")
or print("Got here")
.
My laziness and lack of creativity knows no boundaries, so if I need multiple unique entries, I often copy, paste, and append numbers to my entries: print("Yay 2")
, print("Yay 3")
, print("Yay 4")
… I know, are you judging yet?
So rather than develop the creative muscle, I've decided to lean on laziness and old habits, so let's make old habit more efficient :) I no longer want to copy, paste, and increment my uncreative log statements. Instead, I'll let Emacs do it for me!
There isn't a whole lot to the implementation. It searches the current buffer for other instances of the same logging string and captures the largest counter found. It subsequently prints the same string with the counter incremented. This can be done in a few lines of elisp, but I figure I wanted some additional features like auto indenting and changing the logging string when using a prefix.
(defvar ar/unique-log-word "Yay") (defun ar/insert-unique-log-word (prefix) "Inserts `ar/unique-log-word' incrementing counter. With PREFIX, change `ar/unique-log-word'." (interactive "P") (let* ((word (cond (prefix (setq ar/unique-log-word (read-string "Log word: "))) ((region-active-p) (setq ar/unique-log-word (buffer-substring (region-beginning) (region-end)))) (ar/unique-log-word ar/unique-log-word) (t "Reached"))) (config (cond ((equal major-mode 'emacs-lisp-mode) (cons (format "(message \"%s: \\([0-9]+\\)\")" word) (format "(message \"%s: %%s\")" word))) ((equal major-mode 'swift-mode) (cons (format "print(\"%s: \\([0-9]+\\)\")" word) (format "print(\"%s: %%s\")" word))) (t (error "%s not supported" major-mode)))) (match-regexp (car config)) (format-string (cdr config)) (max-num 0) (case-fold-search nil)) (when ar/unique-log-word (save-excursion (goto-char (point-min)) (while (re-search-forward match-regexp nil t) (when (> (string-to-number (match-string 1)) max-num) (setq max-num (string-to-number (match-string 1)))))) (setq max-num (1+ max-num))) (unless (looking-at-p "^ *$") (end-of-line)) (insert (concat (if (looking-at-p "^ *$") "" "\n") (format format-string (if ar/unique-log-word (number-to-string (1+ max-num)) (string-trim (shell-command-to-string "grep -E '^[a-z]{6}$' /usr/share/dict/words | shuf -n 1")))))) (call-interactively 'indent-for-tab-command)))
I want to be lazy in other languages, so the function can now be extended to support other languages. Here's the Swift counterpart.
Since I sometimes log function names, I figured making it region-aware would help with that.
I'm sure there's a package out there that does something similar, but I figure this would be a fun little elisp hack.
Happy logging!
Update
Set ar/unique-log-word
to nil and let it generate a random word. Maybe I get to learn new words as I debug ;)
dwim-shell-command on Windows + upload to 0x0.st
You can now use dwim-shell-command on Windows. Shoutout to Kartik Saranathan, who sent a pull request to get rid of ls
usage.
Also thanks to Bram for sharing his upload to 0x0.st implementation. I'd been wanting to do something similar for imgur, but 0x0.st is a much better alternative!
dwim-shell-commands-upload-to-0x0
is now part of dwim-shell-commands.el (the optional part of the package). It has a couple of additional touches:
- Open the uploaded image in eww browser.
- Automatically copy the upload URL to kill-ring. You're likely gonna share this link, right?
If you're unfamiliar with dwim-shell-command
, it enables Emacs shell commands with DWIM behaviour:
- Asynchronously.
- Using noweb templates.
- Automatically injecting files (from dired or other buffers) or kill ring.
- Managing buffer focus with heuristics.
- Showing progress bar.
- Quick buffer exit.
- More reusable history.
In addition to replacing shell-command
with dwim-shell-command
, I also use it to bring all sorts of command line utilities to familiar Emacs workflows (in dired or current buffers), without having to remember complex command invocations.
I've covered many of the use-cases before:
- Emacs DWIM shell-command
- Emacs: Password-protect current pdf (revisited)
- png to icns (Emacs DWIM style)
- Emacs: DWIM shell command (multi-language)
- dwim-shell-command on Melpa
- dwim-shell-command improvements
- dwim-shell-command video streams
- dwim-shell-command with template prompts
- dwim-shell-command usages: pdftotext and scp
- Emacs: Reveal in macOS Finder (DWIM style)
- Emacs: macOS sharing (DWIM style)
chatgpt-shell siblings now on MELPA also
In chatgpt-shell updates, I highlighted dall-e-shell
(a DALL-E Emacs shell), ob-chatgpt-shell
(ChatGPT org babel support), and ob-dall-e-shell
(DALL-E org babel support) were initially excluded from the chatgpt-shell MELPA submission while I worked out their split.
That's now sorted and the packages are available on MELPA.
Here's ob-chatgpt-shell
and ob-dall-e-shell
in action.
Here's dall-e-shell
.
Generating elisp org docs
chatgpt-shell's README includes few org tables documenting the package's customizable variables as well as available commands. Don't worry, this isn't really another ChatGPT post.
Here's an extract of the docs table:
| Custom variable | Description | |---------------------------------------+-------------------------------------------------------------| | chatgpt-shell-display-function | Function to display the shell. | | chatgpt-shell-curl-additional-options | Additional options for `curl' command. | | chatgpt-shell-system-prompt | The system message helps set the behavior of the assistant. |
While the table docs didn't take long to build manually, they quickly became out of sync with their elisp counterparts. Not ideal, as it'll require a little more careful maintenance in the future.
Emacs being the self-documenting editor that it is, I figured I should be able to extract customizable variables, commands, along with their respective docs, and generate these very same org tables.
I had no idea how to go about this, but apropos-variable and apropos-command surely knew where to fetch the details from. A peak into apropos.el
quickly got me on my way. Turns out mapatoms is just what I needed. It iterates over obarray, Emacs's symbol table. We can use it to extract the symbols we're after.
Since we're filtering symbols from chatgpt-shell
, we can start by including only those whose symbol-name
match "^chatgpt-shell". Out of all matching, we should only keep custom variables. We can use custom-variable-p
to check for that. This gives us all relevant variables. We can subsequently get each variable's corresponding docs using (get symbol 'variable-documentation)
and put it into a list.
Now, if we pull our org babel rabbit out of our Emacs magic hat, we can use :results table
to print the list as an org table. The source block powering this magic trick looks as follows:
#+begin_src emacs-lisp :results table :colnames '("Custom variable" "Description") (let ((rows)) (mapatoms (lambda (symbol) (when (and (string-match "^chatgpt-shell" (symbol-name symbol)) (custom-variable-p symbol)) (push `(,symbol ,(car (split-string (or (get (indirect-variable symbol) 'variable-documentation) (get symbol 'variable-documentation) "") "\n"))) rows)))) rows) #+end_src
And just like that… we effortlessly get our elisp docs in an org table, straight from Emacs's symbol table.
It's worth noting that our snippet used indirect-variable
to resolve aliases but also limited descriptions to the first line in each docstring.
To build a similar table for interactive commands, we can use the following block (also including bindings).
#+BEGIN_SRC emacs-lisp :results table :colnames '("Binding" "Command" "Description") (let ((rows)) (mapatoms (lambda (symbol) (when (and (string-match "^chatgpt-shell" (symbol-name symbol)) (commandp symbol)) (push `(,(mapconcat #'help--key-description-fontified (where-is-internal symbol shell-maker-mode-map nil nil (command-remapping symbol)) ", ") ,symbol ,(car (split-string (or (documentation symbol t) "") "\n"))) rows)))) rows) #+END_SRC
You see? This post wasn't really about ChatGPT. Aren't you glad you stuck around? 😀
LLM bookmarks
- Bark – Text-prompted generative audio model | Hacker News.
- Brex’s Prompt Engineering Guide | Hacker News.
- GitHub - suno-ai/bark: 🔊 Text-Prompted Generative Audio Model.
- How to run your own LLM (GPT).
- PromptPerfect - Elevate Your Prompts to Perfection with AI Prompt Engineering.
- Running LLMs Locally | Y.K. Goon.
- ShareGPT: Share your wildest ChatGPT conversations with one click..
- Show HN: Automatic prompt optimizer for LLMs | Hacker News.
- The Art of ChatGPT Prompting: A Guide to Crafting Clear and Effective Prompts.
- The Art of Midjourney AI: A Guide to Creating Images from Text.
chatgpt-shell updates
About a month ago, I posted about an experiment to build a ChatGPT Emacs shell using comint mode. Since then, it's turned into a package of sorts, evolving with user feedback and pull requests.
Now on MELPA
While chatgpt-shell is a young package still, it seems useful enough to share more widely. As of today, chatgpt-shell
is available on MELPA. Many thanks to Chris Rayner for his MELPA guidance to get the package added.
I'll cover some of the goodies included in the latest chatgpt-shell
.
Delegating to Org Babel
chatgpt-shell
now evaluates Markdown source blocks by delegating to org babel. I've had success with a handful of languages. In some instances, some babel headers may need overriding in chatgpt-shell-babel-headers
.
Here's a Swift execution via babel, showing standard output.
In addition to standard output, chatgpt-shell
can now render blocks generating images. Here's a rendered SwiftUI layout via ob-swiftui.
Can also do diagrams. Here's ditaa in action.
Renaming blocks
At times, ChatGPT may forget to label source blocks or maybe you just want to name it differently… You can now rename blocks at point.
Send prompt/region
There are a handful of commands to send prompts from other buffers, including the region. For example chatgpt-shell-explain-code
.
- chatgpt-shell-send-region
- chatgpt-shell-generate-unit-test
- chatgpt-shell-refactor-code
- chatgpt-shell-proofread-doc
- chatgpt-shell-eshell-summarize-last-command-output
- chatgpt-shell-eshell-whats-wrong-with-last-command
Saving/restoring transcript
You can save your current session to a transcript and restore later.
History improvements
Nicolas Martyanoff has a great post on making IELM More Comfortable. A couple of improvements that stood out for me were:
- Making the command history persistent.
- Searching history with
shell-maker-search-history
/M-r
viacompleting-read
.
shell-maker-search-history
, coupled with your completion framework of choice, can be pretty handy. I happen to use Oleh Krehel's ivy.
shell-maker (make your own AI shells)
While ChatGPT is a popular service, there are many others sprouting. Some are cloud-based, others local, proprietary, open source… In any case, it'd be great be able to hook on to them without much overhead. shell-maker should help with that. The first shell-maker
clients are chatgpt-shell
and dall-e-shell
.
While I've built dall-e-shell
, it'd be great to see what others can do with shell-maker
. If you wire it up to anything, please get in touch (Mastodon / Twitter / Reddit / Email).
dall-e-shell, ob-chatgpt-shell, and ob-dall-e-shell (on MELPA too)
UPDATE: dall-e-shell, ob-chatgpt-shell, and ob-dall-e-shell are now available on MELPA also.
You've seen dall-e-shell
in the previous section. Here's what ob-chatgpt-shell
and ob-dall-e-shell
look like in an org mode document:
How are you using chatgpt-shell
?
Whether you are an existing chatgpt-shell
user, or would like to give things a try, installing from MELPA should generally make things easier for ya. As I mentioned, chatgpt-shell
is a young package still. There are unexplored Emacs integrations out there. I'd love to hear about whatever you come up with (Mastodon / Twitter / Reddit / Email).
Recording and screenshotting windows: the lazy way
While there's no substitution for great written documentation, a quick demo can go a long way in conveying what a tool if capable of doing or what a tip/trick can achieve.
If you've read a handful of my posts, you would have come across either a screenshot or a short clip with some demo. Historically, I've used the macOS's built-in utility invoked via ⌘ + Shift + 5
. It does a fine job for screenshots. For video captures, it's got a couple of small quirks.
Record window
Unlike screenshots, macOS video capture cannot record a specific window. While you can select a region, it's easy to inadvertently include a portion of your wallpaper in the recording. Not a big deal, but I felt posted screencasts could look as clean as their screenshot counterparts if we could record the window alone.
Let's compare grabbing a region vs window alone. I know the clean look may be subjective, but see what I mean?
Figure 1: Capture region (includes wallpaper/background)
Figure 2: Capture window only (ahhh, so clean)
Cancel recording
macOS has a handy shortcut (⌘ + Ctrl + Esc
) to stop recording. If you got your demo right, you're done. If not, you have one more step remaining (right click to delete the blooper).
Also not a huge deal, but I was hoping for a single shortcut to stop recording and also automatically discard. I haven't found one, but would love to hear if otherwise.
macosrec enters the chat
I wanted more flexibility to build my own recording/screenshotting flows. A command line utility could be quite versatile at that, so I built macosrec.
macosrec
enables taking a screenshot or recording a window video entirely from the command line.
elisp glues the world
Command line utilities can be invoked in all sorts of ways, but I'm an Emacs nutter so you can see where this is going… I want Emacs key bindings to control the lot.
C-c _ | Take screenshot of a window |
C-c ( | Start recording window |
C-c ) | Stop recording window |
C-c 8 | Abort recording |
Integrating command line utilities into Emacs and making them quickly accessible seems to have become a full-time hobby of mine. I kid, but it's become a pretty painless process for me. I built dwim-shell-command for that. If you've never heard of DWIM, it stands for "Do what I mean". To give you an idea of the kinds of things I'm using DWIM commands for, check the following out:
- dwim-shell-commands-audio-to-mp3
- dwim-shell-commands-bin-plist-to-xml
- dwim-shell-commands-clipboard-to-qr
- dwim-shell-commands-drop-video-audio
- dwim-shell-commands-files-combined-size
- dwim-shell-commands-git-clone-clipboard-url
- dwim-shell-commands-git-clone-clipboard-url-to-downloads
- dwim-shell-commands-image-to-grayscale
- dwim-shell-commands-image-to-icns
- dwim-shell-commands-image-to-jpg
- dwim-shell-commands-image-to-png
- dwim-shell-commands-pdf-password-protect
- dwim-shell-commands-reorient-image
- dwim-shell-commands-resize-gif
- dwim-shell-commands-resize-image
- dwim-shell-commands-resize-video
- dwim-shell-commands-speed-up-gif
- dwim-shell-commands-speed-up-video
- dwim-shell-commands-unzip
- dwim-shell-commands-video-to-gif
- dwim-shell-commands-video-to-optimized-gif
- dwim-shell-commands-video-to-webp
If it ever took you a little while to find the right command incantation to get things right, only to forget all about it next time you need it (I'm looking at you ffmpeg), dwim-shell-command
can help you easily save things for posterity and make them easily accessible in the future.
Since we're talking ffmpeg, here's all it takes to have gif conversion handy:
(defun dwim-shell-commands-video-to-gif () "Convert all marked videos to gif(s)." (interactive) (dwim-shell-command-on-marked-files "Convert to gif" "ffmpeg -loglevel quiet -stats -y -i '<<f>>' -pix_fmt rgb24 -r 15 '<<fne>>.gif'" :utils "ffmpeg"))
There's no way I'll remember the ffmpeg command, but I can always fuzzy search my trusty commands with something like "to gif"
and apply to either the current buffer file or any selected dired files.
So where am I going with this? I wrote DWIM shell commands for the bindings I previously described:
C-c _ | dwim-shell-commands-macos-screenshot-window |
C-c ( | dwim-shell-commands-macos-start-recording-window |
C-c ) | dwim-shell-commands-macos-end-recording-window |
C-c 8 | dwim-shell-commands-macos-abort-recording-window |
Out of all of commands, dwim-shell-commands-macos-start-recording-window
is likely the most interesting one.
(defun dwim-shell-commands-macos-start-recording-window () "Select and start recording a macOS window." (interactive) (let* ((window (dwim-shell-commands--macos-select-window)) (path (dwim-shell-commands--generate-path "~/Desktop" (car window) ".mov")) (buffer-file-name path) ;; override so <<f>> picks it up (inhibit-message t)) (dwim-shell-command-on-marked-files "Start recording a macOS window." (format "# record .mov macosrec --record '%s' --mov --output '<<f>>' # speed .mov up x1.5 ffmpeg -i '<<f>>' -an -filter:v 'setpts=1.5*PTS' '<<fne>>_x1.5.<<e>>' # convert to gif x1.5 ffmpeg -loglevel quiet -stats -y -i '<<fne>>_x1.5.<<e>>' -pix_fmt rgb24 -r 15 '<<fne>>_x1.5.gif' # speed .mov up x2 ffmpeg -i '<<f>>' -an -filter:v 'setpts=2*PTS' '<<fne>>_x2.<<e>>' # convert to gif x2 ffmpeg -loglevel quiet -stats -y -i '<<fne>>_x2.<<e>>' -pix_fmt rgb24 -r 15 '<<fne>>_x2.gif'" (cdr window)) :silent-success t :monitor-directory "~/Desktop" :no-progress t :utils '("ffmpeg" "macosrec"))))
As you likely expect, this command invokes macosrec
to start recording a window. The nifty part is that when it's done recording (and saving the .mov file), it automatically creates multiple variants. For starters, it creates x1.5 and x2 .mov videos, but it also generates their .gif counterparts.
Let's recap here for a sec. You start recording a window video with C-c (
, end with C-c )
, and automagically have all these generated files waiting for you.
You can subsequently inspect any of the video candidates and pick the most appropriate variant. Discard whatever else you don't need.
The output bundle is tailored to my needs. Maybe you want to invoke gifsycle for more optimized versions? Or maybe you want automatic webp generation via ffmpeg
? DWIM does that I mean, so you likely have other plans…
dwim-shell-commands-macos-start-recording-window
and all other DWIM commands are now included in dwim-shell-commands.el, which ships optionally as part of dwim-shell-command.
macosrec is also on GitHub, but if you want to be on your way, you can install via:
brew tap xenodium/macosrec brew install macosrec
This is my way to record and screenshot windows the lazy way. How would you tweak to make it yours?
ob-swiftui updates
While experimenting with delegating Markdown blocks to Org babel in Emacs chatgpt-shell, I resurrected ob-swiftui. A package I had written to execute and render SwiftUI blocks in org babel.
ob-swiftui has two modes of rendering SwiftUI blocks: :results window
, which runs outside of Emacs in a native window and :results file
, which renders and saves to a file. The latter can be viewed directly from Emacs.
:results file
was a little clunky. That is, it hardcoded dimensions I had to manually modify if the canvas wasn't big enough. It was also a little slow.
The clunkyness really came through with my chatgpt-shell experiments, so I took a closer look and made a few changes to remove hardcoding and speeds things up.
The results ain't too shabby.
Another tiny improvement is that if you'd like to compose a more complex layout made of multiple custom views, ob-swiftui
now looks for a ContentView
as that root view by default. Specifying another root view was already possible but it had to be explicitly requested via :view
param.
You can now omit the :view
param if you name the root view ContentView
:
#+begin_src swiftui struct ContentView: View { var body: some View { TopView() BottomView() } } struct TopView: View { var body: some View { Text("Top text") } } struct BottomView: View { var body: some View { Text("Bottom text") } } #+end_src
The improvements have been pushed to ob-swiftui and will soon be picked up on melpa.
Edit: Added ContentView details.
My Emacs eye candy
I get the occasional question about my Emacs theme, font, and other eye candy. I'm always tickled and happy to share.
It's been a while since I've made visually significant changes to my Emacs config. May as well briefly document for posterity…
Nyan Mode
First things first. The adorable and colorful little fella in my mode line is a Nyan Cat (if you dare, check the meme video). Yes, I know it's sooo 2011, but it's 2023 and I still love the little guy hanging out in my Emacs mode line. I still get asked about it.
This fabulous feature comes to us via the great Nyan Mode package. If looks haven't convinced you, Nyan also packs scrolling functionality. Click anywhere in it.
Oh, and if you can't get enough of Nyan, there's also zone-nyan for Emacs.
Emacs Plus (macOS)
I should mention I'm running Emacs 28 on macOS via the excellent Emacs Plus homebrew recipe. These are all the options I enable.
brew install emacs-plus@28 --with-imagemagick --with-no-frame-refocus --with-native-comp --with-savchenkovaleriy-big-sur-icon
Icon
Since we're talking eye candy, let's chat about --with-savchenkovaleriy-big-sur-icon
. This Emacs Plus option enables Valeriy Savchenko's wonderful icon.
Titlebar
I've enabled both transparent title bar as well as dark appearance, giving a minimal window decoration.
(add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . dark))
Note: both of these variables are prefixed ns-
(macOS-only settings).
Font (JetBrains Mono)
I've been on JetBrains Mono font for quite some time now. In the past, I've also been a fan of Mononoki and Menlo (on macOS) or Meslo (similar elsewhere).
Theme (Material)
I'm using the great Material Theme for Emacs, with a bunch of tweaks of my own.
Modeline tabs/ribbons (Moody)
The moody package adds a nice touch displaying mode line elements as tabs and ribbons.
Modeline menus (Minions)
The minions package removes lots of minor mode clutter from the mode line and stashes it away in menus.
Hiding modeline (hide mode line mode)
Hiding the mode line isn't something I use in most major modes. However, I found it complements my shell (eshell) quite well. While I was sceptical at first, once I hid the mode line in my shell I never looked back. I just didn't miss it. I also love the uncluttered clean vibe. hide-mode-line-mode can help with that.
Welcome screen
Back in October 2022, I experimented with adding a minimal welcome screen. I was initially hesitant, as I was already a fan of the welcome scratch buffer. In any case, I figured I'd eventually get tired of it and remove it. Well, it's enabled in my config still ;) My initial attachment to a landing scratch quickly faded. I'm only a C-x b
binding away from invoking ivy-switch-buffer to get me anywhere.
The great Emacs logo originally shared by u/pearcidar43.
Zones
I've been meaning to re-enable zones in my config. They always gave me a good tickle. I've already mentioned zone-nyan, but if you're new to zones, they kick off after a period of inactivity (similar to a screensaver).
Here's zone-pgm-rotate
in all its glory. Oh and it's built-in!
Coincidentally, I had a go at writing a basic zone a little while ago.
Config
Most of the items mentioned I pulled from my Emacs config's fe-ui.el. There's more there if you're interested.
What is some of your favorite Emacs eye candy? reddit / mastodon / twitter.
shell-maker, a maker of Emacs shells
A few weeks ago, I wrote about an experiment to bring ChatGPT to Emacs as a shell. I was fairly new to both ChatGPT and building anything on top of comint. It was a fun exercise, which also generated some interest.
As mentioned in the previous post, I took inspiration in other Emacs packages (primarily ielm) to figure out what I needed from comint. Soon, I got ChatGPT working.
As I was looking at OpenAI API docs, I learned about DALL-E: "an AI system that can create realistic images and art from a description in natural language."
Like ChatGPT, they also offered an API to DALL-E, so I figured I may as well try to write a shell for that too… and I did.
There was quite a bit of code duplication between the two Emacs shells I had just written. At the same time, I started hearing from folks about integrating other tools, some cloud-based, some local, proprietary, open source.. There's Cody, invoke-ai, llama.cpp, alpaca.cpp, and the list continues to grow.
With that in mind, I set out to reduce the code duplication and consolidate into a reusable package. And so shell-maker
was born, a maker of Emacs shells.
shell-maker
's internals aren't too different from the code I had before. It's still powered by comint, but instead offers a reusable convenience wrapper.
It takes little code to implement a shell, like the sophisticated new greeter-shell
;)
(require 'shell-maker) (defvar greeter-shell--config (make-shell-maker-config :name "Greeter" :execute-command (lambda (command _history callback error-callback) (funcall callback (format "Hello \"%s\"" command) nil)))) (defun greeter-shell () "Start a Greeter shell." (interactive) (shell-maker-start greeter-shell--config))
shell-maker is available on GitHub and currently bundled with chatgpt-shell. If there's enough interest and usage, I may just break it out into its own package. For now, it's convenient to keep with chatgpt-shell
and dall-e-shell
.
If you plug shell-maker
into other tools, I'd love to hear about it.
Happy shell making!
Flat Habits 1.1.4 released
Flat Habits 1.1.4 is now available on the App Store.
Flat Habits is a habit tracker that’s mindful of your time, data, and privacy. It's a simple but effective iOS app.
If you care about how your data is stored, Flat Habits is powered by org plain text markup without any cloud component. You can use your favorite editor (Emacs, Vim, VSCode, etc.) to poke at habit data, if that's your cup of tea.
What's new?
- Quicker toggling, now exposing Done/Skip.
- Double tap marks Done.
- Also display in 12 hour time format.
- Overdue habits are now labelled "past" and coloured orange.
- Don't dismiss creation dialog if tapping outside.
- Set #+STARTUP: nologdrawer in new files.
Are you a fan?
Is Flat Habits helping you keep up with your habits? Please rate/review 😊
A ChatGPT Emacs shell
UPDATE: chatgpt-shell
has evolved a bit and is now on MELPA.
I had been meaning to give ChatGPT a good try, preferably from Emacs. As an eshell fan, ChatGPT seemed like the perfect fit for a shell interface of sorts. With that in mind, I set out to wire ChatGPT with Emacs's general command interpreter (comint).
I had no previous experience building anything comint-related, so I figured I could just take a peek at an existing comint-derived mode to achieve a similar purpose. inferior-emacs-lisp-mode
(ielm) seemed to fit the bill just fine, so I borrowed quite a bit to assemble a basic shell experience.
From then on, it was mostly about sending each request over to the ChatGPT API to get a response. For now, I'm relying on curl to make each request. The invocation is fairly straightforward:
curl "https://api.openai.com/v1/chat/completions" \ -H "Authorization: Bearer YOUR_OPENAI_KEY" \ -H "Content-Type: application/json" \ -d "{ \"model\": \"gpt-3.5-turbo\", \"messages\": [{\"role\": \"user\", \"content\": \"YOUR PROMPT\"}] }"
There are two bits of information needed in each request. The API key, which you must get from OpenAI, and the prompt text itself (i.e. whatever you want ChatGPT to help you with). The results are not too shabby.
I've uploaded the code to GitHub as a tiny chatgpt-shell package. It's a little experimental and rough still, but hey, it does the job for now. Head over to github to take a look. The latest iteration handles multiline prompts (use C-j for newlines) and basic code highlighting.
Let's see where it all goes. Pull requests for improvements totally welcome ;-)
*scratch*
a new minimal org mode scratch area for iOS
While we already have lots of note-taking apps on iOS, I wanted a minimal *scratch*
area (à la Emacs), so I built one.
What's the use-case? You're on the go. Someone's telling you directions, or a phone number, name of a restaurant, anything really… you just need to write it down right now, quickly!
No time to create a new contact, a note, a file, or spend time on additional taps, bring up keyboard… You just want to write it somewhere with the least amount of friction.
Being an Emacs and org user, I had to sprinkle the app with basic markup support for headings, lists and checkboxes. Also, having a *scratch*
"buffer" on my iPhone gives me that warm emacsy fuzzy feeling :)
You can download *scratch*
from the App Store.
Find it useful? Please help me spread the word. Tell your friends.
Chicken Karaage recipe
Huge fan of Chicken Karaage, but never really made it at home until recently.
Dice the chicken
- 350 grams boneless chicken thighs
Dice the chicken up.
Marinade for 30 mins
- 1 tablespoon soy sauce (Kikkoman or similar)
- 1 tablespoon cooking Sake
- 2 tablespoons of grated ginger (include liquids)
- 1/2 teaspoon Mirin
Mix all ingredients into a ziploc bag. Add the diced chicken and let it marinade for 30 minutes in the fridge.
Pat dry
- Paper towels
After marinating, pat the chicken dry with paper towels and set aside.
Breading
- Potato starch
Ok, not quite breading since we're using potato starch but same goal. Sprinkle the chicken pieces and make sure they are fully coated with the starch.
Frying (1st round)
- Vegetable oil
- Paper towels
Heat up (roughly at 160°C) enough oil in a pan to cover the chicken pieces. Cook for about 3 minutes. The pieces don't have to be super golden at this point. There will be another round of frying for that.
Rest for 4 minutes
- Paper towels
Let the chicken rest on paper towels for about 4 minutes before frying again.
Frying (2nd round)
- Vegetable oil
- Paper towels
This time heat up the oil at roughly 200°C. This is a quick in-and-out action to make the chicken crispy. Cook for 30 seconds. Take out and set aside on some paper towels. Let it cool and it's ready to eat.
Dipping
- Kewpie mayo
- Sriracha sauce
This is totally optional, but I'm a fan of both Kewpie mayo and Sriracha sauce. You can dip your chicken in either or both!
Emacs: org-present in style
I had been meaning to check out David Wilson's System Crafters post detailing his presentations style achieved with the help of org-present and his own customizations. If you're looking for ways to present from Emacs itself, David's post is well worth a look.
org-present's spartan but effective approach resonated with me. David's touches bring the wonderfully stylish icing to the cake. I personally liked his practice of collapsing slide subheadings by default. This lead me to think about slide navigation in general…
There were two things I wanted to achieve:
- Easily jump between areas of interest. Subheadings, links, and code blocks would be a good start.
- Collapse all but the current top-level heading within the slide, as navigation focus changes.
A quick search for existing functions led me to org-next-visible-heading
, org-next-link
, and org-next-block
. While these make it easy to jump through jump between headings, links, org block on their own, I wanted to jump to whichever one of these is next (similar a web browser's tab behaviour). In a way, DWIM style.
I wrapped the existing functions to enable returning positions. This gave me ar/rg-next-visible-heading-pos
, ar/rg-next-link-pos
, and ar/rg-next-block-pos
respectively. Now that I can find out the next location of either of these items, I can subsequently glue the navigation logic in a function like ar/org-present-next-item
. To restore balance to the galaxy, I also added ar/org-present-previous-item
.
(defun ar/org-present-next-item (&optional backward) "Present and reveal next item." (interactive "P") ;; Beginning of slide, go to previous slide. (if (and backward (eq (point) (point-min))) (org-present-prev) (let* ((heading-pos (ar/org-next-visible-heading-pos backward)) (link-pos (ar/org-next-link-pos backward)) (block-pos (ar/org-next-block-pos backward)) (closest-pos (when (or heading-pos link-pos block-pos) (apply (if backward #'max #'min) (seq-filter #'identity (list heading-pos link-pos block-pos)))))) (if closest-pos (progn (cond ((eq heading-pos closest-pos) (goto-char heading-pos)) ((eq link-pos closest-pos) (goto-char link-pos)) ((eq block-pos closest-pos) (goto-char block-pos))) ;; Reveal relevant content. (cond ((> (org-current-level) 1) (ar/org-present-reveal-level2)) ((eq (org-current-level) 1) ;; At level 1. Collapse children. (org-overview) (org-show-entry) (org-show-children) (run-hook-with-args 'org-cycle-hook 'children)))) ;; End of slide, go to next slide. (org-present-next))))) (defun ar/org-present-previous-item () (interactive) (ar/org-present-next-item t)) (defun ar/org-next-visible-heading-pos (&optional backward) "Similar to `org-next-visible-heading' but for returning position. Set BACKWARD to search backwards." (save-excursion (let ((pos-before (point)) (pos-after (progn (org-next-visible-heading (if backward -1 1)) (point)))) (when (and pos-after (not (equal pos-before pos-after))) pos-after)))) (defun ar/org-next-link-pos (&optional backward) "Similar to `org-next-visible-heading' but for returning position. Set BACKWARD to search backwards." (save-excursion (let* ((inhibit-message t) (pos-before (point)) (pos-after (progn (org-next-link backward) (point)))) (when (and pos-after (or (and backward (> pos-before pos-after)) (and (not backward) (> pos-after pos-before)))) pos-after)))) (defun ar/org-next-block-pos (&optional backward) "Similar to `org-next-block' but for returning position. Set BACKWARD to search backwards." (save-excursion (when (and backward (org-babel-where-is-src-block-head)) (org-babel-goto-src-block-head)) (let ((pos-before (point)) (pos-after (ignore-errors (org-next-block 1 backward) (point)))) (when (and pos-after (not (equal pos-before pos-after))) ;; Place point inside block body. (goto-char (line-beginning-position 2)) (point))))) (defun ar/org-present-reveal-level2 () (interactive) (let ((loc (point)) (level (org-current-level)) (heading)) (ignore-errors (org-back-to-heading t)) (while (or (not level) (> level 2)) (setq level (org-up-heading-safe))) (setq heading (point)) (goto-char (point-min)) (org-overview) (org-show-entry) (org-show-children) (run-hook-with-args 'org-cycle-hook 'children) (goto-char heading) (org-show-subtree) (goto-char loc)))
Beware, this was a minimal effort (with redundant code, duplication, etc) and should likely be considered a proof of concept of sorts, but the results look promising. You can see a demo in action.
While this was a fun exercise, I can't help but think there must be a cleaner way of doing it or there are existing packages that already do this for you. If you do know, I'd love to know.
Future versions of this code will likely be updated in my Emacs org config.
Update
Removed a bunch of duplication and now rely primarily on existing org-next-visible-heading
, org-next-link
, and org-next-block
.
Emacs: insert and render SF symbols
About a week ago, I added an Emacs function to insert SF symbol names. This is specially useful for SwiftUI. I didn't bother too much with inserting symbols themselves since I hadn't figured out a way to render them for all buffers. That's now changed.
Christian Tietze and Alan Third both have useful posts in this space:
I'm currently using the following to render SF symbols in all buffers (macOS only):
;; Enable rendering SF symbols on macOS. (when (memq system-type '(darwin)) (set-fontset-font t nil "SF Pro Display" nil 'append))
Now that I can render SF symbols everywhere, I may be more included to use them to spif things up.
I've added sf-symbol-insert
to sf.el, let's see if usage sticks.
Emacs: Macro me some SF Symbols
For inserting SF Symbols in SwiftUI, I typically rely on Apple's SF Symbols app to browse the symbols's catalog. Once I find a symbol I'm happy with, I copy its name and paste it into my Swift source. This works fairly well.
With Christian Tietze recently posting how he rendered SF Symbols in Emacs, I figured there may be a way to shift the above workflow to rely on Emacs completion instead. While I initially went down a rabbit hole to programmatically extract SF symbols (via something like SFSafeSymbols), I took a step back to rethink the strategy.
From the SF Symbols app, one can select multiple symbols and copy/paste either the symbols themselves or their respective names. The catch is you can only copy disjointed data. That is, you can copy the symbols or their names, but not both in one go. Let's take a look at what the disjointed data looks like. I've pasted both under separate sections in an Emacs buffer.
If I could rejoin these two sets, I would have a lookup table I could easily invoke from Emacs.
There are roughly 4500 symbols, so copying, pasting, along with text manipulation isn't manually feasible. Lucky for us, an Emacs keyboard macro is the perfect hammer for this nail. You can see the macro in action below.
This looks fairly magical (and it is), but when you break it down into its building blocks, it's nothing more than recording your keystrokes and replaying them. Starting with the cursor at the beginning of square.and.arrow.up
, these are the keystrokes we'd need to record:
- C-s
- iseach-forward to search for a character and jump to it
- =
- insert
=
so we jump to= Symbols =
- <return>
- runs isearch-exit since we're done jumping.
- C-n
next-line
.- C-a
beginning-of-line
.- C-SPC
set-mark-command
to activate the region.- C-f
forward-char
to select symbol.- C-w
kill-ring-save
to cut/kill the symbol.- C-u C-<space>
set-mark-command
(with prefix) to jump back to where we started before searching.- C-y
yank
to yank/paste the symbol.- C-<space>
set-mark-command
to activate the region.- C-e
end-of-line
to select the entire line.- "
- As a smartparens user, inserting quote with region places quotes around selection.
- C-n
next-line
.- C-a
beginning-of-line
. We are now at a strategic location where we can replay the above commands.
To start/end recording and executing keyboard macros, use:
- C-x (
- kmacro-start-macro
- C-x )
- kmacro-end-macro
- C-x e
- kmacro-end-and-call-macro runs your macro. Press
e
immediately after to execute again. - C-u 0 C-x e
- kmacro-end-and-call-macro (with zero prefix) repeat until there is an error.
Our previous example ran on a handful of SF symbols. Let's bring out the big guns and run on the entire dataset. This time, we'll run the entire flow, including macro creation and executing until there is an error (i.e. process the whole lot).
Now that we have our data joined, we can feed it to the humble completing-read.
It's worth highlighting that to render SF Symbols in Emacs, we must propertize our text with one of the macOS SF fonts, for example "SF Pro".
With all the pieces in place, let's use our new function to insert SF symbol names in a SwiftUI snippet. Since we're using completing-read
we can fuzzy search our lookups with our favorite completion frameworks (in my case via ivy).
While this post is macOS-specific, it gives a taste of how powerful Emacs keyboard macros can be. Be sure to check out Emacs Rocks! Episode 05: Macros in style and Keyboard Macros are Misunderstood - Mastering Emacs. For those that dabble in elisp, you can appreciate how handy completing-read is with very little code.
The full source to sf-symbol-insert-name is available in my Emacs config repo. The function is fairly bare bones and has had fairly little testing. Patches totally welcome.
Update
There is some redundancy in the snippet I had forgotten to remove. Either way, latest version at sf.el.
Emacs: ffmpeg and macOS aliasing commands
On a recent mastodon post, Chris Spackman mentioned he uses Emacs to save ffmpeg commands he's figured out for later usage. Emacs is great for this kind of thing. I've tried different approaches over time and eventually landed on dwim-shell-command, a small package I wrote. Like Chris, I also wanted a way to invoke magical incantations of known shell commands without having to remember all the details.
Chris's post reminded me of a few use-cases I'd been meaning to add DWIM shell commands for.
ffmpeg
- Trimming seconds from videos
dwim-shell-commands-video-trim-beginning
using:ffmpeg -i '<<f>>' -y -ss <<Seconds:5>> -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'
dwim-shell-commands-video-trim-end
using:ffmpeg -sseof -<<Seconds:5>> -i '<<f>>' -y -c:v copy -c:a copy '<<fne>>_trimmed.<<e>>'
Side-node: The
<<Seconds:5>>
placeholder is recognized as a query, so Emacs will prompt you for a numeric value.
- Extracting audio from videos
dwim-shell-commands-video-to-mp3
using:ffmpeg -i '<<f>>' -vn -ab 128k -ar 44100 -y '<<fne>>.mp3'
With these new dwim shell commands added, I can easily apply them one after the other. No need to remember command details.
macOS aliases
After rebuilding Emacs via the wonderful emacs-plus, I recently broke my existing /Applications/Emacs.app
alias. No biggie, one can easily add a new one alias from macOS Finder, but I've been wanting to do it from Emacs. Turns out there's a bit of AppleScript we can turn into a more memorale command like dwim-shell-commands-macos-make-finder-alias
:
osascript -e 'tell application \"Finder\" to make alias file to POSIX file \"<<f>>\" at POSIX file \"%s\"'
It's highly unlikely I'll remember the AppleScript snippet (are there better ways?), but I'll easily find and invoke my new command with fuzzy searching:
Included in dwim-shell-command
All of these are now included in dwim-shell-commands.el, which you can optionally load after installing dwim-shell-command from MELPA.
Emacs: Context-aware yasnippets
Back in 2020, I wrote a semi-automatic yasnippet to generate Swift initializers. I say semi-automatic because it could have been a little smarter. While it helped generate some of the code, what I really wanted was full context-aware generation. The Swift struct already had a few properties defined, so a smarter yasnippet should have been able to use this info for code generation.
With an extra push, we could have written a smarter yasnippet, but it may require a fair bit of parsing logic. Fast forward to today, and bringing context-awareness seems like the right match for Tree-sitter. While Tree-sitter can enable faster and more reliable syntax-highlighting in our beloved text editor, it can also power smarter tools. It does so by exposing a semantic snapshot of our source code using a syntax tree.
Let's see how we can use Tree-sitter to realise our original yasnippet vision. We'll start with the same struct snippet we used back in 2020. The goal is to generate an initializer using the existing definitions.
struct Coordinate { public let x: Int public let y: Int public let z: Int }
While Emacs will will soon ship its own Tree-sitter integration, I've opted to try out the emacs-tree-sitter package as Swift support is currently included in tree-sitter-langs.
I have much to learn much about Tree-sitter syntax trees, but the package ships with a handy tool to dump the tree via tree-sitter-debug-mode
.
With a syntax tree in mind, one can craft a query to semantically extract parts of the code. In our case, we want property names and types.
I've yet to get acquainted with Tree-sitter's query syntax, but the package also ships with another handy tool that helps view query results via tree-sitter-query-builder
.
The following query extracts all the let properties
in file. You can see the builder in action above, highlighting our query results.
(struct_declaration (constant_declaration (identifier) @name (type) @value))
If we want to be more thorough, we should likely cater for classes, vars, int/string literals, etc. so the query needs to be extended as follows. I'm sure it can be written differently, but for now, it does the job.
(struct_declaration (variable_declaration (identifier) @name (type) @type)) (struct_declaration (variable_declaration (identifier) @name (string) @value)) (struct_declaration (variable_declaration (identifier) @name (number) @value)) (struct_declaration (constant_declaration (identifier) @name (type) @value)) (struct_declaration (constant_declaration (identifier) @name (string) @value)) (struct_declaration (constant_declaration (identifier) @name (number) @value)) (class_declaration (variable_declaration (identifier) @name (type) @type)) (class_declaration (variable_declaration (identifier) @name (string) @value)) (class_declaration (variable_declaration (identifier) @name (number) @value)) (class_declaration (constant_declaration (identifier) @name (type) @type)) (class_declaration (constant_declaration (identifier) @name (string) @value)) (class_declaration (constant_declaration (identifier) @name (number) @value))
Now that we got our Tree-sitter query sorted, let's write a little elisp to extract the info we need from the generated tree. We'll write a swift-class-or-struct-vars-at-point
function to extract the struct (or class) at point and subsequently filter its property names/types using our query. To simplify the result, we'll return a list of alists.
(defun swift-class-or-struct-vars-at-point () "Return a list of class or struct vars in the form '(((name . \"foo\") (type . \"Foo\")))." (cl-assert (seq-contains local-minor-modes 'tree-sitter-mode) "tree-sitter-mode not enabled") (let* ((node (or (tree-sitter-node-at-point 'struct_declaration) (tree-sitter-node-at-point 'class_declaration))) (vars) (var)) (unless node (error "Neither in class nor struct")) (mapc (lambda (item) (cond ((eq 'identifier (tsc-node-type (cdr item))) (when var (setq vars (append vars (list var)))) (setq var (list (cons 'name (tsc-node-text (cdr item)))))) ((eq 'type (tsc-node-type (cdr item))) (setq var (map-insert var 'type (tsc-node-text (cdr item))))) ((eq 'string (tsc-node-type (cdr item))) (setq var (map-insert var 'type "String"))) ((eq 'number (tsc-node-type (cdr item))) (setq var (map-insert var 'type "Int"))) (t (message "%s" (tsc-node-type (cdr item)))))) (tsc-query-captures (tsc-make-query tree-sitter-language "(struct_declaration (variable_declaration (identifier) @name (type) @type)) (struct_declaration (variable_declaration (identifier) @name (string) @value)) (struct_declaration (variable_declaration (identifier) @name (number) @value)) (struct_declaration (constant_declaration (identifier) @name (type) @value)) (struct_declaration (constant_declaration (identifier) @name (string) @value)) (struct_declaration (constant_declaration (identifier) @name (number) @value)) (class_declaration (variable_declaration (identifier) @name (type) @type)) (class_declaration (variable_declaration (identifier) @name (string) @value)) (class_declaration (variable_declaration (identifier) @name (number) @value)) (class_declaration (constant_declaration (identifier) @name (type) @type)) (class_declaration (constant_declaration (identifier) @name (string) @value)) (class_declaration (constant_declaration (identifier) @name (number) @value))") node nil)) (when var (setq vars (append vars (list var)))) vars))
(((type . "Int") (name . "x")) ((type . "Int") (name . "y")) ((type . "Int") (name . "z")))
Finally, we write a function to generate a Swift initializer from our property list.
(defun swift-class-or-struct-initializer-text (vars) "Generate a Swift initializer from property VARS." (cl-assert (seq-contains local-minor-modes 'tree-sitter-mode) "tree-sitter-mode not enabled") (format (string-trim " init(%s) { %s }") (seq-reduce (lambda (reduced var) (format "%s%s%s: %s" reduced (if (string-empty-p reduced) "" ", ") (map-elt var 'name) (map-elt var 'type))) vars "") (string-join (mapcar (lambda (var) (format "self.%s = %s" (map-elt var 'name) (map-elt var 'name))) vars) "\n ")))
init(x: Int, y: Int, z: Int) { self.x = x self.y = y self.z = z }
We're so close now. All we need is a simple way invoke our code generator. We can use yasnippet for that, making init
our expandable keyword.
# -*- mode: snippet -*- # name: init all # key: init # -- `(swift-class-or-struct-initializer-text (swift-class-or-struct-vars-at-point))`
And with all that, we've got our yasnippet vision accomplished!
Be sure to check out this year's relevant EmacsConf talk: Tree-sitter beyond syntax highlighting.
All code is now pushed to my config repo. By the way, I'm not super knowledgable of neither yasnippet nor Tree-sitter. Improvements are totally welcome. Please reach out on the Fediverse if you have suggestions!
Update
Josh Caswell kindly pointed out a couple of interesting items:
- tree-sitter-langs's Swift grammar is fairly outdated/incomplete.
- There are more up-to-date Swift grammar implementations currently available:
- tree-sitter-swifter (by Josh Caswell himself)
- tree-sitter-swift (by Alex Pinkus)
Emacs: quickly killing processes
Every so often, I need to kill the odd unresponsive process. While I really like proced
(check out Mickey Petersen's article), I somehow find myself using macOS's Activity Monitor to this purpose. Kinda odd, considering I prefer to do these kinds of things from Emacs.
What I'd really like is a way to quickly fuzzy search a list of active processes and choose the unresponsive culprid, using my preferred completion frontend (in my case ivy).
The function below gives us a fuzzy-searchable process utility. While we could use ivy-read
directly in our implementation, we're better of using completing-read to remain compatible with other completion frameworks. I'm a big fan of the humble completing-read
. You feed it a list of candidates and it prompts users to pick one.
To build our process list, we can lean on proced
's own source: proced-process-attributes
. We transform its output to an alist, formatting the visible keys to contain the process id, owner, command name, and the command line which invoked the process. Once a process is chosen, we can send a kill signal using signal-process dwim-shell-command and our job is done.
(require 'dwim-shell-command) (require 'map) (require 'proced) (require 'seq) (defun dwim-shell-commands-kill-process () "Select and kill process." (interactive) (let* ((pid-width 5) (comm-width 25) (user-width 10) (processes (proced-process-attributes)) (candidates (mapcar (lambda (attributes) (let* ((process (cdr attributes)) (pid (format (format "%%%ds" pid-width) (map-elt process 'pid))) (user (format (format "%%-%ds" user-width) (truncate-string-to-width (map-elt process 'user) user-width nil nil t))) (comm (format (format "%%-%ds" comm-width) (truncate-string-to-width (map-elt process 'comm) comm-width nil nil t))) (args-width (- (window-width) (+ pid-width user-width comm-width 3))) (args (map-elt process 'args))) (cons (if args (format "%s %s %s %s" pid user comm (truncate-string-to-width args args-width nil nil t)) (format "%s %s %s" pid user comm)) process))) processes)) (selection (map-elt candidates (completing-read "kill process: " (seq-sort (lambda (p1 p2) (string-lessp (nth 2 (split-string (string-trim (car p1)))) (nth 2 (split-string (string-trim (car p2)))))) candidates) nil t))) (prompt-title (format "%s %s %s" (map-elt selection 'pid) (map-elt selection 'user) (map-elt selection 'comm)))) (when (y-or-n-p (format "Kill? %s" prompt-title)) (dwim-shell-command-on-marked-files (format "Kill %s" prompt-title) (format "kill -9 %d" (map-elt selection 'pid)) :utils "kill" :error-autofocus t :silent-success t))))
I've pushed dwim-shell-commands-kill-process
to my config dwim-shell-commands.el. Got suggestions? Alternatives? Lemme know.
Update
I've moved dwim-shell-commands-kill-process
from my Emacs config to dwim-shell-commands.el. A few advantages:
- Killing processes is now async.
- Should anything go wrong, an error message is now accessible.
- You can easily install via MELPA.
If you prefer the previous version (without a dependency on dwim-shell-command), have a look at the initial commit.
Hey Emacs, change the default macOS app for…
A few weeks ago, I added an "open with" command to dwim-shell-commands.el. It's pretty handy for opening files using an external app (ie. not Emacs) other than the default macOS one.
dwim-shell-commands-macos-open-with
and dwim-shell-commands-open-externally
are typically enough for me to handle opening files outside of Emacs. But every now and then I'd like to change the default macOS app associated with specific file types. Now this isn't particularly challenging in macOS, but it does require a little navigating to get to the right place to change this default setting.
Back in March 2020, I tweeted about duti: a command-line utility capable of setting default applications for various document types on macOS. While I liked the ability to change default apps from the command-line, the habit never quite stuck.
Fast forward to 2022. I've been revisiting lots of my command-line usages (specially those that never stuck) and making them more accessible from Emacs via dwim-shell-command. I seldom change default apps on macOS, so my brain forgets about duti
itself, let alone its arguments, order, etc. But with a dwim shell command like dwim-shell-commands-macos-set-default-app
, I can easily invoke the command via swiper's counsel-M-x
fuzzy terms: "dwim set".
As an added bonus, I get to reuse dwim-shell-commands--macos-apps
from "open with" to quickly pick the new default app, making the whole experience pretty snappy.
(defun dwim-shell-commands-macos-set-default-app () "Set default app for file(s)." (interactive) (let* ((apps (dwim-shell-commands-macos-apps)) (selection (progn (cl-assert apps nil "No apps found") (completing-read "Set default app: " apps nil t)))) (dwim-shell-command-on-marked-files "Set default app" (format "duti -s \"%s\" '<<e>>' all" (string-trim (shell-command-to-string (format "defaults read '%s/Contents/Info.plist' CFBundleIdentifier" (map-elt apps selection))))) :silent-success t :no-progress t :utils "duti"))) (defun dwim-shell-commands--macos-apps () "Return alist of macOS apps (\"Emacs\" . \"/Applications/Emacs.app\")." (mapcar (lambda (path) (cons (file-name-base path) path)) (seq-sort #'string-lessp (seq-mapcat (lambda (paths) (directory-files-recursively paths "\\.app$" t (lambda (path) (not (string-suffix-p ".app" path))))) '("/Applications" "~/Applications" "/System/Applications")))))
As usual, I've added dwim-shell-commands-macos-set-default-app
to dwim-shell-commands.el, which you can install via MELPA.
Did you find this tiny integration useful? Check out Hey Emacs, where did I take that photo?
Hey Emacs, where did I take that photo?
I was recently browsing through an old archive of holiday photos (from dired of course). I wanted to know where the photo was taken, which got me interested in extracting Exif metadata.
Luckily the exiftool command line utility does the heavy lifting when it comes to extracting metadata. Since I want it quickly accessible from Emacs (in either dired or current buffer), a tiny elisp snippet would give me just that (via dwim-shell-command).
(defun dwim-shell-commands-image-exif-metadata () "View EXIF metadata in image(s)." (interactive) (dwim-shell-command-on-marked-files "View EXIF" "exiftool '<<f>>'" :utils "exiftool"))
The above makes all Exif metadata easily accessible, including the photo's GPS coordinates. But I haven’t quite answered the original question. Where did I take the photo? I now know the coordinates, but I can’t realistically deduce neither the country nor city unless I manually feed these values to a reverse geocoding service like OpenStreetMap. Manually you say? This is Emacs, so we can throw more elisp glue at the problem, mixed in with a little shell script, and presto! We've now automated the process of extracting metadata, reverse geocoding, and displaying the photo's address in the minibuffer. Pretty nifty.
(defun dwim-shell-commands-image-reverse-geocode-location () "Reverse geocode image(s) location." (interactive) (dwim-shell-command-on-marked-files "Reverse geocode" "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\" if [ -z \"$lat\" ]; then echo \"no latitude\" exit 1 fi lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\" if [ -z \"$lon\" ]; then echo \"no longitude\" exit 1 fi json=$(curl \"https://nominatim.openstreetmap.org/reverse?format=json&accept-language=en&lat=${lat}&lon=${lon}&zoom=18&addressdetails=1\") echo \"json_start $json json_end\"" :utils '("exiftool" "curl") :silent-success t :error-autofocus t :on-completion (lambda (buffer) (with-current-buffer buffer (goto-char (point-min)) (let ((matches '())) (while (re-search-forward "^json_start\\(.*?\\)json_end" nil t) (push (match-string 1) matches)) (message "%s" (string-join (seq-map (lambda (json) (map-elt (json-parse-string json :object-type 'alist) 'display_name)) matches) "\n"))) (kill-buffer buffer)))))
Displaying the photo's address in the minibuffer is indeed pretty nifty, but what if I’d like to drop a pin in a map for further exploration? This is actually simpler, as there's no need for reverse geocoding. Following a similar recipe, we merely construct an OpenStreetMap URL and open it in our favourite browser.
(defun dwim-shell-commands-image-browse-location () "Open image(s) location in browser." (interactive) (dwim-shell-command-on-marked-files "Browse location" "lat=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f2-2)\" if [ -z \"$lat\" ]; then echo \"no latitude\" exit 1 fi lon=\"$(exiftool -csv -n -gpslatitude -gpslongitude '<<f>>' | tail -n 1 | cut -s -d',' -f3-3)\" if [ -z \"$lon\" ]; then echo \"no longitude\" exit 1 fi if [[ $OSTYPE == darwin* ]]; then open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\" else xdg-open \"http://www.openstreetmap.org/?mlat=${lat}&mlon=${lon}&layers=C\" fi" :utils "exiftool" :error-autofocus t :silent-success t))
Got suggestions? Improvements? All three functions are now included in dwim-shell-commands.el as part of dwim-shell-command. Pull requests totally welcome ;)
Emacs: A welcoming experiment
The *scratch*
buffer is the first thing I see when I launch an Emacs session. Coupled with persistent-scratch, it's served me well over the years. I gotta say though, my scratch buffer accumulates random bits and often becomes a little messy. It's not the most visually appealing landing buffer when launching Emacs. But who cares, I'm only a C-x b
binding away from invoking ivy-switch-buffer
to get me wherever I need to be. It's powered by ivy-use-virtual-buffers
, which remembers recent files across sessions.
Having said all of this, I recently ran into u/pearcidar43's post showcasing a wonderful Emacs banner. Lucky for us, they shared the image, so I got curious about building a minimal welcome buffer of sorts. Nothing fancy, the only requirements being to load quickly and enable me to get on with my C-x b
ritual. Throw in a little bonus to exit quickly by pressing just q
if I so desire.
I didn't know a whole lot on how to go about it, so I took a peak at emacs-dashboard for inspiration. Turns out, I needed little code to get
the desired effect in my early-init.el
:
(defun ar/show-welcome-buffer () "Show *Welcome* buffer." (with-current-buffer (get-buffer-create "*Welcome*") (setq truncate-lines t) (let* ((buffer-read-only) (image-path "~/.emacs.d/emacs.png") (image (create-image image-path)) (size (image-size image)) (height (cdr size)) (width (car size)) (top-margin (floor (/ (- (window-height) height) 2))) (left-margin (floor (/ (- (window-width) width) 2))) (prompt-title "Welcome to Emacs!")) (erase-buffer) (setq mode-line-format nil) (goto-char (point-min)) (insert (make-string top-margin ?\n )) (insert (make-string left-margin ?\ )) (insert-image image) (insert "\n\n\n") (insert (make-string (floor (/ (- (window-width) (string-width prompt-title)) 2)) ?\ )) (insert prompt-title)) (setq cursor-type nil) (read-only-mode +1) (switch-to-buffer (current-buffer)) (local-set-key (kbd "q") 'kill-this-buffer))) (setq initial-scratch-message nil) (setq inhibit-startup-screen t) (when (< (length command-line-args) 2) (add-hook 'emacs-startup-hook (lambda () (when (display-graphic-p) (ar/show-welcome-buffer)))))
This being Emacs, I can bend it as far as needed. In my case, I didn't need much, so I can probably stop here. It was a fun experiment. I'll even try using it for a little while and see if it sticks. I'm sure there's plenty more that could be handled (edge cases, resizes, etc.), but if you want something more established, consider something like emacs-dashboard instead. I haven't used it myself, but is pretty popular.
Emacs: Open with macOS app
On a recent Reddit comment, tdstoff7 asked if I had considered writing an "Open with" DWIM shell command for those times one would like to open a file externally using an app other than the default. I hadn't, but nice idea.
Take images as an example. Though Emacs can display them quickly, I also open images externally using the default app (Preview in my case). But then there are those times when I'd like to open with a different app for editing (maybe something like GIMP). It'd be nice to quickly choose which app to open with.
There isn't much to the code. Get a list of apps, ask user to pick one (via completing-read), and launch the external app via dwim-shell-command-on-marked-files
.
There's likely a better way of getting a list of available apps (happy to take suggestions), but searching in "/Applications" "~/Applications" and "/System/Applications" does the job for now.
(defun dwim-shell-commands-macos-open-with () "Convert all marked images to jpg(s)." (interactive) (let* ((apps (seq-sort #'string-lessp (seq-mapcat (lambda (paths) (directory-files-recursively paths "\\.app$" t (lambda (path) (not (string-suffix-p ".app" path))))) '("/Applications" "~/Applications" "/System/Applications")))) (selection (progn (cl-assert apps nil "No apps found") (completing-read "Open with: " (mapcar (lambda (path) (propertize (file-name-base path) 'path path)) apps))))) (dwim-shell-command-on-marked-files "Open with" (format "open -a '%s' '<<*>>'" (get-text-property 0 'path selection)) :silent-success t :no-progress t :utils "open")))
dwim-shell-commands-macos-open-with
is now included in dwim-shell-command, available on melpa. What other uses can you find for it?
Improving on Emacs macOS sharing
A quick follow-up to Emacs: macOS sharing (DWIM style)… Though functional, the implementation had a couple of drawbacks.
Tohiko noticed fullscreen wasn't working at all while Calvin proposed enumeration for tighter Emacs integration.
Calvin's suggestion enables using completing-read to pick the sharing service. This makes the integration feel more at home. As a bonus, it also enables sharing from fullscreen Emacs.
As an ivy user, you can see a vertical list of sharing services.
Here's the new snippet, now pushed to dwim-shell-commands.el:
(defun dwim-shell-commands--macos-sharing-services () "Return a list of sharing services." (let* ((source (format "import AppKit NSSharingService.sharingServices(forItems: [ %s ]).forEach { print(\"\\($0.prompt-title)\") }" (string-join (mapcar (lambda (file) (format "URL(fileURLWithPath: \"%s\")" file)) (dwim-shell-command--files)) ", "))) (services (split-string (string-trim (shell-command-to-string (format "echo '%s' | swift -" source))) "\n"))) (when (seq-empty-p services) (error "No sharing services available")) services)) (defun dwim-shell-commands-macos-share () "Share selected files from macOS." (interactive) (let* ((services (dwim-shell-commands--macos-sharing-services)) (service-name (completing-read "Share via: " services)) (selection (seq-position services service-name #'string-equal))) (dwim-shell-command-on-marked-files "Share" (format "import AppKit _ = NSApplication.shared NSApp.setActivationPolicy(.regular) class MyWindow: NSWindow, NSSharingServiceDelegate { func sharingService( _ sharingService: NSSharingService, didShareItems items: [Any] ) { NSApplication.shared.terminate(nil) } func sharingService( _ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error ) { let error = error as NSError if error.domain == NSCocoaErrorDomain && error.code == NSUserCancelledError { NSApplication.shared.terminate(nil) } exit(1) } } let window = MyWindow( contentRect: NSRect(x: 0, y: 0, width: 0, height: 0), styleMask: [], backing: .buffered, defer: false) let services = NSSharingService.sharingServices(forItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)}) let service = services[%s] service.delegate = window service.perform(withItems: [\"<<*>>\"].map{URL(fileURLWithPath:$0)}) NSApp.run()" selection) :silent-success t :shell-pipe "swift -" :join-separator ", " :no-progress t :utils "swift")))
dwim-shell-command is available on melpa. What other uses can you find for it?
Emacs: Reveal in macOS Finder (DWIM style)
Just the other day, Graham Voysey filed an escaping bug against dwim-shell-command. Once he verified the the fix, he also posted two uses of dwim-shell-command-on-marked-files
. I've made some small tweaks, but here's the gist of it:
(defun dwim-shell-commands-feh-marked-files () "View all marked files with feh." (interactive) (dwim-shell-command-on-marked-files "View with feh" "feh --auto-zoom --scale-down '<<*>>'" :silent-success t :utils "feh")) (defun dwim-shell-commands-dragon-marked-files () "Share all marked files with dragon." (interactive) (dwim-shell-command-on-marked-files "View with dragon" "dragon --on-top '<<*>>'" :silent-success t :utils "dragon"))
I love seeing what others get up to by using dwim-shell-command
. Are there new magical command-line utilities out there I don't know about? In this instance, I got to learn about feh and dragon.
feh is a no-frills image viewer for console users while dragon is a simple drag-and-drop source/sink for X or Wayland. Both utilities are great uses of dwim-shell-command
, enabling a seamless transition from Emacs to the outside world. These days I'm rarely on a linux box, so I was keen to ensure macOS had these cases covered.
Preview is a solid macOS equivalent to feh. Preview
is already macOS's default image viewer. A simple open '<<f>>'
would do the job, but if we'd like to make this command more portable, we can accomodate as follows:
(defun dwim-shell-commands-open-externally () "Open file(s) externally." (interactive) (dwim-shell-command-on-marked-files "Open externally" (if (eq system-type 'darwin) "open '<<f>>'" "xdg-open '<<f>>'") :silent-success t :utils "open"))
Special mention goes to Bozhidar Batsov's crux which achieves similar functionality via crux-open-with
. crux provides a bunch of other useful functions. Some of my favourites being crux-duplicate-current-line-or-region
, crux-transpose-windows
, crux-delete-file-and-buffer
, and crux-rename-buffer-and-file
, but I digress.
Moving on to a dragon equivalent on macOS, I thought I had it covered via reveal-in-osx-finder or reveal-in-folder. Turns out, neither of these reveal multiple dired-selected files within Finder. At first, I thought this could be easily achieved by passing additional flags/params to macOS's open
command, but it doesn't seem to be the case. Having said that, this Stack Overflow post, has a solution in Objective-C, which is where things got a little more interesting. You see, back in July I added multi-language support to dwim-shell-command and while it highlighted language flexibility, I hadn't yet taken advantage of this feature myself. That is, until today.
The Objective-C snippet from the Stack Overflow post can be written as a Swift one-liner. Ok I lie. It's actually two lines, counting the import, but you can see that this multi-language Emacs transition/integration is pretty easy to add.
(defun dwim-shell-commands-macos-reveal-in-finder () "Reveal selected files in macOS Finder." (interactive) (dwim-shell-command-on-marked-files "Reveal in Finder" "import AppKit NSWorkspace.shared.activateFileViewerSelecting([\"<<*>>\"].map{URL(fileURLWithPath:$0)})" :join-separator ", " :silent-success t :shell-pipe "swift -"))
<<*>>
is the centrepiece of the snippet above. It gets instantiated with a list of files joined using the ", "
separator.
NSWorkspace.shared.activateFileViewerSelecting(["/path/to/file1", "/path/to/file2"].map { URL(fileURLWithPath: $0) })
The proof of the pudding is of course in the eating, so ummm let's show it in action:
I should mention the webp animation above was also created using my trusty dwim-shell-commands-video-to-webp
also backed by dwim-shell-command.
(defun dwim-shell-commands-video-to-webp () "Convert all marked videos to webp(s)." (interactive) (dwim-shell-command-on-marked-files "Convert to webp" "ffmpeg -i '<<f>>' -vcodec libwebp -filter:v fps=fps=10 -compression_level 3 -lossless 1 -loop 0 -preset default -an -vsync 0 '<<fne>>'.webp" :utils "ffmpeg"))
dwim-shell-command is available on melpa. What other uses can you find for it?
UPDATE: Most DWIM shell commands I use are available as part of dwim-shell-commands.el. See dwim-shell-command
's install command line utilities.
Plain Org v1.5 released
If you haven't heard of Plain Org, it gives you access to org files on iOS while away from your beloved Emacs.
Hadn't had time to post, but v1.5 has been available on the App Store for a couple of weeks now. The update is mostly a bugfix release, primarily addressing inline editing issues that appeared on iOS 16, along with a few other changes:
- Render form feeds at end of headings at all times.
- Fixes new files not recognized by org-roam.
- Fixes share sheet saving from cold launch.
- Fixes inline editing on iOS 16.
I love org markup, but we (iPhone + org users) are a fairly niche bunch. If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it. Or just support via the App Store :)
dwim-shell-command usages: pdftotext and scp
dwim-shell-command is a little Emacs package I wrote to enable crafting more reusable shell commands. I intended to use it as an async-shell-command alternative (and I do these days). The more surprising win was bringing lots of command-line utilities (sometimes with complicated invocations) and making them quickly accessible. I no longer need to remember their respective parameters, order, flags, etc.
I've migrated most one-liners and scripts I had to dwim-shell-command equivalents. They are available at dwim-shell-commands.el. Having said that, it's great to discover new usages from dwim-shell-command
users.
Take u/TiMueller's Reddit comment, showcasing pdftotext. Neat utility I was unaware of. It does as it says on the tin and converts a pdf to text. Can be easily saved to your accessible repertoire with:
(defun dwim-shell-commands-pdf-to-txt () "Convert pdf to txt." (interactive) (dwim-shell-command-on-marked-files "pdf to txt" "pdftotext -layout '<<f>>' '<<fne>>.txt'" :utils "pdftotext"))
tareefdev wanted a quick command to secure copy remote files to a local directory. Though this use-case is already covered by Tramp, I suspect a DWIM command would make it a little more convenient (async by default). However, Tramp paths aren't usable from the shell unless we massage them a little. We can use dwim-shell-command-on-marked-files
's :post-process-template
to drop the "/ssh:" prefix.
(defun dwim-shell-commands-copy-remote-to-downloads () (interactive) (dwim-shell-command-on-marked-files "Copy remote to local Downloads" "scp '<<f>>' ~/Downloads/" :utils "scp" :post-process-template (lambda (script file) ;; Tramp file path start with "/ssh:". Drop it. (string-replace file (string-remove-prefix "/ssh:" file) script))))
dwim-shell-command is available on MELPA (531 downloads as of 2022-10-01).
$ rm Important.txt (uh oh!)
Setting Emacs up to use your system trash can potentially save your bacon if you mistakenly delete a file, say from dired.
Unsurprisingly, the trash safety net also extends to other Emacs areas. For example, discarding files from Magit (via magit-discard
) becomes a recoverable operation. As an eshell user, the trash can also help you recover from rm
blunders.
You can enable macOS system trash in Emacs by setting trash-directory
along with defining system-move-file-to-trash
:
(setq trash-directory "~/.Trash") ;; See `trash-directory' as it requires defining `system-move-file-to-trash'. (defun system-move-file-to-trash (file) "Use \"trash\" to move FILE to the system trash." (cl-assert (executable-find "trash") nil "'trash' must be installed. Needs \"brew install trash\"") (call-process "trash" nil 0 nil "-F" file))
Cycling through window layouts (revisited)
Last year, I wrote a little script to cycle through window layouts via Hammerspoon. The cycling set I chose didn't stick, so here's another go.
function reframeFocusedWindow() local win = hs.window.focusedWindow() local maximizedFrame = win:screen():frame() maximizedFrame.x = maximizedFrame.x + 15 maximizedFrame.y = maximizedFrame.y + 15 maximizedFrame.w = maximizedFrame.w - 30 maximizedFrame.h = maximizedFrame.h - 30 local leftFrame = win:screen():frame() leftFrame.x = leftFrame.x + 15 leftFrame.y = leftFrame.y + 15 leftFrame.w = leftFrame.w / 2 - 15 leftFrame.h = leftFrame.h - 30 local rightFrame = win:screen():frame() rightFrame.x = rightFrame.w / 2 rightFrame.y = rightFrame.y + 15 rightFrame.w = rightFrame.w / 2 - 15 rightFrame.h = rightFrame.h - 30 if win:frame() == maximizedFrame then win:setFrame(leftFrame) return end if win:frame() == leftFrame then win:setFrame(rightFrame) return end win:setFrame(maximizedFrame) end hs.hotkey.bind({"alt"}, "F", reframeFocusedWindow)
Looping through layouts is done with a global key-binding of option f
or, if familiar with a macOS keyboard, ⌥ f
.
For those unfamiliar with Hammerspoon… If you're a tinkerer and a macOS user, you'd love Hammerspoon. Like elisp gluing all things Emacs, Hammerspoon uses Lua to glue all things macOS. For example, here's a stint at writing a narrowing utility for macOS using chooser.
dwim-shell-command with template prompts
Somewhat recently, I wanted to quickly create an empty/transparent png file. ImageMagick's convert has you covered here. Say you want a transparent 200x400 image, you can get it with:
convert -verbose -size 200x400 xc:none empty200x400.png
Great, I now know the one-liner for it. But because I'm in the mood of saving these as seamless command-line utils, I figured I should save the dwim-shell-command equivalent.
I wanted configurable image dimensions, so I used read-number together with format to create the templated command and fed it to dwim-shell-command-on-marked-files
. Job done:
(defun dwim-shell-commands-make-transparent-png () "Create a transparent png." (interactive) (let ((width (read-number "Width: " 200)) (height (read-number "Height: " 200))) (dwim-shell-command-on-marked-files "Create transparent png" (format "convert -verbose -size %dx%d xc:none '<<empty%dx%d.png(u)>>'" width height width height) :utils "convert")))
The resulting dwim-shell-commands-make-transparent-png
is fairly simple, but dwim-shell-command aims to remove friction so you're more inclined to save reusable commands. In this case, we can shift querying and formatting into the template.
<<Width:200>>
can be interpreted as "ask the user for a value using the suggested prompt and default value."
With template queries in mind, dwim-shell-commands-make-transparent-png
can be further reduced to essentially the interactive command boilerplate and the template itself:
(defun dwim-shell-commands-make-transparent-png () "Create a transparent png." (interactive) (dwim-shell-command-on-marked-files "Create transparent png" "convert -verbose -size <<Width:200>>x<<Height:200>> xc:none '<<empty<<Width:200>>x<<Height:200>>.png(u)>>'" :utils "convert"))
Note: Any repeated queries (same prompt and default) are treated as equal. That is, ask the user once and replace everywhere. If you'd like to request separate values, change either prompt or the default value.
Seamless command-line utils
Just the other day, I received a restaurant menu split into a handful of image files. I wanted to forward the menu to others but figured I should probably send it as a single file.
ImageMagick's convert command-line utility works great for this purpose. Feed it some images and it creates a pdf for you:
convert image1.png image2.png image3.png combined.pdf
Using convert
for this purpose was pretty straightforward. I'm sure I'll use it again in a similar context, but what if I can make future usage more seamless? In the past, I would just make a note of usage and revisit when needed. Though this works well enough, it often requires some amount of manual work (looking things up, tweaking command, etc) if you happen to forget the command syntax.
I wanted common one-liners (or longer shell scripts) to be easily reusable and accessible from Emacs. Turns out, the dwim-shell-command experiment is working fairly well for this purpose. In addition to providing template expansion, it generally tries to do what I mean (focus when needed, reveal new files, rename buffers, etc).
Here's how I saved the convert
command instance for future usage:
(defun dwim-shell-commands-join-as-pdf () "Join all marked images as a single pdf." (interactive) (dwim-shell-command-on-marked-files "Join as pdf" "convert -verbose '<<*>>' '<<joined.pdf(u)>>'" :utils "convert"))
From now on, any time I'd like to join multiple files into a pdf, I can now select them all and invoke dwim-shell-commands-join-as-pdf
.
In the saved command, '<<*>>'
expands to either dired selected files or whatever file happens to be open in the current buffer. The buffer file isn't of much help for joining multiple items, but can be handy for other instances (say I want to convert current image to jpeg).
Moving on to '<<joined.pdf(u)>>'
, we could have just written as joined.pdf
, but wrapping it ensures the resulting file name is unique. That is, if joined.pdf
already exists, write joined(1).pdf
instead.
These kinds of command-line integrations are working well for me. Take the webp animation above, it was created by invoking dwim-shell-commands-video-to-webp
on a .mov
file. Easy peasy. While I can easily memorize the convert
command for the pdf instance, I'm hopeless in the webp scenario:
ffmpeg -i '<<f>>' -vcodec libwebp -filter:v fps=fps=10 -compression_level 3 -lossless 1 -loop 0 -preset default -an -vsync 0 '<<fne>>'.webp
While searching through command line history helps to quickly re-spin previous commands, it requires remembering the actual utility used for any particular action. On the other hand, wrapping with Emacs functions enables me to remember the action itself, using more memorable names. Also, fuzzy searching works a treat.
It's been roughly a month since I started playing around with this idea of wrapping command-line utilities more seamlessly. Since then, I've brought in a bunch of use-cases that are now quickly accessible (all in dwim-shell-commands.el):
- dwim-shell-commands-audio-to-mp3
- dwim-shell-commands-clipboard-to-qr
- dwim-shell-commands-copy-to-desktop
- dwim-shell-commands-copy-to-downloads
- dwim-shell-commands-docx-to-pdf
- dwim-shell-commands-download-clipboard-stream-url
- dwim-shell-commands-drop-video-audio
- dwim-shell-commands-epub-to-org
- dwim-shell-commands-external-ip
- dwim-shell-commands-files-combined-size
- dwim-shell-commands-git-clone-clipboard-url
- dwim-shell-commands-git-clone-clipboard-url-to-downloads
- dwim-shell-commands-http-serve-dir
- dwim-shell-commands-image-browse-location
- dwim-shell-commands-image-exif-metadata
- dwim-shell-commands-image-reverse-geocode-location
- dwim-shell-commands-image-to-grayscale
- dwim-shell-commands-image-to-icns
- dwim-shell-commands-image-to-jpg
- dwim-shell-commands-image-to-png
- dwim-shell-commands-install-iphone-device-ipa
- dwim-shell-commands-join-as-pdf
- dwim-shell-commands-kill-gpg-agent
- dwim-shell-commands-kill-process
- dwim-shell-commands-macos-bin-plist-to-xml
- dwim-shell-commands-macos-caffeinate
- dwim-shell-commands-macos-hardware-overview
- dwim-shell-commands-macos-open-with
- dwim-shell-commands-macos-reveal-in-finder
- dwim-shell-commands-macos-set-default-app
- dwim-shell-commands-macos-share
- dwim-shell-commands-macos-toggle-dark-mode
- dwim-shell-commands-macos-toggle-display-rotation
- dwim-shell-commands-make-transparent-png
- dwim-shell-commands-move-to-desktop
- dwim-shell-commands-move-to-downloads
- dwim-shell-commands-open-clipboard-url
- dwim-shell-commands-open-externally
- dwim-shell-commands-pdf-password-protect
- dwim-shell-commands-pdf-to-txt
- dwim-shell-commands-ping-google
- dwim-shell-commands-rename-all
- dwim-shell-commands-reorient-image
- dwim-shell-commands-resize-gif
- dwim-shell-commands-resize-image
- dwim-shell-commands-resize-video
- dwim-shell-commands-speed-up-gif
- dwim-shell-commands-speed-up-video
- dwim-shell-commands-stream-clipboard-url
- dwim-shell-commands-svg-to-png
- dwim-shell-commands-unzip
- dwim-shell-commands-url-browse
- dwim-shell-commands-video-to-gif
- dwim-shell-commands-video-to-optimized-gif
- dwim-shell-commands-video-to-webp
What other use-cases would you consider? dwim-shell-command
is available on melpa.
Update
2022-11-14 dwim-shell-commands.el list updated.
Emacs freebie: macOS emoji picker
I recently ran a little experiment to bring macOS's long-press-accents-like behavior to Emacs. What I forgot to mention is that macOS's character viewer just works from our beloved editor.
If you have a newer MacBook model, you can press the 🌐 key to summon the emoji picker (character viewer). You may need to set this key binding from macOS keyboard preferences.
I'm happy to take this Emacs freebie, kthxbye.
Edits:
- Like other macOS apps, this dialog can be invoked via control-command-space (thanks mtndewforbreakfast). Note: you'd lose this ability if you
(setq mac-command-modifier 'meta)
in your config. - The 🌐 key is a feature on newer MacBook hardware and likely needs configuration (thanks Fabbi-).
dwim-shell-command video streams
I continue hunting for use-cases I can migrate to dwim-shell-command… After adding clipboard support (via ) I found one more.
- Copy URL from browser.
- Invoke
dwim-shell-commands-mpv-stream-clipboard-url
. - Enjoy picture in picture from Emacs ;)
What's the secret sauce? Very little. Invoke the awesome mpv with a wrapping function using dwim-shell-command-on-marked-files
.
(defun dwim-shell-commands-mpv-stream-clipboard-url () "Stream clipboard URL using mpv." (interactive) (dwim-shell-command-on-marked-files "Streaming" "mpv --geometry=30%x30%+100%+0% \"<<cb>>\"" :utils "mpv" :no-progress t :error-autofocus t :silent-success t))
The typical progress bar kinda got in the way, so I added a new option :no-progress
to dwim-shell-command-on-marked-files
, so it can be used for cases like this one.
An accentuated Emacs experiment (à la macOS)
macOS has a wonderful input mechanism where you press and hold a key on your keyboard to display the accent menu. It's easy to internalize: long press "a" if you want to input "á".
On Emacs, C-x 8 ' a would be the equivalent, but it just didn't stick for me. Fortunately, there's an alternative, using dead keys. Mickey Petersen gives a wonderful introduction. Having said all this, I still longed for macOS's input mechanism.
Thanks to Christian Tietze's post, I discovered the accent package. While it doesn't handle press-and-hold, it does the heavy lifting of offering a menu with character options. If I could just bring that press-and-hold…
My initial attempt was to use key chords (via use-package):
(use-package accent :ensure t :chords (("aa" . ar/spanish-accent-menu) ("ee" . ar/spanish-accent-menu) ("ii" . ar/spanish-accent-menu) ("oo" . ar/spanish-accent-menu) ("uu" . ar/spanish-accent-menu) ("AA" . ar/spanish-accent-menu) ("EE" . ar/spanish-accent-menu) ("II" . ar/spanish-accent-menu) ("OO" . ar/spanish-accent-menu) ("UU" . ar/spanish-accent-menu) ("nn" . ar/spanish-accent-menu) ("NN" . ar/spanish-accent-menu) ("??" . ar/spanish-accent-menu) ("!!" . ar/spanish-accent-menu)) :config (defun ar/spanish-accent-menu () (interactive) (let ((accent-diacritics '((a (á)) (e (é)) (i (í)) (o (ó)) (u (ú ü)) (A (Á)) (E (É)) (I (Í)) (O (Ó)) (U (Ú Ü)) (n (ñ)) (N (Ñ)) (\? (¿)) (! (¡))))) (ignore-error quit (accent-menu)))))
While it kinda works, "nn" quickly got in the way of my n/p magit navigation. Perhaps key chords are still an option for someone who doesn't need the "nn" chord, but being a Spanish speaker, I need that "ñ" from long "n" presses!
I'm now trying a little experiment using an after-change-functions
hook to monitor text input and present the accent menu. I'm sure there's a better way (anyone with ideas?). For now, it gives me something akin to press-and-hold.
I'm wrapping the hook with a minor mode to easily enable/disable whenever needed. I'm also overriding accent-diacritics
to only include the characters I typically need.
(use-package accent :ensure t :hook ((text-mode . accent-menu-mode) (org-mode . accent-menu-mode) (message-mode . accent-menu-mode)) :config (setq accent-diacritics '((a (á)) (e (é)) (i (í)) (o (ó)) (u (ú ü)) (A (Á)) (E (É)) (I (Í)) (O (Ó)) (U (Ú Ü)) (n (ñ)) (N (Ñ)) (\? (¿)) (! (¡)))) (defvar accent-menu-monitor--last-edit-time nil) (define-minor-mode accent-menu-mode "Toggle `accent-menu' if repeated keys are detected." :lighter " accent-menu mode" (if accent-menu-mode (progn (remove-hook 'after-change-functions #'accent-menu-monitor--text-change t) (add-hook 'after-change-functions #'accent-menu-monitor--text-change 0 t)) (remove-hook 'after-change-functions #'accent-menu-monitor--text-change t))) (defun accent-menu-monitor--text-change (beginning end length) "Monitors text change BEGINNING, END, and LENGTH." (let ((last-edit-time accent-menu-monitor--last-edit-time) (edit-time (float-time))) (when (and (> end beginning) (eq length 0) last-edit-time (not undo-in-progress) ;; 0.27 seems to work for my macOS keyboard settings. ;; Key Repeat: Fast | Delay Until Repeat: Short. (< (- edit-time last-edit-time) 0.27) (float-time (time-subtract (current-time) edit-time)) (accent-menu-monitor--buffer-char-string (1- beginning)) (seq-contains-p (mapcar (lambda (item) (symbol-name (car item))) accent-diacritics) (accent-menu-monitor--buffer-char-string beginning)) (string-equal (accent-menu-monitor--buffer-char-string (1- beginning)) (accent-menu-monitor--buffer-char-string beginning))) (delete-backward-char 1) (ignore-error quit (accent-menu))) (setq accent-menu-monitor--last-edit-time edit-time))) (defun accent-menu-monitor--buffer-char-string (at) (when (and (>= at (point-min)) (< at (point-max))) (buffer-substring-no-properties at (+ at 1)))))
As a bonus, it ocurred to me that I could use the same press-and-hold to handle question marks in Spanish (from my UK keyboard).
dwim-shell-command improvements
Added a few improvements to dwim-shell-command.
Dired region
In DWIM style, if you happen to have a dired region selected, use region files instead. There's no need to explicitly mark them.
Clipboard (kill-ring) replacement
Use <<cb>>
to substitute with clipboard content. This is handy for cloning git repos, using a URL copied from your browser.
git clone <<cb>>
This illustrates <<cb>>
usage, but you may want to use dwim-shell-commands-git-clone-clipboard-url
from dwim-shell-commands.el instead. It does the same thing internally, but makes the command more accessible.
(defun dwim-shell-commands-git-clone-clipboard-url () "Clone git URL in clipboard to `default-directory'." (interactive) (dwim-shell-command-on-marked-files (format "Clone %s" (file-name-base (current-kill 0))) "git clone <<cb>>" :utils "git"))
Counter replacement
Use <<n>>
to substitute with a counter. You can also use <<3n>>
to start the counter at 3.
Handy if you'd like to consistently rename or copy files.
mv '<<f>>' 'image(<<n>>).png'
Can also use an alphabetic counter with <<an>>
. Like the numeric version, can use any letter to start the counter with.
mv '<<f>>' 'image(<<an>>).png'
Prefix counter
Use a prefix command argument on dwim-shell-commands
to repeat the command a number of times. Combined with a counter, you can make multiple copies of a single file.
Optional error prompt
Set dwim-shell-command-prompt-on-error
to nil to skip error prompts. Focus process buffers automatically instead.
Configurable prompt
By default, dwim-shell-command
shows all supported placeholders. You can change that prompt to something shorter using dwim-shell-command-prompt
.
⚠️ Use with care ⚠️
The changes are pretty fresh. Please use with caution (specially the counter support).
dwim-shell-command on Melpa
Figure 3: <<cb>>
gets replaced by a clipboard (kill ring) URL
My pull request to add dwim-shell-command to melpa has been merged. Soon, you'll be able to install directly from Milkypostman’s Emacs Lisp Package Archive.
dwim-shell-command
is another way to invoke shell commands from our beloved editor. Why a different way? It does lots of little things for you, removing friction you didn't realise you had. You can check out the README, but you'll appreciate it much more once you try it out.
In addition, it's enabled me to bring lots of command-line tools into my Emacs config and make them highly accessible. You can see my usages over at dwim-shell-command-commands.el.
What kind of command-line tools? ffmpeg, convert, gifsycle, atool, qdpf, plutil, qrencode, du, sips, iconutil, and git (so far anyway). Below is a simple example, but would love to hear how you get to use it.
(defun dwim-shell-command-audio-to-mp3 () "Convert all marked audio to mp3(s)." (interactive) (dwim-shell-command-on-marked-files "Convert to mp3" "ffmpeg -stats -n -i '<<f>>' -acodec libmp3lame '<<fne>>.mp3'" :utils "ffmpeg"))
I've written about dwim-shell-command
before:
- Emacs: DWIM shell command (multi-language)
- png to icns (Emacs DWIM style)
- Emacs: Password-protect current pdf (revisited)
- Emacs DWIM shell-command
Irreal's also covered it:
A lifehack for your shell
I'm a fan of the unzip command line utility that ships with macOS. I give it a .zip file and it unzips it for me. No flags or arguments to remember (for my typical usages anyway). Most importantly, I've fully internalized the unzip
command into muscle memory, probably because of its perfect mnemonic.
But then there's .tar, .tar.gz, .tar.xz, .rar, and a whole world of compression archives, often requiring different tools, flags, etc. and I need to remember those too.
Can't remember where I got this "life hack" from, but it suggests something along the lines of…
Once you find a lost item at home, place it in the first spot you looked.
Great, I'll find things quickly. Win.
Now, I still remember a couple of unarchiving commands from memory (looking at you tar xvzf
), but I've noticed the first word that pops into mind when extracting is always unzip
.
There's the great atool wrapper out there to extract all kinds of archives (would love to hear of others), but unlucky for me, its name never comes to mind as quickly as unzip
does.
With "life hack" in mind, let's just create an unzip
eshell alias to atool
. Next time I need to unarchive anything, the first word that comes to mind (unzip!) will quickly get me on my way…
alias unzip 'atool --extract --explain $1'
Or if you prefer to add to your Emacs config:
(eshell/alias "unzip" "atool --extract --explain $1")
While I'm fan of Emacs eshell, it's not everyone's cup of tea. Lucky for us all, aliases are a popular feature across shells. Happy unzipping!
Bonus
Since I'm a keen on using "unzip" mnemonic everywhere in Emacs (not just my shell), I now have a DWIM shell-command for it:
(defun dwim-shell-command-unzip () "Unzip all marked archives (of any kind) using `atool'." (interactive) (dwim-shell-command-on-marked-files "Unzip" "atool --extract --explain '<<f>>'" :utils "atool"))
UPDATE:
Aliases missing on remote machines
Concerns about aliases not available on remote machines. Valid. Certainly brings challenges if you can't modify the environment on the remote machine. The severity would depend on how frequently you have to do this. Fortunately for me, it's infrequent.
Additionally, if accessing remote machine via eshell, this is a non-issue. You get to transparently bring most of your environment with you anyway.
Unzip keyword is overloaded
The alias is overloading the unzip command. I know. It's a little naughty. Going with it for now. I used to use "extract" (also in comments), which I still like but somehow "unzip" still wins my memory race. There's also "x" (nice option), which seems to originate from prezto. I could consider unzipp, unzip1, or some other variation.
Not sure how I missed this, but there's also an existing alias for atool: aunpack. Could be a great alternative.
Pause before extracting archives
Valid point. In my case, the pause typically happens before I invoke the alias.
Littering
If the archive didn't have a root dir, it can litter your current directory. Indeed a pain to clean up. For this, we can atool's --subdir
param to always create subdirectory when extracting.
Alias to retrain
Neat trick: alias unzip = “echo ‘use atool’”
to help retrain yourself. Reminds me of Emacs guru-mode.
Emacs zones to lift you up
As I prune my Emacs config off, I came across a forgotten bit of elisp I wrote about 6 years ago. While it's not going to power up your Emacs fu, it may lift your spirits, or maybe just aid discovery of new words.
You see, I had forgotten about zone.el altogether: a fabulous package to tickle your heart. You can think of it as screensaver built into Emacs.
If the built-in zones don't do it for ya, check out the few on melpa (nyan, sl, and rainbow).
So, my nostalgic bit of elisp dates Jun 17 2016
: a basic but functional zone (zone-words), displaying words from WordNet. Surely the package can use plenty of improvements (here's one), but hey this is Emacs and pretty much all existing code will run, no matter how old. In Emacs time, 2016 is practically yesterday!
Emacs: DWIM shell command (multi-language)
UPDATE: dwim-shell-command is now available on melpa.
I keep on goofying around with dwim-shell-command and it's sibling dwim-shell-command-on-marked-files
from dwim-shell-command.el.
In addition to defaulting to zsh, dwim-shell-command-on-marked-files
now support other shells and languages. This comes in handy if you have snippets in different languages and would like to easily invoke them from Emacs. Multi-language support enables "using the best tool for the job" kinda thing. Or maybe you just happen to know how to solve a particular problem in a specific language.
Let's assume you have an existing Python snippet to convert files from csv to json. With dwim-shell-command-on-marked-files
, you can invoke the Python snippet to operate on either dired or buffer files.
(defun dwim-shell-command-csv-to-json-via-python () "Convert csv file to json (via Python)." (interactive) (dwim-shell-command-on-marked-files "Convert csv file to json (via Python)." " import csv import json text = json.dumps({ \"values\": list(csv.reader(open('<<f>>')))}) fpath = '<<fne>>.json' with open(fpath , 'w') as f: f.write(text)" :shell-util "python" :shell-args "-c"))
Or, maybe you prefer Swift and already had a snippet for the same thing?
(defun dwim-shell-command-csv-to-json-via-swift () "Convert csv file to json (via Swift)." (interactive) (dwim-shell-command-on-marked-files "Convert csv file to json (via Swift)." " import Foundation import TabularData let filePath = \"<<f>>\" print(\"reading \\(filePath)\") let content = try String(contentsOfFile: filePath).trimmingCharacters(in: .whitespacesAndNewlines) let parsedCSV = content.components(separatedBy: CSVWritingOptions().newline).map{ $0.components(separatedBy: \",\") } let jsonEncoder = JSONEncoder() let jsonData = try jsonEncoder.encode([\"value\": parsedCSV]) let json = String(data: jsonData, encoding: String.Encoding.utf8) let outURL = URL(fileURLWithPath:\"<<fne>>.json\") try json!.write(to: outURL, atomically: true, encoding: String.Encoding.utf8) print(\"wrote \\(outURL)\")" :shell-pipe "swift -"))
You can surely solve the same problem in elisp, but hey it's nice to have options and flexibility.
png to icns (Emacs DWIM style)
UPDATE: dwim-shell-command is now available on melpa.
Since writing a DWIM version of the shell-command, I've been having a little fun revisiting command line utilities I sometimes invoke from my beloved editor. In this instance, converting a png file to an icns icon. What's more interesting about this case is that it's not just a one-liner, but a short script in itself. Either way, it's just as easy to invoke from Emacs using dwim-shell-command--on-marked-files
.
(defun dwim-shell-command-convert-image-to-icns () "Convert png to icns icon." (interactive) (dwim-shell-command-on-marked-files "Convert png to icns icon" " # Based on http://stackoverflow.com/questions/12306223/how-to-manually-create-icns-files-using-iconutil # Note: png must be 1024x1024 mkdir <<fne>>.iconset sips -z 16 16 '<<f>>' --out '<<fne>>.iconset/icon_16x16.png' sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_16x16@2x.png' sips -z 32 32 '<<f>>' --out '<<fne>>.iconset/icon_32x32.png' sips -z 64 64 '<<f>>' --out '<<fne>>.iconset/icon_32x32@2x.png' sips -z 128 128 '<<f>>' --out '<<fne>>.iconset/icon_128x128.png' sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_128x128@2x.png' sips -z 256 256 '<<f>>' --out '<<fne>>.iconset/icon_256x256@2x.png' sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_512x512.png' sips -z 512 512 '<<f>>' --out '<<fne>>.iconset/icon_256x256@2x.png' sips -z 1024 1024 '<<f>>' --out '<<fne>>.iconset/icon_512x512@2x.png' iconutil -c icns '<<fne>>.iconset'" :utils '("sips" "iconutil") :extensions "png"))
Emacs: Password-protect current pdf (revisited)
UPDATE: dwim-shell-command is now available on melpa.
With a recent look at writing DWIM shell commands, I've been revisiting my custom Emacs functions invoking command line utilities.
Take this post, for example, where I invoke qpdf via a elisp. Using the new dwim-shell-command--on-marked-files
in dwim-shell-command.el, the code is stripped down to a bare minimum:
(defun dwim-shell-commands-pdf-password-protect () "Password protect pdf." (interactive) (dwim-shell-command-on-marked-files "Password protect pdf" (format "qpdf --verbose --encrypt '%s' '%s' 256 -- '<<f>>' '<<fne>>_enc.<<e>>'" (read-passwd "user-password: ") (read-passwd "owner-password: ")) :utils "qpdf" :extensions "pdf"))
Compare the above dwim-shell-command--on-marked-files
usage to my previous implementation:
(defun pdf-password-protect () "Password protect current pdf in buffer or `dired' file." (interactive) (unless (executable-find "qpdf") (user-error "qpdf not installed")) (unless (equal "pdf" (or (when (buffer-file-name) (downcase (file-name-extension (buffer-file-name)))) (when (dired-get-filename nil t) (downcase (file-name-extension (dired-get-filename nil t)))))) (user-error "no pdf to act on")) (let* ((user-password (read-passwd "user-password: ")) (owner-password (read-passwd "owner-password: ")) (input (or (buffer-file-name) (dired-get-filename nil t))) (output (concat (file-name-sans-extension input) "_enc.pdf"))) (message (string-trim (shell-command-to-string (format "qpdf --verbose --encrypt '%s' '%s' 256 -- '%s' '%s'" user-password owner-password input output))))))
This really changes things for me. I'll be more inclined to add more of these tiny integrations to lots of great command line utilities. Take this recent Hacker News post on ocrmypdf as an example. Their cookbook has lots of examples that can be easily used via dwim-shell-command--on-marked-files
.
What command line utils would you use?
Emacs DWIM shell-command
UPDATE: dwim-shell-command is now available on melpa.
I've talked about DWIM before, where I bend Emacs to help me do what I mean. Emacs is also great for wrapping command-line one-liners with elisp, so I can quickly invoke commands without thinking too much about flags, arguments, etc.
I keep thinking the shell-command is ripe for plenty of enhancements using our DWIM fairydust.
Do what I mean how?
Smart template instantiation
I've drawn inspiration from dired-do-shell-command, which substitutes special characters like * and ? with marked files. I'm also drawing inspiration from org babel's noweb syntax to substitute <<f>>
(file path), <<fne>>
(file path without extension), and <<e>>
(extension). My initial preference was to use something like $f
, $fne
, and $e
, but felt they clashed with shell variables.
Operate on dired marked files
This is DWIM, so if we're visiting a dired buffer, the shell command should operate on all the marked files.
Operate on current buffer file
Similarly, if visiting a buffer with an associated file, operate on that file instead.
Automatically take me to created files
Did the command create a new file in the current directory? Take me to it, right away.
Show me output on error
I'm not usually interested in the command output when generating new files, unless there was an error of course. Offer to show it.
Show me output if no new files
Not all commands generate new files, so automatically show me the output for these instances.
Make it easy to create utilities
ffmpeg is awesome, but man I can never remember all the flags and arguments. I may as well wrap commands like these in a convenient elisp function and invoke via execute-extended-command, or my favorite counsel-M-x (with completion), bound to the vital M-x
.
All those gifs you see in this post were created with dwim-shell-command-convert-to-gif
, powered by the same elisp magic.
(defun dwim-shell-command-convert-to-gif () "Convert all marked videos to optimized gif(s)." (interactive) (dwim-shell-command--on-marked-files "Convert to gif" "ffmpeg -loglevel quiet -stats -y -i <<f>> -pix_fmt rgb24 -r 15 <<fne>>.gif" :utils "ffmpeg"))
This makes wrapping one-liners a breeze, so let's do some more…
(defun dwim-shell-command-convert-audio-to-mp3 () "Convert all marked audio to mp3(s)." (interactive) (dwim-shell-command-on-marked-files "Convert to mp3" "ffmpeg -stats -n -i '<<f>>' -acodec libmp3lame '<<fne>>.mp3'" :utils "ffmpeg")) (defun dwim-shell-command-convert-image-to-jpg () "Convert all marked images to jpg(s)." (interactive) (dwim-shell-command-on-marked-files "Convert to jpg" "convert -verbose '<<f>>' '<<fne>>.jpg'" :utils "convert")) (defun dwim-shell-command-drop-video-audio () "Drop audio from all marked videos." (interactive) (dwim-shell-command-on-marked-files "Drop audio" "ffmpeg -i '<<f>>' -c copy -an '<<fne>>_no_audio.<<e>>'" :utils "ffmpeg"))
Make it spin ;)
Ok, not quite, but use Emacs's progress-reporter just for kicks.
Use it everywhere
dwim-shell-command
covers my needs (so far anyway), so I'm binding it to existing bindings.
(use-package dwim-shell-command :bind ("M-!" . dwim-shell-command)) (use-package dired :bind (:map dired-mode-map ([remap dired-do-async-shell-command] . dwim-shell-command) ([remap dired-do-shell-command] . dwim-shell-command) ([remap dired-smart-shell-command] . dwim-shell-command)))
Bring those command line utilities
On the whole, this really changes things for me. I'll be more inclined to bring command line utilities to seamless Emacs usage. Take this recent Hacker News post on ocrmypdf as an example. Their cookbook has lots of examples that can be easily used via dwim-shell-command--on-marked-files
. What command line utilities would you bring?
Where's the code?
UPDATE: dwim-shell-command is now available on melpa.
The code for dwim-shell-command.el is likely a bit rough still, but you can take a peak if interested. Keep in mind this is DWIM, tailored for what ✨I✨ mean. Some of the current behavior may not be your cup of tea, but this is Emacs. You can bend it to do what ✨you✨ mean. Happy Emacsing.
Emacs: Password-protect current pdf
UPDATE: Check out Password-protect current pdf (revisted) for a simpler version.
Every so often, I need to password-protect a pdf. On macOS, Preview has a simple solution, but I figured there must be a command line utility to make this happen. There are options, but qdf did the job just fine.
qpdf --verbose --encrypt USER-PASSWORD OWNER-PASSWORD KEY-LENGTH -- input.pdf output.pdf
So what does qpdf
have to do with Emacs? Command-line utilities are easy to invoke from Emacs via shell-command
(M-!), but I don't want to remember the command nor the parameters. I may as well add a function that does what I mean and password-protect either buffers or dired files.
(defun pdf-password-protect () "Password protect current pdf in buffer or `dired' file." (interactive) (unless (executable-find "qpdf") (user-error "qpdf not installed")) (unless (equal "pdf" (or (when (buffer-file-name) (downcase (file-name-extension (buffer-file-name)))) (when (dired-get-filename nil t) (downcase (file-name-extension (dired-get-filename nil t)))))) (user-error "no pdf to act on")) (let* ((user-password (read-passwd "user-password: ")) (owner-password (read-passwd "owner-password: ")) (input (or (buffer-file-name) (dired-get-filename nil t))) (output (concat (file-name-sans-extension input) "_enc.pdf"))) (message (string-trim (shell-command-to-string (format "qpdf --verbose --encrypt '%s' '%s' 256 -- '%s' '%s'" user-password owner-password input output))))))
Plain Org v1.4 released
Plain Org v1.4 is now available on the App Store.
I was on a long flight recently 🦘, so I gave list and checkbox editing a little love. There's a couple of other minor improvements included.
If you haven't heard of Plain Org, it gives you access to org files on iPhone while away from your beloved Emacs.
I love org markup, but we (iPhone + org users) are a fairly niche bunch. If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it.
On to v1.4 release notes…
Improved list/checkbox editing
Adding list or checkbox items is traditionally cumbersome via the iPhone's keyboard. This release adds new toolbar actions and smart return to simplify things.
Render form feed characters
Form feed characters are now rendered within expanded headings.
Note: There's a limitation. Form feed characters at the end of a heading aren't currently displayed.
Other
Increased all button tap areas in edit toolbar. This should hopefully improve interaction.
Plain Org v1.3 released
Plain Org v1.3 is now available on the App Store. The update receives a few features, bug fixes, and improvements.
If you haven't heard of Plain Org, it gives you access to org files on iPhone while away from your beloved Emacs.
I love org markup, but we (iPhone + org users) are a fairly niche bunch. If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it.
On to v1.3 release notes…
Toggle recurring tasks
You can now toggle recurring tasks with either catchup <2022-04-15 Fri ++1d>
, restart <2022-04-15 Fri .+1d>
, or cumulate <2022-04-15 Fri +1d>
repeaters.
Log state transitions
Fullscreen view
The navigation bar now hides on scroll. This can be enabled/disabled via View > Full Screen
menu.
The previous screenshot text comes from Org Mode - Organize Your Life In Plain Text, a magnificent org resource.
Deadline and scheduled date rendered
In the past, SCHEDULED
and DEADLINE
were rendered (but only one of them at a time). Now both are rendered alongside each other (deadline has an orange tint).
Roundtripping fidelity
Many roundtripping fidelity improvements included in 1.3. Shoutout to u/Oerm who reported unnecessary formatting changes in unmodified areas and helped test all fixes.
Other bug fixes improvements
- Disable raw text edit menu when file is not accessible.
- Minor improvements to inline editing layouts (vertical height and drawers).
- ABRT and HABIT now recognized as a popular keywords.
- Improve state transition alignment to match org mode behaviour.
- Fixes roundtripping state transition notes (leading to data loss).
- Log creation from share sheet.
- Increment DEADLINE and SCHEDULED, not just first found.
- Roundtrip more whitespace in untouched areas.
- Fixes org syntax inadvertently parsed within begin_src blocks (leading to data loss).
Plain Org v1.2.1 released
Plain Org v1.2.1 is now available on the App Store. The update receives minor features, bug fixes, and improvements.
If you haven't heard of Plain Org, it gives you access to org files on iPhone while away from your beloved Emacs.
I love org markup, but we (iPhone + org users) are a fairly niche userbase. If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it.
On to v1.2.1 release notes…
Render LOGBOOK
State transitions and LOGBOOK drawers are now recognized and rendered as such.
Either of the following snippets are rendered as togglable LOGBOOK drawers.
* TODO Feed the fish - State "DONE" from "TODO" [2022-03-11 Fri 12:23]
* TODO Feed the cat :LOGBOOK: - State "DONE" from "TODO" [2022-03-11 Fri 12:23] :END:
Add task to top/bottom
Up until now, tasks were always appended to the bottom of things. This didn't work so well if you like seeing recent items bubbling up to the top.
This version adds a new setting: Settings > Add new tasks to > Top/Bottom, giving you the choice.
Note: Top is the new default value, please change this setting if you'd like to keep the previous behaviour.
Checking for changes
Local file changes aren't always detected via state change notifications, so additional checks are now in place to offer reloading files.
Open inactive files
After adding new tasks via iOS's share sheet, if the item was added to a file other than the active one, offer to open that instead.
Other improvements
- Color keyword red/green depending on #+TODO: position.
- Round-trip planning order (SCHEDULED, CLOSED, DEADLINE).
- Improve tag alignment to match org mode behaviour (best effort, sorry).
- Improve vertical spacing prior to lists.
- Improve share sheet reliability.
- Fix opening local links from list items.
- Fix indent for list items without previous content.
- Fix race condition in adding TITLE and ID to new files.
- Fix incorrect keyword color selection in search toolbar.
- Fix menu inadvertently closing.
- Fix menu tapping for iPad.
Emacs DWIM: swiper vs isearch vs phi-search
I've talked about DWIM in the past, that wonderful Emacs ability to do what ✨I✨ mean.
Emacs being hyper-configurable, we can always teach it more things, so it can do exactly what we mean.
There are no shortages of buffer searching packages for Emacs. I'm a fan of Oleh Krehel's swiper, but before that, I often relied on the built-in isearch. Swiper is my default goto mechanism and have it bound to C-s
(replacing the built-in isearch-forward).
Swiper services most needs until I start combining with other tools. Take keyboard macros and multiple cursors. Both wonderful, but neither can rely on swiper to do their thing. Ok, swiper does, but in a different way.
Rather than binding C-s
to swiper, let's write a DWIM function that's aware of macros and multiple cursors. It must switch between swiper, isearch, and phi-search depending on what I want (search buffer, define macro, or search multiple cursors).
Let's also tweak swiper's behavior a little further and prepopulate its search term with the active region. Oh, and I also would like swiper to wrap around (see ivy-wrap). But only swiper, not other ivy utilities. I know, I'm picky, but that's the whole point of DWIM… so here's my function to search forward that does exactly what ✨I✨ mean:
(defun ar/swiper-isearch-dwim () (interactive) ;; Are we using multiple cursors? (cond ((and (boundp 'multiple-cursors-mode) multiple-cursors-mode (fboundp 'phi-search)) (call-interactively 'phi-search)) ;; Are we defining a macro? (defining-kbd-macro (call-interactively 'isearch-forward)) ;; Fall back to swiper. (t ;; Wrap around swiper results. (let ((ivy-wrap t)) ;; If region is active, prepopulate swiper's search term. (if (and transient-mark-mode mark-active (not (eq (mark) (point)))) (let ((region (buffer-substring-no-properties (mark) (point)))) (deactivate-mark) (swiper-isearch region)) (swiper-isearch))))))
The above snippet searches forward, but I'm feeling a little off-balance. Let's write an equivalent to search backwards. We can then bind it to C-r
, also overriding the built-in isearch-backward.
(defun ar/swiper-isearch-backward-dwim () (interactive) ;; Are we using multiple cursors? (cond ((and (boundp 'multiple-cursors-mode) multiple-cursors-mode (fboundp 'phi-search-backward)) (call-interactively 'phi-search-backward)) ;; Are we defining a macro? (defining-kbd-macro (call-interactively 'isearch-backward)) ;; Fall back to swiper. (t ;; Wrap around swiper results. (let ((ivy-wrap t)) ;; If region is active, prepopulate swiper's search term. (if (and transient-mark-mode mark-active (not (eq (mark) (point)))) (let ((region (buffer-substring-no-properties (mark) (point)))) (deactivate-mark) (swiper-isearch-backward region)) (swiper-isearch-backward))))))
These may be on the hacky side of things, but hey… they do the job. If there are better/supported ways of accomplishing a similar thing, I'd love to hear about it.
Grandma's vanilla pound cake
My grandmother Hilda used to bake this for us grandkids. I don't know the origin of the recipe, but my parents, aunts, and cousins, they all bake it too. I'm a big fan, but only get to eat it when visiting. Yesterday, I changed that. Finally baked it myself \o/
Ingredients
- 200g salted butter
- 2 cups (400 g) sugar
- 4 eggs
- 3 cups (375 g) plain flour
- 3 teaspoons baking powder
- 1 tablespoon (15 ml) vanilla extract
- 1 cup (250 ml) milk
- 2 tablespoons (30 ml) Málaga Virgen wine (port works too)
Prep
- Ensure all ingredients are at room temperature before you start.
- Preheat oven at 175C.
- Separate egg yolks and whites. Keep both.
- Consolidate liquids into a bowl (milk + wine + vanilla).
- Consolidate sifted powders into a bowl (flour + baking powder).
Meringue
- Beat egg whites into a snowy meringue. Set aside.
Mixer
- Beat butter in the mixer until creamy (important).
- Add sugar and mix thoroughly ensuring creamy consistency remains (important).
- Mix yolks in thoroughly one by one.
- Mix in the meringue.
- You're done with the mixer.
Hand mixing
- With a wooden spoon, alternate hand mixing the liquids and the powders. Start with liquids and end with powders.
Pour into mould
- Pour the mix into a non-stick baking mould.
Bake
- Bake in oven between 60 and 70 mins, but don’t be afraid to leave longer if needed. Mileage varies across ovens.
- Use a cake tester after 60 minutes to decide how much longer to bake for (if needed).
Emacs: viewing webp images
There's a recent reddit post asking how to view webp images in Emacs. I didn't know the answer, but it's something I had wanted for some time. This post was a nice reminder to go and check things out. Was happy to contribute an answer.
Turns out, it's very simple. Just set image-use-external-converter
and install relevant external tools.
(setq image-use-external-converter t)
I'm a use-package
user, so I prefer to set with:
(use-package image :custom ;; Enable converting external formats (ie. webp) to internal ones. (image-use-external-converter t))
So what are the external tools needed? C-h v
image-use-external-converter
gives us the info we need:
If non-nil, create-image will use external converters for exotic formats.
Emacs handles most of the common image formats (SVG, JPEG, PNG, GIF and some others) internally, but images that don't have native support in Emacs can still be displayed if an external conversion program (like ImageMagick "convert", GraphicsMagick "gm" or "ffmpeg") is installed.
This variable was added, or its default value changed, in Emacs 27.1.
I happen to be a macOS user, so I install ImageMagick with:
brew install imagemagick
Emacs: Fuzzy search Apple's online docs
When building software for the Apple ecosystem, Xcode is often the editor of choice. With Emacs being my personal preference, I rarely find other iOS devs with a similar mindset.
When I saw Mikael Konradsson's post describing his Emacs Swift development setup, I reached out to say hello. While exchanging tips and tricks, the topic of searching Apple's docs came up. It had been a while since I looked into this, so it was a great reminder to revisit the space.
Back in June 2020, I wrote a snippet to fuzzy search hackingwithswift.com, using Emacs's ivy completion framework. With a similar online API, we could also search Apple's docs. Turns out, there is and we can we can use it to search developer.apple.com from our beloved editor.
;;; counsel-apple-search.el -*- lexical-binding: t; -*- (defun ar/counsel-apple-search () "Ivy interface for dynamically querying apple.com docs." (interactive) (require 'request) (require 'json) (require 'url-http) (ivy-read "apple docs: " (lambda (input) (let* ((url (url-encode-url (format "https://developer.apple.com/search/search_data.php?q=%s" input))) (c1-width (round (* (- (window-width) 9) 0.3))) (c2-width (round (* (- (window-width) 9) 0.5))) (c3-width (- (window-width) 9 c1-width c2-width))) (or (ivy-more-chars) (let ((request-curl-options (list "-H" (string-trim (url-http-user-agent-string))))) (request url :type "GET" :parser 'json-read :success (cl-function (lambda (&key data &allow-other-keys) (ivy-update-candidates (mapcar (lambda (item) (let-alist item (propertize (format "%s %s %s" (truncate-string-to-width (propertize (or .title "") 'face '(:foreground "yellow")) c1-width nil ?\s "…") (truncate-string-to-width (or .description "") c2-width nil ?\s "…") (truncate-string-to-width (propertize (string-join (or .api_ref_data.languages "") "/") 'face '(:foreground "cyan1")) c3-width nil ?\s "…")) 'url .url))) (cdr (car data))))))) 0)))) :action (lambda (selection) (browse-url (concat "https://developer.apple.com" (get-text-property 0 'url selection)))) :dynamic-collection t :caller 'ar/counsel-apple-search))
Plain Org v1.2 released
Although Plain Org v1.2 has been in the App Store for a little while, the release write-up was overdue, sorry. The update receives some new features and bugfixes.
If you haven't heard of Plain Org, it gives ya access to your org files on iOS while away from your beloved Emacs.
If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it.
Ok, now on to what's included in the v1.2 release…
Edit heading sections inline
v1.0 introduced outline editing (for headings only). In v1.2, we can also edit section content. Press the return
key multiple times to exit out section editing.
Filter by keyword/priority/tag
From the search dialog, you can now filter by keyboard, priority, and tag.
Render drawers and properties
Drawers are now rendered and can be expanded to view their content.
Open files via the Files app's "Share" sheet
From the Files app, you can now explicitly request launching files in Plain Org by using the "Share" menu.
Render LaTeX src blocks (experimental)
This one has its rough edges at the moment, so have to mark it experimental, but… you can can now render #+begin_src latex
blocks.
Insert title/id in new files
New files created via Plain Org automatically get #+TITLE:
and :ID:
inserted by default as follows:
#+TITLE: My favorite title :PROPERTIES: :ID: 7C845D38-8D80-41B5-BEB1-94F673807355 :END:
UPDATE: Sorry, this feature currently has a bug. You may not get these values inserted into your new document. Working on a fix.
Adding new tags quicker
Add tags quicker via the new + button.
Enable/disable sticky tags
Keywords, indent, and tags are maintained when adding new headings via outline editing. If you prefer disabling sticky tags, this can now be disabled.
Improved navigation bar
v1.2 makes the navigation bar feel more at home on your iPhone. It uses a large title which scrolls into the navigation bar.
Bugfixes
- Fix table rendering for iPad width.
- Fix image's horizontal padding.
- Fix adding new tags on new headings.
- Fix snapshotting bug resulting in Syncthing conflicts.
- Fix tapping menu after presenting other dialogs.
- Filter out parenthesis in file-local keywords like
TODO(t)
. - Commit pending inline changes if search is requested.
- Fix opening local links inside tables.
- Roundtrip whitespace in empty headings.
- Roundtrip trailing whitespace when raw-editing heading content.
- Tapping on body content should not toggle expansion.
Happy New Year and forming new habits
Hacker News has a summary of Atomic Habits (the book). In my case, I really enjoyed reading the entire book. I liked its narrative, mixing actionable and concrete advice with personal stories and experiments.
After reading Atomic Habits during the first lockdown, I was excited to try out its actionables, specially tracking to keep me honest.
I tried a bunch of iOS apps, but wanted no friction, no tracking, no cloud, no social, no analytics, no account, etc. so eventually built Flat Habits (flathabits.com). Also wanted to own my habit data (as plain text), so I made sure Flat Habits stored its data locally as an org file.
I'm an Emacs nutter and can say the strength in habit tracking lies in removing daily friction from the tracking process itself. A quickly accessible mobile app can really help with that. For me, Emacs plays a less important role here. The plain text part is cherry on top (bringing piece of mind around lock-in). In my case, it's been months since I looked at the plain text file itself from an Emacs org buffer. The iOS app, on the other hand, gets daily usage.
As for forming lasting habits (the actual goal here)… it's been well over a year since I started running as a regular form of exercise. While reading Atomic Habits really changed how I think of habits, a tracker played a crucial part in the daily grind. I happen to have built a tracker that plays nice with Emacs.
It's a new year. If you're looking at forming new habits, you may want some inspiration and also practical and concrete guidance. The book Atomic Habits can help with that. You can decide on which apps and how to implement the tracking process later on. Pen and paper is also a viable option and there are plenty of templates you can download.
There's a surplus of habit-tracking apps on the app stores. I built yet another one for iOS, modeled after my needs.
Plain Org v1.1 released 🎄☃️
Plain Org v1.1 is now available on the App Store. The update receives new features and bugfixes.
If you're finding Plain Org useful, please help support this effort by getting the word out. Tell your friends, tweet, or blog about it.
What is Plain Org?
Ok, now on to what's included in the v1.1 release…
Compact mode
By default, Plain Org layout uses generous padding. The new option Menu -> View -> Compact mode
packs more content into your screen.
Regroup active and inactive tasks
Regrouping tasks now bubbles active ones up. Similarly, inactive tasks drop to the bottom of their node. Changes are persisted to the org file.
Native table rendering
Tables are now rendered natively but also support displaying links and other formatting within cells.
Open local ID links
If your file provider supports granting access to folders, local ID links (ie. id:eb155a82-92b2-4f25-a3c6-0304591af2f9
) can now be resolved and opened from Plain Org. Note that for ID links to resolve, other org files must live in either the same directory or a subdirectory.
Fill paragraphs
If your org paragraphs contain newlines optimizing for bigger screens, you can toggle Menu -> View -> Fill paragraph
to optimize rendering for your iPhone. This rendering option makes no file modifications.
By the way, the previous screenshot text comes from Org Mode - Organize Your Life In Plain Text, a magnificent org resource.
Show/hide basic scheduling
Use the new Menu -> View -> Scheduling
to toggle showing SCHEDULED
or DEADLINE
dates.
Show/hide tags
Similarly, the new Menu -> View -> Tags
option toggles displaying tags.
Native list rendering
Lists are now rendered natively. With the exception of numbered cases, list items now share a common bullet icon. Description lists are also recognized and receive additional formatting when rendered.
- First list item * Second list item + Third list item 1. Numbered list item + Term :: Description for term
Numbered checkboxes are now recognized and receive the same formatting and interaction as their non-numbered counterparts.
1. [ ] First checkbox 2. [X] Second checkbox 3. [X] Third checkbox
Reload current file
Plain Org may not be able to automatically reload files for some syncing providers. In those instances, use Menu -> Reload
to explicitly request a reload.
Open .txt files
Although .org files are plain text files, they aren't always recognized by other text-editing apps. This release enables opening .txt files, so you can choose to render them in Plain Org, while giving you the option to edit elsewhere.
Bugfixes
- Improve vertical whitespace handling.
- Fixes rendering edge cases.
- Fail gracefully when creating new files on unsupported cloud providers.
- Prevent creating new files with redundant extensions.
- File access improvements.
- Replicates property spacing behaviour using Emacs's
org-property-format
default value. - Fixes keyword picker border rendering.
- Improves rendering performance for large nodes.
Emacs bends again
While adding more rendering capabilities to Plain Org, it soon became apparent some sort of screenshot/snapshot testing was necessary to prevent regressing existing features. That is, we first generate a rendered snapshot from a given org snippet, followed by some visual inspection, right before we go and save the blessed snapshot (often referred to as golden) to our project. Future changes are validated against the golden snapshot to ensure rendering is still behaving as expected.
Let's say we'd like to validate table rendering with links, we can write a test as follows:
func testTableWithLinks() throws { assertSnapshot( matching: OrgMarkupText.make( """ | URL | Org link | |------------------------+-------------| | https://flathabits.com | [[https://flathabits.com][Flat Habits]] | | Regular text | Here too | |------------------------+-------------| """), as: .image(layout: .sizeThatFits)) }
The corresponding snapshot golden can be seen below.
This is all done rather effortlessly thanks to Point Free's wonderful swift-snapshot-testing utilities.
So what does any of this have to do with Emacs? You see, as I added more snapshot tests and made modifications to the rendering logic, I needed a quick way to visually inspect and override all goldens. All the main pieces were already there, I just needed some elisp glue to bend Emacs my way™.
First, I needed to run my Xcode builds from the command line. This is already supported via xcodebuild. Next, I needed a way to parse test execution data to extract failing tests. David House's xcodebuild-to-json handles this perfectly. What's left? Glue it all up with some elisp.
Beware, the following code snippet is packed with assumptions about my project, it's messy, surely has bugs, can be optimized, etc. But the important point here is that Emacs is such an amazing malleable power tool. Throw some elisp at it and you can to bend it to your liking. After all, it's your editor.
And so here we are, I can now run snapshot tests from Emacs using my hacked up plainorg-snapshot-test-all
function and quickly override (or ignore) all newly generated snapshots by merely pressing y/n keys. Oh, and our beloved web browser was also invited to the party. Press "d" to open two browser tabs if you'd like to take a closer look (not demoed below).
Success. Emacs bends again.
;;; -*- lexical-binding: t; -*- (defun plainorg-snapshot-test-all () "Invoke xcodebuild, compare failed tests screenshots side-to-side, and offer to override them." (interactive) (let* ((project (cdr (project-current))) (json-tmp-file (make-temp-file "PlainOrg_Tests_" nil ".json")) (default-directory project)) (unless (file-exists-p (concat project "PlainOrg.xcodeproj")) (user-error "Not in PlainOrg project")) (set-process-sentinel (start-process "xcodebuild" (with-current-buffer (get-buffer-create "*xcodebuild*") (let ((inhibit-read-only t)) (erase-buffer)) (current-buffer)) "/usr/bin/xcodebuild" "-scheme" "PlainOrg" "-target" "PlainOrgTests" "-destination" "name=iPhone 13" "-quiet" "test") (lambda (p e) (with-current-buffer (get-buffer "*xcodebuild*") (let ((inhibit-read-only t)) (insert (format "xcodebuild exit code: %d\n\n" (process-exit-status p))))) (when (not (eq 0 (process-exit-status p))) (set-process-sentinel (start-process "xcodebuild-to-json" "*xcodebuild*" "/opt/homebrew/bin/xcodebuild-to-json" "--derived-data-folder" (format "/Users/%s/Library/Developer/Xcode/DerivedData/" (user-login-name)) "--output" json-tmp-file) (lambda (p e) (with-current-buffer (get-buffer "*xcodebuild*") (let ((inhibit-read-only t)) (insert (format "xcodebuild-to-json exit code: %d\n\n" (process-exit-status p))))) (when (= 0 (process-exit-status p)) (with-current-buffer (get-buffer "*xcodebuild*") (let ((inhibit-read-only t)) (insert "Screenshot comparison started\n\n"))) (plainorg--snapshot-process-json (get-buffer "*xcodebuild*") json-tmp-file) (with-current-buffer (get-buffer "*xcodebuild*") (let ((inhibit-read-only t)) (insert "\nScreenshot comparison finished\n")) (read-only-mode +1)))))))) (switch-to-buffer-other-window "*xcodebuild*"))) (defun plainorg--snapshot-process-json (result-buffer json) "Find all failed snapshot tests in JSON and offer to override screenshots, comparing them side to side." (let ((hashtable (with-current-buffer (get-buffer-create "*build json*") (erase-buffer) (insert-file-contents json) (json-parse-buffer)))) (mapc (lambda (item) (when (equal (gethash "id" item) "SnapshotTests") (mapc (lambda (testCase) (when (and (gethash "failureMessage" testCase) (string-match-p "Snapshot does not match reference" (gethash "failureMessage" testCase))) (let* ((paths (plainorg--snapshot-screenshot-paths (gethash "failureMessage" testCase))) (override-result (plainorg--snapshot-override-image "Expected screenshot" (nth 0 paths) ;; old "Actual screenshot" (nth 1 paths) ;; new (nth 0 paths)))) (when override-result (with-current-buffer result-buffer (let ((inhibit-read-only t)) (insert override-result) (insert "\n"))))))) (gethash "testCases" item)))) (gethash "classes" (gethash "details" hashtable))))) (defun plainorg--snapshot-screenshot-paths (failure-message) "Extract a paths list from FAILURE-MESSAGE of the form: failed - Snapshot does not match reference. @− \"/path/to/expected/screenshot.1.png\" @+ \"/path/to/actual/screenshot.1.png\" Newly-taken snapshot does not match reference. " (mapcar (lambda (line) (string-remove-suffix "\"" (string-remove-prefix "\"" line))) (seq-filter (lambda (line) (string-prefix-p "\"" line)) (split-string failure-message "\n")))) (defun plainorg--snapshot-override-image (old-buffer old new-buffer new destination) (let ((window-configuration (current-window-configuration)) (action) (result)) (unwind-protect (progn (delete-other-windows) (split-window-horizontally) (switch-to-buffer (with-current-buffer (get-buffer-create old-buffer) (let ((inhibit-read-only t)) (erase-buffer)) (insert-file-contents old) (image-mode) (current-buffer))) (switch-to-buffer-other-window (with-current-buffer (get-buffer-create new-buffer) (let ((inhibit-read-only t)) (erase-buffer)) (insert-file-contents new) (image-mode) (current-buffer))) (while (null result) (setq action (read-char-choice (format "Override %s? (y)es (n)o (d)iff in browser? " (file-name-base old)) '(?y ?n ?d ?q))) (cond ((eq action ?n) (setq result (format "Keeping old %s" (file-name-base old)))) ((eq action ?y) (copy-file new old t) (setq result (format "Overriding old %s" (file-name-base old)))) ((eq action ?d) (shell-command (format "open -a Firefox %s --args --new-tab" old)) (shell-command (format "open -a Firefox %s --args --new-tab" new))) ((eq action ?q) (set-window-configuration window-configuration) (setq result (format "Quit %s" (file-name-base old))))))) (set-window-configuration window-configuration) (kill-buffer old-buffer) (kill-buffer new-buffer)) result))
Plain Org has joined the chat (iOS)
The App Store is a crowded space when it come to markdown apps. A quick search yields a wonderful wealth of choice. Kinda overwhelming, but a great problem to have nonetheless.
For those of us with org as our markup of choice, the App Store is far less crowded. I wish we could fill more than a screen's worth of search results, so you know… I could show you another pretty gif scrolling through org results. For now, we'll settle on a single frame showcasing our 4 org options.
Beorg, MobileOrg, Flat Habits, and Orgro are all great options. Each with strengths of their own. Organice, while not on the App Store, is another option for those looking for a web alternative. Of these, I had already authored one of them. More on that in a sec… You see, about a year ago I wanted to play with Swift, SPM, and lsp itself. Also, having Swift code completion in Emacs via lsp-sourcekit sounded like a fun thing to try out, so I started using it while writing a Swift org parser.
While working on the parser, I happened to be reading Atomic Habits (awesome book btw)… It was also a great time to play around with SwiftUI, which by the way, is pretty awesome too. With Atomic Habits fresh in mind, org parser in one hand, and SwiftUI in the other, I built Flat Habits: a lightweight habit tracker powered by org.
I love being able to save habit data to plain text and easily track on my iPhone (via Flat Habits) or laptop (via Emacs). I wanted to extend similar convenience to org tasks, so I built Plain Org.
My previous post mentioned quickly adding new tasks and searching existing ones as Plain Org's driving goals. Of course, neither of those are as useful without automatic cloud syncing, so pluging into iOS's third party cloud support was a must-have.
With these baseline features in place, I started an alpha/beta group via TestFlight. Early Plain Org adopters have been wonderfully supportive, given lots of great feedback, and helped shape the initial feature set you see below.
There's plenty more that can be supported, but hey let's get v1 out the door. Gotta start somewhere.
Plain Org v1 features
- View and edit your org mode tasks while on the go.
- Beautifully rendered org markup.
- Sync your org files using your favorite cloud provider.
- Create new files.
- Outline-style editing with toolbar
- Keywords
- Indent
- Priority
- Tags
- Formatting: bold, italic, underline, strikethrough, verbatim, and code.
- Add links from Safari via share extension.
- Add new tasks via Spotlight.
- Reorder headings via drag/drop.
- Checkboxes
- Interactive toggling.
- Quickly reset multiple checkboxes.
- Follow local links.
- Show inline images.
- File-local keywords and visibility.
- Filter open/closed tasks.
- Show/hide stars.
- Edit raw text.
- Light/dark mode.
Plain Org joins the chat
Today Plain Org joins the likes of Beorg, MobileOrg, Flat Habits, and Orgro on the App Store.
This post was written in org mode.
Plain Org for iOS (a month later)
A month ago, I posted about my desire to bring org tasks/TODOs to iOS and make them quickly available from my iPhone.
Since then, I've received some great feedback, which I've been slowly chipping away at. My intent isn't so much to move my org workflow over to iOS, but to supplement Emacs while away from my laptop.
As of now, this is what the inline edit experience looks like:
If, like me, you prefer dark mode. The app's got ya covered:
Plain Org is not yet available on the App Store, but you can get a TestFlight invite if you send me an email address. Ping me on reddit, twitter, or email me at "plainorg" + "@" + "xenodium.com".
You can also check out progress over at the r/plainorg subreddit.
Org habits on iOS? Check! Tasks, you're next
I'm an org mode fan. This blog is powered by org. It's more of an accidental blog that started as a single org file keeping notes. I use org babel too. Oh and org habits. My never-ending list of TODOs is also powered by org. I manage all of this from Emacs and peek at TODOs using org agenda. This all works really well while I'm sitting in front of my laptop running Emacs.
But then I'm away from my laptop… and I need to quickly record habits on the go. I need it to be low-friction. Ssh'ing to an Emacs instance from a smartphone isn't an option. I'm an iPhone user, so whatever the solution, it should play nice with Emacs and org mode. I built Flat Habits for habit tracking and I'm fairly happy with the result. As of today, my longest-tracked habit is on a 452-day streak.
Moving on to org tasks/TODOs… I want something fairly frictionless while on the go. With Flat Habits as a stepping stone, I can now reuse some parts to build Plain Org. This new app should give me quick access to my tasks. The two driving goals are: quickly add new tasks and search existing ones while away from my laptop. Ok, maybe basic editing helps too. Oh and it should sync over cloud, of course.
I now have an early implementation of sorts, available on TestFlight. If you'd like to give it a try, send me an email address to receive the the invite. Ping me on reddit, twitter, or email me at "plainorg" + "@" + "xenodium.com".
Flat Habits 1.1 released
Flat Habits 1.1 is now available on the App Store. Flat Habits is a habit tracker that’s mindful of your time, data, and privacy. It's powered by org plain text markup, enabling you to use your favorite editor (Emacs, Vim, VSCode, etc.) to poke at your habit data.
What's new?
This release implements a few of features requested by users.
Multiday weekly habits
This is the chunkiest addition and most requested feature. You can now select multiple days when scheduling weekly habits.
Historical management
Sometimes you forget to mark a habit done or make a mistake toggling one. Either way, you can now toggle any habit day from the calendar/streak view.
Long tap
Long tap shows you the editing option available for that day.
Short tap
Short tap typically toggles between "Done" and "Not done".
Where's today?
A few folks rightfully asked for today's date to be highlighted in the calendar view, and so we now have a red circle.
Improved error messages
Hopefully you don't run into issues, but if you do, I hope the app helps ya sort them out.
Bugfixes
- Tapping on blur now dismisses habit edit dialog.
- Future habits now longer editable.
- Skipped habits no longer have a default tap action.
- Undoing from streak/calendar view now refreshes correctly.
- Undoing habit addition on iPad removes streak/calendar view.
macOS: Show in Finder / Show in Emacs
From Christian Tietze's Open macOS Finder Window in Emacs Dired, I learned about reveal-in-osx-finder. This is handy for the few times I want to transition from Emacs to Finder for file management. I say few times since Emacs's directory editor, dired, is just awesome. I've written about dired customizations here and here, but since dired is just another buffer, you can apply your Emacs magic like multiple cursors to batch rename files in an editable dired buffer.
To transition from macOS Finder to Emacs, Christian offers an Emacs interactive command that fetches Finder's location and opens a dired buffer via AppleScript. On a similar note, I learned from redditor u/pndc that Finder's proxy icons can be dragged over to Emacs, which handily drops ya into a dired buffer.
With these two solutions in mind, I looked into a third one to offer a context menu option in Finder to show the file in Emacs. This turned out to be fairly easy using Automator, which I've rarely used.
I created a flow that runs a shell script to "Show in Emacs", revealing the selected file or folder in an dired buffer. This is similar to Christian's solution, but invoked from Finder itself. The flow also uses dired-goto-file which moves the point (cursor) to the file listed under dired.
current_dir=$(dirname "$1") osascript -e 'tell application "Emacs" to activate' path/to/emacsclient --eval "(progn (dired \"$current_dir\") (dired-goto-file \"$1\"))"
As a bonus, I added an "Open in Emacs" option, which does as it says on the tin. Rather than show the file listed in a dired buffer, it gets Emacs to open it in your favorite major mode. This option is not technically needed since Finder already provides an "Open With" context menu, but it does remove a few click here and there.
osascript -e 'tell application "Emacs" to activate' /Users/alvaro/homebrew/bin/emacsclient --eval "(find-file \"$1\")"
On a side note, Emacs defaults to creating new frames when opening files via "Open With" menu (or "open -a Emacs foo.txt"). I prefer to use my existing Emacs frame, which can be accomplished by setting ns-pop-up-frames to nil.
(setq ns-pop-up-frames nil)
Emacs: smarter search and replace
Not long ago, I made a note to go back and read Mac for Translators's Emacs regex with Emacs lisp post. The author highlights Emacs's ability to apply additional logic when replacing text during a search-and-replace session. It does so by leveraging elisp expressions.
Coincidentally, a redditor recently asked What is the simplest way to apply a math formula to all numbers in a buffer/region? Some of the answers also point to search and replace leveraging elisp expressions.
While I rarely need to apply additional logic when replacing matches, it's nice to know we have options available in our Emacs toolbox. This prompted me to check out replace-regexp's documentation (via M-x describe-function or my favorite M-x helpful-callable). There's lots in there. Go check its docs out. You may be pleasantly surprised by all the features packed under this humble function.
For instance, \& expands to the current match. Similarly, \#& expands to the current match, fed through string-to-number. But what if you'd like to feed the match to another function? You can use \, to signal evaluation of an elisp expression. In other words, you could multiply by 3 using \,(* 3 \#&) or inserting whether a number is odd or even with something like \,(if (oddp \#&) "(odd)" "(even)").
Take the following text:
1 2 3 4 5 6
We can label each value "(odd)" or "(even)" as well as multiply by 3, by invoking replace-regexp as follows:
M-x replace-regexp
[PCRE] Replace regex:
[-0-9.]+
Replace regex [-0-9.]+:
\& \,(if (oddp \#&) "(odd)" "(even)") x 3 = \,(* 3 \#&)
1 (odd) x 3 = 3 2 (even) x 3 = 6 3 (odd) x 3 = 9 4 (even) x 3 = 12 5 (odd) x 3 = 15 6 (even) x 3 = 18
It's worth noting that replace-regexp's cousin query-replace-regexp also handles all this wonderful magic.
Happy searching and replacing!
Previewing SwiftUI layouts in Emacs (revisited)
Back in May 2020, I shared a snippet to extend ob-swift to preview SwiftUI layouts using Emacs org blocks.
When I say extend, I didn't quite modify ob-swift itself, but rather advised org-babel-execute:swift to modify its behavior at runtime.
Fast-forward to June 2021 and Scott Nicholes reminded me there's still interest in org babel SwiftUI support. ob-swift seems a little inactive, but no worries there. The package offers great general-purpose Swift support. On the other hand, SwiftUI previews can likely live as a single-purpose package all on its own… and so I set off to bundle the rendering functionality into a new ob-swiftui package.
Luckily, org babel's documentation has a straightforward section to help you develop support for new babel languages. They simplified things by offering template.el, which serves as the foundation for your language implementation. For the most part, it's a matter of searching, replacing strings, and removing the bits you don't need.
The elisp core of ob-swiftui is fairly simple. It expands the org block body, inserts the expanded body into a temporary buffer, and finally feeds the code to the Swift toolchain for execution.
(defun org-babel-execute:swiftui (body params) "Execute a block of SwiftUI code in BODY with org-babel header PARAMS. This function is called by `org-babel-execute-src-block'" (message "executing SwiftUI source code block") (with-temp-buffer (insert (ob-swiftui--expand-body body params)) (shell-command-on-region (point-min) (point-max) "swift -" nil 't) (buffer-string)))
The expansion in ob-swiftui–expand-body is a little more interesting. It decorates the block's body, so it can become a fully functional and stand-alone SwiftUI macOS app. If you're familiar with Swift and SwiftUI, the code should be fairly self-explanatory.
From an org babel's perspective, the expanded code is executed whenever we press C-c C-c (or M-x org-ctrl-c-ctrl-c) within the block itself.
It's worthing mentioning that our new implementation supports two babel header arguments (results and view). Both extracted from params using map-elt and replaced in the expanded Swift code to enable/disable snapshotting or explicitly setting a SwiftUI root view.
(defun ob-swiftui--expand-body (body params) "Expand BODY according to PARAMS and PROCESSED-PARAMS, return the expanded body." (let ((write-to-file (member "file" (map-elt params :result-params))) (root-view (when (and (map-elt params :view) (not (string-equal (map-elt params :view) "none"))) (map-elt params :view)))) (format " // Swift snippet heavily based on Chris Eidhof's code at: // https://gist.github.com/chriseidhof/26768f0b63fa3cdf8b46821e099df5ff import Cocoa import SwiftUI import Foundation let screenshotURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString + \".png\") let preview = %s // Body to run. %s extension NSApplication { public func run<V: View>(_ view: V) { let appDelegate = AppDelegate(view) NSApp.setActivationPolicy(.regular) mainMenu = customMenu delegate = appDelegate run() } public func run<V: View>(@ViewBuilder view: () -> V) { let appDelegate = AppDelegate(view()) NSApp.setActivationPolicy(.regular) mainMenu = customMenu delegate = appDelegate run() } } extension NSApplication { var customMenu: NSMenu { let appMenu = NSMenuItem() appMenu.submenu = NSMenu() let quitItem = NSMenuItem( title: \"Quit \(ProcessInfo.processInfo.processName)\", action: #selector(NSApplication.terminate(_:)), keyEquivalent: \"q\") quitItem.keyEquivalentModifierMask = [] appMenu.submenu?.addItem(quitItem) let mainMenu = NSMenu(title: \"Main Menu\") mainMenu.addItem(appMenu) return mainMenu } } class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate { var window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 414 * 0.2, height: 896 * 0.2), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) var contentView: V init(_ contentView: V) { self.contentView = contentView } func applicationDidFinishLaunching(_ notification: Notification) { window.delegate = self window.center() window.contentView = NSHostingView(rootView: contentView) window.makeKeyAndOrderFront(nil) if preview { screenshot(view: window.contentView!, saveTo: screenshotURL) // Write path (without newline) so org babel can parse it. print(screenshotURL.path, terminator: \"\") NSApplication.shared.terminate(self) return } window.title = \"press q to exit\" window.setFrameAutosaveName(\"Main Window\") NSApp.activate(ignoringOtherApps: true) } } func screenshot(view: NSView, saveTo fileURL: URL) { let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds)! view.cacheDisplay(in: view.bounds, to: rep) let pngData = rep.representation(using: .png, properties: [:]) try! pngData?.write(to: fileURL) } // Additional view definitions. %s " (if write-to-file "true" "false") (if root-view (format "NSApplication.shared.run(%s())" root-view) (format "NSApplication.shared.run {%s}" body)) (if root-view body ""))))
For rendering inline SwiftUI previews in Emacs, we rely on NSView's bitmapImageRepForCachingDisplay to capture an image snapshot. We write its output to a temporary file and piggyback-ride off org babel's :results file header argument to automatically render the image inline.
Here's ob-swiftui inline rendering in action:
When rendering SwiftUI externally, we're effectively running and interacting with the generated macOS app itself.
The two snippets give a general sense of what's needed to enable org babel to handle SwiftUI source blocks. Having said that, the full source and setup instructions are both available on github.
ob-swiftui is now available on melpa.
Blurring the lines between shell and editor
I recently tweeted that Vivek Haldar's 10-year old post rings true today just the same. He writes about the levels of Emacs proficiency. All 6 levels are insightful in their own right, but for the sake of this post, let's quote an extract from level 4. Shell inside Emacs:
"And then, you learned about it: M-x shell.
It was all just text. Why did you need another application for it? Why should only the shell prompt be editable? Why can’t I move my cursor up a few lines to where the last command spewed out its results? All these problems simply disappear when your shell (or shells) simply becomes another Emacs buffer, upon which all of the text manipulation power of Emacs can be brought to bear."
In other words, we aren't merely removing shell restrictions, but opening up possibilities…
Take Emacs eshell looping, for example. I use it so infrequently, I could never remember eshell's syntax. I would refer back to EmacsWiki's Eshell For Loop or Mastering Emacs's Mastering Eshell comments for a reminder. It finally dawned on me. I don't need to internalize this eshell syntax. I have YASnippet available like any other buffer. I could just type "for" and let YASnippet do the rest for me.
All I need is a tiny YASnippet:
#name : Eshell for loop #key : for # -- for f in ${1:*} { ${2:echo} "$f"; $3} $0
Want a gentle and succinct YASnippet intro? Check out Jake's YASnippet introduction video.
If you're a shell-mode user, YASnippet would have you covered in your favorite shell. The expansion snippet can be modified to a Bash equivalent, giving us the same benefit. We type "for" and let YASnippet expand and hop over arguments. Here's a Bash equivalent emphasizing the hopping a little more:
#name : bash for loop #key : for # -- for f in ${1:*}; do ${2:echo} $f; done $0
ps. Looks like vterm, term, or ansi-term work too. See Shane Mulligan's post: Use YASnippets in term and vterm in emacs.
xcodebuild's SPM support (Xcode 11)
Had been a while since I looked into generating Xcode projects from a Swift package. On my latest use of the generate-xcodeproj subcommand, I was greeted by a nice warning surprise…
swift package generate-xcodeproj
warning: Xcode can open and build Swift Packages directly. 'generate-xcodeproj' is no longer needed and will be deprecated soon. generated: ./FooBar.xcodeproj
Xcode can handle Swift packages directly. Similarly, xcodebuild can handle them too. This isn't new. It's likely been available since Xcode 11. I just totally missed it.
Note: I've yet to dig into Xcode 13 beta, as Swift packages may already support the build/test features I was after in xcodebuild (like build/test on Catalyst).
In any case, on to xcodebuild… but let's first create a brand new Swift package.
Creating a Swift package library
mkdir FooBar && cd FooBar swift package init --type library
Creating library package: FooBar Creating Package.swift Creating README.md Creating .gitignore Creating Sources/ Creating Sources/FooBar/FooBar.swift Creating Tests/ Creating Tests/FooBarTests/ Creating Tests/FooBarTests/FooBarTests.swift
List package schemes
We can use xcodebuild to list the available schemes.
xcodebuild -list
Command line invocation: /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -list User defaults from command line: IDEPackageSupportUseBuiltinSCM = YES Resolve Package Graph Resolved source packages: FooBar: /tmp/FooBar Information about workspace "FooBar": Schemes: FooBar
Show supported platform, architecture, etc
Similarly, we can list destinations supported for the schemes.
xcodebuild -showdestinations -scheme FooBar
Command line invocation: /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -showdestinations -scheme FooBar User defaults from command line: IDEPackageSupportUseBuiltinSCM = YES Resolve Package Graph Resolved source packages: FooBar: /tmp/FooBar Available destinations for the "FooBar" scheme: { platform:macOS, arch:x86_64, id:... } { platform:macOS, arch:x86_64, variant:Mac Catalyst, id:... } ... { platform:iOS Simulator, id:..., OS:14.5, name:iPhone 12 Pro } Ineligible destinations for the "FooBar" scheme: ...
macOS builds
Let's build for macOS, though let's first import UIKit into FooBar.swift. This ensures we get an expected failure when building for macOS.
import UIKit struct FooBar { var text = "Hello, World!" }
Now let's attempt to build it…
xcodebuild build -quiet -scheme FooBar -destination 'platform=macOS'
--- xcodebuild: WARNING: Using the first of multiple matching destinations: { platform:macOS, arch:x86_64, id:3D097357-EB7D-565D-9058-CE7C3135927B } { platform:macOS, arch:x86_64, variant:Mac Catalyst, id:3D097357-EB7D-565D-9058-CE7C3135927B } /tmp/FooBar/Sources/FooBar/FooBar.swift:1:8: error: no such module 'UIKit' import UIKit ^ note: Using new build system note: Building targets in parallel note: Planning build note: Analyzing workspace note: Using build description from disk note: Build preparation complete ** BUILD FAILED **
The failure expected as UIKit isn't available on your typical macOS builds.
macOS Catalyst builds
We do, however, have Catalyst available, so we can use its variant to build for macOS with UIKit support, and.. voilà!
xcodebuild build -quiet -scheme FooBar -destination 'platform=macOS,variant=Mac Catalyst' && echo \\o/
\o/
Emacs org block completion on melpa
When enabled, the character "<" triggers company completion of org blocks.
I get the occasional ping to package the code from this post and publish it on melpa. Finally gave it a go. Moved the code here.
This was my first time publishing on melpa. The process was very smooth. Big thanks to melpa volunteers!
Emacs DWIM: do what ✨I✨ mean
Update: There's a DWIM follow-up for searching.
I was a rather puzzled the first time I spotted DWIM in an Emacs interactive command name. Don't think I remember what the command itself was, but what's important here is that DWIM stands for do what I mean.
I love DWIM interactive commands. They enable commands to be smarter and thus pack more functionality, without incurring the typical cognitive overhead associated with remembering multiple commands (or key bindings). The Emacs manual does a great job describing DWIM for the comment-dwim command:
The word “dwim” is an acronym for “Do What I Mean”; it indicates that this command can be used for many different jobs relating to comments, depending on the situation where you use it.
It's really great to find built-in DWIM-powered Emacs commands. Third-party packages often include them too. I typically gravitate towards these commands and bind them in my Emacs config. Examples being upcase-dwim, downcase-dwim, or mc/mark-all-dwim.
But what if the DWIM command doesn't exist or the author has written a command for what they mean? This is your editor, so you can make it do what you mean.
Take for example, org-insert-link, bound to C-c C-l by default. It's handy for inserting org mode links. I used it so frequently that I quickly internalized its key binding. Having said that, I often found myself doing some lightweight preprocessing prior to invoking org-insert-link. What if I can make org-insert-link do what I mean?
What do I mean?
Use URLs when in clipboard
If the URL is already in the clipboard, don't ask me for it. Just use it.
Use the region too
If I have a region selected and there's a URL in the clipboard, just sort it out without user interaction.
Automatically fetch titles
Automatically fetch URL titles from their HTML tag, but ask me for tweaks before insertion.
Fallback to org-insert-link
If my DWIM rules don't apply, fall back to using good ol' org-insert-link.
My most common use case here is when editing an existing link where I don't want neither its title nor URL automatically handled.
The code
This is your own DWIM command that does what you mean. Strive to write a clean implementation, but hey you can be forgiven for not handling all the cases that other folks may want or inlining more code than usual. The goal is to bend your editor a little, not write an Emacs package.
(defun ar/org-insert-link-dwim () "Like `org-insert-link' but with personal dwim preferences." (interactive) (let* ((point-in-link (org-in-regexp org-link-any-re 1)) (clipboard-url (when (string-match-p "^http" (current-kill 0)) (current-kill 0))) (region-content (when (region-active-p) (buffer-substring-no-properties (region-beginning) (region-end))))) (cond ((and region-content clipboard-url (not point-in-link)) (delete-region (region-beginning) (region-end)) (insert (org-make-link-string clipboard-url region-content))) ((and clipboard-url (not point-in-link)) (insert (org-make-link-string clipboard-url (read-string "title: " (with-current-buffer (url-retrieve-synchronously clipboard-url) (dom-text (car (dom-by-tag (libxml-parse-html-region (point-min) (point-max)) 'title)))))))) (t (call-interactively 'org-insert-link)))))
Org web tools package
I showed how to write your own DWIM command, so you can make Emacs do what ✨you✨ mean. ar/org-insert-link-dwim was built for my particular needs.
Having said all of this, alphapapa has built a great package with helpers for the org web/link space. It doesn't do what I mean (for now anyway), but it may work for you: org-web-tools: View, capture, and archive Web pages in Org-mode1.
Emacs link scraping (2021 edition)
A recent Hacker News post, Ask HN: Favorite Blogs by Individuals, led me to dust off my oldie but trusty command to extract comment links. I use it to dissect these wonderful references more effectively.
You see, I wrote this command back in 2015. We can likely revisit and improve. The enlive package continues to do a fine job fetching, parsing, and querying HTML. Let's improve my code instead… we can shed a few redundant bits and maybe use newer libraries and features.
Most importantly, let's improve the user experience by sanitizing and filtering URLs a little better.
We start by writing a function that looks for a URL in the clipboard and subsequently fetches, parses, and extracts all links found in the target page.
(require 'enlive) (require 'seq) (defun ar/scrape-links-from-clipboard-url () "Scrape links from clipboard URL and return as a list. Fails if no URL in clipboard." (unless (string-prefix-p "http" (current-kill 0)) (user-error "no URL in clipboard")) (thread-last (enlive-query-all (enlive-fetch (current-kill 0)) [a]) (mapcar (lambda (element) (string-remove-suffix "/" (enlive-attr element 'href)))) (seq-filter (lambda (link) (string-prefix-p "http" link))) (seq-uniq) (seq-sort (lambda (l1 l2) (string-lessp (replace-regexp-in-string "^http\\(s\\)*://" "" l1) (replace-regexp-in-string "^http\\(s\\)*://" "" l2))))))
Let's chat (current-kill 0) for a sec. No improvement from my previous usage, but let's just say building interactive commands that work with your current clipboard (or kill ring in Emacs terminology) is super handy (see clone git repo from clipboard).
Moving on to sanitizing and filtering URLs… Links often have trailing slashes. Let's flush them. string-remove-suffix to the rescue. This and other handy string-manipulating functions are built into Emacs since 24.4 as part of subr-x.el.
Next, we can keep http(s) links and ditch everything else. The end-goal is to extract links posted by users, so these are typically fully qualified external URLs. seq-filter steps up to the task, included in Emacs since 25.1 as part of the seq.el family. We remove duplicate links using seq-uniq and sort them via seq-sort. All part of the same package.
When sorting, we could straight up use seq-sort and string-lessp and nothing else, but it would separate http and https links. Let's not do that, so we drop http(s) prior to comparing strings in seq-sort's predicate. replace-regexp-in-string does the job here, but if you'd like to skip regular expressions, string-remove-prefix works just as well.
Yay, sorting no longer cares about http vs https:
https://andymatuschak.org http://antirez.com https://apenwarr.ca/log ...
With all that in mind, let's flatten list processing using thread-last. This isn't strictly necessary, but since this is the 2021 edition, we'll throw in this macro added to Emacs in 2016 as part of 25.1. Arthur Malabarba has a great post on thread-last.
Now that we've built out ar/scrape-links-from-clipboard-url function, let's make its content consumable!
The completing frameworks way
This is the 2021 edition, so power up your completion framework du jour and feed the output of ar/scrape-links-from-clipboard-url to our completion robots…
I'm heavily vested in ivy, but since we're using the built-in completing-read function, any completion framework like vertico, selectrum, helm, or ido should kick right in to give you extra powers.
(defun ar/view-completing-links-at-clipboard-url () "Scrape links from clipboard URL and open all in external browser." (interactive) (browse-url (completing-read "links: " (ar/scrape-links-from-clipboard-url))))
The auto-open way (use with caution)
Sometimes you just want to open every link posted in the comments and use your browser to discard, closing tabs as needed. The recent HN news instance wasn't one of these cases, with a whopping 398 links returned by our ar/scrape-links-from-clipboard-url.
Note: I capped the results to 5 in this gif/demo to prevent a Firefox tragedy (see seq-take).
In a case like Hacker News's, we don't want to surprise-attack the user and bomb their browser by opening a gazillion tabs, so let's give a little heads-up using y-or-n-p.
(defun ar/browse-links-at-clipboard-url () (interactive) (let ((links (ar/scrape-links-from-clipboard-url))) (when (y-or-n-p (format "Open all %d links? " (length links))) (mapc (lambda (link) (browse-url link)) links))))
The org way
My 2015 solution leveraged an org mode buffer to dump the fetched links. The org way is still my favorite. You can use whatever existing Emacs super powers you already have on top of the org buffer, including searching and filtering fueled by your favourite completion framework. I'm a fan of Oleh's swiper.
The 2021 implementation is mostly a tidy-up, removing some cruft, but also uses our new ar/scrape-links-from-clipboard-url function to filter and sort accordingly.
(require 'org) (defun ar/view-links-at-clipboard-url () "Scrape links from clipboard URL and dump to an org buffer." (interactive) (with-current-buffer (get-buffer-create "*links*") (org-mode) (erase-buffer) (mapc (lambda (link) (insert (org-make-link-string link) "\n")) (ar/scrape-links-from-clipboard-url)) (goto-char (point-min)) (switch-to-buffer (current-buffer))))
Emacs + community + packages + your own glue = awesome
To power our 2021 link scraper, we've used newer libraries included in more recent versions of Emacs, leveraged an older but solid HTML-parsing package, pulled in org mode (the epicenter of Emacs note-taking), dragged in our favorite completion framework, and tickled our handy browser all by smothering the lot with some elisp glue to make Emacs do exactly what we want. Emacs does rock.
gpg: decryption failed: No secret key (macOS)
gpg: decryption failed: No secret key
OMG! Where's my secret key gone!?
But but but, gpg –list-secret-keys says they're there. Puzzled…
Ray Oei's Stack Overflow answer solved the mystery for me: pinentry never got invoked, so likely something's up with the agent… Killing (and thus restaring) the gpg-agent did the trick:
gpgconf --kill gpg-agent
Thank you internet stranger. Balance restored.
Emacs plus –with-native-comp
I'm a big fan of Boris Buliga's Emacs Plus homebrew recipe for customizing and installing Emacs builds on macOS.
For a little while, I took a detour and built Emacs myself, so I could enable Andrea Corallo's fantastic native compilation. I documented the steps here. Though it was fairly straightforward, I did miss Emacs Plus's convenience.
I had been meaning to check back on Emacs Plus for native compilation support. Turns out, it was merged back in Dec 2020, and it works great!
Enabling native compilation is simple (just use –with-native-comp). As a bonus, you get all the Emacs Plus goodies. I'm loving –with-elrumo2-icon, enabling a spiffy icon to go with macOS Big Sur. –with-no-frame-refocus is also handy to avoid refocusing other frames when another one is closed.
In any case, here's the minimum needed to install Emacs Plus with native compilation support enabled:
brew tap d12frosted/emacs-plus brew install emacs-plus@28 --with-native-comp
Sit tight. Homebrew will build and install some chunky dependencies (including gcc and libgccjit).
Note: Your init.el needs tweaking to take advantage of native compilation. See my previous post for how I set mine, or go straight to my config.
Cycling window layouts with hammerspoon
Back in January, Patrik Collison tweeted about Rectangle's Todo mode. Rectangle looks great. Although I've not yet adopted it, Todo mode really resonates with me. I've been achieving similar functionality with hammerspoon.
Here's a quick and dirty function to cycle through my window layouts:
function reframeFocusedWindow() local win = hs.window.focusedWindow() local maximizedFrame = win:screen():frame() maximizedFrame.x = maximizedFrame.x + 15 maximizedFrame.y = maximizedFrame.y + 15 maximizedFrame.w = maximizedFrame.w - 30 maximizedFrame.h = maximizedFrame.h - 30 local leftFrame = win:screen():frame() leftFrame.x = leftFrame.x + 15 leftFrame.y = leftFrame.y + 15 leftFrame.w = leftFrame.w - 250 leftFrame.h = leftFrame.h - 30 local rightFrame = win:screen():frame() rightFrame.x = rightFrame.w - 250 + 15 rightFrame.y = rightFrame.y + 15 rightFrame.w = 250 - 15 - 15 rightFrame.h = rightFrame.h - 30 -- Make space on right if win:frame() == maximizedFrame then win:setFrame(leftFrame) return end -- Make space on left if win:frame() == leftFrame then win:setFrame(rightFrame) return end win:setFrame(maximizedFrame) end
A here's my ⌥-F binding to reframeFocusedWindow:
hs.hotkey.bind({"alt"}, "F", reframeFocusedWindow)
Flat Habits meets org agenda
UPDATE: Flat Habits now has its own page at flathabits.com.
Flat Habits v1.0.2 is out today, with habit-toggling now supported from the streak view.
Flat Habits runs on org, making it a great complement to Emacs and org agenda \o/
Flat Habits for iOS (powered by org)
UPDATE: Flat Habits now has its own page at flathabits.com.
No friction. No social. No analytics. No account. No cloud. No lock-in.
Why an app?
Tracking and accountability may help you develop positive habits. A simple habit-tracking app should make this easy. I'm not a habits expert, but got inspired by James Clear's Atomic Habits. Read that book if you're interested in the topic.
I wanted a frictionless habit tracker that gets out of the way, so I built one to my taste.
Sounds like a lot of work?
You mean habit tracking? It's not. I tried to make the app simple and focused. Mark a habit done whenever you do it. It's really encouraging to see your daily streaks grow. I really don't want to break them.
What kind of habits?
Any recurring habit you'd like to form like exercise, water the plants, read, make your bed, recycle, call grandma, yoga, cleaning, drink water, meditate, take a nap, make your lunch, journal, laundry, push-ups, sort out the dryer filter, floz, take your vitamins, take your meds, eat salad, eat fruit, practice a language, practice an instrument, go to bed early…
So it's like a task/todo app?
Nope. This app focuses solely on habits. Unlike todos/tasks, habits must happen regularly. If you don't water the plants, they will die. If you don't exercise regularly, you won't get the health benefits. Keep your habits separate from that long list of todos. You know, that panic-inducing list you're too afraid to look at.
Where is my data stored?
On your iPhone as a plain text file (in org mode format). You can view, edit, or migrate your data at any time (use export from the menu). You may also save it to a shared location, so you can access it from multiple devices/apps. Some of us like to use our beloved text editors (Emacs, Vim, VSCode, etc.) to poke at habits.
Got more questions?
I may not have the answer, but I can try. Ping me at flathabits*at*xenodium.com.
Privacy policy
No personal data is sent to any server, as there is no server component to this app. There are neither third party integrations, accounts, analytics, nor trackers in this app. All your data is kept on your iPhone, unless you choose a cloud provider to sync or store your data. See your cloud provider's privacy policy for details on how they may handle it.
If you choose to send feedback by email, you have the option to review and attach logs to help diagnose issues. If you'd like an email thread to be deleted, just ask.
To join TestFlight as a beta tester, you likely gave your email address. If you'd like your email removed, just ask. Note that TestFlight has its own Terms Of Service.
Frictionless org habits on iOS
UPDATE: Flat Habits now has its own page at flathabits.com.
I've been wanting org to keep track of my daily habits for a little while. The catalyst: reading James Clear's wonderful Atomic Habits (along with plenty of lock-down inspiration).
As much as I live in Emacs and org mode, it just wasn't practical enough to rely on my laptop for tracking habits. I wanted less friction, so I've been experimenting with building a toy app for my needs. Naturally, org support was a strict requirement, so I could always poke at it from my beloved editor.
I've been using the app every day with success. The habits seem to be sticking, but equally important, it's been really fun to join the fabulous world of Emacs/Org with iOS/SwiftUI.
This is all very experimental1 and as mentioned on reddit (follow-up here) and twitter, the app isn't available on the App Store. I may consider publishing if there's enough interest, but in the mean time, you can reach out and install via TestFlight.
Send me an email address to flathabits*at*xenodium.com for a TestFlight invite.
2021-03-12 Update: Now with iOS Files app/sync integration
If you can sync your org file with your iPhone (ie. Drive/Dropbox/iCloud), and list it in the Files app, you should be able to open/edit1 with Flat Habits (that's the name now). With iOS Files integration, you should be able to sync your habits between your iPhone and your funky editor powering org mode2.
Symbolicating iOS crashes
export DEVELOPER_DIR=$(xcode-select --print-path) /Applications/Xcode.app/Contents/SharedFrameworks/DVTFoundation.framework/Versions/A/Resources/symbolicatecrash crashlog.crash MyFoo.app.dSYM
Emacs: mu4e icons
Recently spotted mu4e-marker-icons, which adds mu4e icons using all-the-icons.
Although I'm not currently using all-the-icons, it did remind me to take a look at mu4e's built-in variables to spiff up my email. It's pretty simple. Find the icons you like and set them as follows:
(setq mu4e-headers-unread-mark '("u" . "📩 ")) (setq mu4e-headers-draft-mark '("D" . "🚧 ")) (setq mu4e-headers-flagged-mark '("F" . "🚩 ")) (setq mu4e-headers-new-mark '("N" . "✨ ")) (setq mu4e-headers-passed-mark '("P" . "↪ ")) (setq mu4e-headers-replied-mark '("R" . "↩ ")) (setq mu4e-headers-seen-mark '("S" . " ")) (setq mu4e-headers-trashed-mark '("T" . "🗑️")) (setq mu4e-headers-attach-mark '("a" . "📎 ")) (setq mu4e-headers-encrypted-mark '("x" . "🔑 ")) (setq mu4e-headers-signed-mark '("s" . "🖊 "))
Luxembourg travel bookmarks
South Africa travel bookmarks
Swift package code coverage (plus Emacs overlay)
While playing around with Swift package manager, I had a quick look into code coverage options. Luckily, coverage reporting and exporting are supported out of the box (via llvm-cov).
Ensure tests are invoked as follows:
swift test --enable-code-coverage
A high level report can be generated with:
xcrun llvm-cov report .build/x86_64-apple-macosx/debug/FooPackageTests.xctest/Contents/MacOS/FooPackageTests \ -instr-profile=.build/x86_64-apple-macosx/debug/codecov/default.profdata -ignore-filename-regex=".build|Tests"
Filename Regions Missed Regions Cover Functions Missed Functions Executed Lines Missed Lines Cover -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- /tmp/Foo/Sources/Foo/Foo.swift 2 1 50.00% 2 1 50.00% 6 3 50.00% -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- TOTAL 2 1 50.00% 2 1 50.00% 6 3 50.00%
llvm-cov can export as lcov format:
xcrun llvm-cov export -format="lcov" .build/x86_64-apple-macosx/debug/FooPackageTests.xctest/Contents/MacOS/FooPackageTests -instr-profile=.build/x86_64-apple-macosx/debug/codecov/default.profdata -ignore-filename-regex=".build|Tests" > coverage.lcov
With the report in lcov format, we can look for an Emacs package to visualize coverage in source files. Found coverlay.el to require minimal setup. I was interested in highlighting only untested areas, so I set tested-line-background-color to nil:
(use-package coverlay :ensure t :config (setq coverlay:tested-line-background-color nil))
After installing coverlay, I enabled the minor mode via M-x coverlay-minor-mode, invoked M-x coverlay-watch-file to watch coverage.lcov for changes, and voilà!
Hiking bookmarks
Patience
Via Orange Book, a reminder to myself:
- In investing, patience is rewarded.
- In growing a talent, patience is rewarded.
- In building a business, patience is rewarded.
- In love and friendships, patience is rewarded.
- Patience = success
I feel like there's an Emacs lesson somewhere in there…
Chess bookmarks
40 Coolest neighbourhoods in the world
Via TimeOut's 40 Coolest Neighbourhoods in the World Right Now:
- Esquerra de l’Eixample, Barcelona
- Downtown, Los Angeles
- Sham Shui Po, Hong Kong
- Bedford-Stuyvesant, New York
- Yarraville, Melbourne
- Wedding, Berlin
- Shaanxi Bei Lu/Kangding Lu, Shanghai
- Dennistoun, Glasgow
- Haut-Marais, Paris
- Marrickville, Sydney
- Verdun, Montreal
- Kalamaja, Tallinn
- Hannam-dong, Seoul
- Bonfim, Porto
- Ghosttown, Oakland
- Chula-Samyan, Bangkok
- Alvalade, Lisbon
- Noord, Amsterdam
- Centro, São Paulo
- Holešovice, Prague
- Lavapiés, Madrid
- Opebi, Lagos
- Narvarte, Mexico City
- Uptown, Chicago
- Little Five Points, Atlanta
- Wynwood, Miami
- Phibsboro, Dublin
- Nørrebro, Copenhagen
- Bugis, Singapore
- Gongguan, Taipei
- Soho, London
- Binh Thanh, Ho Chi Minh City
- Melville, Johannesburg
- Kabutocho, Tokyo
- Porta Venezia, Milan
- Taman Paramount, Kuala Lumpur
- Allston, Boston
- Bandra West, Mumbai
- Arnavutköy, Istanbul
- Banjar Nagi, Ubud
Emacs: Rotate my macOS display
Every so often, I rotate my monitor (vertical vs horizontal) for either work or to watch a movie. macOS enables changing the display rotation via a dropdown menu (under Preferences > Displays > Rotation) where you can pick between Standard, 90°, 180°, and 270°. That's all fine, but what I'd really like is a quick way to toggle between my preferred two choices: Standard and 270°.
Unsurprisingly, I'd also like to invoke it as an interactive command via Emacs's M-x (see Emacs: connect my Bluetooth speaker). With narrowing frameworks like ivy, helm, and ido, invoking these commands is just a breeze.
Turns out, this was pretty simple to accomplish, thanks to Eric Nitardy's fb-rotate command line utility. All that's left to do is wrap it in a tiny elisp function hack, add the toggling logic, and voilà!
The screen capture goes a little funky when rotating the display, but you get the idea. It works better in person :)
…and here's the snippet:
(defun ar/display-toggle-rotation () (interactive) (require 'cl-lib) (cl-assert (executable-find "fb-rotate") nil "Install fb-rotate from https://github.com/CdLbB/fb-rotate") ;; # Display_ID Resolution ____Display_Bounds____ Rotation ;; 2 0x2b347692 1440x2560 0 0 1440 2560 270 [main] ;; From fb-rotate output, get the `current-rotation' from Column 7, row 1 zero-based. (let ((current-rotation (nth 7 (split-string (nth 1 (process-lines "fb-rotate" "-i")))))) (call-process-shell-command (format "fb-rotate -d 1 -r %s" (if (equal current-rotation "270") "0" "270")))))
Emacs: Clone git repo from clipboard
Cloning git repositories is a pretty common task. For me, it typically goes something like:
- Copy git repo URL from browser.
- Drop to Emacs eshell.
- Change current directory.
- Type "git clone ".
- Paste git repo URL.
- Run git command.
- Change directory to cloned repo.
- Open dired.
No biggie, but why go through the same steps every time? We can do better. We have a hyper malleable editor, so let's get it to grab the URL from clipboard and do its thing.
shell-command or async-shell-command can help in this space, but require additional work: change location, re-type command, what if directory already exists… This is Emacs, so we can craft the exact experience we want. I did take inspiration from shell-command to display the process buffer correctly (git progress, control codes, etc.) and landed on the following experience:
;; -*- lexical-binding: t -*- (defun ar/git-clone-clipboard-url () "Clone git URL in clipboard asynchronously and open in dired when finished." (interactive) (cl-assert (string-match-p "^\\(http\\|https\\|ssh\\)://" (current-kill 0)) nil "No URL in clipboard") (let* ((url (current-kill 0)) (download-dir (expand-file-name "~/Downloads/")) (project-dir (concat (file-name-as-directory download-dir) (file-name-base url))) (default-directory download-dir) (command (format "git clone %s" url)) (buffer (generate-new-buffer (format "*%s*" command))) (proc)) (when (file-exists-p project-dir) (if (y-or-n-p (format "%s exists. delete?" (file-name-base url))) (delete-directory project-dir t) (user-error "Bailed"))) (switch-to-buffer buffer) (setq proc (start-process-shell-command (nth 0 (split-string command)) buffer command)) (with-current-buffer buffer (setq default-directory download-dir) (shell-command-save-pos-or-erase) (require 'shell) (shell-mode) (view-mode +1)) (set-process-sentinel proc (lambda (process state) (let ((output (with-current-buffer (process-buffer process) (buffer-string)))) (kill-buffer (process-buffer process)) (if (= (process-exit-status process) 0) (progn (message "finished: %s" command) (dired project-dir)) (user-error (format "%s\n%s" command output)))))) (set-process-filter proc #'comint-output-filter)))
Updates
- Added lexical binding.
- Checks clipboard for ssh urls also.
Pulled pork recipe
Made pulled pork a couple of times. Freestyled a bit. No expert here, but result was yummie.
Grind/blend spices
- 2 teaspoons smoked paprika
- 2 teaspoons cumin seeds
- 2 teaspoons whole pepper corn mix
- 2 teaspoons chilly flakes
If spices are whole, grind or blend them. Set aside.
Optionally: Substitute 1 teaspoon of paprika with chipotle pepper.
Mix into a paste
- 2 tablespoons honey
- 1 teaspoon of dijon mustard
Mix the honey, mustard, and previous spices into a paste.
Rub the mix in
Rub mix thoroughly into the pork shoulder.
Bake (1 hour)
Place in a pot (lid off) and bake in the oven for 1 hour at 200 °C.
Add liquids
- 1/2 cup of water.
- 4 tablespoons of apple cider vinegar.
Add liquids to pot.
Bake (3-5 hours)
Bake between 3 to 5 hours 150 °C. Check every hour or two. Does the meat fall easily when spread with two forks? If so, you're done.
Pull apart
Use two forks to pull the meat apart.
Zettelkasten bookmarks
Battlestation bookmarks
Emacs: chaining org babel blocks
Recently wanted to chain org babel blocks. That is, aggregate separate source blocks and execute as one combined block.
I wanted the chaining primarily driven through header arguments as follows:
#+name: block-0 #+begin_src swift print("hello 0") #+end_src #+name: block-1 #+begin_src swift :include block-0 print("hello 1") #+end_src #+RESULTS: block-1 : hello 0 : hello 1
I didn't find the above syntax and behaviour supported out of the box (or didn't search hard enough?). Fortunately, this is our beloved and malleable editor, so we can always bend it our way! Wasn't quite sure how to go about it, so I looked at other babel packages for inspiration. ob-async was great for that.
Turns out, advicing org-babel-execute-src-block did the job:
(defun adviced:org-babel-execute-src-block (&optional orig-fun arg info params) (let ((body (nth 1 info)) (include (assoc :include (nth 2 info))) (named-blocks (org-element-map (org-element-parse-buffer) 'src-block (lambda (item) (when (org-element-property :name item) (cons (org-element-property :name item) item)))))) (while include (unless (cdr include) (user-error ":include without value" (cdr include))) (unless (assoc (cdr include) named-blocks) (user-error "source block \"%s\" not found" (cdr include))) (setq body (concat (org-element-property :value (cdr (assoc (cdr include) named-blocks))) body)) (setf (nth 1 info) body) (setq include (assoc :include (org-babel-parse-header-arguments (org-element-property :parameters (cdr (assoc (cdr include) named-blocks))))))) (funcall orig-fun arg info params))) (advice-add 'org-babel-execute-src-block :around 'adviced:org-babel-execute-src-block)
Before I built my own support, I did find that noweb got me most of what I needed, but required sprinkling blocks with placeholder references.
Combining :noweb and :prologue would have been a great match, if only prologue did expand the noweb reference. I'm sure there's an alternative I'm missing. Either way, it was fun to poke at babel blocks and build my own chaining support.
Emacs: quote wrap all in region
As I find myself moving more shell commands into Emacs interactive commands to create a Swift package/project, enrich dired's featureset, or search/play Music (macOS), I often need to take a single space-separated string, make an elisp list of strings, and feed it to functions like process-lines. No biggie, but I thought it'd be a fun little function to write: take the region and wrap all items in quotes. As a bonus, made it toggable.
(defun ar/toggle-quote-wrap-all-in-region (beg end) "Toggle wrapping all items in region with double quotes." (interactive (list (mark) (point))) (unless (region-active-p) (user-error "no region to wrap")) (let ((deactivate-mark nil) (replacement (string-join (mapcar (lambda (item) (if (string-match-p "^\".*\"$" item) (string-trim item "\"" "\"") (format "\"%s\"" item))) (split-string (buffer-substring beg end))) " "))) (delete-region beg end) (insert replacement)))
Emacs: org block complete and edit
I quickly got used to Emacs org block company completion. I did, however, almost always found myself running org-edit-special immediately after inserting completion. I use C-c ' for that. That's all fine, but it just felt redundant.
Why not automatically edit the source block in corresponding major mode after completion? I think I can also get used to that!
Or maybe the automatic approach is too eager? There's also a middle ground: ask immediately after.
Or maybe I don't want either in the end? Time will tell, but I now have all three options available:
(defcustom company-org-block-edit-mode 'auto "Customize whether edit mode, post completion was inserted." :type '(choice (const :tag "nil: no edit after insertion" nil) (const :tag "prompt: ask before edit" prompt) (const :tag "auto edit, no prompt" auto)))
The new option is now in the company-org-block snippet with my latest config.
Emacs: create a Swift package/project
Been playing around with Swift Package Manager (SPM). Creating a new Swift package (ie. project) is pretty simple.
To create a library package, we can use the following:
swift package init --type library
Alternatively, to create a command-line utility use:
swift package init --type executable
Turns out, there are a few options: empty, library, executable, system-module, manifest.
With a little elisp, we can write a completing function to quickly generate a Swift package/project without the need to drop to the shell.
Bonus: I won't have to look up SPM options if I ever forget them.
(defun ar/swift-package-init () "Execute `swift package init', with optional name and completing type." (interactive) (let* ((name (read-string "name (default): ")) (type (completing-read "project type: " ;; Splits "--type empty|library|executable|system-module|manifest" (split-string (nth 1 (split-string (string-trim (seq-find (lambda (line) (string-match "--type" line)) (process-lines "swift" "package" "init" "--help"))) " ")) "|"))) (command (format "swift package init --type %s" type))) (unless (string-empty-p name) (append command "--name " name)) (shell-command command)) (dired default-directory) (revert-buffer))
Improved Ctrl-p/Ctrl-n macOS movement
macOS supports many Emacs bindings (out of the box). You can, for example, press C-p and C-n to move the cursor up and down (whether editing text in Emacs or any other macOS app). Jacob Rus's Customizing the Cocoa Text System offers a more in-depth picture and also shows how to customize global macOS keybindings (via DefaultKeyBinding.dict).
In addition to moving Emacs point (cursor) up/down using C-p/C-n, I've internalized the same bindings to select an option from a list. Good Emacs examples of these are company mode and ivy.
Vertical cursor movement using Emacs bindings works well in most macOS apps, including forms and text boxes in web pages. However, selecting from a completion list doesn't quite work as expected. Although the binding is technically handled, it moves the cursor within the text widget, ignoring the suggested choices.
Atif Afzal's Use emacs key bindings everywhere has a solution for the ignored case. He uses Karabiner Elements to remap c-p and c-n to arrow-up and arrow-down.
It's been roughly a week since I started using the Karabiner remapping, and I've yet to find a case where a web page (or any other macOS app) did not respond to c-p and c-n to move selection from a list.
My ~/.config/karabiner/karabiner.json configuration is as follows:
{ "global": { "check_for_updates_on_startup": true, "show_in_menu_bar": true, "show_profile_name_in_menu_bar": false }, "profiles": [ { "complex_modifications": { "parameters": { "basic.simultaneous_threshold_milliseconds": 50, "basic.to_delayed_action_delay_milliseconds": 500, "basic.to_if_alone_timeout_milliseconds": 1000, "basic.to_if_held_down_threshold_milliseconds": 500, "mouse_motion_to_scroll.speed": 100 }, "rules": [ { "description": "Ctrl+p/Ctrl+n to arrow up/down", "manipulators": [ { "from": { "key_code": "p", "modifiers": { "mandatory": [ "control" ] } }, "to": [ { "key_code": "up_arrow" } ], "conditions": [ { "type": "frontmost_application_unless", "bundle_identifiers": [ "^org\\.gnu\\.Emacs" ] } ], "type": "basic" }, { "from": { "key_code": "n", "modifiers": { "mandatory": [ "control" ] } }, "to": [ { "key_code": "down_arrow" } ], "conditions": [ { "type": "frontmost_application_unless", "bundle_identifiers": [ "^org\\.gnu\\.Emacs" ] } ], "type": "basic" } ] } ] }, "devices": [], "fn_function_keys": [], "name": "Default profile", "parameters": { "delay_milliseconds_before_open_device": 1000 }, "selected": true, "simple_modifications": [], "virtual_hid_keyboard": { "country_code": 0, "mouse_key_xy_scale": 100 } } ] }
Bonus (C-g to exit)
Pressing Esc often dismisses or cancels macOS windows, menus, etc. This is also the case for web pages. As an Emacs user, I'm pretty used to pressing C-g to cancel, quit, or exit things. With that in mind, mapping C-g to Esc is surprisingly useful outside of Emacs. Here's the relevant Karabiner C-g binding for that:
{ "description": "Ctrl+G to Escape", "manipulators": [ { "description": "emacs like escape", "from": { "key_code": "g", "modifiers": { "mandatory": [ "left_control" ] } }, "to": [ { "key_code": "escape" } ], "conditions": [ { "type": "frontmost_application_unless", "bundle_identifiers": [ "^org\\.gnu\\.Emacs" ] } ], "conditions": [ { "type": "frontmost_application_unless", "bundle_identifiers": [ "^org\\.gnu\\.Emacs" ] } ], "type": "basic" } ] }
UPDATE: Ensure bindings are only active when Emacs is not active.
Basmati rice pudding recipe
Combine in a pot
- 2/3 cup of basmati rice
- 400 ml of coconut milk
- 4 cups of milk [1]
- 3 tablespoons of honey [2]
- 1/4 teaspoon of crushed cardamom seeds [3]
- 1/8 teaspoon of salt
Simple. Combine all ingredients in a pot.
[1] Been using powder milk since lockdown, end-result's been tasty. [2] Only tried raw honey so far. [3] Can likely use ground cardamom. I enjoy the scents while crushing.
Boil and simmer
Bring ingredients to a boil and simmer at low heat for 45 minutes. Stir occasionally.
Mix in butter
- 1 tablespoon of butter.
Turn stove off, add a tablespoon of butter, and mix in.
Serve warm or cold
After mixing in the butter, the rice pudding is done. You can serve warm or cold.
Garnish (optional)
- Pistachios
- Cinnamon
Optionally garnish with either pistachios or cinnamon (or both).
Adding images to pdfs (macOS)
The macOS Preview app does a great job inserting signatures to existing pdfs. I was hoping it could overlay images just as easily. Doesn't look like it's possible, without exporting/reimporting to image formats and losing pdf structure. Did I miss something?
In any case, I found formulatepro. Dormant at Google Code Archive, but also checked in to github. With a tiny patch, it builds and runs on Catalina. One can easily insert an image via "File > Place Image…".
DIY bookmarks
Skiing bookmarks
Emacs: search/play Music (macOS)
While trying out macOS's Music app to manage offline media, I wondered if I could easily search and control playback from Emacs. Spoiler alert: yes it can be done and fuzzy searching music is rather gratifying.
Luckily, the hard work's already handled by pytunes, a command line interface to macOS's iTunes/Music app. We add ffprobe and some elisp glue to the mix, and we can generate an Emacs media index.
Indexing takes roughly a minute per 1000 files. Prolly suboptimal, but I don't intend to re-index frequently. For now, we can use a separate process to prevent Emacs from blocking, so we can get back to playing tetris from our beloved editor:
(defun musica-index () "Indexes Music's tracks in two stages: 1. Generates \"Tracks.sqlite\" using pytunes (needs https://github.com/hile/pytunes installed). 2. Caches an index at ~/.emacs.d/.musica.el." (interactive) (message "Indexing music... started") (let* ((now (current-time)) (name "Music indexing") (buffer (get-buffer-create (format "*%s*" name)))) (with-current-buffer buffer (delete-region (point-min) (point-max))) (set-process-sentinel (start-process name buffer (file-truename (expand-file-name invocation-name invocation-directory)) "--quick" "--batch" "--eval" (prin1-to-string `(progn (interactive) (require 'cl-lib) (require 'seq) (require 'map) (message "Generating Tracks.sqlite...") (process-lines "pytunes" "update-index") ;; Generates Tracks.sqlite (message "Generating Tracks.sqlite... done") (defun parse-tags (path) (with-temp-buffer (if (eq 0 (call-process "ffprobe" nil t nil "-v" "quiet" "-print_format" "json" "-show_format" path)) (map-elt (json-parse-string (buffer-string) :object-type 'alist) 'format) (message "Warning: Couldn't read track metadata for %s" path) (message "%s" (buffer-string)) (list (cons 'filename path))))) (let* ((paths (process-lines "sqlite3" (concat (expand-file-name "~/") "Music/Music/Music Library.musiclibrary/Tracks.sqlite") "select path from tracks")) (total (length paths)) (n 0) (records (seq-map (lambda (path) (let ((tags (parse-tags path))) (message "%d/%d %s" (setq n (1+ n)) total (or (map-elt (map-elt tags 'tags) 'title) "No title")) tags)) paths))) (with-temp-buffer (prin1 records (current-buffer)) (write-file "~/.emacs.d/.musica.el" nil)))))) (lambda (process state) (if (= (process-exit-status process) 0) (message "Indexing music... finished (%.3fs)" (float-time (time-subtract (current-time) now))) (message "Indexing music... failed, see %s" buffer))))))
Once media is indexed, we can feed it to ivy for that narrowing-down fuzzy-searching goodness! It's worth mentioning the truncate-string-to-width function. Super handy for truncating strings to a fixed width and visually organizing search results in columns.
(defun musica-search () (interactive) (cl-assert (executable-find "pytunes") nil "pytunes not installed") (let* ((c1-width (round (* (- (window-width) 9) 0.4))) (c2-width (round (* (- (window-width) 9) 0.3))) (c3-width (- (window-width) 9 c1-width c2-width))) (ivy-read "Play: " (mapcar (lambda (track) (let-alist track (cons (format "%s %s %s" (truncate-string-to-width (or .tags.title (file-name-base .filename) "No title") c1-width nil ?\s "…") (truncate-string-to-width (propertize (or .tags.artist "") 'face '(:foreground "yellow")) c2-width nil ?\s "…") (truncate-string-to-width (propertize (or .tags.album "") 'face '(:foreground "cyan1")) c3-width nil ?\s "…")) track))) (musica--index)) :action (lambda (selection) (let-alist (cdr selection) (process-lines "pytunes" "play" .filename) (message "Playing: %s [%s] %s" (or .tags.title (file-name-base .filename) "No title") (or .tags.artist "No artist") (or .tags.album "No album"))))))) (defun musica--index () (with-temp-buffer (insert-file-contents "~/.emacs.d/.musica.el") (read (current-buffer))))
The remaining bits are straigtforward. We add a few interactive functions to control playback:
(defun musica-info () (interactive) (let ((raw (process-lines "pytunes" "info"))) (message "%s [%s] %s" (string-trim (string-remove-prefix "Title" (nth 3 raw))) (string-trim (string-remove-prefix "Artist" (nth 1 raw))) (string-trim (string-remove-prefix "Album" (nth 2 raw)))))) (defun musica-play-pause () (interactive) (cl-assert (executable-find "pytunes") nil "pytunes not installed") (process-lines "pytunes" "play") (musica-info)) (defun musica-play-next () (interactive) (cl-assert (executable-find "pytunes") nil "pytunes not installed") (process-lines "pytunes" "next")) (defun musica-play-next-random () (interactive) (cl-assert (executable-find "pytunes") nil "pytunes not installed") (process-lines "pytunes" "shuffle" "enable") (let-alist (seq-random-elt (musica--index)) (process-lines "pytunes" "play" .filename)) (musica-info)) (defun musica-play-previous () (interactive) (cl-assert (executable-find "pytunes") nil "pytunes not installed") (process-lines "pytunes" "previous"))
Finally, if we want some convenient keybindings, we can add something like:
(global-set-key (kbd "C-c m SPC") #'musica-play-pause) (global-set-key (kbd "C-c m i") #'musica-info) (global-set-key (kbd "C-c m n") #'musica-play-next) (global-set-key (kbd "C-c m p") #'musica-play-previous) (global-set-key (kbd "C-c m r") #'musica-play-next-random) (global-set-key (kbd "C-c m s") #'musica-search)
Hooray! Controlling music is now an Emacs keybinding away: \o/
comments on twitter.
UPDATE1: Installing pytunes with pip3 install pytunes didn't just work for me. Instead, I cloned and installed as:
git clone https://github.com/hile/pytunes pip3 install file:///path/to/pytunes pip3 install pytz brew install libmagic
UPDATE2: Checked in to dot files.
Cheese cake recipe (no crust)
Preheat oven
Preheat oven at 175°C.
Ingredients at room temperature
Ensure the cream cheese, sour cream, and eggs are at room temperature before starting.
Mix cream cheese
- 900g of cream cheese
Mix the cream cheese thoroughly.
Mix sugar
- 240g of sugar
Add half the sugar. Mix in thoroughly. Add second half and mix.
Mix sour cream, corn flour, and vanilla.
- 100g sour cream
- 40g corn flour
- 1tbsp vanilla bean paste
Add the three ingredients and mix well.
Mix eggs
- 3 eggs
- 1 egg yolk
Add the eggs and mix for 30 seconds.
Mix by hand
Finish mixing thoroughly by hand, using a wooden spoon.
Prepare pan
- Springform pan
- Parchment paper
A springform pan works best here. Wrap its plate with parchment paper and lock it in place.
Pour mix
- Strainer
Pour the mix through a strainer and into the prepared pan.
Rest mix
Let the mix rest in the pan for 10 minutes to let air bubbles out.
Bake
Bake for an 1 hour 10 minutes. Maybe add another 10 minutes (or more) if surface is still pale. Turn the oven off, leave door half open, and let it sit for 20 minutes.
Cool off
Take out and let it cool off to room temperature.
Refrigerate
Refrigerate for 4 hours (or overnight) before removing the sides of the pan.
Eat!
Nom nom. Yum yum.
Bonus (topping)
I winged this one and it worked out well. Heated up frozen berries with some honey and used it as topping. The whole combo was pretty tasty.
Faster macOS dock auto-hide
Via Marcin Swieczkowski's Upgrading The OSX Dock, change default to make macOS's dock auto-hide faster:
defaults write com.apple.dock autohide-time-modifier -float 0.2; killall Dock
Smarter Swift snippets
Jari Safi published a wonderful Emacs video demoing python yasnippets in action. The constructor snippet, automatically setting ivars, is just magical. I wanted it for Swift!
I took a look at the __init__ snippet from Jorgen Schäfer's elpy. It uses elpy-snippet-init-assignments to generate the assignments.
With small tweaks, we can get the same action going on for Swift \o/
init.yasnippet:
# -*- mode: snippet -*- # name: init with assignments # key: init # -- init(${1:, args}) { ${1:$(swift-snippet-init-assignments yas-text)} } $0
.yas-setup.el:
(defun swift-snippet-init-assignments (arg-string) (let ((indentation (make-string (save-excursion (goto-char start-point) (current-indentation)) ?\s))) (string-trim (mapconcat (lambda (arg) (if (string-match "^\\*" arg) "" (format "self.%s = %s\n%s" arg arg indentation))) (swift-snippet-split-args arg-string) "")))) (defun swift-snippet-split-args (arg-string) (mapcar (lambda (x) (if (and x (string-match "\\([[:alnum:]]*\\):" x)) (match-string-no-properties 1 x) x)) (split-string arg-string "[[:blank:]]*,[[:blank:]]*" t)))
Swift package manager build for iOS
While playing around with Swift package manager, it wasn't immediately obvious how to build for iOS from the command line. The default behaviour of invoking swift build is to build for the host. In my case, macOS. In any case, this was it:
swift build -Xswiftc "-sdk" -Xswiftc "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk" -Xswiftc "-target" -Xswiftc "x86_64-apple-ios13.0-simulator"
ps. Can get the SDK path with:
xcrun --sdk iphonesimulator --show-sdk-path
QR code bookmarks
Trying out gccemacs on macOS
UPDATE: I'm no longer using these steps. See Emacs plus –with-native-comp for an easier alternative.
Below are the instructions I use to build Andrea Corallo's gccemacs on macOS. It is based on Allen Dang's handy instructions plus some changes of my own.
Install gcc and libgccjit via homebrew
brew install gcc libgccjit
Save configure script
Create configure-gccemacs.sh
#!/bin/bash set -o nounset set -o errexit # Configures Emacs for building native comp support # http://akrl.sdf.org/gccemacs.html readonly GCC_DIR="$(realpath $(brew --prefix libgccjit))" [[ -d $GCC_DIR ]] || { echo "${GCC_DIR} not found"; exit 1; } readonly SED_DIR="$(realpath $(brew --prefix gnu-sed))" [[ -d $SED_DIR ]] || { echo "${SED_DIR} not found"; exit 1; } readonly GCC_INCLUDE_DIR=${GCC_DIR}/include [[ -d $GCC_INCLUDE_DIR ]] || { echo "${GCC_INCLUDE_DIR} not found"; exit 1; } readonly GCC_LIB_DIR=${GCC_DIR}/lib/gcc/10 [[ -d $GCC_LIB_DIR ]] || { echo "${GCC_LIB_DIR} not found"; exit 1; } export PATH="${SED_DIR}/libexec/gnubin:${PATH}" export CFLAGS="-O2 -I${GCC_INCLUDE_DIR}" export LDFLAGS="-L${GCC_LIB_DIR} -I${GCC_INCLUDE_DIR}" export LD_LIBRARY_PATH="${GCC_LIB_DIR}" export DYLD_FALLBACK_LIBRARY_PATH="${GCC_LIB_DIR}" echo "Environment" echo "-----------" echo PATH: $PATH echo CFLAGS: $CFLAGS echo LDFLAGS: $LDFLAGS echo DYLD_FALLBACK_LIBRARY_PATH: $DYLD_FALLBACK_LIBRARY_PATH echo "-----------" ./autogen.sh ./configure \ --prefix="$PWD/nextstep/Emacs.app/Contents/MacOS" \ --enable-locallisppath="${PWD}/nextstep/Emacs.app/Contents/MacOS" \ --with-mailutils \ --with-ns \ --with-imagemagick \ --with-cairo \ --with-modules \ --with-xml2 \ --with-gnutls \ --with-json \ --with-rsvg \ --with-native-compilation \ --disable-silent-rules \ --disable-ns-self-contained \ --without-dbus
Make it executable
chmod +x configure-gccemacs.sh
Clone Emacs source
git clone --branch master https://github.com/emacs-mirror/emacs gccemacs
Configure build
cd gccemacs
../configure-gccemacs.sh
Native lisp compiler found?
Verify native lisp compiler is found:
Does Emacs have native lisp compiler? yes
Build
Put those cores to use. Find out how many you got with:
sysctl hw.logicalcpu
hw.logicalcpu: 4
Ok so build with:
make -j4 NATIVE_FAST_BOOT=1
cp -r lisp nextstep/Emacs.app/Contents/Resources/
cp -r native-lisp nextstep/Emacs.app/Contents
make install
Note: Using NATIVE_FAST_BOOT=1 significantly improves build time (totalling between 20-30 mins, depending on your specs). Without it, the build can take hours.
The macOS app build (under nextstep/Emacs.app) is ready, but read on before launching.
Remove ~/emacs.d
You likely want to start with a clean install, byte-compiling all packages with the latest Emacs version. In any case, rename ~/emacs.d (for backup?) or remove ~/emacs.d.
init.el config
Ensure exec-path includes the script's "–prefix=" value, LIBRARY_PATH points to gcc's lib dir, and finally set comp-deferred-compilation. I wrapped the snippet in my exec-path-from-shell config, but setting early in init.el should be enough.
(use-package exec-path-from-shell :ensure t :config (exec-path-from-shell-initialize) (if (and (fboundp 'native-comp-available-p) (native-comp-available-p)) (progn (message "Native comp is available") ;; Using Emacs.app/Contents/MacOS/bin since it was compiled with ;; ./configure --prefix="$PWD/nextstep/Emacs.app/Contents/MacOS" (add-to-list 'exec-path (concat invocation-directory "bin") t) (setenv "LIBRARY_PATH" (concat (getenv "LIBRARY_PATH") (when (getenv "LIBRARY_PATH") ":") ;; This is where Homebrew puts gcc libraries. (car (file-expand-wildcards (expand-file-name "~/homebrew/opt/gcc/lib/gcc/*"))))) ;; Only set after LIBRARY_PATH can find gcc libraries. (setq comp-deferred-compilation t)) (message "Native comp is *not* available")))
Launch Emacs.app
You're good to go. Open Emacs.app via finder or shell:
open nextstep/Emacs.app
Deferred compilation logs
After setting comp-deferred-compilation (in init.el config section), .elc files should be asyncronously compiled. Function definition should be updated to native compiled equivalent.
Look out for an Async-native-compile-log buffer. Should have content like:
Compiling .emacs.d/elpa/moody-20200514.1946/moody.el... Compiling .emacs.d/elpa/minions-20200522.1052/minions.el... Compiling .emacs.d/elpa/persistent-scratch-20190922.1046/persistent-scratch.el... Compiling .emacs.d/elpa/which-key-20200721.1927/which-key.el... ...
Can also check for .eln files:
find ~/.emacs.d -iname *.eln | wc -l
149
UPDATE1: Added Symlink Emacs.app/Contents/eln-cache section for update 11.
UPDATE2: Noted using NATIVE_FAST_BOOT makes the build much faster.
UPDATE3: Removed symlinks and copied content instead. This simplifies things. Inspired by Ian Wahbe's build-emacs.sh.
UPDATE4: Removed homebrew recipe patching. Thanks to Dmitry Shishkin's instructions.
UPDATE5: Use new flag –with-native-compilation and master branch.
SwiftUI macOS desk clock
For time display, I've gone back and forth between an always-displayed macOS's menu bar to an auto-hide menu bar, and letting Emacs display the time. Neither felt great nor settled.
With some tweaks, Paul Hudson's How to use a timer with SwiftUI, led me to build a simple desk clock. Ok, let's not get fancy. It's really just an always-on-top floating window, showing a swiftUI label, but hey I like the minimalist feel ;)
Let's see if it sticks around or it gets in the way… Either way, here's standalone snippet. Run with swift deskclock.swift.
import Cocoa import SwiftUI let application = NSApplication.shared let appDelegate = AppDelegate() NSApp.setActivationPolicy(.regular) application.delegate = appDelegate application.mainMenu = NSMenu.makeMenu() application.run() struct ClockView: View { @State var time = "--:--" let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { GeometryReader { geometry in VStack { Text(time) .onReceive(timer) { input in let formatter = DateFormatter() formatter.dateFormat = "HH:mm" time = formatter.string(from: input) } .font(.system(size: 40)) .padding() }.frame(width: geometry.size.width, height: geometry.size.height) .background(Color.black) .cornerRadius(10) .frame(maxWidth: .infinity, maxHeight: .infinity) } } } extension NSWindow { static func makeWindow() -> NSWindow { let window = NSWindow( contentRect: NSRect.makeDefault(), styleMask: [.closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) window.level = .floating window.setFrameAutosaveName("everclock") window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle, .fullScreenPrimary] window.makeKeyAndOrderFront(nil) window.isMovableByWindowBackground = true window.titleVisibility = .hidden window.backgroundColor = .clear return window } } class AppDelegate: NSObject, NSApplicationDelegate { var window = NSWindow.makeWindow() var hostingView: NSView? func applicationDidFinishLaunching(_ notification: Notification) { hostingView = NSHostingView(rootView: ClockView()) window.contentView = hostingView NSApp.activate(ignoringOtherApps: true) } } extension NSRect { static func makeDefault() -> NSRect { let initialMargin = CGFloat(60) let fallback = NSRect(x: 0, y: 0, width: 100, height: 150) guard let screenFrame = NSScreen.main?.frame else { return fallback } return NSRect( x: screenFrame.maxX - fallback.width - initialMargin, y: screenFrame.maxY - fallback.height - initialMargin, width: fallback.width, height: fallback.height) } } extension NSMenu { static func makeMenu() -> NSMenu { let appMenu = NSMenuItem() appMenu.submenu = NSMenu() appMenu.submenu?.addItem( NSMenuItem( title: "Quit \(ProcessInfo.processInfo.processName)", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q" )) let mainMenu = NSMenu(title: "Main Menu") mainMenu.addItem(appMenu) return mainMenu } }
ffmpeg bookmarks
Black lives matter (BLM) bookmarks
Dogs bookmarks
Emacs, search hackingwithswift.com
Paul Hudson authors excellent Swift material at hackingwithswift.com. I regularly land on the site while searching for snippets from the browser. I was wondering if I could search for snippets directly from Emacs.
Turns out, hackingwithswift uses a JSON HTTP request for querying code examples. With this in mind, we can use ivy-read like Oleh Krehel's counsel-search and search for Swift snippets from our favorite editor:
(require 'request) (require 'json) (defun ar/counsel-hacking-with-swift-search () "Ivy interface to query hackingwithswift.com." (interactive) (ivy-read "hacking with swift: " (lambda (input) (or (ivy-more-chars) (let ((request-curl-options (list "-H" (string-trim (url-http-user-agent-string))))) (request "https://www.hackingwithswift.com/example-code/search" :type "GET" :params (list (cons "search" input)) :parser 'json-read :success (cl-function (lambda (&key data &allow-other-keys) (ivy-update-candidates (mapcar (lambda (item) (let-alist item (propertize .title 'url .url))) data))))) 0))) :action (lambda (selection) (browse-url (concat "https://www.hackingwithswift.com" (get-text-property 0 'url selection)))) :dynamic-collection t :caller 'ar/counsel-hacking-with-swift-search))
Preview SwiftUI layouts using Emacs org blocks
✨ UPDATE: The snippets in this post are outdated. See ob-swiftui for better SwiftUI babel support. ✨
Chris Eidhof twitted a handy snippet that enables quickly bootstrapping throwaway SwiftUI code. It can be easily integrated into other tools for rapid experimentation.
Being a SwiftUI noob, I could use some SwiftUI integration with my editor of choice. With some elisp glue and a small patch, Chris's snippet can be used to generate SwiftUI inline previews using Emacs org babel. This is particularly handy for playing around with SwiftUI layouts.
We can piggyback ride off zweifisch's ob-swift by advicing org-babel-execute:swift to inject the org source block into the bootstrapping snippet. We also add a hook to org-babel-after-execute-hook to automatically refresh the inline preview.
If you're a use-package user, the following snippet should make things fairly self-contained (if you have melpa set up already).
(use-package org :hook ((org-mode . org-display-inline-images)) :config (use-package ob :config (use-package ob-swift :ensure t :config (org-babel-do-load-languages 'org-babel-load-languages (append org-babel-load-languages '((swift . t)))) (defun ar/org-refresh-inline-images () (when org-inline-image-overlays (org-redisplay-inline-images))) ;; Automatically refresh inline images. (add-hook 'org-babel-after-execute-hook 'ar/org-refresh-inline-images) (defun adviced:org-babel-execute:swift (f &rest args) "Advice `adviced:org-babel-execute:swift' enabling swiftui header param." (let* ((body (nth 0 args)) (params (nth 1 args)) (swiftui (cdr (assoc :swiftui params))) (output)) (when swiftui (assert (or (string-equal swiftui "preview") (string-equal swiftui "interactive")) nil ":swiftui must be either preview or interactive") (setq body (format " import Cocoa import SwiftUI import Foundation let screenshotURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString + \".png\") let preview = %s NSApplication.shared.run { %s } extension NSApplication { public func run<V: View>(@ViewBuilder view: () -> V) { let appDelegate = AppDelegate(view()) NSApp.setActivationPolicy(.regular) mainMenu = customMenu delegate = appDelegate run() } } extension NSApplication { var customMenu: NSMenu { let appMenu = NSMenuItem() appMenu.submenu = NSMenu() let quitItem = NSMenuItem( title: \"Quit \(ProcessInfo.processInfo.processName)\", action: #selector(NSApplication.terminate(_:)), keyEquivalent: \"q\") quitItem.keyEquivalentModifierMask = [] appMenu.submenu?.addItem(quitItem) let mainMenu = NSMenu(title: \"Main Menu\") mainMenu.addItem(appMenu) return mainMenu } } class AppDelegate<V: View>: NSObject, NSApplicationDelegate, NSWindowDelegate { var window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 414 * 0.2, height: 896 * 0.2), styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], backing: .buffered, defer: false) var contentView: V init(_ contentView: V) { self.contentView = contentView } func applicationDidFinishLaunching(_ notification: Notification) { window.delegate = self window.center() window.contentView = NSHostingView(rootView: contentView) window.makeKeyAndOrderFront(nil) if preview { screenshot(view: window.contentView!, saveTo: screenshotURL) // Write path (without newline) so org babel can parse it. print(screenshotURL.path, terminator: \"\") NSApplication.shared.terminate(self) return } window.setFrameAutosaveName(\"Main Window\") NSApp.activate(ignoringOtherApps: true) } } func screenshot(view: NSView, saveTo fileURL: URL) { let rep = view.bitmapImageRepForCachingDisplay(in: view.bounds)! view.cacheDisplay(in: view.bounds, to: rep) let pngData = rep.representation(using: .png, properties: [:]) try! pngData?.write(to: fileURL) } " (if (string-equal swiftui "preview") "true" "false") body)) (setq args (list body params))) (setq output (apply f args)) (when org-inline-image-overlays (org-redisplay-inline-images)) output)) (advice-add #'org-babel-execute:swift :around #'adviced:org-babel-execute:swift))))
Snippet also at github gist and included in my emacs config.
UPDATE: See ob-swiftui for a better version of babel SwiftUI support.
Once the snippet is evaluated, we're ready to use in an org babel block. We introduced the :swiftui header param to switch between inline static preview and interactive mode.
To try out an inline preview, create a new org file (eg. swiftui.org) and a source block like:
#+begin_src swift :results file :swiftui preview VStack(spacing: 10) { HStack(spacing: 10) { Rectangle().fill(Color.yellow) Rectangle().fill(Color.green) } Rectangle().fill(Color.blue) HStack(spacing: 10) { Rectangle().fill(Color.green) Rectangle().fill(Color.yellow) } } .frame(maxWidth: .infinity, maxHeight: .infinity) #+end_src
#+results:
Place the cursor anywhere inside the source block (#+begin_src/#+end_src) and press C-c C-c (or M-x org-ctrl-c-ctrl-c).
To run interactively, change the :swiftui param to interactive and press C-c C-c (or M-x org-ctrl-c-ctrl-c). When running interactively, press "q" (without ⌘) to quit the Swift app.
comments on twitter.
Update
- Tweaked the snippet to make it more self-contained and made the steps more reproducible. Need to work out how to package things to make them more accessible. May be best to contribute as a patch to ob-swift and we can avoid the icky advice-add.
- Thanks to Chris Eidhof for PNG support (instead of TIFF). Also TIL Swift's print has got a terminator param.
Open Emacs elfeed links in the background
Christopher Wellons's elfeed is a wonderful Emacs rss reader. In Mike Zamansky's Using Emacs 72 - Customizing Elfeed video, he highlights a desire to open elfeed entries in the background. That is, open the current rss entry (or selected entries) without shifting focus from Emacs to your browser. This behaviour is somewhat analogous to ⌘-clicking/ctrl-clicking on multiple links in the browser without losing focus.
I've been wanting elfeed to open links in the background for some time. Zamansky's post was a great nudge to look into it. He points to the relevant elfeed-search-browse-url function, re-implemented to suit his needs. In a similar spirit, I wrote a function to open the current rss entry (or selected entries) in the background.
I'm running macOS, so I took a look at browse-url-default-macosx-browser to get an idea of how URLs are opened. Simple. It let's macOS handle it via the "open" command, invoked through start process. Looking at open's command-line options, we find –background which "does not bring the application to the foreground."
open --background http://xenodium.com
"b" is already bound to elfeed-search-browse-url, so in our snippet we'll bind "B" to our new background function, giving us some flexibility:
(use-package elfeed :ensure t :bind (:map elfeed-search-mode-map ("B" . ar/elfeed-search-browse-background-url)) :config (defun ar/elfeed-search-browse-background-url () "Open current `elfeed' entry (or region entries) in browser without losing focus." (interactive) (let ((entries (elfeed-search-selected))) (mapc (lambda (entry) (assert (memq system-type '(darwin)) t "open command is macOS only") (start-process (concat "open " (elfeed-entry-link entry)) nil "open" "--background" (elfeed-entry-link entry)) (elfeed-untag entry 'unread) (elfeed-search-update-entry entry)) entries) (unless (or elfeed-search-remain-on-entry (use-region-p)) (forward-line)))))
Maybe xdg-open does a similar thing on linux (I've not looked). Ping me if you have a linux solution and I can update the function.
Happy Emacsing.
ps. I noticed elfeed uses browse-url-generic if elfeed-search-browse-url's is invoked with a prefix. Setting browse-url-generic-program and browse-url-generic-args to use background options may be a more generic solution. For now, a custom function does the job.
comments on twitter.
Enrich Emacs dired's batching toolbox
Shell one-liners are super handy for batch-processing files. Say you'd like to convert a bunch of images from HEIC to jpg, you could use something like:
for f in *.HEIC ; do convert "$f" "${f%.*}.jpg"; done
Save the one-liner (or memorize it) and pull it from your toolbox next time you need it. This is handy as it is, but Emacs dired is just a file-management powerhouse. Its dired-map-over-marks function is just a few elisp lines away from enabling all sorts of batch processing within your dired buffers.
Dired already enables selecting and deselecting files using all sorts of built-in mechanisms (dired-mark-files-regexp, find-name-dired, etc) or wonderful third-party packages like Matus Goljer's dired-filters.
Regardless of how you selected your files, here's a snippet to run ImageMagick's convert on a bunch of selected files:
;;; -*- lexical-binding: t; -*- (defun ar/dired-convert-image (&optional arg) "Convert image files to other formats." (interactive "P") (assert (or (executable-find "convert") (executable-find "magick.exe")) nil "Install imagemagick") (let* ((dst-fpath) (src-fpath) (src-ext) (last-ext) (dst-ext)) (mapc (lambda (fpath) (setq src-fpath fpath) (setq src-ext (downcase (file-name-extension src-fpath))) (when (or (null dst-ext) (not (string-equal dst-ext last-ext))) (setq dst-ext (completing-read "to format: " (seq-remove (lambda (format) (string-equal format src-ext)) '("jpg" "png"))))) (setq last-ext dst-ext) (setq dst-fpath (format "%s.%s" (file-name-sans-extension src-fpath) dst-ext)) (message "convert %s to %s ..." (file-name-nondirectory dst-fpath) dst-ext) (set-process-sentinel (if (string-equal system-type "windows-nt") (start-process "convert" (generate-new-buffer (format "*convert %s*" (file-name-nondirectory src-fpath))) "magick.exe" "convert" src-fpath dst-fpath) (start-process "convert" (generate-new-buffer (format "*convert %s*" (file-name-nondirectory src-fpath))) "convert" src-fpath dst-fpath)) (lambda (process state) (if (= (process-exit-status process) 0) (message "convert %s ✔" (file-name-nondirectory dst-fpath)) (message "convert %s ❌" (file-name-nondirectory dst-fpath)) (message (with-current-buffer (process-buffer process) (buffer-string)))) (kill-buffer (process-buffer process))))) (dired-map-over-marks (dired-get-filename) arg))))
The snippet can be shorter, but wouldn't be as friendly. We ask users to provide desired image format, spawn separate processes (avoids blocking Emacs), and generate a basic report. Also adds support for Windows.
BEWARE
The snippet isn't currently capping the number of processes, but hey we can revise in the future…
Update
Thanks to Philippe Beliveau for pointing out a bug in snippet (now updated) and changes to make it Windows compatible.
Banana oats pancakes recipe
Blend
- Ripe banana.
- 2 Eggs.
- 1/3 cup instant oats.
- 1/2 teaspoon baking powder.
Really is this easy. Add all ingredients and blend.
Cook
Medium to low heat. Cook for 3 minutes. Flip. Cook for 1 minute. You're done.
Emacs: connect my Bluetooth speaker
Connecting and disconnecting bluetooth devices on macOS is fairly simple: use the menu bar utility.
But could we make it quicker from our beloved editor?
Turns out with a little elisp glue, we can fuzzy search our Bluetooth devices and toggle connections. We can use Oleh Krehel's ivy-read for fuzzy searching and Felix Lapalme's nifty BluetoothConnector to list devices and toggle Bluetooth connections.
As a bonus, we can make it remember the last selected device, so you can quickly toggle it again.
(defun ar/ivy-bluetooth-connect () "Connect to paired bluetooth device." (interactive) (assert (string-equal system-type "darwin") nil "macOS only. Sorry :/") (assert (executable-find "BluetoothConnector") nil "Install BluetoothConnector from https://github.com/lapfelix/BluetoothConnector") (ivy-read "(Dis)connect: " (seq-map (lambda (item) (let* ((device (split-string item " - ")) (mac (nth 0 device)) (name (nth 1 device))) (propertize name 'mac mac))) (seq-filter (lambda (line) ;; Keep lines like: af-8c-3b-b1-99-af - Device name (string-match-p "^[0-9a-f]\\{2\\}" line)) (with-current-buffer (get-buffer-create "*BluetoothConnector*") (erase-buffer) ;; BluetoothConnector exits with 64 if no param is given. ;; Invoke with no params to get a list of devices. (unless (eq 64 (call-process "BluetoothConnector" nil (current-buffer))) (error (buffer-string))) (split-string (buffer-string) "\n")))) :require-match t :preselect (when (boundp 'ar/misc-bluetooth-connect--history) (nth 0 ar/misc-bluetooth-connect--history)) :history 'ar/misc-bluetooth-connect--history :caller 'ar/toggle-bluetooth-connection :action (lambda (device) (start-process "BluetoothConnector" (get-buffer-create "*BluetoothConnector*") "BluetoothConnector" (get-text-property 0 'mac device) "--notify"))))
comments on twitter.
Duti: changing default macOS apps
Future self example, setting mpv.io to open all aiff files on macOS:
duti -s io.mpv aiff
Neapolitan pizza recipe
Full disclosure: I'm a complete noob at making pizza. It's my second pizza, but hey, it was tasty and fun to make! Making pizza at home is not as far-fetched as I initially thought.
UPDATES:
I've made this recipe a couple of times. Made two improvements worth mentioning.
Flan tin / quiche pan
My first pizzas were rectangular, matching the baking tray shape, but I really wanted round pies. I found a quiche pan at home and gave that a try. Worked pretty well. The dish bottom comes up, which is pretty handy.
Double baking
Bake in two stages:
- Bake the pizza for 6 minutes (without the mozarella) at bottom of oven.
- Add mozzarella and make for 4 minutes at top of the oven.
Recipe
Ok, on to the recipe now…
Dissolve the yeast
- 7g of yeast.
- 325ml of lukewarm water.
Dissolve the yeast in the lukewarm water.
Mixing the dough
- 500g of flour.
- 1 teaspoon of salt.
Gradually add flour to the yeast and water mix, using the bottom of a spoon to work it until smooth (no lumps). The dough will be very sticky at first. Stay faithful to the spoon. It'll work. BBC's How to make pizza like a Neapolitan master has a great demo. I followed the dough technique.
Kneading the dough
Sprinkle some flour on the table and knead the dough (punch, stretch, and fold many times) from previous step. Eventually, the dough will hold its shape.
Make 4 balls
Roll the dough into a cylinder and cut into 4 pieces. Make 4 balls.
Make the tomato sauce
- 500g of passata.
- 3 cloves of garlic.
I love garlic. Who doesn't? Slice the garlic finely and combine with the passata in a class jar. Shake it a little. Garlic and passata. That's your sauce.
Cover for 2 hours
Place the 4 dough balls into a container and cover with a damp cloth for 2 hours. You can make 4 pizzas.
*Rookie mistake: I should have used a bigger container. The balls grew and merged.
Preheat oven
Preheat the oven at 250°C.
Stretch base
Sprinkle more flour on table prior to shaping the dough. Place ball on table, flatten. Flip over, flatten again. Gradually stretch until you have the shape and thickness desired.
Place base on baking tray
- Semolina
- Aluminium foil
Line up the tray with some aluminium foil. Before transferring the base on to the baking tray, sprinkle semolina (or breadcrumbs) on the foil (it helps prevent the dough from sticking).
Toppings
- Tomato sauce.
- Salt.
- Olive oil.
- Parmesan cheese.
- 125g of Mozzarella cheese.
- Fresh basil.
Spread some of the tomato sauce with a spoon. Sprinkle salt, olive oil, and parmesan cheese. Break the mozzarella into pieces and spread throughout. Add some basil leaves. Your basic margherita pizza is now ready for the oven.
Bake pizza
Place the tray in the oven for 10 minutes. This worked for my oven, which goes up to 250°C. Either way, keep an eye on it.
Post baking toppings
- Anchovies.
Controversial, but I really like anchovies. Add them post-baking and you're good to go. Enjoy your pizza.
TIL (today I learned) bookmarks
mu/mu4e 1.4 released
mu/mu4e 1.4 is out. About a week ago, I built and installed its pre-release version (1.3.10) and noted build steps on macOS. It's been working great for me. Today, I updated to 1.4.
I was keen to try the new release out. I had been experiencing a short delay immediately after syncing/indexing mail. An initial investigation pointed to contact syncing, but I didn't dig further. The 1.4 release notes had a promising entry:
In many cases, `mu4e' used to receive all contacts after each indexing operation; this was slow for some users, so we have updated this to only get the contacts that have changed since the last round.
After upgrading. The delay is gone for me \o/
Note: there are a few config tweaks needed for the 1.4 upgrade, but these are well-documented in the release notes. For me, it primarily consisted of:
- Swapping elisp mu4e-maildir var for mu init –maildir path/to/local/IMAP.
- Swapping elisp mu4e-user-mail-address-list for mu init –my-address address1@domain.com –my-address address2@domain.com.
- Disabling mu4e-maildirs-extension (not yet compatible with mu 1.4). No issues here, since I hardly ever look at the mu4e-main buffer. I have global binding to my unread messages that looks a little something like this:
(defun ar/mu4e-view-unread-messages () (interactive) (mu4e-headers-search-bookmark (concat "flag:unread AND " "flag:unread AND " "NOT flag:trashed AND " "(maildir:/box1/INBOX OR " "maildir:/box2/INBOX)")))
comments on twitter.
Libya travel bookmarks
Trimming videos with ffmpeg
Via Bernd Verst's Trim Videos Instantly:
Start time + duration
ffmpeg -ss hh:mm:ss.msec -i in.mpeg -c copy -map 0 -t hh:mm:ss.msec out.mpeg
Start time + end time
ffmpeg -ss hh:mm:ss.msec -i in.mpeg -c copy -map 0 -to hh:mm:ss.msec out.mpeg
Emacs's counsel-M-x meets multiple cursors
I'm a fan of Magnar Sveen's multiple cursors Emacs implementation. It's just so fun to use and works very well with commands bound to my favorite keys.
Every now and then I'd like to execute extended commands on all cursors, but they have no keys bound to them. If you're an ivy/counsel fan like me (and all packages by Abo Abo), you use counsel-M-x to invoke commands. However, counsel-M-x doesn't support multiple cursors out of the box. Luckily, this is Emacs and we can fix that…
Back in December 2019, I made a note to revisit u/snippins1987's weekly tip to pair helm-M-x with multiple cursors. Finally got back to it. With a few changes, we can also make the snippet work with counsel-M-x \o/.
(defun adviced:counsel-M-x-action (orig-fun &rest r) "Additional support for multiple cursors." (apply orig-fun r) (let ((cmd (intern (car r)))) (when (and (boundp 'multiple-cursors-mode) multiple-cursors-mode cmd (not (memq cmd mc--default-cmds-to-run-once)) (not (memq cmd mc/cmds-to-run-once)) (or mc/always-run-for-all (memq cmd mc--default-cmds-to-run-for-all) (memq cmd mc/cmds-to-run-for-all) (mc/prompt-for-inclusion-in-whitelist cmd))) (mc/execute-command-for-all-fake-cursors cmd)))) (advice-add #'counsel-M-x-action :around #'adviced:counsel-M-x-action)
Portland travel bookmarks
String inflection Emacs package
string-inflection (by Akira Ikeda) is a nifty package to cycle through string case styles: camel, snake, kebab… The package includes a handful of cycling functions for different languages (Ruby, Python and Java), but it's easy to mix and match to roll your own. For now, I'm binding C-M-j to string-inflection-cycle, which is an alias to string-inflection-ruby-style-cycle.
(use-package string-inflection :ensure t :bind (:map prog-mode-map ("C-M-j" . string-inflection-cycle)))
comments on twitter
Turkey travel bookmarks
Dal Makhani (black lentils) recipe
Soak beans (overnight)
- 1 cup of rajmah (kidney beans).
- 2 cups of sabut urad (black lentils).
Place the beans in a bowl with plenty of water. The beans will soak it up so ensure there's enough.
Cooking the beans
- 3 liters of water.
- 1 cinamon stick.
- 1 tablespoon of turmeric.
- 2 bay leaves.
Drain the beans and combine new ingredients into a pot. Bring to a boil and simer for 1.5 hours. Check beans aren't firm (give 'em a try'). If so extend another 15-30 mins.
Prepare paste
- 1 4 cm piece of ginger.
- 1 large onion.
- 6 garlic cloves.
- 2 tomatoes.
Put through blender (with choppin pulse) or food processor until you get a paste.
Golden paste
- Paste.
- 3 tablespoons of butter.
- 1 tablespoon of cumin seeds.
- 1 tablespoon of coriander powder.
- 1 tablespoon of chilly powder (or less to make milder).
- 1 fresh red hot pepper (find one with medium heat level) chopped.
- 1 tablespoon of cumin powder.
- 1/4 cup of water.
- 3/4 tablespoon of salt.
Heat up the butter (medium heat) and brown the cumin seeds (maybe 30 seconds). Add the paste from previous step. Cook for about 4 minutes or until golden. Add the remaining ingredients in step (except water) and cook for another 30 seconds. Add the water and salt and mix to make more fluid and remove from heat.
Tying it all together
- 1 tablespoon of panchpuram (cumin, fenugreek, mistard, and fennel seeds).
- 300 ml of double cream.
Combine the cooked beans, golden paste, and seeds. Simmer for about 15 minutes. Add the cream and cook for about 2 minutes. You are effectively done.
Garnish (optional)
You can serve and optionally garnish with some chopped coriander. Recommended.
Serve with
Basmati rice, rotis, buttered buns, or even corn tortillas (unorthodox, but hey).
Modern Emacs lisp libraries
Quickly finding related built-in elisp functions (without prefixes) can sometimes take a little poking around.
Some modern and predictable built-in exceptions I now reach out to are:
- map.el for key/values, alists, hash-tables and arrays (built-in as of Emacs 25.1).
- seq.el for sequence manipulation functions (built-in as of Emacs 25.1).
- subr-x.el has a handful of string functions (built-in as of Emacs 24.4).
- let-alist.el wonderful syntax for alists, great for json (built-in as of Emacs 25.1).
If you don't mind reaching out to third-party libs (you likely have some of these already installed), here are some modern, predictable, and well-documented ones that always get me out of trouble:
- s.el string manipulation.
- f.el file/path handling.
- dash.el for lists.
- ht.el for hashtables.
- ts.el for date and time.
I'm happy with built-ins like map.el, seq.el, and let-alist.el. subr-x.el is also pretty nice, although not as full-featured as third-party s.el.
Am I missing out on other modern built-ins or third-party libraries?
UPDATE: Added a handful of newly discovered libraries plus suggestions by Daniel Martín (thanks!). Not tried any of these myself.
- rtree.el binary tree that stores ranges (Built-in).
- ov.el simple way to manipulate overlay.
- Toby Cubitt's libraries (heap, overlays, queue, avl-tree, trie, dict-tree, etc).
- shadchen.el pattern-matching.
- m-buffer.el buffers.
- esxml.el xml and html.
- a.el associative structures.
- asoc.el association list library.
- yk-color.el linear RGB color manipulation.
comments on twitter
Emacs smartparens auto-indent
While I do most editing in Emacs, I use Xcode every now and then. I like Xcode's pair matching (of brackets) combined with its auto-indent.
While the wonderful smartparens gives Emacs pair-matching powers, it doesn't automatically indent between pairs (out of the box anyway).
Luckily, smartparens does provide sp-local-pair, which enables us to achieve a similar goal.
With a short snippet, we can autoindent between {}, [], and () when pressing return in-between.
(defun indent-between-pair (&rest _ignored) (newline) (indent-according-to-mode) (forward-line -1) (indent-according-to-mode)) (sp-local-pair 'prog-mode "{" nil :post-handlers '((indent-between-pair "RET"))) (sp-local-pair 'prog-mode "[" nil :post-handlers '((indent-between-pair "RET"))) (sp-local-pair 'prog-mode "(" nil :post-handlers '((indent-between-pair "RET")))
comments on twitter
Solarpunk bookmarks
sqlite bookmarks
- DuckDB: SQLite for Analytics | Hacker News.
- Inserting 130M SQLite rows per minute from a scripting language | Hacker News.
- Inserting One Billion Rows in SQLite Under A Minute - blag.
- LiteCLI – A user-friendly command-line client for SQLite database (Hacker News).
- SQLite As An Application File Format.
- Zumero: Efficient sync by using multiple SQLite files.
covid-19 bookmarks
- A Data-Centric Approach to Plan Appropriate COVID-19 Response in the United States.
- Coronavirus action plan: a guide to what you can expect across the UK - GOV.UK.
- Coronavirus COVID-19 Global Cases by Johns Hopkins CSSE.
- COVID-19 (r/COVID19).
- COVID-19 Discussion (r/China_Flu/).
- Covid-19 DocSearch free access.
- COVID19 - AMA with r/COVID19 mod u/Jennifer Cole at 10.00pm GMT 25 Feb.
- Handbook of Covid-19 Prevention and Treatment from Hospital with 0% fatality (HN).
- microCOVID Project (calculate risk).
- New research suggests runners should be further than 2m apart.
- Novel Coronavirus (2019-nCoV) (r/coronavirus).
- Physics of COVID-19 Transmission | MIT OpenCourseWare.
- Self-care Tips if you become sick with COVID-19 from an activist nurse.
- WHO: When and how to use masks.
Security bookmarks
Nix bookmarks
Plants bookmarks
Fixing Honeywell CM927's dead screen
My Honeywell CM927 thermostat's screen had been getting progressively worse over the last year. As of late, the screen was of little use.
A random search yielded the Honeywell CM927 LCD screen fail - common? thread, with a promising comment by Phil:
"Strip the unit and remove the circuit board (just a few plastic clips, no screws). Remove the LCD assembly from the circuit board (more plastic clips and an eight pin push connection). Removed the LCD unit from the clear plastic housing (more plastic clips). Finally heat up the plastic ribbon where it is stuck to the circuit board (hair dryer will do trick) and then firmly press it onto the circuit board… probably worth doing this several times; in effect you are remating the ribbon to the circuit board by softening the adhesive. Put it all back together and it should be working again."
Phil's instructions were great. There's also a super handy video by El Tucan, also linked by Stevie.
Success \o/
Heating up the plastic ribbon and pressing it onto the circuit board did the trick for me. Took a few tries for all segments to appear, but the screen is looking great again.
Thank you Internet strangers! :)
SwiftUI bookmarks
- ActionOver: A custom SwiftUI modifier to present an Action Sheet on iPhone and a Popover on iPad and Mac.
- 8 Common SwiftUI Mistakes - and how to fix them – Hacking with Swift.
- Building a Widget for iOS with SwiftUI and WidgetKit - SchwiftyUI.
- Line-Wrapping Stacks - Swift You and I.
- Pull-to-Refresh in SwiftUI | Swift with Majid.
- SwiftUI Import/Export files | Rizwan's Blog 👨💻.
- The SwiftUI Toolbar in iOS 14.
- Trailing Closure (SwiftUI tutotials).
- URLSession: Common pitfalls with background download & upload tasks.
- SwiftUI live-blur materials that you can use like a background color.
- <SwiftUI for Absolute Beginners>读书 - emacsist.
- @Environment values.
- `@State` `onChange`.
- `SwiftUI` Framework Learning and Usage Guide.
- A Companion for SwiftUI - The SwiftUI Lab.
- A deep dive into Swift’s function builders | Swift by Sundell.
- A Fast Fuzzy Search Implementation · objc.io.
- A guide to SwiftUI’s state management system | Swift by Sundell.
- A guide to the SwiftUI layout system - Part 1 | Swift by Sundell.
- A SwiftUI iOS system components and interactions demo app based on iOS 14.
- Advanced SwiftUI Animations - Part 1: Paths - The SwiftUI Lab.
- Advanced SwiftUI Transitions - The SwiftUI Lab.
- AutomaticSettings: Data driven settings UI.
- Building a MapView app with SwiftUI — Morning SwiftUI.
- Building Pager view in SwiftUI | Majid’s blog about Swift development.
- Building ViewModels with Combine framework.
- Category: Combine – Donny Wals.
- Combine: Asynchronous Programming with Swift.
- Composable styling in SwiftUI | Swift with Majid.
- Constructing Data with Swift Function Builders – Oliver Binns.
- Context Menu, Alert and ActionSheet in SwiftUI.
- Create an SPM Package for SwiftUI | Daniel Saidi.
- CwlFitting: A small SwiftUI package to aid with "shrink-to-fit" + "fill-aligned" VStack and HStack arrangements.
- debugPrint() SwiftUI modifier.
- Deep dive into Swift frameworks - The.Swift.Dev..
- Default a View in NavigationView with SwiftUI - DEV Community.
- designcode's SwiftUI course.
- Detecting changes to a folder in iOS using Swift.
- Dismiss Gesture for SwiftUI Modals - The SwiftUI Lab.
- First learnings from adopting SwiftUI - Christos Karaiskos - Medium (card example).
- Function Builders in Swift and SwiftUI.
- GeometryReader to the Rescue - The SwiftUI Lab.
- Gestures in SwiftUI - Better Programming - Medium.
- GitHub - AppPear/ChartView: ChartView made in SwiftUI.
- GitHub - dasautoooo/Parma: A SwiftUI view for displaying Markdown with custom..
- GitHub - Dimillian/MovieSwiftUI: SwiftUI & Combine app using MovieDB API..
- GitHub - Jinxiansen/SwiftUI: `SwiftUI` Framework Learning and Usage Guide..
- GitHub - mecid/SwiftUICharts: A simple line and bar charting library written for SwiftUI.
- GitHub - nalexn/EnvironmentOverrides: QA assistant for a SwiftUI app.
- GitHub - nerdsupremacist/FancyScrollView (list with growing/snapping header).
- GitHub - paololeonardi/WaterfallGrid: A waterfall grid layout view for SwiftUI..
- GitHub - SimpleBoilerplates/SwiftUI-Cheat-Sheet: SwiftUI Cheat Sheet.
- GitHub - siteline/SwiftUI-Introspect: Introspect underlying UIKit components.
- GitHub - SwiftUIX/SwiftUIX: An extension to the standard SwiftUI library..
- Gradient in SwiftUI | Majid’s blog about Swift development.
- How to add a toolbar above the keyboard using inputAccessoryView.
- How to animate along zIndex in SwiftUI.
- How to create a side menu (hamburger menu) in SwiftUI | BLCKBIRDS.
- How to Create a Splash Screen With SwiftUI | raywenderlich.com.
- how to display a search bar with SwiftUI - Stack Overflow.
- How to fix slow List updates in SwiftUI – Hacking with Swift.
- How to Schedule Notifications and Add Badges in SwiftUI.
- Image resizing techniques in Swift (smooth scroll).
- Implement a Search Bar in SwiftUI - Better Programming - Medium.
- Implementing Context Menus in iOS 13 Using SwiftUI or UIKit.
- In the new SwiftUI, is there any reason you would still use ObservedObject instead of StateObject?.
- Inspecting the View Tree with PreferenceKey - Part 1 - The SwiftUI Lab.
- Integrate SwiftUI on UIKIT project its actually pretty easy.
- ios - How to make view the size of another view in SwiftUI - Stack Overflow.
- Lessons learned with Swift + iOS development.
- LLDB "_regexp-break <file>:<line>:<column>", breakpoint at a particular source code line and column.
- Mastering grids in SwiftUI | Swift with Majid.
- Mastering ScrollView in SwiftUI | Swift with Majid.
- Multiplatform Messages app for macOS, iOS, iPadOS in SwiftUI.
- Must-have SwiftUI extensions | Majid’s blog about Swift development.
- New property wrappers in SwiftUI (@ScaledMetric, @SceneStorage, @AppStorage, @StateObject).
- On iOS 14, the keyboard is added to safe area.
- Our New Book: Thinking in SwiftUI · objc.io.
- Performance Battle: AnyView vs Group - Alexey Naumov.
- Practical Combine: An introduction to Combine with real examples.
- Programmatic navigation in SwiftUI project - Alexey Naumov.
- Property Wrappers in Swift 5.1. An introduction to one of Swift 5.1’s.
- Recreate iOS style Welcome Screen to any app in 3 minutes.
- Recreate this Control Center widget in SwiftUI.
- Remote images in SwiftUI - DEV Community.
- Resizing Techniques and Image Quality That Every iOS Developer Should Know (Swift).
- Reusable Image Cache in Swift - Flawless iOS - Medium.
- Search View in SwiftUI | Ordinary Coding.
- Selecting dates and times with DatePicker.
- Short video showing you how to debug, learn, or teach Combine operators with Timelane - the approach is always the same, add lanes - analyze the data.
- Sidebar navigation in SwiftUI | Swift with Majid.
- StaggeredList Sample App: A Staggered Pinterest Like Layout using SwiftUI.
- State and Data Flow | Apple Developer Documentation.
- Stretchable header.
- swift - HStack with SF Symbols Image not aligned centered - Stack Overflow.
- swift - ImagePicker in SwiftUI - Stack Overflow.
- Swift Property Wrappers - NSHipster.
- Swift UI Property Wrappers (@State, @StateObject, @EnvironmentObject, @ObservedObject, @Binding).
- swiftui - Not Receiving scenePhase Changes (foreground/background).
- SwiftUI and Redux — Clean Code and Small, Independent Components.
- SwiftUI Animation (buttons, current-rotations, etc) | Sarun.
- SwiftUI Animation | Sarun.
- SwiftUI basic components (form example).
- SwiftUI Buttons and images (using systemName).
- SwiftUI courses.
- SwiftUI Custom Styling - The SwiftUI Lab (scaleEffect and opacity on isPressed).
- SwiftUI DatePicker.
- SwiftUI displaying customizable quick action card.
- https://troz.net/post/2020/swiftui_mac_big_sur/][SwiftUI for Mac on Big Sur">TrozWare]].
- SwiftUI gives you .isPlaceholder in WidgetKit generate a placeholders
- SwiftUI Layout System | Alexander Grebenyuk.
- SwiftUI NavigationView tutorial with examples - Simple Swift Guide.
- SwiftUI notes - Tomasz Nazarenko Blog.
- SwiftUI picker gotchas.
- SwiftUI Search Bar in the Navigation Bar.
- SwiftUI snippets by Jeroen Zonneveld.
- SwiftUI Tutorial: How to Build a Form UI for iOS Apps.
- SwiftUI Tutorials on SwiftUI Hub.
- swiftui.gallery | A gallery of SwiftUI code example snippets.
- swiftui.gallery: sign up form sample.
- swiftui: A collaborative list of awesome SwiftUI resources.
- SwiftUI: Equal widths view constraints — finestructure.
- SwiftUI: Shake Animation · objc.io.
- SwiftUIStaggeredList: Staggered Layout List Using SwiftUI.
- SwiftUI’s New App Lifecycle and Replacements for AppDelegate.
- Swipe gesture SwiftUI | Daniel Saidi.
- SwuiftUI books.
- Tagged “SwiftUI” | Sarun.
- TextField in SwiftUI | Majid’s blog about Swift development.
- The Complete SwiftUI Documentation You’ve Been Waiting For.
- The difference between @StateObject, @EnvironmentObject, and @ObservedObject.
- The Power of the Hosting+Representable Combo (scroll SwiftUI list).
- The ultimate Combine framework tutorial in Swift - The.Swift.Dev..
- Tweet on improving List SwiftUI performace (searching).
- UICollectionView Custom Layout Tutorial: Pinterest | raywenderlich.com.
- URL Image view in SwiftUI.
- Using Combine (extensive online book).
- Using iOS 14's Menu as a Picker in SwiftUI.
- View composition in SwiftUI | Majid’s blog about Swift development.
- Views Choose Their Own Sizes – Netsplit.com.
- Visualize Combine Magic with SwiftUI Part 1 - Flawless iOS - Medium.
- What’s the difference between @StateObject and @ObservedObject? – Donny Wals.
- Why I quit using the ObservableObject - Alexey Naumov.
- Working with Focus on SwiftUI Views - The SwiftUI Lab.
Studying for Life in the UK test
Today, I passed the Life in the UK test. Wasn't quite sure how to study for it. During my commutes, I listened to the Life in the UK 2019 Test audio book.
A friend recommended lifeintheuktestweb.co.uk. Overall, I found their practice tests very useful. Taking a bunch tests helped me internalize the material.
Took some notes along the way (mostly data with years attached) and dumped it into an org table. This helped me form a mental timeline.
NOTE: These tables alone are not comprehensive enough to prepare for the exam. You'll need to know additional information without dates attached.
Events
Year | Event |
---|---|
2012 | Diamond Jubilee |
1999 | Scottish Parliament formed |
1973 | UK joins the EU \o/ |
1972 | Mary Peters wins Gold medal (pentathlon) |
1957 | Treaty of Rome signed (March 25) |
1950 | UK signs European Convention of Human Rights |
1949 | Ireland become a republic |
1947 | Granted independence India, Pakistan and Ceylon (Sri Lanka) |
1945 | Clement Attlee elected |
1945 | Alexander Fleming discovers penicillin |
1945 | WWII ends |
1944 | Butler Act (free secondary education England/Wales) |
1940 | Battle of Britain |
1939 | Germany invades Poland |
1930s | Turing Machine |
1936 | BBC first regular television service |
1932 | First television broadcast |
1930 | British Film Studios Fluorish |
1928 | Women/men with same voting age |
1918 | WWI ends (November 11, 11am) |
1903 | Emmeline Pankhurst Women’s Social and Political Union (suffragettes) |
1902 | Motor-car racing in UK |
1896 | First film shown publicly |
1899-1902 | The Boer War (South Africa) |
1870-1914 | 120000 Russian and Polish Jews fled to Britain to escape prosecution |
1853-1856 | Crimean War |
1851 | Great Exhibition (showcased Crystal Palance) |
1837 | Queen Victoria becomes queen (at 18) |
1833 | Emancipation Act (abolished slavery throughout British Emprire) |
1832 | The Reform Act (increase number of people with voting rights) |
1776 | North American colonies want out (don't tax us without representation) |
1745 | Bonnie Prince Charlie gets support by clansmen from Scottish highlands |
1714 | Queen Ann dies, George I becomes King |
1689 | Bill of rights (limit rights of kings) |
1688 | William of Orange invades England (proclaims king) |
1680-1720 | Huguenots refugees came to England (from France) |
1695 | Free press (newspapers) established |
1679 | Habeas Corpus Act (right to trial) |
1649-1660 | Cromwell rules republic for 11 years (Charles I executed) |
1642 | English Civil war (Cavaliers vs Roundheads) |
1606 | Union flag created |
1588 | English beat Spanish Armada |
1348 | Black death (third population die) |
1314 | Battle of Bannockburn: Robert the Bruce (Scottish King) beats English invasion |
1284 | Statute of Rhuddlan (Wales joins Crown, by King Edward I) |
1215 | Magna Carta created |
1066 | Norman Conquest (Saxon King Harold killed by William I) |
300-400 AD | Christians appear in Britain |
789 AD | Vikings first visit Britain and raid coastal towns |
6000 years ago | Farmers come to Britain |
Population
Year | Population |
---|---|
2010 | > 62 million |
2005 | < 60 million |
1998 | 57 million |
1951 | 50 million |
1901 | 40 million |
1851 | 20 million |
1700 | 5 million |
1600 | > 4 million |
Georgia travel bookmarks
Wizard zines comics in Emacs eshell
Over at wizardzines.com, Julia Evans authors wonderful zines on topics like git, networking, linux, command-line utilities, and others. Some zines are paid. Some are free. No affiliation here, just a fan.
A little while ago, Julia tweeted about a utility she's building to view her original comics on similar topics. I instantly thought it'd be a fun tool to implement for Emacs eshell.
Since then, I subscribed to wizardzines.com/saturday-comics and received a few comics (awk, tar, and bash tricks). I saved them locally (using topic name and dropping file extensions).
ls -1 ~/Downloads/wizardzines-comics/
awk |
bash |
tar |
By no means battle-tested, but here's an elisp snippet defining the ecomic command. It displays inlined comics in the handy eshell.
(require 'eshell) (require 'iimage) (defvar wizardzines-comics-path "~/Downloads/wizardzines-comics") (defun eshell/ecomic (&rest args) "Display command comic in ARGS. Note: ensure comic images live in `wizardzines-comics-path', named with command name and no extension." (eshell-eval-using-options "ecomic" args '((?h "help" nil nil "show this usage screen") :external "ecomic" :show-usage :usage "COMMAND Show COMMAND comic from Julia Evans' https://wizardzines.com/saturday-comics") (let* ((command (nth 0 (eshell-stringify-list (eshell-flatten-list args)))) (image-fpath (concat (file-name-as-directory (expand-file-name wizardzines-comics-path)) command))) (unless (file-exists-p image-fpath) (error "comic: \"%s\" not found :-(" command)) (eshell-buffered-print "\n") (add-text-properties 0 (length image-fpath) `(display ,(create-image image-fpath) modification-hooks (iimage-modification-hook)) image-fpath) (eshell-buffered-print image-fpath) (eshell-flush))))
comments on twitter
Updates
- Tweaked title.
Emacs counsel default search switches
Following up from Enhanced Emacs searching with counsel switches, rather than remembering silver searcher and ripgrep switches, we can use counsel's ivy-initial-inputs-alist to set these up as default visible switches.
(push '(counsel-ag . "--file-search-regex '' -- ") ivy-initial-inputs-alist) (push '(counsel-rg . "--glob '**' -- ") ivy-initial-inputs-alist)
The default switches stay out of the way in typical searches, but can be easily modified to include (or exclude) results matching specific file names.
comments on twitter
Enhanced Emacs searching with counsel switches
The counsel family of Emacs search commands are great for searching the filesystem. More specifically, counsel-rg, counsel-ag, and counsel-pt, which use the popular ripgrep, silver searcher, and platinum searcher utilities.
counsel-rg is my default searcher. It returns results quickly, with live updates as I tweak the search query.
Up until recently, my queries typically matched text in files only. This works great, but every so often I wished I could amend the query to include (or exclude) results matching specific file names. Turns out, you can prepend the search query with additional switches using the "–" separator.
The switches are usually utility-specific, but if we wanted to keep results from file names matching a glob, we can prepend the ripgrep query with something like "–glob Make* –" or the shorter version "-g Make* –".
rg: -g Make* – install
Emacs org block company completion
UPDATE: This is now available on melpa.
Back in 2015, I bound the "<" key to a hydra for quickly inserting org blocks. The idea came from Oleg's post on org-mode block templates in Hydra. The suggested binding settled in my muscle memory without much effort.
Fast forward to Febrary 2019. I replaced the hydra with org-insert-structure-template when org-try-structure-completion was removed from org mode. No biggie, as I kept the same binding to "<" and hardly noticed the change.
Since my primary use-case for easy templates is inserting source blocks, I was keen to expedite choosing the source language as well as inserting the source block itself.
Writing a small company mode completion backend fits my primary use-case pretty well.
The company backend looks as follow (Warning: Snippet needs Org v9.2).
Note: This code is not up to date. Install via melpa or see its repository.
(require 'map) (require 'org) (require 'seq) (defvar company-org-block-bol-p t "If t, detect completion when at begining of line, otherwise detect completion anywhere.") (defvar company-org--regexp "<\\([^ ]*\\)") (defun company-org-block (command &optional arg &rest ignored) "Complete org babel languages into source blocks." (interactive (list 'interactive)) (cl-case command (interactive (company-begin-backend 'company-org-block)) (prefix (when (derived-mode-p 'org-mode) (company-org-block--grab-symbol-cons))) (candidates (company-org-block--candidates arg)) (post-completion (company-org-block--expand arg)))) (defun company-org-block--candidates (prefix) "Return a list of org babel languages matching PREFIX." (seq-filter (lambda (language) (string-prefix-p prefix language)) ;; Flatten `org-babel-load-languages' and ;; `org-structure-template-alist', join, and sort. (seq-sort #'string-lessp (append (mapcar #'prin1-to-string (map-keys org-babel-load-languages)) (map-values org-structure-template-alist))))) (defun company-org-block--template-p (template) (seq-contains (map-values org-structure-template-alist) template)) (defun company-org-block--expand (insertion) "Replace INSERTION with actual source block." (delete-region (point) (- (point) (1+ ;; Include "<" in length. (length insertion)))) (if (company-org-block--template-p insertion) (company-org-block--wrap-point insertion ;; May be multiple words. ;; Take the first one. (nth 0 (split-string insertion))) (company-org-block--wrap-point (format "src %s" insertion) "src"))) (defun company-org-block--wrap-point (begin end) "Wrap point with block using BEGIN and END. For example: #+begin_BEGIN | #+end_END" (insert (format "#+begin_%s\n" begin)) (insert (make-string org-edit-src-content-indentation ?\s)) ;; Saving excursion restores point to location inside code block. (save-excursion (insert (format "\n#+end_%s" end)))) (defun company-org-block--grab-symbol-cons () "Return cons with symbol and t whenever prefix of < is found. For example: \"<e\" -> (\"e\" . t)" (when (looking-back (if company-org-block-bol-p (concat "^" company-org--regexp) company-org--regexp) (line-beginning-position)) (cons (match-string-no-properties 1) t)))
To use, add the backend enable company-mode in org-mode:
(add-to-list 'company-backends 'company-org-block) (company-mode +1)
Updates
- Removed language-specific header logic (use org-babel-default-header-args instead).
- Also completes non-source block templates from org-structure-template-alist.
- Source in my dot files.
- Removed unnecessary binding. Just add company backend as usual.
- Thanks to Takaaki Ishikawa for suggesting `org-edit-src-content-indentation'.
- Thanks to Thomas Kobber for highlighting incompatibility with older org versions.
IRC bookmarks
A more reusable Emacs shell-command history
Cameron Desautel has a great post on Working Faster in Emacs by Reading the "Future", highlighting M-n's usefulness for inserting minibuffer default values.
Invoking M-n in shell-command's prompt is handy for quickly getting the current buffer's file name. This works great for one-off shell commands like "chmod +x script.sh" or "tidy -xml -i -m data.xml". Unfortunately, these commands aren't easily reusable from shell-command's minibuffer history, since it'll keep hardcoded file names.
There's likely existing built-in functionality or a more elaborate package for this, but advising read-shell-command enables us to write more reusable commands like "chmod +x $f" or "tidy -xml -i -m $f". We merely replace $f with (buffer-file-name), and let everything else continue as usual.
(defun ar/adviced-read-shell-command (orig-fun &rest r) "Advice around `read-shell-command' to replace $f with buffer file name." (let ((command (apply orig-fun r))) (if (string-match-p "\\$f" command) (replace-regexp-in-string "\\$f" (or (buffer-file-name) (user-error "No file file visited to replace $f")) command) command))) (advice-add 'read-shell-command :around 'ar/adviced-read-shell-command)
It's worth mentioning that searching minibuffer history is pretty simple when leveraging counsel to fuzzy search (via counsel-minibuffer-history, bound to C-r by default).
On a final note, searching minibuffer history for cache hits is way more useful with richer history content. Be sure to save minibuffer history across Emacs sessions and increase shell-command-history using the built-in savehist-mode.
(use-package savehist :custom (savehist-file "~/.emacs.d/savehist") (savehist-save-minibuffer-history t) (history-length 10000) (savehist-additional-variables '(shell-command-history)) :config (savehist-mode +1))
Taiwan travel bookmarks
Emacs swiper and multiple cursors
Emacs swiper is awesome. I bound swiper-isearch to C-s. Also a big fan of multiple cursors. I use it regularly (it's fun).
I had totally missed Ole's post back in 2015: A simple multiple-cursors extension to swiper. Turns out, swiper has multiple cursors support out of the box (bound to C-7 by default). Yay!
UPDATE: Thanks to irreal's post, please remember to add swiper-mc to mc/cmds-to-run-once list (or things won't work as expected). This typically happens interactively when you invoke C-7 the first time around. Make sure you answer "n" when you see a prompt like:
If you happen to choose "y" by mistake, take a look at ~/.emacs.d/.mc-lists.el to correct it. Remove swiper-mc from mc/cmds-to-run-for-all and add it to mc/cmds-to-run-once. Invoke m-x eval-buffer to reset the values and you're good to go.
Speeding up gifs with gifsycle
Drop frames and speed gif up with gifsycle (via How to remove every second frame from an animated gif?):
gifsicle -U in.gif `seq -f "#%g" 0 3 398` -O2 -o out.gif
ps. 398 is the total number of frames, which you can get with:
identify in.gif
Spam blacklisting with Emacs org babel
Some email provider accept regular expressions to blacklist additional spam. My blacklist is long and tedious to update, but hey… Emacs org babel can simplify things here.
It's way easier to maintain a blacklist (with no regex) using an org table.
Blacklist
#+name: spam-entries | .spammy | | dodgyfella@hotmail.com | | henryzeespammer.com | | yumspam.com |
and subsequently use org babel (elisp snippet) to generate the regex.
Regex gen
#+begin_src emacs-lisp :var rows=spam-entries (require 'dash) (require 's) (concat "^" (s-join "|" (mapcar (lambda (entry) (setq entry (regexp-quote (s-trim entry))) (assert (s-present? entry)) (cond ;; Blacklist email address: joe@spammer.spammy ((s-contains-p "@" entry) (format "(%s)" entry)) ;; Blacklist top-level domain: .spammy ((s-starts-with-p "\\." entry) (format "([^.]*%s)" entry)) ;; Blacklist domain: @spammer.spammy (t (format "(.*@%s)" entry)))) (-sort 'string< (-map (lambda (row) (nth 0 row)) rows)))) "$") #+end_src #+RESULTS: : ^([^.]*\.spammy)|(dodgyfella@hotmail\.com)|(.*@henryzeespammer\.com)|(.*@yumspam\.com)$
UPDATE: Tweaked elisp and regex (but not animation) also found John Bokma's post: Blacklisting domains with Postfix.
Rewriting dates with Emacs multiple cursors
Needed to rewrite the date format in a couple of csv columns. Emacs multiple cursors helps here, but needed a function to parse and reformat the dates themselves.
I can likely reformat dates using the built-in parse-time-string and format-time-string functions, but hey why not give the ts.el library a try…
(defun ar/region-to-timestamp () "Convert date like \"29 Apr 2019\" to \"2019-04-29\"." (interactive) (let ((date (ts-parse (buffer-substring (region-beginning) (region-end))))) (delete-region (region-beginning) (region-end)) (insert (ts-format "%Y-%m-%d" date))))
Bound the new function to a temporary keybinding, so I can invoke from multiple cursors:
(bind-key "M-q" #'ar/region-to-timestamp)
and voilà!
Show/hide Emacs dired details in style
Emacs dired is a powerful directory browser/editor. By default, it shows lots of handy file and directory details.
I typically prefer hiding file and directory details until I need them. The built-in dired-hide-details-mode makes this easy with the "(" key toggle. Coupled with Steve Purcell's diredfl (for coloring), it strikes a great user experience.
With a short snippet, you can also show/hide dired details in style:
(use-package dired :hook (dired-mode . dired-hide-details-mode) :config ;; Colourful columns. (use-package diredfl :ensure t :config (diredfl-global-mode 1)))
UPDATE: Thanks to Daniel Martín, who pointed me to dired-git-info. This package adds git logs to dired file and directory details.
Binding dired-git-info-mode to ")" is a nice complement to dired-hide-details-mode's "(" binding.
(use-package dired-git-info :ensure t :bind (:map dired-mode-map (")" . dired-git-info-mode)))
Bulk buying bookmarks
Speeding up Emacs tramp via ControlMaster
Via Florian Margaine's Eshell config, I discovered ssh's ControlMaster. It enables sharing multiple sessions over a single network connection. This has the benefit of speeding up Emacs TRAMP.
In your ~/.ssh/config add:
Host * ControlPath ~/.ssh/master-%h:%p ControlMaster auto ControlPersist 10m
csv bookmarks
Slovakia travel bookmarks
Thumbnailing pdf page
If you ever need to thumbnail a pdf page, imagemagick has got you covered. For example, to thumbnail page 3, you can use:
convert path/to/input.pdf[2] path/to/output.png
convert -resize 10000x10000 path/to/input.pdf[2] path/to/output.png convert: FailedToExecuteCommand `'gs' -sstdout=%stderr -dQUIET -dSAFER -dBATCH -dNOPAUSE -dNOPROMPT -dMaxBitmap=500000000 -dAlignToPixels=0 -dGridFitTT=2 '-sDEVICE=pngalpha' -dTextAlphaBits=4 -dGraphicsAlphaBits=4 '-r72x72' -dFirstPage=3 -dLastPage=3 '-sOutputFile=/var/folders/2y/nj_s07ms7l5gfsffh89_79zm0000gn/T/magick-30950xzlPsgqGUwtA%d' '-f/var/folders/2y/nj_s07ms7l5gfsffh89_79zm0000gn/T/magick-30950jpGyui82uGOQ' '-f/var/folders/2y/nj_s07ms7l5gfsffh89_79zm0000gn/T/magick-30950cuDVTNjArshs'' (1) @ error/pdf.c/InvokePDFDelegate/292.
However, I had the error above (missing gs), resolved by installing ghostscript.
brew install ghostscript
Outdoor bookmarks
gnuplot bookmarks
mu4e as macOS mail composer
Via Using Emacs as Default Mailer on macOS, a tiny script to handle mailto: links.
From /Script Editor, save following script as Application (MailOnEmacs.app). From Mail.app, Preferences -> Default email reader and chosse MailOnEmacs.app.
on open location myurl tell application "Emacs" to activate set text item delimiters to {":"} do shell script "/path/to/emacsclient --eval '(browse-url-mail \"" & myurl & "\")'" end open location
New sudo user snippet
I don't add linux sudoers frequently enough. Always looking it up. Keeping snippet.
adduser -m -d /home/<username> <username> passwd <username> usermod -aG sudo <username>
Plotting ledger reports in org
My ledger file
Save path to my.ledger in ledger-file block.
#+name: ledger-file #+begin_src emacs-lisp "my.ledger" #+end_src
gnuplot terminal (png or qt)
Select gnuplot terminal. Using png to output images, but qt is handy too for interactive chart inspection.
Use png for inline or qt for interactive #+name: gnuplot-term #+begin_src emacs-lisp "png" #+end_src
Monthly Income and Expenses
Generate income report.
#+name: income-data #+begin_src bash :results table :noweb yes ledger -f <<<ledger-file>>> -j reg ^Income -M --collapse --plot-amount-format="%(format_date(date, \"%Y-%m-%d\")) %(abs(quantity(scrub(display_amount))))\n" #+end_src
Generate expenses report.
#+name: expenses-data #+begin_src sh :results table :noweb yes ledger -f <<<ledger-file>>> -j reg ^Expenses -M --collapse #+end_src
Plot income vs expenses.
set terminal myterm size 3500,1500 set style data histogram set style histogram clustered gap 1 set style fill transparent solid 0.4 noborder set xtics nomirror scale 0 center set ytics add ('' 0) scale 0 set border 1 set grid ytics set title "Monthly Income and Expenses" set ylabel "Amount" plot income using 2:xticlabels(strftime('%b', strptime('%Y-%m-%d', strcol(1)))) title "Income" linecolor rgb "light-salmon", '' using 0:2:2 with labels left font "Courier,8" rotate by 15 offset -4,0.5 textcolor linestyle 0 notitle, expenses using 2 title "Expenses" linecolor rgb "light-green", '' using 0:2:2 with labels left font "Courier,8" rotate by 15 offset 0,0.5 textcolor linestyle 0 notitle
Changing MAC address in org
Via Minko Gechev's tweet. Saving in an org block, just because…
changeMAC() { local mac=$(openssl rand -hex 6 | sed 's/\(..\)/\1:/g; s/.$//') ifconfig en0 ether $mac ifconfig en0 down ifconfig en0 up echo "Your new physical address is $mac" } changeMAC
Your new physical address is aa:36:ee:d2:ee:66
ps. Also see Execute org blocks as root.
Charting bookmarks
Building swift-format
Trying out Google's swift-format. Build with:
git clone -b swift-5.2-branch https://github.com/apple/swift-format.git
cd swift-format
swift build
.build/x86_64-apple-macosx/debug/swift-format --help
OVERVIEW: Format or lint Swift source code. USAGE: swift-format [options] <filename or path> ... OPTIONS: --configuration The path to a JSON file containing the configuration of the linter/formatter. --in-place, -i Overwrite the current file when formatting ('format' mode only). --mode, -m The mode to run swift-format in. Either 'format', 'lint', or 'dump-configuration'. --recursive, -r Recursively run on '.swift' files in any provided directories. --version, -v Prints the version and exists --help Display available options POSITIONAL ARGUMENTS: filenames or paths One or more input filenames
UPDATE: Now uses swift-5.2-branch (for Xcode 11.4), according to Matching swift-format to Your Swift Version.
Ledger query snippets
Expenses paid in cash between two dates
ledger -f my.ledger reg "^Expenses" and expr 'any(account=~/Assets:Cash:Wallet/)' -b 02/19 -e 04/09
Bank account income between two dates
ledger -f my.ledger reg "^Assets:Bank:Acme" and expr "amount > 0" -b 02/19 -e 04/09
Formatting reg output
ledger -f my.ledger reg "^Assets:Bank:Acme" --format="%(payee) %(amount)\n"
Batch file renaming with counsel, find-dired, and wdired
The first time I saw wdired in action, it blew my mind. wdired makes dired (directory editor) buffers writeable, so you can edit them like any other Emacs buffer. You can subsequently use all your favorite file-editing tricks to rename files (amongst other things). You can see it in action at the end of Emacs Rocks episode 16.
When combining find-dired with wdired, one can easily find matching files and quickly batch rename them using something like multiple cursors or keyboard macros. I've been a fan of the find-dired -> dired-toggle-read-only -> mc/mark-all-like-this workflow for quite some time, but I always wished I could adjust find-dired queries a little quicker by getting immediate feedback.
Completion frontends like ivy and helm are perfect for getting this kind of immediate feedback. Peeking into ivy's counsel source, I borrowed some ideas to glue counsel-style narrowing on a find command, which I can easily translate to a writeable dired buffer for all that joyful-mutiple-cursor-editing experience.
The code for ar/counsel-find is a little rough but can be found at here.
VPS bookmarks
Svelte bookmarks
Mark region, indent, restore location
When I'm not using an automatic code formatter (ie. clang-format, gofmt, etc.), I often find myself using Emacs region marking commands like mark-defun, er/expand-region, and mark-whole-buffer prior to pressing <tab>, which is bound to indent-for-tab-command.
This is all working as expected: the selection gets indented and the point is left in the current location.
Say we have the following snippet we'd like to indent.
Mark region with C-M-h (mark-defun)
Indent with <tab> (indent-for-tab-command)
We're done. The selected function is now indented as expected.
But… I always wished the point returned to the location prior to initiating the region-marking command, in this case mark-defun.
In short, I wish the point had ended in the following location.
I'm not aware of an existing package that helps with this, so here's a tiny minor mode (divert-mode) to help with restoring point location after indenting a region. The diverted-events variable can be used to track specific region selecting commands and associate breadcrumb functions to replace the point location as needed.
;;; diverted.el --- Identify temporary diversions and automatically ;;; move point back to original location. ;;; Commentary: ;; Automatically come back to a original location prior to diversion. ;;; Code: (require 'cl) (require 'seq) (defstruct diverted-event from ;; Initial function (eg. 'mark-defun) to ;; Follow-up function (eg. 'indent-for-tab-command) breadcrumb) (defvar diverted-events (list (make-diverted-event :from 'mark-defun :to 'indent-for-tab-command :breadcrumb (lambda () (diverted--pop-to-mark-command 2))) (make-diverted-event :from 'er/expand-region :to 'indent-for-tab-command :breadcrumb (lambda () (diverted--pop-to-mark-command 2))) (make-diverted-event :from 'mark-whole-buffer :to 'indent-for-tab-command :breadcrumb (lambda () (diverted--pop-to-mark-command 2)))) "Diversion events to look for.") (defun diverted--resolve (symbol) "Resolve SYMBOL to event." (seq-find (lambda (event) (equal symbol (diverted-event-from event))) diverted-events)) (defun diverted--pop-to-mark-command (n) "Invoke `pop-to-mark-command' N number of times." (dotimes (_ n) (pop-to-mark-command))) (defun diverted--advice-fun (orig-fun &rest r) "Get back to location prior to diversion using advice around `diverted-events' (ORIG-FUN and R)." (let ((recognized-event (diverted--resolve last-command))) (when recognized-event (funcall (diverted-event-breadcrumb recognized-event)) (message "Breadcrumbed prior to `%s'" (diverted-event-from recognized-event))))) (defun diverted-mode-enable () "Enable diverted-mode." (interactive) (diverted-mode-disable) (mapc (lambda (event) (advice-add (diverted-event-to event) :after 'diverted--advice-fun) (message "Looking for `%s' after `%s' diversions." (diverted-event-to event) (diverted-event-from event))) diverted-events) (message "diverted-mode enabled")) (defun diverted-mode-disable () "Disable diverted-mode." (interactive) (mapc (lambda (event) (advice-remove (diverted-event-to event) 'diverted--advice-fun) (message "Ignoring `%s' after `%s' diversions." (diverted-event-to event) (diverted-event-from event))) diverted-events) (message "diverted-mode disabled")) (define-minor-mode diverted-mode "Detect temporary diversions and restore point location." :init-value nil :lighter " diverted" :global t (if diverted-mode (diverted-mode-enable) (diverted-mode-disable))) (provide 'diverted) ;;; diverted.el ends here
UPDATE(2019-04-20): Source on github.
Wider web bookmarks
- Attic: Search Hundreds of Small and Local Stores and Boutiques.
- Awesome Search.
- Indieseek.xyz Directory.
- Million Short - What haven't you found?.
- Pinboard: social bookmarking for introverts.
- Qwant Lite.
- Startpage (claims most private seatch engine).
- UbuWeb.
- wiby.me - the search engine for classic websites.
- WikiArt.org - Visual Art Encyclopedia.
Compound interest calculations
Saving Tony Bedford's python snippets for calculating compound interest. Really just an excuse to fire up Emacs and play with org babel.
t = 20 # years r = 0.07 # rate pv = 200000.00 # present value fv = pv * (1+r)**t # future value print("Pension of %.2f at %d%% will be worth %.2f in %d years" % (pv, 100 * r, fv, t))
Pension of 200000.00 at 7% will be worth 773936.89 in 20 years
t = 20 # years r = 0.07 # rate pv = 200000.00 # present value n = 1 fv = pv * (1 + r/n)**(n*t) # future value print ("First formula calculates final value to: %.2f" % fv) fv = pv * (1 + r/n)**(n*1) # year 1 only print("Year %d: %.2f" % (1, fv)) for i in range (2, t+1): fv = fv * (1 + r/n)**(n*1) # Calculate one year at a time print("Year %d: %.2f" % (i, fv))
First formula calculates final value to: 773936.89 Year 1: 214000.00 Year 2: 228980.00 Year 3: 245008.60 Year 4: 262159.20 Year 5: 280510.35 Year 6: 300146.07 Year 7: 321156.30 Year 8: 343637.24 Year 9: 367691.84 Year 10: 393430.27 Year 11: 420970.39 Year 12: 450438.32 Year 13: 481969.00 Year 14: 515706.83 Year 15: 551806.31 Year 16: 590432.75 Year 17: 631763.04 Year 18: 675986.46 Year 19: 723305.51 Year 20: 773936.89
Building mu/mu4e on macOS
I've now built Emacs's mu/mu4e releases a handful of times on macOS. These are the steps, so I don't forget.
1.4
Updated steps for building mu/mu4e 1.4:
brew install gmime export CPPFLAGS="-I$(brew --prefix)/Cellar/gmime/3.2.3/include -I$(brew --prefix)/include" export LDFLAGS=-L$(brew --prefix)/Cellar/gmime/3.2.3/lib export PKG_CONFIG_PATH=$(brew --prefix)/Cellar/gmime/3.2.3/lib/pkgconfig:$(brew --prefix)/opt/libffi/lib/pkgconfig export EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs ./configure --prefix=$(~/local) make install
1.2
Recently built Emacs's mu/mu4e 1.2.0 from source on macOS. Steps:
brew install gmime export CPPFLAGS=-I$(brew --prefix)/Cellar/gmime/3.2.3/include export LDFLAGS=-L$(brew --prefix)/Cellar/gmime/3.2.3/lib export PKG_CONFIG_PATH=$(brew --prefix)/Cellar/gmime/3.2.3/lib/pkgconfig:$(brew --prefix)/opt/libffi/lib/pkgconfig ./configure --prefix=$(~/local) --disable-dependency-tracking make install
UPDATE(2019-04-16): Another approach at Irreal's Mu/mu4e 1.2 Available.
Reading spreadsheets with python/pandas
Via Daily python tip, a snippet to read xls files in python. This will come in handy. Saving for future.
Get set up with:
pip install pandas pip install xlrd
Read with:
import pandas xlf = pandas.ExcelFile("sheet.xlsx") print xlf.sheet_names
[u'my sheet']
Inserting numbers with Emacs multiple cursors
TIL that multiple cursor's mc/insert-numbers enables you to quickly enter increasing numbers for each cursor. I have mc/insert-numbers bound to # in region-bindings-mode-map. By default, sequence starts at 0, but invoking mc/insert-numbers with prefix enables you to quickly change that.
Came in handy when numbering an org table:
Brazil travel bookmarks
Bath travel bookmarks
Half marathon training
From https://assets.bupa.co.uk/~/media/images/healthmanagement/pdfs/half-marathon-beginner.pdf
Starting from week 4:
Week | Monday | Tuesday | Wednesday | Thursday | Friday | Saturday | Sunday |
---|---|---|---|---|---|---|---|
4 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins cross | Rest | (03/24) 60 mins |
6.7 km | 5.1 Km | - | - | ||||
41:51 m | 30:00 m | ||||||
61.3 Kg | 60.8 Kg | ||||||
5 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins interval | Rest | (03/31) 11 Km |
11.9 Km | - | 5.99 Km | 11.0 Km | ||||
80:00 m | 40 m | 60:08 m | |||||
6 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins interval/cross | Rest | (04/07) 13 Km |
7 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins interval | Rest | (04/14) 60 mins |
8 | Rest | 40 mins easy | 30 mins tempo | Rest | 50 mins interval/cross | Rest | (04/21) 16 Km |
9 | Rest | 40 mins easy | 30 mins tempo | Rest | 50 mins interval | Rest | (04/28) 8 Km |
10 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins interval/cross | Rest | (05/05) 19 Km |
11 | Rest | 40 mins easy | 30 mins tempo | Rest | 40 mins interval | Rest | (05/12) 10 Km |
12 | Rest | 40 mins easy | 30 mins tempo | Rest | 50 mins easy | Rest | (05/19) Race |
No Emacs frame refocus on macOS
This one's been bugging me for a while. On macOS, Emacs automatically focuses (raises) other frames when one is closed.
This has the unfortunate side-effect that I could be moved from one macOS desktop/space to another when closing an Emacs frame.
Finally managed do something about it. Since I install Emacs on macOS via homebrew, a small patch on emacs-plus recipe small patch on emacs-plus recipe did the job.
UPDATE: Pull request merged in d12frosted/emacs-plus.
The patch patch has been merged into d12frosted/homebrew-emacs-plus. To use:
brew tap d12frosted/emacs-plus brew install emacs-plus --without-spacemacs-icon --with-no-frame-refocus
Balance restored.
Checksums on linux/macOS
MD5
md5 file
SHA-1
shasum -a 1 file
SHA-256
shasum -a 256 file
Language server protocol (LSP) bookmarks
Copy from desktop to mobile via QR code
Marcin Borkowski has a nice tip to quickly copy text or URLs between desktop and mobile using QR codes.
Wrote a little elisp to do a similar thing using the clipboard via Emacs:
(defun ar/misc-clipboard-to-qr () "Convert text in clipboard to qrcode and display within Emacs." (interactive) (let ((temp-file (concat (temporary-file-directory) "qr-code"))) (if (eq 0 (shell-command (format "qrencode -s10 -o %s %s" temp-file (shell-quote-argument (current-kill 0))) "*qrencode*")) (switch-to-buffer (find-file-noselect temp-file t)) (error "Error: Could not create qrcode, check *qrencode* buffer"))))
ps. Encoding your WiFi access point password into a QR code shows how to encode WiFi access point passwords:
qrencode -o wifi.png "WIFI:T:WPA;S:<SSID>;P:<PASSWORD>;;"
More comprehensively:
SSID=SSID_GOES_HERE pwgen -s 63 > 00wifi.txt qrencode -o 00wifi.png "WIFI:T:WPA;S:${SSID};P:$(cat 00wifi.txt);;"
Parsing dates in Go
Ensure the reference time ("Mon Jan 2 15:04:05 -0700 MST 2006") is used in layout string.
For example:
package main import ( "fmt" "time" ) func main() { goodLayout := "January 2 2006" if t, err := time.Parse(goodLayout, "March 10 2019"); err != nil { fmt.Printf("%s\n", err) } else { fmt.Printf("%v\n", t) } badLayout := "January 2 2009" if t, err := time.Parse(badLayout, "March 10 2019"); err != nil { fmt.Printf("%s\n", err) } else { fmt.Printf("%v\n", t) } }
2019-03-10 00:00:00 +0000 UTC parsing time "March 10 2019" as "January 2 2009": cannot parse "19" as "009"
Life in the UK bookmarks
C language bookmarks
Video editing bookmarks
- Adding subtitles to your videos the easy way - Bernd Verst.
- Adventures in Netflix (screenshotting HDMI).
- Camera Stabilisation with FFmpeg - Adaptive Samples.
- DaVinci Resolve 17 | Blackmagic Design.
- digiKam.
- Dragon Quest 64: Non-Rectangular Video Cropping.
- FFmpeg - How to achieve a circular video overlay..?.
- GitHub - mifi/lossless-cut: The swiss army knife of lossless video/audio editing.
- GitHub - moxuse/Kusabi: Coding environment 3D graphics with PureScript..
- GitHub - whyboris/Video-Hub-App: Official repository for Video Hub App 2.
- HOW TO DATAMOSH: PART 1 - YouTube.
- How to trim video clips instantly without reencoding | Hacker News.
- Inserting a Watermark with FFmpeg (Linux Magazine).
- Learn FFmpeg the hard way (Hacker News).
- Non-Rectangular Video Cropping with FFMpeg | Hacker News.
- Trim Videos Instantly - Bernd Verst.
Icons bookmarks
Salt beef recipe
How to make salt beef (use 1.8kg brisket instead) and brining a brisket (celery and peppercorns) both from The Guardian were recommended by a friend.
Geneva travel bookmarks
Swapping Emacs ivy collections/sources
Ivy is great. I've been meaning to figure out a way to swap sources while running ivy. This would enable me to cycle through different sources using the existing search parameters.
At first look, 'ivy-set-sources seemed like the right choice, but it's used during setup to agregate sources. Subsequent 'ivy-set-sources calls are ignored during an 'ivy-read session.
There's an ivy feature request over at github with a similar goal in mind. Although the feature is not yet supported, there's a handy suggestion to use 'ivy-quit-and-run to quit the current command and run a different one.
With 'ivy-quit-and-run in mind, we can write our 'ar/ivy-read function to take a list of sources and add a little logic to cycle through them using a keybiding, in my case <left> and <right>.
;;; -*- lexical-binding: t; -*- (require 'cl) (cl-defstruct ar/ivy-source prompt collection action) (cl-defun ar/ivy-read (sources &key index initial-input) (let ((kmap (make-sparse-keymap)) (source)) (cl-assert (> (length sources) 0)) (when (null index) (setq index 0)) (setq source (nth index sources)) (define-key kmap (kbd "<right>") (lambda () (interactive) (ivy-quit-and-run (ar/ivy-read sources :index (if (>= (1+ index) (length sources)) 0 (1+ index)) :initial-input ivy-text)))) (define-key kmap (kbd "<left>") (lambda () (interactive) (ivy-quit-and-run (ar/ivy-read sources :index (if (< (1- index) 0) (1- (length sources)) (1- index)) :initial-input ivy-text)))) (ivy-read (ar/ivy-source-prompt source) (ar/ivy-source-collection source) :action (ar/ivy-source-action source) :initial-input initial-input :keymap kmap))) (defun ar/ivy-food-menu () (interactive) (ar/ivy-read (list (make-ar/ivy-source :prompt "Pizza: " :action (lambda (selection) (message "Selected pizza: %s" selection)) :collection (lambda (str pred v) (list "Bianca Neve - Mozzarella, Ricotta, Sausage, Extra Virgin Olive Oil, Basil" "Boscaiola - Mozzarella, Tomato Sauce, Sausage, Mushrooms, Extra Virgin Olive Oil, Basil" "Calzone - Ricotta, Ham, Mushrooms, Artichokes. Topped with Tomato Sauce and Extra Virgin Olive Oil." "Capricciosa - Mozzarella,Tomato Sauce, Prosciutto Cotto Ham, Mushrooms, Artichokes, Extra Virgin Olive Oil." "Carciofi - Mozzarella, Tomato Sauce, Artichokes, Extra Virgin Olive Oil, Basil." "Diavola - Mozzarella, Tomato Sauce, Spicy Salami, Extra Virgin Olive Oil, Basil." "Funghi - Mozzarella, Tomato Sauce, Mushrooms, Extra Virgin Olive Oil, Basil."))) (make-ar/ivy-source :prompt "Tacos: " :action (lambda (selection) (message "Selected taco: %s" selection)) :collection (lambda (str pred v) (list "Pork pibil - Slow cooked in citrus & spices, with pink pickled onions." "Grilled chicken & avocado - Ancho rub, guacamole & green tomatillo salsa." "Plantain - Sweet & spicy chipotle & crumbled feta." "Poblano pepper - Caramelised onions, corn & cashew nut mole." "Buttermilk chicken - Served crispy fried with habanero & white onion relish & spiced mayo." "Sustainable battered cod - mSC certified cod with shredded slaw, chipotle mayo & pickled cucumber." "Chargrilled steak - Avocado & chipotle salsas."))) (make-ar/ivy-source :prompt "Burgers: " :action (lambda (selection) (message "Selected burger: %s" selection)) :collection (lambda (str pred v) (list "The cheese - Aged beef patty with american cheese, gherkins, ketchup & mustard." "The yeah! - Aged beef patty with american cheese, gherkins, yeah! sauce & salad." "The yfc or hot yfc - Crispy chicken with lime or chipotle crema, lettuce, pickled onion & slaw." "The rancher - Grilled chicken with ranch dressing, bacon & salad." "The bubbah - Aged beef patty with smokey aubergine, pickled red cabbage, lettuce, roast toms, onions & cheddar." "The bulgogi - Sesame-spiced beef patty with miso mayo, pickled radish, onion, cucumber & spring onion." "The summer - Aged beef patty with sriracha mayo, lettuce, onion, toms, avo, cheddar & bacon."))))))
ps. Menu data from Star of Kings, Wahaca, and Pizzarino.
Podcast bookmarks
Emacs on macOS Mojave
Had issues running Emacs on macOS Mojave (blank unresponsive screen). Bleeding edge emacs-plus did the job:
brew tap d12frosted/emacs-plus brew install emacs-plus --without-spacemacs-icon --HEAD
brew info emacs-plus
d12frosted/emacs-plus/emacs-plus: stable 26.1, devel 26.1-rc1, HEAD GNU Emacs text editor https://www.gnu.org/software/emacs/ /Users/some-user/homebrew/Cellar/emacs-plus/HEAD-8fe21b0 (3,985 files, 123.0MB) * Built from source on 2019-01-12 at 09:26:09 with: --without-spacemacs-icon From: https://github.com/d12frosted/homebrew-emacs-plus/blob/master/Formula/emacs-plus.rb ==> Dependencies Build: pkg-config Recommended: little-cms2, gnutls, librsvg, imagemagick@6 Optional: dbus, mailutils ==> Requirements Optional: x11 ==> Options --with-ctags Don't remove the ctags executable that Emacs provides --with-dbus Build with dbus support --with-emacs-icons-project-EmacsIcon1 Using Emacs icon project EmacsIcon1 --with-emacs-icons-project-EmacsIcon2 Using Emacs icon project EmacsIcon2 --with-emacs-icons-project-EmacsIcon3 Using Emacs icon project EmacsIcon3 --with-emacs-icons-project-EmacsIcon4 Using Emacs icon project EmacsIcon4 --with-emacs-icons-project-EmacsIcon5 Using Emacs icon project EmacsIcon5 --with-emacs-icons-project-EmacsIcon6 Using Emacs icon project EmacsIcon6 --with-emacs-icons-project-EmacsIcon7 Using Emacs icon project EmacsIcon7 --with-emacs-icons-project-EmacsIcon8 Using Emacs icon project EmacsIcon8 --with-emacs-icons-project-EmacsIcon9 Using Emacs icon project EmacsIcon9 --with-emacs-icons-project-emacs-card-blue-deep Using Emacs icon project emacs-card-blue-deep --with-emacs-icons-project-emacs-card-british-racing-green Using Emacs icon project emacs-card-british-racing-green --with-emacs-icons-project-emacs-card-carmine Using Emacs icon project emacs-card-carmine --with-emacs-icons-project-emacs-card-green Using Emacs icon project emacs-card-green --with-mailutils Build with mailutils support --with-modern-icon Using a modern style Emacs icon by @tpanum --with-no-titlebar Experimental: build without titlebar --with-pdumper Experimental: build from pdumper branch and with increasedremembered_data size (--HEAD only) --with-x11 Experimental: build with x11 support --with-xwidgets Experimental: build with xwidgets support (--HEAD only) --without-cocoa Build a non-Cocoa version of Emacs --without-gnutls Build without gnutls support --without-imagemagick@6 Build without imagemagick@6 support --without-librsvg Build without librsvg support --without-libxml2 Build without libxml2 support --without-little-cms2 Build without little-cms2 support --without-modules Build without dynamic modules support --without-multicolor-fonts Build without a patch that enables multicolor font support --without-spacemacs-icon Build without Spacemacs icon by Nasser Alshammari --devel Install development version 26.1-rc1 --HEAD Install HEAD version ==> Caveats Emacs.app was installed to: /Users/some-user/homebrew/Cellar/emacs-plus/26.1 To link the application to default Homebrew App location: brew linkapps or: ln -s /Users/some-user/homebrew/Cellar/emacs-plus/26.1/Emacs.app /Applications --natural-title-bar option was removed from this formula, in order to duplicate its effect add following line to your init.el file (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . dark)) or: (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)) (add-to-list 'default-frame-alist '(ns-appearance . light)) If you are using macOS Mojave, please note that most of the experimental options are forbidden on Mojave. This is temporary decision. To have launchd start d12frosted/emacs-plus/emacs-plus now and restart at login: brew services start d12frosted/emacs-plus/emacs-plus Or, if you don't want/need a background service you can just run: emacs
Trying out Emacs pdf tools
Late to the party, giving pdf-tools a try.
The macOS install instructions have a prerequisite:
brew install poppler automake
Installed with:
(use-package pdf-tools :ensure t :mode ("\\.pdf\\'" . pdf-view-mode) :config (pdf-tools-install) (setq-default pdf-view-display-size 'fit-page) (setq pdf-annot-activate-created-annotations t))
ps. (pdf-tools-install) may not find libffi on macOS. Try:
(setenv "PKG_CONFIG_PATH" (f-join (file-name-as-directory (nth 0 (split-string (shell-command-to-string "brew --prefix")))) "Cellar" "libffi" "3.2.1" "lib" "pkgconfig"))
ASCII art generator bookmarks
Osaka travel bookmarks
Using OCR to create searchable pdfs from images
Used my phone to take a handful of photos of an article from a magazine. Wanted to convert the images to a searchable pdf on macOS.
This was straightforward, having already installed tesseract.
for i in IMG_3*.jpg; do echo $i; tesseract $i $(basename $i .tif) pdf; done
Should now have a handful of OCR'd pdfs:
ls *.jpg.pdf
IMG_3104.jpg.pdf IMG_3105.jpg.pdf IMG_3106.jpg.pdf IMG_3107.jpg.pdf
Finally, join all pdfs into one. Turns out macOS has a handy python script already installed. We can use it as:
/usr/bin/python "/System/Library/Automator/Combine PDF Pages.action/Contents/Resources/join.py" -o joined.pdf IMG_*pdf
ps. pdfgrep is great for searching pdfs.
Useful references
Audiobook providers bookmarks
- 1,000 Free Audio Books: Download Great Books for Free | Open Culture.
- BBC Sound Effects Archive Resource • Research & Education Space.
- Libro.fm (Libro.fm, Your Independent Bookstore for Digital Audiobooks).
- Online Courses & Lectures for Home Study and Lifelong Learning.
- The best free cultural and educational media on the web (Open Culture).
Cookbook bookmarks
Emailing pdfs to kindle from mu4e
Wanted to send a pdf to my kindle for some holiday reading. You can easily do this by emailing the pdf to your kindle-bound email address.
Now, I typically attach files when composing mu4e emails by using mml-attach-file, which attaches the file using <#part>…<#/part>. However, the Amazon service did not find the attached pdf, so no pdf was added to my Kindle.
Fortunately, I found a handy Reddit thread, leding me to a working solution. Wrapping the part using <#multipart type=mixed>…<#/multipart> did the job, using mml-insert-multipart, followed by mml-attach-file.
Resulting attachment should look something like:
<#multipart type=mixed> <#part type="application/pdf" filename="/path/to/file.pdf" disposition=attachment> <#/part> <#/multipart>
I should add a convenience elisp function for this, but that's for another time…
org tip: convert csv to table
Needed to import some csv data to an org table. Turns out org's got you covered out of the box with M-x org-table-create-or-convert-from-region bound to C-c |.
Sponsoring platform bookmarks
Artistic/creative bookmarks
Marketing bookmarks
Bluetooth low energy (BLE) bookmarks
Fun project bookmarks
Snowboarding bookmarks
Scam bookmarks
Passive income bookmarks
DWIM ivy quit
"Do-what-I-mean" (DWIM) functions enable us to introduce new Emacs powers to existing workflows without incurring the typical cost of remembering multiple related functions or introducing yet another key binding. DWIM functions invoke other functions, based on current context.
I wanted a small tweak in Ivy's `minibuffer-keyboard-quit' invocation, commonly invoked via C-g key binding:
- If we have text selected in minibuffer, deselect it.
- If we have any text in minibuffer, clear it.
- If no text in minibuffer, quit.
Added `ar/ivy-keyboard-quit-dwim' for this purpose. Binding it to C-g in ivy-minibuffer-map:
(use-package ivy :ensure t :bind (:map ivy-minibuffer-map ("C-g" . ar/ivy-keyboard-quit-dwim)) :config (defun ar/ivy-keyboard-quit-dwim () "If region active, deactivate. If there's content, clear the minibuffer. Otherwise quit." (interactive) (cond ((and delete-selection-mode (region-active-p)) (setq deactivate-mark t)) ((> (length ivy-text) 0) (delete-minibuffer-contents)) (t (minibuffer-keyboard-quit)))))
Diffing directories content size
Needed to diff two directories, but only interested in file size changes. diff, find, sort, and stat seem to do the job:
diff <(find dir1 -type f -exec stat -f '%N %z' '{}' \; | sort) <(find dir2 -type f -exec stat -f '%N %z' '{}' \; | sort)
1,3c1,2 < dir1/one.txt 14 < dir1/subdir/file.txt 5 < dir1/three.txt 7 --- > dir2/one.txt 19 > dir2/two.txt 0
Note: Using diff, find, sort, and stat on macOS.
Update 1
I've since learned about mtree (thanks Roman!). A nice utility to add to the toolbox.
mtree -p emacs-25.1 -c -k size -d
# user: me # machine: my-machine # tree: /path/to/emacs-25.1 # date: Wed Dec 5 22:21:07 2018 # . /set type=dir . size=1152 # ./admin admin size=960 # ./admin/charsets charsets size=544 # ./admin/charsets/glibc glibc size=3392 # ./admin/charsets/glibc .. # ./admin/charsets/mapfiles mapfiles size=640 # ./admin/charsets/mapfiles ..
Update 2
I've added Emacs ediff to the mix:
(require 'f) (defun ar/ediff-dir-content-size () "Diff all subdirectories (sizes only) in two directories." (interactive) (let* ((dir1-path (read-directory-name "Dir 1: ")) (dir2-path (read-directory-name "Dir 2: ")) (buf1 (get-buffer-create (format "*Dir 1 (%s)*" (f-base dir1-path)))) (buf2 (get-buffer-create (format "*Dir 2 (%s)*" (f-base dir2-path))))) (with-current-buffer buf1 (erase-buffer)) (with-current-buffer buf2 (erase-buffer)) (shell-command (format "cd %s; find . -type d | sort | du -h" dir1-path) buf1) (shell-command (format "cd %s; find . -type d | sort | du -h" dir2-path) buf2) (ediff-buffers buf1 buf2)))
Swift nil-coalescing operator
Paul Hudson, over at Hacking with Swift, has written The Complete Guide to Optionals in Swift. One of the many highlights is the nil-coalescing operator. If you're a fan of the C-like syntax in ternary operations, you'd enjoy chaining with Swift's nil-coalescing operator:
let players = [ "goose": "run!" ] let move = players["duck1"] ?? players["duck2"] ?? players["duck3"] ?? players["goose"] print("\(String(describing: move))")
Optional("run!")
ps. Swift snippet run on Emacs org babel's ob-swift. See Multiline Swift strings for details.
Ocado vs Asda (org table)
Someone handed me an Ocado shopping voucher for 30% off. Sounded promising, even for a one-off.
With my Money or Your Life hat on, I took a closer look for potential savings. Results were disappointing, when compared to alternatives like Asda.
Here's a table comparing Ocado (30% off) and Asda (no discount):
Ocado | Asda | |
---|---|---|
Coconut Merchant Organic Raw Extra Virgin Coconut Oil 500ml | 6.74 | |
KTC 100% pure coconut oil | 2.00 | |
Waitrose Love Life Popcorn Maize 510g | 1.50 | |
Cypressa Popping Corn 2x500g = 1000g | 1.50 | |
Whitworths Ground Almonds | 2.00 | |
Whitworths Ground Almonds | 1.60 | |
Total | £ 5.10 | |
-30% | £ 7.17 |
On the upside, Ocado has plenty of items I cannot find at Asda. May be a good opportunity to get these items at a discount.
Emacs org tables
Small tables are the perfect use-case for Emacs org-mode tables. Been a while since I used one, so great timing for a little refresh.
Here's the org source for the table above (prior to exporting to HTML):
|-------------------------------------------------------------+--------+--------| | | Ocado | Asda | |-------------------------------------------------------------+--------+--------| | [[https://www.ocado.com/webshop/product/Coconut-Merchant-Organic-Raw-Extra-Virgin-Coconut-Oil/372144011][Coconut Merchant Organic Raw Extra Virgin Coconut Oil 500ml]] | 6.74 | | | [[https://groceries.asda.com/product/oils/ktc-coconut-hair-oil/910000033621][KTC 100% pure coconut oil]] | | 2.00 | | [[https://www.ocado.com/webshop/product/Waitrose-Love-Life-Popcorn-Maize/25130011][Waitrose Love Life Popcorn Maize 510g]] | 1.50 | | | [[https://groceries.asda.com/promotion/2-for-pound-1.50/ls89129][Cypressa Popping Corn 2x500g = 1000g]] | | 1.50 | | [[https://www.ocado.com/webshop/product/Whitworths-Ground-Almonds/275684011][Whitworths Ground Almonds]] | 2.00 | | | [[https://groceries.asda.com/product/baking-nuts-seeds-fruit/whitworths-ground-almonds/910000797981][Whitworths Ground Almonds]] | | 1.60 | |-------------------------------------------------------------+--------+--------| | Total | | £ 5.10 | |-------------------------------------------------------------+--------+--------| | -30% | £ 7.17 | | |-------------------------------------------------------------+--------+--------| #+TBLFM: @8$3=vsum(@2$3..@7$3);£ %.2f::@9$2=vsum(@2$2..@7$2) * 0.7;£ %.2f
Execute org blocks as root
Been saving admin code snippets in my own org source blocks, some requiring root access. Handy for keeping tiny self-documented scripts to easily bootstrap other machines. TIL org source block's :dir argument can be used to run block as root by using tramp syntax: :dir /sudo::
As user: #+BEGIN_SRC sh whoami #+END_SRC #+RESULTS: : user As root: #+BEGIN_SRC sh :dir /sudo:: whoami #+END_SRC #+RESULTS: : root
Inline Swift computed properties
Via objc.io and Max Howell's retweet, TIL about Swift's inline computed properties. Another one to try on Org Babel.
func greetWorld() { var message = "hello" var betterMessage: String { return "\(message) world" } print(betterMessage) } greetWorld()
hello world
Multiline Swift strings
Paul Hudson's tweet introduced me to Swift's multiline string indentation control using closing quotes. Neat!
Being an org-mode fan, I thought I'd give Swift multiline strings a try using Org Babel's ob-swift. I get to verify it and document at the same time. Win.
Swift org mode source blocks (ie. BEGIN_SRC/END_SRC) can be added as follows:
#+BEGIN_SRC swift :exports both print(""" Hello World """) print(""" Hello World """) #+END_SRC #+RESULTS: : Hello World : Hello World
By pressing C-c C-c anywhere in the code block, the snippet is executed and its output captured in the RESULT block. Super handy for quickly trying out snippets and keeping as future reference.
As a bonus, the above blocks can be exported to HTML (amongst other formats). With some styling, it looks as follows:
print(""" Hello World """) print(""" Hello World """)
Hello World Hello World
Quickly swapping elfeed filters
I seem to be more efficient in getting through rss feeds by individually browsing through related content. That is, I can get through all Emacs entries a lot faster if I look at Emacs content exclusively, instead of mixing with say BBC news. Elfeed filters are great for filtering related content.
I wanted a way to easily switch through my typical categories of related content by quickly changing elfeed filters using a completion framework.
Emacs's completing-read plays nicely with your favorite completing framework (mine is ivy). With a couple of functions, we can get Emacs to ask us for the filtering category using human-readable options and quickly presenting related content. Binding the new functionality to <tab> is working well for me.
(use-package elfeed :ensure t :commands elfeed :bind (:map elfeed-search-mode-map ("<tab>" . ar/elfeed-completing-filter)) :config (defun ar/elfeed-filter-results-count (search-filter) "Count results for SEARCH-FILTER." (let* ((filter (elfeed-search-parse-filter search-filter)) (head (list nil)) (tail head) (count 0)) (let ((lexical-binding t) (func (byte-compile (elfeed-search-compile-filter filter)))) (with-elfeed-db-visit (entry feed) (when (funcall func entry feed count) (setf (cdr tail) (list entry) tail (cdr tail) count (1+ count))))) count)) (defun ar/elfeed-completing-filter () "Completing filter." (interactive) (let ((categories (-filter (lambda (item) (> (ar/elfeed-filter-results-count (cdr item)) 0)) '(("All" . "@6-months-ago +unread") ("BBC" . "@6-months-ago +unread +bbc") ("Dev" . "@6-months-ago +unread +dev") ("Emacs" . "@6-months-ago +unread +emacs") ("Health" . "@6-months-ago +unread +health") ("Hacker News" . "@6-months-ago +unread +hackernews") ("iOS" . "@6-months-ago +unread +ios") ("Money" . "@6-months-ago +unread +money"))))) (if (> (length categories) 0) (progn (ar/elfeed-view-filtered (cdr (assoc (completing-read "Categories: " categories) categories))) (goto-char (window-start))) (message "All caught up \\o/")))))
We don't actually need two functions, but ar/elfeed-filter-results-count enables us to list only those feeds that actually have new content. The list will shrink as we get through our content. When no content is left, we get a little celebratory message.
Converting docx to pdf on macOS
Wanted to convert a docx document to pdf on macOS. Pandoc to the rescue, but first needed pdflatex installed:
pandoc -t latex some.docx -o some.pdf
pdflatex not found. Please select a different --pdf-engine or install pdflatex
Installed pdflatex on macOS with:
brew install mactex
Can also use HTML5. Install wkhtmltopdf with:
brew install Caskroom/cask/wkhtmltopdf
Convert with:
pandoc -t html5 some.docx -o some.pdf
Faster elfeed browsing with paging
Following up from faster junk mail deletion with mu4e, elfeed is another candidate for enabling actions on pages. In this case, marking rss entries as read, page by Page.
If on use-package, the function can defined and bound to the "v" key using:
(use-package elfeed :ensure t :bind (:map elfeed-search-mode-map ("v" . ar/elfeed-mark-visible-as-read)) :config (defun ar/elfeed-mark-visible-as-read () (interactive) (require 'window-end-visible) (set-mark (window-start)) (goto-char (window-end-visible)) (activate-mark) (elfeed-search-untag-all-unread) (elfeed-search-update--force) (deactivate-mark) (goto-char (window-start))))
Faster junk mail deletion with mu4e
It's been roughly 5 months since my mu4e email migration. Happy with my choice. Mu4e is awesome.
I now have 4 email accounts managed by mu4e, and unfortunately receiving lots of junk mail.
I regularly peek at junk folders for false positives and delete junk email permanently. I've been wanting a quick way to glance at junk mail and easily delete page by page.
Deleting emails page by page is not supported in mu4e by default. Fortunately, this is Emacs and we can change that™.
There's a handy package by Roland Walker called window-end-visible. We can use it to select mu4e emails by page and subsequently glue it all together to enable deleting emails by page.
(require 'mu4e) (require 'window-end-visible) (defun ar/mu4e-delete-page () (interactive) (set-mark (window-start)) (goto-char (window-end-visible)) (activate-mark) (mu4e-headers-mark-for-trash) (mu4e-mark-execute-all t) (deactivate-mark) (goto-char (window-start)))
I'm a use-package fan, so I use it to bind the "v" key to delete visible emails (by page).
(use-package mu4e :bind (:map mu4e-headers-mode-map ("v" . ar/mu4e-delete-page))
Working with vultr's ipv6-only instances
Having recently read Your Money or Your Life, I've been cutting down on personal expenses wherever possible. Specially recurring expenses which include monthly charges from VPS hosting. Let's reduce those charges…
My VPS needs are fairly small (mostly hobby and tinkering). Vultr† has a plan for $2.50/month (not seen anything cheaper). The caveat for the price, you get ipv6 access only (ie. 0000:1111:2222:3333:4444:5555:6666:7777:8888).
So far so good, but my ISP doesn't yet support ipv6:
$ ping6 0000:1111:2222:3333:4444:5555:6666:7777:8888 $ ping6: UDP connect: No route to host
Fortunately, we can still work with ipv6 by using a tunnel (TIL about Hurricane Electric's tunnel broker). After signing up and creating a tunnel, they conveniently show you "Example Configurations" from the "Tunnel Details" menu. In my case, macOS:
ifconfig gif0 create ifconfig gif0 tunnel <ipv4 client broker IP or DCHP internal IP> <ipv4 server IP> ifconfig gif0 inet6 <ipv6 client broker IP> <ipv6 server IP> prefixlen 128 route -n add -inet6 default <ipv6 server IP>
Note: If behind router, use the DHCP internal IP.
After configuring with ifconfig, all is good. Yay!
$ ping6 0000:1111:2222:3333:4444:5555:6666:7777:8888 PING6(56=40+8+8 bytes) 2001:111:22:aaa::2 --> 0000:1111:2222:3333:4444:5555:6666:7777:8888 16 bytes from 0000:1111:2222:3333:4444:5555:6666:7777:8888, icmp_seq=0 hlim=52 time=270.019 ms 16 bytes from 0000:1111:2222:3333:4444:5555:6666:7777:8888, icmp_seq=1 hlim=52 time=290.834 ms 16 bytes from 0000:1111:2222:3333:4444:5555:6666:7777:8888, icmp_seq=2 hlim=52 time=311.960 ms 16 bytes from 0000:1111:2222:3333:4444:5555:6666:7777:8888, icmp_seq=3 hlim=52 time=330.902 ms
I'm an ipv6 noob. I mostly need ssh access. My typical usages need small tweaks.
For ssh:
ssh -6 username@0000:1111:2222:3333:4444:5555:6666:7777:8888
For scp:
scp -6 file.txt username@\[0000:1111:2222:3333:4444:5555:6666:7777:8888\]:/remote/dir/
† I get $10 credit if you use this affiliate link. Thank you.
Shaving bookmarks
Buy it for life bookmarks
Rust bookmarks
Fonts bookmarks
Encrypted disk image on macOS
Sheffield travel bookmarks
Headsphones bookmarks
macOS app bookmarks
- Customizing the Cocoa Text System.
- dmgbuild - A command line tool to build .dmg files.
- GetStream/Winds: macOS rss reader.
- GitHub - herrbischoff/awesome-macos-command-line.
- GitHub - koekeishiya/yabai: A tiling window manager for macOS based on binary.
- GitHub - TermiT/Flycut: Clean and simple clipboard manager for developers.
- Hidden Bar: macOS utility to hide unused menu bar icons, written in Swift.
- LaunchBar 6 (can I implement flows in Emacs)?.
- List of open source applications for macOS (Hacker News).
- Mac Open Web, by Brian Warren.
- Mac Troubleshooting Summary – The Eclectic Light Company.
- MachObfuscator/README.md at master · kam800/MachObfuscator · GitHub.
- macOS · Papers, Slides and Thesis Archive.
- My iPad Setup.
- My wonderful world of macOS.
- my-mac-os: My wonderful world of macOS.
- NetNewsWire 5.0 Relaunches as an Open-Source RSS Reader for the Mac – The Sweet Setup.
- PDF Editor - PDFpen - Edit PDF Files (Smile Software).
- PDF OCR X - Mac & Windows OCR Software to convert PDFs and Images to Text.
- RESTed - Simple HTTP Requests on the Mac App Store.
Gaming bookmarks
Lua bookmarks
Skin product bookmarks
Sustainability bookmarks
Investment platform bookmarks
- Backtest Portfolio Asset Allocation.
- Bravos.
- CMLviz.com - BETA.
- DEGIRO - Online Stock Trading - Stockbroking (cheaper?).
- Free Stock API for Realtime and Historical Data (IEX).
- Freetrade - Free Stock Investing.
- Halifax UK | Buying and selling (Sharedealing).
- Hargreaves Lansdown (ISAs, pensions, funds and shares).
- How I have automated my #algotrading and spend less than ₹10.
- IWeb Share Dealing (cheaper?).
- Lend to UK Businesses | Investment (Funding Circle).
- RateSetter Peer To Peer Lender (P2P Investing and Borrowing).
- Stock Portfolio Management Software (Stock Portfolio Organizer).
- StockDaddy - Free, real-time, easy to use stock portfolio tracker.
- StockLight - Australia's premier investing app.
- Stocks and cryptocurrency portfolio tracker (wallmine).
Minimalist bookmarks
Recover from Time Machine's "backup already in use"
Started seeing "backup already in use" error from my daily Time Machine backups, against my Synology. Disabling and re-enabling AFP did the job (via Synology -> Control Panel -> Files Services -> Enable AFP service).
CMake bookmarks
GTD/Get things done bookmarks
Pandoc bookmarks
Mauritius travel bookmarks
- 20 Amazing Things to Do in Mauritius (2020 Guide).
- Corson vanilla tea.
- Ile aux Cerfs (Mauritius) - Jonathan/Vanessa excursions.
- Le Morne Brabant (Wikipedia).
- Mauritius Beaches - the Best Beaches in Mauritius - Mauritius Attractions.
- Mauritius beaches: pictures, information, resorts, sights.
- Mauritius sights, large pictures, best things to see and do.
- https://www.maurinet.com/tourist_information/public_beaches][Public Beaches">Mauritius Island Online]].
- The 12 best beaches in Mauritius 2020 {with map and photos}.
- The Best Markets in Mauritius.
- The Best Spots to Eat Roti in Port Louis, Mauritius.
- The Essential Guide to Port Louis' Central Market in Mauritius.
Scala bookmarks
Actionable URLs in Emacs buffers
Should have enabled actionable URLs in my Emacs buffers long ago. Can now click or press return to follow links. It's great on eshell, compilation buffers, async shell commands, code, etc.
(use-package goto-addr :hook ((compilation-mode . goto-address-mode) (prog-mode . goto-address-prog-mode) (eshell-mode . goto-address-mode) (shell-mode . goto-address-mode)) :bind (:map goto-address-highlight-keymap ("<RET>" . goto-address-at-point) ("M-<RET>" . newline)) :commands (goto-address-prog-mode goto-address-mode))
Bazel bookmarks
- Bazel_with_GTest: C++ project skeleton with Bazel & GTest.
- Build mobile apps with Bazel. Part 2: iOS.
- GitHub - bazelbuild/rules_docker: Rules for building and handling Docker images with Bazel.
- GitHub - jin/awesome-bazel: A curated list of Bazel rules, tooling and resources.
- Using Bazel to help fix flaky tests - Jake McCrary.
Palestine travel bookmarks
Enabling Control-Meta(⌘)-D on macOS
I use command (⌘) as my Emacs Meta key. Recently discovered C-M-d is not available to Emacs for binding keys on macOS. Stack Exchange had the workaround:
defaults write com.apple.symbolichotkeys AppleSymbolicHotKeys -dict-add 70 '<dict><key>enabled</key><false/></dict>'
Recycling bookmarks
Comoro islands travel bookmarks
France travel bookmarks
Corsica travel bookmarks
Mozambique travel bookmarks
M-r history search in git-commit-mode
I've grown accustomed to M-r bindings to search Emacs history. Been wanting similar functionality to search commit message history. Turns out log-edit-comment-ring has some of my local commit message history. Feeding it to completing-read gives me an easily searchable history when using a completing framework like ivy or helm:
(defun ar/git-commit-search-message-history () "Search and insert commit message from history." (interactive) (insert (completing-read "History: " ;; Remove unnecessary newlines from beginning and end. (mapcar (lambda (text) (string-trim text)) (ring-elements log-edit-comment-ring)))))
Now we bind it to M-r and we're good to go:
(bind-key "M-r" #'ar/git-commit-search-message-history git-commit-mode-map)
May also want to persist log-edit-comment-ring across Emacs sessions by adding log-edit-comment-ring to savehist variables. Also ensure savehist-mode is enabled:
(add-to-list 'savehist-additional-variables log-edit-comment-ring) (savehist-mode +1)
Morning smoothie
Big fan of my morning power smoothie. Best deals I've found so far:
- Almonds (BuyWholeFoodsOnline.co.uk, £1.06/100g @ Amazon).
- Banana.
- Blueberries.
- Chia seeds (£4.99/Kg @ Real Food Source).
- Cocoa powder (Active foods, £ 1.7/100g @ Bulk Powders).
- Coconut water (Innocent, £0.37/100ml @ Tesco).
- Crunchy peanut butter (£ 4.99/Kg @ Real Food Source).
- Matcha powder (Active £6.8/100g @ Bulk Powders).
- Strawberries.
ps. I have no affiliation to either retailer. Prices may change.
Installing ludget (ledger visualization
Needed python3:
brew install python3
Use pip3 to install ludget:
pip3 install ludget
Ledger bookmarks
- "Full-fledged Hledger" Tutorial (interesting approach with great traceability and regeneration).
- "Full-fledged Hledger" Tutorial.
- Accounting and financial statements (Khan Academy).
- Accounting in Plain Text, Part 1 – cvilleFOSS.
- Command Line Accounting - A look at the various ledger ports (mkauer).
- Conquering Your Finances with Emacs and Ledger : emacs.
- Conquering your finances with Emacs and Ledger.
- Convert a CSV file (comma separated values) from your bank into ledger format.
- Envelope Budgeting with ledger.
- Examples of recent and older CSV rules files for ledger.
- full-fledged-hledger: Tutorial on Hledger setup.
- GitHub - barrucadu/finances: A small tool to visualise my hledger journal..
- GitHub - Clever/csvlint: library and command line tool that validates a CSV file.
- Here's how you use ledger to account for Bitcoin transactions.
- Hledger Flow: Step-By-Step.
- Importing transactions from bank. : plaintextaccounting (Reddit).
- Introduction to ledger and text-based accounting | Patrick Skiba.
- Introduction to plain text accounting (sirodoht blog).
- Ledger CLI cheatsheet.
- Ledger CSV format cheatsheet.
- Ledger examples cheatsheet.
- Ledger periods cheatsheet.
- Ledger Practices - Felix Crux.
- Ledger queries cheatsheet.
- Ledger Report Scripts (tested on macOS Mojave).
- Ledger, a powerful CLI accounting tool (Hacker News).
- ledger/ledger-mode tips and tricks? (Reddit).
- Ledger: Command-Line Accounting (convert csv command).
- Ledger: Command-Line Accounting (documentation).
- ludget: ledger-cli data visualization.
- Memo's personal Finance post on plain-text accounting.
- Plain Text Accounting, a guide to Ledger and friends - plaintextaccounting.org (comparisons).
- Plain Text Accounting, a guide to Ledger and friends - plaintextaccounting.org (import).
- Program your Finances: Command-line Accounting (Pete Keen).
- Report Scripts for Ledger CLI with Gnuplot (日光漫想).
- Show HN: Ledger-analytics – Analytics for ledger-cli (Hacker News).
- Terencio's Ledger Emacs config.
- The Plain Text Project.
- TIP: How I use ledger to track my money : emacs.
- Tracking Investments in Lots with Hledger.
- Unrealized gains : plaintextaccounting.
- Using Ledger for YNAB-like envelope budgeting.
- Visualise your finances with hledger, InfluxDB, and Grafana.
- Ways to Categorize Your Spending (Mint).
- Who's using ledger? · ledger/ledger Wiki.
Tip: Convert .texi to .info
Convert with:
makeinfo doc.texi
View with:
Open in Emacs and render as info with:
(defun ar/format-info-mode () (interactive) (let ((file-name (buffer-file-name))) (kill-buffer (current-buffer)) (info file-name)))
Marking 20k emails as read
Mbsync and mu4e are great for syncing and handling IMAP email. I've now migrated 4 email addresses, including an old Yahoo account.
I wanted to mark all my Yahoo unread emails as read. Yahoo's webmail enables marking 500 emails at a time, making the process a little tedious.
Mu-discuss has a handy thread, highlighting that moving/renaming synced messages (in your local file system) would do the job. This worked well for me.
Let's do just that…
WARNING: Copy a small sample of your mails to a separate directory and run some trials until you feel comfortable.
Find your mail directory.
cd path/to/mail
Peek at the messages you'd like to mark unread:
ls -1 new/
1529958027.57518_11.mbp,U=8415:2, 1531346210.38822_3.mbp,U=8741:2, 1532464801.21057_1.mbp,U=9028:2, 1532464801.21057_2.mbp,U=9029:2,
Rename message files by appending "S" to their filename and moving from new/ to cur/ directory.
for FILE in new/*; do mv "${FILE}" cur/$(basename "${FILE}")S; done;
We can verify the move.
ls -1 cur/
1529958027.57518_11.mbp,U=8415:2,S 1531346210.38822_3.mbp,U=8741:2,S 1532464801.21057_1.mbp,U=9028:2,S 1532464801.21057_2.mbp,U=9029:2,S
Let's sync the local changes.
mbsync -Va
…and we're done ;)
Show iOS simulator touches
TIL from this tweet, that you can enable showing touches on iOS simulator. This is handy for making nicer screencasts.
defaults write http://com.apple .iphonesimulator ShowSingleTouches 1
Amsterdam travel bookmarks
Hardware bookmarks
fitbit API, org babel, and gnuplot
Retook running recently. Took the dust off my aria scale and used the opportunity to check out fitbit's API.
First register your app at dev.fitbit.com/apps/new and get a client_id=AABBCC.
You'll also need your USER_ID, from your Fitbitx user profile.
We'll also need a token. I used the implicit grant flow URL in my browser and extracted access_token=TOKEN.
Now let's wire up two org source blocks to fetch the data and subsequently plot using gnuplot.
It's pretty neat. You can take the output from one source block and use it as input to another.
We use curl to fetch data from fitbit's API and pipe through jq and sed to massage the output format into two columns.
Note: Before using gnuplot in org babel, you'll need to install the gnuplot package and add to babel languages.
(use-package gnuplot :ensure t) (use-package ob :config (org-babel-do-load-languages 'org-babel-load-languages '((gnuplot . t))))
curl -s -H "Authorization: Bearer TOKEN" https://api.fitbit.com/1/user/USER_ID/body/weight/date/2018-06-09/2018-07-11.json | jq '.[][] | "\(.dateTime) \(.value)"' | sed 's/"//g'
2018-06-09 | 65.753 |
2018-06-10 | 65.762 |
2018-06-11 | 65.771 |
2018-06-12 | 65.78 |
2018-06-13 | 65.789 |
2018-06-14 | 65.798 |
2018-06-15 | 65.807 |
2018-06-16 | 65.816 |
2018-06-17 | 65.825 |
2018-06-18 | 65.85 |
2018-06-19 | 65.96 |
2018-06-20 | 64.1 |
2018-06-21 | 65.64 |
2018-06-22 | 65.47 |
2018-06-23 | 65.515 |
2018-06-24 | 65.56 |
2018-06-25 | 65.605 |
2018-06-26 | 65.65 |
2018-06-27 | 65.18 |
2018-06-28 | 64.49 |
2018-06-29 | 64.49 |
2018-06-30 | 64.41 |
2018-07-01 | 64.33 |
2018-07-02 | 64.25 |
2018-07-03 | 64.17 |
2018-07-04 | 64.55 |
2018-07-05 | 64.39 |
2018-07-06 | 64.33 |
2018-07-07 | 65.06 |
2018-07-08 | 63.28 |
2018-07-09 | 63.4 |
2018-07-10 | 64.22 |
2018-07-11 | 63.95 |
Now feed the two column data to gnuplot.
reset set title "My recent weight" set xdata time set timefmt '%Y-%m-%d' set format x "%d/%m/%y" set term png set xrange ['2018-06-09':'2018-07-11'] plot data u 1:2 with linespoints title 'Weight in Kg'
Fetching data and plotting through org babel and gnuplot is pretty sweet. I've barely scratched the surface. There's more at Org-babel-gnuplot and Plotting tables in Org-Mode using org-plot. Either way, this is another Emacs super power to keep in the toolbox.
PIPESTATUS for all return codes
From @saruspete's tweet, ${PIPESTATUS[@]} gives ya all piped commands' return codes:
echo foo | grep bar | tr z a | cat echo ${PIPESTATUS[@]}
0 1 0 0
Emacs utilities for your OS
Narrowing utilities are a wonderful way of increasing productivity. I have a few workflows using Emacs's Helm framework.
There are great productivity boosters like Alfred and Quicksilver for macOS, with batteries included.
If you're a tinkerer, you'd enjoy the powerful Hammerspoon. Like elisp gluing all things Emacs, Hammerspoon uses Lua to glue all things macOS. You can build your own narrowing utilities using chooser and a little Lua.
local chooser = hs.chooser.new(function(choice) hs.alert.show(choice['text']) end) chooser:choices({ { ["text"] = "Alfred\n", ["subText"] = "macOS only\n", }, { ["text"] = "Quicksilver\n", ["subText"] = "macOS only\n", }, { ["text"] = "Hammerspoon\n", ["subText"] = "macOS only\n", }, { ["text"] = "Emacs\n", ["subText"] = "is everywhere :)\n", }, }) chooser:show()
Howard Abrams's post on Capturing Content for Emacs inspired me to look at gluing Emacs and macOS to launch my own cross-platform narrowing utilities.
I've also taken this opportunity to look at Oleh Krehel's wonderful completion package: Ivy. We can use it to build a macOS narrowing utility.
Ivy is remarkably easy to use. Turns out, ivy-read is all you need. A simple Emacs completion can be accomplished with little elisp.
(ivy-read "Hello ivy: " '("One " "Two " "Three " "Four "))
Pretty nifty. Let's make this completion more accessible from the rest of the OS. To do so, we create a separate Emacs frame and make it pretty. We also want it to interact with the OS. We'll use ivy-read's :action to invoke a tiny bit of AppleScript.
Oh and we'll also use some funny quotes to tease ourselves about our beloved editor.
(with-current-buffer (get-buffer-create "*modal-ivy*") (let ((frame (make-frame '((auto-raise . t) (background-color . "DeepSkyBlue3") (cursor-color . "MediumPurple1") (font . "Menlo 15") (foreground-color . "#eeeeec") (height . 20) (internal-border-width . 20) (left . 0.33) (left-fringe . 0) (line-spacing . 3) (menu-bar-lines . 0) (minibuffer . only) (right-fringe . 0) (tool-bar-lines . 0) (top . 48) (undecorated . t) (unsplittable . t) (vertical-scroll-bars . nil) (width . 110))))) (set-face-attribute 'ivy-minibuffer-match-face-1 frame :background nil :foreground nil) (set-face-attribute 'ivy-minibuffer-match-face-2 frame :background nil :foreground "orange1") (set-face-attribute 'ivy-minibuffer-match-face-3 frame :background nil :foreground "orange1") (set-face-attribute 'ivy-minibuffer-match-face-4 frame :background nil :foreground "orange1") (set-face-attribute 'ivy-current-match frame :background "#ffc911" :foreground "red") (set-face-attribute 'minibuffer-prompt frame :foreground "grey") (let ((ivy-height 20) (ivy-count-format "")) (ivy-read "Emacs acronyms: " '(" Emacs: Escape-Meta-Alt-Control-Shift " " Emacs: Eight Megabytes And Constantly Swapping " " Emacs: Even a Master of Arts Comes Simpler " " Emacs: Each Manual's Audience is Completely Stupified " " Emacs: Eventually Munches All Computer Storage " " Emacs: Eradication of Memory Accomplished with Complete Simplicity " " Emacs: Easily Maintained with the Assistance of Chemical Solutions " " Emacs: Extended Macros Are Considered Superfluous " " Emacs: Every Mode Accelerates Creation of Software " " Emacs: Elsewhere Maybe All Commands are Simple " " Emacs: Emacs Makes All Computing Simple " " Emacs: Emacs Masquerades As Comfortable Shell " " Emacs: Emacs My Alternative Computer Story " " Emacs: Emacs Made Almost Completely Screwed " " Emacs: Each Mail A Continued Surprise " " Emacs: Eating Memory And Cycle-Sucking " " Emacs: Elvis Masterminds All Computer Software " " Emacs: Emacs Makes A Computer Slow" ) :action (lambda (funny-quote) (async-shell-command (format "osascript -e 'tell app \"System Events\" to display dialog \"%s\" buttons {\"OK\"}'" funny-quote))) :unwind (lambda () (shell-command "/Applications/Hammerspoon.app/Contents/Resources/extensions/hs/ipc/bin/hs -c 'backFromEmacs()'") (delete-frame) (other-window 1))))))
So where's all this going? I wrote a utility to extract all links from this page's org file and make them easily searchable from anywhere on macOS by invoking ⌥-W.
The keys are bound using Lua, Hammerspoon, and emacsclient. This works well on macOS, but there are alternatives for other operating systems.
hs.execute("emacsclient -ne \""..elisp.."\" -s /tmp/emacs*/server")
Here's the resulting utility in action:
These integrations look promising. They enable me to bring cross-platform Emacs utilities into areas I hadn't considered.
Web serving tools bookmarks
- Certbot: Automatically enable HTTPS on your website, deploying Let's Encrypt certificates.
- How to configure WireGuard to tunnel traffic from a macOS client through a Debian server with IPv4 and IPv6.
- HTTPS Is Easy (Irreal).
- I made my own WireGuard VPN server (Hacker News).
- JSON:API — A specification for building APIs in JSON.
- MirageOS: high-performance network applications across a variety of cloud computing and mobile platforms.
- nginxconfig.io.
- Nice nginx features for developers | There is no magic here.
- Poor man's way of handling 1.3 million web request.
- Postman (API Development Environment).
- quark: an extremely small and simple HTTP GET/HEAD-only web server for static content (suckless.org tools).
- The Missing Wireguard Documentation.
- Web Authentication for Actual Humans, Part Two - DEV Community.
URL shortener bookmarks
Trying out mu4e with mbsync
The email fun in Emacs continues. After a few weeks since I started using mu4e and offlineimap, I'm sold. Both are awesome. Mbsync is an offlineimap alternative. Despite resyncing all my mail, the transition was fairly smooth. Here's how…
Install isync (for mbsync)
brew install isync
Configure mbsync
Mbsync uses ~/.mbsyncrc
for configuration. Migrating ~/.offlineimaprc to ~/.mbsyncrc
looks like:
IMAPAccount Personal Host some.imap.host.com User your_user_name PassCmd "gpg --quiet --batch -d ~/.offlineimap_accountname.gpg" Port 993 SSLType IMAPS AuthMechs Login CertificateFile ~/.offlineimapcerts.pem # My IMAP provider doesn't handle concurrent IMAP commands. PipelineDepth 1 IMAPStore Personal-remote Account Personal MaildirStore Personal-local Path ~/IMAP/Personal/ Inbox ~/IMAP/Personal/INBOX Channel Personal Master :Personal-remote: Slave :Personal-local: Patterns * Create Slave Sync All Expunge Both SyncState *
No concurrent IMAP commands supported
My IMAP provider doesn't handle concurrent IMAP commands. mbsync and Office 365 had the answer:
PipelineDepth 1
Initial sync
Run initial from the command line sync:
mbsync -Va
While syncing my largest inbox, it sometimes received an unexpected EOF error:
IMAP error: unexpected EOF from some.imap.host.com (1.2.3.4:993)
First few times, I restarted the syncing manually, but then used a loop to automatically restart it.
Bash loops:
while true; do mbsync -V Personal; sleep 5; done
for i in {1..5}; do mbsync -V Personal; sleep 5; done
Eshell loop:
for i in (number-sequence 1 10) {mbsync -V Personal; sleep 5}
Create mu index
Reindex using mu, but first remove existing index for offlineimap messages:
rm -rf ~/.mu
Ok, do index now:
mu index --maildir=~/IMAP
Mu4e tweaks
The get mail command should now point to mbsync.
(csetq mu4e-get-mail-command "mbsync -Va")
I had issues with duplicate IDs after moving and deleting messages from mu4e. Migrating from offlineimap to mbsync for mu4e had the answer:
(csetq mu4e-change-filenames-when-moving t)
Helpful references
- A modern mutt setup with neomutt, mbsync, msmtp and mu — part one | webgefrickel.
- Migrating from offlineimap to mbsync for mu4e | Pragmatic Emacs.
- My personal Email setup - Notmuch, mbsync, postfix and dovecot.
- org-msg: Compose and reply to emails in a Outlook HTML friendly style.
- Reading IMAP Mail in Emacs on OSX.
Sticky function keys on touch bar
Visible (and sticky) function keys are not the touch bar default for Emacs. Let's change that:
GNU find on macOS
At times, you may need GNU versions of command line utilities on macOS. For example, GNU find.
As usual, Homebrew saves the day. Install with:
brew install findutils
Unless you install with –with-default-names (I don't), GNU utilities will be prefixed with a "g".
gfind --version
find (GNU findutils) 4.6.0 Copyright (C) 2015 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>. This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by Eric B. Decker, James Youngman, and Kevin Dalley. Features enabled: D_TYPE O_NOFOLLOW(enabled) LEAF_OPTIMISATION FTS(FTS_CWDFD) CBO(level=2)
If you need more, there are others:
brew install binutils brew install diffutils brew install ed brew install findutils brew install gawk brew install gnu-indent brew install gnu-sed brew install gnu-tar brew install gnu-which brew install gnutls brew install grep brew install gzip brew install screen brew install watch brew install wdiff --with-gettext brew install wget
PlantUML bookmarks
Adding mu4e maildirs extension
Continuing the mu4e fun, added mu4e-maildirs-extension to display a mail dirs summary.
Trying out mu4e and offlineimap
Managing Email from Emacs. Surely that's crazy-talk, but hey… let's give it a try.
Install offlineimap
Need to sync via imap. Use offlineimap. I'm on macOS, so homebrew is king for installing:
brew install offlineimap
Before can configure offlineimap, we'll need to handle a few things first.
Get a cert fingerprint
Use openssl for getting a certificate fingerprint. From offlineimap's FAQ:
SSL_CERT_DIR="" openssl s_client -connect imap.migadu.com:993 < /dev/null 2>/dev/null | openssl x509 -fingerprint -noout -text -in /dev/stdin
Should give you something like:
SHA1 Fingerprint=AA:BB:CC:DD:EE:DD:FF:AA:00:AA:2A:AA:AA:AA:A8:20:80:AA:A2:AA
Encrypt password
Offlineimap can read passwords in plain text in its .offlineimaprc config file, but that's yuckie. Let's encrypt the password and use gnupg for that. Install it:
brew install gnupg
If you haven't already, generate a key
gpg --full-gen-key
Generate an offlineimap account password file.
echo "YourPassword" | gpg --encrypt --recipient "Your Name" -o ~/.offlineimap_accountname.gpg
Python password wrapper
Based on Fabian's Encrypt OfflineIMAP and msmtp password with GnuPG, I created ~/.read_password.py with:
import os import subprocess def read_password(path): return subprocess.check_output(["gpg\n", "--quiet\n", "--batch\n", "-d\n", os.path.expanduser(path)]).strip()
ps. Alternatively, see The homely Mutt's section to store password in macOS's keychain.
Configure offlineimap
Offlineimap uses ~/.offlineimaprc for configuration. We now have all we need to put the configuration together:
[general] accounts = Personal # Load this python file. pythonfile = ~/.read_password.py [Account Personal] localrepository = Personal-Local remoterepository = Personal-Remote # After syncing, let mu index it. postsynchook = mu index --maildir ~/stuff/active/Mail # Sync imap every 5 minutes. autorefresh = 5 # Alternate between 10 quick syncs and full syncs. quick = 10 [Repository Personal-Local] type = Maildir localfolders = ~/stuff/active/Mail/Personal [Repository Personal-Remote] type = IMAP remotehost = some.imap.host.com remoteuser = your_user_name # Use function defined in .read_password.py to read the password. remotepasseval = read_password("~/.offlineimap_personal_account_password.gpg") # Use the SHA1 fingerprint retrieved with openssl. cert_fingerprint = aabbccddeeddffaa00aa2aaaaaaaa82080aaa2aa
Cert file
You can use macOS's certificates from Keychain Access -> System Roots -> Certificates, select all, and ⌘-⇧-e (for export items). Save to ~/certs.pem and use offlineimap configutation:
sslcacertfile = /path/to/certs.pem
Another option is executing lib/mk-ca-bundle.pl from curl's tarball to generate ca-bundle.crt, using certdata.txt from Mozilla's source tree.
Install mu4e
Manually modified mu4e recipe to pick up my Emacs binary. TIL about homebrew's edit command:
brew edit mu
Changed the one line:
- ENV["EMACS"] = "no" if build.without? "emacs"
- ENV["EMACS"] = "/Users/alvaro/homebrew/Cellar/emacs-plus/26.1-rc1_2/bin/emacs"
Finally installed mu4e:
brew install mu
Configure mu4e
Lastly, configure mu4e:
(add-to-list 'load-path (expand-file-name "~/homebrew/share/emacs/site-lisp/mu/mu4e")) (use-package mu4e :config ;; Update mail using 'U' in main view: (setq mu4e-get-mail-command "offlineimap") (setq mu4e-view-show-addresses t) (setq mu4e-attachment-dir (expand-file-name "~/Downloads/")) (setq mu4e-maildir "path/to/Mail") (setq mu4e-html2text-command "w3m -T text/html") ;; alternatively "textutil -stdin -format html -convert txt -stdout" (setq mu4e-user-mail-address-list '("myself@domain1.com" "myself@domain2.com")) (setq mu4e-context-policy 'pick-first) (setq mu4e-compose-context-policy 'always-ask) (setq mu4e-contexts (list (make-mu4e-context :name "domain1" :enter-func (lambda () (mu4e-message "Entering context myself@domain1.com")) :leave-func (lambda () (mu4e-message "Leaving context myself@domain1.com")) :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg '(:from :to :cc :bcc) "myself@domain1.com"))) :vars '((user-mail-address . "myself@domain1.com") (user-full-name . "My name") (mu4e-sent-folder . "/Domain1/Sent") (mu4e-drafts-folder . "/Domain1/Drafts") (mu4e-trash-folder . "/Domain1/Trash") (mu4e-compose-signature . nil) (mu4e-compose-format-flowed . nil) (smtpmail-smtp-user . "myself@domain1.com") (smtpmail-smtp-server . "smtp.domain1.com") (smtpmail-smtp-service . 587))) (make-mu4e-context :name "domain2" :enter-func (lambda () (mu4e-message "Entering context myself@domain2.com")) :leave-func (lambda () (mu4e-message "Leaving context myself@domain2.com")) :match-func (lambda (msg) (when msg (mu4e-message-contact-field-matches msg '(:from :to :cc :bcc) "myself@domain2.com"))) :vars '((user-mail-address . "myself@domain2.com") (user-full-name . "My name") (mu4e-sent-folder . "/Domain2/Sent") (mu4e-drafts-folder . "/Domain2/Drafts") (mu4e-trash-folder . "/Domain2/Trash") (mu4e-compose-signature . nil) (mu4e-compose-format-flowed . nil) (smtpmail-smtp-user . "myself@domain2.com") (smtpmail-smtp-server . "smtp.domain2.com") (smtpmail-smtp-service . 587)))))) (use-package smtpmail :config (setq smtpmail-stream-type 'starttls) (setq smtpmail-debug-info t) (setq smtpmail-warn-about-unknown-extensions t) (setq smtpmail-queue-mail t) (setq smtpmail-default-smtp-server nil) ;; Created with mu mkdir path/to/Mail/queue ;; Also avoid indexing. ;; touch path/to/Mail/queue/.noindex (setq smtpmail-queue-dir "path/to/Mail/queue/cur")) (use-package message :config (setq message-send-mail-function 'smtpmail-send-it))
Authinfo
Create an ~/.authinfo file for sendmail authentication with:
machine smtp.host1.com login account1@host1.com password somepassword1 machine smtp.host2.com login account2@host2.com password somepassword2
Encrypt ~/.authinfo with M-x epa-encrypt-file. Keep ~/.authinfo.gpg and delete ~/.authinfo.
Mu4e helpful references
- A Complete Guide to Email in Emacs using Mu and Mu4e.
- A year with Notmuch mail - a super-fast email client available on Emacs.
- Archaic: text based email clients rock - DEV Community (lots of great links).
- areina's Manage your email in emacs with mu4e.
- Better Email with mu4e (NaN).
- Configuring Emacs mu4e with nullmailer, offlineimap and multiple identities.
- Drowning in Email; mu4e to the Rescue.
- Ebzzry: Setting up Mail in Emacs.
- Emacs as email client with offlineimap and mu4e on OS X.
- Email done right (mu).
- Encrypt OfflineIMAP and msmtp password with GnuPG.
- Etienne's Mu4e customization.
- GitHub - OfflineIMAP/imapfw: imapfw (IMAP/mail framework).
- Gmail Gnus GPG Guide (GGGG).
- Handling Email with Emacs (helm included).
- I got mu4e working! (Reddit).
- Installing mu and mu4e with homebrew with emacs.
- Iqbal Ansari's mail config.
- iqbalansari/dotEmacs: calendar integration.
- Mail in Emacs with mu4e and mbsync (jherrlin).
- Master your inbox with mu4e and org-mode.
- Mu's github mirror.
- Mu's page.
- mu4e: an E-mail Client for Emacs.
- mu4easy: mu4e + mbsync configuration for multiple accounts..
- OfflineIMAP with SSL files on OSX - Sebastian Christ.
- OfflineIMAP: sync and backup tool for IMAP (Hacker News) and mbsync in comments.
- Practical guide to use Gnus with Gmail.
- Pragmatic Emacs's mu4e tutorials.
- Prodissues: Adding mu4e Support To Emacs.
- Prodissues: Emacs GPG For Dummies.
- Reading IMAP Mail in Emacs on OSX.
- Sending queued mails in the background with mu4e.
- Setting Up Mu4e 1.0 C - aliquot.
- Solene's post: Sending mail with mu4e.
- The Emacs Network Security Manager.
- The Ultimate Emailing Agent with Mu4e and Emacs.
- TIP: How to easily manage your emails with mu4e (Reddit).
- Two custom headers for mu4e - Étienne Deparis.
- Using Emacs to read gmail (Emacs subreddit).
- Using mu4e (Brool blog).
- Zamansky mu4e's sample config.
- Zamansky's video on mu4e.
Transparent Emacs titlebars on macOS
Happy with Emacs Plus builds on Mac. You get some eye-candy bonuses like transparent titlebars.
To install:
brew tap d12frosted/emacs-plus brew install emacs-plus --without-spacemacs-icon
Config:
(when (memq window-system '(mac ns)) (add-to-list 'default-frame-alist '(ns-appearance . dark)) ; nil for dark text (add-to-list 'default-frame-alist '(ns-transparent-titlebar . t)))
Lunette: Like Spectacle but for Hammerspoon
Came across Lunette. Gives ya Spectacle Keybindings for Hammerspoon.
Train Emacs to open files externally
TIL about the openwith package. It enables Emacs to defer to external programs for certain files. You choose which ones. Neat.
(use-package openwith :ensure t :config (csetq openwith-associations '(("\\.\\(mp4\\|mp3\\|webm\\|avi\\|flv\\|mov\\)$" "open" (file)))) (openwith-mode 1))
Ejecting USB drives on Synology
For posterity:
Control panel > External devices > USB Disk 1 > Eject
Synology user had no home
Upon ssh'ing to a Synology box, the user had no home.
Could not chdir to home directory /var/services/homes/someone: No such file or directory
Fixed via:
Control Panel > User > Advanced > User Home > [x] Enable user home service
Pre-commit hooks to save you from yourself
Wanted to try out some code, but needed to ensure never checked in. Git pre-commit hooks are handy in this space. Add the following script to search for either @COMMITFAIL or @NOCOMMIT in the staged files. If found, attempts to commit will fail.
Based on https://gist.github.com/rex/223b4be50285f6b8b3e06dea50d15887:
#!/bin/bash set -o nounset set -o errexit echo "Arguments:" echo "$@" echo "---" readonly FILES_PATTERN='(\..+)?$' readonly FORBIDDEN='(@?NOCOMMIT|@?COMMITFAIL)' if ( git diff --cached --name-only | grep -E "$FILES_PATTERN" | xargs grep -E --with-filename -n "$FORBIDDEN" ); then echo "ERROR: @COMMITFAIL or @NOCOMMIT found. Exiting to save you from yourself." exit 1 fi
Save to a file and create a symbolic link to your .git/hooks directory:
ln -s ../../git/commit-fail-pre-hook.sh .git/hooks/pre-commit
Azores travel bookmarks
Debugging Emacs binary
From How do I debug an emacs crash? (Emacs Stack Exchange), disable optimizations when configuring and build:
CFLAGS="-O0 -g3" ./configure ... make
And good 'ol gdb (lldb works too):
gdb ../nextstep/Emacs.app/Contents/MacOS/Emacs
Reference
Paper less bookmarks
- danielquinn/paperless: Scan, index, and archive all of your paper documents.
- Digitizing All Your Paper Stuff.
- Fujitsu ScanSnap iX500 Color Duplex Desk Scanner for Mac and PC.
- Going Paperless: Scanning to Evernote, Revisited | Jamie Todd Rubin.
- guess-filename.py: Derive a file name according to old file name cues and/or PDF file content.
- Hazel for document/download management.
- Installing Tesseract OCR on Mac OS X Lion.
- Paperless | Irreal.
- PDF OCR X - Mac & Windows OCR Software to convert PDFs and Images to Text.
- Video: Batch OCR With The Mac Fujitsu ScanSnap.
Bologna travel bookmarks
- Il Cannone restaurant.
Grep through pdfs
Late to the party, but investing in going paperless. Got a scanner with OCR, which generates searchable pdfs. If I could only grep through them…
brew install pdfgrep
Balance restored.
Hammerspoon bookmarks
- dotfiles/grid.lua at master for simple functions to resize windows.
- Emacs keys everywhere Hammerspoon Script.
- Getting Started With Hammerspoon (by Diego Martín Zamboni).
- Hammerspoon config inspired by Spacemacs.
- Just Enough Lua to Be Productive in Hammerspoon, Part 1.
- Just Enough Lua to Be Productive in Hammerspoon, Part 2.
- launchOrFocusByBundleID for global key bindings (there are Emacs goodies there too).
- My configuration with init.lua and the require()ed modules.
- Seal. Helm-like for hammerspoon.
- Set up a Hyper Key with Hammerspoon on macOS.
- ZeroBrane completion and here also.
Options to reduce Go binary size
A Hacker News's thread Go gets preliminary WebAssembly support has a couple of tips to reduce binaries compiled with Go.
go build -ldflags=-s
UPX (Ultimate Packer for eXecutables) packs the binary further.
upx --ultra-brute
Trying out tesseract
As part of going paperless, looking into OCR. Trying out tesseract.
Install
$ brew install gs $ brew install imagemagick $ brew install tesseract
$ convert -density 300 -depth 8 receipt.pdf receipt.png $ tesseract receipt.png receipt.png.txt
Sapporo travel bookmarks
Gif bookmarks
Trying out ShellCheck
ShellCheck gives you automatic warnings/suggestions in bash/sh shell scripts.
$ brew install shellcheck
Bonus: If using Emacs's flycheck, you get ShellCheck support out of the box.
Image editing bookmarks
- Exif.tools – A multimedia file metadata tool (Hacker News).
- Fred's ImageMagick Scripts (Hacker News).
- How to crop in GIMP (Linux Hint).
- ImageMagick to Sharpen an Image – Linux Hint.
- Jpeg2png: Silky smooth JPEG decoding – no more artifacts (2016) | Hacker News.
- Make your own meme image using Imagemagick.
- The Art of PNG Glitch (Hacker News).
- The Art of PNG Glitch.
Buying matcha powder online
From Reddit's thread:
- https://yunomi.life
- https://shop.ippodo-tea.co.jp/kyoto/shopf/index.html
- https://www.o-cha.com
- http://hojotea.com/categ_e/viewe.htm
- http://yuuki-cha.com
- http://www.hibiki-an.com/index.php
- http://www.thes-du-japon.com/index.php?main_page=index
- https://www.chadoteahouse.com
- https://www.denstea.com
- https://www.sazentea.com/en
- http://www.ujicha-kanbayashi.co.jp
- http://www.marukyu-koyamaen.co.jp/products.html
- http://www.fukumotoen.co.jp/shop/
- http://www.myokoen.com/shop/index.html
Getting macOS app bundle ID
From stack overflow:
Option 1
osascript -e 'id of app "Emacs"'
Option 2
mdls -name kMDItemCFBundleIdentifier -r SomeApp.app
Trying out chunkwm
Installing Chunkwm
$ brew tap crisidev/homebrew-chunkwm $ brew install --HEAD --with-tmp-logging chunkwm
Add a configuration file. Started off from this example.
~/.chunkwmrc chmod +x ~/.chunkwmrc
Note: Ensure core::plugin_dir matches homebrew's plugin directory. Typically something like: /path/to/homebrew/opt/chunkwm/share/chunkwm/plugins
Start chunkwmrc service.
$ brew services start crisidev/chunkwm/chunkwm
Installing skhd (a hotkey daemon)
$ brew install --HEAD --with-logging koekeishiya/formulae/skhd
Start skhd service.
$ brew services start koekeishiya/formulae/skhd
Skhd logs location.
/Users/you/homebrew/var/log/skhd/skhd.[out|err].log
Add a configuration file. Started off from this example.
~/.skhdrc chmod +x ~/.skhdrc
Installing khd (easily invoke hotkeys from terminal)
$ brew install khd
Some additional Mission Control and keyboard shortcut preferences:
Building bazel on macOS
Bootstrap
brew tap bazelbuild/tap brew install bazelbuild/tap/bazel
Build
git clone https://github.com/bazelbuild/bazel.git
cd bazel
bazel build //src:bazel
Get your bazel binary
Self-contained binary in bazel-bin/src/bazel
Known revisions
- https://github.com/bazelbuild/bazel.git @ d81a46cebe07f73d3ea38206262cf7a1a9513b42
- https://github.com/bazelbuild/rules_apple.git @ e921a9f727e039c8b5087d4f98c5d09f189658cd
Extracting files from pkg
mkdir tmp
cd tmp
xar -xf ../Some.pkg
cat Payload | gunzip -dc |cpio -i
Installing Inkscape with homebrew
brew tap caskroom/cask brew install caskformula/caskformula/inkscape
Homebrew install from cache
Came across a 404 while installing graphviz-2.40.1.tar.gz via homebrew. If you can find the package elsewhere, copy over to homebrew's cache directory.
brew --cache
/Users/User/Library/Caches/Homebrew
org-babel Objective-C support
Wanted to quickly execute an Objective-C snippet. org-babel didn't support it out of the box, but adding it was straightforward (looked at ob-C.el and ob-java.el):
(require 'ob) (defcustom org-babel-objc-compile-command "clang -x objective-c -framework Foundation" "For example: \"clang -x objective-c -framework Foundation\"." :group 'org-babel :version "24.3" :type 'string) (defun org-babel-execute:objc (body params) "Compile Objective-C BODY with org PARAMS and execute binary." (let* ((src-file (org-babel-temp-file "org-babel-objc-block-" ".m")) (cmpflag (or (cdr (assq :cmpflag params)) "")) (full-body (org-babel-expand-body:generic body params)) (bin-file (org-babel-process-file-name (org-babel-temp-file "org-babel-objc-block" org-babel-exeext)))) (with-temp-file src-file (insert full-body)) (org-babel-eval (concat org-babel-objc-compile-command " " cmpflag " " src-file " " "-o" " " bin-file) "") ;; Using 2>&1 since org babel does not include stderr in output from NSLog. (let ((results (org-babel-eval (concat (org-babel-process-file-name bin-file) " 2>&1") ""))) (org-babel-reassemble-table (org-babel-result-cond (cdr (assq :result-params params)) (org-babel-read results) (let ((tmp-file (org-babel-temp-file "c-"))) (with-temp-file tmp-file (insert results)) (org-babel-import-elisp-from-file tmp-file))) (org-babel-pick-name (cdr (assq :colname-names params)) (cdr (assq :colnames params))) (org-babel-pick-name (cdr (assq :rowname-names params)) (cdr (assq :rownames params))))))) (provide 'ob-objc)
Add objc to org-babel-load-languages, and you can subsequently compile and run Objective-C blocks like:
#import <Foundation/Foundation.h> int main() { NSLog(@"Hello World"); return 0; }
2017-11-16 01:47:28.923 org-babel-objc-block-Aai8ux[15319:346480] Hello World
iOS dev command-line goodies
Install ipa on device
Get utility with:
npm install -g ipa-deploy npm install -g ios-deploy
Install ipa on connected iPhone:
ipa-deploy path/to/your/App.ipa
Install app on booted simulator
Install ipa on connected iPhone:
xcrun simctl install booted path/to/your/App.app
Install ipa on booted simulator
#!/bin/bash # Unzip ipa, install app, and run on booted simulator. set -o nounset set -o errexit readonly IPA_PATH=$1 readonly TEMP_DIR_PATH=$(mktemp -d) readonly BASENAME=$(basename ${IPA_PATH}) readonly NAME=${BASENAME%.*} readonly APP_DIR_PATH="${TEMP_DIR_PATH}/Payload/${NAME}.app" readonly PLIST_FILE_PATH="${APP_DIR_PATH}/Info.plist" trap "rm -rf ${TEMP_DIR_PATH}" EXIT unzip -o "${IPA_PATH=}" -d "${TEMP_DIR_PATH}" readonly BUNDLE_ID=$(/usr/libexec/PlistBuddy -c "Print CFBundleIdentifier" ${PLIST_FILE_PATH}) xcrun simctl install booted "${APP_DIR_PATH}" xcrun simctl launch booted "${BUNDLE_ID}"
Eshell pcomplete company completion
Howard Abrams's Introduction to eshell video prompted me to poke at eshell some more. This time, I got eshell context aware completion by glueing the excellent company and pcomplete packages.
(require 'cl-lib) (require 'company) (require 'dash) (require 'pcomplete) (require 's) (defun company-pcomplete--overlap-tail (a b) "When A is \"SomeDev\" and B is \"Developer\", return \"eloper\"." (let ((prefix a) (remaining nil)) (while (and (not remaining) (> (length prefix) 0)) (when (s-starts-with? prefix b) (setq remaining (substring b (length prefix)))) (setq prefix (substring prefix 1))) remaining)) (defun company-pcomplete--candidates (prefix) "Get candidates for PREFIX company completion using `pcomplete'." ;; When prefix is: "~/Down" and completion is "Downloads", need ;; to find common string and join into "~/Downloads/". (-map (lambda (item) (if (s-starts-with? prefix item) item (concat prefix (company-pcomplete--overlap-tail prefix item)))) (all-completions prefix (pcomplete-completions)))) (defun company-pcomplete (command &optional arg &rest ignored) "Complete using pcomplete. See `company''s COMMAND ARG and IGNORED for details." (interactive (list 'interactive)) (case command (interactive (company-begin-backend 'company-pcomplete)) (prefix (company-grab-symbol)) (candidates (company-pcomplete--candidates arg))))
Don't forget to add company-pcomplete to company-backends, and if you want an explicit binding, use something like:
(bind-key "<backtab>" #'company-complete eshell-mode-map)
Projectile shell dir company completion
Projectile and company are just amazing Emacs packages. Projectile gives random access to files, while company completes well… anything. For shells, Emacs has a handful of options.
Standing on the shoulders of package giants (dash and f included) and some elisp, we can bring random access to project directories from the shell.
(require 'cl-lib) (require 'company) (require 'dash) (require 'f) (require 'projectile) (defvar-local company-projectile-cd-prefix "cd ") (defun company-projectile-cd (command &optional arg &rest ignored) "Company shell completion for any projectile path." (interactive (list 'interactive)) (case command (interactive (company-begin-backend 'company-projectile-cd)) (prefix (company-grab-symbol-cons company-projectile-cd-prefix (length company-projectile-cd-prefix))) (candidates (company-projectile-cd--candidates (company-grab-symbol-cons company-projectile-cd-prefix (length company-projectile-cd-prefix)))) (post-completion (company-projectile-cd--expand-inserted-path arg)))) (defun company-projectile-cd--candidates (input) "Return candidates for given INPUT." (company-projectile-cd--reset-root) (when (consp input) (let ((search-term (substring-no-properties (car input) 0 (length (car input)))) (prefix-found (cdr input))) (when prefix-found (if (projectile-project-p) (company-projectile-cd--projectile search-term) (company-projectile-cd--find-fallback search-term)))))) (defun company-projectile-cd--projectile (search-term) (-filter (lambda (path) (string-match-p (regexp-quote search-term) path)) (-snoc (projectile-current-project-dirs) ;; Throw project root in there also. (projectile-project-root)))) (defun company-projectile-cd--find-fallback (search-term) (ignore-errors (-map (lambda (path) (string-remove-prefix "./" path)) (apply #'process-lines (list "find" "." "-type" "d" "-maxdepth" "2" "-iname" (format "\*%s\*" search-term)))))) (defun company-projectile-cd--expand-inserted-path (path) "Replace relative PATH insertion with its absolute equivalent if needed." (unless (f-exists-p path) (delete-region (point) (- (point) (length path))) (insert (concat (projectile-project-root) path)))) (defun company-projectile-cd--reset-root () "Reset project root. Useful when cd'ing in and out of projects." (projectile-reset-cached-project-root) (when (projectile-project-p) (projectile-project-root)))
Creating icns icons
Stack overflow yields Where can i find Icon Composer on Mac? when I did a quick search to convert a png to icns. For future reference:
#!/bin/bash -e set -e set -o pipefail if [ "$#" -ne 1 ]; then echo "\nusage: to_icns.sh path/to/image.png\n" exit 1 fi readonly IMAGE_FPATH=$1 readonly BASENAME=$(basename ${IMAGE_FPATH%.*}) mkdir ${BASENAME}.iconset sips -z 16 16 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_16x16.png" sips -z 32 32 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_16x16@2x.png" sips -z 32 32 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_32x32.png" sips -z 64 64 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_32x32@2x.png" sips -z 128 128 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_128x128.png" sips -z 256 256 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_128x128@2x.png" sips -z 256 256 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_256x256.png" sips -z 512 512 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_256x256@2x.png" sips -z 512 512 $IMAGE_FPATH --out "${BASENAME}.iconset/icon_512x512.png" cp $IMAGE_FPATH "${BASENAME}.iconset/icon_512x512@2x.png" iconutil -c icns ${BASENAME}.iconset rm -R ${BASENAME}.iconset echo Wrote ${BASENAME}.icns
Forcing aptX on MacOS bluetooth audio
Bought a pair of QuietComfort 35. Audio quality on MacOS was lagging compared to iOS. Googling led to different posts suggesting the use of Bluetooth Explorer to force aptX usage. Did the trick for me.
Bluetooth Explorer can be downloaded from https://developer.apple.com/download/more. Search for Hardware IO tools:
Open Hardware_IO_Tools_for_Xcode_7.3.dmg and launch Bluetooth Explorer:
Select Audio Options:
Check Force use of aptX:
Don't forget to disconnect and reconnect your Bluetooth device.
Hungary travel bookmarks
Faster cursor movement on macOS
Faster cursor movement on macOS by increasing your keyboard's initial key repeat subsequent key repeat.
defaults write -g KeyRepeat -int 1 defaults write -g InitialKeyRepeat -int 10
Search/insert one-liners with Emacs helm-ag
Emacs helm is awesome. helm-ag is double awesome. Searching for one-liners in your codebase, narrowing down with helm, and easily inserting is triple awesome.
(defun ar/helm-ag (arg) "Helm-ag search remembering last location. With ARG, forget the last location." (interactive "P") (defvar ar/helm-ag--default-locaction nil) (setq ar/helm-ag--default-locaction (read-directory-name "search in: " (if arg default-directory ar/helm-ag--default-locaction) nil t)) (helm-do-ag ar/helm-ag--default-locaction)) (defun ar/helm-ag-insert (arg) ;; Helm-ag and insert match. (interactive "P") (let* ((actions (helm-make-actions "Insert" (lambda (candidate) ;; Drop file:line:column. For example: ;; arc_hostlink.c:13:2:#include <linux/fs.h> ;; => #include <linux/fs.h> (insert (replace-regexp-in-string "^[^ ]*:" "" candidate))))) (helm-source-do-ag (helm-build-async-source "The Silver Searcher" :init 'helm-ag--do-ag-set-command :candidates-process 'helm-ag--do-ag-candidate-process :persistent-action 'helm-ag--persistent-action :action actions :nohighlight t :requires-pattern 3 :candidate-number-limit 9999 :keymap helm-do-ag-map :follow (and helm-follow-mode-persistent 1)))) (call-interactively #'ar/helm-ag)))
Sleep bookmarks
Math bookmarks
GnuPG and macOS
Had problems installing and using GnuPG on macOS, primarily for Emacs use:
gpg: problem with the agent: Inappropriate ioctl for device gpg: error creating passphrase: Operation cancelled gpg: symmetric encryption of '[stdin]' failed: Operation cancelled
Basic installation required:
brew install gnupg
But worked around the error above by using pinentry-mac (UI), instead of Emacs prompts.
brew install pinentry-mac
Edited ~/.gnupg/gpg-agent.conf with:
pinentry-program path/to/homebrew/bin/pinentry-mac
May need to kill gpg-agent to reload config.
gpgconf --kill gpg-agent
Installing gnuplot on macOS
UPDATE(2019-05-19 Sun): Plan A and B use options no longer available since the recent changes to remove all options from Homebrew/homebrew-core formulae. See Plan C.
Plan A
Install gnuplot Qt
If you have the resources, you can try the Qt flavor. You need at least 15GB to download and a long build. Ran out of space on my Macbook Air. Aborted.
brew install gnuplot --with-qt
Plan B
Install xquartz
brew install Caskroom/cask/xquartz
Install gnuplot x11
brew install gnuplot --with-x11
Install feedgnuplot
Feedgnuplot is handy for plotting data streams realtime.
brew install feedgnuplot
Plan C
Install with no options
brew install gnuplot
So far so good, but default gnuplot formula uses Qt and the Cocoa plugin could not be loaded:
qt.qpa.plugin: Could not find the Qt platform plugin "cocoa" in ""
Debugging
- QT_DEBUG_PLUGINS
Turns out you can get plugin logs using the QT_DEBUG_PLUGINS environment variable:
export QT_DEBUG_PLUGINS=1
QFactoryLoader::QFactoryLoader() checking directory path "/Users/myuser/homebrew/Cellar/gnuplot/5.2.6_1/libexec/gnuplot/5.2/platforms" …
This led me to find out about the gnuplot/5.2/gnuplot_qt binary.
- qt_prfxpath
Getting the Qt prefix can be done by inspecting QtCore's strings:
strings /Users/myuser/homebrew/Cellar/qt/5.12.3/Frameworks/QtCore.framework/QtCore | grep qt_prfxpath
qt_prfxpath=/usr/local/Cellar/qt/5.12.3
Ok so qt_prfxpath is pointing to /usr/local/Cellar/qt, while my installation's is at /Users/myuser/homebrew/Cellar/qt. This is problematic and indeed my fault for installing homebrew in /Users/myuser/homebrew instead of the recommended /usr/local.
Symlinking did the job:
sudo mkdir -p /usr/local/Cellar sudo ln -s ~/homebrew/Cellar/qt /usr/local/Cellar/qt
/Users/myuser/homebrew/Cellar/gnuplot/5.2.6_1/libexec/gnuplot/5.2/gnuplot_qt
QFactoryLoader::QFactoryLoader() checking directory path "/Users/myuser/homebrew/Cellar/qt/5.12.3/plugins/platforms" ... QFactoryLoader::QFactoryLoader() looking at "/Users/myuser/homebrew/Cellar/qt/5.12.3/plugins/platforms/libqcocoa.dylib" Found metadata in lib /Users/myuser/homebrew/Cellar/qt/5.12.3/plugins/platforms/libqcocoa.dylib
Success.
Tel Aviv travel bookmarks
- Breakfast club (dancing).
- Claro/Sarona Market.
- Dizengoff Square - Wikipedia.
- Drink Cafe hafuch at Rothschild 12.
- Jaffa's Flea market.
- Nightlife: Kuli Alma's hipster haven. Imperial craft cocktail bar (drink Gold fashioned).
- Park HaYarkon.
- Tel Aviv museum of art.
Jerusalem travel bookmarks
- Jerusalem: Rooftop Mamilla restarurant.
Nepal travel bookmarks
Singapore notes
- Hotel Mono, 18 Mosque street #01-04.
- Buddha tooth relic museum.
- Best Hawker centers.
- Kong Meng San Phor Kark See Monastery.
- Go there (figure out fastest MRT route).
- What to eat at ABC Market (Hawker Centre) aka ABC Brickworks Food Centre?.
- Curry puffs (see Taste test: Crisp curry puffs).
- Singapore’s 17 Michelin-rated Hawker Stalls in 2016.
- Temples
- Hawkers
- Mr and Mrs Mohgan's Super Crispy Roti Prata (source) on Crane Road. Dhal/fish/mutton curry side.
- Roast Paradise (maybe) Address: #01-122 Old Airport Road Food Centre. Hours: Tues-Sun: 11am to 4pm or till sold out, Wed and Sun: 11am to 2pm, Closed on Mondays.
- Fatty Cheong, 肥仔详, (#01-120, ABC Brickworks Food Centre, 6 Jalan Bukit Merah Singapore 150006): char siew and xio bak rice and char siew noodles.
- Hoo Kee Bak Chang (Amoy Street Food Centre): bak zhang (glutinous rice dumpling). Try Choose from three kinds: chestnut ($2.80); chestnut with salted egg yolk ($3.60); and chestnut with mushroom ($3.60).
- Lim Kee (Orchard) Banana Fritters (Maxwell food centre, source).
- Mr Avocado Exotic Juice (Alexandra village food centre, source).
- Tanglin Crispy Curry Puff (Hong Lim Food Centre or Maxwell, source) (东陵酥皮咖喱角). Try sardine curry puff?
- Chuan Kee Satay (source). Long queue for pork satay.
- Selera Rasa Nasi Lemak (source).
- Fu Shun Jin Ji Shao La Mian Jia (Maxwell food centre, source): Char siu + noodles.
- Shanghai La Mian Xiao Long Bao (Alexandra Village food centre, source): xiao long bao or soup dumplings ($4.50 for 7 pieces).
- Timbre+ (hipster hawker centre? source).
- Supertree Grove (go at dusk, see lights turn on).
- Singapore Botanic garden.
- Ginger Garden.
- Palms valley.
- Orchid garden.
- Sri Mariamman Temple.
- Kusu Island?
- Chilly crab (“Jumbo” Chilli Crab Restaurant in Clarke Quay or Harvest Seafood Restaurant)?
- Afternoon tea?
- www.tea-chapter.com.sg
- Bumboats (£2.50 return) leave Changi Point between 6am and 9.30pm for the 10-minute crossing to Palau Ubin. Hire a bicycle in the village where the boats dock.
- Haji Lane (colorful road).
- Tiong Bahru 1930s public housing estate (**)
- Chong Yu Wanton Mee (Tiong Bahru Market And Food Centre #02-30, 30 Seng Poh Road, source).
- old-fashioned treats at Tiong Bahru Galicier (55 Tiong Bahru Rd).
- Chinatown
- Pek Sin Choon Tea: Oldest team merchants.
- Ang Mo Kio: Sri Mariamman Hindu temple.
- Strangelets: quirky stuff from around the world.
- 40 Hands: Allegedly one of most popular coffee joints.
- BooksActually: Coolest book shop.
- Keong Saik (next to Chinatown)
- 1939 Hotel.
- The Library (49 Keong Saik Rd): night drinks, ask for key/password next door (the-study.sg) (**)
- Mariko's (Now Phat Cat laundry): Maybe food or drink at night? (**)
- Rose Citron (23 Keong Said Rd): French and Asian articles.
- Everton park (old housing estate), new meets old
- Coffee
- Nylon coffee roasters (http://nyloncoffee.sg).
- Just Want Coffee (justwant.com.sg).
- Cozy corner coffee.
- Sweets
- Grin Affair (grinaffair.com): natural ingredients into glass jar creations.
- Batterworks (batter-works.com): pastries.
- http://cozycornercoffee.com.
- Seriously ice scream (facebook.com/seriouslyicecream).
- Ji Xiang Confectionery (jixiangconfectionery.com): Traditional glutinous sweets. (**)
- Food
- The Provision Shop (Blk 3 Everton Park): for a classic and affordable meal.
- Chew the Fat (Blk 6 Everton Park): comfort food.
- Eden's Kitchen (http://edenskitchen.sg): healthy, green tea, coconut oil, etc.
- Coffee
- Jalan Besar
- Char: unconventional char siu (source).
- The Banana Leaf Apollo (Little India).
- Beach Road Scissors-Cut (220 Jln Besar): Curry Rice. (**)
- Fu Zhou Poh Hwa Oyster Cake (166 Jln Besar): UFO-shaped snacks. (**)
- Swee Choon Tim Sum Restaurant: a dim sum institution!. (**)
- Papa Palheta coffee: best coffee in town?.
- General Company: awesome design and workshops. (**)
- The Bravery: brunch, aka awesome pancakes. (**)
- AEIOU: Retro shopping.
- Geylang (preserved shophouses and rich in Malay history)
- Hajjah Mona Nasi Padang (Geylang Serai food centre): Order nasi padang (try dry one).
- Biryani Express (Geylang Serai food centre)
- Red light district. Still?
- Brawn & Brains (Coffee).
- Old Geylang (crocodile, turtle soup, other oddities).
- Hi-Thrift (1 Geylang Rd): Second hand treasures? (**).
Email provider bookmarks
Go snippets
Command-line flags
import ( "flag" ) type args struct { flag1 string flag2 string arg string } func parseArgs() args { args := args{} flag.StringVar(&args.flag1, "flag1\n", "\n", "some flag 1 with sample `value`") flag.StringVar(&args.flag2, "flag2\n", "\n", "some flag 2 with sample `value`") flag.CommandLine.Usage = func() { fmt.Fprintf(os.Stderr, "Usage of %s:\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "\n myarg\n\n") flag.PrintDefaults() } flag.Parse() args.arg = flag.Arg(0) if args.flag1 == "" || args.flag2 == "" || args.arg == "" { flag.CommandLine.Usage() os.Exit(1) } return args } func main() { args := parseArgs() fmt.Printf("Args: %#v\n", args) }
go run main.go -flag1 val1 -flag2 val2 arg
Javascript snippets
Sydney travel bookmarks
- 17 Stunning Sydney Pools That Will Make You Want To Jump Back In The Water.
- 48 Hours in Sydney.
- Bourke Street Bakery.
- Collector Store (Surrey Hills).
- Coogee Pavilion.
- Four ate five.
- Harry's Cafe de Wheels: Famous for Pies and Peas, Meat Pies, Hot Dogs.
- Hurricane’s grill & bar Bondi beach.
- Lox Stock & Barrel.
- Marigold citymark (dim sum).
- Reuben Hills.
- Seans.
- Sydney's Best Markets - The Trusted Traveller.
- The eight (dim sum).
- The Glenmore.
- Three Blue Ducks.
Laos travel bookmark
Singapore travel bookmarks
- Any place to go thrift shopping in Singapore? (Reddit).
- East coast lagoon.
- Food post on SG.
- Hillstreet Tai Hwa Pork Noodles: Everybody Queue up!.
- Little India.
- More SG spots.
- Second hand shopping in Singapore.
- SG spots.
- Singapore's best hawker centres - Telegraph.
- The Insider's Guide to Singapore (SG Magazine Online).
- Treasure Hunt: 5 Places to thrift in Singapore.
- Visakan Veerasamy on Twitter: "what do you know about Singapore?".
- What is the best hawker center in singapore? (Reddit).
Cambodia travel bookmarks
- Pub Street (Siem Reap, Cambodia).
New York travel bookmarks
API design bookmarks
- A bird's eye view on API development.
- A Guide to Designing and Building RESTful Web Services with WCF 3.5 (Microsoft).
- Ask HN: Suggestions for books about API design? | Hacker News.
- Best Practices for Designing a Pragmatic RESTful API.
- Build APIs You Won't Hate.
- Designing and Evaluating Reusable Components.
- Harry Moreno | API Design Link Roundup.
- How Do I Make This Hard to Misuse?.
- How to Design a Good API and Why it Matters (Google).
- How To Design A Good API and Why it Matters - YouTube.
- How to design API function creating objects: By Neil Henning.
- HTTP API Design Guide.
- JSON API — A specification for building APIs in JSON.
- Microsoft REST API Guidelines.
- Notes on RESTful APIs (Updated).
- REST API Documentation Best Practices.
- REST API Tutorial.
- REST+JSON API Design - Best Practices for Developers - YouTube.
- RESTful Service Design - UC Berkeley.
- Rusty's API Design Manifesto.
- Scott Meyers: The Most Important Design Guideline?.
- Swift.org - API Design Guidelines.
- Teach a Dog to REST.
- The Best API Documentation.
- The Little Manual of API Design (Jasmin Blanchette, Trolltech).
- Web API Design - Crafting interfaces that developers love.
- Write code that is easy to delete, not easy to extend.
Handy pdf utilities
Straight out of How (and why) I made a zine, some handy utilities for generating pdfs…
Convert pngs to pdfs
# start with a bunch of PNG images of your zine pages # convert them all to PDF for i in *.png do # imagemagick is the best thing in the world convert $i $i.pdf done
Combine pdfs
Combine pdfs using pdftk:
pdftk *.pdf cat output zine.pdf
Combine pdfs using poppler:
pdf unite PDF1.pdf PDF2.pdf PDF3.pdf
Reorder pdf pages
# pdfmod is a GUI that lets you reorder pages pdfmod zine.pdf
Add margins to pdf
# pdfcrop lets you add margins to the pdf. this is good because otherwise the # printer will cut off stuff at the edges pdfcrop --margin '29 29 29 29' zine.pdf zine-intermediate.pdf
Turn pdf into booklet
# pdfjam is this wizard tool that lets you take a normal ordered pdf and turn # it into something you can print as a booklet on a regular printer. # no more worrying about photocopying machines pdfjam --booklet true --landscape --suffix book --letterpaper --signature 12 --booklet true --landscape zine-intermediate.pdf -o zine-booklet.pdf
Fuzzy search Emacs compile history
I wrote about searching bash history with Emacs Helm some time ago. Since then, I've learned about completing-read to generically handle simple Emacs completions (very handy for supporting Helm, Ivy, and Ido completions).
Here's a simple way to combine completing-read and the compile command to enable fuzzy searching your compile history:
(defun ar/compile-completing () "Compile with completing options." (interactive) (let ((compile-command (completing-read "Compile command: " compile-history))) (compile compile-command) (add-to-list 'compile-history compile-command)))
Jumping on the Emacs 25 bandwagon
Can't miss out on all the new fun. Emacs 25 RC2 is out and lots of people already using it. Since I'm mostly on MacOS these days, installing via homebrew with –devel, gets you RC2:
brew install emacs --devel --with-cocoa --with-gnutls --with-librsvg --with-imagemagick
The only hiccup so far's been org mode failing to export, which was fixed by re-installing it (follow this thread).
San Francisco's Mission District travel bookmarks
- Atlas Cafe.
- Blue Bottle Coffee.
- Cafe la Boheme.
- Clarion Alley.
- Coffee Bar.
- Dynamo donut & coffee.
- Four Barrel Coffee.
- Grand Coffee.
- Haus Coffee.
- Kafe 99.
- Linea cafe.
- Mission skateboards.
- pNakamoto's Bitcoin shop.
- Philz Coffee.
- Ritual Coffee roasters.
- Rodger's coffee & tea.
- Sightglass Coffee.
- Stable Cafe.
- Sugar lump coffee lounge.
Moscow travel bookmarks
Vietnam travel bookmarks
- Can Ba Quan
- Nikki Tren.
- Vietnamese Cajun.
- Exploring Vietnam's remote Con Dao Islands.
- Hoi An, Vietnam- Travel guide.
- List of Locations: Somebody Feed Phil - Ho Chi Minh City, Vietnam.
- Pho Bo Phu Gia
- DC: 146K LY Chinh Thang.
- 0908 208 866.
- Simon Standly and Vin Dao (food journalists)
- Somebody Feed Phil, List of Locations: Ho Chi Minh City.
- Thuc Pham Duc Viet
- Bahn Mi
- Pate Bu Cha
- Nhan Dat Bi Cha
Pokémon Go bookmarks
Coffee bookmarks
Machine learning bookmarks
- A Course in Machine Learning (Hacker News).
- How to start learning deep learning (Hacker News).
- How to start learning deep learning.
- Machine Learning is Fun! The world’s easiest introduction to Machine Learning.
- Practical Deep Learning for Coders 2019 (Hacker News).
- What are the best ways to pick up Deep Learning skills as an engineer? (Quora).
Emacs and emotional vocab
Having read Are You in Despair? That’s Good, I was encouraged to expand my emotional vocabulary. As a zone.el fan (checkout nyan, sl, and rainbow), I looked into writing a zone program. When zone-when-idle is set, zone acts as a screensaver of sorts. We can use this to display random emotional vocab whenever Emacs is idle for a period of time. Let's get to it…
Zone keeps a list of programs to choose from when kicked off. Below is a basic zone-hello program, along with an interactive command for previewing. Not much to these. The tiny program prepares the screen for zoning and inserts text while no input is pending.
(defun zone-hello () (delete-other-windows) (setq mode-line-format nil) (zone-fill-out-screen (window-width) (window-height)) (delete-region (point-min) (point-max)) (goto-char (point-min)) (while (not (input-pending-p)) (insert "hello zone\n") (zone-park/sit-for (point-min) 0.2))) (defun zone-hello-preview () (interactive) (let ((zone-programs [zone-hello])) (zone)))
Here's what zone-hello looks like:
Back to improving our emotional vocabulary, we'll need a dictionary for our goal. A quick search yields a potential list of words. We can use WordNet to define them while offline. These two sources will do for now. We tie it all together in zone-words.el and the resulting zone program looks as follow:
UPDATE: Just came across Animations With Emacs. A post with awesome zone examples.
Emacs: Find number of days between dates
Needed to find the number of days between two dates. Emacs calendar must know this…
- Fire up the manual (M-x info-emacs-manual or C-h r).
- Info-goto-node (or g).
- Type "counting days" and voilá:
To determine the number of days in a range, set the mark on one date using `C-<SPC>', move point to another date, and type `M-=' (`calendar-count-days-region'). The numbers of days shown is inclusive; that is, it includes the days specified by mark and point.
Note: you can use the mouse to jump to another date, or "g d" (calendar-goto-date).
RoutingHTTPServer snippet
RoutingHTTPServer snippet:
RoutingHTTPServer *routingHTTPServer = [[RoutingHTTPServer alloc] init]; [routingHTTPServer setPort:8000]; [routingHTTPServer setDefaultHeader:@"Server" value:@"YourAwesomeApp/1.0"]; [routingHTTPServer handleMethod:@"GET" withPath:@"/hello" block:^(RouteRequest *request, RouteResponse *response) { [response setHeader:@"Content-Type" value:@"text/plain"]; [response respondWithString:@"Hello!"]; }]; NSError *error = nil; if (![routingHTTPServer start:&error]) { NSLog(@"Error starting HTTP Server: %@\n", error); }
Alaska travel bookmarks
- Anchorage.
- Denali NP.
- Exit Glacier / Kenai Fjord NP.
- Ice Falls Hike.
- Iditarod race husky camp.
- Seward: Kenai Fjord Wildlife cruise (Major Marine cruises).
- Talkeetna fishing.
UIViewController bookmarks
- What's your number one tip for avoiding massive view controllers?.
- 8 Patterns to Help You Destroy Massive View Controller.
- Blending Cultures: The Best of Functional, Protocol-Oriented, and Object-Oriented Programming.
- Dan Abramov - Live React: Hot Reloading with Time Travel.
- Comparing Reactive and Traditional.
- ReSwift: Getting Started.
- StateView is a UIView substitute that automatically updates itself when data changes.
- The Objective-C version to "Comparing Reactive and Traditional".
- Let's Play: Refactor the Mega Controller!.
- How to use Redux to manage navigation state in a React Native.
- StateView: UIView substitute automatically updating itself when data changes.
- Mysteries of Auto Layout, Part 2.
- Netflix JavaScript Talks - RxJS Version 5.
- Reactive Streams.
When OOO impulse kicks in…
- You start moving trivial bits of code into classes, with the anticipation that you might use it one day. Stop.
- On naming, semantic clarity trumps brevity. Yup, the verbosity may be worth it.
Pakistan travel bookmarks
- How Philly Cheesesteaks Became a Big Deal in Lahore, Pakistan.
- Karachi.
- Lahore.
- Rabelpindi.
- Shinwari BBQ.
Money bookmarks
- 10 Countries Where That Social Security Check Will Let You Retire in Style (TheStreet).
- 20 items to consider for taxes: Income Tax, Council Tax, and Inheritance Tax (lovemoney.com).
- 25 Bloggers Share The Worst Financial Advice They've Ever Received - Be Net Worthy.
- 25 y/o. Potentially £2m. Bit overwhelmed. : UKPersonalFinance.
- 9 Best New Personal Finance Podcasts - Financial Panther.
- A guide to index funds : UKPersonalFinance.
- A guide to passive investing in the UK.
- A Simple Example of Contributing Half Your Age as a Percentage of Salary to a Pension : UKPersonalFinance.
- AdviserBook | Find a regulated financial adviser near you.
- Amazon.com: How to Interview a Financial Advisor eBook: Piaw Na: Kindle Store.
- Any critique against the Vanguard FTSE global all cap index? : UKPersonalFinance.
- Authorised and recognised funds (FCA).
- Bank Account Savings: Open up multiple accounts to maximise your savings.
- Benefit Calculator - About You - Turn2us.
- Best practices for portfolio rebalancing (Vanguard research July 2010).
- Best practices for portfolio rebalancing (Vanguard).
- Bogleheads® investment philosophy - Bogleheads.
- Borderless account pricing: What are the fees? - TransferWise.
- Buy-to-Let Rental Yield Map 2019 - TotallyMoney.
- Bye Yahoo, and thanks for all the fish (The Financial Hacker) - See comments for alternatives.
- Calculating Your Portfolio’s Rate of Return.
- Can I open a LISA and a Private Pension (Vanguard LifeStrategy)?.
- Capital Gains Tax for Expats - Experts for Expats.
- cFIREsim.
- Cheapest way to pay for a yearly USD subscription in the UK : UKPersonalFinance.
- Check if you need to send a Self Assessment tax return - GOV.UK.
- Check the recognised overseas pension schemes notification list - GOV.UK.
- ChooseFI (Join the Financial Independence Movement).
- Citizens Advice.
- Codementor (Get live 1:1 coding help, hire a developer, & more).
- Coursera (Online Courses From Top Universities. Join for Free).
- Crowdsourced Financial Independence and Early Retirement Simulator/Calculator.
- Crystal's Quora answer to becoming a millionaire (full sensible/conservative advice) .
- Don't Steal Money from Day Traders Before They Lose It (Hacker News).
- EricaJoy on Twitter: what is the process for finding a financial advisor/accountant/etc.
- ETF portfolios made simple (justETF).
- EU investing - Bogleheads.
- European active managers beaten by passives, 10-year study finds (Financial Times).
- Fin-dee (FIRE calculator).
- Finance and capital markets | Economics and finance | Khan Academy.
- Financial education book for teenagers by Martin Lewis : UKPersonalFinance.
- Financial Independence Podcast.
- Financial Independence Retiring Early UK (r/fireuk).
- Financial independence/retire early in Europe (reddit).
- Financial Samurai png (typical reasons to sell assets in short term = bad idea long term).
- Find an Adviser.
- Find Top-Rated Financial Advisers, Mortgage Advisers, Solicitors and Accountants.
- FIRECalc: A different kind of retirement calculator.
- Fund Charting – top-performing funds.
- Fund managers rarely outperform the market for long - Buttonwood #passive-over-active.
- Global All Cap VS Small Cap - first time 24 Male : UKPersonalFinance.
- globaltracker - UKPersonalFinance.
- How do I take a wage from my vanguard lifestrategy fund.
- How does AQR Capital compare with Two Sigma? - Quora.
- How I built a six figure passive income by age 47.
- How I paid off £5000 of consumer debt in 5 months – URBANPLANNED.
- How much more expensive do kids get as they get older? : UKPersonalFinance.
- How to become rich by investing: rational Investing Based on Evidence vs Speculation.
- How to buy your first house for dummies : UKPersonalFinance.
- How to work out what the tax will be on my bonus?.
- I have £x, what should I do with with it? - UKPersonalFinance Wiki.
- IAMA 24yo selling my company for £500,000 - need serious advice. : UKPersonalFinance.
- Income tax calculator: Find out your take-home pay.
- Index Funds - UKPersonalFinance Wiki.
- Index Trackers vs. Managed Funds | The Motley Fool UK #passive-over-active.
- Invaluable Books - Cashflow Cop.
- Investing 101 - UKPersonalFinance Wiki.
- Investing in gold.
- Investment for beginners.
- Investment Jargon Putting Me Off : UKPersonalFinance.
- Is BTL still worth it? : UKPersonalFinance.
- ISA allowance: ISA limits & rules - MoneySavingExpert.
- jlcollinsnh.
- Killik Explains: A short guide to personal pensions (SIPPs) - YouTube.
- Killik Explains: Retirement Saving - Lifetime ISAs vs Pensions - YouTube.
- LifeStrategy® 100 vs FTSE Global All Cap Index Fund – What are the differences?.
- Lifetime ISAs: free 1000 towards your first home or retirement.
- Lifetime ISAs: free £33,000 towards your first home or retirement.
- Low cost index trackers that will save you money (Monevator).
- Mad Fientist: Financial Independence Podcast.
- Managing a windfall - Bogleheads.
- Meet The FIREstarter! - theFIREstarter.
- Millennial Money (Next Generation Personal Finance).
- Mom's Rules of Finance (Workforce Millionaire - Investing in Your Future).
- Monevator — Make more money, invest profitably, retire early.
- Monevator: Compare the UK’s cheapest online brokers.
- Money transfer: compare ways to send money online with Monito.
- Monolune (trading tools, articles).
- Monthly Savings Juggler.
- Morningstar® Integrated Web Tools™ - Instant X-Ray.
- Mortgages - UKPersonalFinance Wiki.
- Mr. Money Mustache — Early Retirement through Badassity.
- National Savings and Investments NS&I.
- NDAQ Stock Quote - Nasdaq, Inc. Common Stock Price - Nasdaq.
- Overseas mortgages: everything you need to know.
- Parking Ticket Appeals: Fight unfair fines - Money Saving Expert.
- Personal Finance Flowchart (github).
- Personal Savings Allowance 2018/19 - up to £1,000 interest tax-free.
- Plotted my income and outgoings.
- Portfolio Performance (crossplatform app).
- Post-Retirement Calculator: Will My Money Survive Early Retirement? Visualizing Longevity Risk - Engaging Data.
- Premium Bond Probability Calculator.
- Premium Bonds: are they worth it? - MoneySavingExpert.
- Purchasing a new property with a baby on the way..? : UKPersonalFinance.
- rebo - Portfolio management software for UK private investors.
- Reddit Personal Finance Flowchart (interactive).
- Remortgaging in 2019 - is now the right time to fix & for how long? - Money To The Masses.
- Rockstar Finance (Curating the best of money and personal finance).
- Rockstar Finance's Directory of Bloggers.
- Safe withdrawal rate (morningstar).
- Salary Negotiation: Make More Money, Be More Valued | Kalzumeus Software.
- SankeyMATIC (BETA): Build a diagram (redditor expenses/wages).
- Saving Ninja: What Platform to Use Now? (iWeb vs Charles Stanley vs Halifax share dealing vs Aviva vs Vanguard Investor).
- Savings accounts: 1.5% easy access or up to 2.7% fixed.
- So, you want to be a landlord?? : financialindependence.
- Started my dream job and now I am pregnant : UKPersonalFinance.
- Starting Late But Retiring Rich: The Story Of Stephen And Becky ChooseFI.
- Stocks & shares ISAs: find the best platform - MSE.
- Stocks — Part XXI: Investing with Vanguard for Europeans.
- Tax rates 2018/19: tax bands explained - MoneySavingExpert.
- Tax rates 2018/19: tax bands explained - MoneySavingExpert.
- The 10 Pillars Of FI ChooseFI.
- The Escape Artist (You can escape to financial freedom).
- The Financial Hacker – A new view on algorithmic trading.
- The Lemon Fool (Discussion forums for UK shares, Personal Finance and Investment).
- The Rise Of Stealth Wealth.
- The world according to fleet-float equity market capitilization.
- Tim Bennett Explains: Which is best - an ISA or a SIPP? - YouTube.
- Top Cash ISAs: 1.35% easy access, 2.3% fixed - MSE.
- UK Financial Planning Week.
- UK Funds FAQ.
- UKPersonalFinance redditor quote: "Deprogrammed myself from the zeitgeist of brands/labels. I either buy cheap or I buy quality dependent on application, but in either event I ignore the label."
- Understanding your employees' tax codes: What the numbers mean - GOV.UK.
- United Kingdom | Countries (FIREhub.eu).
- Use the Avios Rewards Flight Calculator to plan flights.
- Vanguard FTSE Global All Cap Index A Acc GBP (trustnet).
- Vanguard FTSE Global All Cap Index Fund Investor A GBP Accumulation (Morningstar).
- Vanguard FTSE Global All Cap Index Fund Investor A GBP Accumulation, GB00BD3RZ582:GBP summary - FT.com.
- Vanguard LifeStrategy 100% Equity A Acc (Trustnet).
- Vanguard LifeStrategy 80% Equity A (Trustnet).
- Vanguard LifeStrategy® Funds.
- Vanguard: Helping you reach your investing goals | Vanguard.
- Vanguard: LifeStrategy® 40% Equity Fund - Accumulation.
- Vanguard’s simple Pension Calculator.
- Way Of The Financial Samurai: Core Principles For Financial Independence.
- What are some good net worth tracking tools? (for the UK) : FIREUK.
- What Does a Declaration of Trust / Deed of Trust Do?.
- What if You Only Invested at Market Peaks?.
- What is the smartest financial habit that you have? - Quora.
- Where does export to? (2018) | OEC - The Observatory of Economic Complexity.
- Why a world equity index tracker?.
- Why are most people broke? - Quora.
- Why Bond Prices and Yields Move in Opposite Directions.
- Why do we believe that the price of stocks will grow over the long term.
- Why Jack Bogle Doesn't Like ETFs (Forbes - YouTube).
- www.stonebanks.co.uk (capital gains calculator).
- X-O.co.uk - Execution Only Share Dealing (broker).
Scotland travel bookmarks
St. Petersburg travel bookmarks
8 week half-marathon training
An 8-week training schedule:
WEEK | MON | TUE | WED | THU | FRI | SAT | SUN |
---|---|---|---|---|---|---|---|
1 | Rest | 5 Km | 5 Km | Rest | 5 Km | ||
29:56 | 29:54 | 29:45 | 1:00:55 | ||||
2 | Rest | 7 Km | 5 Km | Rest | 5 Km | 10 Km | |
41:36 | 27:52 | 28:23 | 59:17 | ||||
3 | Rest | 5 Km | Rest | 5 Km | 12 Km | ||
49:29 | 29:33 | 27:50 | 1:06 | ||||
4 | Rest | 8 Km | Rest | 8 Km | Rest | 5 Km | 14 Km |
46:39 | 49:28 | 29:40 | |||||
5 | Rest | 8 Km | Rest | Rest | |||
48:50 | 53:38 | ||||||
6 | Rest | 8 Km | Rest | 8 Km | 19 Km | ||
51:39 | 37:09 | 2:02 | |||||
7 | Rest | 8 Km | Rest | 12 Km | Rest | 8 Km | 16 Km |
52:55 | |||||||
8 | Rest | 8 Km | Rest | 5 Km | 5 K | Rest | Race |
Haskell bookmarks
- A gentle introduction to profunctors talk.
- A Haskell Reading List (Hacker News).
- A Haskell Reading List.
- Advice for Haskell beginners (2017) (Hacker News).
- An opinionated guide to Haskell in 2018.
- Haskell Programming: From First Principles.
- Haskell's kind system - a primer.
- Higher order functions.
- Intero: Complete interactive development program for Haskell.
- Introduction to higher-order functions.
- Pragmatic Haskell for Beginners, Lecture 1.
- Renzo Carbonara (Hackage).
Haskell notes
Referential transparency
An expression consistently evaluating to the same result, regardless of context.
References
Database bookmarks
Bruges travel bookmarks
- assietteblanche.be.
- Beer flavored meals at Den Dyver.
- bistrozwarthuis.be.
- Eat fries in front of the belfry and climb it.
- kok-au-vin.be.
- kurtspan.be.
- Minnewater and the old Beguinage.
- Old Saint john's Hospital.
- Relic of the Holy Blood and City hall.
- restomojo.tk.
- The Chocolate Line.
- The Garre, near the Burg and drink their house Tripel.
- tomsdiner.be.
- Try out Straffe Hendrik beer at brewery terrace.
- Walk behind Gruuthuse over the little Saint Bonifaas bridge.
Emacs lisp snippets
cl-loop for in
(cl-loop for day in '("mon" "tue" "wed" "thu" "fri" "sat" "sun") do (print day))
"mon" "tue" "wed" "thu" "fri" "sat" "sun"
cl-loop for from to
(cl-loop for x from 1 to 5
do (print x))
1 2 3 4 5
pcase literal matching
(pcase "word" ('word (message "Matched 'word symbol")) ("word" (message "Matched \"word\" string")))
Matched "word" string
Avoid nesting with the help of thread-first and thread-last.
(thread-last "12.....34" (string-remove-prefix "1") (string-remove-suffix "4"))
2.....3
Find file upwards, up parents, up hierarchy
(locate-dominating-file FILE NAME)
Find executable in PATH
(executable-find COMMAND)
Read string with completion (helm/ido/ivy friendly)
(completing-read PROMPT COLLECTION &optional PREDICATE REQUIRE-MATCH INITIAL-INPUT HIST DEF INHERIT-INPUT-METHOD)
Execute command/process and return list (similar to shell-command-to-string)
(process-lines PROGRAM &rest ARGS)
Iterating org buffer
(org-element-map (org-element-parse-buffer) '(headline link) (lambda (element) (cond ((and (eq (org-element-type element) 'headline) (= (org-element-property :level element) 1)) (print "headline")) ((eq (org-element-type element) 'link) (print "link"))) nil))
Some modern Objective-C idioms
NSNumber literals
NSNumber *number1 = @1024; NSNumber *number2 = @1024.123f; NSNumber *number3 = @'A'; NSNumber *number4 = @YES; NSNumber *number5 = @24ul; // Unsigned long. NSNumber *number6 = @123456ll; // Long Long. NSNumber *number7 = @5050.50; // Float. NSNumber *number8 = @1543; // Integer NSNumber *number9 = @111.456; // Double
Array literals
NSArray *names = @[@"John\n", @"Peter\n", @"Jaye\n", @"George\n", @"Max"]; NSArray *mutableNames = [@[@"John\n", @"Peter\n", @"Jaye\n", @"George\n", @"Max"] mutableCopy];
Cross-platform development bookmarks
Generating a random MAC address
As some point I had to generate a random MAC address. This is the snippet I used:
import random def randomMAC(): mac = [0x00, 0x16, 0x3e, random.randint(0x00, 0x7f), random.randint(0x00, 0xff), random.randint(0x00, 0xff), ] return ':'.join(map(lambda x: "%02x" % x, mac)) print 'MAC => %s' % randomMAC()
MAC => 00:16:3e:7e:f7:fa
Defined elisp variables matching regexp
You can use "M-x apropos-variable" to get documentation for variables matching a pattern. For more flexibility, some elisp can help with getting a list of all variables matching a regexp:
(defun ar/variables-matching-pattern (pattern) "Get a list of all variables matching PATTERN." (let ((matched-variables '())) (mapatoms (lambda (symbol) ;; Symbol is variable? (when (and (boundp symbol) (string-match pattern (symbol-name symbol))) (add-to-list 'matched-variables symbol)))) matched-variables)) (let ((variables "")) (mapc (lambda (variable-symbol) (setq variables (concat variables (format "%s => %s\n" (symbol-name variable-symbol) (symbol-value variable-symbol))))) (ar/variables-matching-pattern "^tern-.*")) variables)
tern-mode-keymap => (keymap (3 keymap (4 . tern-get-docs) (3 . tern-get-type) (18 . tern-rename-variable)) (27 keymap (44 . tern-pop-find-definition) (67108910 . tern-find-definition-by-name) (46 . tern-find-definition))) tern-update-argument-hints-async => nil tern-known-port => nil tern-mode => nil tern-activity-since-command => -1 tern-project-dir => nil tern-last-point-pos => nil tern-last-completions => nil tern-explicit-port => nil tern-idle-time => 2.5 tern-find-definition-stack => nil tern-last-argument-hints => nil tern-idle-timer => nil tern-server => nil tern-last-docs-url => nil tern-buffer-is-dirty => nil tern-command-generation => 0 tern-flash-timeout => 0.5 tern-update-argument-hints-timer => 500 tern-mode-hook => nil tern-command => (tern)
Proselint via Emacs flycheck
Based on Linting Prose in Emacs…
Needs proselint installed:
pip install proselint
Also needs a flycheck checker defined:
(flycheck-define-checker proselint "A linter for prose." :command ("proselint" source-inplace) :error-patterns ((warning line-start (file-name) ":" line ":" column ": " (id (one-or-more (not (any " ")))) (message) line-end)) :modes (gfm-mode markdown-mode org-mode text-mode)) (add-to-list 'flycheck-checkers 'proselint)
Generate go struct definition from json file
From Generate go struct definition from json file, and before I forget:
curl http://url.tld/file.json | goj