index rss mastodon twitter github linkedin email
Álvaro Ramírez
sponsor

Álvaro Ramírez

05 April 2024 Fresh Eyes now on the App Store

A couple of days ago, I introduced Fresh Eyes, a little macOS utility to help me practice the 20-20-20 rule and take better care of my vision while on the computer.

big-sur-eyes.gif

Today, Fresh Eyes was approved and is now available on the macOS App Store.

Fresh eyes icon

download-on-app-store.png

03 April 2024 Fresh Eyes: 20-20-20 for macOS

I've been lucky to have enjoyed healthy vision throughout my life. That is, until recently. Nothing major, I'll need glasses for some activities. I also learned from the optometrist I should follow the 20-20-20 rule to reduce eye strain.

The 20-20-20 rule is simple:

Take a break from looking at your computer screen every 20 minutes and look away at something roughly 20 feet away (6 metres) for 20 seconds.

While there are no shortages of macOS timer apps available, I figured it'd be fun to build a 20-20-20 one anyway.

Meet Fresh Eyes. I've been using it in the last few days. If you'd like to give it a try, send me an email at me@xenodium.com and I'll reply with a TestFlight invite.

fresh-eyes.gif

If looking for alternatives, Samuel W. Flint offers a couple of great options:

25 March 2024 Emacs 29.3 emergency release

It was only last week when I upgraded to Emacs 29.2. Yup, I was late to the party. This week, we have a new release.

Emacs 29.3 is an emergency bugfix release, so this time I've upgraded promptly. I'm on macOS using the great Emacs Plus so upgraded via Homebrew using:

brew reinstall emacs-plus@29 --with-imagemagick --with-no-frame-refocus --with-native-comp --with-savchenkovaleriy-big-sur-3d-icon --with-poll

29.3.png

ps. Like this splash screen? Check out the Emacs eye candy post.

24 March 2024 Emacs: Toggling the continuation indicator

By default, Emacs typically displays curly arrows when wrapping lines. While likely a handy feature to some, I didn't really find much use for it. At the same time, I never looked into their removal until now.

Turns out, there's a continuation entry in fringe-indicator-alist variable that handles this. Removing this entry also removes the curly arrows.

(setq-default fringe-indicator-alist
              (delq (assq 'continuation fringe-indicator-alist) fringe-indicator-alist))

Alternatively, one could write a simple function to toggle displaying the continuation indicator.

curly.gif

(defun toggle-continuation-fringe-indicator ()
  (interactive)
  (setq-default
   fringe-indicator-alist
   (if (assq 'continuation fringe-indicator-alist)
       (delq (assq 'continuation fringe-indicator-alist) fringe-indicator-alist)
     (cons '(continuation right-curly-arrow left-curly-arrow) fringe-indicator-alist))))

That's it for this post. A tiny tip. Perhaps there's a better way to handle it. If you know, I'd love to know too (Mastodon / Twitter / Reddit / Email).

22 March 2024 The Org bundle

Plain Org / plainorg.com

My more generic solution to access org files on the go and away from Emacs.

plainorg-600x0w.png

Flat Habits / flathabits.com

My take on frictionless habit tracking truly respecting user privacy and their time (absolutely no distractions).

flathabits-600x0w.png

*scratch* / App Store

Sure, we have tons of note-taking apps but most require more steps than desirable to write something down ASAP. Launch the app and you're good to write. No new note creation, bring keyboard up, etc.

scratch-600x0w.webp

Common denominator

In addition to being offline-first, no cloud, no login, no ads, no tracking, no social… each app targets a specific purpose, sharing an important common denominator: they all use org markup as the underlying storage.

The Org bundle / App Store

While you can still get each of my apps individually, you now have the option to get them all as a single bundle: The Org bundle.

bundle.png

Journelly joining the bundle soon…

Continuing on the org storage theme, I got another app in the works. Also joining The Org bundle, maintaining its privacy-first approach: offline, no cloud, no login, no ads, no tracking, no social… this time in the journaling space.

Journelly is currently in beta, want to join?

journelly-demo.gif

19 March 2024 sqlite-mode-extras on MELPA

sqlite-previous.gif

Emacs 29 introduced the handy sqlite-mode. Soon after, I tried a couple of experiments here and there to bring additional functionality.

Folks reached out. The additions seemed useful to them and were keen on upstreaming or pushing to MELPA. While I can't commit to upstreaming at this moment, I can happily meet halfway on MELPA.

As of a couple of days, you can find sqlite-mode-extras on MELPA and GitHub. Contributions totally welcome.

While I haven't heard of issues, please continue treating the package as experimental and exercise safety with your data. Please back up.

19 March 2024 Som tam salad dressing recipe (improvised)

Lately, I've been slightly obsessed with Som Tam, a magnificent salad packing both crunch and flavour.

I didn't have all the right ingredients for the full-blown salad at home, so I set out to experiment with the dressing's punchy flavours. While I've gone a little rogue here, I mean no disrespect to the faithful recipe and all its glory. Luckily, I did have fish sauce at home, which I considered the core ingredient, and made do with everything else I could find.

This is where I landed:

  • 2 cloves of garlic
  • Thai chillies to taste (improvised with chilli flakes)
  • 2 tablespoons of fish sauce
  • 1 tablespoons of palm sugar (improvised with honey)
  • 2 limes squeezed (improvised with lemon)
  • 1 tablespoon of dried shrimp (didn't have any)
  • 1 small plum tomato (I used 2 of those bite-size ones)

Using my trusty mortar and pestle, I ground and crushed the garlic, chilli flakes, and tomato, forming a paste of sorts. Then added the remaining liquids (fish sauce, honey and lemon) diluting the paste.

  • 2 tablespoons of roasted peanuts (roasted some cashews)

Most recipes seem to suggest using peanuts, though my local Thai restaurant uses cashews. Luckily I had cashews at home, so I'm copied my local. Roasted them on pan for a few minutes.

As you can imagine, I didn't just have a green papaya laying around at home, so I experimented with other crunchy veggies. While I won't reveal what the other veggies were (oh man, I've gone way off script), both my other half and I were happy with the results.

Som Tam dressing packs an awesome punch. If your salads were feeling a little boring, give this a try!

14 March 2024 My first bread (pane dei Castelli recipe)

No-knead method

Dough

  • 1 1/4 cups (300g) lukewarm water
  • 2 teaspoons (8g) salt
  • 1 teaspoon (3.5g) yeast
  • 3 cups (420g) all purpose flour

Mix thoroughly (I like to use Ciro's spoon mixing method from this video), cover and rest for 6 hours. Stretch and fold if the dough collapsed on itself to rescue.

Bake

  • Preheat oven (and dutch oven) at 230°C.
  • Flour.

I didn't have a dutch oven, but my oven-proof saucepans (with lid) did just fine. Carefully take the pan out of the oven, sprinkle the bottom with some flour, and place the dough inside. Cover with lid (careful, also hot) and bake for 30-35 minutes.

Crust

  • Reduce heat to 200°C.

Remove the lid and bake for another 10 minutes or until you get the crust darkness of your choice.

Rest

Let the bread cool on a cooling rack for 45 mins before cutting. If no rack available, set upsidedown.

Stretch and fold method

Dough

  • 1 1/4 cups (300g) lukewarm water
  • 2 1/2 teaspoons (10g) salt
  • 2 teaspoon (7g) yeast
  • 3 cups (420g) all purpose flour

Mix thoroughly (I like to use Ciro's spoon mixing method from this video), cover and rest for 6 hours. Stretch and fold if the dough collapsed on itself to rescue.

Stretch and fold

See Emma Fontanella's stretch and fold technique and apply 4-5 times every 30 mins.

Follow no-knead method

Remaining steps are the same as the no-knead method.

11 March 2024 Seafood stew recipe

I've made this seafood stew a handful of times and it's always delivered.

IMG_7030.jpg

Garlic almond paste

  • 1/8 cup of olive oil.
  • 8 cloves of garlic chopped.
  • 1/4 cup almond meal (flour).

Cook garlic in low-medium heat until softened. Add almond meal and cook 3-4 mins or until golden. Set aside to cool.

  • 1 large handful of parsley.

Blend the almond mixture to make a paste. Set aside.

Spices

  • 1 large onion halved and sliced.

In a large pan, cook onion until softened.

  • 1/8 cup of olive oil.
  • 1 large red chilli finely chopped (on occasions, I use chilli flakes).
  • 1 teaspoon of smoked paprika.
  • 2 bay leaves.

Add chilli, paprika, and bay leaves and cook for 30 seconds (or fragrant).

Liquids

  • 1/3 cup dry white wine (I've used cooking sake on occasion).
  • 1000g of passata (typically comes in 500g packs).
  • 400g plum tomatoes tin (undrained).
  • 1 teaspoon of saffron threads (soaked in 2 tablespoons of water).
  • 1 tablespoon of tomato paste.
  • 2 cups of fish stock (I've used 2 stock cubes + same amount of water).

Add the wine, passata, plum tomatoes, saffron, tomato pate, and fish stock. Simmer for 10 minutes.

Fish

  • 700g of firm white fish (cut into 5 cm chunks).
  • 12 mussels.

Add the almond paste, fish, and mussels. Cook for 3-4 minutes.

  • 12 prawns (shelled).

Add the prawns. Cook for 3 minutes (or pink).

Finishing touches

Season with salt and pepper to taste. I've added roughly a teaspoon of salt.

Garnish generously with parsley and you're good to go.

13 January 2024 A Cloudflare Workers primer: hello world

o______________o
| Hello world! |
o--------------o
        \   ^__^
         \  (oo)_______
            (__)\       )\/\
                ||----w |
                ||     ||

Keen to get started with your Hello World Cloudflare Worker? Skip to the setup section.

A little background

The vast majority of my software development experience has been centered around client-side software. The few times I've needed a server-side component for a hobby project, I've historically provisioned a linux virtual machine somewhere and ran whatever services I needed. I have to admit though, I don't enjoy the provisioning process, configuration, maintenance, upgrades, database admin, etc. which take time away from the part I enjoy more: building and experimenting with features.

While containers have made things somewhat simpler, much of the maintenance tradeoffs remain.

These days, the server-managing overhead has been greatly reduced by "serverless" solutions. Odd terminology for a server offering, but I digress. It more or less refers to removing most of that additional responsibility that comes with managing your own servers and enabling you to focus on building your business logic. Having said that, I've typically shied away from these services, with the possibly irrational fear of vendor lock-in.

The thing is, if most of my potential server-side needs merely require an entry point (where I could route/handle incoming requests) and possibly some persistence (maybe a database), I should be able to abstract these things away and build server-side logic against portable abstractions. With that in place, maybe there's little vendor lock-in to worry about? Who knows, the devil's in the detail. If I keep shying away from these services, I'll never know, so maybe I should try some and see.

Let's try Cloudflare Workers

There are no shortages of serverless options offering functions as a service. Google Cloud, AWS Lambda, Azure Functions, Vercel Functions, Netlify Functions, Fastly, Cloudflare workers, I could go on…

While I haven't researched the different offerings, I had made a mental note to check out Cloudflare Workers as they had announced D1, their database backed by SQLite …and who doesn't love SQLite? ;) OK, I'm no expert here, but I have had a pleasant experience whenever I've used it. These days, even Emacs 29 got some SQLite love, which prompted me to add cell navigation/navigation and try other experiments.

D1 / SQLite in beta

Keep in mind that D1 is in public beta and not yet recommended for large production workloads. From the Cloudflare site:

"While the D1 team expects breaking changes and issues to be minimal, they may still occur. The D1 team generally does not recommend running large production workloads on beta products."

Workers cost

In terms of pricing (as of 2024-01-13), the free tier enables workers to handle up 100,000 requests per day. Plenty for trying things out.

In any case, we're only checking out Cloudflare's offering, so let's move on…

Settings up a new Cloudflare Worker (via web dash)

Cloudflare has a tiny snippet on their Workers landing page that sets things up rather quickly, but I won't be using it.

~/ $ npm create cloudflare -- my-app
~/ $ cd my-app
~/ $ npx wrangler deploy
Published https://my-app.world.workers.dev

⚠️ Note: before you get copying and pasting, read on.

Cloudflare's snippet is helpful, but it does quite a bit under the hood. I'm somewhat of a node and serverless noob, so I wanted to understand things a little more and figure out the bare minimum needed to start a minimal Cloudflare Worker project.

Instead, we'll first click here and there over at https://dash.cloudflare.com to spin off our new worker from the web and later continue from the command line.

cf-new-0.png

cf-new-1.png

Give the worker a name. We'll call it "todos" to give ya a little sneak peak at what the next post is possibly about… But you can call it whatever you'd like. Keep in mind you'll need to use this name to refer to your new worker.

cf-new-2.png

Congrats, you've now deployed a new worker. You can access it via the URL that looks something like https://todos.somewhere.workers.dev

hello-dark.png

This is great and all, but we want to build something with this new worker, so let's set up our local development environment…

Prerequisites

You'll need node.js installed on your machine.

I happen to be on macOS, so I installed node via Homebrew.

brew install node

Create a new node project

We want to start with a bare bones node project, so let's do just that.

mkdir HelloCloudflareWorker
cd HelloCloudflareWorker
npm init -y

Install TypeScript (compiler)

I like some guardrails when targetting Javascript, so I'll use the TypeScript compiler in this project. Let's install it.

npm install --save-dev typescript
npx tsc --init

Install Cloudflare Typescript types

To have Cloudflare types information accessible to the TypeScript compiler, we'll need to install that too.

npm install --save-dev @cloudflare/workers-types

Install Wrangler (Cloudflare tooling)

To manage your worker from the command-line, you'll need Cloudflare's wrangler tool. Let's install it.

npm install --save-dev wrangler

Point Wrangler to our worker

We're done installing things now. Let's point wrangler to our new worker by creating its config file.

wrangler.toml

name = "todos"
main = "worker/worker.ts"

Worker entry point

By default, the worker we created using Cloudflare's dash has the following entry point:

export default {
  async fetch(request, env, ctx) {
    return new Response( 'Hello World!'):
  }
}

However, this isn't yet included in our development environment. We need to write our first bit of code. You may have noticed our wrangler.toml is pointing to the main entry point (worker/worker.ts) and this file doesn't exist yet. Let's create it, though be sure to also create its owning directory:

mkdir worker

Now we can create our very own worker/worker.ts. Let's make the first change that shapes worker to our liking. Rather than just printing "Hello World", let's style things up using our cow friend. We'll create worker/worker.ts and include the spiffed up message.

worker/worker.ts

import { Env, ExecutionContext } from '@cloudflare/workers-types';

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    let defaultResponse = `
   o______________o
   | Hello World! |
   o--------------o
           \\   ^__^
            \\  (oo)\_______
               (__)\\       )\\/\\
                   ||----w |
                   ||     ||`
    return new Response(defaultResponse);
  }
};

It's worth mentioning the import statement, since it brings Cloudflare's type information for both Env and ExecutionContext.

Running worker locally

Okay, we now have our worker/worker.ts code ready to go. Let's run it locally. For that we use the wrangler utility.

npx wrangler dev

dev.png

With that, you'll notice the worker is now running locally and waiting to be visited at http://localhost:8787.

moo.png

Deploying worker

When we first created the worker via https://dash.cloudflare.com, it automatically deployed to https://todos.somewhere.workers.dev. But our mods only ran locally. Let's deploy, again with the wrangler utility.

npx wrangler deploy

deploying.png

We're good to go. Let's point our browser to the worker's public location.

deployed.png

…and with that, we have a functional Cloudflare Worker and a local development environment to shape things up however we'd like. What would you use the Worker for?

Gave this primer a try? I'd love to hear from ya (Mastodon / Twitter / Reddit / Email).

Enjoying this content? Find it useful?

Consider ✨sponsoring me✨ or buy ✨my iOS apps✨.

01 January 2024 A chatgpt-shell compose ux experiment

It's been roughly 9 months since I experimented with wiring the ChatGPT API to an Emacs comint buffer in chatgpt-shell. ChatGPT's request-response nature maps fairly well to a shell's mode of interaction.

In the past, I've also talked about blurring the lines between shell and editor. That is, using Emacs as your shell (eshell being my favourite) enables compounding goodies from both shell and editor when both are used from the same app.

Keeping interactions within the same app also cuts down on some of that friction that comes with context switching between your text editor and the browser for llm things.

Today, my interactions with llms typically consists of copying and pasting details from other Emacs buffers, crafting a query, and finally submitting by pressing enter (RET) from a shell like chatgpt-shell.

shell-find-bug.gif

With the entire interaction happening from Emacs, we're already cutting a fair amount of friction… But we can do better, specially when copying, pasting, and crafting those multi-line queries (you don't want to prematurely submit those shell queries by inadvertently pressing RET when you want a newline).

chatgpt-shell-prompt-compose

This is where chatgpt-shell-prompt-compose comes in, an opinionated experiment bringing some of my favourite "compose" features over from the likes of magit commit buffers, org capture, mu4e compose, and so on…

You can bring a compose buffer up by invoking M-x chatgpt-shell-prompt-compose. From there, you can both craft and send your queries. If you're a magit fan, the process should feel fairly familiar with crafting a git commit message by editing away and quickly committing (via C-c C-c binding). Similarly, you can also abort with the familiar C-c C-k binding.

10k.gif

I use this compose utility often enough that I bound it to C-c C-e, though this may not be your cup of tea (needs overriding other mode maps).

(use-package chatgpt-shell
  :commands
  (chatgpt-shell
   chatgpt-shell-prompt-compose)
  :bind (("C-c C-e" . chatgpt-shell-prompt-compose)
         :map org-mode-map
         ("C-c C-e" . chatgpt-shell-prompt-compose)
         :map eshell-mode-map
         ("C-c C-e" . chatgpt-shell-prompt-compose)
         :map mu4e-compose-mode-map
         ("C-c C-e" . chatgpt-shell-prompt-compose)
         :map emacs-lisp-mode-map
         ("C-c C-e" . chatgpt-shell-prompt-compose)))

While the compose buffer displays a single query/response at a time, it also follows on from previous requests. You can press r to reply and continue the conversation.

marathon.gif

The compose buffer is fairly stateless and mostly serves as viewport over the last query in the shell itself. If you invoke chatgpt-shell-prompt-compose with a prefix (ie. C-u), it wipes the shell history. You can do it from the compose buffer itself, if you forgot to prior to launching.

You can also use the o binding to jump to the "other buffer" (the shell carrying the conversation history).

other.gif

If using the r and o bindings in a compose buffer sounds a little strange, fear not. The compose buffer is writeable while crafting queries, thus you can safely insert any character. Once a query is submitted (via C-c C-c), the buffer automatically becomes read-only, and thus unlocking single-character bindings.

Another magit commit favorite of mine is using the M-p or M-n bindings to insert previous messages via git-commit-prev-message or git-commit-next-message.

With that in mind, I also brought M-p and M-n over to the editable compose buffer.

previous-next-history.gif

If cycling isn't efficient enough, you can also use the typical M-r binding to search and insert from history.

search-history.png

Now, getting back to removing some of that copy-pasting friction… Selecting text in any buffer and invoking M-x chatgpt-shell-prompt-compose (or C-c C-e in my case) automatically pastes the region into the compose buffer. You get to tweak your query before submitting (via that familiar C-c C-c), in a more flexible buffer (compared to a shell).

Note: You can also invoke the compose command with a region as many times as you'd like. Each region is sent to the compose buffer, so you can craft more involved queries before submission.

find-and-fix-bug.gif

While I typically prefer short query responses (using diffs like the example above), I sometimes want full snippets as follow-ups. I found myself typing "show entire snippet" often enough, that I now use one of those single-character bindings (e) for this purpose.

show-entire-snippet.gif

Compose bindings

I've showcased most of the compose key bindings, here's the whole lot (so far anyway), which you can also view from chatgpt-shell-prompt-compose's documentation.

Editing

  • C-c C-c to send the buffer query.
  • C-c C-k to cancel compose buffer.
  • M-r search through history.
  • M-p cycle through previous item in history.
  • M-n cycle through next item in history.

Read-only

  • C-c C-c After sending offers to abort query in-progress.
  • q Exits the read-only buffer.
  • g Refresh (re-send the query). Useful to retry on disconnects.
  • n Jump to next source block.
  • p Jump to next previous block.
  • r Reply to follow-up with additional questions.
  • e Send "Show entire snippet" query.
  • o Jump to other buffer (ie. the shell itself).
  • C-M-h Mark block at point.

Buyer beware: it's all pretty experimental

When I started playing with the compose buffer idea, I wasn't too sure whether or not its usage would stick, so I basically hacked chatgpt-shell-prompt-compose to pieces. A cheap prototype of sorts to validate the idea before fully committing to a more involved solution.

I'll eventually rewrite chatgpt-shell-prompt-compose as either a major or minor mode if there's enough interest.

For now, I'll continue using as is to validate its usefulness.

If you give chatgpt-shell-prompt-compose a try, I'd love to hear your feedback (Mastodon / Twitter / Reddit / Email).

Enjoying this content? Find it useful? Consider sponsoring.

22 December 2023 A Murder at the End of the World: Are you Vi or Emacs?

I've enjoyed watching A Murder at the End of the World. The show may resonate with folks following the tech world. Won't say much more than that…

What I can maybe say is, the shows features Reddit, Brave browser, terminal usage (ifconfig, nmap, hydra, responder), and a reference to the good 'ol Vi vs Emacs rivalry, which I hope folks these days don't take further than friendly teasing between dear cousins.

In any case, being an Emacs nut, the scene gave me a good tickle. It's a great show, with a lovely Emacs cherry on top! While the show title and description didn't immediately draw me in, I'm glad I gave it a chance.

vi-or-emacs.webp

what-is-emacs.webp

17 December 2023 An basic Mullvad WireGuard setup for macOS

Needed a VPN to test an API from a different location. Gave Mullvad a try.

Pretty neat, you can generate an account number without providing an email address. You can also pre-pay with a ton of options, including cash, crypto, credit cards, PayPal, wire transfers…

After seeing your account credited, one can download a generated WireGuard configuration. Also a WireGuard noob, so took this opportunity to give it a try.

The WireGuard macOS app has an "Import Tunnel(s) from File…" option where you can import the .conf file downloaded from Mullvad's generated config. After that, all I had to do was click the "Activate" button and Bob's your uncle.

wg-redact.png

You can test your connection via:

curl https://am.i.mullvad.net/connected
You are connected to Mullvad (server xxxxxx). Your IP address is xxx.xxx.xxx.xxx

I had a brief stint at using the command-line alternative via homebrew brew install wireguard-go wireguard-tools, but that seems to fail silently:

wg-quick up xxxxx
[#] wireguard-go utun
[+] Interface for xxxxx is utun7
[#] wg setconf utun7 /dev/fd/63
[#] ifconfig utun7 inet xxx.xxx.xxx.xxx/xx xxx.xxx.xxx.xxx alias
[#] ifconfig utun7 inet6 xxxx:xxxx:xxxx:xxxx::x:xxxx/xxx alias
[#] ifconfig utun7 up
[#] route -q -n add -inet6 ::/1 -interface utun7
[#] route -q -n add -inet6 8000::/1 -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx/x -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx/x -interface utun7
[#] route -q -n add -inet xxx.xxx.xxx.xxx -gateway xxx.xxx.xxx.xxx
[#] networksetup -getdnsservers Wi-Fi
[#] networksetup -getsearchdomains Wi-Fi
[#] networksetup -getdnsservers iPhone USB
[#] networksetup -getsearchdomains iPhone USB
[#] networksetup -getdnsservers Thunderbolt Bridge
[#] networksetup -getsearchdomains Thunderbolt Bridge
[#] networksetup -getdnsservers xxxxx
[#] networksetup -getsearchdomains xxxxx
[#] networksetup -setdnsservers iPhone USB xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains iPhone USB Empty
[#] networksetup -setdnsservers xxxxx xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains xxxxx Empty
[#] networksetup -setdnsservers Wi-Fi xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains Wi-Fi Empty
[#] networksetup -setdnsservers Thunderbolt Bridge xxx.xxx.xxx.xxx
[#] networksetup -setsearchdomains Thunderbolt Bridge Empty
[+] Backgrounding route monitor
curl https://am.i.mullvad.net/connected
You are not connected to Mullvad. Your IP address is xxx.xxx.xxx.xxx

I'm on a Macbook M1 Pro, running macOS Sonoma. If you got wg-quick working on Sonoma, I'd love to hear from ya (Mastodon / Twitter / Reddit / Email).

06 December 2023 An iOS journaling app powered by org plain text

I've been experimenting with building a rich text editing component for iOS, powered by org markup. The idea is to offer a mobile-friendly editing experience, backed by our beloved plain text format.

rich-text-experiment.gif

To make things a little more interesting, I'm introducing a new org-based app to help anyone with regular journaling.

👉 Meet ✨Journelly✨

journelly.jpg

Plain text is the serialization format. No conversion/import/export needed.

journelly.gif

Though it's early days, it's fairly functional. Been using it daily for some time. You can opt in to use an external org file and sync with your beloved Emacs.

Want to give it a try? Want a TestFlight invite? Send me an email address (any would do) at either of these: Mastodon / Twitter / Reddit / Email.

The topic of org being fairly Emacs-oriented, though a strength for someone far down the rabbit hole, it is understandable to call it out for someone in a different position. Lucky for us, org markup is plain text and can be implemented by apps other than Emacs, like Journelly itself for iOS and even more experimentally on macOS:

macos.png

And like Journelly for iOS, I got other org things available on iOS:

As an Org mode fan, so I wrote Plain Org for iOS. It's on the App Store.
Inspired by Atomic Habits, I wrote Flat Habits for iOS. Also on the App Store.
I needed an Emacs-inspired scratch buffer on iOS (who doesn't?), so I built one.

Just like the stuff I do or write about? Sponsor me.

29 November 2023 Building your own bookmark launcher

sponsor✨ this content

I've been toying with the idea of managing browser bookmarks from you know where. Maybe dump a bunch of links into an org file and use that as a quick and dirty bookmark manager. We'll start with a flat list plus fuzzy searching and see how far that gets us.

The org file would look a little something like this:

My bookmarks
- [[https://lobste.rs/t/emacs][Emacs editor (Lobsters)]]
- [[https://emacs.stackexchange.com][Emacs Stack Exchange]]
- [[https://www.reddit.com/r/emacs][Emacs subreddit]]
- [[https://emacs.ch][Emacs.ch (Mastodon)]]
- [[https://www.emacswiki.org][EmacsWiki]]
- [[https://planet.emacslife.com/][Planet Emacslife]]

Next we need fuzzy searching, but first let's write a little elisp to extract all links from the org file:

(require 'org-element)
(require 'seq)

(defun browser-bookmarks (org-file)
  "Return all links from ORG-FILE."
  (with-temp-buffer
    (let (links)
      (insert-file-contents org-file)
      (org-mode)
      (org-element-map (org-element-parse-buffer) 'link
        (lambda (link)
          (let* ((raw-link (org-element-property :raw-link link))
                 (content (org-element-contents link))
                 (title (substring-no-properties (or (seq-first content) raw-link))))
            (push (concat title
                          "\n"
                          (propertize raw-link 'face 'whitespace-space)
                          "\n")
                  links)))
        nil nil 'link)
      (seq-sort 'string-greaterp links))))

The snippet uses org-element to iterate over links to collect/return them in a list. We join both the title and url, so searching can match either of these values. We also add a little formatting (new lines/face) to spiff things up.

(browser-bookmarks "/private/tmp/bookmarks.org")
(#("Planet Emacslife\nhttps://planet.emacslife.com/\n" 17 46
   (face whitespace-space))
 #("EmacsWiki\nhttps://www.emacswiki.org\n" 10 35
   (face whitespace-space))
 #("Emacs.ch (Mastodon)\nhttps://emacs.ch\n" 20 36
   (face whitespace-space))
 #("Emacs (Stack Exchange)\nhttps://emacs.stackexchange.com\n" 23 54
   (face whitespace-space))
 #("Emacs (Reddit)\nhttps://www.reddit.com/r/emacs\n" 15 45
   (face whitespace-space))
 #("Emacs (Lobsters)\nhttps://lobste.rs/t/emacs\n" 17 42
   (face whitespace-space)))

We can now feed our list to our preferred narrowing framework (ivy, helm, ido, vertico) and use it to quickly select a bookmark. In the past, I've used the likes of ivy-read directly, though have since adopted the humble but mighty completing-read which hooks up to any of the above frameworks.

With that in mind, let's use completing-read to make a selection and split the text to extract the corresponding URL. Feed it to browse-url, and you got your preferred browser opening your bookmark.

(defun open-bookmark ()
  (interactive)
  (browse-url (seq-elt (split-string (completing-read "Open: " (browser-bookmarks "/private/tmp/bookmarks.org")) "\n") 1)))

I remain a happy ivy user, so we can see its fuzzy searching in action.

emacs-bookmark.gif

At this point, we now have our bookmark-launching Emacs utility. It's only an M-x open-bookmark command away, but we want to make it accessible from anywhere in our operating system, in my case macOS.

Let's enable launching from the command line, though before we do that, let's craft a dedicated frame for this purpose.

(defmacro present (&rest body)
  "Create a buffer with BUFFER-NAME and eval BODY in a basic frame."
  (declare (indent 1) (debug t))
  `(let* ((buffer (get-buffer-create (generate-new-buffer-name "*present*")))
          (frame (make-frame '((auto-raise . t)
                               (font . "Menlo 15")
                               (top . 200)
                               (height . 20)
                               (width . 110)
                               (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)
                               (undecorated . t)
                               (unsplittable . t)
                               (vertical-scroll-bars . nil)))))
     (set-face-attribute 'ivy-current-match frame
                         :background "#2a2a2a"
                         :foreground 'unspecified)
     (select-frame frame)
     (select-frame-set-input-focus frame)
     (with-current-buffer buffer
       (condition-case nil
           (unwind-protect
               ,@body
             (delete-frame frame)
             (kill-buffer buffer))
         (quit (delete-frame frame)
               (kill-buffer buffer))))))

Most of the snippet styles our new frame and invokes the body parameter. While I don't typically resort to macros, we get a little syntatic sugar here, so we can invoke like so:

(defun present-open-bookmark-frame ()
  (present (browse-url (seq-elt (split-string (completing-read "Open: " (browser-bookmarks "/private/tmp/bookmarks.org")) "\n") 1))))

Wrapping our one-liner with the present-open-bookmark-frame function enables us to easily invoke from the command line, with something like

emacsclient -ne "(present-open-bookmark-frame)"

command.gif

Now that we can easily invoke from the command line, we have the flexibility to summon from anywhere. We can even bind to a key shortcut, available anywhere (not just Emacs). I typically do this via Hammerspoon, with some helpers, though there are likely simpler options out there.

function emacsExecute(activate, elisp)
   if activate then
      activateFirstOf({
            {
               bundleID="org.gnu.Emacs",
               name="Emacs"
            }
      })
   end

   local socket, found = emacsSocketPath()
   if not found then
      hs.alert.show("Could not get emacs socket path")
      return "", false
   end

   local output,success = hs.execute("/opt/homebrew/bin/emacsclient -ne \""..elisp.."\" -s "..socket)
   if not success then
      hs.alert.show("Emacs did not execute: "..elisp)
      return "", false
   end

   return output, success
end

function openBookmark()
   appRequestingEmacs = hs.application.frontmostApplication()
   emacsExecute(false, "(present-open-bookmark-frame)")
   activateFirstOf({
         {
            bundleID="org.gnu.Emacs",
            name="Emacs"
         }
   })
end

hs.hotkey.bind({"alt"}, "W", openBookmark)

With that, we have our Emacs-powered bookmark launcher, available from anywhere.

launcher.gif

While we used our Emacs frame presenter to summon our universal bookmark launcher, we can likely the same mechanism for other purposes. Maybe a clipboard (kill ring) manager?

kill-ring.png

What would you use it for? Get in touch (Mastodon / Twitter / Reddit / Email).

Enjoying this content? Find it useful? Consider ✨sponsoring✨.

25 November 2023 Native Emacs/macOS UX integrations via Swift modules

Once you learn a little elisp, Emacs becomes this hyper malleable editor/platform. A live playground of sorts, where almost everything is up for grabs at runtime. Throw some elisp at it, and you can customize or extend almost anything to your heart's content. I say almost, as there's a comparatively small native core, that would typically require recompiling if you wanted to make further (native) mods. But that isn't entirely true. Emacs 25 enabled us to further extend things by loading native dynamic modules, back in 2016.

Most of my Emacs-bending adventures have been powered by elisp, primarily on macOS. I also happen to have an iOS dev background, so when Valeriy Savchenko announced his project bringing Emacs dynamic modules powered by Swift, I added it to my never-ending list of things to try out.

Fast-forward to a year later, and Roife's introduction to emt finally gave me that much-needed nudge to give emacs-swift-module a try. While I wish I had done it earlier, I also wish emacs-swift-module had gotten more visibility. Native extensions written in Swift can open up some some neat integrations using native macOS UX/APIs.

While I'm new to Savchenko's emacs-swift-module, the project has wonderful documentation. It quickly got me on my way to build an experimental dynamic module introducing a native context menu for sharing files from my beloved editor.

emacs-share.webp

Most of the elisp/native bridging magic happens with fairly little Swift code:

try env.defun(
  "macos-module--share",
  with: """
    Share files in ARG1.

    ARG1 must be a vector (not a list) of file paths.
    """
) { (env: Environment, files: [String]) in
  let urls = files.map { URL(fileURLWithPath: $0) }

  let picker = NSSharingServicePicker(items: urls)
  guard let view = NSApp.mainWindow?.contentView else {
    return
  }

  let x = try env.funcall("macos--emacs-point-x") as Int
  let y = try env.funcall("macos--emacs-point-y") as Int

  let rect = NSRect(
    x: x + 15, y: Int(view.bounds.height) - y + 15, width: 1, height: 1
  )
  picker.show(relativeTo: rect, of: view, preferredEdge: .maxY)
}

This produced an elisp macos-module--share function I could easily access from elisp like so:

(defun macos-share ()
  "Share file(s) with other macOS apps.

If visiting a buffer with associated file, share it.

While in `dired', any selected files, share those.  If region is
active, share files in region.  Otherwise share file at point."
  (interactive)
  (macos-module--share (vconcat (macos--files-dwim))))

On a side note, (macos--files-dwim) chooses files depending on context. That is, do what I mean (DWIM) style. If there's a file associated with current buffer, share it. When in dired (the directory editor, aka file manager), look at region, selected files, or default to file at point.

(defun macos--files-dwim ()
  "Return buffer file (if available) or marked/region files for a `dired' buffer."
  (if (buffer-file-name)
      (list (buffer-file-name))
    (or
     (macos--dired-paths-in-region)
     (dired-get-marked-files))))

(defun macos--dired-paths-in-region ()
  "If `dired' buffer, return region files.  nil otherwise."
  (when (and (equal major-mode 'dired-mode)
             (use-region-p))
    (let ((start (region-beginning))
          (end (region-end))
          (paths))
      (save-excursion
        (save-restriction
          (goto-char start)
          (while (< (point) end)
            ;; Skip non-file lines.
            (while (and (< (point) end) (dired-between-files))
              (forward-line 1))
            (when (dired-get-filename nil t)
              (setq paths (append paths (list (dired-get-filename nil t)))))
            (forward-line 1))))
      paths)))

I got one more example of a native macOS integration I added. Being an even simpler one, and in hindsight, I prolly should have introduced it first. In any case, this one reveals dired files in macOS's Finder app (including the selection itself).

reveal.webp

try env.defun(
  "macos-module--reveal-in-finder",
  with: """
    Reveal (and select) files in ARG1 in macOS Finder.

    ARG1 mus be a vector (not a list) of file paths.
    """
) { (env: Environment, files: [String]) in
  NSWorkspace.shared.activateFileViewerSelecting(files.map { URL(fileURLWithPath: $0) })
}

The corresponding elisp is nearly identical to its macos-share sibling:

(defun macos-reveal-in-finder ()
  "Reveal file(s) in macOS Finder.

If visiting a buffer with associated file, reveal it.

While in `dired', any selected files, reveal those.  If region is
active, reveal files in region.  Otherwise reveal file at point."
  (interactive)
  (macos-module--reveal-in-finder (vconcat (macos--files-dwim))))

My Swift module experiment introduces two native macOS UX integrations, now available via M-x macos-share and M-x macos-reveal-in-finder. I've pushed all code to it's own repo.

I hope this post brings visibility to the wonderful emacs-swift-module project and sparks new, native, and innovative integrations for those on macOS. Can't wait to see what others can do with it.

Enjoying this content? Find it useful? Consider ✨sponsoring✨.

16 November 2023 Migrating/re-encrypting pass's password store

Note to self, I needed to migrate/re-encrypt someone's password store (aka pass). Straightforward:

Get the new key, probably already in gpg key chain. Try listing it:

gpg --list-keys

To re-encrypt, pass init with new key is enough. It'll prompt for old pass key.

cd path/to/.password-store
pass init NEW-GPG-KEY

30 October 2023 How I smash burgers

I'm neither a burger expert nor a connoisseur of any kind, yet I sure have a lot of fun smashing burgers at home. Needless to say, I shamelessly enjoy gobbling them too!

burger-cut.png

Figure 1: my smash burger

I'll share details on how I smash my burgers, but here's a quick ingredient list, if that's all you need.

  • Mince beef (20%-30% fat).
  • Streaky bacon.
  • Brioche burger buns.
  • American cheese slices (cheddar individual slices work too).
  • Lettuce.
  • Tomatoes.
  • Onions.
  • Pickles.
  • Jalapeños.
  • Garlic.
  • Chipotle powder.
  • Mayonnaise.
  • Salt.
  • Pepper.
  • Oil.
  • Greaseproof paper.
  • Butter.

The calling

My quest to smash burgers at home didn't start until earlier this year, while watching the The Menu. I just could't stop craving the burger from that scene, so I set out to start smashing my own.

the-menu.webp

The gear

Don't rush to buy anything fancy. Your existing gear will likely do the job just fine. I'd say try a few things out and only upgrade when needed. I'll share the gear I use and where I felt I needed tweaking.

Skillet

While I didn't have a griddle at home, I did have a couple of trusty Lodge skillets (cast iron and carbon steel). Both work great for burgers, though I have a slight preference for the carbon steel one, as it's the bigger of the two and gives a little more room for manoeuvring, specially when smashing two burgers at a time.

cast-iron.png

carbon.png

Heat the skillet up and add a little oil. If the oil starts smoking, be quick to drop the patties and start smashing.

Grill Spatula (too big/stiff for skillet)

Somewhat inspired by the film, I got myself a wide spatula so I could firmly press those patties against the skillet, and to flip of course.

While this kind of spatula may work well on a spacious griddle, I felt constrained on a relatively small cast iron. Specially when flipping. I went looking for an alternative.

spatula.png

Spatula + smasher (my winning combo)

Over at the r/castiron subreddit, I discovered fish spatulas. They are fairly agile on cast irons but also work great for loosening burger patties before flipping.

ready.webp

While effective for flipping, fish spatulas are obviously no good for smashing. So I got myself a burger smasher. This combo worked well for me.

smash.webp

When smashing, use greaseproof paper to prevent the patties from sticking to the smasher.

Ingredients

While I've drawn inspiration from others, I've landed on my own preferred ingredients. I'm sure that will continue changing over time. Pick and choose as your heart desires.

Minced/ground beef

Minced beef with higher fat content (around 20-30%) is often recommended for a couple of reasons:

  • Flavour: Fat equals flavour in cooking. The higher fat content will melt during cooking and become 'self-basting', resulting in a juicier and more flavourful burger.
  • Texture: The fat in the beef melts under heat, helping the burger achieve a crispy, caramelized exterior known as the Maillard reaction, which contrasts nicely against the soft, juicy interior.

In the UK, I can typically find minced beef with 15%-20% fat content at the main supermarkets.

pattie.png

Be sure to salt and pepper to taste (as in picture) on one side. Once flipped on pan, salt and pepper the other side.

Bacon

I tend to prefer smoked streaky bacon, but hey these will be your burgers. Your burgers, your rules.

streaky.png

Buns (brioche)

I hear potato buns are great for burgers. I've yet to try them. So far, I've settled for brioche. I happen to find these near me, so I've gone with them.

brioche.png

Butter the buns and brown on the skillet for a minute. Check the buns often. Brioche buns can burn quickly.

American cheese

American cheese is often the burger cheese of choice.

american-cheese.webp

While American cheese isn't widely available in the UK, the individually wrapped orange-looking cheddar cheese slices work just fine.

cheese.png

Toppings

I like my burgers with lettuce, tomatoes, onions, pickles, and occasionally jalapeños. For pickles, I typically just take cornichons and slice them up.

toppings_x0.30.png

Burger sauce (chipotle/garlic/mayo)

While classic burger sauce is often made with mayo, ketchup, pickles, and mustard, I've gone fairly rogue here.

You see, I love chipotle mayo. I'm also a fan of garlic mayo, so I figured why not both? Turns out these three ingredients work great together.

I like to draw out the flavours by first mixing the garlic and chipotle with a little hot water.

  • 1 garlic clove.
  • 2 teaspoons of chipotle powder.
  • 1 tablespoon of hot water.
  • Pinch of salt.

sauce1.png

sauce2.png

sauce3.png

…and then thicken with mayo.

  • 1/4 cup of mayo.

sauce4.png

sauce5.png

These are very rough measurements, tweak to your preference. Make more garlicky, spicier, or soften things by adding garlic, chipotle, or mayo.

Assembling

I like to assemble in the following order from the bottom bun up.

  1. Sauce on bottom bun.
  2. Lettuce.
  3. Tomatoes.
  4. Onions.
  5. 2 patties (melted cheese on both).
  6. Bacon.
  7. Pickles.
  8. Jalapeños.
  9. Sauce on top bun (oops, I forgot in the picture).

open.png

…and here's the final product.

burger-cut.png

If you gave smashing burgers a go, I'd love to hear about it. Also any tips are very much welcome. Get in touch (Mastodon / Twitter / Reddit / Email).

24 October 2023 Open in Xcode at line number

I live mostly in Emacs. I say mostly 'cause well, I'm fairly pragmatic about it. If there's a workflow elsewhere that's more appropriate for my needs, I'll happily use that instead. While I'd love to do my web browsing from my beloved editor, Firefox ticks the right boxes for me.

I do most of my iOS coding in Emacs. It's a hybrid of sorts between Emacs and Xcode. If I need to use the debugger, Xcode is a clear winner for me. If I happen to be visiting a Swift file in an Emacs buffer, I typically used the handy crux-open-with from crux to open in Xcode, and continue from there. This worked OK, but I always wished opening in Xcode would also jump to the same line number as the Emacs point (cursor) location. This is particularly useful if I had just spotted where I'd like to set a breakpoint in an Emacs buffer and need to transition over to Xcode.

It turns out, there's a nifty command line utility for that. xed, the Xcode text editor invocation tool. It enables telling Xcode what file to open and at what line number:

xed -line 141 path/to/some/file.swift

With that in mind, I've added my own version of crux-open-with, using dwim-shell-command.

When running on macOS, the function checks whether or not I'm visiting a buffer for a programming language, and opens the file in Xcode at the same line number.

(defun dwim-shell-commands-open-externally ()
  "Open file(s) externally."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Open externally"
   (if (eq system-type 'darwin)
       (if (derived-mode-p 'prog-mode)
           (format "xed --line %d '<<f>>'"
                   (line-number-at-pos (point)))
         "open '<<f>>'")
     "xdg-open '<<f>>'")
   :shell-args '("-x" "-c")
   :silent-success t
   :utils (if (eq system-type 'darwin)
              "open"
            "xdg-open")))

xed_x0.8_x2.webp

dwim-shell-commands-open-externally is now added to dwim-shell-commands.el.

ps. If you find opening the same file in a different context handy, you may also like the package browse-at-remote that opens the visited file at its corresponding remote location (for example, GitHub). I can never remember the name of the function (browse-at-remote), so I aliased it to something I'd remember and moved on…

(defalias 'ar/open-at-github #'browse-at-remote))

06 October 2023 Trimming video screenshots

A quick one… I recently wanted to trim the black borders around a video screenshot. While I could use an image editor to manually select and trim, I wondered if there was an imagemagick trick somewhere out there for such a thing… and of course there was:

magick convert -fuzz 3% -define trim:percent-background=0% -trim +repage path/to/input.png path/to/output.png

Pretty neat. It does the job, but I won't remember it next time. May as well make another dwim-shell-command function out of it and conveniently invoke from Emacs via a memorable name plus fuzzy search.

trim.gif

(defun dwim-shell-commands-image-trim-borders ()
  "Trim image(s) border (useful for video screenshots)."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Trim image border"
   "magick convert -fuzz 3% -define trim:percent-background=0% -trim +repage '<<f>>' '<<fne>>_trimmed.<<e>>'"
   :utils "magick"))

While the screenshot I've just used was a little blurry, it's from the movie Tron Legacy, and it features Emacs eshell. This is old news, though well covered.

eshell_trimmed.png

dwim-shell-commands-image-trim-borders is now added to dwim-shell-commands.el

06 October 2023 Displaying image details in mode line

A benefit of running Emacs as a GUI app, is that you can view images from your beloved editor. This is super handy to take a quick peek at any image.

Sometimes, I'd like a little more than just viewing the image. I'd like to see basic image details like type, dimensions, and file size. The imagemagick identify utility is pretty handy for that.

identify -format "%m %wx%h %b" path/to/image.png
PNG 2387x1055 2.28454MB

I could easily invoke shell-command for this or even create a dwim-shell-command function (maybe I will), but if this info was proactively displayed in the mode line, I wouldn't have to fetch it myself.

Since I know I can use the identify command for this, I may as well see if I can plug it into the mode line.

Turns out this wasn't too bad by setting setting mode-line-format. I added a little logic to only include image details while in image-mode and rely on process-lines to fetch the details. This function returns a list, which is a happy coincidence since mode-line-format also expects a list.

(setq-default mode-line-format
              '(" "
                mode-line-front-space
                mode-line-client
                mode-line-frame-identification
                mode-line-buffer-identification
                (:eval
                 (when (eq major-mode 'image-mode)
                   ;; Needs imagemagick installed.
                   (process-lines "identify" "-format" "[%m %wx%h %b]" (buffer-file-name))))
                " "
                mode-line-position
                (vc-mode vc-mode)
                (multiple-cursors-mode mc/mode-line)
                " " mode-line-modes
                mode-line-end-spaces))

buddies.png

I'd love to hear if there's a pure elisp alternative (mastodon/twitter). I gave (image-size (image-get-display-property) :pixels) a try, but that seemed to return the display size in buffer rather than actual file size.

06 October 2023 Creating an iCloud account (via tart VM)

UPDATE: This method no longer works for creating iCloud accounts. I'd love to know if you find an alternative. Please let me know.

I wanted an additional @icloud.com account for myself. My first thought was to head over to https://developer.apple.com and create a new account, but that requires an existing email address. I wanted an actual @icloud.com email address.

web.png

My next thought was to create a new account using the iOS simulator, but that complained about creating too many accounts already. Strange, as I hadn't created any.

iphone.png

I could create an account from macOS settings itself, though that would require logging out my current account (and the syncing implications). To get around that, I could maybe create a temporary macOS user. Instead, I somewhat revisited the simulator route and looked for a VM option to run macOS. This gave me an excuse to play with VM options on macOS.

I had been meaning to check out lima as per Hacker News's Lima: A nice way to run Linux VMs on Mac. The Hacker News's thread has a handful of great recommendations. Amongst them, tart (macOS and Linux VMs on Apple Silicon) stood out, as it also gave me the Mac on Mac option.

Installing tart via Homebrew followed the typical brew command… a breeze via my trusty Emacs eshell:

brew install cirruslabs/cli/tart

Cloning a VM image, while straightforward, it did take a little while for the chunky download:

tart clone ghcr.io/cirruslabs/macos-sonoma-base:latest sonoma-base

Running the macOS Sonoma VM was a breeze:

tart run sonoma-base

…and with that, I got a full (and disposable) macOS VM I can use to create another @icloud.com account:

sonoma1.png

sonoma2.png

While there may be simpler options out there to create an @icloud.com account (please do let me know mastodon/twitter), the VM did the job. I'd been meaning to find a low friction mechanism to run VMs for a different reason, but that's a post for another time.

04 October 2023 Virtual machine (VM) bookmarks

16 September 2023 Emacs hangs saving .authinfo.gpg (workaround)

My Emacs (v29.1) was hanging when saving changes to .authinfo.gpg. Turns out, I ran into a known issue with a workaround. Downgrading gnupgp to a version older than 2.4.1 sorts things out.

I'm on macOS. Downgraded by downloading the 2.4.0 Homebrew formula at https://raw.githubusercontent.com/Homebrew/homebrew-core/59edfe598541186430d49cc34f42671e849e2fc9/Formula/gnupg.rb and installing with:

brew unlink gnupg
brew install ~/Downloads/gnupg.rb

15 September 2023 Redact that buffer

As I was getting ready to take an Emacs screenshot in the previous post, I figured I may want to redact email addresses before moving forward. I had a quick look for existing options and found redacted.el, built-in toggle-rot13-mode, and unpackaged/lorem-ipsum-overlay. All great options. I wanted a solution I could feed a single regular expression to obscure matches. I also wanted toggling capabilities, so I had a quick go at it…

redact-regexp.gif

I also wanted the ability to redact the entire buffer content, so feeding a space to the regexp query also translates to [[:graph:]], effectively redacting all visible characters.

redact-all.gif

The solution is overlay-based, ensuring the buffer content remains unchanged. The function may have its own rough edges, yet it certainly scratched the itch for the current need. I'll leave ya with the snippet.

(defun ar/toggle-redact-buffer ()
  "Redact buffer content matching regexp. A space redacts all."
  (interactive)
  (let* ((redacted)
         (regexp (string-trim (read-regexp "Redact regexp" 'regexp-history-last)))
         (matches (let ((results '()))
                    (when (string-empty-p regexp)
                      (setq regexp "[[:graph:]]")
                      (setq regexp-history-last regexp)
                      (add-to-history 'regexp-history regexp))
                    (save-excursion
                      (goto-char (point-min))
                      (while (re-search-forward regexp nil t)
                        (push (cons (match-beginning 0) (match-end 0)) results)))
                    (nreverse results))))
    (mapc (lambda (match)
            (dolist (overlay (overlays-in (car match) (cdr match)))
              (setq redacted t)
              (delete-overlay overlay))
            (unless redacted
              (overlay-put (make-overlay (car match) (cdr match))
                           'display (make-string (- (cdr match) (car match)) ?x))))
          matches)))

15 September 2023 Send note to Kindle

While on Mastodon, I spotted @summeremacs looking into sending Emacs text selections to a Kindle via email. This sparked my interest as I previously looked into sending pdfs to my Kindle via mu4e.

Kindle offers a neat service where you can email a file to your @kindle.com address and it automatically shows up in your Kindle library.

I already do email from my beloved editor, and like most Emacs things, it's powered by elisp. In other words, it's basically up for grabs if you'd like to glue it to anything else, so I did…

I can now select a region and invoke M-x send-to-kindle-as-txt to send it over to my Kindle.

send-to-my-kindle.gif

Soon enough, the note shows up on my Kindle.

listed.png

Opening the note reveals the same content we had previously selected and sent from our malleable editor.

repeated.png

While it looks kinda magical, it's fairly simple under the hood. It takes the region content, writes it to a txt file, creates an email message buffer attaching the file, and finally sends via message-send-and-exit.

If M-x send-to-kindle-as-txt is invoked with a C-u prefix, you get to inspect the message buffer right before sending via C-c C-c.

email.png

Here's the full snippet.

(defcustom send-to-kindle-from-email
  nil
  "Your own email address to send from via mu4e."
  :type 'string
  :group 'send-to-kindle)

(defcustom send-to-kindle-to-email
  nil
  "Your Kindle email address to send pdf to."
  :type 'string
  :group 'send-to-kindle)

(defun send-to-kindle-as-txt (review)
  (interactive "P")
  (unless send-to-kindle-from-email
    (setq send-to-kindle-from-email
          (read-string "From email address: ")))
  (unless send-to-kindle-to-email
    (setq send-to-kindle-to-email
          (read-string "To email address: ")))
  (let* ((content (string-trim (if (region-active-p)
                                   (buffer-substring (region-beginning) (region-end))
                                 (buffer-string))))
         (note-name (let ((name (string-trim (read-string "Note name: "))))
                      (if (string-empty-p name)
                          (nth
                           0 (string-split
                              (substring content 0 (min 40 (length content))) "\n"))
                        name)))
         (path (concat (temporary-file-directory) note-name))
         (txt (concat path ".txt"))
         (buffer (get-buffer-create (generate-new-buffer-name "*Email txt*"))))
    (with-temp-buffer
      (insert content)
      (write-file txt))
    (with-current-buffer buffer
      (erase-buffer)
      ;; Disable hooks
      (let ((message-mode-hook nil))
        (message-mode))
      (insert
       (format
        "From: %s
To: %s
Subject: %s
--text follows this line--
<#multipart type=mixed>
<#part type=\"text/plain\" filename=\"%s\" disposition=attachment>
<#/part>
<#/multipart>"
        send-to-kindle-from-email
        send-to-kindle-to-email
        note-name txt))
      (unless review
        (message-send-and-exit)))
    (when review
      (switch-to-buffer buffer))))

By the way, and I only just learned this today… To take a screenshot on a Kindle Paperwhite, tap on these opposite corners.

tap-screenshot.png

10 September 2023 SHA-256 hash from URL, the easy way

From time to time, I need to generate a SHA-256 hash from a file hosted on some server. For me, this flow typically goes something along the lines of:

  • Copy the file URL from browser.
  • Drop to Emacs eshell.
  • Change current directory.
  • Type "curl -o file"
  • Paste the file URL.
  • Run curl command.
  • Type "shasum -a 256 file".
  • Run shasum command.
  • Copy the generated hash.
  • Maybe delete the downloaded file?

We can maybe shave some steps off by downloading directly from the browser, though that may also bring additional clicks and navigating to a download location.

Amongst the steps, shasum is the star player, and its output can be seen below.

shasum -a 256 path/to/downloaded/file
3da351027e5b1495c7c7fe4abbf8d7ac9625da3604be5a35c9a9cbb92f6f955a  path/to/downloaded/file

Not a huge deal. One can copy the hash from the output, but why go through multiple small manual steps when I know I can get Emacs to simplify the lot? I've expedited a similar flow in the past when cloning git repos. Let's simplify again so hashing a hosted file boils down to:

  • Copy the file URL from browser.
  • Run an Emacs interactive command.

This is where I pull out dwim-shell-command (a little package I wrote) and glue the lot to get an expedited experience.

hash.gif

There isn't much to the function other than glueing a little elisp and a shell script via dwim-shell-command for some buffer/error handling.

(defun dwim-shell-commands-sha-256-hash-file-at-clipboard-url ()
  "Download file at clipboard URL and generate SHA-256 hash."
  (interactive)
  (let ((url (current-kill 0)))
    (unless (string-match-p "^http[s]?://" url)
      (user-error "No URL in clipboard"))
    (dwim-shell-command-on-marked-files
     "Generate SHA-256 hash from clipboard URL."
     (format
      "temp_file=$(mktemp)
       function cleanup {
         rm -f $temp_file
       }
       trap cleanup EXIT
       curl --no-progress-meter --location --fail --output $temp_file %s || exit 1
       shasum -a 256 $temp_file | awk '{print $1}'"
      (shell-quote-argument url))
     :utils '("curl" "shasum")
     :on-completion
     (lambda (buffer process)
       (if-let ((success (= (process-exit-status process) 0))
                (hash (with-current-buffer buffer
                        (string-trim (buffer-string)))))
           (progn
             (kill-buffer buffer)
             (kill-new hash)
             (message "Copied %s to clipboard"
                      (propertize hash 'face 'font-lock-string-face)))
         (switch-to-buffer buffer))))))

dwim-shell-commands-sha-256-hash-file-at-clipboard-url is now in dwim-shell-commands.el, the optional counterpart in dwim-shell-command.

UPDATE

There's better way. Thanks to Philip Kaludercic for suggesting curl -s example.com | sha256sum - | cut -d " " -f1 and Sacha Chua who pinged me about it.

Also note I'm now relying on the <<cb>> template, since dwim-shell-command replaces it with the clipboard/kill ring.

(defun dwim-shell-commands-sha-256-hash-file-at-clipboard-url ()
  "Download file at clipboard URL and generate SHA-256 hash."
  (interactive)
  (unless (string-match-p "^http[s]?://" (current-kill 0))
    (user-error "No URL in clipboard"))
  (dwim-shell-command-on-marked-files
   "Generate SHA-256 hash from clipboard URL."
   "curl -s '<<cb>>' | sha256sum - | cut -d ' ' -f1"
   :utils '("curl" "sha256sum")
   :on-completion
   (lambda (buffer process)
     (if-let ((success (= (process-exit-status process) 0))
              (hash (with-current-buffer buffer
                      (string-trim (buffer-string)))))
         (progn
           (kill-buffer buffer)
           (kill-new hash)
           (message "Copied %s to clipboard"
                    (propertize hash 'face 'font-lock-string-face)))
       (switch-to-buffer buffer)))))

05 September 2023 Inline previous result and why you should edebug

Artur Malabarba's Debugging Elisp Part 1: Earn your independence is nearly a decade old, yet it rings just as true today.

Learning to Edebug really "is the right decision for anyone who doesn't know how to Edebug." Why, you may ask? He best puts it as "running into errors is not only a consequence of tinkering with your editor, it is the only road to graduating in Emacs."

For me personally, it earned me that independence to bend Emacs my way. Don't like how something works? Pull up the debugger to help me understand how a package or function works. I've done this countless of times to bend things my way.

Speaking of edebug, I had been meaning to tweak edebug's result display behaviour for quite some time. As you step through code, edbug prints the result of previous expressions to the minibuffer. This works well, but I couldn't help but feel like my eyes were constantly jumping between the code and the minibuffer at the bottom of the window.

edebug-minibuffer.gif

I wanted to minimize the eye jumping experience, so I figured I could likely bend things my way and print the result at point. How did I go about it? The same way I often do. Figure out what function is called for a given key binding via describe-key or my favourite replacement helpful-key from helpful.el. This led me to edebug-next-mode in edebug.el. At that point, I could have set a breakpoint in edebug-next-mode and eventually step into the relevant code, but hey we had a better clue. We knew that all output started with "Result:", so we could just search for that string in edebug.el instead. Jackpot! edebug-compute-previous-result and its adjacent edebug-previous-result are just the right functions:

(defun edebug-compute-previous-result (previous-value)
  (if edebug-unwrap-results
      (setq previous-value
            (edebug-unwrap* previous-value)))
  (setq edebug-previous-result
        (concat "Result: "
                (edebug-safe-prin1-to-string previous-value)
                (eval-expression-print-format previous-value))))

(defun edebug-previous-result ()
  "Print the previous result."
  (interactive)
  (message "%s" edebug-previous-result))

We can see that edebug-previous-result invokes message which is responsible for displaying the debugged expression's result in the minibuffer. Modifying this functions behaviour would be enough to achieve inline display, but I also want to remove "Result:" from the displayed message. Neither of these functions offer configurability, so we'll resort to advising both functions. That is, monkey patch them (errm I know… lovely).

(defun adviced:edebug-compute-previous-result (_ &rest r)
  "Adviced `edebug-compute-previous-result'."
  (let ((previous-value (nth 0 r)))
    (if edebug-unwrap-results
        (setq previous-value
              (edebug-unwrap* previous-value)))
    (setq edebug-previous-result
          (edebug-safe-prin1-to-string previous-value))))

(advice-add #'edebug-compute-previous-result
            :around
            #'adviced:edebug-compute-previous-result)

adviced:edebug-compute-previous-result removes "Result:" in addition to dropping (eval-expression-print-format previous-value), which I don't typically rely on.

(require 'eros)

(defun adviced:edebug-previous-result (_ &rest r)
  "Adviced `edebug-previous-result'."
  (eros--make-result-overlay edebug-previous-result
    :where (point)
    :duration eros-eval-result-duration))

(advice-add #'edebug-previous-result
            :around
            #'adviced:edebug-previous-result)

adviced:edebug-previous-result is in charge of display via message, so all we need is some replacement. I initially played with popup-tip and that did the job just fine, but Colin led me to a better path while pointing to Clojure and Common Lisp. This reminded me of eros: Evaluation Result OverlayS for Emacs Lisp, which I already used. Swapping message for eros--make-result-overlay did the trick. Yes, this is a private function, but I can live with that. This code is only an advice-remove away from disabling, but hey look at those inline results!

edebug-inline.gif

27 August 2023 Further sqlite-mode extensions

I've continued poking at Emacs 29's sqlite-mode. Since my last post on extensions, I've experimented a little with adding a handful of interactive functions:

  • sqlite-mode-extras-compose-and-execute: Compose and execute a query.

compose-execute.gif

  • sqlite-mode-extras-execute: Execute a query.

execute.gif

  • sqlite-mode-extras-add-row: Add row to table at point.

add-row.gif

  • sqlite-mode-extras-delete-row-dwim: Similar to sqlite-mode-delete but also enables deleting range in region.

delete-rows.gif

  • sqlite-mode-extras-refresh: Refreshes the buffer re-querying the database.
  • sqlite-mode-extras-ret-dwim: If on table, toggle expansion. If on row, edit it.
  • sqlite-mode-extras-execute-and-display-select-query: Executes a query and displays results.

select-earth.gif

I've been playing with the following key bindings:

(use-package sqlite-mode-extras
  :bind (:map
         sqlite-mode-map
         ("n" . next-line)
         ("p" . previous-line)
         ("b" . sqlite-mode-extras-backtab-dwim)
         ("f" . sqlite-mode-extras-tab-dwim)
         ("+" . sqlite-mode-extras-add-row)
         ("D" . sqlite-mode-extras-delete-row-dwim)
         ("C" . sqlite-mode-extras-compose-and-execute)
         ("E" . sqlite-mode-extras-execute)
         ("S" . sqlite-mode-extras-execute-and-display-select-query)
         ("DEL" . sqlite-mode-extras-delete-row-dwim)
         ("g" . sqlite-mode-extras-refresh)
         ("<backtab>" . sqlite-mode-extras-backtab-dwim)
         ("<tab>" . sqlite-mode-extras-tab-dwim)
         ("RET" . sqlite-mode-extras-ret-dwim)))

The code lives in sqlite-mode-extras.el under my Emacs config repo. Beware, it's fairly experimental and hasn't been tested thoroughly.

22 August 2023 My custom Tesco Clubcard pkpass

My significant other and I had two plastic Tesco Clubcards. I lost mine, so I took a picture of hers. I was fairly certain a barcode photo would scan just as well at self-checkout, and it did.

This got me thinking about Apple's Wallet pkpasses. I don't really know much about them. Could I potentially create my own .pkpass? If I could just include the same barcode as in the photo, it should do the job just fine.

Now I should mention, Tesco does have an app on the App Store. If you just want the official Wallet pass on your iPhone, use that. But I was curious about whether or not I could create my own pass.

Turns out I can. I followed Apple's building your first pass which runs you through creating Wallet identifiers/certificates, editing pass.json, and downloading/building signpass (the utility used to sign .pass bundles).

The signpass utility is included in WalletCompanionFiles.zip, which comes with a handful of sample passes.

WalletCompanionFiles
│
├── SamplePasses
│   │
│   ├── BoardingPass.pass
│   ├── Coupon.pass
│   ├── Event.pass
│   ├── Event.pkpass
│   ├── Generic.pass
│   └── StoreCard.pass
│       │
│       ├── pass.json
│       └── ...
└── signpass

Being a rewards card, I opted to look into StoreCard.pass, but like all other passes, the barcode itself is what makes each pass scannable. The barcode details are specified in the bundles's pass.json file. I needed to figure out the relevant values describing the Tesco barcode.

"barcode": {
  "format": "???",
  "message": "???",
  "messageEncoding": "???"
}

I had no clue what values I should use for a Tesco Clubcard. I did, however, have a photo of the barcode I needed. This is in fact what prompted looking into scanning barcodes from Emacs, which worked just great. It gave me all the crucial bits for the Clubcard.

"barcode": {
  "format": "PKBarcodeFormatCode128",
  "message": "1234567890123456",  // not my actual Clubcard number of course.
  "messageEncoding": "iso-8859-1"
}

That's all that's needed for the barcode section, the most useful part of the pass. We're not done though. We also need our registered Wallet identifiers, so the signpass utility can sign.

"passTypeIdentifier": "my.com.identifier.passmaker", // also not my actual one.
"teamIdentifier": "AAABBBCCCD", // nor this one.

We should be able to sign the pass with the following:

signpass -p StoreCard.pass

We're technically done. We now have a working card, but it looks just like the sample store card included in WalletCompanionFiles.

lemons.png

What's the fun in that? Now that I can make my own Clubcard, let's customize it!

For imagery, I replaced a couple of images in the .pass bundle:

StoreCard.pass
│
├── pass.json
├── icon.png
├── logo.png // replaced
└── strip.png // replaced

I replaced logo.png using a Tesco logo I found on Wikipedia. I had initially removed strip.png, but that made the card feel a little empty. I was thinking of using a Tesco carrier bag to bulk the space up. While I didn't find a suitable bag image, I did land on "Very Little Helps, 2008". Using my limited GIMP skills, I cropped one of the images and also replaced strip.png.

The remaining customizations took place in pass.json and should be fairly self-explanatory. There's the text shown in all labels as well as three customizable colours (background, label, and foreground).

{
  "formatVersion": 1,
  "passTypeIdentifier": "my.com.identifier.passmaker", // also not my actual one.
  "teamIdentifier": "AAABBBCCCD", // nor this one.
  "serialNumber": "AnySerialNumberYouWant",
  "barcode": {
    "format": "PKBarcodeFormatCode128",
    "message": "1234567890123456",
    "messageEncoding": "iso-8859-1"
  },
  "organizationName": "Not Tesco of course",
  "description": "Not a Tesco reqards card",
  "logoText": "Clubcard",
  "foregroundColor": "rgb(255, 255, 255)",
  "labelColor": "rgb(255, 255, 255)",
  "backgroundColor": "rgb(2, 81, 158)", // Blue for that Tesco look
  "storeCard": {
    "auxiliaryFields": [
      {
        "key": "membership",
        "label": "Member since 2023",
        "value": ""
      },
      {
        "key": "membership2",
        "label": "Expires sometime",
        "value": ""
      }
    ]
  }
}

…and with all that, here's what my very own custom Tesco Clubcard pkpass looks like. As you can appreciate, my image-editing skills aren't all that great, but hey this will do for now.

bsy.png

Update

Redditor u/stupergenius suggested using the image's original background color. Nice suggestion. Tweaked via pass.json:

"foregroundColor": "rgb(2, 81, 158)",
"labelColor": "rgb(15, 58, 105)",
"backgroundColor": "rgb(166, 202, 214)",

bsy-light.png

07 August 2023 Extending sqlite-mode (cell navigation + edits)

I recently wrote about Emacs 29's new sqlite-mode, which enables you to browse sqlite databases from your beloved editor.

Out of the box, it supports the following browsing features:

  • sqlite-mode-list-data: List the data from the table under point.
  • sqlite-mode-list-column: List the columns of the table under point.
  • sqlite-mode-list-tables: Re-list the tables from the currently selected database.

On the editing side of things it supports row deletion:

  • sqlite-mode-delete: Delete the row under point.

While fairly spartan, it lays foundations for additional tools and features.

Two features I would like to have:

  1. TAB navigation across table rows and columns.
  2. Updating the row's field at point.

This would give me the familiar behaviour I'm used to in my org tables as well as other common spreadsheet tools.

Luckily, this is Emacs, so we can bend it our way… and I sure did!

Here's tab navigating forward:

sqlite-forward.gif

Here's tab navigating backward:

sqlite-previous.gif

And updating row fields:

sqlite-edits.gif

Most of the navigation is achieved by querying the current buffer to figure out column positions. Editing was in some ways easier, as I looked at sqlite-mode-delete to figure out how it handled the query.

To get the more familiar navigation behaviour, I've adjusted my key bindings as follows:

(use-package sqlite-mode-extras
  :bind (:map
         sqlite-mode-map
         ("n" . next-line)
         ("p" . previous-line)
         ("<backtab>" . sqlite-mode-extras-backtab-dwim)
         ("<tab>" . sqlite-mode-extras-tab-dwim)
         ("RET" . sqlite-mode-extras-ret-dwim)))

The code for sqlite-mode-extras-tab-dwim, sqlite-mode-extras-backtab-dwim, and sqlite-mode-extras-ret-dwim is little rough still (hacky even), but hey still fun.

For now, the code lives in sqlite-mode-extras.el under my Emacs config repo. Improvements/fixes totally welcome!

06 August 2023 Emacs 29's sqlite-mode

I've jumped on the Emacs 29 bandwagon! Mickey Petersen has a great rundown of What's New in Emacs 29.1.

Now every so often, I need to take a quick peek at an sqlite3 table. Emacs 29.1 ships sqlite-mode, which can help with that. Use sqlite-mode-open-file to open a database.

Pressing RET on a table shows its content via sqlite-mode-list-data. DEL does as you'd expect and delete a row via sqlite-mode-delete.

sqlite-mode.gif

30 July 2023 Emacs: scan this QR/bar code

Another day, another tool brought to my Emacs fingertips. A while ago, I wrote about easily copying text from desktop to mobile via QR codes. Later on, I brought it under dwim-shell-command as dwim-shell-commands-clipboard-to-qr.

This time around, I needed the opposite: to scan a code from an image file. This is where zbar's zbarimg comes in. These days, I'm mostly on macOS, so I installed via Homebrew:

$ brew install zbar

There's really nothing to the command. You feed it an image, and it outputs the scanned details. Perfect.

$ zbarimg path/to/code-128.png
CODE-128:hello world
scanned 1 barcode symbols from 1 images in 0.02 seconds

The only challenge is my brain. I probably won't remember the name of this wonderful tool next time I need it, so I'll just add it to my dwim-shell-commands.el arsenal with a memorable name:

(defun dwim-shell-commands-image-scan-code ()
  "Scan any code from image(s)."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Scan code"
   "zbarimg '<<f>>'"
   :utils "zbarimg"))

In the future, rather than reaching out to zbarimg directly, I'll use my trusty fuzzy search and… voilà!

scan-dired.gif

Because dwim-shell-command operates on either dired files or current file, we can also apply our new function when viewing the QR code itself.

scan-image.gif

dwim-shell-commands-image-scan-code is now pushed to dwim-shell-commands.el, the optional package in dwim-shell-command.

25 July 2023 A cure for JavaScript fatigue?

It's been roughly a decade since I wrote any significant amount of JavaScript. Back then, I primarily relied on the Google Closure Compiler, now maybe an archaeological artefact? These days, it's hard not to bump into any JavaScript project that doesn't rely on npm, along with many other tools like the Typescript compiler, ESLint, Prettier… There are a ton of available frameworks too. I was somewhat put off (or maybe just lazy?) by the initial ramp-up to reenter the JavaScript world. I guess that's what some refer to as Javascript Fatigue.

I'm giving JavaScript another try, but this time with an Emacs chatgpt-shell standing by. Reentering the JavaScript world as a noob, I often know what I want to enable, but I'm unfamiliar with which project knobs to turn to set things up.

While I may want to dig deeper into things in the future, at present I just want to dabble with JavaScript. I want a local project set up as quickly as possible. ChatGPT has been pretty handy at that. The Emacs ChatGPT shell and its minibuffer prompts work fairly well for my needs, yet I often found myself wishing it could behave more like a magit commit buffer. That is, launch a dedicated buffer (not the shell itself), ask the question, maybe paste some snippets, and send it on its way with that oh so familiar and satisfying C-c C-c binding (sending mail also says hello).

This is where M-x chatgpt-shell-prompt-compose comes in. It's a mash between the ChatGPT shell and a magit commit buffer:

node-chatgpt.gif

In the background, the buffer is still powered by the shell itself, so you can reuse it to ask clarifying questions.

compose.gif

A couple of additional features worth mentioning… Invoking chatgpt-shell-prompt-compose with an active region automatically copies the region content over to the compose buffer. This is handy if you'd like to create more elaborate prompts with further editing. So far, this feels more natural than editing text from the shell or the minibuffer, where RET doesn't insert new lines.

The compose buffer is powered by a background shell (storing history for us). Typing clear followed by C-c C-c clears the background shell history.

chatgpt-shell-prompt-compose is available in chatgpt-shell v0.72.1. I've so far bound it to C-c C-e, though I've already found some unfortunate clashes.

12 July 2023 ChatGPT visits the Emacs doctor

Emacs is a part-time job. A multi-language development environment. A lisp machine. An email client. A web browser. A zettelkasten. A spreadsheet. A mastodon client. A shell. A ledger. A super agenda. An operating system. Some say it sends ripples into the atmosphere or plays tetris for you. It may even warm your place up during the winter. Can meme with you. It's an ultra-malleable editor with endless possibilities, powered by your life-long customizations. Oh man, no wonder we need to chat to someone from time to time. You know what I mean? "Sir, this is a Wendy's".

Luckily, we also have the built-in Emacs psychotherapist we can chat to, courtesy of M-x doctor. It's powered by elisp, and like all Emacs things, it's basically up for grabs. What I mean is, elisp implements many of these features, but also glues the lot for you. Once you learn a little elisp, you can build new Emacs features but also glue others for that magical compound effect.

got-a-problem.gif

Figure 2: The Emacs doctor

A little while ago, I wanted to give ChatGPT a try, preferably from Emacs (of course). I figured a shell interface would be a great fit for the interaction. Emacs already shipped with a general command interpreter (comint), so I cobbled together a ChatGPT Emacs shell.

cyberpunk.gif

Figure 3: chatgpt-shell

So where am I going with all this? The fine netizens r/emaphis and salgernon both planted a great seed:

I haven't forgotten about you. Let's take chatgpt-shell, M-x doctor, our versatile elisp glue, and let's make them talk:

000026814.jpg

Figure 4: courtesy of thriveth and dr.dk.

There isn't too much to the code, but beware:

  1. If you want to run it, you'll need chatgpt-shell installed and set up.
  2. This was a quick fun hack. No code judging ;)

The snippet is further down… Start with chatgpt-shell-visit-doctor as the entry point, setting things up for us. It creates both the *chatgpt* and *doctor* buffers and arranges the windows next to each other.

We also set a ChatGPT system prompt to guide things a little:

"Pretend to be an overwhelmed Emacs user who is obsessed with configuring their init.el file. You are in a session talking to a psychotherapist. Limit your output to no more than 20 words. In the course of 5 exchanges between you and the therapist, show improvements. On the 8th exchange after therapist speaks, declare you are cured and only output 'Thank you doc, I think I'm cured!'"

ChatGPT and Emacs doctor can go on and on, so we limit ChatGPT responses to 20 words per response and 8 exchanges. We don't want the session to abruptly end without a resolution, so we'll use Thank you doc, I think I'm cured! as our key phrase to end the session.

We register chatgpt-shell--on-chatgpt-patient-response as a hook to receive ChatGPT output, which we feed to the *doctor* buffer. We subsequently get a doctor response that's fed back to ChatGPT via chatgpt-shell--insert-doc-response.

We add some additional freebies like binding Ctrl-c Ctrl-c to chatgpt-shell-leave-doctor, so we can bail out of the exchange from the *chatgpt* buffer.

We also introduced chatgpt-shell--insert-delayed-text as a replacement for insert to slow things down a little. For visual effects, really.

(require 'chatgpt-shell)

(defun chatgpt-shell-visit-doctor ()
  (interactive)
  (setq chatgpt-shell--doctor-in-session t)
  (when (get-buffer "*doctor*")
    (kill-buffer "*doctor*"))
  (delete-other-windows)
  (split-window-horizontally)
  (other-window 1)
  (doctor)
  (visual-line-mode 1)
  (when (fboundp 'accent-menu-mode)
    (accent-menu-mode -1))
  (mapc
   (lambda (shell-buffer)
     (kill-buffer shell-buffer))
   (chatgpt-shell--shell-buffers))
  (other-window 1)
  (setq chatgpt-shell-system-prompts
        '(("Doc" . "Pretend to be an overwhelmed Emacs user who is obsessed with configuring their init.el file. You are in a session talking to a psychotherapist. Limit your output to no more than 20 words. In the course of 5 exchanges between you and the therapist, show improvements. On the 8th exchange after therapist speaks, declare you are cured and only output \"Thank you doc, I think I'm cured!\".")))
  (setq chatgpt-shell-system-prompts nil)
  (setq chatgpt-shell-system-prompt nil)
  (with-current-buffer (chatgpt-shell)
    (define-key chatgpt-shell-mode-map (kbd "C-c C-c")
      'chatgpt-shell-leave-doctor)
    (shell-maker-set-buffer-name (current-buffer)
                                 "*chatgpt*"))
  (chatgpt-shell--insert-doc-response))

(defun chatgpt-shell--doc-conversation ()
  (let ((convo (with-current-buffer "*doctor*"
                 (split-string (buffer-string) "\n\n"))))
    (seq-remove
     (lambda (item)
       (string-empty-p (string-trim item)))
     (append
      ;; Replace first doc line, so it drops "Each time you are finished talking, type RET twice."
      (list "I am the psychotherapist.  Please, describe your problems.")
      (mapcar
       (lambda (item)
         (replace-regexp-in-string "\n" " " item))
       (cdr convo))))))

(defun chatgpt-shell--doc-response ()
  (let* ((conversation (chatgpt-shell--doc-conversation))
         (length (seq-length conversation))
         (doc-response (nth (1- length) conversation)))
    doc-response))

(defun chatgpt-shell--insert-doc-response ()
  (with-current-buffer "*chatgpt*"
    (goto-char (point-max))
    (chatgpt-shell--insert-delayed-text (chatgpt-shell--doc-response))
    (call-interactively 'shell-maker-submit)))

(defun chatgpt-shell--insert-delayed-text (text)
  "Insert TEXT into the current buffer, with a delay between each character."
  (dolist (char (string-to-list text))
    (insert char)
    (sit-for 0.009)))

(defun chatgpt-shell--on-chatgpt-patient-response (command output)
  (if (and chatgpt-shell--doctor-in-session
           (not (string-match-p (regexp-quote "I'm cured") output)))
      (progn
        (with-current-buffer "*chatgpt*"
          (goto-char (point-max)))
        (with-selected-window (get-buffer-window "*doctor*")
          (goto-char (point-max))
          (chatgpt-shell--insert-delayed-text output)
          (call-interactively 'doctor-ret-or-read)
          (chatgpt-shell--insert-doc-response)))
    (message "Bye Bye")))

(defun chatgpt-shell-leave-doctor ()
  (interactive)
  (setq chatgpt-shell--doctor-in-session nil)
  (call-interactively 'chatgpt-shell-ctrl-c-ctrl-c))

(add-hook 'chatgpt-shell-after-command-functions
          #'chatgpt-shell--on-chatgpt-patient-response)

(global-set-key (kbd "M-q") #'chatgpt-shell-visit-doctor)

Ok, enough already. Let's see it in action.

chatgpt-doc_optimized.gif

If you'd like to take a closer look for analysis, here's the official transcript of this fruitful session:

I am the psychotherapist. Please, describe your problems. Each time you are finished talking, type RET twice.

I spend hours tweaking my Emacs config, but it's never perfect. I'm overwhelmed and obsessed with customization.

Is it because it's not ever perfect you are overwhelmed and obsessed with customization that you came to me?

Yes, I can't stop myself from constantly trying to tweak and improve my Emacs configuration. It's consuming me.

Emacs? Hah! I would appreciate it if you would continue.

Sorry about that. Yes, I came to you because I'm overwhelmed by my obsession with customizing my Emacs configuration.

Does it bother you that you are overwhelmed by your obsession with customizing your emacs configuration?

Absolutely! I feel trapped in an endless cycle of tweaking and configuring, unable to achieve satisfaction.

Why do you say that?

I constantly seek perfection in my Emacs config, but it's an unattainable goal that leaves me exhausted and frustrated.

When did you first know that it's an unattainable goal that leaves you exhausted and frustrated?

I've realized it recently after spending countless hours trying to perfect my Emacs configuration without any lasting satisfaction.

Are you sure?

Thanks you doc, I think I'm cured.

While we can work exclusively with the *doctor* buffer making requests directly to the ChatGPT API, where's the fun in that? Getting the buffers to talk to each other enables us to marvel at both the beauty and absurdity of being able to glue anything together in our lovely Emacs world.

Happy Emacsing!

09 July 2023 chatgpt-shell v0.60.1 updates

Back in April, I shared chatgpt-shell updates, showcasing chatgpt-shell features. It's been a little while, so here's an update with the latest additions.

Like this project? Consider ✨sponsoring✨.

Multi-session support

You can run multiple shell instances independently configured to use different versions or system prompts.

This was biggest recent change. Please report issues.

cat-turtle.gif

Display system prompt and version

The current shell's version and system prompt are now displayed more prominently in both the shell prompt and buffer name.

display.png

With multi-session support, displaying shell details in the buffer name becomes more important as it makes it easier to find shells across your buffer list.

Rename shell buffers

While buffer names are now automatically derived, one can also use chatgpt-shell-rename-buffer to use custom buffer names.

ob-chatgpt-shell improvements

Use :temperature to specify the temperature.

Use :context CONTEXT-NAME to pick and choose which source blocks to aggregate as context. Thank you Thomas Moulia.

Use :preflight t to debug ob-chatgpt-shell execution.

preflight.png

chatgpt-shell-write-git-commit

Adds chatgpt-shell-write-git-commit, so you can generate commit messages using the current region. Thank you Simon Judd.

Approximate context length

chatgpt-shell now uses chatgpt-shell--approximate-context-length to approximate the context size and discard history if necessary. This is pretty experimental but seems to work well enough. It's enabled by default to get some feedback. Please file bugs if needed or send PRs to improve.

S-<return> for multiline input

In addition to C-J to insert multi-line input, S-<return> is also supported. Thank you shouya for the submission.

Welcome message

A welcome message now makes the help much more discoverable for new or sporadic users. Thank you shouya for the suggestion.

welcome.png

Help me

While the README documents the shells and Emacs is self-documenting, we now have a help command to make things a little more discoverable.

help.png

Hello chatgpt-shell-mode and dall-e-shell-mode

Both chatgpt-shell and dall-e-shell are both based on shell-maker and until recently both shared shell-maker-mode as their major mode. This didn't play well with yasnippet. Both shells now enable independent major modes: chatgpt-shell-mode and dall-e-shell-mode. Thank you Daniel Liden for the proposal.

Saving transcript customizations

Make transcript saving more customizable via shell-maker-transcript-default-path and shell-maker-transcript-default-filename. Thank you gnusupport.

New ChatGPT model versions

New OpenAI model versions were recently released and added to chatgpt-shell: gpt-3.5-turbo-0613 and gpt-4-0613. Thanks you Norio Suzuki.

Load awesome prompts

M-x chatgpt-shell-load-awesome-prompts to download and import curated prompts from awesome-chatgpt-prompts. Thank you Daniel Gomez.

awesome.png

ob-async

We had reports that ob-chatgpt-shell didn't play nice with ob-async. Thank you William Medrano for the solution.

Configurable prompts

Functions like chatgpt-shell-describe-code ask ChatGPT to describe the code in region. These functions used hardcoded English prompts. These are now configurable, so users can tweak or translate if preferred. Thank you Norio Suzuki.

  • chatgpt-shell-prompt-header-describe-code
  • chatgpt-shell-prompt-header-refactor-code
  • chatgpt-shell-prompt-header-generate-unit-test
  • chatgpt-shell-prompt-header-proofread-region
  • chatgpt-shell-prompt-header-whats-wrong-with-last-command
  • chatgpt-shell-prompt-header-eshell-summarize-last-command-output

05 July 2023 Duplicate this!

James Dyer has a nice post sharing his frequent dired need to duplicate files. He offers a solution using a custom interactive command. His use-case resonated with me.

Similarly, James' recommendation to bind his file-duplicating command to C-c d [1] sent a signal to my brain triggering Bozhidar Batsov's crux-duplicate-current-line-or-region.

crux-duplicate-current-line-or-region is part of a "collection of Ridiculously useful extensions for Emacs" (yeah that's crux). The command itself does what it says on the tin.

Let's duplicate the current line.

duplicate-line.gif

Now let's duplicate the current region.

duplicate-region.gif

Since I already have a well-internalized key-binding duplicating lines/regions in text buffers, I could extend a similar behaviour to dired files with almost zero adoption effort.

In case you haven't noticed, I've made it a part-time job to make command line utilities easily accessible from Emacs (1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21) via dwim-shell-command. Partly because it's fairly quick and partly 'cause it's fun.

Jame's post gave me yet another opportunity to exercise my errrm part-time job. This time, duplicating files. All I need is the cp utility and a template:

cp -R '<<f>>' '<<f(u)>>'

I seldom type these template's myself when I want to execute a command (via M-x dwim-shell-command). I typically wrap these templates in interactive commands, making them easily accessible via M-x and your favorite completion framework. I happen to use ivy.

(require 'dwim-shell-command)

(defun dwim-shell-commands-duplicate ()
  "Duplicate file(s)."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Duplicate file(s)."
   "cp -R '<<f>>' '<<f(u)>>'"
   :utils "cp"))

There's nothing much to the command. Most logic is handled by the template, replacing <<f>> with the current file and <<f(u)>> with a uniquified version of it. Having said this, there's a bunch of free DWIM love that kicks in, courtesy of the dwim-shell-command package by yours truly. Let's give our new dwim-shell-commands-duplicate command a spin.

Like crux-duplicate-current-line-or-region duplicates the current line, our new command duplicates the current dired file.

duplicate-file.gif

Got multiple files to duplicate? Like crux-duplicate-current-line-or-region, we can use the region for a similar purpose.

duplicate-files.gif

While we have been using the region to duplicate adjacent files, we can also mark specific files.

duplicate-marked.gif

Our cp -R '<<f>>' '<<f(u)>>' template uses the -R (recursive) flag, so we get another freebie. In addition to files, we can also duplicate directories.

duplicate-dirs.gif

Lastly, because we're on a DWIM train, if your current buffer happens to be visiting a file, you can M-x dwim-shell-commands-duplicate the current file to duplicate it. You're automatically dropped to a dired buffer, with point on the new file (à la dired-jump).

duplicate-buffer.gif

While duplicating files using a template was a mere cp -R '<<f>>' '<<f(u)>>' away, we get a bunch of free DWIM magic applied to a handful of use-cases and contexts. What made the file-duplicating use-case extra special is that it maps almost exactly to an equivalent text command. Keep the same key bindings and we almost get a "free feature".

(use-package crux
  :ensure t
  :commands crux-open-with
  :bind
  (("C-c d" . crux-duplicate-current-line-or-region)))

(use-package dwim-shell-command
  :ensure t
  :bind (:map dired-mode-map
              ("C-c d" . dwim-shell-commands-duplicate))
  :config
  ;; Loads all my own dwim shell commands
  ;; (including `dwim-shell-commands-duplicate')
  (require 'dwim-shell-commands))

You can find my ever-growing list of similar commands over at dwim-shell-commands.el (the optional part of the package). Got some nifty usages? Would love to check 'em out. Get in touch.

Like this or other content? ✨Sponsor✨ via GitHub Sponsors.

[1]: I actually use C-x C-d for crux-duplicate-current-line-or-region but same same…

Update

If you're keen on a regex-based approach, u/arthurno1 offers a great built-in alternative: dired-do-copy-regexp (bound to % C).

29 June 2023 Stitching images from the comfort of dired

I recently wanted a few images stitched together. A perfect job for ImageMagick. A quick search yielded the magical incantation:

convert image1.jpg image2.jpg image3.jpg +append joined.jpg

Great, now I know, but I'll rarely use it and will soon forget it. I may as well add it to my repository of DWIM command line utilities, wrapped in a convenient Emacs function, applicable from different contexts… know what I mean? 🙃

I built dwim-shell-command for this purpose. You can take the above command and easily turn it into an interactive Emacs command with something like the following:

(require 'dwim-shell-command)

(defun dwim-shell-commands-join-images-horizontally ()
  "Join all marked images horizontally as a single image."
  (interactive)
  (dwim-shell-command-on-marked-files
   "Join images horizontally"
   "convert -verbose '<<*>>' +append 'joined.jpg'"
   :utils "convert"))

You can select as many images as you'd like from the comfort of your dired and make the ImageMagick happen.

burgers.gif

The snippet does the job just fine, but we can make it smarter. For starters, let's not hardcode the output filename. We'll ask the user instead. While we're asking, let's offer a default filename, but let's not assume the output extension is .jpg. Let's guess based on the image selection. While we're at it, let's not override the output file if already exists. Uniquify it.

Most of the above can be achieved by either using dwim-shell-command helpers or its templating language. For example, <<joined.png(u)>> ensures that if joined.png already exists, it automatically generates joined(1).png instead.

(require 'dwim-shell-command)

(defun dwim-shell-commands-join-images-horizontally ()
  "Join all marked images horizontally as a single image."
  (interactive)
  (let ((filename (format "joined.%s"
                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
    (dwim-shell-command-on-marked-files
     "Join images horizontally"
     (format "convert -verbose '<<*>>' +append '<<%s(u)>>'"
             (dwim-shell-command-read-file-name
              (format "Join as image named (default \"%s\"): " filename)
              :default filename))
     :utils "convert")))

Here's the new horizontal command in action…

burger_row_x1.5_optimized.gif

Notice how this time we didn't mark the images using dired-mark, typically bound to m. Instead, we made our selection using the region. Also, if you haven't gotten your junk food fix yet, here's the fries equivalent ;)

fries_row_x1.5_optimized.gif

We'll rinse all and repeat to get the vertical command equivalent. I know, I know, there's fair amount of duplication but c'est la vie.

(require 'dwim-shell-command)

(defun dwim-shell-commands-join-images-vertically ()
  "Join all marked images vertically as a single image."
  (interactive)
  (let ((filename (format "joined.%s"
                          (or (seq-first (dwim-shell-command--file-extensions)) "png"))))
    (dwim-shell-command-on-marked-files
     "Join images vertically"
     (format "convert -verbose '<<*>>' -append '<<%s(u)>>'"
             (dwim-shell-command-read-file-name
              (format "Join as image named (default \"%s\"): " filename)
              :default filename))
     :utils "convert")))

…and for our grand finale, we'll vertically join our burgers and fries. Behold!

finale_x1.5_optimized.gif

These commands are now part of dwim-shell-command. To get them, load the optional commands via (require 'dwim-shell-commands).

18 June 2023 noweb: the lesser known org babel glue

While Org babel's noweb isn't something I've frequently used for literate programming, its simplicity makes it rather versatile to glue all sorts of babel things I hadn't previously considered.

The idea is simple. Add a placeholder like <<other-block>> to an org babel source block, and it will be automatically replaced (verbatim) with the content (or result) of referred block before execution. You'll also need the :noweb yes header argument.

#+NAME: other-block
#+begin_src swift
  print("Hello 0")
#+end_src

#+RESULTS: other-block
: Hello 0

#+BEGIN_SRC swift :noweb yes
  <<other-block>>
  print("Hello 1")
#+END_SRC

#+RESULTS:
: Hello 0
: Hello 1

Since <<other-block>> is replaced with the content of said block, at execution time, the block is effectively equivalent to executing:

print("Hello 0")
print("Hello 1")
Hello 0
Hello 1

Why is this so versatile? Org babel can include/execute all sorts of languages, so you can mix and match the result from one language and massage it to appear as the body of another block using the same (or different) language.

I was recently asked how to include the result from one babel block in another using ob-chatgpt-shell. While the initial question was looking for a solution involving variables, we can use noweb to achieve a similar goal.

Note that in this case, I'll be using <<hello()>>, with (), to refer to #+RESULTS: rather than the source block itself.

#+NAME: hello
#+BEGIN_SRC chatgpt-shell
Say hello in spanish
#+END_SRC

#+RESULTS: hello
Hola

#+BEGIN_SRC chatgpt-shell :noweb yes
<<hello()>>
What does the previous line say verbatim?
#+END_SRC

Executing the block

<<hello()>>
What does the previous line say verbatim?

Gives us

The previous line says "Hola".

On a similar note, I was asked if the results from a previous source block could be fed to a Swift Chart block using ob-swiftui.

While I'm new to Swift Charts, I do love glueing things via Emacs lisp. I figured I could write a little elisp to generate random data and feed it to a SwiftUI block via <<data()>>. The result is pretty neat, based on Apple's LineMark example.

data-chart.gif

#+NAME: data
#+begin_src emacs-lisp :lexical no
  (concat (mapconcat (lambda (n)
                       (format "MonthlyHoursOfSunshine(city: \"Seattle\", month: %d, hoursOfSunshine: %d),"
                               n (random 100)))
                     (number-sequence 1 20) "\n")
          "\n"
          (mapconcat (lambda (n)
                       (format "MonthlyHoursOfSunshine(city: \"Cupertino\", month: %d, hoursOfSunshine: %d),"
                               n (random 100)))
                     (number-sequence 1 20) "\n"))
#+end_src

#+begin_src swiftui :results file :noweb yes
  import Charts

  struct MonthlyHoursOfSunshine: Identifiable {
    var city: String
    var date: Date
    var hoursOfSunshine: Double
    var id = UUID()

    init(city: String, month: Int, hoursOfSunshine: Double) {
      let calendar = Calendar.autoupdatingCurrent
      self.city = city
      self.date = calendar.date(from: DateComponents(year: 2020, month: month))!
      self.hoursOfSunshine = hoursOfSunshine
    }
  }

  struct ContentView: View {
    var data: [MonthlyHoursOfSunshine] = [
<<data()>>
    ]
    var body: some View {
      Chart(data) {
        LineMark(
          x: .value("Month", $0.date),
          y: .value("Hours of Sunshine", $0.hoursOfSunshine)
        )
        .foregroundStyle(by: .value("City", $0.city))
      }
      .frame(minWidth: 800, minHeight: 300)
      .padding()
      .colorScheme(.dark)
    }
  }
#+end_src

While I've shown fairly basic usages of noweb, we can accomplish some nifty integrations. Check out the noweb reference syntax for more examples and additional header arguments like tangle, strip-tangle, and others.

25 May 2023 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).

removed-lambda.gif

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.

alist.gif

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.

edit.gif

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

18 May 2023 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!

log-elisp.gif

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)))
           ((equal major-mode 'ada-mode)
            (cons (format "Ada.Text_Io.Put_Line (\"%s: \\([0-9]+\\)\");" word)
                  (format "Ada.Text_Io.Put_Line (\"%s: %%s\");" word)))
           ((equal major-mode 'c++-mode)
            (cons (format "std::cout << \"%s: \\([0-9]+\\)\" << std::endl;" word)
                  (format "std::cout << \"%s: %%s\" << std::endl;" 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)))

Note: This snippet may evolve independently of this post. For the latest, chech my Emacs config's fe-prog.el.

I want to be lazy in other languages, so the function can now be extended to support other languages. Here's the Swift counterpart.

log-swift.gif

Since I sometimes log function names, I figured making it region-aware would help with that.

log-selection.gif

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 1

Set ar/unique-log-word to nil and let it generate a random word. Maybe I get to learn new words as I debug ;)

word.gif

Update 2

Added Ada and C++ support, thanks to James Dyer's post.

11 May 2023 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!

0x0.gif

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:

01 May 2023 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.

melpa-siblings.jpg

Here's ob-chatgpt-shell and ob-dall-e-shell in action.

babel.png

Here's dall-e-shell.

dalle.png

28 April 2023 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.

docs.gif

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

commands.gif

You see? This post wasn't really about ChatGPT. Aren't you glad you stuck around? 😀

25 April 2023 LLM bookmarks

25 April 2023 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.

cyberpunk.gif

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.

swift.gif

In addition to standard output, chatgpt-shell can now render blocks generating images. Here's a rendered SwiftUI layout via ob-swiftui.

swiftui.gif

Can also do diagrams. Here's ditaa in action.

ditaa.gif

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.

rename.gif

Send prompt/region

There are a handful of commands to send prompts from other buffers, including the region. For example chatgpt-shell-explain-code.

explain-region.gif

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

restore.gif

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 via completing-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.

dalle.png

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:

babel.png

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

22 April 2023 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?

record-bg.gif

Figure 5: Capture region (includes wallpaper/background)

record.gif

Figure 6: 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).

delete.png

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.

macosrec.gif

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.

to-gif.png

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.

bunch.png

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?

20 April 2023 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.

file-render.gif

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.

15 April 2023 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.

Emacs.png

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.

Nyan.png

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.

swap.png

Titlebar

I've enabled both transparent title bar as well as dark appearance, giving a minimal window decoration.

decoration.jpg

(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)

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.

eshell.gif

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.

welcome.png

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!

rotate.webp

Coincidentally, I had a go at writing a basic zone a little while ago.

zone.gif

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.

08 April 2023 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.

streamer.gif

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.

dalle.gif

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 ;)

maria.gif

(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!

06 April 2023 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.

today_no_filter.png

download-on-app-store.png

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 😊

21 March 2023 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.

chatgpt.gif

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 ;-)

04 March 2023 *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.


*scratch* icon

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.

scratch-download_no_audio_x2.6.webp

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.



download-on-app-store.png

29 January 2023 Chicken Karaage recipe

Huge fan of Chicken Karaage, but never really made it at home until recently.

frying.jpg

fried.jpg

dipping.jpg

sauces.jpg

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!

10 January 2023 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:

  1. Easily jump between areas of interest. Subheadings, links, and code blocks would be a good start.
  2. 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.

org-navigate_x1.6.webp

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.

08 January 2023 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.

sf-insert-trimmed_x1.8.webp

31 December 2022 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.

disjointed.png

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.

mini-macro_x1.6.webp

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

sf-symbol-no-mouse-short_x1.4.webp

Now that we have our data joined, we can feed it to the humble completing-read.

sf-symbols-insert-name.png

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

sf-search_x1.2.webp

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.

18 December 2022 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

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

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

trim_convert_mp3_x1.4.webp

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:

make-emacs-alias_x1.4.webp

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.

12 December 2022 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.

snippet.gif

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.

syntax-tree.png

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.

query-builder.png

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!

init-sitter_x2.webp

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:

  1. tree-sitter-langs's Swift grammar is fairly outdated/incomplete.
  2. There are more up-to-date Swift grammar implementations currently available:

13 November 2022 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).

kill_x1.8.webp

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.

06 November 2022 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".

set-default_x1.3.webp

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?

02 November 2022 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).

dwim-exif_x1.3.webp

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

minibuffer-address_x1.3.webp

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

photo-map_x1.4.webp

(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 ;)

24 October 2022 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.

welcome-minimal_x0.5.webp

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.

14 October 2022 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.

open-with_x2.webp

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?

12 October 2022 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.

share-completing_x1.4.webp

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?

12 October 2022 Emacs: macOS sharing (DWIM style)

UPDATE: See an improved implementation here.

A few days ago, I wrote dwim-shell-commands-macos-reveal-in-finder. While I've written a bunch of other dwim-shell-commands, what set this case apart was the use of Swift to glue an Emacs workflow.

(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 -"))

There is hardly any Swift involved, yet it scratched a real itch I couldn't otherwise reach (reveal multiple dired files in macOS's Finder).

divinedominion's reddit comment got me thinking of other use-cases, so I figured why not push this Swift-elisp beeswax a little further… Let's add macOS's sharing ability via dwim-shell-command, so I could invoke it from the comfort of my beloved dired or any 'ol Emacs buffer visiting a file.

(defun dwim-shell-commands-macos-share ()
  "Share selected files from macOS."
  (interactive)
  (let* ((position (window-absolute-pixel-position))
         (x (car position))
         (y (- (x-display-pixel-height)
               (cdr position))))
    (dwim-shell-command-on-marked-files
     "Share"
     (format
      "import AppKit

       _ = NSApplication.shared

       NSApp.setActivationPolicy(.regular)

       let window = InvisibleWindow(
         contentRect: NSRect(x: %d, y: %s, width: 0, height: 0),
         styleMask: [],
         backing: .buffered,
         defer: false)

       NSApp.activate(ignoringOtherApps: true)

       DispatchQueue.main.async {
         let picker = NSSharingServicePicker(items: [\"<<*>>\"].map{URL(fileURLWithPath:$0)})
         picker.delegate = window
         picker.show(
           relativeTo: .zero, of: window.contentView!, preferredEdge: .minY)
       }

       NSApp.run()

       class InvisibleWindow: NSWindow, NSSharingServicePickerDelegate, NSSharingServiceDelegate {
         func sharingServicePicker(
           _ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?
         ) {
           if service == nil {
             print(\"Cancelled\")

             // Delay so \"More...\" menu can launch System Preferences
             DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
               NSApplication.shared.terminate(nil)
             }
           }
         }

         func sharingServicePicker(
           _ sharingServicePicker: NSSharingServicePicker,
           delegateFor sharingService: NSSharingService
         ) -> NSSharingServiceDelegate? {
           return self
         }

         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)
         }
       }" x y)
     :silent-success t
     :shell-pipe "swift -"
     :join-separator ", "
     :no-progress t
     :utils "swift")))

Sure there is some trickery involved here (like creating an invisible macOS window to anchor the menu), but hey the results are surprisingly usable. Take a look…

share-done_x1.4.webp

I've pushed dwim-shell-commands-macos-share to dwim-shell-commands.el in case you'd like to give it a try. It's very much an experiment of sorts, so please treat it as such. For now, I'm looking forward to AirDropping more files and seeing if the flow sticks. Oh, and I just realised I can use this to send files to iOS Simulators. Win.

dwim-shell-command is available on melpa. What other uses can you find for it?

09 October 2022 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:

dwim-reveal.webp

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.

01 October 2022 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.

po.png

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 :)

01 October 2022 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"))

pdf-to-txt_x2.webp

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

17 September 2022 $ 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.

recovered_x1.6.webp

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))

11 September 2022 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.

cycle_layout.webp

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.

18 August 2022 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."

query.png

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"))

empty.webp

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.

14 August 2022 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.

joined_minimal_x1.2.webp

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.

fuzzy.png

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.

03 August 2022 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.

hearts_x1.5.webp

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

01 August 2022 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.

  1. Copy URL from browser.
  2. Invoke dwim-shell-commands-mpv-stream-clipboard-url.
  3. Enjoy picture in picture from Emacs ;)

mpv.webp

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.

30 July 2022 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 "á".

macosaccent.webp

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.

accentuated.webp

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

porque.webp

28 July 2022 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.

backup_x2.webp

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

clone.webp

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'

numberedsorted_x2.2.webp

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'

alphacount_x2.2.webp

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.

repeat.webp

Optional error prompt

Set dwim-shell-command-prompt-on-error to nil to skip error prompts. Focus process buffers automatically instead.

couldnt.png

Configurable prompt

By default, dwim-shell-command shows all supported placeholders. You can change that prompt to something shorter using dwim-shell-command-prompt.

prompt.jpg

⚠️ Use with care ⚠️

The changes are pretty fresh. Please use with caution (specially the counter support).

24 July 2022 dwim-shell-command on Melpa

clone.webp

Figure 7: <<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:

Irreal's also covered it:

16 July 2022 A lifehack for your shell

unzip_x2.gif

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"))

unzip-dired_x1.5.gif

UPDATE:

Lobste.rs has great comments. Thanks all:

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.

atool alternatives

Nice to see other options suggested dtrx (comment), archiver (comment), unar (comment), bsdtar from libarchive (comment), unp, patool, and the tangentially related zgrep (comment).

13 July 2022 Emacs zones to lift you up

zone.gif

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!

10 July 2022 Emacs: DWIM shell command (multi-language)

UPDATE: dwim-shell-command is now available on melpa.

csv.gif

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.

09 July 2022 png to icns (Emacs DWIM style)

UPDATE: dwim-shell-command is now available on melpa.

icns.gif

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"))

09 July 2022 Emacs: Password-protect current pdf (revisited)

UPDATE: dwim-shell-command is now available on melpa.

passprotect.gif

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?

07 July 2022 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.

template.png

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.

diredmark.gif

Operate on current buffer file

Similarly, if visiting a buffer with an associated file, operate on that file instead.

blur.png

Automatically take me to created files

Did the command create a new file in the current directory? Take me to it, right away.

showme.png

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.

couldnt.png

Show me output if no new files

Not all commands generate new files, so automatically show me the output for these instances.

apple.gif

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"))

togif_x1.5.gif

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.

progress.gif

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.

02 June 2022 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))))))

24 April 2022 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.

list_this.gif

Render form feed characters

Form feed characters are now rendered within expanded headings.

form_feed.jpg

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.

15 April 2022 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.

recurring.gif

Log state transitions

logging.gif

Fullscreen view

The navigation bar now hides on scroll. This can be enabled/disabled via View > Full Screen menu.

fullscreen.gif

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

deadline_scheduled.png

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

27 March 2022 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:

logbook.jpg

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.

top_bottom.png

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.

reload.jpg

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.

load_other.jpg

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.

12 March 2022 Grandma's vanilla pound cake

pound_cake.jpg

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

05 March 2022 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

21 February 2022 Emacs: Fuzzy search Apple's online docs

color_search.gif

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))

13 February 2022 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.

inline.gif

Filter by keyword/priority/tag

From the search dialog, you can now filter by keyboard, priority, and tag.

select_filter.png

filter_results.png

Render drawers and properties

Drawers are now rendered and can be expanded to view their content.

drawer.gif

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.

share.png

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.

latex_src.png

latex_render.png

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.

new_tag.png

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.

sticky_tags_setting.png

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.

navbar.gif

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.

download-on-app-store.png

03 January 2022 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.

today_no_filter.png today_no_filter.png today_no_filter.png

12 December 2021 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.

compact.gif

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.

regroup.gif

Native table rendering

Tables are now rendered natively but also support displaying links and other formatting within cells.

table.gif

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.

idlink.gif

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.

fillparagraph.gif

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.

scheduling.gif

Show/hide tags

Similarly, the new Menu -> View -> Tags option toggles displaying tags.

hidetags.gif

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

listitems.png

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

numbered.png

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.

download-on-app-store.png

28 November 2021 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.

testTableWithLinks.1.png

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.

diff.gif

;;; -*- 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))

10 November 2021 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.

store-side-comparison-mid.gif

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.

magit.png

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.

flat_habits.gif

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.

intro.png


download-on-app-store.png
This post was written in org mode.

19 September 2021 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:

inline_keyword_toolbar.gif

If, like me, you prefer dark mode. The app's got ya covered:

dark.png

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.

19 August 2021 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.

flat_habits.gif

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.

plainorgdemo.gif

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

11 July 2021 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.

multi_day_creation.gif

multi_day_calendar.png

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.

long_tap.gif

Short tap

Short tap typically toggles between "Done" and "Not done".

short_tap.gif

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.

today.png

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.

11 July 2021 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.

show_in_emacs.gif

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.

show_in_emacs.png

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.

open_in_emacs.png

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)

27 June 2021 Emacs: smarter search and replace

smarter_replace.gif

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!

20 June 2021 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.

ob-swiftui.gif

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:

obswiftui50.gif

When rendering SwiftUI externally, we're effectively running and interacting with the generated macOS app itself.

ob-swiftui-window.gif

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.

19 June 2021 Blurring the lines between shell and editor

yas-eshell.gif

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.

yas-for.gif

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.

updated:

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:

yasbash.gif

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

16 June 2021 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/

06 June 2021 Emacs org block completion on melpa

When enabled, the character "<" triggers company completion of org blocks.

hello-company-org-block.gif

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!

01 June 2021 Emacs DWIM: do what ✨I✨ mean

Update: There's a DWIM follow-up for searching.

do-what-i-mean.gif

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.

link-this-text.gif

Automatically fetch titles

Automatically fetch URL titles from their HTML tag, but ask me for tweaks before insertion.

do-what-i-mean.gif

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.

edit-link.gif

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.

23 May 2021 OCR bookmarks

19 May 2021 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.

17 May 2021 Emacs plus –with-native-comp

brew-native-comp.png

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.

02 May 2021 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.

cycle.gif

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)

10 April 2021 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_agenda.gif

today_no_filter.png

23 March 2021 Flat Habits v1.0.1 (org import menu)

UPDATE: Flat Habits now has its own page at flathabits.com.

Flat Habits v1.0.1 is now released and available in the App Store.

org import (import vs in-place)

We can now import org files from the menu. Importing gives ya the option to either import (copy into the app) or open in-place. The latter enables users to sync org files with other iOS apps or just open/edit from Emacs for the full org-mode/agenda experience.

today_no_filter.png today_no_filter.png

Syncing with your desktop can be achieved by either iCloud or by enabling other providers in the Files app (after installing the likes of Google Drive, Dropbox, etc).

Please note that importing (copying into the app) is currently the recommended flow. Opening in-place and syncing is still fairly experimental, so please back up your org files regularly. If you do run into syncing issues, please get in touch.

Good luck with your habits!

17 March 2021 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.

So what is it?

An iOS app to help you form and track lasting habits.

today_no_filter.png today_no_filter.png today_no_filter.png

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.

21 February 2021 Frictionless org habits on iOS

UPDATE: Flat Habits now has its own page at flathabits.com.

flat_habits.gif

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.

filesapp_shorter.gif

20 February 2021 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

23 January 2021 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:

mu4eicons.png

(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" . "🖊 "))

02 January 2021 Luxembourg travel bookmarks

02 January 2021 South Africa travel bookmarks

29 December 2020 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à!

coverage.png

29 December 2020 Hiking bookmarks

28 December 2020 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…

26 December 2020 Chess bookmarks

20 December 2020 40 Coolest neighbourhoods in the world

Via TimeOut's 40 Coolest Neighbourhoods in the World Right Now:

  1. Esquerra de l’Eixample, Barcelona
  2. Downtown, Los Angeles
  3. Sham Shui Po, Hong Kong
  4. Bedford-Stuyvesant, New York
  5. Yarraville, Melbourne
  6. Wedding, Berlin
  7. Shaanxi Bei Lu/Kangding Lu, Shanghai
  8. Dennistoun, Glasgow
  9. Haut-Marais, Paris
  10. Marrickville, Sydney
  11. Verdun, Montreal
  12. Kalamaja, Tallinn
  13. Hannam-dong, Seoul
  14. Bonfim, Porto
  15. Ghosttown, Oakland
  16. Chula-Samyan, Bangkok
  17. Alvalade, Lisbon
  18. Noord, Amsterdam
  19. Centro, São Paulo
  20. Holešovice, Prague
  21. Lavapiés, Madrid
  22. Opebi, Lagos
  23. Narvarte, Mexico City
  24. Uptown, Chicago
  25. Little Five Points, Atlanta
  26. Wynwood, Miami
  27. Phibsboro, Dublin
  28. Nørrebro, Copenhagen
  29. Bugis, Singapore
  30. Gongguan, Taipei
  31. Soho, London
  32. Binh Thanh, Ho Chi Minh City
  33. Melville, Johannesburg
  34. Kabutocho, Tokyo
  35. Porta Venezia, Milan
  36. Taman Paramount, Kuala Lumpur
  37. Allston, Boston
  38. Bandra West, Mumbai
  39. Arnavutköy, Istanbul
  40. Banjar Nagi, Ubud

05 December 2020 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à!

rotate.gif

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")))))

29 November 2020 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:

git_clone_dired.gif

;; -*- 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)))

Comment on reddit or twitter.

Updates

  • Added lexical binding.
  • Checks clipboard for ssh urls also.

23 November 2020 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.

grind.jpg

ground.jpg

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.

almost.jpg

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.

out.jpg

Pull apart

Use two forks to pull the meat apart.

final.jpg

01 November 2020 Zettelkasten bookmarks

28 October 2020 Battlestation bookmarks

27 October 2020 Emacs: chaining org babel blocks

Recently wanted to chain org babel blocks. That is, aggregate separate source blocks and execute as one combined block.

chain.gif

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.

noweb.gif

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.

25 October 2020 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.

wrap-toggle-region.gif

(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)))

18 October 2020 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!

automatic.gif

Or maybe the automatic approach is too eager? There's also a middle ground: ask immediately after.

prompted.gif

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.

11 October 2020 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.

swift-package.gif

(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))

04 October 2020 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.

bindings-borked.gif

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.

bindings-fixed.gif

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.

04 October 2020 Basmati rice pudding recipe

rice_pudding.jpg

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

27 September 2020 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…".

formulatepro.png

27 September 2020 DIY bookmarks

24 September 2020 Skiing bookmarks

19 September 2020 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.

music_search.gif

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.

12 September 2020 Cheese cake recipe (no crust)

berried.jpg

inoven.jpg

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.

28 August 2020 Faster macOS dock auto-hide

dock.gif

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

25 August 2020 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/

snippet.gif

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)))

23 August 2020 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

23 August 2020 QR code bookmarks

16 August 2020 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.

02 August 2020 SwiftUI macOS desk clock

everclock.gif

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
  }
}

30 July 2020 Mending bookmarks

17 June 2020 ffmpeg bookmarks

14 June 2020 Black lives matter (BLM) bookmarks

14 June 2020 Dogs bookmarks

06 June 2020 Emacs, search hackingwithswift.com

hws.gif

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))

23 May 2020 Preview SwiftUI layouts using Emacs org blocks

ob-swiftui.gif

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:

vstack.jpg

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.

11 May 2020 Enrich Emacs dired's batching toolbox

Update

I now use dwim-shell-command, which reduces the logic to:

(defun dwim-shell-commands-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"))

Original post

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.

batch-dired.gif

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.

09 May 2020 Banana oats pancakes recipe

banpan.jpg

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.

06 May 2020 Emacs: connect my Bluetooth speaker

Connecting and disconnecting bluetooth devices on macOS is fairly simple: use the menu bar utility.

macos-menu.png

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"))))

connect-disconnect.gif

comments on twitter.

02 May 2020 Duti: changing default macOS apps

Future self example, setting mpv.io to open all aiff files on macOS:

duti -s io.mpv aiff

26 April 2020 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

round_pie.jpg

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:

  1. Bake the pizza for 6 minutes (without the mozarella) at bottom of oven.
  2. 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

mix1.jpg

mix2.jpg

  • 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

knead1.jpg

knead2.jpg

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

cut_in_four.jpg

four_balls.jpg

Roll the dough into a cylinder and cut into 4 pieces. Make 4 balls.

Make the tomato sauce

garlic_slice.jpg

sauce_done.jpg

  • 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

put_away.jpg

after_hours.jpg

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

stretch.jpg

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

oiled.jpg

parmesan.jpg

mozarella.jpg

  • 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

final.jpg

  • Anchovies.

Controversial, but I really like anchovies. Add them post-baking and you're good to go. Enjoy your pizza.

21 April 2020 TIL (today I learned) bookmarks

19 April 2020 mu/mu4e 1.4 released

mu14.png

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.

14 April 2020 Libya travel bookmarks

07 April 2020 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

06 April 2020 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)

counsel-mx-mc.gif

05 April 2020 Portland travel bookmarks

29 March 2020 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)))

string_inflection.gif

comments on twitter

28 March 2020 Turkey travel bookmarks

25 March 2020 Dal Makhani (black lentils) recipe

dal.jpg

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

21 March 2020 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:

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.

comments on twitter

20 March 2020 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.

xcode_indent.gif

While the wonderful smartparens gives Emacs pair-matching powers, it doesn't automatically indent between pairs (out of the box anyway).

indent_bracket_before.gif

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")))

indent_bracket_after.gif

comments on twitter

20 March 2020 Solarpunk bookmarks

10 March 2020 sqlite bookmarks

26 February 2020 covid-19 bookmarks

15 February 2020 Security bookmarks

15 February 2020 Nix bookmarks

10 January 2020 Plants bookmarks

29 December 2019 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.

before.png

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! :)

after.png

29 December 2019 SwiftUI bookmarks

updated: 23 November 2021

17 December 2019 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

01 December 2019 Georgia travel bookmarks

24 November 2019 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))))

ecomic.gif

comments on twitter

Updates

  • Tweaked title.

21 November 2019 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.

default-switches.gif

comments on twitter

10 November 2019 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

counsel-rg-switch.gif

10 November 2019 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.

company-org-block.gif

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

08 November 2019 IRC bookmarks

03 November 2019 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.

expanded-shell-command.png

(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).

richer-shell-command-history.gif

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))

20 October 2019 Taiwan travel bookmarks

10 October 2019 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:

swiper-mc-no.png

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.

swipermc.gif

08 October 2019 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

08 October 2019 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.

blacklist.png

06 October 2019 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à!

ts.gif

05 October 2019 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.

dired.png

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.

diredfl.gif

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.

dired-git-info.gif

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)))

29 September 2019 Bulk buying bookmarks

01 September 2019 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

01 September 2019 csv bookmarks

10 August 2019 Slovakia travel bookmarks

14 July 2019 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

12 July 2019 Outdoor bookmarks

07 July 2019 gnuplot bookmarks

04 June 2019 gnu global, ctags, and Emacs setup

Universal ctags (newer)

I'm now using universal ctags, as recommended by counsel-etags.

From universal ctag's Building on Mac OS:

brew tap universal-ctags/universal-ctags
brew install --HEAD universal-ctags

.ctags

--langdef=swift
--langmap=swift:+.swift

--kinddef-swift=v,variable,variables
--kinddef-swift=f,function,functions
--kinddef-swift=s,struct,structs
--kinddef-swift=c,class,classes
--kinddef-swift=p,protocol,protocols
--kinddef-swift=e,enum,enums
--kinddef-swift=t,typealias,typealiases

--regex-swift=/(var|let)[ \t]+([^:=]+).*$/\2/v/
--regex-swift=/func[ \t]+([^\(\)]+)\([^\(\)]*\)/\1/f/
--regex-swift=/struct[ \t]+([^:\{]+).*$/\1/s/
--regex-swift=/class[ \t]+([^:\{]+).*$/\1/c/
--regex-swift=/protocol[ \t]+([^:\{]+).*$/\1/p/
--regex-swift=/enum[ \t]+([^:\{]+).*$/\1/e/
--regex-swift=/(typealias)[ \t]+([^:=]+).*$/\2/v/

Exuberant ctags (older/buggy?)

Install gnu global (ensure homebrew uses –with-exuberant-ctags flag).

brew install global
brew install ctags
pip install pygments

.ctags

--langdef=swift
--langmap=swift:.swift
--regex-swift=/[[:<:]]class[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/c,class/
--regex-swift=/[[:<:]]enum[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/e,enum/
--regex-swift=/[[:<:]]func[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/f,function/
--regex-swift=/[[:<:]]protocol[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/P,protocol/
--regex-swift=/[[:<:]]struct[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/s,struct/
--regex-swift=/[[:<:]]typealias[[:>:]][[:space:]]+([[:alnum:]_]+)/\1/t,typealias/

.globalrc

default:\
    :tc=pygments:

ctags:\
    :tc=exuberant-ctags:

exuberant-ctags|plugin-example|setting to use Exuberant Ctags plug-in parser:\
    :tc=common:\
    :ctagscom=ctags:\
    :ctagslib=$libdir/gtags/exuberant-ctags.la:\
    :langmap=Swift\:.swift:\
    :gtags_parser=Swift\:$ctagslib:

pygments:\
    :tc=pygments-parser:

pygments-parser|Pygments plug-in parser:\
    :langmap=Swift\:.swift:\
    :gtags_parser=Swift\:$pygmentslib:\
    :langmap=Common-Lisp\:.cl.lisp.el:\
    :gtags_parser=Common-Lisp\:$pygmentslib:\
    :langmap=Python\:.py.pyw.sc.tac.sage:\
    :gtags_parser=Python\:$pygmentslib:\
    :langmap=Ruby\:.rb.rbw.rake.gemspec.rbx.duby:\
    :gtags_parser=Ruby\:$pygmentslib:\
    :langmap=Objective-C++\:.mm.hh:\
    :gtags_parser=Objective-C++\:$pygmentslib:\
    :langmap=Objective-C\:.m.h:\
    :gtags_parser=Objective-C\:$pygmentslib:\
    :ctagscom=ctags:\
    :pygmentslib=$libdir/gtags/pygments-parser.la:\
    :tc=common:

common:\
    :skip=build/,HTML/,HTML.pub/,tags,TAGS,ID,y.tab.c,y.tab.h,gtags.files,cscope.files,cscope.out,cscope.po.out,cscope.in.out,SCCS/,RCS/,CVS/,CVSROOT/,{arch}/,autom4te.cache/,*.orig,*.rej,*.bak,*~,#*#,*.swp,*.tmp,*_flymake.*,*_flymake,*.o,*.a,*.so,*.lo,*.zip,*.gz,*.bz2,*.xz,*.lzh,*.Z,*.tgz,*.min.js,*min.css:
(use-package counsel-gtags
  :ensure t
  :commands counsel-gtags-mode
  :bind (:map
         counsel-gtags-mode-map
         ("M-." . counsel-gtags-dwim)
         ("M-," . counsel-gtags-go-backward))
  :hook ((swift-mode . counsel-gtags-mode)
         (swift-mode . ggtags-mode)))

;; Needs .ctags and .globalrc in $HOME.
(use-package ggtags
  :ensure t
  :commands ggtags-mode)

Helpful references

https://github.com/osdakira/dotfiles/blob/395640726d669674496a8035458840f0742e54a5/gtags.conf https://github.com/NicholasTD07/dotfiles/blob/e66eb05b408fbcb0d47994fc8a0a79bf438b9e03/.globalrc https://github.com/NicholasTD07/dotfiles/blob/master/.ctags https://github.com/sg2002/gtags.conf-tutorial/blob/master/gtags.conf https://aozsky.com/swift/swift_ide

29 May 2019 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

26 May 2019 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>

24 May 2019 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

21 May 2019 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.

17 May 2019 Charting bookmarks

11 May 2019 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.

06 May 2019 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"

04 May 2019 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.

counsel-find.gif

The code for ar/counsel-find is a little rough but can be found at here.

26 April 2019 VPS bookmarks

22 April 2019 Svelte bookmarks

16 April 2019 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.

before.png

Mark region with C-M-h (mark-defun)

selection.png

Indent with <tab> (indent-for-tab-command)

basic-indent.png

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.

smart-indent.png

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.

14 April 2019 Wider web bookmarks

14 April 2019 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

11 April 2019 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.

30 March 2019 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']

17 March 2019 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:

mc-number.gif

17 March 2019 Brazil travel bookmarks

17 March 2019 Bath travel bookmarks

17 March 2019 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

16 March 2019 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.

before.gif

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.

after.gif

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.

16 March 2019 Checksums on linux/macOS

MD5

md5 file

SHA-1

shasum -a 1 file

SHA-256

shasum -a 256 file

12 March 2019 Language server protocol (LSP) bookmarks

24 February 2019 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"))))

qrencode.gif

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);;"

19 February 2019 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"

13 February 2019 Life in the UK bookmarks

10 February 2019 C language bookmarks

10 February 2019 Video editing bookmarks

10 February 2019 Icons bookmarks

27 January 2019 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.

27 January 2019 Geneva travel bookmarks

13 January 2019 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."))))))

ivy-cycle-sources.gif

ps. Menu data from Star of Kings, Wahaca, and Pizzarino.

12 January 2019 Podcast bookmarks

12 January 2019 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

06 January 2019 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))

pdf-tools.png

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"))

27 December 2018 ASCII art generator bookmarks

26 December 2018 Osaka travel bookmarks

25 December 2018 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.

25 December 2018 Audiobook providers bookmarks

25 December 2018 Cookbook bookmarks

25 December 2018 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…

21 December 2018 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 |.

csv-to-org-table.gif

20 December 2018 Sponsoring platform bookmarks

20 December 2018 Artistic/creative bookmarks

20 December 2018 Marketing bookmarks

19 December 2018 Bluetooth low energy (BLE) bookmarks

18 December 2018 Fun project bookmarks

14 December 2018 Snowboarding bookmarks

11 December 2018 Scam bookmarks

11 December 2018 Passive income bookmarks

08 December 2018 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:

  1. If we have text selected in minibuffer, deselect it.
  2. If we have any text in minibuffer, clear it.
  3. 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)))))

ivy-keybqoard-quit-dwim.gif

05 December 2018 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)))

ediff-dir-content-size.png

02 December 2018 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.

01 December 2018 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

24 November 2018 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

23 November 2018 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

23 November 2018 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

17 November 2018 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/")))))

completing-elfeed.gif

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.

no-left.png

14 November 2018 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

13 November 2018 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))))

elfeed-read-by-page.gif

10 November 2018 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))

delete-junk.gif

06 November 2018 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.

04 November 2018 Shaving bookmarks

04 November 2018 Buy it for life bookmarks

29 October 2018 Rust bookmarks

28 October 2018 Fonts bookmarks

17 October 2018 imenu on Emacs eshell

imenu navigation is one of those Emacs gems I didn't discover until much later on. It does what you'd expect in all types of modes. In rare instances, I've found specific modes missing imenu support. Fortunately, this is Emacs and you can fix that.

Eshell has a handy feature to jump back and forth over previous prompts using M-x eshell-previous-prompt (C-c C-p) and M-x eshell-next-prompt (C-c C-n). Upon learning about these two functions, my immediate reaction was to try imenu. Surprisingly, it didn't "just work\n", but a tiny bit of elisp brought balance back to the Emacs universe.

In an eshell mode hook function, one can set the imenu-generic-expression to help it find your favorite prompt:

(setq-local imenu-generic-expression
                  '(("Prompt" " $ \\(.*\\)" 1)))

Ah it's the little things…

eshell-imenu.gif

ps. If wondering why my imenu experience looks a little different, that's because I'm using Abo Abo's wonderful counsel and M-x counsel-semantic-or-imenu.

14 October 2018 Encrypted disk image on macOS

1.png

2.png

13 October 2018 Sheffield travel bookmarks

13 October 2018 Headsphones bookmarks

05 October 2018 macOS app bookmarks

30 September 2018 Gaming bookmarks

29 September 2018 Lua bookmarks

29 September 2018 Skin product bookmarks

29 September 2018 Sustainability bookmarks

29 September 2018 Investment platform bookmarks

28 September 2018 Minimalist bookmarks

23 September 2018 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).

re-enable_afp_service.png

03 September 2018 CMake bookmarks

28 August 2018 GTD/Get things done bookmarks

28 August 2018 Pandoc bookmarks

27 August 2018 Mauritius travel bookmarks

27 August 2018 Scala bookmarks

22 August 2018 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))

goto-address.png

22 August 2018 Bazel bookmarks

18 August 2018 Palestine travel bookmarks

18 August 2018 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>'

13 August 2018 Recycling bookmarks

updated: 13 August 2018

12 August 2018 Comoro islands travel bookmarks

12 August 2018 France travel bookmarks

12 August 2018 Corsica travel bookmarks

12 August 2018 Mozambique travel bookmarks

12 August 2018 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)

m-r-commit-history.png

09 August 2018 Morning smoothie

Big fan of my morning power smoothie. Best deals I've found so far:

ps. I have no affiliation to either retailer. Prices may change.

08 August 2018 Installing ludget (ledger visualization

Needed python3:

brew install python3

Use pip3 to install ludget:

pip3 install ludget

08 August 2018 Ledger bookmarks

updated: 30 September 2021

07 August 2018 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)))

25 July 2018 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 ;)

24 July 2018 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

15 July 2018 Amsterdam travel bookmarks

14 July 2018 Hardware bookmarks

11 July 2018 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.

fitbitapp.png

You'll also need your USER_ID, from your Fitbitx user profile.

fitbituser.png

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'

weight.png

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.

08 July 2018 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

07 July 2018 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()

chooser.png

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 "))

simple_ivy.png

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))))))

ivy_frame.gif

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:

ivy_links.gif

These integrations look promising. They enable me to bring cross-platform Emacs utilities into areas I hadn't considered.

01 July 2018 Web serving tools bookmarks

24 June 2018 URL shortener bookmarks

17 June 2018 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)

15 June 2018 Sticky function keys on touch bar

Visible (and sticky) function keys are not the touch bar default for Emacs. Let's change that:

touchbar_function_keys.png

13 June 2018 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

13 June 2018 PlantUML bookmarks

29 May 2018 Adding mu4e maildirs extension

Continuing the mu4e fun, added mu4e-maildirs-extension to display a mail dirs summary.

mu4e_maildirs_extension.png

28 May 2018 Trying out mu4e and offlineimap

mu4e.png

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

24 May 2018 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)))

transparent-emacs-titlebars-macos.png

24 May 2018 Lunette: Like Spectacle but for Hammerspoon

Came across Lunette. Gives ya Spectacle Keybindings for Hammerspoon.

23 May 2018 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))

22 May 2018 Show hidden files in Finder

defaults write com.apple.finder AppleShowAllFiles TRUE
killall Finder

22 May 2018 Ejecting USB drives on Synology

For posterity:

Control panel > External devices > USB Disk 1 > Eject

21 May 2018 Remounting Synology encrypted share

Had been a while since I did this… for posterity:

Control panel > Shared Folder > Encryption > Mount

20 May 2018 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

30 April 2018 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

19 April 2018 Azores travel bookmarks

19 April 2018 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

19 April 2018 Paper less bookmarks

18 April 2018 Bologna travel bookmarks

  • Il Cannone restaurant.

17 April 2018 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.

14 April 2018 Hammerspoon bookmarks

14 April 2018 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

09 April 2018 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

08 April 2018 Sapporo travel bookmarks

08 April 2018 Gif bookmarks

08 April 2018 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.

08 April 2018 Image editing bookmarks

05 April 2018 Buying matcha powder online

04 April 2018 Getting macOS app bundle ID

From stack overflow:

Option 1

osascript -e 'id of app "Emacs"'

Option 2

mdls -name kMDItemCFBundleIdentifier -r SomeApp.app

31 March 2018 Trying out chunkwm

chunkwm.png

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:

mission_control.png

keyboard_shortcuts.png

06 February 2018 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

07 January 2018 Extracting files from pkg

mkdir tmp
cd tmp
xar -xf ../Some.pkg
cat Payload | gunzip -dc |cpio -i

07 January 2018 Installing Inkscape with homebrew

brew tap caskroom/cask
brew install caskformula/caskformula/inkscape

16 December 2017 Magit amend commit author

Rarely use it, but handy. Use Magit to amend git commit author.

  • Rebase interactively (r, i).
  • Move point to commit to ammend.
  • Execute command (x).
git commit --amend --author="name <email>"
  • Commit (c, c).

amending.gif

13 December 2017 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

16 November 2017 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

12 November 2017 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}"

01 November 2017 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.

company-pcomplete.png

(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)

10 September 2017 Basic imenu in helpful-mode

I'm finding Wilfred Hughes's helpful-mode, well… rather helpful. However, I'm missing imenu support. Here's a hacky way to get basic imenu.

helpful-imenu.png

(defun helpful--create-imenu-index ()
  "Create an `imenu' index for helpful."
  (beginning-of-buffer)
  (let ((imenu-items '()))
    (while (progn
             (beginning-of-line)
             ;; Not great, but determine if looking at heading:
             ;; 1. if it has bold face.
             ;; 2. if it is capitalized.
             (when (and (eq 'bold (face-at-point))
                        (string-match-p
                         "[A-Z]"
                         (buffer-substring (line-beginning-position)
                                           (line-end-position))))
               (add-to-list 'imenu-items
                            (cons (buffer-substring (line-beginning-position)
                                                    (line-end-position))
                                  (line-beginning-position))))
             (= 0 (forward-line 1))))
    imenu-items))

(defun helpful-mode-hook-function ()
  "A hook function for `helpful-mode'."
  (setq imenu-create-index-function #'helpful--create-imenu-index))

(add-hook 'helpful-mode-hook
          #'helpful-mode-hook-function)

19 August 2017 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.

company-projectile-cd.png

(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)))

09 August 2017 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

06 August 2017 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:

download-bluetooth-explorer.png

Open Hardware_IO_Tools_for_Xcode_7.3.dmg and launch Bluetooth Explorer:

tools-image.png

Select Audio Options:

audio-options.png

Check Force use of aptX:

force-aptx.png

Don't forget to disconnect and reconnect your Bluetooth device.

10 July 2017 Hungary travel bookmarks

08 July 2017 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

07 July 2017 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.

helm-ag-insert.gif

(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)))

29 May 2017 Sleep bookmarks

28 May 2017 Tea bookmarks

23 April 2017 Math bookmarks

23 April 2017 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

13 March 2017 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.

22 January 2017 Tel Aviv travel bookmarks

updated: 27 January 2019
  • 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.

22 January 2017 Jerusalem travel bookmarks

  • Jerusalem: Rooftop Mamilla restarurant.

22 January 2017 Nepal travel bookmarks

02 December 2016 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)
  • Everton park (old housing estate), new meets old
    • 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.
  • Jalan Besar
  • Geylang (preserved shophouses and rich in Malay history)

02 December 2016 Email provider bookmarks

01 December 2016 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

01 December 2016 Javascript snippets

27 November 2016 Sydney travel bookmarks

16 October 2016 Laos travel bookmark

08 October 2016 Singapore travel bookmarks

01 October 2016 Cambodia travel bookmarks

  • Pub Street (Siem Reap, Cambodia).

01 October 2016 New York travel bookmarks

18 September 2016 API design bookmarks

18 September 2016 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

15 September 2016 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:

fuzzy_compile.gif

(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)))

05 September 2016 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

emacs25.png

The only hiccup so far's been org mode failing to export, which was fixed by re-installing it (follow this thread).

31 July 2016 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.

25 July 2016 Moscow travel bookmarks

24 July 2016 Vietnam travel bookmarks

updated: 29 November 2019

19 July 2016 Pokémon Go bookmarks

03 July 2016 Coffee bookmarks

03 July 2016 Machine learning bookmarks

17 June 2016 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:

minimal-zone.gif

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:

emotions.gif

UPDATE: Just came across Animations With Emacs. A post with awesome zone examples.

10 May 2016 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.

emacs-days-between-dates.gif

Note: you can use the mouse to jump to another date, or "g d" (calendar-goto-date).

08 May 2016 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);
 }

06 May 2016 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.

06 May 2016 UIViewController bookmarks

03 May 2016 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.

02 May 2016 Pakistan travel bookmarks

updated: 20 April 2023

02 May 2016 Money bookmarks

updated: 23 November 2021

02 May 2016 Scotland travel bookmarks

02 May 2016 St. Petersburg travel bookmarks

02 May 2016 8 week half-marathon training

An 8-week training schedule:

WEEK MON TUE WED THU FRI SAT SUN
1 Rest 5 Km 5 Km Cycle Rest 5 Km 8 Km 9 Km
    29:56 29:54     29:45 1:00:55
2 Rest 7 Km 5 Km Cycle Rest 5 Km 10 Km
    41:36 27:52     28:23 59:17
3 Rest 8 Km 8.1 Km 5 Km Cycle 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 8 Km Rest 6 Km 16 Km 10 Km
    48:50         53:38
6 Rest 8 Km 8 Km 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
               

02 May 2016 Haskell bookmarks

17 April 2016 Haskell notes

Referential transparency

An expression consistently evaluating to the same result, regardless of context.

28 March 2016 Emacs Objective-C tagging with RTags

Install libclang on Mac

brew install llvm --with-clang

Install RTags

git clone --recursive https://github.com/Andersbakken/rtags.git
cd rtags
cmake -DCMAKE_PREFIX_PATH=/Users/your-user-name/homebrew/opt/llvm -DCMAKE_EXPORT_COMPILE_COMMANDS=1 .
make

Start RTags daemon

path/to/rtags/bin/rdm 2> /tmp/rdm.log

Compilation database

Install xctool

brew install xctool

Generate a compilation database

cd path/to/your/objc-project
xctool -sdk iphonesimulator -arch x86_64 -scheme SomeScheme -reporter pretty -reporter json-compilation-database:compile_commands.json clean build

Load compilation database

path/to/rtags/bin/rc -J path/to/your/objc-project/compile_commands.json

Install RTags Emacs package

(use-package rtags :ensure t
  :config
  (setq rtags-use-helm t) ;; Optional. Enable if helm fan (I am!).
  (setq rtags-path "path/to/rtags/bin/"))

Ready to go

Use any of the rtags interactive commands. For example:

M-x rtags-find-symbol

10 March 2016 Database bookmarks

06 March 2016 Python tips backlog

05 March 2016 Bruges travel bookmarks

02 March 2016 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))

18 February 2016 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];

16 February 2016 Cross-platform development bookmarks

15 February 2016 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

14 February 2016 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)

13 February 2016 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)

11 February 2016 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 | gojson -name=Repository

11 February 2016 Doh! undo last commit (Magit edition)

I previously noted how to undo your last git commit (ie. soft reset). Using Magit:

  1. M-x magit-log-current.
  2. Move point to prior revision.
  3. M-x magit-reset-soft (defaults to revision at point).

Or if you want a single function:

(require 'magit)

(defun ar/magit-soft-reset-head~1 ()
  "Soft reset current git repo to HEAD~1."
  (interactive)
  (magit-reset-soft "HEAD~1"))

06 February 2016 Redux bookmarks

06 February 2016 Javascript tips backlog

updated: 13 February 2016

06 February 2016 Emacs lisp tips backlog

updated: 27 December 2018

04 February 2016 Entering accents in Emacs

Via Irreal's Entering Accented Characters in Emacs, a reminder on how to enter accents using C-x 8. For example:

C-x 8 ' A -> Á

04 February 2016 Really delete iPhone photos

After deleting photos, go to:

Albums -> Recently Deleted -> Select -> Delete All

03 February 2016 Vancouver travel bookmarks

03 February 2016 Schnitzel recipe

Since eating at Fischers's, I've been inclined to make Schnitzel. This is my attempt.

Ingredients

  • Salt and ground black pepper.
  • All-purpose flour.
  • Eggs (beaten).
  • Bread crumbs (natural).
  • Oil.

Preparation

  • Flatten the pork/chicken/veal.
  • Season (salt and pepper).
  • Heat pan with a generous amount of oil.
  • Dip into flour -> egg -> bread crumbs.

Garnish

  • Anchovies.
  • Capers.

Photo

schnitzel.jpg

03 February 2016 Hot reloading with react and redux

By Robert Knight (@robknight_).

Checkout

03 February 2016 Converting Unix epoc time to human readable date

GNU

date -d @192179700
Tue Feb  3 07:15:00 GMT 1976

BSD/OS X

date -r 192179700
Tue Feb  3 07:15:00 GMT 1976

03 February 2016 Objective-C bookmarks

02 February 2016 Timesinking bookmarks

02 February 2016 Suspend and reattach processes

Via climagic's Suspend and reattach a process to screen:

longcmd ; [Ctrl-Z] ; bg ; disown ; screen ; reptyr $( pidof longcmd )

02 February 2016 Czech Republic travel bookmarks

02 February 2016 Meditation tips backlog

02 February 2016 Append jpegs in a video sequence

Via climagic's make slideshow from *.jpg:

for p in *.jpg; do
    ffmpeg -loop_input -f image2 -i $p -t 3 -r 4 -s 1080x720 -f avi - >> slides.avi;
done

02 February 2016 Regular expressions bookmarks

02 February 2016 Typescript bookmarks

02 February 2016 Hiding HTML elements

Hide with display:none (exclude from layout) and visibility:hidden (include in layout).

01 February 2016 Echo Emacs keybiding from function name

Picked up via Emacs Redux's Display the Keybinding for a Command With Substitute-command-keys, with my own example:

(message (substitute-command-keys "Press \\[ar/ox-html-export] to export org file"))
Press <f6> to export org file

01 February 2016 Emacs dired for batch byte compilation

Recently updated org-mode and started seeing an invalid function error:

Error (use-package): ob :config: Invalid function: org-babel-header-args-safe-fn

Just learned dired enables you to mark files and byte compile via M-x dired-do-byte-compile.

29 January 2016 Serializing to JSON on iOS

NSDictionary *dictionary = @{
  @"key1" : @"val1\n",
  @"key2" : @"val2\n",
  @"key3" : @"val3\n",
  @"key4" : @"val4\n",
  @"key5" : @"val5\n",
  @"key6" : @"val6\n",
};
NSError *error;
NSData *jsonData =
    [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
if (error) {
  // noooooooooo!
}
NSString *json =
    [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];

25 January 2016 Fischer's London: yes, but…

Yes

Step into a Viennese blast from the past. Beautiful setting and pleasant vibe. Ordered a dirty martini on the rocks, a bottle of Merlot, Käsespätzle (with bacon), and Wiener Schnitzel (with anchovy/capers/egg). All very tasty.

But…

Surprisingly, desserts (Topfenstrudel, Berggasse and coffee) were nothing spectacular. Also not a cheap eat (£50 per person).

Photos

fischers-03.JPG fischers-04.JPG fischers-05.JPG fischers-06.JPG fischers-07.JPG fischers-08.JPG fischers-09.JPG fischers-10.JPG fischers-11.JPG fischers-12.JPG

25 January 2016 Polar travel bookmarks

24 January 2016 Sweden travel bookmarks

20 January 2016 Handwriting bookmarks

20 January 2016 Chocolate fondant recipe

My girlfriend recently made a delicious chocolate fondant. Saving the The Guardian's recipe:

Ingredients (2 servings)

  • 60g unsalted butter, cut into dice, plus extra to grease
  • 1 tbsp cocoa powder
  • 60g dark chocolate, broken into pieces
  • 1 egg and 1 egg yolk
  • 60g caster sugar
  • 1 tbsp plain flour

Preparation

  1. Pre-heat the oven to 200C if cooking immediately, and put a baking tray on the middle shelf. Butter the inside of 2 small ramekins or pudding moulds, and then put the cocoa in one and turn it to coat the inside, holding it over the second mould to catch any that escapes. Do the same with the other mould.
  2. Put the butter and chocolate into a heatproof bowl set over, but not touching, a pan of simmering water and stir occasionally until melted. Allow to cool slightly.
  3. Vigorously whisk together the egg, yolk, sugar and a pinch of salt until pale and fluffy. Gently fold in the melted chocolate and butter, and then the flour. Spoon into the prepared moulds, stopping just shy of the top – at this point the mixture can be refrigerated until needed, or even frozen, as the puddings will not wait around once cooked.
  4. Put on to a hot baking tray and cook for 12 minutes (14 if from cold, 16 if frozen) until the tops are set and coming away from the sides of the moulds. Leave to rest for 30 seconds and then serve in the ramekins or turn out on to plates if you're feeling confident – they're great with clotted cream or plain ice cream.

20 January 2016 Parenting bookmarks

updated: 03 July 2021

19 January 2016 Ippudo London: yes, but…

Yes

Central St. Giles location. Ordered a Kirin Ichiban beer and a Spicy Tonkotsu with a seasoned boiled egg. Awesome medium-spice broth, tasty egg and firm noodles. Got additional noodles for £1.50.

But…

The space feels soulless. Think generic, chain, Pizza Express…

Photos

ipuddo_00.JPG ipuddo_01.JPG ipuddo_02.JPG ipuddo_03.JPG ipuddo_04.JPG ipuddo_05.JPG ipuddo_06.JPG ipuddo_07.JPG

19 January 2016 Added Emacs zone-rainbow

kawabata's zone-rainbow popped up on melpa today. Added to zone-programs. Just because :)

(use-package zone-rainbow :ensure t
  :after zone
  :config
  (setq zone-programs (vconcat [zone-rainbow] zone-programs)))

zone-rainbow.gif

19 January 2016 Safari's Web Inspector keyboard shortcuts

Via WebKit's blog, Web Inspector Keyboard Shortcuts:

  • ⌃⌘Y or ⌘\ continue.
  • F8 or ⇧⌘; step out.
  • F7 or ⌘; step in.
  • F6 or ⌘’ step over.

14 January 2016 Copenhagen travel bookmarks

updated: 01 September 2019

12 January 2016 Import UIKit for simpler debugging

I bookmarked An @import-ant Change in Xcode and immediately forgot about it. The gist is to import UIKit to simplify inspecting objects during an lldb session:

(lldb) expr @import UIKit

Shorten typing by creating aliases in ~/.lldbinit:

command alias uikit expr @import UIKit
command alias foundation expr @import Foundation

12 January 2016 iOS development tips backlog

updated: 12 January 2016

11 January 2016 Basic Emacs keybindings on Linux desktop

Miss C-a, C-e in your browser and other Linux apps? You can enable the GTK Emacs key theme:

$ gsettings set org.gnome.desktop.interface gtk-key-theme "Emacs"

or if on Cinnamon:

$ gsettings set org.cinnamon.desktop.interface gtk-key-theme Emacs

If your desktop environment is not running gnome-settings-daemon, start it with:

$ gnome-settings-daemon

More at Emacs Keybindings in Chrome Without Gnome and How to get Emacs key bindings in Ubuntu.

08 January 2016 Emacs Objective-C completion with Irony

Install libclang on Mac

brew install llvm --with-clang

Configure Emacs

(use-package irony :ensure t
  :config
  (add-hook 'objc-mode-hook 'irony-mode)
  (add-hook 'irony-mode-hook 'irony-cdb-autosetup-compile-options))

(use-package company-irony :ensure t
  :config
  (add-hook  'objc-mode-hook (lambda ()
                               (setq-local company-backends '((company-irony)))))
  (add-hook 'irony-mode-hook 'company-irony-setup-begin-commands))

install irony server

Run:

M-x irony-install-server

NOTE: Needs libclang: Install with "brew install llvm –with-clang" By default, irony-install-server did not find libclang on Mac OS. irony-install-server invokes cmake for you. Work around by adding:

-DCMAKE_PREFIX_PATH=/Users/your-user-name/homebrew/opt/llvm

For example:

cmake -DCMAKE_PREFIX_PATH=/Users/your-user-name/homebrew/opt/llvm -DCMAKE_INSTALL_PREFIX\=/Users/your-user-name/.emacs.d/irony/ /Users/your-user-name/.emacs.d/elpa/irony-20160106.1223/server && cmake --build . --use-stderr --config Release --target install

Compilation database

Install xctool

brew install xctool

Generate compilation database

xctool -sdk iphonesimulator -arch x86_64 -scheme SomeScheme -reporter pretty -reporter json-compilation-database:compile_commands.json clean build

Set Irony's database path

M-x irony-cdb-json-add-compile-commands-path

07 January 2016 Finland travel bookmarks

updated: 13 October 2018

07 January 2016 Northern lights travel bookmarks

updated: 07 January 2016

06 January 2016 Mexico travel bookmarks

03 January 2016 Emacs highlight-symbol-mode

Been a fan of highlight-thing-mode. It automatically highlights all instances of symbol at point. Today, I gave highlight-symbol a try. Similar concept, but also adds the ability to jump to next/previous instances of symbol at point.

(use-package highlight-symbol :ensure t
  :config
  (set-face-attribute 'highlight-symbol-face nil
                      :background "default"
                      :foreground "#FA009A")
  (setq highlight-symbol-idle-delay 0)
  (setq highlight-symbol-on-navigation-p t)
  (add-hook 'prog-mode-hook #'highlight-symbol-mode)
  (add-hook 'prog-mode-hook #'highlight-symbol-nav-mode))

highlight-symbol.gif

03 January 2016 Gandhi's ever-contemporary wisdom

Anger

"I do get angry, but I feel angry with myself for it. Full conquest of anger is possible only through self-realization. We should love even those who have the worst opinion of us. This is ahimsa, the rest is only ignorance."

Bad handwriting

"I am now of opinion that children should first be taught the art of drawing before learning how to write. Let the child learn his letters by observation as he does different objectives, such as flowers, birds, etc., and let him learn handwriting only after he has learned to draw objects."

Conduct of the Ashram

"Service without humility is selfishness and egotism."

Eating

"There is a great deal of truth in the saying that man becomes what he eats. The grosser the food, the grosser the body."

Heart

"There are chords in every human heart. If we only know how to strike the right chord, we bring out the music."

Moral law

The law of truth and love.

Renouncing or forgoing

Nishkulanand sings: "Renunciation of objects, without the renunciation of desires, is short-lived, however hard you may try."

Silence

"Man spoils matters much more by speech than by silence."

Time

"Every minute that runs to waste never returns. Yet, knowing this, how much time do we waste?"

The palate

"Turn to the birds and beasts, and what do you find? They never eat merely to please the palate, they never go on eating till their inside is full to overflowing. And yet, we regard ourselves as superior to the animal creation!"

Vow of Swadeshi

"The person who has taken the vow of swadeshi will never use articles which conceivably involve violation of truth in their manufature or on the part of their manufacturers."

02 January 2016 Functional programming bookmarks

02 January 2016 9 Productivity tips

From HBR's 9 Productivity Tips from People Who Write About Productivity:

  1. Block time away from reactive tasks (email).
  2. Business = wasted energy.
  3. Exercise, sleep, and 90 minute work bursts.
  4. Incomplete tasks prompt healthy thinking out of context.
  5. Time off or stepping back is invaluable.
  6. Genuinely help were most successful/enjoyable.
  7. Plan for saying no while highlighting priority and seeking feedback.
  8. Measure important behavior change.
  9. Make time now (automate, simplify, etc.).

01 January 2016 First meal of 2016

Pancakes

  • 1 teaspoon of salt.
  • 1.5 cups of milk.
  • 2 cups of flour.
  • 2 eggs.
  • 2 tablespoons sugar.
  • 4 tablespoons of melted butter.
  • 6 teaspoons of baking powder.

Makes 10/11 pancakes.

shortstack.jpg

01 January 2016 Last meal of 2015

For our last meal of 2015, I contributed dal and rotis. This is my first attempt at making either one of these. Both recipes based on Anupy Singla's Indian for Everyone.

Dal Makhani (Buttered black lentils)

dal-grid.png dal.jpg

Roti-Chapati-Phulka

roti-grid.png roti.jpg

30 December 2015 Find in $PATH with type and which

I typically use which to figure out the first binary found in $PATH:

which -a emacsclient
/Users/user/homebrew/bin/emacsclient
/usr/bin/emacsclient

I always forget about type though:

type -a emacsclient
emacsclient is /Users/user/homebrew/bin/emacsclient
emacsclient is /usr/bin/emacsclient

30 December 2015 npm basics

Global vs local package installation location

{prefix}/lib/node_modules

vs

path/to/project/node_modules

View npm config

npm config list
; cli configs
user-agent = "npm/2.14.2 node/v4.0.0 darwin x64"

; node bin location = /Users/user/.nvm/versions/node/v4.0.0/bin/node
; cwd = /Users/user/stuff/active/blog
; HOME = /Users/user
; 'npm config ls -l' to show all defaults.

Get config value

npm config get prefix
/Users/user/.nvm/versions/node/v4.0.0

Set config value

npm config set prefix=$HOME/some/location

Install package globally

node install --global <package-name>

or

node install -g <package-name>

List global packages

npm list --global

You can also use –depth=0 to make less verbose.

/Users/user/.nvm/versions/node/v4.0.0/lib
├─┬ babel-eslint@4.1.3
│ ├── acorn-to-esprima@1.0.4
│ ├─┬ babel-core@5.8.25
│ │ ├── babel-plugin-constant-folding@1.0.1
│ │ ├── babel-plugin-dead-code-elimination@1.0.2
...

Install local package

npm install <package-name> --save

–save will add <package-name> dependency to your package.json.

package.json

Uninstall package

npm uninstall <package-name>

Install package at version

npm install <package-name>@1.7.0

Search packages

npm search linter

Online documentation

Online documentation is great so far. More at docs.npmjs.com.

25 December 2015 Clojure bookmarks

21 December 2015 Mac OS X tips backlog

updated: 06 March 2016

20 December 2015 Search bash history with Emacs helm

Following up from changing CWD with helm projectile, here's a way to search your bash history with helm:

helm-bash-history.gif

(defun ar/helm-helm (title candidates on-select-function)
  "Helm with TITLE CANDIDATES and ON-SELECT-FUNCTION."
  (helm :sources `((name . ,title)
                   (candidates . ,candidates)
                   (action . ,on-select-function))
        :buffer "*helm-exec*"
        :candidate-number-limit 10000))

(defun ar/shell-send-command (command)
  "Send COMMAND to shell mode."
  (assert (string-equal mode-name "Shell") nil "Not in Shell mode")
  (goto-char (point-max))
  (comint-kill-input)
  (insert command)
  (comint-send-input))

(defun ar/helm-shell-search-history ()
  "Narrow down bash history with helm."
  (interactive)
  (assert (string-equal mode-name "Shell") nil "Not in Shell mode")
  (ar/helm-helm "bash history"
                (with-temp-buffer
                  (insert-file-contents "~/.bash_history")
                  (reverse
                   (delete-dups
                    (split-string (buffer-string) "\n"))))
                #'ar/shell-send-command))

Bonus: Replace existing M-r binding to use ar/helm-shell-search-history.

(bind-key "M-r" #'ar/helm-shell-search-history shell-mode-map)

19 December 2015 Medicine bookmarks

19 December 2015 View DICOM files from your X-ray

Got a CD with my chest X-ray from the hospital. Was expecting a pdf or an image of sorts, but the CD content was rather different. For starters, it was targeted at Windows users (AUTORUN.INF, MediaViewerLauncher.EXE and a bunch of DLLs):

$ find . -exec file --mime-type '{}' \;

./AUTORUN.INF: text/plain
./DICOMDIR: application/dicom
./MediaViewerLauncher.EXE: application/octet-stream
...
./Libraries/BASEPRINTER.DLL: application/octet-stream
./Libraries/CDDATABURNER.DLL: application/octet-stream
./Libraries/COM.DLL: application/octet-stream
...
./Libraries/ACE.DLL: application/octet-stream
./Libraries/ACE_SSL.DLL: application/octet-stream
./Libraries/ATL90.DLL: application/octet-stream
...
./DICOM/PAT_0000: application/x-directory
./DICOM/PAT_0000/STD_0000/SER_0000/OBJ_0001/IM_0001: application/dicom
./DICOM/PAT_0000/STD_0000/SER_0001/OBJ_0001/ED_0001: application/dicom
./DICOM/PAT_0000/STD_0000/SER_0002/OBJ_0001/ED_0001: application/dicom
./Worklist/ClinicalInfo/067eccde-b299-e511-9114-005056ad3afe.mht: text/html
./Worklist/Report/067eccde-b299-e511-9114-005056ad3afe.mht: text/html
./Worklist/Worklist.wl: application/octet-stream

I'm on a Mac, so most of these files were not useful to me. The more interesting files were IM_0001 and ED_0001 with "application/dicom" MIME type. DICOM files stand for Digital Imaging and Communications in Medicine. How to view these on a Mac? OsiriX viewer is an option. OsiriX, though on the heavy side (100.7MB download), it rendered the X-ray successfully.

2015-12-03-osirix-x-ray-small.png

Unsurprisingly, ImageMagick's convert utility also handles DICOM files. Converting to PNG worked well.

$ convert ./DICOM/PAT_0000/STD_0000/SER_0001/OBJ_0001/ED_0001 ED_0001.png

2015-12-03-imagemagick-x-ray-small.png

DICOM files also hold patient's metadata and optional reports. The file format is well known. OsiriX gives you access to it, but a few lines of python can also extract it for you. First install the pydicom package:

$ sudo pip install pydicom

Running the python interpreter is enough to peak at the metadata:

>>> import dicom
>>> ds = dicom.read_file("./DICOM/PAT_0000/STD_0000/SER_0000/OBJ_0001/IM_0001")
>>> ds
(0008, 0000) Group Length                        UL: 400
(0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
(0008, 0016) SOP Class UID                       UI: Computed Radiography Image Storage
(0008, 0020) Study Date                          DA: '20151203'
(0008, 0021) Series Date                         DA: '20151203'
(0008, 0023) Content Date                        DA: '20151203'
(0008, 0030) Study Time                          TM: '120519.000000'
(0008, 0031) Series Time                         TM: '120520.000000'
(0008, 0033) Content Time                        TM: '120643.000000'
(0008, 0060) Modality                            CS: 'CR'
(0008, 0070) Manufacturer                        LO: 'Canon Inc.'
...

There were other DICOM files with a report:

>>> import dicom
>>> ds = dicom.read_file("./DICOM/PAT_0000/STD_0000/SER_0001/OBJ_0001/ED_0001")
>>> ds
(0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
(0008, 0016) SOP Class UID                       UI: Encapsulated PDF Storage
...
(0042, 0012) MIME Type of Encapsulated Document  LO: 'application/pdf'

DCMTK is another alternative tool to extract DICOM metadata. The source is available and can be built:

$ tar xf dcmtk-3.6.0.tar.gz
$ cd dcmtk-3.6.0
$ cmake .
$ make

Or installed via homebrew:

$ brew install dcmtk

DCMTK includes dcmdump. You can use it to dump DICOM files:

$ dcmdata/apps/dcmdump DICOM/PAT_0000/STD_0000/SER_0000/OBJ_0001/IM_0001
# Dicom-File-Format

# Dicom-Meta-Information-Header
# Used TransferSyntax: Little Endian Explicit
(0002,0000) UL 192                                      #   4, 1 FileMetaInformationGroupLength
(0002,0001) OB 01\00                                    #   2, 1 FileMetaInformationVersion
(0002,0002) UI =ComputedRadiographyImageStorage         #  26, 1 MediaStorageSOPClassUID
(0002,0003) UI [1.2.392.200046.100.2.1.1.42667.20151203120519.1.1.1] #  52, 1 MediaStorageSOPInstanceUID
(0002,0010) UI =LittleEndianExplicit                    #  20, 1 TransferSyntaxUID
(0002,0012) UI [1.3.46.670589.42.1.4.4.5]               #  24, 1 ImplementationClassUID
(0002,0013) SH [PhilipsISPACS445]                       #  16, 1 ImplementationVersionName
...

Of interest, David Clunie's Medical Image Format Site.

18 December 2015 Tip: GOOGLETRANSLATE your Spreadsheet

Examples from reference:

=GOOGLETRANSLATE("Hello World\n","en\n","es")
=GOOGLETRANSLATE(A2,B2,C2)
=GOOGLETRANSLATE(A2)

18 December 2015 Organize your data with camlistore

Checking out camlistore to organize all sorts of data. Scaleway enables you to deploy camlistore servers.

17 December 2015 Maps dev bookmarks

17 December 2015 Use ImageMagick to convert image to grayscale

Another ImageMagick one-liner I'll likely forget.

mogrify -type Grayscale image.png

14 December 2015 Drill down Emacs dired with dired-subtree

JCS, from Irreal, recently highlighted fuco's dired-hacks. dired-subtree is super handy for drilling subdirectories down. Bound <tab> and <backtab> to toggle and cycle subtrees.

(use-package dired-subtree :ensure t
  :after dired
  :config
  (bind-key "<tab>" #'dired-subtree-toggle dired-mode-map)
  (bind-key "<backtab>" #'dired-subtree-cycle dired-mode-map))

dired-subtree.gif

14 December 2015 GPG (GnuPG) examples

Generate key

gpg --full-generate-key

Export private key

gpg --export-secret-key -a <keyid> > <private.asc>

Import key

gpg --import < <private.asc>

Delete public key

gpg --delete-keys <keyid>

Delete private key

gpg --delete-secret-keys <keyid>

Edit key

gpg --edit-key <keyid>
gpg> uid (lists IDs)
gpg> uid 2 (marks ID)
gpg> deluid (deletes marked ID)
Really remove this user ID? (y/N) y

Change passphrase of the secret key

gpg --edit-key Your-Key-ID-Here
gpg> passwd
gpg> save

14 December 2015 CSS bookmarks

12 December 2015 Resume partial downloads with ssh and rsync

rsync --rsync-path=/usr/local/bin/rsync \
      --partial \
      --progress \
      --rsh=ssh \
      john@host:/path/to/file \
      path/to/partial/file

12 December 2015 Emacs text faces

  • Text faces = Text styles.
  • Face attributes: font, height, weight, slant, foreground/background color, and underlining or overlining.
  • Font lock mode automatically assigns faces to text.
  • M-x list-faces-display: Shows faces defined.
  • M-x helm-colors: Also handy.
  • Unspecified attributes are taken from 'default' face.

08 December 2015 Preview HTML pages on github

07 December 2015 Flutter setup

Based on Getting Started with Flutter.

$ curl -O https://storage.googleapis.com/dart-archive/channels/stable/release/1.13.0/sdk/dartsdk-macos-x64-release.zip
$ unzip dartsdk-macos-x64-release.zip
$ export PATH=`pwd`/dart-sdk/bin:$PATH

Verify with:

$ pub --version

07 December 2015 Playing with Dart's analysis server

Dart SDK ships with an analysis server. Very handy if you'd like to write a completion plugin for your favorite editor. The API is well documented. Of interest, there's LocalDartServer.java, part of dartedit.

$ dart path/to/bin/snapshots/analysis_server.dart.snapshot  --sdk=path/to/dart-sdk

NOTE: The server reads requests from standard input. Either escape or execute the following as one-liner json requests.

{
  "id": "1\n",
  "method": "analysis.setAnalysisRoots\n",
  "params": {
    "included": [
      "path/to/your/dart/project"
    ],
    "excluded": []
  }
}
{
  "id": "3\n",
  "method": "completion.getSuggestions\n",
  "params": {
    "file": "path/to/some/file.dart\n",
    "offset": 673
  }
}

07 December 2015 Dart bookmarks

06 December 2015 Flutter bookmarks

06 December 2015 Swift bookmarks

updated: 11 September 2022

30 November 2015 Installing Emacs spaceline

Gave Spaceline a try. Spacemacs's powerline theme. Setup was super simple (Thanks Eivind Fonn and Sylvain Benner):

(use-package spaceline :ensure t
  :config
  (use-package spaceline-config
    :config
    (spaceline-toggle-minor-modes-off)
    (spaceline-toggle-buffer-encoding-off)
    (spaceline-toggle-buffer-encoding-abbrev-off)
    (setq powerline-default-separator 'rounded)
    (setq spaceline-highlight-face-func 'spaceline-highlight-face-evil-state)
    (spaceline-define-segment line-column
      "The current line and column numbers."
      "l:%l c:%2c")
    (spaceline-define-segment time
      "The current time."
      (format-time-string "%H:%M"))
    (spaceline-define-segment date
      "The current date."
      (format-time-string "%h %d"))
    (spaceline-toggle-time-on)
    (spaceline-emacs-theme 'date 'time))

spaceline.png

29 November 2015 package.el incomprehensible buffer

Came across "incomprehensible buffer" error in package.el. Workaround patch:

--- a/lisp/emacs-lisp/package.el
+++ b/lisp/emacs-lisp/package.el
@@ -1161,6 +1161,7 @@ package--with-work-buffer
(let* ((url (concat ,url-1 ,file))
       (callback (lambda (status)
                   (let ((b (current-buffer)))
+                    (goto-char (point-min))
                     (unwind-protect (wrap-errors
                                      (when-let ((er (plist-get
                                                      status :error)))
                                        (error "Error retrieving: %s %S" url er))

29 November 2015 Leading bookmarks

29 November 2015 Online reading backlog

updated: 21 May 2016

28 November 2015 Travel lifestyle bookmarks

26 November 2015 SQL bookmarks

26 November 2015 Unix/Linux tools bookmarks

updated: 23 November 2021

26 November 2015 Couchbase React Native bookmarks

26 November 2015 Installing Emacs 25 devel on Mac OS X

Stable

brew update
brew install emacs --HEAD --use-git-head --with-cocoa --with-srgb --with-gnutls
brew linkapps emacs

Development

  brew update
  brew install emacs --devel --with-cocoa --with-srgb --with-gnutls
  brew linkapps emacs
then

Had problems loading seq. Removed byte-compiled packages:

$ find ~/.emacs.d/elpa -iname *.elc -exec rm '{}' \;

25 November 2015 Diagram tools bookmarks

24 November 2015 Licensing bookmarks

23 November 2015 Synology bookmarks

22 November 2015 Backup bookmarks

22 November 2015 Making hummus

Made hummus, based on Delicious Istanbul's 5 Secrets to Perfect Hummus (wayback machine) post.

  • 160 g dry chickpeas.
  • 4 cloves garlic minced.
  • 1 tsp fine sea salt.
  • 1 1/2 tbsp lemon juice.
  • 1/4 tsp ground cumin.
  • 6 tbsp tahini paste.
  • 2/3 cup cooking water.
  • Extra virgin olive oil, for serving.
  • Red pepper flakes, for serving.
  • Zahter mixture, for serving.
  • Soak chickpeas overnight.
  • Discard water and rinse chickpeas.
  • Cook in low heat (about 5 cups water) for 1.5 hours for until soft (but keeping shape.) Check if can be mashed with thumnb.
  • Save cooking water.
  • Peal chickpeas (optional).
  • Blend ingredients until silky paste. Taste and add lemon/salt/cooking water.

Keeps in fridge for 3-4 days. Freeze otherwise.

hummus-01.jpg hummus-02.jpg hummus-03.jpg hummus-04.jpg hummus-05.jpg hummus-06.jpg hummus-07.jpg hummus-08.jpg hummus-09.jpg

22 November 2015 Nara travel bookmarks

21 November 2015 Kubernetes bookmarks

21 November 2015 Docker bookmarks

21 November 2015 Angular bookmarks

21 November 2015 Mac OS bookmarks

21 November 2015 easy_install->pip->conda

Spotted Conda package manager. It handles python installations, in addition to package management. There's also a package index provided by Binstar. Installed Miniconda, the bare bones Conda environment.

Can't find a python package in Binstar? Here's a post on Using PyPi Packages with Conda. If that fails, you can try pip from your Conda python environment.

16 November 2015 Traditional music bookmarks

04 November 2015 Recover from an unresponsive Emacs

Wilfred Hughes has a handy tip to bail you out of a hung Emacs instance:

pkill -SIGUSR2 emacs

ps. Not had a chance to try it, but next time it happens…

25 October 2015 Training for under 50 min 10k run

Not much training time for an under 50 minute 10k run, but here's an attempt (based on time-to-run's sub-50):

Mon Tue Wed Thu Fri Sat Sun
Oct 26 Oct 27 Oct 28 Oct 29 Oct 30 Oct 31 Nov 1
    60 min 30 min 2k @ 4.55/k rest 105 min
        2 min rest    
      (repeat x 3)    
Nov 2 Nov 3 Nov 4 Nov 5 Nov 6 Nov 7 Nov 8
30 min 30 min 1k @ 4.50/k 30 min 30 min rest 5k @ 4.55/k
    90 sec rest        
    (repeat x 5)        
Nov 9 Nov 10 Nov 11 Nov 12 Nov 13 Nov 14 Nov 15
10k easy 30 min 1k @ 4.55/k 30 min 30 min rest race day
    1 min easy        
    (repeat x 3)        

25 October 2015 Reading a running training plan

A sample from Kona Part 2's comments:

2.5 w/u to 4x(1.25@11.5 w/0.25R@7) to 3x(3.75@10.5 w/0.5R@7) to 2.5 c/d.

Is read from left to right as:

2.5 mile warm up to four times through 1.25 miles at 11.5 miles per hour with 0.25 miles recovery at 7 miles per hour to three times through 3.75 miles at 10.5 miles per hour with 0.5 miles recovery at 7 miles per hour to 2.5 miles cool down.

23 October 2015 Find binary in PATH using python

import distutils.spawn
print distutils.spawn.find_executable('git')
/usr/bin/git

22 October 2015 Indonesia travel bookmarks

22 October 2015 Malaysia travel bookmarks

22 October 2015 Mongolia travel bookmarks

22 October 2015 Running bookmarks

22 October 2015 Media player bookmarks

16 October 2015 UX toolbox bookmarks

08 October 2015 Change Emacs shell's CWD with helm projectile

If using Emacs shell and helm projectile, you can wire these up to quickly change your current working directory.

change-cwd-with-helm-projectile.gif

(require 'helm-projectile)

(defun ar/shell-cd (dir-path)
"Like shell-pop--cd-to-cwd-shell, but without recentering."
  (unless (string-equal mode-name "Shell")
    (error "Not in Shell mode"))
  (message mode-name)
  (goto-char (point-max))
  (comint-kill-input)
  (insert (concat "cd " (shell-quote-argument dir-path)))
  (let ((comint-process-echoes t))
    (comint-send-input)))

(defun ar/helm-projectile-shell-cd ()
  "Change shell current working directory using helm projectile."
  (interactive)
  (unless (string-equal mode-name "Shell")
    (error "Not in Shell mode"))
  (let ((helm-dir-source (copy-tree  helm-source-projectile-directories-list)))
    (add-to-list 'helm-dir-source '(action . ar/shell-cd))
    (add-to-list 'helm-dir-source '(keymap . nil))
    (add-to-list 'helm-dir-source '(header-line . "cd to directory..."))
    (helm :sources helm-dir-source
          :buffer "*helm-dirs*"
          :candidate-number-limit 10000)))

07 October 2015 Thermostat reset on Bosch WKD28350GB

My Bosch washer/dryer (WKD28350GB) stopped drying recently. Resetting the dryer's thermostat red breaker did the trick.

WKD28350GB-01.jpg WKD28350GB-02.jpg WKD28350GB-03.jpg WKD28350GB-04.jpg

Edit: Similar post here.

05 October 2015 Javascript fetch node sample

Playing with node and fetch:

// Requisite: npm install node-fetch --save
// Save to fetch-demo.js
// Run: node fetch-demo.js

var fetch = require('node-fetch');

fetch("http://xenodium.com/data/javascript-fetch-node-sample/message.json\n", {
  method: 'GET',
  timeout: 5000
}).then(function(response) {
  return response.json();
}).then(function(response) {
  console.log('subject: ' + response.subject);
  console.log('body: ' + response.body);
}).catch(function(reason) {
  console.log(reason);
});

01 October 2015 Extract dominant colors in images

There's a handy HN post pointing to Javier López's Using imagemagick, awk and kmeans to find dominant colors in images. A comment also highlights color-extract, written in Go.

28 September 2015 Find a word with regex and WordNet

Recently wanted to come up with a random keyword. Querying WordNet and a regular expression did the job.

Installed WordNet on Mac:

$ brew install wordnet

Want a word ending in "esome"?

$ wn esome -grepn -grepv -grepa -grepr | egrep -o -e "\w*esome\b" | sort | uniq

adventuresome
awesome
blithesome
bunglesome
cuddlesome
esome
fivesome
gruesome
lithesome
lonesome
lovesome
meddlesome
mettlesome
nettlesome
threesome
tiresome
torturesome
troublesome
unwholesome
venturesome
wholesome

26 September 2015 Soundcloud's Go best practices (GopherCon 2014)

Having watched the video, some takeaways:

Single GOPATH

$GOPATH/src/github.com/soundcloud/foo

Repo structure

github.com/soundcloud/whatever

  • README.md
  • Makefile
  • main.go
  • support.go
  • foo
    • foo.go
    • bar.go
  • whatever-server
    • main.go
  • wharever-worker
    • main.go

Formatting and style

Use gofmt.

Google's codereview guidelines.

Avoid named return parameters.

Avoid make and new (unless you know sizes).

Use struct{} for sentinel values: sets, signal chans.

  • Conveys no information in it this part.
  • Instead of empty interface.
  • instead of boolean.

Break long lines at parameters

  • No need to compact.
  • Keep trailing coma in last argument.

Flags

func main() {
  var (
    foo = flags.String("foo\n", "doch\n", "...")
    bar = flat.Int("bar\n", 34, "...")
  )
  flag.Parse()
  // ...
}

Logging

  • package log
  • Telemetry
  • Push model (gets expensive over time)
    • Graphite
    • Statsd
    • AirBrake
  • Pull model (chosen)
    • expvar
    • Prometheus

Testing

  • package testing
    • Unit tests
    • reflect.DeepEqual
  • Integration
    • Use flags for starting services
    • // +build integration

Code validation

  • On Save
    • Go fmt
    • Go import (go fmt++)
  • On Build
    • Go vet
    • Golint
    • Go test
  • On Deploy
    • go test -tags=integration
  • GoCov?

Dependency management

  • Unimportant projects
    • go get -d (and hope)
  • Important
    • VENDOR (ie. copy into your repo)
      • Git submodules (no!).
      • Git subtrees (seem OK).
      • Tool (godep?).
      • Build
      • For binaries (use _vendor subdir)

23 September 2015 Sync pip with Mac OS updates

My pip installation recently broke after a Mac OS update.

$ pip
Traceback (most recent call last):
  File "/usr/local/bin/pip\n", line 5, in <module>
    from pkg_resources import load_entry_point
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py\n", line 2793, in <module>
    working_set.require(__requires__)
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py\n", line 673, in require
    needed = self.resolve(parse_requirements(requirements))
  File "/System/Library/Frameworks/Python.framework/Versions/2.7/Extras/lib/python/pkg_resources.py\n", line 576, in resolve
    raise DistributionNotFound(req)
pkg_resources.DistributionNotFound: pip==1.1

Updating my pip installation fixes the break:

$ sudo easy_install -U pip

20 September 2015 Chinatown treats review

Recommended? yep

There's a corner in Chinatown hosting some truly superb treats. If you get caught in the rush between Newport court and Newport place, you'd likely fail to notice some the awesome street food stands.

chinatown.jpg newport-court.jpg

Chilly squid

I've walked past this place many times and never noticed it. They serve a handful of items, but the grilled chilly squid skewers caught my attention. They're grilled, brushed with chilly sauce and finished with sprinkled sesame and cumin seeds. Super tasty.

squid-stand.jpg

chilly-squid-1.jpg chilly-squid-2.jpg

Pancake + Crisp + Egg + Hot chillies = Jiān Bǐng 煎餅

I first had these delicious breakfast savory pancakes at a Beijing street food stall. Never expected to randomly find Jiān Bǐng in London. It's a crepe with an additional egg spread, hoisin sauce, chilly sauce, hot chillies, topped with spring onions and coriander, all wrapping a wonderfully crispy bread cracker. And.. it's awesome.

pancake-3.jpg

Tai Yaki

Chinatown Bakery is hard to miss. Pedestrian traffic slows down as we all fall under the spell of the Tai Yaki machine. This wonderful assembly line produces fish-shaped sweet waffles filled with custard. They are the perfect dessert after some savory street snacks. You can get a bag of 4 for £2.

fish-1.jpg fish-4.jpg

All near each other

All these delights are within a stone's throw away from each other.

pano.png

Useful?

Was this post useful to you? do reply!

Better suggestion?

London is full of overhyped, gimmicky, and unnecessarily expensive restaurants. Very few deliver truly awesome food (even those expensive ones). Got suggestions? I'd love to hear from you @xenodium.

18 September 2015 React bookmarks

updated: 12 July 2018

16 September 2015 Chinese rice vinegar

Note to self to buy Gold Plum Chinkiang Vinegar. Awesome with dim sum.

chinkiang-vinegar.jpg

13 September 2015 Use ImageMagick to batch-resize images

Using percentage:

$ mogrify -resize 10% *.png

Using dimensions:

$ mogrify -resize 120x120 *.png

Lots of other alternatives from ImageMagick's documentation:

-resize scale%
-resize scale-x%xscale-y%
-resize width
-resize xheight
-resize widthxheight
-resize widthxheight^
-resize widthxheight!
-resize widthxheight>
-resize widthxheight<
-resize area@

Fix image aspect ratios for Instagram:

$ mogrify -resize 1080x1350 -gravity center -extent 1080 *.jpg

08 September 2015 Lucky 7 review

Recommended? yep

Lucky 7 is a small nostalgic American diner on Westbourne Park road. I like the vibe, the space, and the unpretentious waiting staff. I go to Lucky 7 often enough, originally for the buttermilk pancakes, but the list of favorites on the menu keeps growing.

lucky-7-menu-board.jpg lucky-7-staff.jpg

Smileys by w.dyer.

Buttermilk Banana pancakes

These are my favorite pancakes in London by far. Banana buttermilk pancakes and a few free coffee refills usually sort me out until dinner time. Add a side of bacon if extra hungry. You probably don't need it though.

lucky-7-coffee.jpg lucky-7-pancakes.jpg

Reuben sandwich

The reuben has been on Lucky 7's specials menu for months now. Not had many of these in London, but compared to The Brass Rail's, this reuben was a clear winner. The sandwich is huge and comes with fries. My girlfriend and I struggled to finish one between the two of us.

lucky-7-reuben-fries.jpg

lucky-7-reuben-cross-section.jpg

Vanilla milkshake (add malt!)

This milkshake hits the spot every time, but it's filling. You almost have to decide between the shake and an actual meal. If you must have it, add malt. Sorry, no picture.

Huevos Rancheros

This is a breakfast dish I can equally make (better?) at home, but Lucky 7 wins hands down every time I'm feeling particularly lazy. Sorry, no picture.

Useful?

Was this post useful to you? do reply!

Better suggestion?

London is full of overhyped, gimmicky, and unnecessarily expensive restaurants. Very few deliver truly awesome food (even those expensive ones). Got suggestions? I'd love to hear from you @xenodium.

02 September 2015 Sierra Leone travel bookmarks

02 September 2015 London travel bookmarks

23 August 2015 Use ImageMagick to auto-orient images

Recently needed to rotate images based on EXIF metadata. ImageMagick to the rescue:

$ for i in *.png; do convert -auto-orient "$i" "$i"; done

23 August 2015 Bengali Macher Jhol

My friend Sakhya brought me the wonderful Cookbook of Regional Cuisines of India. After improvisations and substitutions, here's my attempt at making Bengali Machcher Jhol:

bengali-machcher-jhol-00.png bengali-machcher-jhol-01.png bengali-machcher-jhol-02.png bengali-machcher-jhol-03.png bengali-machcher-jhol-04.png bengali-machcher-jhol-05.png

22 August 2015 New habits for 2015

  • 20 min morning meditations.
  • A better way to tie your shoes.
  • Cold showers (all of them!).
  • Keys, wallet, phone, badge, and headphones live together.
  • Listen to audio books.
  • Morning runs.
  • Nightly flossing.

22 August 2015 Meditation retreats bookmarks

08 July 2018 Human memory bookmarks

22 August 2015 Mindfulness/meditation bookmarks

22 August 2015 Learning bookmarks

22 August 2015 Bundi travel bookmarks

  • Bundi Haveli (accomodation).
  • Hadoti Palace (accomodation).
  • Haveli Braj Bhushan Ji ki (accomodation).
  • Haveli Katkoun Guest House (accomodation).
  • Kasera Paradise (accomodation).

14 August 2015 Upgrading PL30 headphones

I've loved my Soundmagic PL30 in-ear headphones. They're relatively inexpensive, comfortable, and great for exercising (they stay in). Audio quality and bass have been good enough (I don't need much). Unfortunately, I've had two pairs of PL30's and both stopped working after a year or two. I'm replacing the last pair with RHA's MA750 (an upgrade, me hopes).

Other contenders considered: Etymotic Research HF5, and Shure SE215. Also considered bluetooth alternatives like JayBird BlueBuds X and Plantronics BackBeat GO 2.

I'm somewhat nervous to pay more for a pair of headphones. Let's hope they don't meet the same unfortunate fate. We'll see.

13 August 2015 Quotes

13 August 2015 Bhutan travel bookmarks

12 August 2015 Cooking bookmarks

11 August 2015 9 week half-marathon training

While reading Zen Habits: Mastering the Art of Change, I comitted to running half marathon in mid-October. That's roughly two months from now. Here's a 9 week training schedule:

WEEK MON TUE WED THU FRI SAT SUN
1 Rest 5 Km 5 Km Cycle Rest 5 Km 7 Km
2 Rest 5 Km 5 Km Cycle Rest 5 Km 8 Km
3 Rest 7 Km 5 Km Cycle Rest 5 Km 10 Km
4 Rest 8 Km 5 Km Cycle Rest 5 Km 12 Km
5 Rest 8 Km Rest 8 Km Rest 5 Km 14 Km
6 Rest 8 Km Rest 8 Km Rest 6 Km 16 Km
7 Rest 8 Km 8 Km 8 Km Rest 8 Km 19 Km
8 Rest 8 Km Rest 12 Km Rest 8 Km 16 Km
9 Rest 8 Km Rest 5 Km 5 Km Rest Race

My times:

WEEK MON TUE WED THU FRI SAT SUN
1 Rest 29:04 Rest 26:36 38:40
2 Rest 29:11 28:50 Rest 27:07 44:55
3 Rest 40:46 26:29 Rest 57:01
4 Rest 46:46 Rest 30:08 1:12:10
5 Rest 46:59 Rest 44:46 Rest 24:50 1:25:24
6 Rest 50:02 Rest 46:24 Rest 1:37:39
7 Rest 46:54 46:41 46:42 Rest 1:57:57
8 Rest 45:28 Rest 48:13 (8km) Rest 43:56
9 Rest 44:24 Rest 27:12 26:09 Rest 1:58:28

11 August 2015 Shanghai travel bookmarks

updated: 29 November 2015

10 August 2015 Singapore job board bookmarks

08 August 2015 Germany travel bookmarks

08 August 2015 Menorca travel bookmarks

08 August 2015 Travel tools bookmarks

08 August 2015 Philippines travel bookmarks

23 July 2015 Add site-specific browsers to your workflow

There are three browser tabs continously used in my workflow: GMail, Google Calendar, and Google Play Music. I normally have many more tabs open, but these three I access periodically. As the number of open tabs increases, and I fail to cleanup, getting back to my usual three gets a little trickier.

So far, I've kept each of these services open in separate windows. But that doesn't always work. Click on any link in your inbox and you're back to playing cleanup. This is where site-specific browsers (SSB) can help.

Epichrome enables you to build Chrome-based SSBs (on Mac OSX). Build an SSB for the usual suspects and easily jump to them using the app switcher.

switcher-small.png

More at OSX Chrome SSB and Quora thread

UPDATE: Enable the Chrome extension to open URLs in default browser.

helper.png

And choose the default browser to open URLs.

default.png

20 July 2015 Sardinia travel bookmarks

  • Alghero.
  • Baja Sardinia.
  • Budoni.
  • Cala Goloritze, Sardinia.
  • Castelsardo (gifts maybe?).
  • Food: Maialetto sardo (Pig), Sebadas, Pardula, Papassinas, Pani e sapa.
  • L'Asinara boat trip (abandoned penitentiary).
  • La Pelosa beach.
  • Nuraghe.
  • Porto Cervo.
  • Porto Torres.
  • San Teodoro.
  • Stintino (fishing port).
  • Zedda e Piras vinyards (Alghero).

17 July 2015 Open closest build file in Emacs

Whether it's Makefile, SConstruct, BUILD, or your favorite build file, chances are you have to tweak it from time to time. ar/open-build-file searches your current and parent directories to find a build file.

(defvar ar/project-file-names '("Makefile" "SConstruct" "BUILD"))

(defun ar/parent-directory (path)
  "Get parent directory for PATH."
  (unless (equal "/" path)
    (file-name-directory (directory-file-name path))))

(defun ar/find-upwards (path filename)
  "Search upwards from PATH for a file named FILENAME."
  (let ((file (concat path filename))
        (parent (ar/parent-directory (expand-file-name path))))
    (if (file-exists-p file)
        file
      (when parent
        (ar/find-upwards parent filename)))))

(defun ar/open-closest (filename)
  "Open the closest FILENAME in current or parent dirs (handy for finding Makefiles)."
  (let ((closest-file-path (ar/find-upwards (buffer-file-name)
                                                 filename)))
    (when closest-file-path
      (message closest-file-path)
      (switch-to-buffer (find-file-noselect closest-file-path)))
    closest-file-path))

(defun ar/open-build-file ()
  "Open the closest project file in current or parent directory.
For example: Makefile, SConstruct, BUILD, etc.
Append `ar/project-file-names' to search for other file names."
  (interactive)
  (catch 'found
    (mapc (lambda (filename)
            (when (ar/open-closest filename)
              (throw 'found t)))
          ar/project-file-names)
    (error "No project file found")))

15 July 2015 Create iOS static fat libraries

Have separate static libraries for different iOS architectures? Stitch 'em up into a single fat library using with lipo:

$ lipo -create libOne_i386.a libOne_x86_64.a libOne_armv7.a libOne_arm64.a -output libOne.a

15 July 2015 Settling scores with an org table

Recently kept track of expenses between a group of us. To settle the scores, I emailed an exported HTML table from an org file. This was simple enough and required no external viewer from recepients. The org table, in all its textful glory, looked as follows…


| Date             | Item           |   Charge |
|------------------+----------------+----------|
| [2015-06-18 Thu] | Cash           |    20.00 |
| [2015-07-11 Sat] | Lucky 7        |    42.97 |
| [2015-07-13 Mon] | Santa Maria    |    32.00 |
| [2015-07-12 Sun] | Tayyabs        |    46.00 |
| [2015-07-13 Mon] | The Brass Rail |    39.00 |
| [2015-07-13 Mon] | Underground    |    10.00 |
| [2015-07-10 Fri] | Cash           |    20.00 |
| [2015-07-13 Mon] | Cash           |    20.00 |
| [2015-07-14 Tue] | Cash           |    20.00 |
|------------------+----------------+----------|
|                  | total          | £ 249.97 |
#+TBLFM: @11$3=vsum(@2..@10);£ %.2f

…while the exported HTML below could be easily pasted on to an email.

Date Item Charge
18 June 2015 Cash 20.00
11 July 2015 Lucky 7 42.97
13 July 2015 Santa Maria 32.00
12 July 2015 Tayyabs 46.00
13 July 2015 The Brass Rail 39.00
13 July 2015 Underground 10.00
10 July 2015 Cash 20.00
13 July 2015 Cash 20.00
14 July 2015 Cash 20.00
  total £ 249.97

13 July 2015 Recognize new password prompts in Emacs shell

At some point, you may come across a trusted command-line utility prompting you for a password, and Emacs shell happily displaying each typed character to the nearby-world to see. Luckily, you can train Emacs to recognize new password prompts and hide the typed characters in modes deriving from comint. Append the password prompt REGEXP:

(setq comint-password-prompt-regexp (concat comint-password-prompt-regexp
                                            "\\|"
                                            "Password for red alert:"))

11 July 2015 Bosnia and Hercegovina travel bookmarks

11 July 2015 Ireland travel bookmarks

09 July 2015 Pizza in London

Not tried these yet. Taking note:

  • Bravi Ragazzi (Streatham).
  • Homeslice (Covent Garden).
  • Lord Morpeth (Hackney).
  • Santa Maria (Ealing).
  • Voodoo Ray's (Dalston).
  • Well Kneaded Wagon (Date-dependent location).

09 July 2015 mp4 to gif

Converting mp4 to gif is handy for posting short screencasts. You can convert to gif using ffmpeg and optimize with imagemagick. To install:

apt-get install ffmpeg imagemagick (linux)
brew install ffmpeg imagemagick (Mac)

Convert to gif:

ffmpeg -i my.mp4 -pix_fmt rgb24 -r 5 my.gif

Optimize with:

convert -dither none -layers Optimize my.gif my_optimized.gif

UPDATE: There's also licecap and subsequently optimize with:

cat source.gif | gifsicle --colors 256 --optimize=3 --delay=15 > target.gif

UPDATE: Also consider for .mov:

ffmpeg -i in.mov -pix_fmt rgb24 -r 10 -f gif - | gifsicle --optimize=3 --delay=3 > out.gif

06 July 2015 Keyboards bookmarks

05 July 2015 United States travel bookmarks

05 July 2015 Lebanon travel bookmarks

05 July 2015 Slovenia travel bookmarks

05 July 2015 Belgium travel bookmarks

03 July 2015 Fishing with Emacs

OK not quite, but having recently learned about C-M-w (append-next-kill), I used it in a keyboard macro to fish out matching lines. This is similar to flush-lines, except the kill ring is also populated. This is handy, if you need the flushed lines. Here's an example.

fishing.gif

Here's the equivalent in Emacs lisp:

(defun flush-kill-lines (regex)
  "Flush lines matching REGEX and append to kill ring.  Restrict to \
region if active."
  (interactive "sFlush kill regex: ")
  (save-excursion
    (save-restriction
      (when (use-region-p)
        (narrow-to-region (point) (mark))
        (goto-char 0))
      (while (search-forward-regexp regex nil t)
        (move-beginning-of-line nil)
        (kill-whole-line)))))

03 July 2015 California travel bookmarks

01 July 2015 Rebind caps lock to control key on Mac OS X

Let's see if this one sticks. I'll give caps lock as control a try. Rebinding the keys on Mac OS X is easy enough:

caps-lock-to-control.png

System Preferences -> Keyboard -> Keyboard Tab -> Modifier Keys…

30 June 2015 Searchable ebooks in Emacs

If you haven't bought Mastering Emacs by Mickey Petersen, you should. It's a wonderful source of Emacs tips. Having just finished the ebook on my Kindle, I was keen to go back and fish out some of that newly found wisdom. My immediate reaction was to figure out a way to make the ebook searchable from Emacs.

The ebook is available in epub and pdf format. Though Emacs's docview is super handy for viewing pdf's, searching didn't feel as comfortable as searching in org mode. The epub, on the other hand, proved useful. Pandoc can easily convert from epub to org.

pandoc  --from=epub --to=org mastering-emacs.epub > mastering-emacs.org

After a some tidying (mostly removing BEGIN_HTML/END_HTML blocks and adding TITLE/AUTHOR), the resulting org file is surprisingly clean and easy to search/navigate. helm-swoop and helm-org-in-buffer-headings are great for that.

mastering-emacs-org-00.png

mastering-emacs-org-01.png

mastering-emacs-org-02.png

21 June 2015 Portugal travel bookmarks

updated: 09 July 2023

21 June 2015 Bulgaria travel bookmarks

updated: 21 June 2015

21 June 2015 Presenting bookmarks

21 June 2015 Bali travel bookmarks

17 June 2015 WWDC app for OS X

Guilherme Rambo created a great OS X OS X app for viewing WWDC content. Just installed it. Super handy. Thanks. Installing as simple as:

$ brew cask install wwdc

15 June 2015 Debugging Objective-C reference cycles

Overriding retain/release/autorelease may be handy while debugging:

- (instancetype)retain {
  NSLog(@"%p, retain\n", self);
  return [super retain];
}

- (oneway void)release {
  NSLog(@"%p, release\n", self);
  [super release];
}

- (instancetype)autorelease {
  NSLog(@"%p, autorelease\n", self);
  return [super autorelease];
}

13 June 2015 London grub

Beyond the hype, buzz, and pricey gimmicks… Places to eat in London:

  • Antipode.
  • Arang.
  • Bone Daddies.
  • Gelupo.
  • Grind.
  • Holy Cow.
  • Kerbisher and Malt.
  • Kulu Kulu (South Ken).
  • Le Relais de Venise.
  • Lucky 7's.
  • Royal China.
  • Shree Krishna Vada Pav.
  • Sri Suwoon.
  • Tayyabs.
  • The Cow.
  • Tonkotsu.

08 June 2015 My working playlist

It's been a while since I spotted The Ultimate Music Collection for Getting Work Done. Since then, I've been on the lookout for music to work to. Some favorites:

02 June 2015 Xcode bookmarks

02 June 2015 Costa Rica travel bookmarks

02 June 2015 Australia travel bookmarks

02 June 2015 Samoa travel bookmarks

02 June 2015 Norway travel bookmarks

updated: 28 March 2020

02 June 2015 Los Angeles travel bookmarks

28 May 2015 Mastering Emacs is out

Emacs is amazingly alive. New packages are regularly listed on melpa and a new book just came out: Mastering Emacs by Mickey Petersen.

cover.png

25 May 2015 South Carolina travel bookmarks

25 May 2015 Colorado travel bookmarks

25 May 2015 Bash bookmarks

25 May 2015 restclient.el

Installed Pashky's restclient.el Emacs package. Super helpful when trying out REST APIs.

restclient.png

25 May 2015 Seatle travel bookmarks

25 May 2015 Berlin travel bookmarks

25 May 2015 Skeuomorph

From Wikipedia, skeuomorph ˈskjuːəmɔrf is a derivative object that retains ornamental design cues from structures that were necessary in the original. Examples include pottery embellished with imitation rivets reminiscent of similar pots made of metal and a software calendar that imitates the appearance of binding on a paper desk calendar.

25 May 2015 define-word

Installed Abo Abo's define-word Emacs package. A handy package to define words at point.

define-word.png

22 May 2015 Flushing empty lines in Emacs

Via masteringemacs.org, removing blank lines in a buffer:

M-x flush-lines RET ^$ RET

22 May 2015 Regex bookmarks

06 May 2015 Write to temp iOS snippet

NSString *tempDir = NSTemporaryDirectory();
NSLog(@"%@\n", tempDir);
NSString *dataFilePath = [tempDir stringByAppendingPathComponent:@"my.file"];
[data writeToFile:dataFilePath atomically:YES];

04 May 2015 Greece travel bookmarks

04 May 2015 Sri Lanka travel bookmarks

04 May 2015 Switzerland travel bookmarks

04 May 2015 Thailand travel bookmarks

04 May 2015 Madagascar travel bookmarks

04 May 2015 Hong Kong travel bookmarks

04 May 2015 Barcelona travel bookmarks

04 May 2015 Iceland travel bookmarks

30 April 2015 Building clang-format

Based on instructions from Building clang-format and friends on OSX Mountain Lion.

#!/bin/bash
  set -o nounset
  set -o errexit

  # Based on instructions from:
  # http://blog.hardcodes.de/articles/63/building-clang-format-and-friends-on-osx-mountain-lion

  readonly LLVM_DIR_PATH='/tmp/llvm'

  update_repo() {
    if [[ ! -d $1 ]]; then
      git clone $2
    else
      cd $1
      git pull
      cd ..
    fi
    cd ..
  }

  update_all_repos() {
    update_repo "llvm" "http://llvm.org/git/llvm.git"
    pushd "${LLVM_DIR_PATH}/llvm/tools"
    update_repo "clang" "http://llvm.org/git/clang.git"
    popd
    cd "../../${LLVM_DIR_PATH}/llvm/tools/clang/tools"
    update_repo "clang-tools-extra" "http://llvm.org/git/clang-tools-extra.git"
    cd "../../.."
  }

  build_clang() {
    mkdir -p clang
    mkdir -p build
    cd clang
    ../llvm/configure --enable-libcpp --enable-cxx11 --enable-debug-symbols=no --enable-optimized --prefix="${LLVM_DIR_PATH}/build"
    make install
  }

  mkdir -p $LLVM_DIR_PATH
  cd ${LLVM_DIR_PATH}
  update_all_repos
  build_clang

Bonus: use clang-format-configurator.

30 April 2015 Programmatic iOS Auto Layout

Basic iOS auto layout usage. See Adopting Auto Layout and Visual Format language for reference.

- (instancetype)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
    // Disable autoresizing mask translation for parent.
    self.translatesAutoresizingMaskIntoConstraints = NO;

    _subview1 = [[UIView alloc] init];
    // Disable autoresizing mask translation for subview.
    _subview1.translatesAutoresizingMaskIntoConstraints = NO;

    _subview1.backgroundColor = [UIColor redColor];
    [self addSubview:_subview1];

    // Creates a dictionary of bindings to be used in visual format.
    NSDictionary *viewBindings = NSDictionaryOfVariableBindings(_subview1);

    // H: horizontal layout
    // |-50- spacing in relation to superview
    // [_subview1(==50)] subview1's width
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[_subview1(==50)]"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:viewBindings]];
    [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_subview1(==50)]"
                                                                 options:0
                                                                 metrics:nil
                                                                   views:viewBindings]];
  }
  return self;
}

Also consider:

  • A UIView Subclass should implement intrinsicContentSize.
  • A UIView Subclass should never add constraints on neither itself (ie. self) nor superview.

23 April 2015 Japan travel bookmarks

updated: 24 February 2024

21 October 2015 Kyoto travel bookmarks

updated: 29 December 2020

19 April 2015 Tokyo travel bookmarks

updated: 03 December 2021

19 April 2015 UK travel bookmarks

19 April 2015 Development quotes

19 April 2015 Development philosophy

19 April 2015 Spain travel bookmarks

19 April 2015 Meet up bookmarks

17 April 2015 Plantuml example

Played with Plantuml. Convenient for generating UML diagrams from text. Here's the Language Reference Guide. Here's an example:

@startuml
  abstract class Singer {
    abstract void sing()
    void Dance()
  }

  skinparam monochrome true
  Singer <|-- PopSinger
  Singer <|-- SalsaSinger

  class PopSinger {
    void sing()
  }

  class SalsaSinger {
    void sing()
  }

@enduml

plantuml-singer.png

Install plantuml on Mac OS X:

brew install plantum

Generating diagram:

$GRAPHVIZ_DOT=~/homebrew/bin/dot java -jar path/to/plantuml.8018.jar diagram.plantuml

ps. Installation and verification gist.

ps2. More handy UML examples in this fork.

14 April 2015 Helm-describe-helm-attribute

Writing A Spotify Client in 16 Minutes is fantastic for picking up helm and Emacs lisp tips. Of interest helm-describe-helm-attribute, second to the awesomeness of helm-spotify integration.

<iframe width='420'
        height='315'
        src='https://www.youtube.com/embed/XjKtkEMUYGc'
        frameborder='0'
        allowfullscreen>
</iframe>

12 April 2015 Youtube videos in your org html export

Sacha Chua and John Wiegley posted a wonderful video on Emacs lisp development tips. Embedding the following raw HTML using #+BEGIN_HTML/#+END_HTML:

<iframe width="420"
        height="315"
        src="https://www.youtube.com/embed/QRBcm6jFJ3Q"
        frameborder="0"
        allowfullscreen>
</iframe>

results in an embedded video when exporting your org file:

<iframe width="420"
        height="315"
        src="https://www.youtube.com/embed/QRBcm6jFJ3Q"
        frameborder="0"
        allowfullscreen>
</iframe>

12 April 2015 .net bookmarks

10 April 2015 UK property bookmarks

09 April 2015 Git commit message style

Adopted Tim Pope's Git commit message style. Also enabled Emacs's git-commit-training-wheels-mode:

(use-package git-commit-training-wheels-mode :ensure t
  :commands (git-commit-training-wheels-mode))

(use-package git-commit-mode :ensure t
  :config
  (add-hook 'git-commit-mode-hook 'git-commit-training-wheels-mode)
  :commands (git-commit-mode))

Another great post by Chris Beams.

06 April 2015 fci-mode and org-html-export-to-html bug

Having enabled fci-mode in most programing modes, org-html-export-to-html now exports an additional unicode character in source blocks. This thread has a workaround:

(defun org-html-fontify-code (code lang)
  ;; ...
  (funcall lang-mode)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (when (require 'fill-column-indicator nil 'noerror)
    (fci-mode -1))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (insert code)
  ;; ...

03 April 2015 Try cocoapods out

Cocapods try:

$ pod try POD_NAME

03 April 2015 Cornwall travel bookmarks

03 April 2015 Austria travel bookmarks

02 April 2015 Cinnamon desktop run dialog

Note to self. Open with Alt-f2.

29 March 2015 Books for 2015

29 March 2015 Ayahuasca bookmarks

28 March 2015 Emacs init.el bookmarks

28 March 2015 CSS vertical align using flex

Codepen snippet:

div{
  height: 200px;
  background: #ccc;
  display: flex;
  justify-content: center;
  align-items: center;
}

p{
  margin: auto
}

26 March 2015 London diving schools

26 March 2015 Helm buffer URLs

Venturing into Emacs lisp and Helm. Here's a go at listing all URLs in current buffer.

(require 'goto-addr)

(defun ar/helm-buffer-url-candidates ()
  "Generate helm candidates for all URLs in buffer."
  (save-excursion
    (goto-char (point-min))
    (let ((helm-candidates '())
          (url))
      (while (re-search-forward goto-address-url-regexp
                                nil t)
        (setq url
              (buffer-substring-no-properties (match-beginning 0)
                                              (match-end 0)))
        (add-to-list 'helm-candidates
                     (cons url
                           url)))
      helm-candidates)))

(defun ar/helm-buffer-urls ()
  "Narrow down and open a URL in buffer."
  (interactive)
  (helm :sources `(((name . "Buffer URLs")
                    (candidates . ,(ar/helm-buffer-url-candidates))
                    (action . (lambda (url)
                                (browse-url url)))))))

23 March 2015 Doh! undo last git commit

$ git reset --soft HEAD~1

22 March 2015 Resetting variables using defvar

Want to re-evaluate defvars and modify variables? eval-defun (bound to C-M-x) can help. From the manual:

If the current defun is actually a call to `defvar', then reset the variable using its initial value expression even if the variable already has some other value. (Normally `defvar' does not change the variable's value if it already has a value.) Treat `defcustom' similarly.

22 March 2015 Broken Xcode plugins?

Some Xcode plugins stopped loading after updating Xcode. Ensure the latest DVTPlugInCompatibilityUUIDs is added to the plugin's Info.plist. Get from:

$ defaults read \
    /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID

Additional suggestions as Stack Overflow.

22 March 2015 Born to Run references

updated: 23 March 2015

References from reading Christopher McDougall's Born to Run:

Recipe by Megan Mignot, based on book references:

Mama Tita’s Pancakes

  • 1 ½ cups cooked brown rice
  • 1 cup coconut milk
  • 2 ripe bananas
  • 1 tablespoon honey
  • ½ cup white cornmeal
  • 2 teaspoons baking powder

19 March 2015 Org tips from 2015-03-18 Emacs hangout

updated: 22 March 2015

Lots of great tips in Emacs Hangout 2015-03-18. Favorites:

  • Private org drawer to prevent export:
:PRIVATE:
My super duper secret text I don't want to export.
:END:
  • C-c C-p/C-c C-n Jump over sections.
  • (setq org-hide-leading-stars t).
  • (org-bullets-mode).
  • (org-refile).
  • C-u (org-refile) jumps, no refile.
  • (helm-org-in-buffer-headings).

19 March 2015 Food bookmarks

19 March 2015 Ethiopia travel bookmarks

19 March 2015 China travel bookmarks

18 March 2015 South Korea travel bookmarks

18 March 2015 Sharing on iOS

NSString *title = @"Sharing on iOS bookmarks.";
NSURL *url = [NSURL URLWithString:@"http://xenodium.com/#sharing-on-ios"];
UIImage *image = [UIImage imageNamed:@"beautiful-image"];

UIActivityViewController *controller =
  [[UIActivityViewController alloc]
    initWithActivityItems:@[title, url, image]
    applicationActivities:nil];

// self being a UIViewController.
[self presentViewController:controller animated:YES completion:nil];
  • Sharing through Mail app on simulator isn't supported.

viewServiceDidTerminateWithError: Error Domain=_UIViewServiceInterfaceErrorDomain Code=3 "The operation couldn’t be completed. (_UIViewServiceInterfaceErrorDomain error 3.)" UserInfo=… {Message=Service Connection Interrupted}

18 March 2015 San Francisco travel bookmarks

18 March 2015 Istanbul travel bookmarks

18 March 2015 Rome travel bookmarks

17 March 2015 Italy travel bookmarks

17 March 2015 Emacs lisp debug on entry

Wanted to track down which package was enabling ido-mode on my behalf. debug-on-entry to the rescue. Pass the method name in question and you're good to go.

(debug-on-entry 'ido-mode)

When done, use cancel.

(cancel-debug-on-entry 'ido-mode)

17 March 2015 Burma travel bookmarks

updated: 27 January 2019

16 March 2015 OS X Screencasts to animated GIF

15 March 2015 Writing Xcode plugins

defaults read \
    /Applications/Xcode.app/Contents/Info DVTPlugInCompatibilityUUID

15 March 2015 Uninstalling Alcatraz from Xcode

$ rm -rf ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/Alcatraz.xcplugin
$ rm -rf ~/Library/Application\ Support/Alcatraz

ps. Removing all plugins:

$ rm -rf ~/Library/Application\ Support/Developer/Shared/Xcode/Plug-ins/*

14 March 2015 Prefill Emacs swiper with current region

The new swiper Emacs package is proving to be a great alternative to helm-swoop. Here's how to prefill with current region:

(defun ar/prefilled-swiper ()
  "Pre-fill swiper input with region."
  (interactive)
  (if (region-active-p)
      (let ((region-text (buffer-substring (region-beginning)
                                           (region-end))))
        (swiper region-text))
    (swiper)))

(global-set-key (kbd "C-s")
                #'ar/prefilled-swiper)

14 March 2015 Change macOS app icon

  • Open the new icon (.icns) in Preview.
  • Click on large image
  • Select all (⌘-a).
  • Copy (⌘-c).
  • Ctrl-click on app icon.
  • Select Get Info.
  • Click on app icon (top-left).
  • Paste (⌘-v).
  • Done!

overriding.png

after.png

11 March 2015 Hack on Emacs London meetup bookmarks

09 March 2015 Working with OS X and Emacs tips

From M-x all-things-emacs, Ryan McGeary's OS X/Emacs workflow.

Dotfiles

09 March 2015 Building ycmd

Build

$ git clone https://github.com/Valloric/ycmd.git
$ cd ycmd
$ git submodule update --init --recursive
$ ./build.sh --clang-completer

Test

$ python ycmd
  serving on http://127.0.0.1:54265

08 March 2015 Regular bookmarks

08 March 2015 Photography bookmarks

08 March 2015 Paris travel bookmarks

updated: 13 July 2016

08 March 2015 Org mode bookmarks

updated: 23 November 2021

11 September 2018 London bar backlog

08 March 2015 London food backlog

updated: 25 May 2019

Barnsbury

  • TODO Sunday (breakfast, cafe, working).

Battersea

Brick Lane

  • TODO Chez Elles (french).
  • TODO Fika (Swedish, cinnamon buns, coffee).

Bermondsey

  • TODO Druid Street market
    • FAT London (Kimchee ).
    • & Cultured butter.

Bethnal Green

DONE Lahpet (Burmese) 02 December 2018 27 December 2018

Brixton

  • TODO Nanban (Japanese soul food).

Borough

Camberwell

Canada Water

Cannon street

Chiswick

Covent Garden

Clapham

Crouch end

Ealing

Earls court

Edware road

  • DONE GOGI (korean): Average. Also, they lied to me and apologized with £10 credit.

Euston

  • TODO Roti King (Indian/Pakistani/Singaporean): 40, Doric Way, Euston, NW1 1LH.

Farringdon

  • TODO Quality Chop House.
  • TODO Daddy Donkey (Mexican).
  • TODO J+A (coffe, working).

Fitzrovia

Golders Green

  • TODO Cafe Japan (Sushi).

Hammersmith

Hamstead

Harrow

Hatch End

Holborn

Holloway Road

  • TODO Xi'an impression (Xi'an)

Islington

Kensington

  • TODO Clarke's, try the burger.

Kensal Green

Kentish town

King's Cross

Leytonstone

Liverpool street

  • TODO Gunpowder (Indian).
  • TODO Cinnamon Kitchen (Indian).

London Bridge

Marylebone

  • DONE Fischer's (Austrian). Great atmosphere and schnitzel. Not cheap.
  • TODO Nambutei (Sushi).
  • TODO Lurra (Basque).

Mayfair

Mornington Crescent

  • TODO Asakusa (Japanese).

Notting Hill

Old Street

Olympia

  • DONE Aborz (Iranian). 27 December 2018

Oxford street

  • TODO Roti Chai (Indian).

Peckham

Piccadilly

Sheppherds bush

  • TODO Caco & Co (Portuguese cafe). 14 July 2019
  • TODO Chop chop (Noodle bar). 14 July 2019

Shoreditch

Sloane Square

Soho

Southhall

DONE Brilliant restaurant (healthier Indian). 27 December 2018

Southbank

South Kensington

  • TODO Hour Glass (Pub restaurant).

Smithfield

Spitafields

  • DONE Som Saa (Thai), highly recommended. 27 December 2018
  • TODO Lahpet (Burmese) 02 December 2018

Strand

Stoke Newingtom

Tottenham Court Road.

Tower Hill

Tufnell Park

  • TODO Monsoon (Indian), try lamb naga.

Turnham Green

  • TODO Chief Coffee.

Victoria

Walthamstow

Waterloo

West Hamstead

Whitechapel

  • TODO Lahore Kebab House, try seekh kebabs with roti.
  • TODO Sushinoen.

Wimbledon

  • TODO Dalchini (Indian), try spicy cocunut fish curry.

07 March 2015 UX bookmarks

updated: 04 June 2021

07 March 2015 Recipes

Jeera rice (cumin rice)

Sizzle spices (40 seconds)

  • 1 tablespoon oil
  • 1 teaspoon cumin seeds
  • 4 cloves
  • 2 black cardamon pods
  • 1 cinnamon stick

Sautee onion (2 mins or browned/opaque)

  • 1 small yellow onion (chopped)

Sautee rice + salt (1 minute)

  • 2 cups of basmati rice
  • 1.2 teaspoon of salt

Boil, then partially cover and simmer (8 minutes or water gone)

  • 4 cups water

Rest 5 minutes (covered)

Slow-cooked lamb

  1. Preheat oven: 240°C (no fan) 220°C (fan).
  2. Lamb face up in tray.
  3. Cook for 30 mins (or brown).
  4. Take lamb out.
  5. Add to tray: broth, onions, rosemary.
  6. Lamb face down (broth covers 1/3 or 1/4).
  7. Cover with lid (or baking/parchment paper then 2 layers of foil).
  8. Bake for 3.5 hours at 180°C (no fan).
  9. Take out.
  10. Turn lamb face up (over again). Check if liquid needs top-up.
  11. Cook for 2 more hours.
  12. Check if ready. Does meat fall off the bone with fork?
  13. If not, keep for another 30 mins. Check again.
  14. You are done \o/.

Tom Kha Gai soup

  • Chicken or Prawns
  • 2 kaffir lime leaves
  • 1 lemongrass stalk
  • 1 1/2 cocunut milk
  • 3/4 sliced fresh galaghal
  • 1 1/2 chicken stock or water
  • 1/2 cup mushrooms
  • 3 1/2 tbsp sugar
  • 1/2 cup of cilantro
  • 1-4 thai chillies
  • 1-2 tbsp chili oil
  • 1 green onion

Veg-Fruit juice

  • Lime
  • Ginger
  • Apple
  • Chilly
  • Celery
  • Fig
  • Blueberries

Berry Hempster

  • Hemp milk
  • Hemp protein
  • Strawberry
  • Blueberry
  • Date

How to cook Beef Chow Fun.

Vietnamese Pork Lettuce Wraps (my own versions)

  • Pork Mince.
  • Chopped garlic.
  • Chopped chillies.
  • Chopped ginger.
  • 4 tablespoons of soy sauce.
  • 2 tablespoons of apple cider vinegar.
  • Sesame oil (for cooking mince).
  • Half tablespoon of coconut sugar.
  • Lettuces (for wrapping).

07 March 2015 Music backlog

updated: 11 July 2021

06 March 2015 UX scrapbook bookmarks

09 October 2015 Travel blog bookmarks

06 March 2015 Travel bookmarks

updated: 05 August 2020

06 March 2015 Startup bookmarks

updated: 09 July 2023

06 March 2015 Romania travel bookmarks

updated: 01 December 2019

06 March 2015 Productivity tips backlog

updated: 11 January 2016

06 March 2015 Productivity bookmarks

06 March 2015 Privacy bookmarks

updated: 30 September 2021

06 March 2015 Lifestyle/wellbeing/health bookmarks

updated: 27 June 2021

06 March 2015 Graphics bookmarks

updated: 04 March 2024

06 March 2015 Nexus Q bookmarks

06 March 2015 Golang bookmarks

updated: 15 April 2020

06 March 2015 GitHub bookmarks

06 March 2015 Courses bookmarks

05 March 2015 Reload inputrc

Reload .iputrc from bash prompt: C-x C-r. More at bashref manual.

05 March 2015 Learning Emacs lisp

updated: 13 April 2015
  • Use nreverse and nconc to operate on lists in-place.
  • Set buffer local variables:
(setq-local my-clever-var)
  • Execute before saving buffer:
(add-hook 'write-file-hooks
          (lambda ()
            (message "about to save!")))
  • Possibly use to start processes and send file content:
(make-comint NAME PROGRAM &optional STARTFILE &rest SWITCHES)
  • Creating markers:
(setq my-marker (copy-marker (point)))
  #<marker at 10251 in *ielm*>

(marker-buffer my-marker)
  #<buffer *ielm*>

(marker-position my-marker)
  10251 (#o24013, #x280b, ?⠋)
  • Get org heading at point:
(org-get-heading 'no-tags 'no-todo)
  • Remove string text properties. From manual:

(substring-no-properties STRING &optional FROM TO)

Return a substring of STRING, without text properties. It starts at index FROM and ends before TO. TO may be nil or omitted; then the substring runs to the end of STRING. If FROM is nil or omitted, the substring starts at the beginning of STRING. If FROM or TO is negative, it counts from the end.

  • Skip org entry metadata/drawers:
(org-end-of-meta-data-and-drawers)
  • Random access to org entry using id (or CUSTOM_ID):
(org-open-link-from-string "[[#%exciting-custom-id]]")
  • Go to where the function is defined.
  • Press C-u C-M-x. Edebug breakpoint for function.
  • Invoke function in question.
  • n/c will get you around.
  • q when done.
  • Pretty printing objects:
(let ((my-var (list "val1"
                    "val2"
                    "val3")))
  (pp-to-string my-var))
  • Search and/or replace in curent buffer:
(re-search-forward "needle"
                   nil t)
(match-beginning 0) ;; Start location of match from last search.
(match-end 0) ;; End location of match from last search.
(replace-match "love")

;; needle-in-haystack
  • Restrict buffer editing to a region:
(narrow-to-region (point)
                  (point-max))
  • Restore restriction:
(save-restriction (narrow-to-region (point)
                                    (point-max))
  • Restore point, mark, and current buffer:
(save-excursion (goto-char (point-max))
                (insert "Hello elisp."))
  • Concatenating strings:
(concat "Hello " "elisp " "world.")
  • Grabbing thing at point:
(thing-at-point 'word)
(thing-at-point 'symbol)
(thing-at-point 'line)
  • Unit test with ert.
  • Basic iteration with dolist:
(dolist (v '("a" "b" "c"))
  (print v))
  • Output to other buffer:
(with-current-buffer (get-buffer-create "*some buffer*")
  (princ '(some list to print)
         (current-buffer)))
  • For a temporary buffer, use with-temp-buffer:
(with-temp-buffer
  (insert "abc")
  (point))
  • Cons cells bookmark.
  • Check for substring:
(string-match-p REGEXP STRING &optional START)
  • Matching substrings and accessing groups:
(setq haystack "Always click [[http://reddit.com/r/emacs][here]].")
(setq needle-re "\\[\\[\\(.*\\)]\\[\\(.*\\)]]")
  "\\[\\[\\(.*\\)]\\[\\(.*\\)]]"

(string-match needle-re haystack)
  13 (#o15, #xd, ?\C-m)

(match-string 0 haystack)
  "[[http://reddit.com/r/emacs][here]]"

(match-string 1 haystack)
  "http://reddit.com/r/emacs"

(match-string 2 haystack)
  "here"
  • Return argument unchanged (noop):
(identity ARG)
  • Org insert today's timestamp
(org-insert-time-stamp (current-time))
(car LIST)
  • All but first element
(cdr LIST)
  • Add NEWELT to front of PLACE
(push NEWELT PLACE)
  • Invoke 'FUNCTION for each in SEQUENCE
(mapcar FUNCTION SEQUENCE)
  • Search/replace
(while (search-forward "Hello")
  (replace-match "Bonjour"))
  • Save to kill ring = copy.
  • Point = cursor position.
  • Mark = a buffer position.
  • Kill = cut text.
  • Yank = paste.
  • Buffer:File = 1:1.
  • Window:Buffer = 1:1.
  • Frame:Window = 1:many.
  • Font lock = syntax highlighting.

06 March 2015 Apple Watch bookmarks

05 March 2015 iOS bookmarks

updated: 07 July 2021

05 March 2015 Kerala travel bookmarks

  • Bagel Shop, 30 Pali Mala Road, off Carter Road, Bandra (W) (+91 22 2605-0178). Daily 9.00AM-10.00PM. Meal for two R500-R800.
  • Hotel Natraj, 22-24 City Station Road, Udaipur (near Bapu Bazaar), +91-294-2487488, +91-94147-57893,
  • Kala Ghoda Café,10 Ropewalk Lane, Kala Ghoda (+91 22 2263-3866). Daily 8.30AM-11.30PM. Meal for two R600.
  • Kochin (Fort Kochin) - old port town with Chinese, Portuguese, Dutch, British and Jewish heritage.
  • Munnar - hill station and centre of tea, coffee and spice growing. Great hiking and spectacular views.
  • Periyar Wildlife Sanctuary.
  • Suzette, Atlanta Building, Nariman Point (+91 22 2288-0055). Daily 9.00AM-11.00PM. Also at Bandra. Meal for two R600-R1,000.
  • Varkala - chilled out beach resort.
  • Yoga House, 53 Chimbai Road, behind St Andrew's Church, off Hill Road, Bandra (W)(+91 22 6554- 5001). Daily 7.00AM-10.30PM.

05 March 2015 India travel bookmarks

updated: 02 January 2021

05 March 2015 Git bookmarks

04 March 2015 Language learning bookmarks

18 February 2015 Git conflict resolution déjà vu?

use git rerere. here's a post.

17 January 2015 Graphics design tools bookmarks

23 April 2015 Emacs key bindings and maps

based on masteringemacs.org.

bonus tip

prefix key, followed by c-h, lists keys in prefix.

keymap

maps key to action.

keymap found in buffer and most major modes.

keys

  • undefined: self explanatory.
  • prefix key: ie. c-x (part of complete key).
  • complete key: complete input executes associated command.

mapping

  • (define-key keymap key def): add to current buffer map.
  • (local-set-key key command): add to active buffer (no map option).
  • (local-unset-key key)
  • (global-set-key key command): add to global keymap (all buffers).
  • (global-unset-key key)

key codes

  • kbd: macro transaltes human-readable key to emacs readable.
  • function and navigation keys must be surrounded by <>.
  • example: (kbd "c-c p") or (kbd "<f8>") of (kbd "<down>").

remapping

  • use remap to replace mapping (ie. kill-line with my/kill-line).
  • (define-key keymap [remap original-function] 'my-own-function).

reserved keys

  • "c-c ?" generally reserved for you, but third party packages use it.
  • function keys (ie. f1-f12).
  • hyper and super (ancient).

lookup order

  • in a nutshell: minor mode keys, local keys, global keys.
  • full order:
    1. overriding-terminal-local-map: terminal-specific key binds.
    2. overriding-local-map: override all other local keymaps (avoid if possible).
    3. char property at point: useful for yasnippet.
    4. emulation-mode-map-alists: advanced multi-mode keymap.
    5. minor-mode-overriding-map-alist: minor modes in major modes.
    6. minor-mode-map-alist: as previous (preferred for minor modes) <–—
    7. current-local-map: buffers current local map.
    8. current-global-map: last place to look (ie. global).

mode hooks

  • (local-set-key (kbd "c-c q") 'my-awesome-method)) in hook-method.
  • for key-chord-define, use current-local-map.

30 December 2014 Video backlog

updated: 12 January 2019

12 September 2014 Flight-booking bookmarks

18 September 2014 Frugal bookmarks

18 September 2014 Charities bookmarks

18 September 2014 Origami bookmarks

30 December 2014 Movie backlog

updated: 17 December 2022

29 December 2014 Microservices bookmarks

30 December 2014 Books backlog

updated: 06 February 2021

29 December 2014 Gardening bookmarks

03 December 2014 Emacs tips backlog

updated: 18 June 2017

09 July 2014 Installing Emacs 24.4 on Linux

sudo apt-get install texinfo build-essential xorg-dev libgtk-3-dev libjpeg-dev libncurses5-dev libgif-dev libtiff-dev libm17n-dev libpng12-dev librsvg2-dev libotf-dev

09 July 2014 Installing Emacs 24.4 on Mac OS X

See Yamamoto's Mac OS X port. To install:

$ brew tap railwaycat/emacsmacport
$ brew install emacs-mac

02 November 2014 Xcode6 tips

Shortcuts

  • ⌘⇧o Fuzzy file search.
  • ⌘⌥j Fuzzy file search (showing in Xcode project hierarchy).
  • ⌘⇧j Show file in Xcode project hierarchy.
  • ⌘⌥0 Show/hide utility area (right panel).
  • ⌘0 Show/hide navigation area (left panel).
  • ⇧⌘Y Show/hide debug area (bottom panel).
  • Ctrli Indent selection.
  • ⌘\ Toggle breakpoint on line.
  • ⌘/ Toggle comment.
  • ⌘[1-8] Select tabs on left panel.
  • Ctrl[1-x] Select top file navigation menu items.

Xcode features

  • Snippets.
  • Templates.
  • View debugging.
  • Simctl (send files to simulator).

Plugins of interest

  • Fuzzy autocomplete.
  • Uncrustify for indentation.
  • xcs code switch expansion.
  • Org and order (for properties).

12 December 2015 Simple ssh tunnel

Via @climagic, connections to tcp localhost:9909 will be made to 192.168.1.1:80 via SSH tunnel to home.

ssh -L 9909:192.168.1.1:80 home

20 September 2014 gpg/pgp bookmarks

20 September 2014 Emacs lisp bookmarks

updated: 28 November 2021

19 September 2014 Emacs bookmarks

updated: 12 November 2023

][Christian Tietze: Emacs: center window on current monitor]].

11 September 2014 Resetting gnome-terminal preferences

Resetting preferences

gconftool --recursive-unset /apps/gnome-terminal

Want 256 colors?

Edit .bash_profile

export TERM="screen-256color"

Ensure .bash_profile is loaded

From gnome-terminal window:

gnome-terminal Edit Profiles… Edit Title and Command X Run command as login shell

Solarized

Bonus: See post to get solarized on gnome-terminal.

09 October 2014 C++ bookmarks

14 July 2014 Java bookmarks

updated: 16 November 2015

14 July 2014 Browser bookmarks

14 July 2014 Node bookmarks

14 July 2014 JavaScript bookmarks

updated: 02 December 2019

14 July 2014 HTML5 bookmarks

14 July 2014 Networking bookmarks

13 July 2014 Python bookmarks

13 July 2014 Development bookmarks

updated: 30 September 2021

][Hexagonal Grids]].

13 July 2014 Paswordless ssh with authorized keys

On local host

ssh-keygen
cat ~/.ssh/id_dsa.pub | ssh user@remotehost 'cat >> ~/.ssh/authorized_keys'

On remote host

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

UPDATE: Add "AddKeysToAgent yes" to .ssh/config and enter password only once.

04 November 2013 Some python idioms

  • Prefer double quotes if escaping single quotes.
  • Prefer string interpolation over join. Eg. "'%s'" % member_default.
  • Prefer double underscore for privates.
  • Prefer with statement to implicitly close file.
with open(path, 'r') as text_file:
    text = text_file.read()
  • Prefer list comprehensions to filter.
  • Prefer using separate modules over classes if only using for separation.
  • Keep in mind: "eafp vs lbyl" (ie. just let it throw).
  • Prefer exceptions over assertions.
  • Throw ValueError for wrong input.
  • Return explicit False if remaining case is always false.

Footnotes:

1

The app's been fairly stable, but who knows… please backup your org file before feeding it to the lion.

2

Yes, this post was written in org mode.