A Life Configuring Emacs
Table of Contents
- 1. Why Emacs?
- 1.1. Emacs is a flexible platform for developing end-user applications
- 1.2. The Power of Text Manipulation
- 1.3. Keyboard Navigation and Alteration
- 1.4. Emacs Proverbs as Koan
- 1.5. Possibly Interesting Reads
- 1.6. Fun commands to try out
- 1.7. What Does Literate Programming Look Like?
- 1.8. Why a monolithic configuration?
- 2. Booting Up
- 2.1.
~/.emacs
vs.init.org
- 2.2. Who am I?
- 2.3. Emacs Package Manager
- 2.4. Installing OS packages, and automatically keeping my system up to data, from within Emacs
- 2.5. Syncing to the System's
$PATH
- 2.6. Restarting Emacs —Keeping buffers open across sessions?
- 2.7. “Being at the Helm” —Completion & Narrowing Framework
- 2.8. Org-Mode Administrivia
- 2.9. Password-locking files —“encryption”
- 2.10. all-the-icons
- 2.11. Hydra: Supply a prefix only once
- 2.12. Helpful Utilities & Shortcuts
- 2.1.
- 3. Staying Sane
- 3.1. Undo-tree: Very Local Version Control
- 3.2. Automatic Backups
- 3.3.
magit
—Emacs' porcelain interface to git - 3.4. Pretty Magit Commit Leaders
- 3.5. Version Control with SVN —Using Magit! Disabled
- 3.6. Highlighting TODO-s & Showing them in Magit
- 3.7. Silently show me when a line was modified and by whom
- 3.8. delete-by-moving-to-trash t
- 3.9. Jumping to extreme semantic units
- 3.10. Get CheatSheets and view them easily
- 4. Literate Programming
- 5. Life within Org-mode
- 6. Cosmetics
- 6.1. Startup message: Emacs & Org versions
- 6.2. My to-do list: The initial buffer when Emacs opens up
- 6.3. A sleek, informative, & fancy mode line
- 6.4. Exquisite Fonts and Themes
- 6.5. Never lose the cursor
- 6.6. Dimming Unused Windows
- 6.7. Flashing when something goes wrong
- 6.8. Hiding Scrollbar, tool bar, and menu
- 6.9. Highlight & complete parenthesis pair when cursor is near ;-)
- 6.10. Proportional fonts for Headlines
- 6.11. Making Block Delimiters Less Intrusive
- 6.12. Hiding Emphasise Markers, Inlining Images, and LaTeX-as-PNG
- 6.13. Show off-screen heading at the top of the window
- 6.14. Powerful Directory Editing with
dired
- 6.15. Persistent Scratch Buffer
- 6.16. Tabs Disabled
- 6.17. Window resizing using the golden ratio Disabled
- 6.18. Org-Emphasise for Parts of Words Disabled
- 6.19. Preview link under cursor
- 6.20. Replace phrases with nice SVG labels
- 7. Prose
- 7.1. Whitespace
- 7.2. Formatting Text
- 7.3. Fill-mode —Word Wrapping
- 7.4. Pretty Lists Markers
- 7.5. Fix spelling as you type —thesaurus & dictionary too!
- 7.6. Using a Grammar & Style Checker
- 7.7. Lightweight Prose Proofchecking
- 7.8. Placeholder Text —For Learning & Experimenting
- 7.9. Some text to make us smile
- 7.10. Unicode Input via Agda Input
- 7.11. Increase/decrease text size
- 7.12. Moving Text Around
- 7.13. Enabling CamelCase Aware Editing Operations
- 7.14. Delete Selection Mode
- 7.15.
M-n,p
: Word-at-Point Navigation ╱╲ Automatic highlighting current symbol/word - 7.16. Letter-based Navigation
- 7.17.
C-c e n,p
: Taking a tour of one's edits - 7.18. visual-regexp
- 7.19. LaTeX ⇐ Org-Mode
- 7.20. HTML ⇐ Org-mode
- 8. Programming
- 8.1. Quickly Run Code Snippets
- 8.2. C-x C-e ∷ REPL-driven development for ELisp / NodeJS / Python / Etc !
- 8.3. devdocs
- 8.4. How do I do something?
- 8.5. Sleek Semantic Selection
- 8.6. Managing Processes/Servers from within Emacs —Work-specific functions
- 8.7. Project management & navigation
- 8.8. TODO Projectile
- 8.9. TODO Are there any errors in my code?
- 8.10. Jumping to definitions & references
- 8.11. Documentation Pop-Ups
- 8.12. Turbolog: What's the value of this expression? JavaScript
- 8.13. Which function are we writing?
- 8.14. Highlight defined Lisp symbols
- 8.15. Being Generous with Whitespace
- 8.16. Coding with a Fruit Salad: Semantic Highlighting
- 8.17. Jump between windows using Cmd+Arrow & between recent buffers with Meta-Tab relocate
- 8.18. Shell / Terminal relocate
- 8.19. Github Browser Extensions (for Chrome)
- 8.20. Browse remote files
- 8.21. A nice Emacs interface for a portion of the “gh” CLI
- 8.22. copy-as-format: Emacs function to copy buffer locations as GitHub/Slack/JIRA etc… formatted code.
- 8.23. See all company related PRs
- 8.24. SQL —via LSP
- 8.25. Docker
- 8.26. my/open-in-terminal '⌘
- 8.27. Check if application APP is currently running, in use.
- 8.28. LSP: Making Emacs into a generic full-featured programming IDE
- 8.29. JSON
- 8.30. w-screencapture
- 8.31. Screencapturing the Current Emacs Frame
- 8.32. Comment-boxes up to the fill-column
- 8.33. Auto-format on Save
- 8.34. Searching Hydra
- 8.35. Peer Review / Pull Request Template for Work
- 9. Web-Development
- 10. Lisp Helpers / Kill all buffers that are not associated with a file
- 11. Toggles Hydra Not_yet_tangled
- 12. Lost Souls Outdated_Documentation Not_yet_tangled
- 12.1. Note: M-S-SPC is for my personal servers dashboard.
- 12.2. Zoom
- 12.3. Ibuffer
- 12.4. find function at point
- 12.5. “C-x 2” and “C-x 3” now create a new window horizontally/vertically and send cursor there
- 12.6. Semantic Change
- 12.7. Drag Stuff Disabled
- 12.8. Indentation Guide
- 12.9. Commenting
- 12.10. Having a workspace manager in Emacs
- 12.11. Editor Documentation with Contextual Information
- 12.12.
README
—Frominit.org
toinit.el
- 12.13. Org-mode's
<𝒳
Block Expansions - 12.14. What's changed & who's to blame?
- 12.15. Emacs keybindings for my browser Disabled
- 12.16. Using Emacs in any text area on my OS Disabled
- 12.17. Reload buffer with
f5
- 12.18. Kill to start of line
- 12.19. Killing buffers & windows:
C-x k
has a family - 12.20. Switching from 2 horizontal windows to 2 vertical windows
- 12.21. Obtaining Values of
#+KEYWORD
Annotations - 12.22. Publishing articles to my personal blog
- 12.23. Jumping without hassle
- 12.24. The evils of this world
- 12.25. TODO Hydra Timer
- 12.26. Makes Org/Markdown previewabvle as we type!!! ♥
- 12.27. Draw pretty unicode tables in org-mode to_include
- 12.28. Cucumber
- 12.29. Syntax highlighting —numbers and escape characters
- 12.30. shell-command-and-run
- 12.31. Keeping my system up to date
- 12.32. Compile posterity
- 12.33. Let's jump to a current Chrome browser tab, or one from our Chrome history, from within Emacs.
- 12.34. Get Shell history within Emacs via Completing Read with Helm
- 12.35. Launch macOS apps with Helm
- 12.36. Use Org Mode links in other modes: Links can be opened and edited like in Org Mode.
- 12.37. Let's make working with Emacs Lisp even better!
- 12.38. A butler for your buffers. Group buffers into workspaces with programmable rules, and easily switch to and manipulate them.
- 12.39. Let's try out this dope theme and this one too!
- 13. Conclusion —Why Configuration Files Should be Literate
Abstract
Hello! Herein I document the configurations I utilise with Emacs.
As a literate program file with Org-mode, I am ensured optimal navigation through my ever growing configuration files, ease of usability and reference for peers, and, most importantly, better maintainability for myself!
Dear reader, when encountering a foregin command X
I encourage you to execute
(describe-symbol 'X)
, or press C-h o with the cursor on X
. An elementary
Elisp Cheat Sheet can be found at and
is a 2-page 3-column PDF of the bindings in this configuration.
- C-h e ⇒ What'd Emacs do?
- C-h o ⇒ What's this thing?
- C-h l ⇒ What'd I do?
- C-h ? ⇒ What're the help topics? —gives possible completions to “C-h ⋯”.
- “I accidentally hit a key, which one and what did it do!?” ⇒ C-h e and C-h l, then use C-h o to get more details on the action. ;-)
Finally, C-h d asks nicely what ‘d’ocumentation you're interested in.
After providing a few keywords, the apropos
tool yields possible functions
and variables that may accomplish my goal.
This article is about how I like to do things ---I'm not insisting others should do things this way.
Always remember that to argue, and win, is to break down the reality of the person you are arguing against. It is painful to lose your reality, so be kind, even if you are right. - Haruki Murakami
Life is too short to not read the very best book you know of right now. - Patrick Collison
Inspiration is for amateurs. The rest of us just show up and get to work. - Chuck Close
“Personal instructions for a new machine”
These steps must be performed at the terminal since they are required to get my Emacs, which then installs everything else when it's first opened.
Install a package manager: https://brew.sh/ :
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Also: Change to the conventional scrolling direction: If I pull my scroll down, I want to go down.
- Apple menu → System Preferences → Mouse → Tick the scroll direction option.
-
brew install --cask emacs
If that fails, try this to install Emacs:
brew tap daviderestivo/emacs-head brew install emacs-head
Then make the command
emacs
available via the terminal —required if doing any melpa development.ln -s /usr/local/opt/emacs-head@27/Emacs.app /Applications sudo ln -s /usr/local/opt/emacs-head@27/Emacs.app/Contents/MacOS/Emacs /usr/local/bin/emacs
- Install git:
brew install git
Get my Emacs setup:
rm -rf ~/.emacs.d; git clone https://github.com/alhassy/emacs.d.git ~/.emacs.d
Open Emacs and watch download and set up many other things … ^_^
This may take ~15 minutes —we install a massive LaTeX setup.
We get: Spell checker, dictionary, LaTeX + pygements, Dropbox, AG (for fast system-wide searching of a string with helm-do-grep-ag, useful for finding definitions), Amethyst window manager.
Amethyst requires some more setup: Open its preferences, then…
- Then select:
Mouse: Focus follows mouse
. - Also:
Shortcuts
, then disable ‘increase/decrease main pane count’ bindings since they override the beloved EmacsM-<,>
keys.
For convenience, on a Mac, add the home (~/
) directory to the default file
navigator: Finder → Preferences → Sidebar, then select home 🏠.
If you notice any “file system access” concerns, give Emacs permissions to read
your files: General Settings → Security & Privacy → Full Disk Access → ⌘-M-g
(to
search) then enter /usr/bin/ruby
—Emacs is launched via a Ruby script in
MacOS.
1. Why Emacs?
A raw code file is difficult to maintain, especially for a large system such as Emacs. Instead, we're going with a ‘literate programming’ approach: The intialisation configuration is presented in an essay format, along with headings and subheadings, intended for consumption by humans such as myself, that, incidentally, can be ‘tangled’ into a raw code file that is comprehensible by a machine. We achieve this goal using org-mode —which is Emacs' killer app.
Super Simple Intro to Emacs’ Org-mode
Emacs’ Org-mode is an outliner, a rich markup language, spreadsheet tool, literate programming system, and so much more. It is an impressive reason to use Emacs (•̀ᴗ•́)و
Org-mode syntax is very natural; e.g., the following is Org-mode! ( Org Mode Is One of the Most Reasonable Markup Languages to Use for Text )
+ Numbered and bulleted lists are as expected. - Do the things: 1. This first 2. This second 44. [@44] This forty-fourth - =[@𝓃]= at the beginning of an iterm forces list numbering to start at 𝓃 - =[ ]= or =[X]= at the beginning for checkbox lists - Use =Alt ↑, ↓= to move items up and down lists; renumbering happens automatically. + Definitions lists: ~- term :: def~ + Use a comment, such as =# separator=, between two lists to communicate that these are two lists that /happen/ to be one after the other. Or use any /non-indented/ text to split a list into two. * My top heading, section words ** Child heading, subsection more words *** Grandchild heading, subsubsection even more!
Export In Emacs, press C-c C-e h o to obtain an HTML webpage ---like this one!— of the Org-mode markup; use C-c C-e l o to obtain a PDF rendition.
You can try Org-mode notation and see how it renders live at: http://mooz.github.io/org-js/
You make a heading by writing * heading
at the start of a line, then you can
TAB to fold/unfold its contents. A table of contents, figures, tables can be
requested as follows:
# figures not implemented in the HTML backend # The 𝓃 is optional and denotes headline depth #+toc: headlines 𝓃 #+toc: figures #+toc: tables
Markup elements can be nested.
Syntax Result /Emphasise/
, italicsEmphasise *Strong*
, boldStrong */very strongly/*
, bold italicsvery strongly =verbatim=
, monospaced typewriterverbatim
+deleted+
deleted_inserted_
inserted super^{script}ed
superscripted sub_{scripted}ed
subscripteded - Markup can span across multiple lines, by default no more than 2.
- In general, markup cannot be ‘in the middle’ of a word.
- New lines demarcate paragraphs
- Use
\\
to force line breaks without starting a new paragraph - Use at least 5 dashes,
-----
, to form a horizontal rule
provides support for numerous other kinds of markup elements, such as red:hello
which becomes “ hello ”.
Working with tables
#+ATTR_HTML: :width 100% #+name: my-tbl #+caption: Example table | Who? | What? | |------+-------| | me | Emacs | | you | Org |
Note the horizontal rule makes a header row and is formed by typing doit then pressing TAB. You can TAB between cells.
- You can make an empty table with
C-c |
, which is just org-table-create-or-convert-from-region, then give it row×column dimensions. - Any lines with comma-separated-values (CSV) can be turned into an Org table by
selecting the region and pressing
C-u C-c |
. (Any CSV file can thus be visualised nicely as an Org table). - Use
C-u C-u C-u C-c |
to make a table from values that are speared by a certain regular expression.
Working with links
Link syntax is [[source url][description]]
; e.g., we can refer to the above
table with [[my-tbl][woah]]
.
Likewise for images: file:path-to-image.
Mathematics
Source
\[ \sin^2 x + \cos^2 x = \int_\pi^{\pi + 1} 1 dx = {3 \over 3} \]
Result
\[ \sin^2 x + \cos^2 x = \int_\pi^{\pi + 1} 1 dx = {3 \over 3} \]
- Instead of
\[...\]
, which displays a formula on its own line, centred, use$...$
to show a formula inline. - Captioned equations are numbered and can be referenced via links, as shown below.
Source
#+name: euler \begin{equation} e ^ {i \pi} + 1 = 0 \end{equation} See equation [[euler]].
Result
\begin{equation} \label{orgbefd1a4} e ^ {i \pi} + 1 = 0 \end{equation}See equation \eqref{orgbefd1a4}.
Source code
Source
#+BEGIN_SRC C -n int tot = 1; (ref:start) for (int i = 0; i != 10; i++) (ref:loop) tot *= i; (ref:next) printf("The factorial of 10 is %d", tot); #+END_SRC
Result
1: int tot = 1; (start) 2: for (int i = 0; i != 10; i++) (loop) 3: tot *= i; (next) 4: printf("The factorial of 10 is %d", tot);
The labels (ref:name)
refer to the lines in the source code and can be
referenced with link syntax: [[(name)]]
. Hovering over the link, in the HTML
export, will dynamically highlight the corresponding line of code. To strip-out
the labels from the displayed block, use -r -n
in the header so it becomes
#+begin_src C -r -n
, now the references become line numbers.
Another reason to use Org:
If you use :results raw
, you obtain dynamic templates that may use Org-markup:
Source
#+BEGIN_SRC C printf("*bold* +%d+ (strikethrough) /slanted/", 12345); #+END_SRC ♯+RESULTS: *bold* +12345+ (strikethrough) /slanted/
Result
printf("*bold* +%d+ (strikethrough) /slanted/", 12345);
♯+RESULTS:
bold 12345 (strikethrough) slanted
The #+RESULTS:
is obtained by pressing C-c C-c on the src
block, to execute
it and obtain its result.
Also: Notice that a C program can be run without a main
;-)
That is, we can write code in between prose that is intended to be read like an essay:
Single source of truth: This mini-tutorial can be included into other Org files by declaring
#+include: ~/.emacs.d/init.org::#Mini-tutorial-on-Org-mode |
For more, see https://orgmode.org/features.html.
Emacs is a flexible platform for developing end-user applications —unfortunately it is generally perceived as merely a text editor. Some people use it specifically for one or two applications.
For example, writers use it as an interface for Org-mode and others use it as an interface for version control with Magit. Org is an organisation tool that can be used for typesetting which subsumes LaTeX, generating many different formats –html, latex, pdf, etc– from a single source, keeping track of schedules & task management, blogging, habit tracking, personal information management tool, and much more. Moreover, its syntax is so natural that most people use it without even knowing! For me, Org allows me to do literate programming: I can program and document at the same time, with no need to seperate the two tasks and with the ability to generate multiple formats and files from a single file.
A list of programs that can be replaced by Emacs
Pieces of (disparate) software can generally be replaced by (applications written on the) Emacs (text processing Lisp platform).
From the table below, of non-editing things you can do with Emacs, it's reasonable to think of Emacs as an operating system —and Vim/Evil is one of its text editors.
Application | ≈ | Emacs Package |
---|---|---|
Habit Tracker / TODO-list | Org mode | |
Agenda / Calendar / Time Tracker | Org mode | |
Literate Programming (like Jupyter) | Org mode | |
Blogging Software | Org mode | |
Reference Information Platform | Org mode with refile and my/reference | |
Word Processing / PDFs / Slidedeck tool | Org mode | |
Spell checker & dictionary & grammar checker | ispell & langtool | |
Reference and citation manager | org-ref | |
PDF Viewer | PDF View mode | |
Powerful Calculator | Calc-mode (Nice article on literate calc mode) | |
Fillable Forms / Data Entry | Widgets | |
Ebook Reader | nov.el and calibredb.el | |
Git / Version control | Magit or vc-mode | |
Shells | eshell or shell | |
Terminal emulators | term, ansi-term, and vterm | |
Package Manager | helm-system-packages | |
File Manager | dired | |
IDE / debugger | LSP / Dap | |
Scripting Language | Emacs Lisp | |
[Neo]Vim / Modal text editor | EVIL mode | |
RSS Newsreader | ElFeed | |
Gnus / Mu4e [very pretty!] / notmuch | ||
Spredsheet tool | Org Table / Simple Emacs Spreadsheet / spreadsheet-mode / csv-mode | |
Automatic file backups | ⟨Built-in⟩ & backup-walker | |
seemless GPG tool | ⟨Built-in⟩ | |
Lisp interpreter | Anywhere press C-x C-e to run a Lisp expression | |
Documentation viewer | tldr-mode; C-h o / describe-symbol | |
Diff / Merge tool | ediff, diff | |
Games | tetris, pacman, mario, etc | |
Psychologist | doctor | |
Typing tutor | typing-of-emacs | |
Modern Internet Browser | xwidget-webkit-browse-url | |
everything else | EAF |
I’m down to essentially Emacs and Chrome for almost all my work —I like using Chrome; I like the integration of all things Google.
- The Nyxt browser is an eerily Emacs-like browser ;-)
Were I “only coding”, then I'd use a popular Integrated Development Environment that requires minimal setup and Just Worksᵀᴹ; but I blog, make cheat sheets, run background services, etc, and so I need an Integrated Computing Environment: Emacs.
If you are a professional writer…Emacs outshines all other editing software in approximately the same way that the noonday sun does the stars. It is not just bigger and brighter; it simply makes everything else vanish. —Neal Stephenson, In the beginning was the command line
Extensible ⇒ IDEs are generally optimised for one framework, unlike Emacs!
- You can program Emacs to automate anything you want.
- Hence, it's an environment, not just an editor.
- ⇒ Unified keybinding across all tools in your environment.
Users are given a high-level full-featured programming language, not just a small configuration language. For the non-programmers, there is Custom, a friendly point-and-click customisation interface.
- Self Documented ⇒ Simply M-x info-apropos or C-h d to search all manuals or look up any function provided by Emacs!
- Mature ⇒ tool with over 40 years of user created features
- Plugins for nearly everything!
- No distinction between built-ins and user-defined features! (Lisp!)
- You can alter others' code without even touching the source.
- Advising functions and ‘hooking’ functionality onto events.
- Free software ⇒ It will never die!
- Emacs is one of the oldest open source projects still under developement.
Of course Emacs comes with the basic features of a text editor, but it is much more; for example, it comes with a powerful notion of ‘undo’: Basic text editors have a single stream of undo, yet in Emacs, we have a tree –when we undo and make new edits, we branch off in our editing stream as if our text was being version controlled as we type! –We can even switch between such branches!
;; Allow tree-semantics for undo operations. (use-package undo-tree :diminish ;; Don't show an icon in the modeline :bind ("C-x u" . undo-tree-visualize) :hook (org-mode . undo-tree-mode) ;; For some reason, I need this. FIXME. :config ;; Always have it on (global-undo-tree-mode) ;; Each node in the undo tree should have a timestamp. (setq undo-tree-visualizer-timestamps t) ;; Show a diff window displaying changes between undo nodes. (setq undo-tree-visualizer-diff t)) ;; Execute (undo-tree-visualize) then navigate along the tree to witness ;; changes being made to your file live!
( The above snippet has a noweb-ref
: It is presented here in a natural
position, but is only executable once use-package
is setup and so it
is weaved there! We can present code in any order and tangle it to
the order the compilers need it to be! )
Emacs is an extensible editor: You can make it into the editor of your dreams!
You can make it suited to your personal needs.
If there's a feature you would like, a behaviour your desire, you can simply code that into Emacs with
a bit of Lisp. As a programming language enthusiast, for me Emacs is my default Lisp interpreter
and a customisable IDE that I use for other programming languages
–such as C, Haskell, Agda, Lisp, and Prolog.
Moreover, being a Lisp interpreter, we can alter the look and feel of Emacs live, without having
to restart it –e.g., press C-x C-e after the final parenthesis of (scroll-bar-mode 0)
to run the code that removes the scroll-bar.
I use Emacs every day. I rarely notice it. But when I do, it usually brings me joy. ─Norman Walsh
I have used Emacs as an interface for developing cheat sheets, for making my blog, and as an application for ‘interactively learning C’. If anything Emacs is more like an OS than just a text editor –“living within Emacs” provides an abstraction over whatever operating system my machine has: It's so easy to take everything with me. Moreover, the desire to mould Emacs to my needs has made me a better programmer: I am now a more literate programmer and, due to Elisp's documentation-oriented nature, I actually take the time and effort to make meaningful documentation –even when the project is private and will likely only be seen by me.
Seeing Emacs as an editor is like seeing a car as a seating-accommodation. – Karl Voit
1.1. Emacs is a flexible platform for developing end-user applications
Just as a web browser is utilised as a platform for deploying applications, or ‘extensions’, written in JavaScript that act on HTML documents, Emacs is a platform for deploying applications written in Emacs Lisp that act on buffers of text. In the same vein, people who say Emacs having Tetris is bloat are akin to non-coders who think their browser has bloat since it has a “view page source” feature —which nearly all browsers have, yet it's only useful to web developers. Unlike a web browser in which the user must get accustomed to its features, Emacs is customised to meet the needs of its user. ( Incidentally, Emacs comes bundled with a web browser. )
In the case of Emacs the boundary between user and programmer is blurred as adapting the environment to one’s needs is already an act of programming with a very low barrier to entry. ---rekado
Don't just get used to your tool, make it get used to you!
Emacs is not just an editor, but a host for running Lisp applications!
For example, Emacs is shipped as a language-specific IDE to a number of communities —e.g., Oz, Common Lisp, and, most notably, Agda. Emacs is a great IDE for a language —one just needs to provide a ‘major mode’ and will then have syntax highlighting, code compleition, jumping to definitions, etc. There is no need to make an IDE from scratch.
1.2. The Power of Text Manipulation
Emacs has ways to represent all kinds of information as text.
E.g., if want to make a regular expression rename of files in a directory, there's no need to learn about a batch renaming tool: M-x dired ⟨RET⟩ M-x wdired-change-to-wdired-mode now simply perform a usual find-and-replace, then save with the usual C-x C-s to effect the changes!
Likewise for other system utilities and services (•̀ᴗ•́)و
Moreover, as will be shown below, you can literally use Emacs anywhere for textually input in your operating system –no copy-paste required.
1.4. Emacs Proverbs as Koan
Below is an extract from William Cobb's “Reflections on the Game of Go”, with minor personalised adjustements for Emacs. Enjoy!
The Japanese term satori refers to the experience of enlightenment, the realisation of how things really are that is the primary aim of practice and meditation. However, the Zen tradition is famous for claiming that one cannot say what it is that one realises, that is, one cannot articulate the content of the enlightenment experience. Although it makes everything clear, it is an experience beyond words. Instead of being given an explanation of how things are, the student of Zen hears sayings called koan, often somewhat paradoxical in character, that come from those who are enlightened:
- “There are no CTRL and META.”
- “If you meet an Emacs you dislike, kill it.”
- “No one knows Emacs.”
- “One can only learn Emacs by living within it.”
- “To know Org mode is to know oneself.”
It is important to realise that koan are intended to move you off of one path and onto another. They are not just attempts to mystify you. For example, the first proverb is in regards to newcomers complaining about too many keybinings —eventually it's muscle memory—, whereas the second is about using the right tool for the right task —Emacs is not for everyone. The fourth is, well, Emacs is an operating system.
1.5. Possibly Interesting Reads
- The Emacs Tour
- How to Learn Emacs: A Hand-drawn One-pager for Beginners / A visual tutorial
- Video Series on Why Emacs Rocks —catch the enthusiasm!
EMACS: The Extensible, Customizable Display Editor
“The programmable editor is an outstanding opportunity to learn to program!”
- What is free software?
- Emacs org-mode examples and cookbook
- An Opinionated Emacs guide for newbies and beyond
- Emacs Mini-Manual, Part I of III
- Org and R Programming —a tutorial on literate programming, e.g., evaluating code within
src
bloc. - Reference cards for GNU Emacs, Org-mode, and Elisp.
- “When did you start using Emacs” discussion on Reddit
- “How to Learn Emacs”
- The Org-mode Reference Manual or Worg: Community-Written Docs which includes a meta-tutorial.
- Awesome Emacs: A community driven list of useful Emacs packages, libraries and others.
- A list of people's nice emacs config files
- Read Lisp, Tweak Emacs: How to read Emacs Lisp so that you can customize Emacs
- Why Racket? Why Lisp?
—If eye-candy, a sleek and beautiful GUI, would entice you then consider starting with spacemacs. Here's a helpful installation video, after which you may want to watch Org-mode in Spacemacs tutorial—
Remember: Emacs is a flexible platform for developing end-user applications; e.g., this configuration file is at its core an Emacs Lisp program that yields the editor of my dreams –it encourages me to grow and to be creative, and I hope the same for all who use it; moreover, it reflects my personality such as what I value and what I neglect in my workflow.
I’m stunned that you, as a professional software engineer, would eschew inferior computer languages that hinder your ability to craft code, but you put up with editors that bind your fingers to someone else’s accepted practice. ---Howard Abrams
1.6. Fun commands to try out
Finally, here's some fun commands to try out:
M-x doctor
—generalising the idea of rubber ducksM-x tetris
orM-x gomoku
orM-x snake
—a break with a classicC-u 𝓃 M-x hanoi
for the 𝓃-towers of Hanoi
M-x butterfly
—in reference to “real programmers”
A neat way to get started with Emacs is to solve a problem you have, such as taking notes or maintaining an agenda —both with Org-mode.
Before we get started…
1.7. What Does Literate Programming Look Like?
Briefly put, literate programming in Emacs allows us to evaluate source code within our text files, then using the results as values in other source blocks. When presenting an algorithm, we can talk it out, with a full commentary thereby providing ‘reproducible research’: Explorations and resulting algorithms are presented together in a natural style.
⟨ This image was created in org-mode; details below or by looking at the source file 😉 ⟩
Here's an example of showing code in a natural style, but having the resulting code appear in a style amicable to a machine. Here's what you type:
It's natural to decompose large problems, #+begin_src haskell :noweb-ref defn-of-f :tangle no f = h ∘ g #+end_src But we need to define $g$ and $h$ /beforehand/ before we can use them. Yet it's natural to “motivate” their definitions ---rather than pull a rabbit out of hat. Org lets us do that! Here's one definition, #+begin_src haskell :noweb-ref code-from-other-places :tangle no g = ⋯ #+end_src then the other. #+begin_src haskell :noweb-ref code-from-other-places :tangle no h = ⋯ #+end_src Of-course, we might also want a preamble: #+BEGIN_SRC haskell :tangle myprogram.hs import ⋯ #+END_SRC We can now tangle together the tagged code blocks in the order we want. #+BEGIN_SRC haskell :tangle myprogram.hs :comments none :noweb yes <<code-from-other-places>> <<defn-of-f>> #+END_SRC ( You can press “C-c C-v C-v” to see what this block expands into! )
Now C-c C-v C-t (org-babel-tangle) yields a file named myprogram.hs
with contents in an order
amicable to a machine.
import ⋯ g = ⋯ h = ⋯ f = h ∘ g
Interestingly, unlike certain languages, Haskell doesn't care too much about declaration order.
Warning! If we have different language blocks tangled to the same file, then
they are tangled alphabetically —e.g., if one of the blocks is marked
emacs-lisp
then its contents will be the very first one in the resulting source
file, since emacs-lisp
begins with e
which is alphabetically before h
of
haskell
.
1.8. Why a monolithic configuration?
Why am I keeping my entire configuration —from those involving cosmetics &
prose to those of agendas & programming— in one file? Being monolithic —“a
large, mountain-sized, indivisible block of stone”— is generally not ideal in
nearly any project: E.g., a book is split into chapters and a piece of software
is partitioned into modules. Using Org-mode, we can still partition our setup
while remaining in one file. An Emacs configuration is a personal, leisurely
project, and one file is a simple architecture: I don't have to worry about many
files and the troubles of moving content between them; instead, I have headings
and move content almost instantaneously —org-refile by pressing w
at the start
of the reader. Moreover, being one file, it is easy to distribute and to extract
artefacts from it —such as the README for Github, the HTML for my blog, the
colourful PDF rendition, and the all-important Emacs Lisp raw code
file. Moreover, with a single #
I can quickly comment out whole sections,
thereby momentarily disabling features.
There's no point in being modular if there's nothing explaining what's going on, so I document.
The section of this read further argues the benefits of maintaining
literate, and monolithic, configuration files. As a convention, I will try to
motivate the features I set up and I will prefix my local functions with, well,
my/
—this way it's easy to see all my defined functions, and this way I cannot
accidentally shadow existing utilities. Moreover, besides browsing the web, I do
nearly everything in Emacs and so the start-up time is unimportant to me: Once
begun, I have no intention of spawning another instance nor closing the current
one. ( Upon an initial startup using this configuration, it takes a total of
121 seconds to install all the packages featured here. )
Enjoy!
2. Booting Up
Let's decide on where we want to setup our declarations for personalising Emacs to our needs. Then, let's bootstrap Emacs' primintive packaging mechanism with a slick interface —which not only installs Emacs packages but also programs at the operating system level, all from inside Emacs! Finally, let's declare who we are and use that to setup Emacs email service.
2.1. ~/.emacs
vs. init.org
Emacs is extensible: When Emacs is started, it tries to load a user's Lisp
program known as an initialisation (‘init’) file which specifies how Emacs
should look and behave for you. Emacs looks for the init file using the
filenames ~/.emacs.el
, ~/.emacs
, or ~/.emacs.d/init.el
—it looks for the first
one that exists, in that order; at least it does so on my machine. Below we'll
avoid any confusion by ensuring that only one of them is in our system.
Regardless, execute C-h o user-init-file to see the name of the init file
loaded. Having no init file is tantamount to have an empty init file.
- One can read about the various Emacs initialisation files online or within Emacs by the sequence C-h i m emacs RET i init file RET.
- A friendly tutorial on ‘beginning a
.emacs
file’ can be read online or within Emacs by C-h i m emacs lisp intro RET i .emacs RET. - After inserting some lisp code, such as
(set-background-color "salmon")
, and saving, one can load the changes with M-x eval-buffer, eval-buffer. - In a terminal, use
emacs -Q
to open emacs without any initialisation files.
Besides writing Lisp in an init file, one may use Emacs' customisation
interface, M-x customize: Point and click to change Emacs to your needs. The
resulting customisations are, by default, automatically thrown into your init
file ---~/.emacs
is created for you if you have no init file. This interface is
great for beginners.
We shall use ~/.emacs.d/init.el
as the initialisation file so that all of our
Emacs related files live in the same directory: ~/.emacs.d/
.
A raw code file is difficult to maintain, especially for a large system such as Emacs. Instead, we're going with a ‘literate programming’ approach: The intialisation configuration is presented in an essay format, along with headings and subheadings, intended for consumption by humans such as myself, that, incidentally, can be ‘tangled’ into a raw code file that is comprehensible by a machine. We achieve this goal using org-mode ---Emacs' killer app— which is discussed in great detail later on.
/Adventure time!/ “Honey, where's my init?”
Let's use the three possible locations for the initialisation files to explore how Emacs finds them. Make the following three files.
~/.emacs.el
;; Emacs looks for this first; (set-background-color "chocolate3") (message-box ".emacs.el says hello")
~/.emacs
;; else; looks for this one; (set-background-color "plum4") (message-box ".emacs says hello")
~/.emacs.d/init.el
;; Finally, if neither are found; it looks for this one. (set-background-color "salmon") (message-box ".emacs.d/init.el says hello")
Now restart your Emacs to see how there super tiny initilaisation files affect your editor. Delete some of these files in-order for others to take effect!
Adventure time! Using Emacs’ Easy Customisation Interface
~/.emacs
since
Emacs may explicitly add, or alter, code in it.
Let's see this in action!
Execute the following to see additions to the ~/.emacs
have been added by
‘custom’.
- M-x customize-variable RET line-number-mode RET
- Then press: toggle, state, then 1.
- Now take a look: C-x C-f ~/.emacs
Support for ‘Custom’
README.org
's and other matters, so Emacs' Custom utility
will remember to not prompt me each time for the safety of such local variables.
(setq custom-file "~/.emacs.d/custom.el") (ignore-errors (load custom-file)) ;; It may not yet exist.
2.2. Who am I?
Let's set the following personal Emacs-wide variables —to be used locations such as email.
(setq user-full-name "Musa Al-hassy" user-mail-address "alhassy@gmail.com")
For some fun, run this cute method.
(animate-birthday-present user-full-name)
2.3. Emacs Package Manager
There are a few ways to install packages —run C-h C-e for a short overview. The easiest, for a beginner, is to use the command package-list-packages then find the desired package, press i to mark it for installation, then install all marked packages by pressing x.
- Interactively: M-x list-packages to see all melpa packages that can install
- Press Enter on a package to see its description.
- Or more quickly, to install, say, unicode fonts: M-x package-install RET unicode-fonts RET.
“From rags to riches”: Recently I switched to Mac —first time trying the OS.
I had to do a few package-install
's and it was annoying. I'm looking for the
best way to package my Emacs installation —including my installed packages and
configuration— so that I can quickly install it anywhere, say if I go to
another machine. It seems use-package allows me to configure and auto
install packages. On a new machine, when I clone my .emacs.d
and start Emacs,
on the first start it should automatically install and compile all of my
packages through use-package
when it detects they're missing. ♥‿♥
First we load package
, the built-in package manager. It is by default only
connected to the GNU ELPA (Emacs Lisp Package Archive) repository, so we
extended it with other popular repositories; such as the much larger MELPA
(Milkypostman's ELPA) —it builds packages directly from the source-code
repositories of developers rather than having all packages in one repository.
;; Make all commands of the “package” module present. (require 'package) ;; Internet repositories for new packages. (setq package-archives '(("gnu" . "http://elpa.gnu.org/packages/") ("nongnu" . "https://elpa.nongnu.org/nongnu/") ("melpa" . "http://melpa.org/packages/"))) ;; Update local list of available packages: ;; Get descriptions of all configured ELPA packages, ;; and make them available for download. (package-refresh-contents)
- All installed packages are placed, by default, in
~/.emacs.d/elpa
. - Neato: If one module requires others to run, they will be installed automatically.
The declarative configuration tool use-package is a macro/interface that manages our packages and the way they interact.
(unless (package-installed-p 'use-package) (package-install 'use-package)) (require 'use-package)
We can now invoke (use-package XYZ :ensure t)
which should check for the XYZ
package and makes sure it is accessible. If the file is not on our system, the
:ensure t
part tells use-package
to download it —using the built-in package
manager— and place it somewhere accessible, in ~/.emacs.d/elpa/
by default.
By default we would like to download packages, since I do not plan on installing
them manually by downloading Lisp files and placing them in the correct places
on my system.
(setq use-package-always-ensure t)
Notice that use-package allows us to tersely organise a package's
configuration —and that it is not a package manger, but we can make it one by
having it automatically install modules, when needed, using :ensure t
.
Super Simple ‘use-package’ Mini-tutorial
Here are common keywords we will use, in super simplified terms.
:init f₁ … fₙ
Always executes code formsfᵢ
before loading a package.:diminish str
Uses optional stringstr
in the modeline to indicate this module is active. Things we use often needn't take real-estate down there and so no we provide nostr
.:config f₁ … fₙ
Only executes code formsfᵢ
after loading a package.The remaining keywords only take affect after a module loads.
:bind ((k₁ . f₁) … (kₙ . fₙ)
Lets us bind keyskᵢ
, such as"M-s o"
, to functions, such asoccur
.- When n = 1, the extra outer parenthesis are not necessary.
:hook ((m₁ … mₙ) . f)
Enables functionalityf
whenever we're in one of the modesmᵢ
, such asorg-mode
. The. f
, along with the outermost parenthesis, is optional and defaults to the name of the package —Warning: Erroneous behaviour happens if the package's name is not a function provided by the package; a common case is when package's name does not end in-mode
, leading to the invocation((m₁ … mₙ) . <whatever-the-name-is>-mode)
instead.Additionally, when n = 1, the extra outer parenthesis are not necessary.
Outside of
use-package
, one normally uses aadd-hook
clause. Likewise, an ‘advice’ can be given to a function to make it behave differently —this is known as ‘decoration’ or an ‘attribute’ in other languages.:custom (k₁ v₁ d₁) … (kₙ vₙ dₙ)
Sets a package's custom variableskᵢ
to have valuesvᵢ
, along with optional user documentationdᵢ
to explain to yourself, in the future, why you've made this decision.This is essentially
setq
within:config
.- Use the standalone keyword
:disabled
to turn off loading a module that, say, you're not using anymore.
We now bootstrap use-package
.
The use of :ensure t
only installs absent modules, but it does no updating.
Let's set up an auto-update mechanism.
(use-package auto-package-update :config ;; Delete residual old versions (setq auto-package-update-delete-old-versions t) ;; Do not bother me when updates have taken place. (setq auto-package-update-hide-results t) ;; Update installed packages at startup if there is an update pending. (auto-package-update-maybe))
Here's another example use of use-package
. Later on, I have a “show recent files
pop-up” command set to C-x C-r
; but what if I forget? This mode shows me all key
completions when I type C-x
, for example. Moreover, I will be shown other
commands I did not know about! Neato :-)
;; Making it easier to discover Emacs key presses. (use-package which-key :diminish :config (which-key-mode) (which-key-setup-side-window-bottom) (setq which-key-idle-delay 0.05))
⟨ Honestly, I seldom even acknowledge this pop-up; but it's always nice to show to people when I'm promoting Emacs. ⟩
Above, the :diminish
keyword indicates that we do not want the mode's name to be
shown to us in the modeline —the area near the bottom of Emacs. It does so by
using the diminish
package, so let's install that.
(use-package diminish)
Here are other packages that I want to be installed onto my machine.
;; Haskell's cool (use-package haskell-mode :defer t) ;; Lisp libraries with Haskell-like naming. (use-package dash) ;; “A modern list library for Emacs” (use-package s ) ;; “The long lost Emacs string manipulation library”. ;; Let's use the “s” library. (defvar my/personal-machine? (not (s-contains? "work-machine" (shell-command-to-string "scutil --get ComputerName"))) "Is this my personal machine, or my work machine? At one point, on my work machine I run the following command to give the machine a sensible name. sudo scutil --set ComputerName work-machine dscacheutil -flushcache") (defvar my/work-machine? (not my/personal-machine?)) ;; Library for working with system files; ;; e.g., f-delete, f-mkdir, f-move, f-exists?, f-hidden? (use-package f)
Note:
- dash: “A modern list library for Emacs”
- E.g.,
(--filter (> it 10) (list 8 9 10 11 12))
- E.g.,
- s: “The long lost Emacs string manipulation library”.
- E.g.,
s-trim, s-replace, s-join
.
- E.g.,
Remember that snippet for undo-tree
in the introductory section?
Let's activate it now, after use-package
has been setup.
;; Allow tree-semantics for undo operations. (use-package undo-tree :diminish ;; Don't show an icon in the modeline :bind ("C-x u" . undo-tree-visualize) :hook (org-mode . undo-tree-mode) ;; For some reason, I need this. FIXME. :config ;; Always have it on (global-undo-tree-mode) ;; Each node in the undo tree should have a timestamp. (setq undo-tree-visualizer-timestamps t) ;; Show a diff window displaying changes between undo nodes. (setq undo-tree-visualizer-diff t)) ;; Execute (undo-tree-visualize) then navigate along the tree to witness ;; changes being made to your file live!
DRY: Don 't Repeat Yourself!
In the HTML export, above it looks like I just copy-pasted the undo tree setup from earlier, but that is not the case! All I did was declare to Org that I'd like that named snippet to be tangled now, here in the resulting code file.
#+begin_src emacs-lisp :noweb yes <<undo-tree-setup>> #+end_src
You can press C-c C-v C-v, org-babel-expand-src-block, to see what this block expands into —which is what was shown above.
Quelpa allows us to build Emacs packages directly from source repositories. It
derives its name from the German word Quelle, for souce [code], adjoined to
ELPA. Its use-package
interface allows us to use use-package
like normal but
when we want to install a file from souce we use the keyword :quelpa
.
(use-package quelpa :custom (quelpa-upgrade-p t "Always try to update packages") :config ;; Get ‘quelpa-use-package’ via ‘quelpa’ (quelpa '(quelpa-use-package :fetcher git :url "https://github.com/quelpa/quelpa-use-package.git")) (require 'quelpa-use-package))
Let's use this to obtain an improved info-mode from the EmacsWiki. [Disabled for now]
(use-package info+ :disabled :quelpa (info+ :fetcher wiki :url "https://www.emacswiki.org/emacs/info%2b.el"))
2.4. Installing OS packages, and automatically keeping my system up to data, from within Emacs
Sometimes Emacs packages depend on existing system binaries, use-package
let's
us ensure these exist using the :ensure-system-package
keyword extension.
- This is like
:ensure t
but operates at the OS level and uses your default OS package manager. - It has multiple features.
Let's obtain the extension.
;; Auto installing OS system packages (use-package use-package-ensure-system-package :config (system-packages-update)) ;; Please don't bother me when shell buffer names are in use, just make a new ;; buffer. (setq async-shell-command-buffer 'new-buffer) ;; Display the output buffer for asynchronous shell commands only when the ;; command generates output. (setq async-shell-command-display-buffer nil) ;; Don't ask me if I want to kill a buffer with a live process attached to it; ;; just kill it please. (setq kill-buffer-query-functions (remq 'process-kill-buffer-query-function kill-buffer-query-functions)) ;; Ensure our operating system is always up to date. ;; This is run whenever we open Emacs & so wont take long if we're up to date. ;; It happens in the background ^_^ ;; ;; After 5 seconds of being idle, after starting up. (defvar my/installed-packages (shell-command-to-string "brew list") "What is on my machine already? Sometimes when I install a GUI based application and do not have access to it everywhere in my path, it may seem that I do not have that application installed. For instance, (system-packages-package-installed-p \"google-chrome\") returns nil, even though Google Chrome is on my machine. As such, we advise the `system-packages-ensure' installtion method to only do installs of pacakges that are not in our `my/installed-packages' listing. ") (advice-add 'system-packages-ensure :before-until (lambda (pkg) (s-contains-p pkg my/installed-packages)))
After an update to Mac OS, one may need to restore file system access privileges to Emacs.
Here's an example use for Emacs packages that require OS packages:
(shell-command-to-string "type rg") ;; ⇒ rg not found (use-package rg :ensure-system-package rg) ;; ⇒ There's a buffer *system-packages* ;; installing this tool at the OS level!
If you look at the *Messages*
buffer, via C-h e
, on my machine it says
brew install rg: finished
—it uses brew
which is my OS package manager!
- The use-package-ensure-system-package documentation for a flurry of use cases.
The extension makes use of system-packages; see its documentation to learn
more about managing installed OS packages from within Emacs. This is itself
a powerful tool, however it's interface M-x system-packages-install
leaves much
to be desired —namely, tab-compleition listing all available packages,
seeing their descriptions, and visiting their webpages.
This is remedied by M-x helm-system-packages then RET
to see a system
package's description, or TAB
for the other features!
This is so cool!
;; An Emacs-based interface to the package manager of your operating system. (use-package helm-system-packages)
The Helm counterpart is great for discovarability, whereas
the plain system-packages
is great for programmability.
(setq system-packages-noconfirm :do-not-prompt-me-about-confirms) ;; After 1 minute after startup, kill all buffers created by ensuring system ;; packages are present. (run-with-timer 60 nil (lambda () (kill-matching-buffers ".*system-packages.*" t :kill-without-confirmation)))
It is tedious to arrange my program windows manually, and as such I love tiling window managers, which automatically arrange them. I had been using xmonad until recently when I obtained a Mac machine and now use Amethyst —“Tiling window manager for macOS along the lines of xmonad.”
;; Unlike the Helm variant, we need to specify our OS pacman. (when (eq system-type 'darwin) (setq system-packages-package-manager 'brew)) ;; If the given system package doesn't exist; install it. (when (eq system-type 'darwin) (system-packages-ensure "amethyst")) ;; This is a MacOS specific package. (ignore-errors (system-packages-ensure "google-chrome")) ;; My choice of web browser (system-packages-ensure "microsoft-teams") ;; For remote work meetings ;; Gif maker; needs privileges to capture screen. ;; ;; ⇒ Move the screen capture frame while recording. ;; ⇒ Pause and restart recording, with optional inserted text messages. ;; ⇒ Global hotkey (shift+space) to toggle pausing while recording (system-packages-ensure "licecap") ;; Use: ⌘-SPACE licecap ;; Pack, ship and run any application as a lightweight container (system-packages-ensure "docker") ;; Free universal database tool and SQL client (system-packages-ensure "dbeaver-community") ;; Kubernetes IDE (system-packages-ensure "lens") ;; Platform built on V8 to build network applications ;; Also known as: node.js, node@16, nodejs, npm (system-packages-ensure "node") ;; https://nodejs.org/ ;; Nice: https://nodesource.com/blog/an-absolute-beginners-guide-to-using-npm/ ;; Manage multiple Node.js versions (shell-command "curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.38.0/install.sh | bash") ;; According to https://github.com/nvm-sh/nvm, nvm shouldn't be installed via brew.
#+ENDSRC
Amethyst requires some more setup: Open its preferences, then…
- Then select:
Mouse: Focus follows mouse
. - Also:
Shortcuts
, then disable ‘increase/decrease main pane count’ bindings since they override the beloved EmacsM-<,>
keys.
Neato! Now I can live in Emacs even more ^_^
(Open Scripting Architecture (OSA) Scripts) Amethyst is great, but it has a problem of randomly not working. Unfortunatley it has no command line interface, so let's make one in Emacs: Now ⌘-a r relaunches Amethyst.
Details
(defun ⌘-quit (app) "Kill application APP; e.g., “amethyst” or “Safari”" (shell-command (format "osascript -e 'quit app \"%s\"'" app))) (defun ⌘-open (app) "Open application APP; e.g., “amethyst” or “Safari”" (async-shell-command (format "osascript -e 'launch app \"%s\"'" app))) ;; (bind-key "???-a r" #'my/relaunch-amethyst) (defun my/relaunch-amethyst () (interactive) (⌘-quit "amethyst") (⌘-open "amethyst"))
We use the osascript
command to tell
the System Events application
to issue
keystrokes to other applications. I found out about by Googling “How to send
keystrokes from terminal”.
;; (bind-key "???-a c" #'amethyst/cycle-layout) (defun amethyst/cycle-layout () (interactive) (shell-command "osascript -e 'tell application \"System Events\" to keystroke space using {shift down, option down}'"))
If you get:
36:51: execution error: System Events got an error: osascript is not allowed to send keystrokes. (1002)
Then: Go to Security & Privacy -> Privacy tab -> Accessibility -> Add osascript (/usr/bin/osascript)
You may need to restart Emacs.
Reads:
I enter “⌘” using a TeX input method setup below (called “Agda”).
2.5. Syncing to the System's $PATH
For one reason or another, on OS X it seems that an Emacs instance
begun from the terminal may not inherit the terminal's environment
variables, thus making it difficult to use utilities like pdflatex
when Org-mode attempts to produce a PDF.
(use-package exec-path-from-shell :init (when (memq window-system '(mac ns x)) (exec-path-from-shell-initialize)))
See the exec-path-from-shell documentation for setting other environment variables.
2.6. Restarting Emacs —Keeping buffers open across sessions?
Sometimes I wish to close then reopen Emacs; unsurprisingly someone's thought of implementing that.
;; Provides only the command “restart-emacs”. (use-package restart-emacs ;; If I ever close Emacs, it's likely because I want to restart it. :bind ("C-x C-c" . restart-emacs) ;; Let's define an alias so there's no need to remember the order. :config (defalias 'emacs-restart #'restart-emacs))
The following is disabled. I found it a nuisance to have my files open across sessions —If I'm closing Emacs, it's for a good reason.
;; Keep open files open across sessions. (desktop-save-mode 1) (setq desktop-restore-eager 10)
Instead, let's try the following: When you visit a file, point goes to the last place where it was when you previously visited the same file.
(setq-default save-place t) (setq save-place-file "~/.emacs.d/etc/saveplace")
2.7. “Being at the Helm” —Completion & Narrowing Framework
Whenever we have a choice to make from a list, Helm provides possible
completions and narrows the list of choices as we type. This is extremely
helpful for when switching between buffers, C-x b
, and discovering & learning
about other commands! E.g., press M-x
to see recently executed commands and
other possible commands! “Fuzzy finding”: Press M-x
and just start typing,
methods mentioning what you've typed are suddenly listed! Moreover, C-c i
(helm-imenu) will show you the headers in an Org file or the top-level
variables/functions/types when programming. Finally, whenever a Helm session has
started, toggle follow-mode with C-c C-f
to obtain contextual-awareness; e.g.,
C-c i RET C-c C-f
will change your screen as you scroll through the menu.
(A killer feature is helm-do-grep-ag which will do a search in your whole project, file tree).
Remembrance comes with time, until then ask Emacs! |
Try and be grateful!
(use-package helm :diminish :init (helm-mode t) :bind (("M-x" . helm-M-x) ("C-x C-f" . helm-find-files) ("C-x b" . helm-mini) ;; See buffers & recent files; more useful. ("C-x r b" . helm-filtered-bookmarks) ("C-x C-r" . helm-recentf) ;; Search for recently edited files ("C-c i" . helm-imenu) ;; C.f. “C-x t m” (imenu-list) ;; ("C-u C-c i" . imenu-list) ;; TODO FIXME Key sequence C-u C-c i starts with non-prefix key C-u ("C-h a" . helm-apropos) ;; Look at what was cut recently & paste it in. ("M-y" . helm-show-kill-ring) ("C-x C-x" . helm-all-mark-rings) :map helm-map ;; We can list ‘actions’ on the currently selected item by C-z. ("C-z" . helm-select-action) ;; Let's keep tab-completetion anyhow. ("TAB" . helm-execute-persistent-action) ("<tab>" . helm-execute-persistent-action))) ;; Show me nice file icons when using, say, “C-x C-f” or “C-x b” (use-package helm-icons :custom (helm-icons-provider 'all-the-icons) :config (helm-icons-enable))
Helm provides generic functions for completions to replace tab-completion in Emacs with no loss of functionality.
The
execute-extended-command
, the default “M-x”, is replaced withhelm-M-x
which shows possible command completions.- If we want the
M-x
minibuffer to appear at the top of the screen, or middle, we can use emacs-mini-frame as shown beautifully here. I likehelm-M-x
, for now.
Likewise with
apropos
, which is helpful for looking up commands. It shows all meaningful Lisp symbols whose names match a given pattern.- If we want the
- The ‘Helm-mini’,
C-x b
, shows all buffers, recently opened files, bookmarks, and allows us to create new bookmarks and buffers! The ‘Helm-imenu’,
C-c i
, yields a a menu of all “top-level items” in a file; e.g., functions and constants in source code or headers in an org-mode file.⟳ Nifty way to familarise yourself with a new code base, or one from a while ago.
- When Helm is active,
C-x
lists possible course of actions on the currently selected item.
When helm-mode
is enabled, even help commands make use of it.
E.g., C-h o
runs describe-symbol
for the symbol at point,
and C-h w
runs where-is
to find the key binding of the symbol at point.
Both show a pop-up of other possible commands.
Here's a nifty tutorial: A package in a league of its own: Helm
Let's ensure C-x b
shows us: Current buffers, recent files, and bookmarks
—as well as the ability to create bookmarks, which is via C-x r b
manually.
For example, I press C-x b
then type any string and will have the option of
making that a bookmark referring to the current location I'm working in, or
jump to it if it's an existing bookmark, or make a buffer with that name,
or find a file with that name.
(setq helm-mini-default-sources '(helm-source-buffers-list helm-source-recentf helm-source-bookmarks helm-source-bookmark-set helm-source-buffer-not-found))
Incidentally, Helm even provides an interface for the top
program via
helm-top
. It also serves as an interface to popular search engines
and over 100 websites such as google, stackoverflow, ctan
, and arxiv
.
(system-packages-ensure "surfraw") ; ⇒ “M-x helm-surfraw” or “C-x c s”
If we want to perform a google search, with interactive suggestions,
then invoke helm-google-suggest
—which can be acted for other serves,
such as Wikipedia or Youtube by C-z
. For more google specific options,
there is the google-this
package.
Let's switch to a powerful searching mechanism – helm-swoop. It allows us to
not only search the current buffer but also the other buffers and to make live
edits by pressing C-c C-e
when a search buffer exists. Incidentally, executing
C-s
on a word, region, will search for that particular word, region; then make
changes with C-c C-e
and apply them by C-x C-s
.
(use-package helm-swoop :bind (("C-s" . 'helm-swoop) ;; search current buffer ("C-M-s" . 'helm-multi-swoop-all) ;; Search all buffer ;; Go back to last position where ‘helm-swoop’ was called ("C-S-s" . 'helm-swoop-back-to-last-point) ;; swoop doesn't work with PDFs, use Emacs' default isearch instead. ; :map pdf-view-mode-map ("C-s" . isearch-forward) ) :custom (helm-swoop-speed-or-color nil "Give up colour for speed.") (helm-swoop-split-with-multiple-windows nil "Do not split window inside the current window."))
C-u 𝓃 C-s
does a search but showing 𝓃 contextual lines!helm-multi-swoop-all
,C-M-s
, lets us grep files anywhere!
Lets also use helm-do-grep-ag (C-x c M-g a) search all files in the current directory for a particular (regexp) string
- Shows matches live as you type
- Very helpful when looking for a definition of something
(system-packages-ensure "ag")
Marking my place when I jump around
Let's use M-m
to get a nice menu of places we've been recently.
;; Save/mark a location with “C-u M-m”, jump back to it with “M-m”. (bind-key* "M-m" (lambda () (interactive) (if (not current-prefix-arg) (helm-mark-ring) (push-mark) (message "[To return to this location, press M-m] ∷ %s" (s-trim (substring-no-properties (thing-at-point 'line)))))))
Finally, note that there is now a M-x helm-info
command to show documentation,
possibly with examples, of the packages installed. For example,
M-x helm-info RET dash RET -parition RET
to see how the parition function from the
dash library works via examples ;-)
;; Make `links' from elisp symbols (quoted functions, variables and fonts) in Gnu-Emacs Info viewer to their help documentation. (use-package inform :config (require 'inform))
2.8. Org-Mode Administrivia
Let's conclude this ‘boot-up’ by getting Emacs' killer app, Org-mode, setup; along with the extras that allow us to ignore heading names, but still utilise their contents —e.g., such as a heading named ‘preamble’ that contains org-mode setup for a file.
(use-package emacs :ensure org-contrib :diminish org-indent-mode :config (require 'ox-extra) (ox-extras-activate '(ignore-headlines)))
org-plus-contrib
contain the files that are included with Emacs plus all
contributions from the org-mode repository.
- Use the
:ignore:
tag on headlines you'd like to have ignored, while not ignoring their content. - Use the
:noexport:
tag to omit a headline and its contents.
;; Replace the content marker, “⋯”, with a nice unicode arrow. (setq org-ellipsis " ⤵") ;; Fold all source blocks on startup. (setq org-hide-block-startup t) ;; Lists may be labelled with letters. (setq org-list-allow-alphabetical t) ;; Avoid accidentally editing folded regions, say by adding text after an Org “⋯”. (setq org-catch-invisible-edits 'show) ;; I use indentation-sensitive programming languages. ;; Tangling should preserve my indentation. (setq org-src-preserve-indentation t) ;; Tab should do indent in code blocks (setq org-src-tab-acts-natively t) ;; Give quote and verse blocks a nice look. (setq org-fontify-quote-and-verse-blocks t) ;; Pressing ENTER on a link should follow it. (setq org-return-follows-link t)
I rarely use tables, but here is a useful Org-Mode Table Editing Cheatsheet and a friendly tutorial.
Moreover, since I end up using org-mode most of the time, let's make that the default mode.
(setq initial-major-mode 'org-mode)
Finally, let's get some extra Org-mode mark-up goodies, such as kbd:C-c_C-e
which renders as C-c C-e. Documentations and screenshots at:
https://alhassy.github.io/org-special-block-extras/
;; TODO org-special-block-extras.el:681:1:Error: Symbol’s value as variable is void: o--supported-blocks ;; (when nil use-package org-special-block-extras :hook (org-mode . org-special-block-extras-mode) :custom ;; The places where I keep my ‘#+documentation’ (org-special-block-extras--docs-libraries '("~/org-special-block-extras/documentation.org")) ;; Disable the in-Emacs fancy-links feature? (org-special-block-extras-fancy-links '(elisp badge kbd link-here doc tweet)) ;; Details heading “flash pink” whenever the user hovers over them? (org-html-head-extra (concat org-html-head-extra "<style> summary:hover {background:pink;} </style>")) ;; The message prefixing a ‘tweet:url’ badge (org-special-block-extras-link-twitter-excitement "This looks super neat (•̀ᴗ•́)و:") :config ;; Use short names like ‘defblock’ instead of the fully qualified name ;; ‘org-special-block-extras--defblock’ (org-special-block-extras-short-names)) ;; Let's execute Lisp code with links, as in “elisp:view-hello-file”. (setq org-confirm-elisp-link-function nil)
2.9. Password-locking files —“encryption”
With the following incantation, we name our files 𝒳.𝒴.gpg
where 𝒳 is the file
name and 𝒴 is the usual extension, then upon save we will be prompted for an
encryption method, we can press Enter on OK
to just provide a password for
that file. You can open that file without the passphrase for a limited amount of
time —i.e., it's cached, saved, for your current computing session until
logout— or force authentication by invoking gpgconf --kill gpg-agent
.
(system-packages-ensure "gnupg") ;; i.e., brew install gnupg ;; “epa” ≈ EasyPG Assistant ;; Need the following in init to have gpg working fine: ;; force Emacs to use its own internal password prompt instead of an external pin entry program. (setq epa-pinentry-mode 'loopback) ;; https://emacs.stackexchange.com/questions/12212/how-to-type-the-password-of-a-gpg-file-only-when-opening-it (setq epa-file-cache-passphrase-for-symmetric-encryption t) ;; No more needing to enter passphrase at each save ^_^ ;; ;; Caches passphrase for the current emacs session?
The purpose of encrypting a file is so that an adversary —e.g., an immoral computer administrator or a thief who stole your computer— will have to spend so much decrypting the data than the data is actually worth. As such, one uses GPG keys…!
GPG Details
Trivia: “gpg” stands for GnuPG, which abbreviates GNU Privacy Guard.
To obtain encrypted messages from others, you will need a “GPG key”: They use your “public key” (which others can see) to encrypt a file, which only you can open since you have the associated “private key” (which only you see).
Possibly interesting reads:
2.10. all-the-icons
(use-package all-the-icons :config (all-the-icons-install-fonts 'install-without-asking)) ;; (cl-defun all-the-icons-faicon (icon &rest _) ;; #("" 0 1 (rear-nonsticky t display (raise -0.24) font-lock-face (:family "FontAwesome" :height 1.2) face (:family "FontAwesome" :height 1.2))))
2.11. Hydra: Supply a prefix only once
Hydras let us do “super temporary modal editing” |
Sometimes we have keybindings that share a common prefix, say C-c j
and C-c k
,
and we invoke them in an arbitrary sequence, it would be nice to invoke the
shared prefix only once thereby having:
C-c j C-c j C-c k C-c k M-3 C-c j M-5 C-c k |
≈ | C-c jjkk3j5k |
- The “hydra-zoom” example from the documentation really showcases this utility.
- After the prefix is supplied, all extensions are shown in a minibuffer.
;; Invoke all possible key extensions having a common prefix by ;; supplying the prefix only once. (use-package hydra)
From the Hydra repository is a ‘description for poets’:
Once you summon the Hydra through the prefixed binding (the body + any one head), all heads can be called in succession with only a short extension.
The Hydra is vanquished once Hercules, any binding that isn't the Hydra's head, arrives. Note that Hercules, besides vanquishing the Hydra, will still serve his original purpose, calling his proper command. This makes the Hydra very seamless, it's like a minor mode that disables itself auto-magically.
⇒ The Hydra Wiki has many example hydras for common uses cases ⇐
Below are two examples; one to simplify textual navigation and another for
window navigation.
Yet another possible hydra would be to avoid remembering word operations, such
as copying a word, upcasing it, killing a word from anywhere within it —in
contrast kill-word
kills to the end of the word—, etc. Likewise for line
operations, such as copying a line from anywhere in it. See below for another small and useful example.
When there are multiple actions, it's nice to see such a menu displayed in the middle of the frame; so we use hydra-posframe. Moreover, it can be useful to group related actions under a common heading —e.g., textual navigation may occur at the line level or word level or screen level— we obtain a nice interface by declaraing hydras using pretty-hydra-define —this saves us the trouble of formating docstrings using classic hydra.
;; TODO Fix me, breaking Github Actions test setup ;; Show hydras overlyaed in the middle of the frame ;; (use-package hydra-posframe ;; :quelpa (hydra-posframe :fetcher git :url ;; "https://github.com/Ladicle/hydra-posframe.git") ;; :hook (after-init . hydra-posframe-mode) ;; :custom (hydra-posframe-border-width 5)) ;; Neato doc strings for hydras (use-package pretty-hydra)
To actually define hydras, we make a helper function: my/defhydra —which combines defhydra and pretty-hydra-define.
Implementation
;; TODO convert my existing defhydras to my/defhydra. (defmacro my/defhydra (key title icon-name &rest body) "Make a hydra whose heads appear in a pretty pop-up window. Heads are signalled by keywords and the hydra has an icon in its title. KEY [String]: Global keybinding for the new hydra. TITLE [String]: Either a string or a plist, as specified for pretty-hydra-define. The underlying Lisp function's name is derived from the TITLE; which is intentional since hydra's are for interactive, pretty, use. One uses a plist TITLE to specify what a hydra should do *before* any options, or to specify an alternate quit key (:q by default). ICON-NAME [Symbol]: Possible FontAwesome icon-types: C-h v `all-the-icons-data/fa-icon-alist'. BODY: A list of columns and entries. Keywords indicate the title of a column; 3-lists (triples) indicate an entry key and the associated operation to perform and, optionally, a name to be shown in the pop-up. See DEFHYDRA for more details. For instance, the verbose mess: ;; Use ijkl to denote ↑←↓→ arrows. (global-set-key (kbd \"C-c w\") (pretty-hydra-define my/hydra/\\t\\tWindow\\ Adjustment ;; Omitting extra work to get an icon into the title. (:title \"\t\tWindow Adjustment\" :quit-key \"q\") (\"Both\" ((\"b\" balance-windows \"balance\") (\"s\" switch-window-then-swap-buffer \"swap\")) \"Vertical adjustment\" ((\"h\" enlarge-window \"heighten\") (\"l\" shrink-window \"lower\")) \"Horizontal adjustment\" ((\"n\" shrink-window-horizontally \"narrow\") (\"w\" enlarge-window-horizontally \"widen\" ))))) Is replaced by: ;; Use ijkl to denote ↑←↓→ arrows. (my/defhydra \"C-c w\" \"\t\tWindow Adjustment\" windows :Both (\"b\" balance-windows \"balance\") (\"s\" switch-window-then-swap-buffer \"swap\") :Vertical_adjustment (\"h\" enlarge-window \"heighten\") (\"l\" shrink-window \"lower\") :Horizontal_adjustment (\"n\" shrink-window-horizontally \"narrow\") (\"w\" enlarge-window-horizontally \"widen\"))" (let* ((name (intern (concat "my/hydra/" (if (stringp title) title (plist-get title :title))))) (icon-face `(:foreground ,(face-background 'highlight))) (iconised-title (concat (when icon-name (concat (all-the-icons-faicon (format "%s" icon-name) :face icon-face :height 1.0 :v-adjust -0.1) " ")) (propertize title 'face icon-face)))) `(global-set-key (kbd ,key) (pretty-hydra-define ,name ,(if (stringp title) (list :title iconised-title :quit-key "q") title) ,(thread-last body (-partition-by-header #'keywordp) (--map (cons (s-replace "_" " " (s-chop-prefix ":" (symbol-name (car it)))) (list (cdr it)))) (-flatten-n 1))))))
2.12. Helpful Utilities & Shortcuts
Let's save a few precious seconds,
;; change all prompts to y or n (fset 'yes-or-no-p 'y-or-n-p) ;; Make RETURN key act the same way as “y” key for “y-or-n” prompts. ;; E.g., (y-or-n-p "Happy?") accepts RETURN as “yes”. (define-key y-or-n-p-map [return] 'act) ;; Enable all ‘possibly confusing commands’ such as helpful but ;; initially-worrisome “narrow-to-region”, C-x n n. (setq-default disabled-command-function nil)
3. Staying Sane
3.1. Undo-tree: Very Local Version Control
undo-tree-visualize, C-x u, gives a visual representation of the current buffer's edit history.
;; Allow tree-semantics for undo operations. (use-package undo-tree :diminish ;; Don't show an icon in the modeline :bind ("C-x u" . undo-tree-visualize) :hook (org-mode . undo-tree-mode) ;; For some reason, I need this. FIXME. :config ;; Always have it on (global-undo-tree-mode) ;; Each node in the undo tree should have a timestamp. (setq undo-tree-visualizer-timestamps t) ;; Show a diff window displaying changes between undo nodes. (setq undo-tree-visualizer-diff t)) ;; Execute (undo-tree-visualize) then navigate along the tree to witness ;; changes being made to your file live!
( We're just showing the <<undo-tree-setup>>
from earlier since this is a good
place for such a setup. More importantly, we are not copy-pasting the setup: It
is written only once; in a single source of truth! )
;; By default C-z is suspend-frame, i.e., minimise, which I seldom use. (global-set-key (kbd "C-z") (lambda () (interactive) (undo-tree-mode) ;; Ensure the mode is on (undo-tree-visualize)))
3.2. Automatic Backups
By default, Emacs saves backup files —those ending in ~
— in the current
directory, thereby cluttering it up. Let's place them in ~/.emacs.d/backups
, in
case we need to look for a backup; moreover, let's keep old versions since
there's disk space to go around —what am I going to do with 500gigs when nearly
all my ‘software’ is textfiles interpreted within Emacs 😼
;; New location for backups. (setq backup-directory-alist '(("." . "~/.emacs.d/backups"))) ;; Silently delete execess backup versions (setq delete-old-versions t) ;; Only keep the last 1000 backups of a file. (setq kept-old-versions 1000) ;; Even version controlled files get to be backed up. (setq vc-make-backup-files t) ;; Use version numbers for backup files. (setq version-control t)
Why backups? Sometimes I may forget to submit a file, or edit, to my version control system, and it'd be nice to be able to see a local automatic backup. Whenever ‘I need space,’ then I simply empty the backup directory, if ever. That the backups are numbered is so sweet ^_^
Like package installations, my backups are not kept in any version control system, like git; only locally.
Finally, let's not create .#
files (crashes 'npm') nor bother confirming killing processes.
(setq confirm-kill-processes nil create-lockfiles nil)
3.2.1. What changed? —Walking through backups
Let's use an elementary diff system for backups: backup-walker
essentially makes
all our backups behave as if they were (implicitly) version controlled.
(use-package backup-walker :commands backup-walker-start)
In a buffer that corresponds to a file, invoke backup-walker-start to see a
visual diff of changes between versions; then n
and p
to move between diffs. By
default, you see the changes ‘backwards’: Red means delete these things to get
to the older version; i.e., the red ‘-’ are newer items.
There is also diff-backup for comparing a file with its backup.
3.2.2. Save ≈ Backup
Emacs only makes a backup the very first time a buffer is saved; I'd prefer Emacs makes backups everytime I save! —If I saved, that means I'm at an important checkpoint, so please check what I have so far as a backup!
;; Make Emacs backup everytime I save (defun my/force-backup-of-buffer () "Lie to Emacs, telling it the curent buffer has yet to be backed up." (setq buffer-backed-up nil)) (add-hook 'before-save-hook 'my/force-backup-of-buffer) ;; [Default settings] ;; Autosave when idle for 30sec or 300 input events performed (setq auto-save-timeout 30 auto-save-interval 300)
It is intestesting to note that the above snippet could be modified to make our own backup system, were Emacs lacked one, by having our function simply save copies of our file —on each save— where the filename is augmented with a timestamp.
3.3. magit
—Emacs' porcelain interface to git
Let's setup an Emacs ‘porcelain’ interface to git —it makes working with version control tremendously convenient.
(Personal reminder: If using 2FA [two factor authentication], then when you do
git operations, such as git push
, you must use your PAT [personal access token]
instead of your password! Also: Install refined-github: Browser extension that
simplifies the GitHub interface and adds useful features!)
;; Bottom of Emacs will show what branch you're on ;; and whether the local file is modified or not. (use-package magit :init (require 'magit-files) :bind (("C-c M-g" . magit-file-dispatch)) :custom ;; Do not ask about this variable when cloning. (magit-clone-set-remote.pushDefault t))
Why use magit
as the interface to the git version control system? In a magit
buffer nearly everything can be acted upon: Press return
, or space
, to see
details and tab
to see children items, usually.
- C-x g, magit-status, gives you a nice buffer with an overview of the Git repo that you're buffer is currently visiting.
C-c M-g, magit-file-dispatch, lets you invoke Git actions on the current file directly; e.g., following up with blame, log, diff, stage, or commit the current file.
For ease, above, we have also bound this to C-c g —reminiscent of C-x g :smile:
;; When we invoke magit-status, show green/red the altered lines, with extra ;; green/red on the subparts of a line that got alerted. (system-packages-ensure "git-delta") (use-package magit-delta :hook (magit-mode . magit-delta-mode)) ;; Don't forget to copy/paste the delta config into the global ~/.gitconfig file. ;; Copy/paste this: https://github.com/dandavison/delta#get-started
Blame, magit-blame, is super nice: The buffer gets annotations for each chunk of text, regarding who authoured it, when, and their commit title. Then q to quit the blame.
Likewise, magit-log-buffer-file is super neat!
Super Simple ‘magit’ Mini-tutorial
Below is my personal quick guide to working with magit —for a full tutorial seedired
- See the contents of a particular directory.
magit-init
- Put a project under version control.
The mini-buffer will prompt you for the top level folder version.
A
.git
folder will be created there. magit-status
,C-x g
- See status in another buffer.
Press
?
to see options, including:- g
- Refresh the status buffer.
- TAB
- See collapsed items, such as what text has been changed.
q
- Quit magit, or go to previous magit screen.
s
Stage, i.e., add, a file to version control. Add all untracked files by selecting the Untracked files title.
The staging area is akin to a pet store; commiting is taking the pet home.
k
- Kill, i.e., delete a file locally.
K
- This'
(magit-file-untrack)
which doesgit rm --cached
. i
- Add a file to the project
.gitignore
file. Nice stuff =) u
- Unstage a specfif staged change highlighed by cursor.
C-u s
stages everything –tracked or not. c
- Commit a change.
- A new buffer for the commit message appears, you write it then
commit with
C-c C-c
or otherwise cancel withC-c C-k
. These commands are mentioned to you in the minibuffer when you go to commit. - You can provide a commit to each altered chunk of text!
This is super neat, you make a series of local such commits rather
than one nebulous global commit for the file. The
magit
interface makes this far more accessible than a standard terminal approach! - You can look at the unstaged changes, select a region, using
C-SPC
as usual, and commit only that if you want! - When looking over a commit,
M-p/n
to efficiently go to previous or next altered sections. - Amend a commit by pressing
a
onHEAD
.
- A new buffer for the commit message appears, you write it then
commit with
d
- Show differences, another
d
or another option.- This is magit! Each hunk can be acted upon; e.g.,
s
orc
ork
;-)
- This is magit! Each hunk can be acted upon; e.g.,
v
- Revert a commit.
x
- Undo last commit. Tantamount to
git reset HEAD~
when cursor is on most recent commit; otherwise resets to whatever commit is under the cursor. l
- Show the log, another
l
for current branch; other options will be displayed.- Here
space
shows details in another buffer while cursour remains in current buffer and, moreover, continuing to pressspace
scrolls through the other buffer! Neato.
- Here
P
- Push.
F
- Pull.
:
- Execute a raw git command; e.g., enter
whatchanged
.
Notice that every time you press one of these commands, a ‘pop-up’ of realted git options appears! Thus not only is there no need to memorise many of them, but this approach makes discovering other commands easier.
[Disabled] Homemade ‘uncomitted changes’ Notification
Let's always notify ourselves of a file that has uncommited changes —we might have had to step away from the computer and forgotten to commit.
(require 'magit-git) (defun my/magit-check-file-and-popup () "If the file is version controlled with git and has uncommitted changes, open the magit status popup." (let ((file (buffer-file-name))) (when (and file (magit-anything-modified-p t file)) (message "This file has uncommited changes!") (when nil ;; Became annyoying after some time. (split-window-below) (other-window 1) (magit-status))))) ;; I usually have local variables, so I want the message to show ;; after the locals have been loaded. (add-hook 'find-file-hook '(lambda () (add-hook 'hack-local-variables-hook 'my/magit-check-file-and-popup)))
3.3.1. Credentials: I am who I am
First, let's setup our git credentials.
;; See here for a short & useful tutorial: ;; https://alvinalexander.com/git/git-show-change-username-email-address (when (equal "" (shell-command-to-string "git config user.email ")) (shell-command (format "git config --global user.name \"%s\"" user-full-name)) (shell-command (format "git config --global user.email \"%s\"" user-mail-address))) ;; Also need to customise email routes per organization ;; https://docs.github.com/en/github/managing-subscriptions-and-notifications-on-github/configuring-notifications#customizing-email-routes-per-organization (ignore-error (unless my/personal-machine? (shell-command (format "git config --global user.email \"%s\"" work/email)))) ;; If we ever need to use Git in the terminal, it should be done with Emacs as ;; the underlying editor (shell-command "git config --global core.editor emacs")
3.3.2. Encouraging useful commit messages
Let's try our best to have a useful & consistent commit log:
(defun my/git-commit-reminder () (insert "\n\n# The commit subject line ought to finish the phrase: # “If applied, this commit will ⟪your subject line here⟫.” ") (beginning-of-buffer)) (add-hook 'git-commit-setup-hook 'my/git-commit-reminder)
Super neat stuff!
3.3.3. Maybe clone … everything?
Below are the git repos I'd like to clone —along with a function to do so quickly.
;; Clone git repo from clipboard (cl-defun maybe-clone (remote &optional local) "Clone a REMOTE repository [from clipboard] if the LOCAL directory does not exist. If called interactively, clone URL in clipboard into ~/Downloads then open in dired. Yields ‘repo-already-exists’ when no cloning transpires, otherwise yields ‘cloned-repo’. LOCAL is optional and defaults to the base name; e.g., if REMOTE is https://github.com/X/Y then LOCAL becomes ∼/Y." (interactive "P") (when (interactive-p) (setq remote (substring-no-properties (current-kill 0))) (cl-assert (string-match-p "^\\(http\\|https\\|ssh\\)://" remote) nil "No URL in clipboard")) (unless local (setq local (concat "~/" (if (interactive-p) "Downloads/" "") (file-name-base remote)))) (require 'magit-repos) ;; Gets us the magit-repository-directories variable. (add-to-list 'magit-repository-directories `(,local . 0)) (if (file-directory-p local) 'repo-already-exists (shell-command (concat "git clone " remote " " local)) (dired local) 'cloned-repo)) (maybe-clone "https://github.com/alhassy/emacs.d" "~/.emacs.d") (maybe-clone "https://github.com/alhassy/alhassy.github.io" "~/blog") (maybe-clone "https://github.com/alhassy/holy-books")
Many more repos to clone
(maybe-clone "https://github.com/alhassy/melpa") (maybe-clone "https://github.com/alhassy/org-special-block-extras") ;; (maybe-clone "https://github.com/alhassy/next-700-module-systems-proposal.git" "~/thesis-proposal") ;; (maybe-clone "https://github.com/JacquesCarette/MathScheme") ;; (maybe-clone "https://github.com/alhassy/gentle-intro-to-reflection" "~/reflection/") ;; (maybe-clone "https://github.com/alhassy/org-agda-mode") ;; (maybe-clone "https://github.com/JacquesCarette/TheoriesAndDataStructures") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/RATH/RATH-Agda" "~/RATH-Agda") ;; (maybe-clone "https://github.com/alhassy/MyUnicodeSymbols") ;; Deleted? (maybe-clone "https://github.com/alhassy/islam") (maybe-clone "https://github.com/alhassy/CheatSheet") (maybe-clone "https://github.com/alhassy/ElispCheatSheet") ;; (maybe-clone "https://github.com/alhassy/CatsCheatSheet") ;; (maybe-clone "https://github.com/alhassy/OCamlCheatSheet") ;; (maybe-clone "https://github.com/alhassy/AgdaCheatSheet") (maybe-clone "https://github.com/alhassy/RubyCheatSheet") ;; (maybe-clone "https://github.com/alhassy/PrologCheatSheet") ;; (maybe-clone "https://github.com/alhassy/FSharpCheatSheet") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/armstmp/cs3mi3.git" "~/3mi3") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/alhassm/CAS781" "~/cas781") ;; cat adventures ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/carette/cs3fp3.git" "~/3fp3") ;; (maybe-clone "https://github.com/alhassy/interactive-way-to-c") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/assignment-distribution.git" "~/3ea3/assignment-distribution") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/notes.git" "~/3ea3/notes") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/assignment-development.git" "~/3ea3/assignment-development") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/kandeeps.git" "~/3ea3/sujan") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/horsmane.git" "~/3ea3/emily") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/3ea3-winter2019/anderj12.git" "~/3ea3/jacob") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/alhassm/3EA3.git" "~/3ea3/_2018") ;; (maybe-clone "https://gitlab.cas.mcmaster.ca/2DM3/LectureNotes.git" "~/2dm3")
This maybe-clone utility has genuinely
made it easier for me to learn about new projects and codebases from Github:
I type it in with the repo's address, then C-x C-e ---eval-last-sexp—
and then I can view it in my beloved Emacs (─‿‿─)
.
Moreover, this handy tool makes it so that you can list your Git repositories with magit-list-repositories: It marks modified repos as “dirty”.
It may be useful to know that (magit-anything-modified-p t file)
can be used to
check if file
has been modified (magit-anything-modified-p), whereas
(magit-status repo)
checks the status of a repository (magit-status).
3.3.4. Gotta love that time machine
Finally, one of the main points for using version control is to have access to historic versions of a file. The following utility allows us to M-x git-timemachine on a file and use p/n/g/q to look at previous, next, goto arbitrary historic versions, or quit.
(use-package git-timemachine :defer t)
If we want to roll back to a previous version, we just write-file or C-x C-s as usual! The power of text!
vc-annotate is also very useful to go through history and work out when things went wrong.
3.3.5. Jump to a (ma)git repository with C-u C-x g
;; Jump to a (ma)git repository with C-u C-x g. ;; ;; To get a selection of repositories (that have been visited at least once), ;; call with “C-u M-x magit-status” or “C-u C-x g”; use “C-u C-u C-x g” to ;; manually enter a path to a repository. ;; ;; We use projectile's record of known projects, and keep only projects with ;; .git directory. (with-eval-after-load 'projectile (setq magit-repository-directories (thread-last (projectile-relevant-known-projects) (--filter (unless (file-remote-p it) (file-directory-p (concat it "/.git/")))) (--map (list (substring it 0 -1) 0))))) ;; Follow-up utility (defun my/update-repos () "Update (git checkout main & pull) recently visited repositories." (interactive) (cl-loop for (repo _depth) in magit-repository-directories ;; Is it “main” or “master” for trunk = (s-trim (shell-command-to-string (format "cd %s; git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@'" repo))) do (message (format "🤖 %s ∷ Checking out & pulling main" repo)) (shell-command (format "cd %s; git checkout %s; git pull" repo trunk))) (message "🥳 Happy coding!"))
3.4. Pretty Magit Commit Leaders
⟨ Following Pretty Magit - Integrating commit leaders | Modern Emacs ⨾⨾ Code comes from there as well. Notable alteration: Helm compleition shows description of leaders. ⟩
Add faces to Magit to achieve icon and colored commit leaders. I also integrate Helm to prompt a leader when committing so there's no need to remember or type out completely every leader we choose.
- It's not just aesthetics. It's about visual clarity.
- Here is an alternate approach: Add icons based on words mentioned in commit titles —no leaders required.
Boring details ~ See linked article instead
(cl-defmacro pretty-magit (WORD ICON PROPS &optional (description "") NO-PROMPT?) "Replace sanitized WORD with ICON, PROPS and by default add to prompts." `(prog1 (add-to-list 'pretty-magit-alist (list (rx bow (group ,WORD (eval (if ,NO-PROMPT? "" ":")))) ,ICON ',PROPS)) (unless ,NO-PROMPT? (add-to-list 'pretty-magit-prompt (cons (concat ,WORD ": ") ,description))))) (setq pretty-magit-alist nil) (setq pretty-magit-prompt nil)
My personal choices for leaders are:
(pretty-magit "Add" ?➕ (:foreground "#375E97" :height 1.2) "✅ Create a capability e.g. feature, test, dependency.") (pretty-magit "Delete" ?❌ (:foreground "#375E97" :height 1.2) "❌ Remove a capability e.g. feature, test, dependency.") (pretty-magit "Fix" ?🔨 (:foreground "#FB6542" :height 1.2) "🐛 Fix an issue e.g. bug, typo, accident, misstatement.") (pretty-magit "Clean" ?🧹 (:foreground "#FFBB00" :height 1.2) "✂ Refactor code; reformat say by altering whitespace; refactor performance.") (pretty-magit "Document" ?📚 (:foreground "#3F681C" :height 1.2) "ℹ Refactor of documentation, e.g. help files.") (pretty-magit "Feature" ?⛲ (:foreground "slate gray" :height 1.2) "⛳ 🇮🇶🇨🇦 A milestone commit - flagpost") (pretty-magit "Generate" ?🔭 (:foreground "slate gray" :height 1.2) "Export PDF/HTML or tangle raw code from a literate program") ;; Generating artefacts (pretty-magit "master" ? (:box t :height 1.2) "" t) (pretty-magit "origin" ?🐙 (:box t :height 1.2) "" t) ;; Commit leader examples: https://news.ycombinator.com/item?id=13889155. ;; ;; Cut ~ Remove a capability e.g. feature, test, dependency. ;; Bump ~ Increase the version of something e.g. dependency. ;; Make ~ Change the build process, or tooling, or infra. ;; Start ~ Begin doing something; e.g. create a feature flag. ;; Stop ~ End doing something; e.g. remove a feature flag.
Boring details ~ See linked article instead
(defun add-magit-faces () "Add face properties and compose symbols for buffer from pretty-magit." (interactive) (with-silent-modifications (--each pretty-magit-alist (-let (((rgx icon props) it)) (save-excursion (goto-char (point-min)) (while (search-forward-regexp rgx nil t) (compose-region (match-beginning 1) (match-end 1) icon) (when props (add-face-text-property (match-beginning 1) (match-end 1) props)))))))) (advice-add 'magit-status :after 'add-magit-faces) (advice-add 'magit-refresh-buffer :after 'add-magit-faces) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (setq use-magit-commit-prompt-p nil) (defun use-magit-commit-prompt (&rest args) (setq use-magit-commit-prompt-p t)) (defun magit-commit-prompt () "Magit prompt and insert commit header with faces." (interactive) (when use-magit-commit-prompt-p (setq use-magit-commit-prompt-p nil) (thread-last (--map (format "%s %s" (car it) (cdr it)) pretty-magit-prompt) (completing-read "Insert commit leader ∷ ") ;; My “Generate:” commit type has one use case, for now; so let's insert it filled-in. (funcall (lambda (it) (if (s-starts-with? "Generate:" it) it (car (s-split " " it))))) (insert) (end-of-line)) (add-magit-faces))) (remove-hook 'git-commit-setup-hook 'with-editor-usage-message) (add-hook 'git-commit-setup-hook 'magit-commit-prompt) (advice-add 'magit-commit :after 'use-magit-commit-prompt)
3.5. Version Control with SVN —Using Magit! Disabled
Disabled: I seldom work with SVN anymore. |
Let's use git as an interface to subversion repositories so that we can continue
to use magit
as our version control interface. The utility to do so is called
git svn
—note git 𝒳
on a MacOS is the same as git-𝒳
on other systems.
(use-package magit-svn :hook (magit-mode . magit-svn-mode))
Here's an example. The following command checksout an SVN repo; afterwhich we may open a file
there and do M-x magit-status
to get the expected porcelain git interface ^_^
(async-shell-command "mkdir ~/2fa3; git svn clone --username alhassm https://websvn.mcmaster.ca/csse2fa3/2019-2020_Term2 ~/2fa3/")
In the magit buffer, we may now use the N
key which wraps the git svn
subcommands fetch, rebase, dcommit, branch, tag
. For example:
- Make changes to a file.
- ‘Stage’ them with
s
and ‘commit’ them withc
. - ‘Push’ changes with
N c
.
We get to pretend we're using git
even though the underlying mechanism is svn
!
For move on git svn
, see A simple guide to git-svn or Effectively using Git with
Subversion.
(If I need to work with svn repos often enough, I'd extend my maybe-clone
utility above to account for them.)
3.6. Highlighting TODO-s & Showing them in Magit
Sometimes it's nice to flag a chunk of text by its author, such as ‘ MA ’ for ‘M’usa ‘A’l-hassy, or ‘ HACK ’ for text that needs to be improved. Such flags stand out from other text by being coloured and bold.
;; NOTE that the highlighting works even in comments. (use-package hl-todo ;; I want todo-words highlighted in prose, not just in code fragements. :hook (org-mode . hl-todo-mode) :config ;; Adding new keywords (cl-loop for kw in '("TEST" "MA" "WK" "JC") do (add-to-list 'hl-todo-keyword-faces (cons kw "#dc8cc3"))) ;; Enable it everywhere. (global-hl-todo-mode))
We've added few to the default flag keywords so that in total we have the following flags —where any sequence of at least 3 XXX are considered flags.
(JC WK MA TEST HOLD TODO NEXT THEM PROG OKAY DONT FAIL DONE NOTE KLUDGE HACK TEMP FIXME XXX+)
Lest these get buried in mountains of text, let's have them become mentioned in
a magit status buffer —which uses the keywords from hl-todo
.
;; MA: The todo keywords work in code too! (use-package magit-todos :after magit :after hl-todo ;; :hook (org-mode . magit-todos-mode) :config ;; For some reason cannot use :custom with this package. (custom-set-variables '(magit-todos-keywords (list "TODO" "FIXME" "MA" "WK" "JC"))) ;; Ignore TODOs mentioned in exported HTML files; they're duplicated from org src. (setq magit-todos-exclude-globs '("*.html")) (magit-todos-mode))
- Note that such TODO keywords are not propagated from sections that are COMMENT-ed out in org-mode.
- Ensure you exclude generated files, such as the Emacs backups directory, from
being consulted. Using
magit
, press i to mark items to be ignored. - This feature also works outside of git repos.
Open a Magit status buffer, or run magit-todos-list to show a dedicated to-do list buffer. You can then peek at items with space, or jump to them with enter.
Seeing the TODO list with each commit is an incentive to actually tackle the items there (•̀ᴗ•́)و
3.7. Silently show me when a line was modified and by whom
Quickly & automatically glimpse who, why, and when a line or code block was changed, using blamer.el. Jump back through history to gain further insights as to how and why the code evolved with C-x g l l (magit-log-head) or git-timemachine.
(unless noninteractive (use-package blamer :quelpa (blamer :fetcher github :repo "artawower/blamer.el") :custom (blamer-idle-time 0.3) (blamer-min-offset 70) (blamer-max-commit-message-length 80) ;; Show me a lot of the commit title :custom-face (blamer-face ((t :foreground "#7a88cf" :background nil :height 140 :italic t))) :config (global-blamer-mode 1)))
This is so nice!. I've enabled it once and then it just “works in the background, silently”.
When reading a line and wondering “who changed/wrote this and why”, blamer.el answers that seamlessly —almost as if a transient comment 😁
- The “why” is answered by commit messages; which may lead to improved messages: “Hey, I was just browsing through the code, and landed on this commit, but the message does not tell me why the change was introduced. Would you please write more detailed commit messages.”
- The “when” gives a useful context in time about the change. For example, when looking for a regression/recent-bug, we can immediately see “Did this line even change in the past week? No? That must not be it then” and we move on.
If we want to see commit messages alongside all code, then we can invoke magit-blame-addition or vc-annotate. If we want to “walk-along changes” then we use git-timemachine.
3.8. delete-by-moving-to-trash t
;; Move to OS’ trash can when deleting stuff ;; instead of deleting things outright! (setq delete-by-moving-to-trash t trash-directory "~/.Trash/")
3.9. Jumping to extreme semantic units
Sometimes it's unreasonable for M-<
to take us to the actual start of a buffer;
instead it'd be preferable to go to the first “semantic unit” in the buffer. For
example, when directory editing with dired
we should jump to the first file,
with version control with magit
we should jump to the first section, when
composing mail we should jump to the first body line, and in the agenda we
should jump to the first entry.
;; M-< and M-> jump to first and final semantic units. ;; If pressed twice, they go to physical first and last positions. (use-package beginend :diminish 'beginend-global-mode :config (beginend-global-mode) (cl-loop for (_ . m) in beginend-modes do (diminish m)))
3.10. Get CheatSheets and view them easily
(defvar my/cheatsheet/cached-topics nil) (cl-defun my/cheatsheet (&optional topic) "Clone Al-hassy's ⟨TOPIC⟩CheatSheet repository when called from Lisp; visit the pretty HTML page when called interactively. - Example usage: (my/cheatsheet \"Vue\") - Example usage: M-x my/cheatsheet RET Vue RET." (interactive) (if (not topic) (browse-url (format "https://alhassy.github.io/%sCheatSheet" (completing-read "Topic: " my/cheatsheet/cached-topics))) (push topic my/cheatsheet/cached-topics) (maybe-clone (format "https://github.com/alhassy/%sCheatSheet" topic))))
Let's actually get some repos locally, and use: M-x my/cheatsheet
to view the pretty HTML (or PDF) sheets.
(mapcar #'my/cheatsheet '("ELisp" "GojuRyu" "Rust")) ; Python Prolog Vue Agda JavaScript ; Clojure Ruby Oz Coq Cats Haskell FSharp OCaml
4. Literate Programming
Org-mode lets us run chunks of code anywhere, then feed their outputs to other chunks of code in possibly different programming languages: Org is a meta-(programming language).
Importantly, this means we can write text and whenever we need the result of some computation, we can place it there and then and only request its result appear in PDF/HTML export. The result is a single document.
( There is the org-modern package, which provides a modern look-and-feel: It makes Org look less like a markup and more like a word editor. Nice stuff. )
4.1. High Speed Literate Programming
4.1.1. Manipulating Sections
(setq org-use-speed-commands t)
This enables the Org Speed Keys so that when the cursor is at the beginning of a headline, we can perform fast manipulation & navigation using the standard Emacs movement controls, such as:
- # toggle
COMMENT
-ing for an org-header. s toggles “narrowing” to a subtree; i.e., hide the rest of the document.
If you narrow to a subtree then any export, C-c C-e, will joyously only consider the narrowed detail.
- u for going to upwards to parent heading
- i insert a new same-level heading below current heading.
- c for cycling structure below current heading, or
C
for cycling global structure. - w refile current heading; options list pops-up to select which heading to move
it to. Neato!
g to go to another heading, without refiling anything.
;; When refiling, only show me top level headings [Default]. Sometimes 2 is useful. ;; When I'm refiling my TODOS, then give me all the freedom. (setq org-refile-targets '((nil :maxlevel . 1) (org-agenda-files :maxlevel . 9))) ;; Maybe I want to refile into a new heading; confirm with me. (setq org-refile-allow-creating-parent-nodes 'confirm) ;; Use full outline paths for refile targets ;; When refiling, using Helm, show me the hierarchy paths (setq org-outline-path-complete-in-steps nil) (setq org-refile-use-outline-path 'file-path)
- n/p for next/previous visible heading.
- f/b for jumping forward/backward to the next/previous same-level heading.
- D/U move a heading down/up.
- L/R recursively promote (move leftwards) or demote (more rightwards) a heading.
- I/O clock In/Out to the task defined by the current heading.
- Keep track of your work times!
- v view agenda.
- t/,/:/e to add a TODO state, priority level, tag, or effort estimate
- 1/2/3 to mark a heading with priority, highest to lowest.
- ^ sort children of current subtree; brings up a list of sorting options.
- k/@/a to kill or mark or archive the current subtree
- o to open a link mentioned in the subtree then go to the link; a pop-up of links appears.
We can add our own speed keys by altering the org-speed-commands association list variable; e.g.,
;; TODO FIXME Crashes upon startup. (when nil (add-to-list 'org-speed-commands (cons "P" #'org-set-property))) ;; Use ‘:’ and ‘e’ to set tags and effort, respectively.
⇒ Moreover, ? to see a complete list of keys available. ⇐ |
4.1.3. Modifying ⟨return⟩
- C-⟨return⟩ , C-S-⟨return⟩ make a new heading where the latter marks it as a
TODO
. - By default M-⟨return⟩ makes it easy to work with existing list items, headings, tables, etc by creating a new item, heading, etc.
Usually we want a newline then we indent, let's make that the default.
(add-hook 'org-mode-hook '(lambda () (local-set-key (kbd "<return>") 'org-return-indent)) (local-set-key (kbd "C-M-<return>") 'electric-indent-just-newline))
Notice that I've also added another kind of return, for when I want to break-out of the indentation approach and start working at the beginning of the line.
In summary:
key | method | behaviour |
---|---|---|
⟨return⟩ | org-return-indent | Newline with indentation |
M-⟨return⟩ | org-meta-return | Newline with new org item |
C-M-⟨return⟩ | electric-indent-just-newline | Newline, cursor at start |
C-⟨return⟩ | org-insert-heading-respect-content | New heading after current content |
C-S-⟨return⟩ | org-insert-todo-heading-respect-content | Ditto, but with a TODO marker |
4.2. Executing code from src
blocks
For example, to execute a shell command in Emacs, write a src
with a shell
command, then C-c c-c
to see the results. Emacs will generally query you to
ensure you're confident about executing the (possibly dangerous) code block;
let's stop that:
;; Seamless use of babel: No confirmation upon execution. ;; Downside: Could accidentally evaluate harmful code. (setq org-confirm-babel-evaluate nil) ;; Never evaluate code blocks upon export and replace results when evaluation does occur. ;; For a particular language 𝑳, alter ‘org-babel-default-header-args:𝑳’. (setq org-babel-default-header-args '((:results . "replace") (:session . "none") (:exports . "both") (:cache . "no") (:noweb . "no") (:hlines . "no") (:tangle . "no") (:eval . "never-export")))
Some initial languages we want org-babel to support:
(defvar my/programming-languages '(emacs-lisp shell python haskell rust ruby ocaml dot latex org js css sqlite C) ;; Captial “C” gives access to C, C++, D "List of languages I have used in Org-mode, for literate programming.") ;; Load all the languagues (cl-loop for lang in my/programming-languages do (require (intern (format "ob-%s" lang)))) ;; (org-babel-do-load-languages 'org-babel-load-languages (--map (cons it t) my/programming-languages)) ;; Preserve my indentation for source code during export. (setq org-src-preserve-indentation t) ;; The export process hangs Emacs, let's avoid this. ;; MA: For one reason or another, this crashes more than I'd like. ;; (setq org-export-in-background t)
More languages can be added using add-to-list.
4.3. Executing all #+name: startup-code
for local configurations
Sometimes my Org-files contain configurations that are local to the file,
so I name all such src
blocks #+name: startup-code
and place # -*- eval: (my/execute-startup-blocks) -*-
at the top of the file so that such
blocks are evaluated when the file opens up.
- The
-*- ... -*-
notation is for making local configurations. - Use
M-x add-file-local-variable-prop-line
to have them inserted interactively.
(defun my/execute-startup-blocks () "Execute all startup blocks, those named ‘startup-code’. I could not use ORG-BABEL-GOTO-NAMED-SRC-BLOCK since it only goes to the first source block with the given name, whereas I'd like to visit all blocks with such a name." (interactive) (save-excursion (goto-char 0) (while (ignore-errors (re-search-forward "^\\#\\+name: startup-code")) (org-babel-execute-src-block))))
The following setup enables this feature in a safe fashion —e.g., we do not want to avoid evaluating a random person's potentially dangerous code when we only want to look at it.
;; Please ask me on a file by file basis whether its local variables are ‘safe’ ;; or not. Use ‘!’ to mark them as permanently ‘safe’ to avoid being queried ;; again for the same file. (setq enable-local-variables t)
I have been using a combination of (org-babel-goto-named-src-block ⋯)
in
multi-line local-variable declarations ---M-x add-file-local-variable-prop
—
for a while in many files using a dedicated * footer :noexport:
section, but
this new approach frees from having such sections and instead to having a single
line at the top of the file. Moreover, being at the top of the file, such a line
is a nice ‘in your face’ reminder that there is local configuration that
should have been loaded.
4.4. Quickly pop-up a terminal, run a command, close it —and zsh
Pop up a terminal, do some work, then close it using the same command.
Shell-pop uses only one key action to work: If the buffer exists, and we're in
it, then hide it; else jump to it; otherwise create it if it doesn't exit. Use
universal arguments, e.g., C-u 5 C-t
, to have multiple shells and the same
universal arguments to pop those shells up, but C-t
to pop them away.
(use-package shell-pop :custom ;; This binding toggles popping up a shell, or moving cursour to the shell pop-up. (shell-pop-universal-key "C-t") ;; Percentage for shell-buffer window size. (shell-pop-window-size 30) ;; Position of the popped buffer: top, bottom, left, right, full. (shell-pop-window-position "bottom") ;; Please use an awesome shell. (shell-pop-term-shell "/bin/zsh"))
Oh My Zsh
Now that we have access to quick pop-up for a shell, let's get a pretty and practical shell: zsh along with the Oh My Zsh community configurations give us:
brew install zsh
sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)"
This installs everything ^_^
;; Be default, Emacs please use zsh ;; E.g., M-x shell (unless noninteractive (setq shell-file-name "/bin/zsh"))
Out of the box, zsh comes with
- git support; the left side indicates which branch we're on and whether the repo is dirty, ✗.
- Recursive path expansion; e.g.,
/u/lo/b TAB
expands to/usr/local/bin/
- Over 250+ Plugins and 125+ Themes that are enabled by simply
mentioning their name in the
.zshrc
file.
The defaults have been good enough for me, for now —as all else is achieved via Emacs ;-)
Also, there's the tldr tool which aims to be like terse manuals for
commandline-tools in the style of practical example uses cases: tldr 𝒳
yields a
number of ways you'd actually use 𝒳. ( In Emacs, C-t tldr 𝒳 ⟨return⟩. )
(system-packages-ensure "tldr")
4.5. Snippets —Template Expansion
It is common that there is a sequence of text that we tend to repeat often, possibly with a name or some other parameter altered. Such a ‘snippet’ could be written once then provided by a simple Lisp insert command with the parameters being queried. Luckily, others have written such pleasant utilities.
Besides snippets, there are words that we may want to repeat often but it can be tedious to write them out in full. As such, we employ word completion; which we also use to expand our snippets.
4.5.1. Word Completion
Let's enable “complete anything” mode —it ought to start in half a second and only need two characters to get going, which means word suggestions are provided and so I need only type partial words then tab to get the full word!
(use-package company :diminish :config (global-company-mode 1) (setq ;; Only 2 letters required for completion to activate. company-minimum-prefix-length 2 ;; Search other buffers for compleition candidates company-dabbrev-other-buffers t company-dabbrev-code-other-buffers t ;; Show candidates according to importance, then case, then in-buffer frequency company-transformers '(company-sort-by-backend-importance company-sort-prefer-same-case-prefix company-sort-by-occurrence) ;; Flushright any annotations for a compleition; ;; e.g., the description of what a snippet template word expands into. company-tooltip-align-annotations t ;; Allow (lengthy) numbers to be eligible for completion. company-complete-number t ;; M-⟪num⟫ to select an option according to its number. company-show-numbers t ;; Show 10 items in a tooltip; scrollbar otherwise or C-s ^_^ company-tooltip-limit 10 ;; Edge of the completion list cycles around. company-selection-wrap-around t ;; Do not downcase completions by default. company-dabbrev-downcase nil ;; Even if I write something with the ‘wrong’ case, ;; provide the ‘correct’ casing. company-dabbrev-ignore-case nil ;; Immediately activate completion. company-idle-delay 0) ;; Use C-/ to manually start company mode at point. C-/ is used by undo-tree. ;; Override all minor modes that use C-/; bind-key* is discussed below. (bind-key* "C-/" #'company-manual-begin) ;; Bindings when the company list is active. :bind (:map company-active-map ("C-d" . company-show-doc-buffer) ;; In new temp buffer ("<tab>" . company-complete-selection) ;; Use C-n,p for navigation in addition to M-n,p ("C-n" . (lambda () (interactive) (company-complete-common-or-cycle 1))) ("C-p" . (lambda () (interactive) (company-complete-common-or-cycle -1))))) ;; It's so fast that we don't need a key-binding to start it!
Note that M-/
goes through a sequence of completions —and C-/
manually begins
company mode at point. Besides the arrow keys, we can also use M-
with n, p
to
navigate the options or use C-s
to search the list of suggestions.
- Company backends are available as separate packages.
- Note that by default company mode does not support completion for phrases containing hyphens —this can be altered, if desired.
Besides boring word completion, let's add support for emojis.
(use-package company-emoji :config (add-to-list 'company-backends 'company-emoji))
For example: 🥞 💻 🐵 ✉️😉 🐬 🌵.
➡️On a new line, write :
then any letter to have a tool-tip appear.
All emoji names are lowercase. ◀
- On MacOS,
C-⌘-SPC
brings up an emoji picker, where one drags desired emojis to textual areas. - Here is a list of emoji —all supported by Github.
The libraries emojify
and emojify-logos
provides cool items like :haskell:
:emacs: :org: :ruby: :python:
. Unfortunately they do not easily export to html
with org-mode, so I'm not using them.
4.5.2. Intro to Snippets
A snippet, template, mechanism is a tool that when you press some keystrokes inserts some text, possibly with some fields (‘blanks’) to fill in. Possibly interesting read:
- Snippet Expansion With Yasnippet: Save Yourself Keystrokes and Headaches —a nice before introduction to Yasnippet (“Yet another snippet”)
- Tweaking Emacs: Snippets —a brief article on using snippets for uniformity across languages and to mitigate verbosity of weak languages (i.e., those without macros).
Yasnippet is a pleasant utility for template expansion with the alluring
feature to allow arbitrary Lisp code to be executed during expansion.
The declaration of templates is verbose, requiring a particular file
hierarchy, as such I utilise Yankpad which allows me to employ
an Org-mode approach: Each template corresponds to an org heading of
the form Key:Words:For:Expansion:Here: name of snippet here
and the
template body is then the body of the org heading.
Any of Key, Words, For, Expansion, Here
will rewrite into the body
of the org tree. This is much more terse, and I even don't bother
with that; instead preferring to tangle my templates using yankpad
as a mere interface. It is important to note that Yankpad also provides
features that are not in Yassnippet, such as allowing arbitrary language
code to be executed —one simply uses an org-src block!
Here is a nice self-contained tutorial. |
There can only be one major completion backend for any mode, but
other backends can serve as secondary ones. Here's a function to
make company-yankpad
a secondary of all existing backends.
;; Add yasnippet support for all company backends ;; (cl-defun my/company-backend-with-yankpad (backend) "There can only be one main completition backend, so let's enable yasnippet/yankpad as a secondary for all completion backends. Src: https://emacs.stackexchange.com/a/10520/10352" (if (and (listp backend) (member 'company-yankpad backend)) backend (append (if (consp backend) backend (list backend)) '(:with company-yankpad))))
;; Yet another snippet extension program (use-package yasnippet :diminish yas-minor-mode :config (yas-global-mode 1) ;; Always have this on for when using yasnippet syntax within yankpad ;; respect the spacing in my snippet declarations (setq yas-indent-line 'fixed)) ;; Alternative, Org-based extension program (use-package yankpad :diminish :config ;; Location of templates (setq yankpad-file "~/.emacs.d/yankpad.org") ;; Ignore major mode, always use defaults. ;; Yankpad will freeze if no org heading has the name of the given category. (setq yankpad-category "Default") ;; Load the snippet templates ---useful after yankpad is altered (yankpad-reload) ;; Set company-backend as a secondary completion backend to all existing backends. (setq company-backends (mapcar #'my/company-backend-with-yankpad company-backends)))
With these settings, along with the company
backend, I may type a keyword then
TAB it into expansion.
Yankpad requires we have an org file that contains our templates, so we tangle
such a file ~/.emacs.d/yankpad.org
, and have all of our templates be globally
accessible. Here is the start of my file:
#+Description: This is file is generated from my init.org; do not edit. * Default :global:
Fully discussed example: Using the clipboard for Org-links
Here's an example of a common template I perform by hand —no more! I have the
expected habit of copying (to clipboard) a URL from someplace then forming a
link to it by writing [[URL] [description]]
, since the URL & syntax are already
known, let's expand those and place the cursour at the only unknown —the
description.
** my_org_insert_link: cleverly insert a link copied to clipboard [[${1:`(clipboard-yank)`}][$2]] $0
What's going on here? ( The above, verbatim: [[${1:`(clipboard-yank)`}][$2]] $0
. )
- This template is expanded with the keyword
my-org-insert-link
, then TAB. The cursour lands at position
$1
, which has default text being the result of evaluating(clipboard-yank)
.We may evaluate Lisp code anywhere by enclosing it in backticks.
- If we're satisfied with the current field, we simply tab to the next field. Otherwise, we simply write text —which overwrites the default text.
- After enough tabbing we complete the template and the cursour lands
at position
$0
.
⟪ Having default or mirrored text for $2
would not allow me to see the URL
field, lest I wish to change it or at least confirm it's what I want.
Hence, the $2
field has no default. ⟫
Let's overwrite the usual way to insert such links, via C-c C-l
.
(cl-defun org-insert-link () "Makes an org link by inserting the URL copied to clipboard and prompting for the link description only. Type over the shown link to change it, or tab to move to the description field. This overrides Org-mode's built-in ‘org-insert-link’ utility; whence C-c C-l uses the snippet." (interactive) (insert "my_org_insert_link") (yankpad-expand))
Warning! Snippet names cannot have hypens in them —in this setup at least.
The Yasnippet manual is an accessible read, as is the Yankpad manual, and
showcases many other utilities; such as having certain snippets being
enabled only in particular modes or on demand. Of note is that field $n
can be
accessed in code with the invocation (yas-field-value n)
.
Incidentally, I used this snippet setup to demo the idea of repetitious code in grouping constructs within dependently-typed languages, which was accepted and led to my doctoral research on a ‘do it yourself module system’.
The rest of this section is other templates, not much for now, concluding with actually loading this snippet mechanism globally.
The remaining subsections discuss contents of my yankpad file.
4.5.3. Org-mode Templates —A reason I “generate” templates ;)
This produces a pop-up list of org-mode block types, if src
is selected, then a
list of my commonly used languages pops-up. Alternatively, ignore the pop-up
menu and write any block or language name.
** begin: produce an org-mode block #+begin_${1:environment$(let* ((block '("src" "example" "quote" "verse" "center" "latex" "html" "ascii")) (langs '("c" "emacs-lisp" "lisp" "latex" "python" "sh" "haskell" "plantuml" "prolog")) (type (yas-choose-value block))) (concat type (when (equal type "src") (concat " " (yas-choose-value langs)))))} $0 #+end_${1:$(car (split-string yas-text))}
In this case, yas-text
is equivalent to (yas-field-value 1)
;
it generally refers to the value of the field being mirrored with ${n: ⋯yas-text⋯}
.
However, going through pop-ups takes precious time —besides being slightly annyoing. Let's introduce a template for my most utilised kind of language blocks.
** s_org: src block for org #+begin_src org $0 #+end_src
However, doing this for each language I want is a waste of time and textual
space. Why? The purpose of templates is to reduce repetition, yet the above
block would be repeated with only 3 parts ‘unknown’: The expansion keyword, the
description, and the org-mode source block name. Whence, the template text is
generated by the following basic loop —whose source block is named
my-org-lang-templates
.
;; We make an org BLOCK snippet template for each LANG the user has declared. ;; (cl-loop for (shortcut block takes-language-argument? default-text) in '(("s_" "src" t) ("is_" "inline source" t) ;; Treated specially below ("e_" "example" t) ("q_" "quote") ("v_" "verse") ("c_" "center") ("ex_" "export") ;; only HTML and LATEX ;; https://alhassy.github.io/org-special-block-extras/#Summary ("p_" "parallel" nil "\n$0\n#+columnbreak:\n") ("d_" "details" nil "${1:title}\n$0") ("ed_" "edcomm" nil "${1:editor}\n$0") ("doc_" "documentation" nil "${1: mandatory entry name}\n$0") ("def_" "latex-definitions")) for languages = (if takes-language-argument? (-cons* "org" "agda2" "any" ;; Extra ‘languages’ ;; Also include whatever languages we've loaded for literate programming. (--map (symbol-name (car it)) org-babel-load-languages)) '("")) ;; The “empty language” concat (cl-loop for lang in languages for key = (concat shortcut (if (s-blank? lang) block lang)) for description = (if (s-blank? lang) block (concat block " for " lang)) concat (if (equal "is_" shortcut) (concat "\n** " key ": " description "\nsrc_" lang "[:exports code]{$1} $0") (concat "\n** " key ": " description "\n#+begin_" block " " lang (or default-text "\n$0") "\n#+end_" block "\n"))))
The resulting text of this block, generated below, is tangled to our yankpad by
utilising a noweb source block invocation. An example of the resulting text is
the above s_org
block. The result is (last I checked) 83 template expansions
—that would have been a bit much to write by hand.
#+begin_src org :tangle "~/.emacs.d/yankpad.org" :noweb yes <<my-org-lang-templates()>> #+end_src
nil
Now s_
, due to company mode, brings up a list of languages that I can then
scroll down through, then “enter” upon to expand. Moreover, the prefix s_
means
that the key is mostly irrelevant, since I needn't remember it because
company-mode immediately lists possible completions along with the descriptions
for the snippets. Likewise for examples with e_
or quotes with q_
. Super neat
stuff :-)
Ain't this reminiscent of meta-programming ;-)
Using noweb
invocations, any time the tangling is performed, the yankpad
is kept up to date —no personal intervention from myself.
With the advent of org-special-block-extras, I've made increased usage of links
–such as green:hello
which yields hello and [[kbd:][green]]
which yields
.
** ll_make_a_link: insert a link template ${1:`(let* ((τ (read-string "Link type: ")) (δ (read-string "Link Description: ")) (⊤ (if (s-contains? ":" τ) τ (s-concat τ ":")))) (format "[[%s][%s]]" ⊤ δ))`} $0
4.5.4. Operating System Keyboard Symbols
Write os_
then see a bunch of completions ;-)
(loop for (name expansion) in '((os-command ⌘) (os_option ⌥) (os_alt ⌥) (os_control ⌃) (os_shift ⇧) (os_backspace ⌫) (os_delete ⌫) (os_delete_forward ⌦) (os_enter ⏎) (os_return ⏎) (os_escape ⎋) (os_tab_right ⇥) (os_tab_left ⇤) (os_caps_lock ⇪) (os_eject ⏏)) concat (format "** %s: %s Operating System Keyboard Symbol\n%s\n" name expansion expansion))
nil
4.5.5. Work Templates
** ll_console_log: Log some JS variables console.log("%c ******* LOOK HERE *******", "color: green; font-weight: bold;"); console.log({ ${1:List the variables here whose values you want to log} }); $0 ** uuidgen: Insert the result of “uuidgen” and copy it to the clipboard ${1:`(-let [it (shell-command-to-string "uuidgen | tr '[:upper:]' '[:lower:]' | pbcopy; pbpaste")] (message "Copied to clipboard, uuid: %s" it) it)`}
4.5.6. Elisp Templates
The following snippets were rather useful as I began learning Lisp to construct
my editor of choice —I love Emacs so much. Admittedly, I still need the first
one below and usually beat around the bush by using (cl-loop for ⋯ do ⋯)
, (cl-loop), which is
‘noisier’ but easier to remember and to read for non-Lispers.
** loop: Elisp's for each loop (dolist (${1:var} ${2:list-form}) ${3:body}) ** defun: Lisp functions (cl-defun ${1:fun-name} (${2:arguments}) "${3:documentation}" $0) ** cond: Elisp conditionals (cond (${1:scenario₁} ${2:response₁}) (${3:scenario₂} ${4:response₂}))
4.5.7. Equational Templates
To show ℒ = ℛ
, one starts at the complicated side, say ℒ, then, with the aim of
simplification, tries to end at the simpler side, 𝓡. Along the way, one
justifies each step of the calculation. This approach is popular in the proof
assistant Agda; Examples. Read more about informal calculational proofs.
** fun: Function declaration with type signature ${1:fun-name} : ${2:arguments} $1 ${3:args} = ?$0 ** eqn_begin: Start a ≡-Reasoning block in Agda begin ${1:complicated-side} $0≡⟨ ${3:reason-for-the-equality} ⟩ ${2:simpler-side} ∎ ** eqn_step: Insert a step in a ≡-Reasoning block in Agda ≡⟨ ${2:reason-for-the-equality} ⟩ ${1:new-expression} $0
One expands eqn_begin
, tabs to fill in the three main locations, then
immediately types eqn_step
to produce a new step in a calculational proof.
4.5.8. Fixed replies
Here are some replies that I sometimes need to produce; e.g., to people who insist their way is the right way.
** reply_opinionated_pantomath: What to say to, e.g., an arrogant academic Your certainty inspires me to continuing exploring, and I may arrive at your point of view, but I'm going to need more evidence first. ** reply_em_dashes: Why use em dashes for parenthetical remarks? According to the “Canadian Style Guide” (CSG): The em is an expansive, attention-seeking dash. It supplies much stronger emphasis than the comma, colon or semicolon it often replaces. Positioned around interrupting elements, em dashes have the opposite effect of parentheses—em dashes emphasize; parentheses minimize. From “A Logical Approach to Discrete Math” (LADM), page ix: We place a space on one side of an em dash ---here are examples--- in order to help the reader determine whether the em dash begins or ends a parenthetical remark. In effect, we are creating two symbols from one. In longer sentences---and we do write long sentences from time to time---the lack of space can make it difficult to see the sentence structure---especially if the em dash is used too often in one sentence. Parenthetical remarks delimited by parentheses (like this one) have a space on one side of each parenthesis, so why not parenthetical remarks delimited by em dashes? Interestingly, according to the CSG, there should be no space before or after an em dash. As such, it appears that the spacing is mostly stylistic; e.g., some people surround em-s with spaces on both sides. In particular, when em-s are unmatched, I make no use of additional space ---indeed this form of one-sided parentheses without a space is how LADM is written, as can be seen at the top of page 3.
4.5.9. Emojis
;; ;; https://emojipedia.org/people/ (cl-loop for (emoji name description) in '((😀 "Grinning Face" "Often conveys general pleasure and good cheer or humor.") (😃 "Grinning Face with Big Eyes" "Often conveys general happiness and good-natured amusement. Similar to 😀 Grinning Face but with taller, more excited eyes.") (😄 "Grinning Face with Smiling Eyes" "Often conveys general happiness and good-natured amusement. Similar to 😀 Grinning Face and 😃 Grinning Face With Big Eyes, but with warmer, less excited eyes.") (😁 "Beaming Face with Smiling Eyes" "Often expresses a radiant, gratified happiness. Tone varies, including warm, silly, amused, or proud.") (😆 "Grinning Squinting Face" "Often conveys excitement or hearty laughter. Similar to 😀 Grinning Face but with eyes that might say ‘Squee!’ or ‘Awesome!’ An emoji form of the >< or xD emoticons.") (😅 "Grinning Face with Sweat" "Intended to depict nerves or discomfort but commonly used to express a close call, as if saying ‘Whew!’ and wiping sweat from the forehead. ") (🤣 "Rolling on the Floor Laughing" "Often conveys hysterical laughter more intense than 😂 Face With Tears of Joy.") (😂 "Face with Tears of Joy") (🙂 "Slightly Smiling Face") (🙃 "Upside-Down Face") (😉 "Winking Face") (😊 "Smiling Face with Smiling Eyes") (😇 "Smiling Face with Halo") (🥰 "Smiling Face with Hearts") (😍 "Smiling Face with Heart-Eyes") (🤩 "Star-Struck") (😘 "Face Blowing a Kiss") (😗 "Kissing Face") (☺️ "Smiling Face") (😚 "Kissing Face with Closed Eyes") (😙 "Kissing Face with Smiling Eyes") (🥲 "Smiling Face with Tear") (😋 "Face Savoring Food") (😛 "Face with Tongue") (😜 "Winking Face with Tongue") (🤪 "Zany Face") (😝 "Squinting Face with Tongue") (🤑 "Money-Mouth Face") (🤗 "Hugging Face") (🤭 "Face with Hand Over Mouth") (🤫 "Shushing Face") (🤔 "Thinking Face") (🤐 "Zipper-Mouth Face") (🤨 "Face with Raised Eyebrow") (😐 "Neutral Face") (😑 "Expressionless Face") (😶 "Face Without Mouth") (😏 "Smirking Face") (😒 "Unamused Face") (🙄 "Face with Rolling Eyes") (😬 "Grimacing Face") (🤥 "Lying Face") (😌 "Relieved Face") (😔 "Pensive Face") (😪 "Sleepy Face") (🤤 "Drooling Face") (😴 "Sleeping Face") (😷 "Face with Medical Mask") (🤒 "Face with Thermometer") (🤕 "Face with Head-Bandage") (🤢 "Nauseated Face") (🤮 "Face Vomiting") (🤧 "Sneezing Face") (🥵 "Hot Face") (🥶 "Cold Face") (🥴 "Woozy Face") (😵 "Dizzy Face") (🤯 "Exploding Head") (🤠 "Cowboy Hat Face") (🥳 "Partying Face") (🥸 "Disguised Face") (😎 "Smiling Face with Sunglasses") (🤓 "Nerd Face") (🧐 "Face with Monocle") (😕 "Confused Face") (😟 "Worried Face") (🙁 "Slightly Frowning Face") (☹️ "Frowning Face") (😮 "Face with Open Mouth") (😯 "Hushed Face") (😲 "Astonished Face") (😳 "Flushed Face") (🥺 "Pleading Face") (😦 "Frowning Face with Open Mouth") (😧 "Anguished Face") (😨 "Fearful Face") (😰 "Anxious Face with Sweat") (😥 "Sad but Relieved Face") (😢 "Crying Face") (😭 "Loudly Crying Face") (😱 "Face Screaming in Fear") (😖 "Confounded Face") (😣 "Persevering Face") (😞 "Disappointed Face") (😓 "Downcast Face with Sweat") (😩 "Weary Face") (😫 "Tired Face") (🥱 "Yawning Face") (😤 "Face with Steam From Nose") (😡 "Pouting Face") (😠 "Angry Face") (🤬 "Face with Symbols on Mouth") ) for nom = (s-replace " " "_" name) for desc = (s-collapse-whitespace (or description "")) concat (concat ;; f_… ⇒ get emoji from company menu showing only name & emoji (format "\n** f_%s: %s %s \n%s" nom emoji "" emoji) ;; fd_… ⇒ get emoji from company menu showing name, emoji, & ‘d’escription (format "\n** fd_%s: %s %s \n%s" nom emoji desc emoji)))
;; Get all unicode emojis to appear within Emacs ;; See also: https://emacs.stackexchange.com/questions/5689/force-a-single-font-for-all-unicode-glyphs?rq=1 (unless noninteractive (set-fontset-font t nil "Apple Color Emoji"))
4.5.10. my_⋯
Templates to obtain User Information
Let's add templates for links to common user information ^_^
** my_name: User's name `user-full-name` ** my_email: User's email address `user-mail-address` ** my_github: User's Github repoistory link https://github.com/alhassy/ ** my_emacsdrepo: User's version controlled Emacs init file https://github.com/alhassy/emacs.d ** my_blog: User's blog website https://alhassy.github.io/ ** my_webpage: User's organisation website http://www.cas.mcmaster.ca/~alhassm/ ** my_twitter: User's Twitter profile https://twitter.com/musa314 ** my_masters_thesis A Mechanisation of Internal Galois Connections In Order Theory Formalised Without Meets https://macsphere.mcmaster.ca/bitstream/11375/17276/2/thesis.pdf
It may be useful to also have Org-link variants of these …
4.5.11. Templates from other places in my init
In this setup, I have some templates appear elsewhere, tagged with :noweb-ref
templates-from-other-places-in-my-init
. They are presented in natural
positions, but can only occur to the machine after template expansion is setup.
Using org-mode, we are able to present code in any order and tangle it to
the order the compilers need it to be!
Let's activate all such templates, now after template expansion has been setup.
#+begin_src org :noweb yes :tangle "~/.emacs.d/yankpad.org" :comments none <<templates-from-other-places-in-my-init>> #+end_src
You can press C-c C-v C-v, org-babel-expand-src-block, to see what this block expands into…
Expansion
** journal_guided: Introspection & Growth I'm writing from ${1:location}. Gut answer, today I feel ${2:scale}/10. ⇒ ${3:Few words or paragraphs to explain what's on your mind.} ${4: All things which cause us to groan or recoil are part of the tax of life. These things you should never hope or seek to escape. Life is a battle, and to live is to fight. ⟨ Press TAB once you've read this mantra. ⟩ $(when yas-moving-away-p "") } `(progn (eww "https://www.dailyinspirationalquotes.in/") (sit-for 2) (when nil let eww load) (read-only-mode -1) (goto-line 52) (kill-line) (kill-buffer) (yank))` ${7: Self Beliefs: + I am working on a healthier lifestyle, including a low-carb diet. - I’m also investing in a healthy, long-lasting relationship. ➩ These are what I want and are important to me. ⇦ + I will not use any substances to avoid real issues in my life. I must own them. + Everything I’m searching for is already inside of me. + Progress is more important than perfection. ⟨ Press TAB once you've read these beliefs. ⟩ $(when yas-moving-away-p "") } *Three things I'm grateful for:* 1. ${8:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} 2. ${9:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} 3. ${10:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} *Three things that would make today great:* 1. ${11:???} 2. ${12:???} 3. ${13:???} *What one thing is top of mind today?* ${14:???} *What’s one opportunity I want to go after?* ${15:???} *What’s one thing I’m really proud of OR I’m amazed and in awe of?* ${16:???} $0
Note: Since I've insisted that Org blocks are space sensative, any whitespace
before the <<⋯>>
will propogate to the resulting extracted code.
Warning!
This section had
:PROPERTIES: :CUSTOM_ID: Templates-from-other-places-in-my-init :END:
Which, as of Org 9.4, led to the entire section being tangled: This is what the
above incantation requested, but I thought it only worked on src blocks, having
the specified :noweb-ref
, not on :CUSTOM_ID:
incidentally having the same
name.
4.6. Prettify inline source code
;; Show “ src_emacs-lisp[:exports results]{ 𝒳 } ” as “ ℰ𝓁𝒾𝓈𝓅﴾ 𝒳 ﴿ ”. ;; (font-lock-add-keywords 'org-mode '(("\\(src_emacs-lisp\\[.*]{\\)\\([^}]*\\)\\(}\\)" (1 '(face (:inherit (bold) :foreground "gray65") display "ℰ𝓁𝒾𝓈𝓅﴾")) (2 '(face (:foreground "blue"))) (3 '(face (:inherit (bold) :foreground "gray65") display "﴿")) ))) ;; ;; Let's do this for all my languages: ;; Show “ src_LANGUAGE[…]{ ⋯ } ” as “ ﴾ ⋯ ﴿ ”. (cl-loop for lang in my/programming-languages do (font-lock-add-keywords 'org-mode `(( ,(format "\\(src_%s\\[.*]{\\)\\([^}]*\\)\\(}\\)" lang) (1 '(face (:inherit (bold) :foreground "gray65") display "﴾")) (2 '(face (:foreground "blue"))) (3 '(face (:inherit (bold) :foreground "gray65") display "﴿")) )))) ;; (defun my/toggle-line-fontification () "Toggle the fontification of the current line" (interactive) (defvar my/toggle-fontify/current-line -1) (defvar my/toggle-fontify/on? nil) (add-to-list 'font-lock-extra-managed-props 'display) (let ((start (line-beginning-position)) (end (line-end-position))) (cond ;; Are we toggling the current line? ((= (line-number-at-pos) my/toggle-fontify/current-line) (if my/toggle-fontify/on? (font-lock-fontify-region start end) (font-lock-unfontify-region start end)) (setq my/toggle-fontify/on? (not my/toggle-fontify/on?))) ;; Nope, we've moved on to another line. (:otherwise (setq my/toggle-fontify/current-line (line-number-at-pos) my/toggle-fontify/on? :yes_please_fontify) (font-lock-unfontify-region start end))))) ;; TODO FIXME; maybe ignore: Wasted too much time here already. ;; (add-hook 'post-command-hook #'my/toggle-line-fontification nil t) ;; (font-lock-add-keywords nil '((my/toggle-line-fontification)) t)
5. Life within Org-mode
It's hard to estimate how long a task takes if you don't keep track of time spent by ‘clocking-in and clocking-out’ of tasks. We can ‘capture’ todos right in the middle of a task without context-switching; e.g., no opening a todos file! After some reflection on the relative importance of the tasks, we can schedule them into our ‘agenda’.
Let's do this!
5.1. Using Org-Mode as a Day Planner
⟪ This section is based on a dated, yet delightful, tutorial of the same title by John Wiegley. ⟫
We want a day-planner with the following use:
- “Mindlessly” & rapidly create new tasks.
- Schedule and archive tasks at the end, or start, of the work day.
- Glance at a week's tasks, shuffle if need be.
- Prioritise the day's tasks. Aim for ≤15 tasks.
- Progress towards completion of
A
tasks by documenting work completed. - Repeat! During the day, if anything comes up, capture it and intentionally forget about it.
5.1.1. Capturing ideas & notes without interrupting the current workflow
Capture lets me quickly make notes & capture ideas, with associated reference material, without any interruption to the current work flow. Without losing focus on what you're doing, quickly jot down a note of something important that just came up.
‘my/org-capture’ Implementation
(cl-defun my/org-capture-buffer (&optional keys no-additional-remarks (heading-regexp "Subject: \\(.*\\)")) "Capture the current [narrowed] buffer as a todo/note. This is mostly intended for capturing mail as todo tasks ^_^ When NO-ADDITIONAL-REMARKS is provided, and a heading is found, then make and store the note without showing a pop-up. This is useful for when we capture self-contained mail. The HEADING-REGEXP must have a regexp parenthesis construction which is used to obtain a suitable heading for the resulting todo/note." (interactive "P") (let* ((current-content (substring-no-properties (buffer-string))) (heading (progn (string-match heading-regexp current-content) (or (match-string 1 current-content) "")))) (org-capture keys) (insert heading "\n\n\n\n" (s-repeat 80 "-") "\n\n\n" current-content) ;; The overtly verbose conditions are for the sake of clarity. ;; Moreover, even though the final could have “t”, being explicit ;; communicates exactly the necessary conditions. ;; Being so verbose leads to mutual exclusive clauses, whence order is irrelevant. (cond ((s-blank? heading) (beginning-of-buffer) (end-of-line)) ((and no-additional-remarks (not (s-blank? heading))) (org-capture-finalize)) ((not (or no-additional-remarks (s-blank? heading))) (beginning-of-buffer) (forward-line 2) (indent-for-tab-command)))))
With that in-hand, we use a wrapper to org-capture
to make use of it.
(defun my/org-capture (&optional prefix keys) "Capture something! C-c c ⇒ Capture something C-u C-c c ⇒ Capture current [narrowed] buffer. C-u 5 C-c c ⇒ Capture current [narrowed] buffer without adding additional remarks. C-u C-u C-c c ⇒ Goto last note stored. At work, ‘C-c c’ just captures notes under ‘Tasks’; no menu used." (interactive "p") (pcase prefix (4 (my/org-capture-buffer keys)) (5 (my/org-capture-buffer keys :no-additional-remarks)) (t (if my/personal-machine? (org-capture prefix keys) (org-capture prefix "t")))))
- C-c c Capture something
- C-u C-c c Capture current [narrowed] buffer.
- C-u 5 C-c c Capture current [narrowed] buffer without adding additional remarks.
- C-u C-u C-c c Goto last note stored.
E.g., I have a task, or something I wish to note down, rather than opening some file, then making a heading, then writing it; instead, I press C-c c t and a pop-up appears, I make my note, and it disappears —with my notes file(s) now being altered! Moreover, by default it provides a timestamp and a link to the file location where I made the note —helpful for tasks, tickets, to be tackled later on.
;; Location of my todos / captured notes file (unless noninteractive (setq org-default-notes-file (if my/personal-machine? "~/Dropbox/todo.org" "~/Desktop/Work-2022-01-01.org"))) ;; “C-c c” to quickly capture a task/note (define-key global-map "\C-cc" #'my/org-capture) ;; See above.
By default we only get a ‘tasks’ form of capture, let's add some more.
(cl-defun my/make-org-capture-template (shortcut heading &optional (no-todo nil) (description heading) (scheduled nil)) "Quickly produce an org-capture-template. After adding the result of this function to ‘org-capture-templates’, we will be able perform a capture with “C-c c ‘shortcut’” which will have description ‘description’. It will be added to the tasks file under heading ‘heading’. ‘no-todo’ omits the ‘TODO’ tag from the resulting item; e.g., when it's merely an interesting note that needn't be acted upon. Default for ‘description’ is ‘heading’. Default for ‘no-todo’ is ‘nil’. Scheduled items appear in the agenda; true by default. The target is ‘file+headline’ and the type is ‘entry’; to see other possibilities invoke: C-h o RET org-capture-templates. The “%?” indicates the location of the Cursor, in the template, when forming the entry. " `(,shortcut ,description entry (file+headline org-default-notes-file ,heading) ,(concat "*" (unless no-todo " TODO") " %?\n" (when nil ;; this turned out to be a teribble idea. ":PROPERTIES:\n:" (if scheduled "SCHEDULED: %^{Any time ≈ no time! Please schedule this task!}t" "CREATED: %U") "\n:END:") "\n\n ") :empty-lines 1 :time-prompt t))
(setq org-capture-templates (cl-loop for (shortcut heading) in (-partition 2 '("t" "Tasks, Getting Things Done" "r" "Reference Material" "m" "Email" "e" "Emacs (•̀ᴗ•́)و" "i" "Islam" "b" "Blog" "a" "Arbitrary Reading and Learning" "l" "Programming Languages" "p" "Personal Matters")) collect (my/make-org-capture-template shortcut heading)))
Rather than adding notes to particular Org headings in my todo.org
file, I could
defer such a choice by having only one template and have C-c a
automatically use
it. Then I could ‘refile’ tasks to their appropriate parent headings with w
.
This allows us to seperate the concerns of capturing ideas from doing any form
of processing. Something to consider.
;; Update: Let's schedule tasks during the GTD processing phase. ;; ;; For now, let's automatically schedule items a week in advance. ;; ;; (defun my/org-capture-schedule () ;; (org-schedule nil "+7d")) ;; ;; (add-hook 'org-capture-before-finalize-hook 'my/org-capture-schedule)
For now I capture everything into a single file. One would ideally keep separate client, project, information in its own org file.
- ⇒ Org capture actually lets us add any type of entry, ‘programmable template’,
to any type of file! ⇐
- Look at my/make-org-capture-template, above, to notice that capture actually lets you add any type of item to any file.
- ( For now, I'm only using it to add entries to my tasks lists. )
- Org-protocol is a way to create capture notes in org-mode from other applications.
Let's also ensure TODO-s respect hierarchical structure.
;; Cannot mark an item DONE if it has a TODO child. ;; Conversely, all children must be DONE in-order for a parent to be DONE. (setq org-enforce-todo-dependencies t)
Where am I currently capturing?
- During meetings, when a nifty idea pops into my mind, I quickly capture it.
- I've found taking my laptop to meetings makes me an active listener and I get much more out of my meetings since I'm taking notes.
- Through out the day, as I browse the web, read, and work; random ideas pop-up, and I capture them indiscriminately.
- I envision that for a phone call, I would open up a capture to make note of what the call entailed so I can review it later.
- Yet another place to capture content is from mail, such as for reference material, or self-contained tasks.
Anywhere you simply want to make a note, for the current heading, just press C-c C-z. The notes are just your remarks along with a timestamp; they are collected at the top of the tree, under the heading.
;; Ensure notes are stored at the top of a tree. (setq org-reverse-note-order nil)
Quickly look up some reference material…
(cl-defun my/reference (&optional (file org-default-notes-file)) "Look up some reference material super quick. By default we look in user's Org TODOs file. FILE should be an ORG file with a top-level heading that starts with ‘Reference’. We show its subheadings in a completing-read menu, then narrow to that entry." (interactive) (find-file file) (widen) (goto-char (point-min)) (re-search-forward "^* Reference") ;; Start of line. (org-narrow-to-subtree) (org-cycle) (org-cycle) (let* ((headings (org-map-entries (lambda () (org-element-property :title (org-element-at-point)) ) "LEVEL=2")) (topic (completing-read "What to review? " headings))) (search-forward (concat "** " topic)) (org-narrow-to-subtree) (org-cycle))) (defalias 'my/review-reference-notes 'my/reference) (defalias 'w-reference 'my/reference) ;; “w”ork
5.1.2. Step 1: When new tasks come up
Isn't it great that we can squirrel away info into some default location
then immediately return to what we were doing before —with speed & minimal distraction! ♥‿♥
Indeed, if our system for task management were slow then we may not produce
tasks and so forget them altogether! щ(゜ロ゜щ)
Entering tasks is a desirably impulsive act; do not make any further scheduling considerations.
The next step, the review stage occurring at the end or the start of the workday, is for processing.
The reason for this is that entering new tasks should be impulsive, not reasoned. Your reasoning skills are required for the task at hand, not every new tidbit. You may even find that during the few hours that transpire between creating a task and categorizing it, you’ve either already done it or discovered it doesn’t need to be done at all! ---John Wiegley
When my computer isn't handy, I'll make a note on my phone then transfer it later.
5.1.3. Step 2: Filing your tasks
At a later time, a time of reflection, we go to our tasks list and actually
schedule time to get them done by C-c C-s, org-schedule, then pick a
date by entering a number in the form +𝓃
to mean that task is due 𝓃
days from
now.
- Tasks with no due date are ones that “could happen anytime”, most likely no time at all.
- At least schedule tasks reasonably far off in the future, then reassess when the time comes.
An uncompleted task is by default rescheduled to the current day, each day, along with how overdue it is.
- Aim to consciously reschedule such tasks!
Let's keep track of how many times, and when, we have pushed events to other dates.
;; Add a note whenever a task's deadline or scheduled date is changed. (setq org-log-redeadline 'time) (setq org-log-reschedule 'time)
custard
With time, it will become clear what is an unreasonable day verses what is an achievable day.
Repeat tasks by a repeater such as ‘+1m’ or ‘+7d’ in their timestamps; e.g.,
:SCHEDULED: <2005-10-01 Sat +1m>.
Likewise, to schedule an event (to repeat) for
multiple days, just use a bunch of timestamps (with repeaters): :SCHEDULED:
<2022-01-04 Tues 9:00 +1w><2022-01-06 Thu +1w><2022-01-07 Fri 9:00 +1w>
.
(Notice that we have :
on each side of the keyword; and we have a time of day
for the event.)
A ‘project’ is a task that has multiple steps, each as a checkbox item. It can
be given a percentage marker to show progress: Place [%]
after its name, then
press C-c # ---org-update-statistics-cookies— on the name to see a
completion percentage —press C-c C-c on a checkbox item to toggle its
completion state.
5.1.4. Step 3: Quickly review the upcoming week
The next day we begin our work, we press C-c a a to see the scheduled tasks for this week ---C-c C-s to re-schedule the task under the cursor and r to refresh the agenda.
(define-key global-map "\C-ca" 'org-agenda)
- Show the next 𝓃 days schedule ⇐
C-u 𝓃 C-c a a
.
Org agenda is an interactive tool for generating summary reports from Org data —e.g., commonly, the weekly task list is generated from todo tasks.
The agenda dispatch menu, C-c a
, has options for displaying tasks —e.g., C-c a
m
generates a list of entries having the same tags. new ways to view tasks by
altering the org-agenda-custom-commands
variable —e.g., above we added two,
one for completed tasks and one for unscheduled tasks.
Let's setup the basics of our agenda.
;; List of all the files & directories where todo items can be found. Only one ;; for now: My default notes file. (setq org-agenda-files (list org-default-notes-file)) ;; Display tags really close to their tasks. (setq org-agenda-tags-column -10) ;; How many days ahead the default agenda view should look (setq org-agenda-span 'day) ;; May be any number; the larger the slower it takes to generate the view. ;; One day is thus the fastest ^_^ ;; How many days early a deadline item will begin showing up in your agenda list. (setq org-deadline-warning-days 14) ;; In the agenda view, days that have no associated tasks will still have a line showing the date. (setq org-agenda-show-all-dates t) ;; Scheduled items marked as complete will not show up in your agenda view. (setq org-agenda-skip-scheduled-if-done t) (setq org-agenda-skip-deadline-if-done t)
Super Simple ‘agenda’ Mini-tutorial
The agenda view, like nearly all Emacs entities, is interactive:
𝓃 f,b
⇒ Look forward at next week's agenda, or backward to a previous week.- The optional \(𝓃\) means do the action
𝓃
-many times; it defaults to 1.
- The optional \(𝓃\) means do the action
w, d
⇒ toggle week view, or day view; usev
to see possible views.- E.g.,
C-u 2017 v y
shows us the specific year 2017.
- E.g.,
𝓃 n,p
to navigate to next and previous entries.t
⇒ cycle TODO state of the current entry.±
⇒ cycle priority state.𝓃 S-⇆
⇒ Shift date time by \(𝓃\) days; 1 day by default.C-c C-s
⇒ Reschedule an entry; prefix it withC-u
to remove a scheduled entry.- Repeat tasks by a repeater such as ‘+1m’ or ‘+7d’
in their timestamps; e.g.,
DEADLINE: <2005-10-01 Sat +1m>.
s
⇒ save all agenda buffers; i.e., save the org-files where the agenda items live.g
⇒ Rebuild agenda according to any changes made thus far.F
⇒ Toggle ‘follow mode’: As you go up/down entries, you can see their details in an adjacent window.SPC
⇒ Show details of a single entry in other window; stay in Agenda.
RET, TAB
⇒ Go to the current entry in the current window or in a new adjacent window, so as to alter task details.
The agenda view ––even in the 7-days-at-a-time view–– will always begin on the current day. This is important, since while using org-mode as a day planner, you never want to think of days gone past. That’s something you do in other ways, such as when reviewing completed tasks.
(setq org-agenda-start-on-weekday nil) ;; Start each agenda item with ‘○’, then show me it's %timestamp and how many ;; times it's been re-%scheduled. (setq org-agenda-prefix-format " ○ %t%s")
Grouping agenda entries together
Instead of having the day's tasks all in one field, org-super-agenda allows us
to use predicates to group entries together; e.g., by considering an entry's
:tags:
or its priority level. Since I'm placing all my tasks in a single file,
under appropriate parent headings, I want entries to be shown according to their
parent heading. Of-course, the top-most grouping, the important tasks, should be
pulled out of their group and placed at the top.
(use-package origami) (use-package org-super-agenda :hook (org-agenda-mode . origami-mode) ;; Easily fold groups via TAB. :bind (:map org-super-agenda-header-map ("<tab>" . origami-toggle-node)) :config (org-super-agenda-mode) (setq org-super-agenda-groups '((:name "Important" :priority "A") (:name "Personal" :habit t) ;; For everything else, nicely display their heading hierarchy list. (:auto-map (lambda (e) (org-format-outline-path (org-get-outline-path))))))) ;; MA: No noticable effect when using org-super-agenda :/ ;; ;; Leave new line at the end of an entry. ;; (setq org-blank-before-new-entry '((heading . t) (plain-list-item . t)))
The org-super-agenda homepage shows complex configurations and pleasant screenshots contrasting with and without the system. E.g., you can change how entries in particular headings are displayed and coloured.
5.1.5. Step 4: Getting ready for the day
After having seen our tasks for the week, we press d to enter daily view for
the current day. Now we decide whether the items for today are A
: of high
urgency & important; B
: of moderate urgency & importance; or C
: Pretty much
optional, or very quick or fun to do.
A
tasks should be both important and urgently done on the day they were scheduled.- Such tasks should be relatively rare!
- If you have too many, you're anxious about priorities and rendering priorities useless.
C
tasks can always be scheduled for another day without much worry.- Act! If the thought of rescheduling causes you to worry, upgrade it to a
B
orA
.
- Act! If the thought of rescheduling causes you to worry, upgrade it to a
- As such, most tasks will generally be priority
B
: Tasks that need to be done, but the exact day isn't as critical as with anA
task. These are the “bread and butter” tasks that make up your day to day life.
On a task item, or any org-heading, press , then one of A/B/C to set its priority. Then r to refresh.
Pretty Prioritisation Markers
Let's set four priority levels and their colours: The more intense colours are for more urgent tasks.
(setq org-lowest-priority ?C) ;; Now org-speed-eky ‘,’ gives 3 options (setq org-priority-faces '((?A :foreground "red" :weight bold) ;; :background "LightCyan1") (?B :foreground "orange" :weight bold) (?C :foreground "green" :weight bold))) ;; See all colours with: M-x list-colors-display
C-c ,
anywhere to set the priority of the current heading.- We may press
A-D
orSPC
to an remove existing priority.
- We may press
Priority markers are of the form [#𝒳]
, the fancy priorities package visually
renders them as words or icons.
(use-package org-fancy-priorities :diminish org-fancy-priorities-mode :hook (org-mode . org-fancy-priorities-mode) :custom (org-fancy-priorities-list '("High" "MID" "LOW")) ;; "OPTIONAL" ;; Let's use the “Eisenhower map of priority”… ;; :custom (org-fancy-priorities-list '("Urgent and Important" ;; Do now! ;; "Not Urgent But Important" ;; Do schedule this. ;; "Urgent But Not Important" ;; Delegate? ;; "Not Urgent and Not Important")) ;; Don't do / Optional )
At work, my tasks list is massive and constantly growing; so to avoid stressing
myself out, I try to add time effort estimates to my tasks with
org-set-effort then I open my Org agenda and run d
(for day view) then
org-agenda-columns (C-c C-x C-c
) to see a column-view of my time estimates
along with a total time estimate for the day. Since there are unexpected
pair-programming calls, or meetings go longer than expected, I only schedule for
7 hours each day (in a usual 8-hour work day); i.e., if my time estimates exceed
7h then I reschedule or cancel some things.
(require 'org-agenda) ;; How should the columns view look? (setq org-columns-default-format "%60ITEM(Task) %6Effort(Estim){:} %3PRIORITY %TAGS") ;; Press “c” in Org agenda to see the columns view; (default binding C-c C-x C-c is too long!) (org-defkey org-agenda-mode-map "c" #'org-agenda-columns) (org-defkey org-agenda-mode-map "C" #'org-agenda-goto-calendar) ;; Press “e” in columns view to alter “e”ffort “e”stimates. (require 'org-colview) (org-defkey org-columns-map "e" ;; Refresh after making an effort estimate. (lambda () (interactive) (org-agenda-set-effort) (org-agenda-columns)))
Near the top of my TODOS file, I have the following clickable link: It looks pretty and when I click it, takes me where I need to go.
[[elisp:(progn (org-agenda nil "a") (org-agenda-columns) (delete-other-windows))][Show agenda with time estimates]]
5.1.6. Step 5: Doing the work
Since A
tasks are the important and urgent ones, if you do all of the A
tasks and
nothing else today, no one would suffer. It's a good day (─‿‿─).
There should be no scheduling nor prioritising at this stage. You should not be touching your tasks file until your next review session: Either at the end of the day or the start of the next.
- Leverage priorities! E.g., When a full day has several
C
tasks, reschedule them for later in the week without a second thought.- You've already provided consideration when assigning priorities.
5.1.7. Step 6: Moving a task toward completion
My workflow states are described in the section
5.5 and contain states: TODO, STARTED, WAITING, ON_HOLD, CANCELLED, DONE
.
- Tasks marked
WAITING
are ones for which we are awaiting some event, like someone to reply to our query. As such, these tasks can be rescheduled until I give up or the awaited event happens —in which case I go toSTARTED
and document the reply to my query. - The task may be put off indefinitely with
ON_HOLD
, or I may choose never to do it withCANCELLED
. Along withDONE
, these three mark a task as completed and so it needn't appear in any agenda view.
I personally clock-in and clock-out of tasks —keep reading—, where upon clocking-out I'm prompted for a note about what I've accomplished so far. Entering a comment about what I've done, even if it's very little, feels like I'm getting something done. It's an explicit marker of progress.
In the past, I would make a “captain's log” at the end of the day, but that's like commenting code after it's written, I didn't always feel like doing it and it wasn't that important after the fact. The continuous approach of noting after every clock-out is much more practical, for me at least.
5.1.8. Step 7: Archiving Tasks
During the review state, when a task is completed, ‘archive’ it with
org-archive-subtree or C-c C-x C-s: This marks it as done, adds a time
stamp, and moves it to a local *.org_archive
file. What was our ‘to do’ list
becomes a ‘ta da’ list showcasing all we have done (•̀ᴗ•́)و
Archiving keeps task lists clutter free, but unlike deletion it allows us,
possibly rarely, to look up details of a task or what tasks were completed in a
certain time frame —which may be a motivational act, to see that you have
actually completed more than you thought, provided you make and archive tasks
regularly. We can use M-x org-search-view
to search an org file and the
archive file too, if we enable it so.
;; C-c a s ➩ Search feature also looks into archived files. ;; Helpful when need to dig stuff up from the past. (setq org-agenda-text-search-extra-files '(agenda-archives))
;; enables the org-agenda variables. (require 'org-agenda) ;; Need this to have “org-agenda-custom-commands” defined. (unless noninteractive ;; ➩ Show my agenda upon Emacs startup. (org-agenda "a" "a"))
Let's install some helpful views for our agenda.
C-c a c
: See completed tasks at the end of the day and archive them.;; Pressing ‘c’ in the org-agenda view shows all completed tasks, ;; which should be archived. (add-to-list 'org-agenda-custom-commands '("c" todo "DONE|ON_HOLD|CANCELLED" nil))
C-c a u
: See unscheduled, undeadlined, and undated tasks in my todo files. Which should then be scheduled or archived.(add-to-list 'org-agenda-custom-commands '("u" alltodo "" ((org-agenda-skip-function (lambda () (org-agenda-skip-entry-if 'scheduled 'deadline 'regexp "\n]+>"))) (org-agenda-overriding-header "Unscheduled TODO entries: "))))
At the end of the day, let's schedule at least 3 things that must be done the
next day; i.e., have priority A
.
5.2. Tag! You're it!
Even when items are categorised under their own parent headings, they may be
related in some way and that can made explicit by adding a :tag:
to their
headings; e.g., two entries both have the :jasim:@work:
tags, then looking for
the :@work:
tag shows me all entries that are tagged as “at work”.
Tags provide a cross-section of one's entries. |
Tags let us find related stuff quickly, even though they're differently categorised.
After calling org-agenda
, we may select m
to match for tags, or use
org-tags-view
to search for tags.
What to tag? Common tags are :@laptop:, :@work:, :@home:
to identify the
location where tasks take place —Use: When I'm at a particular place, I need
only consider tasks that apply to that place ;-) Other tags I use are :𝑭𝑳:
to
identify remarks or email or request from person 𝑭irstname 𝑳astname; or
something that might be interesting to that person. I also use :video:, :book:,
:paper:
; which let me quickly find all videos! Finally, I also use
:project_name:
to identify notes that may be of interest to a particular
project, but are more appropriately categorised elsewhere —e.g., when learning
about an Emacs feature, I may tag my notes with another project's name to
consider whether that feature could be useful there.
How to tag?
You can just add a :tag₁:⋯:tagₙ:
after a heading. If you press space, before the
tags, then they are automatically indented flushright to column 77; postive
numbers do not flushright but use exact column number.
(setq org-tags-column -77) ;; the default
Use C-c C-q
, or org-set-tags-command
, on a heading or just the speed key :
on
the asterisks of a heading to set the tags of an item —as usual, with Helm we
obtain a window of all existing tags to select from. Unfortunatley, this only
supports having one tag; for more, you can add them in manually or …
;; Press ‘:’ on a heading to add a tag, press TAB to see all tags, press RETURN on a tag to add it, press TAB again to add more tags, when all done press RETURN twice. (use-package helm-org) ;; Helm for org headlines and keywords completion. (add-to-list 'helm-completing-read-handlers-alist '(org-set-tags-command . helm-org-completing-read-tags)) ;; Also provides: helm-org-capture-templates
Now :
or C-c C-q
will show existing tags for the current heading, press TAB
to
obtain a list of all exisiting tags, press C-SPC
to select the desired tags,
then TAB
or RET
to confirm the resulting tag list, and RET
to finish or TAB
to
select more tags.
Let's render tags by Unicode symbols.
(use-package org-pretty-tags :diminish org-pretty-tags-mode :demand t :config (setq org-pretty-tags-surrogate-strings '(("Neato" . "💡") ("Blog" . "✍") ("Audio" . "♬") ("Video" . "📺") ("Book" . "📚") ("Running" . "🏃") ("Question" . "❓") ("Wife" . "💕") ("Text" . "💬") ; 📨 📧 ("Friends" . "👪") ("Self" . "🍂") ("Finances" . "💰") ("Car" . "🚗") ; 🚙 🚗 🚘 ("Urgent" . "🔥"))) ;; 📥 📤 📬 (org-pretty-tags-global-mode 1))
5.3. Automating Pomodoro —“Commit for only 25 minutes!”
Effort estimates are for an entire task. Yet, sometimes it's hard to even get started on some tasks.
- The code below ensures a 25 minute timer is started whenever clocking in happens.
- The timer is in the lower right of the modeline.
- When the timer runs out, we get a notification.
- We may have the momentum to continue on the difficult task, or clock-out and take a break after documenting what was accomplished.
;; Tasks get a 25 minute count down timer (setq org-timer-default-timer 25) ;; Use the timer we set when clocking in happens. (add-hook 'org-clock-in-hook (lambda () (org-timer-set-timer '(16)))) ;; unless we clocked-out with less than a minute left, ;; show disappointment message. (add-hook 'org-clock-out-hook (lambda () (unless (s-prefix? "0:00" (org-timer-value-string)) (message-box "The basic 25 minutes on this difficult task are not up; it's a shame to see you leave.")) (org-timer-stop)))
Note that this does not conflict with the total effort estimate for the task.
(I'm told there's a package already made for this —maybe I need to stop writing code, and do more searches; then again, I've learned a lot by writing code.)
5.4. Journaling
Thus far I've made it easy to quickly capture ideas and tasks, not so much on the analysis phase:
- What was accomplished today?
- What are some notably bad habits? Good habits?
- What are some future steps?
Rather than overloading the capture mechanism for such thoughts, let's employ
org-journal
—journal entries are stored in files such as journal/20190407
,
where the file name is simply the date, or only one file per year as I've set it
up below. Each entry is the week day, along with the date, then each child tree
is an actual entry with a personal title preceded by the time the entry was
made. Unlike capture and its agenda support, journal ensures entries are
maintained in chronological order with calendar support.
Since org files are plain text files, an entry can be written anywhere and later ported to the journal. Or, written directly in the journal file if we add the necessary Org-header: Asterisks and time.
The separation of concerns is to emphasise the capture stage as being quick and relatively mindless, whereas the journaling stage as being mindful. Even though we may utilise capture to provide quick support for including journal entries, I have set my journal to be on a yearly basis —one file per year— since I want to be able to look at previous entries when making the current entry; after all, it's hard to compare and contrast easily unless there's multiple entries opened already.
As such, ideally at the end of the day, I can review what has happened, and what has not, and why this is the case, and what I intend to do about it, and what problems were encountered and how they were solved —in case the problem is encountered again in the future. Consequently, if I encounter previously confronted situations, problems, all I have to do is reread my journal to get an idea of how to progress. Read more about the importance of reviewing your day on a daily basis.
Moreover, by journaling with Org on a daily basis, it can be relatively easy to produce a report on what has been happening recently, at work for example. I'd like to have multiple journals, for work and for personal life, as such I will utilise a prefix argument to obtain my work specific entries.
5.4.1. The Setup
Anyhow, the setup:
(use-package org-journal ;; C-u C-c j ⇒ Work journal ;; C-c j ⇒ Personal journal :bind (("C-c j" . my/org-journal-new-entry)) :config (setq org-journal-dir "~/Desktop/" ;; "~/Dropbox/journal/" org-journal-file-type 'yearly org-journal-file-format "Personal-%Y-%m-%d.org") (defun my/org-journal-new-entry (prefix) "Open today’s journal file and start a new entry. With a prefix, we use the work journal; otherwise the personal journal." (interactive "P") (let ((org-journal-dir (if prefix "~/Desktop/" org-journal-dir)) (org-journal-file-format (if prefix "Work-%Y-%m-%d.org" org-journal-file-format))) (org-journal-new-entry nil) (org-mode) (org-show-all))))
5.4.2. Super Terse Tutorial
Bindings available in org-journal-mode
, when journaling:
C-c C-j
: Insert a new entry into the current journal file.- Note that keys for
org-journal-new-entry
shadow those fororg-goto
.
- Note that keys for
C-c C-s
: Search the journal for a string.- Note that keys for
org-journal-search
shadow those fororg-schedule
.
- Note that keys for
All journal entries are registered in the Emacs Calendar. To see available
journal entries do M-x calendar
. Bindings available in the calendar-mode:
j
: View an entry in a new buffer.i j
: ‘I’nsert a new ‘j’ournal entry into the day’s file.f w/m/y/f/F
: ‘F’ind, search, in all entries of the current week, month, year, all of time, of in all entries in the future.
All journal entries are registered in the Emacs Calendar. To see available journal entries do M-x calendar. Bindings available in the calendar-mode:
- j d display an entry; use j r to jump to the new reading buffer; reading is in view-mode: q to quit reading and SPC to scroll.
- j s w/m/y/f search the journal entries of the current week/month/year or for all time
[/]
go the previous/next day with journal entries
5.4.3. Guided Journaling
Sometimes it can be tough to journal, but filling in a template can be a way to
get started. Later on, we will setup 4.5 which will
allow us to write journal_guided
then TAB
to obtain the template below. Each $𝓃
indicates a position that we may input text, after which we TAB
to move to next
location.
Just like the undo-tree
setup at the start of this read, we use a noweb-ref
to present this template in a natural position; then later when template
expansion it setup, we request it to be tangled.
** journal_guided: Introspection & Growth I'm writing from ${1:location}. Gut answer, today I feel ${2:scale}/10. ⇒ ${3:Few words or paragraphs to explain what's on your mind.} ${4: All things which cause us to groan or recoil are part of the tax of life. These things you should never hope or seek to escape. Life is a battle, and to live is to fight. ⟨ Press TAB once you've read this mantra. ⟩ $(when yas-moving-away-p "") } `(progn (eww "https://www.dailyinspirationalquotes.in/") (sit-for 2) (when nil let eww load) (read-only-mode -1) (goto-line 52) (kill-line) (kill-buffer) (yank))` ${7: Self Beliefs: + I am working on a healthier lifestyle, including a low-carb diet. - I’m also investing in a healthy, long-lasting relationship. ➩ These are what I want and are important to me. ⇦ + I will not use any substances to avoid real issues in my life. I must own them. + Everything I’m searching for is already inside of me. + Progress is more important than perfection. ⟨ Press TAB once you've read these beliefs. ⟩ $(when yas-moving-away-p "") } *Three things I'm grateful for:* 1. ${8:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} 2. ${9:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} 3. ${10:??? … e.g., old relationship, something great yesterday, an opportunity I have today, something simple near me within sight} *Three things that would make today great:* 1. ${11:???} 2. ${12:???} 3. ${13:???} *What one thing is top of mind today?* ${14:???} *What’s one opportunity I want to go after?* ${15:???} *What’s one thing I’m really proud of OR I’m amazed and in awe of?* ${16:???} $0
Besides a bit of webscraping to obtain a daily inspirational quote image, and the necessary yasnippet code, this template was taken from a discussion on Hacker news: “I find journaling indispensable”. In time, I will likely alter it to meet my needs, but I like it as it is right now (•̀ᴗ•́)و
5.5. Workflow States
Here are some of my common workflow states, —the ‘X/Y’ indicates to do action ‘X’ when entering a state and ‘Y’ when leaving it, with ‘!’ denoting a timestamp should be generated and ‘@’ denoting a user note should be made.
(setq org-todo-keywords '((sequence "TODO(t)" "STARTED(s@/!)" "|" "DONE(d/!)") (sequence "WAITING(w@/!)" "ON_HOLD(h@/!)" "|" "CANCELLED(c@/!)"))) ;; Since DONE is a terminal state, it has no exit-action. ;; Let's explicitly indicate time should be noted. (setq org-log-done 'time)
The @
brings up a pop-up to make a local note about why the state changed.
Super cool stuff!
In particular, we transition from TODO
to STARTED
once 15 minutes, or a
reasonable amount, of work has transpired. Since all but one state are marked
for logging, we could use the lognotestate
logging facility of org-mode, which
prompts for a note every time a task’s state is changed.
Entering a comment about what I've done, even if it's very little, feels like
I'm getting something done. It's an explicit marker of progress and motivates me
to want to change my task's states more often until I see it marked DONE
.
Here's how they are coloured,
(setq org-todo-keyword-faces '(("TODO" :foreground "red" :weight bold) ("STARTED" :foreground "blue" :weight bold) ("DONE" :foreground "forest green" :weight bold) ("WAITING" :foreground "orange" :weight bold) ("ON_HOLD" :foreground "magenta" :weight bold) ("CANCELLED" :foreground "forest green" :weight bold)))
Now we press C-c C-t
then the letter shortcut to actually make the state of an org heading.
(setq org-use-fast-todo-selection t)
We can also change through states using Shift- left, or right.
Let's draw a state diagram to show what such a workflow looks like.
PlantUML supports drawing diagrams in a tremendously simple format —it even supports Graphviz/DOT directly and many other formats. Super simple setup instructions can be found here; below are a bit more involved instructions. Read the manual here.
;; Install the tool ; (async-shell-command "brew tap adoptopenjdk/openjdk; brew cask install adoptopenjdk13") ;; Dependency ; (async-shell-command "brew install plantuml") ;; Tell emacs where it is. ;; E.g., (async-shell-command "find / -name plantuml.jar") (setq org-plantuml-jar-path "/usr/local/Cellar/plantuml/1.2020.19/libexec/plantuml.jar") ;; Enable C-c C-c to generate diagrams from plantuml src blocks. (add-to-list 'org-babel-load-languages '(plantuml . t) ) (require 'ob-plantuml) ; Use fundamental mode when editing plantuml blocks with C-c ' (add-to-list 'org-src-lang-modes '("plantuml" . fundamental))
Let's use this!
skinparam defaultTextAlignment center /' Text alignment '/ skinparam titleBorderRoundCorner 15 skinparam titleBorderThickness 2 skinparam titleBorderColor red skinparam titleBackgroundColor Aqua-CadetBlue title My Personal Task States [*] -> Todo /' This is my starting state '/ Done -right-> [*] /' This is an end state '/ Cancelled -up-> [*] /' This is an end state '/ /'A task is “Todo”, then it's “started”, then finally it's “done”. '/ Todo -right-> Started Started -down-> Waiting Waiting -up-> Started Started -right-> Done /'Along the way, I may pause the task for some reason then return to it. This may be since I'm “Blocked” since I need something, or the task has been put on “hold” since it may not be important right now, and it may be “cancelled” eventually. '/ Todo -down-> Waiting Waiting -up-> Todo Waiting -up-> Done Todo -down-> On_Hold On_Hold -> Todo On_Hold -down-> Cancelled Waiting -down-> Cancelled Todo -down-> Cancelled /' The Org-mode shortcuts for these states are as follows. '/ Todo : t On_Hold : h Started : s Waiting : w Cancelled : c Done : d /' If a task is paused, we should document why this is the case. '/ note right of Waiting: Note what is\nblocking us. note right of Cancelled: Note reason\nfor cancellation. note bottom of On_Hold: Note reason\nfor reduced priority. center footer ♥‿♥ Org-mode is so cool (•̀ᴗ•́)و /' Note that we could omit the “center, left, right” if we wished, or used a “header” instead.'/
Of note:
- Multiline comments are with
/' comment here '/
, single quote starts a one-line comment. - Nodes don't need to be declared, and their names may contain spaces if they are enclosed in double-quotes.
One forms an arrow between two nodes by writing a line with
x ->[label here] y
ory <- x
; or using-->
and<--
for dashed lines. The label is optional.To enforce a particular layout, use
-X->
whereX ∈ {up, down, right, left}
.- To declare that a node
x
has fieldsd, f
we make two new lines havingx : f
andx : d
. One adds a note near a node
x
as follows:note right of x: words then newline\nthen more words
.Likewise for notes on the
left, top, bottom
.- A note can be on several lines. It's terminated by
end note
.
- A note can be on several lines. It's terminated by
- Interesting sprites and many other things can be done with PlantUML. Read the docs.
This particular workflow is inspired by Bernt Hansen —while quickly searching through the PlantUML manual: The above is known as an “activity diagram” and it's covered in §4.
Org-mode may be used with PlantUML:
- See §11,12 for using Org-mode notation to form ‘mindmaps’ and ‘work breakdown structures’.
- Org-mode text formatters are also acknowledged but the delimiters must be doubled; see §16.1.
You can quickly write and see the resulting UMLs using https://liveuml.com/, for the most part.
5.6. Clocking Work Time
Let's keep track of the time we spend working on tasks that we may have captured for ourselves the previous day. Such statistics provides a good idea of how long it actually takes me to accomplish a certain task in the future and it lets me know where my time has gone.
- Clock in
- on a heading with
I
, or in the subtree withC-c C-x C-i
. - Clock out
- of a heading with
O
, or in the subtree withC-c C-x C-o
. - Clock report
- See clocked times with
C-c C-x C-r
.
After clocking out, the start and end times, as well as the elapsed time, are added to a drawer to the heading. We can punch in and out of tasks as many times as desired, say we took a break or switched to another task, and they will all be recorded into the drawer.
;; Record a note on what was accomplished when clocking out of an item. (setq org-log-note-clock-out t)
To get started, we could estimate how long a task will take and clock-in; then clock-out and see how long it actually took.
Sometimes, at the beginning at least, I would accidentally invoke the transposed
command C-x C-c
, which saves all buffers and quits Emacs. So here's a helpful
way to ensure I don't quit Emacs accidentally.
(setq confirm-kill-emacs 'yes-or-no-p)
A few more settings:
;; Resume clocking task when emacs is restarted (org-clock-persistence-insinuate) ;; Show lot of clocking history (setq org-clock-history-length 23) ;; Resume clocking task on clock-in if the clock is open (setq org-clock-in-resume t) ;; Sometimes I change tasks I'm clocking quickly ---this removes clocked tasks with 0:00 duration (setq org-clock-out-remove-zero-time-clocks t) ;; Clock out when moving task to a done state (setq org-clock-out-when-done t) ;; Save the running clock and all clock history when exiting Emacs, load it on startup (setq org-clock-persist t) ;; Do not prompt to resume an active clock (setq org-clock-persist-query-resume nil) ;; Include current clocking task in clock reports (setq org-clock-report-include-clocking-task t)
5.6.1. Finding tasks to clock in
Use one of the following options, with the top-most being the first to be tried.
- From anywhere,
C-u C-c C-x C-i
yields a pop-up for recently clocked in tasks. - Pick something off today's agenda scheduled items.
- Pick a
Started
task from the agenda view, work on this unfinished task. - Pick something from the
TODO
tasks list in the agenda view.
C-c C-x C-d
also provides a quick summary of clocked time for the current org file.
5.6.2. Estimates versus actual time
Before clocking into a task, add to the properties drawer :Effort: 1:25
or C-c
C-x C-e
, for a task that you estimate will take an hour and twenty-five minutes,
for example. Now the modeline will mention the time elapsed alongside the task
name. Woah!
(push '("Effort_ALL" . "0:15 0:30 0:45 1:00 2:00 3:00 4:00 5:00 6:00 0:00") org-global-properties)
Use speed keys
e/E
to insert an effort estimate, with the above being provided options, or to increment the current effort to the next one in the above list.
This is also useful when you simply want to put a time limit on a task that wont be completed anytime soon, say writing a thesis or a long article, but you still want to work on it for an hour a day and be warned when you exceed such a time constraint.
When you've gone above your estimate time, the modeline colours it red.
5.7. Habit Formation
The reason to use habits is that they come with a graph indicating consistency by colour, and the goal of the game is to have the longest possible chain —no red days!
A ‘habit’ is a usual (recurring) todo task marked as a habit:
Use C-c C-x p
to set the STYLE
property to habit
on a task to set it as a habit.
;; Show habits for every day in the agenda. (setq org-habit-show-habits t) (setq org-habit-show-habits-only-for-today nil) ;; This shows the ‘Seinfeld consistency’ graph closer to the habit heading. (setq org-habit-graph-column 90) ;; In order to see the habit graphs, which I've placed rightwards, let's ;; always open org-agenda in ‘full screen’. ;; (setq org-agenda-window-setup 'only-window)
inch by inch anything's a cinch! |
!
means today and ⋆
means a task has been done on that day;
intuitively green means you're on track, yellow is warning sign of overdue,
red is overdue, and blue is an acceptable break day.
Here's an example habit from the Org-mode manual, where .+𝒳d/𝒴d
reads
perform the habit once every 𝒳 days, but never let me go 𝒴 days without doing it.
** TODO Shave SCHEDULED: <2020-01-08 Wed .+2d/4d> :PROPERTIES: :STYLE: habit :END:
Shave every 2 days, but we can take a 3-day break; however, on the 4th day, gotta shave! (To “ignore” a habit, just reschedule it for another day.)
Remember that in the agenda view if you alter a task, say with t
to mark it
done, then you need to use s
to save the underlying todo/notes files; otherwise,
any g
will revert the change in the agenda buffer.
5.8. Actually Doing Things —or Sending notifications from Emacs
Let's setup a little audio-visual reminder to regularly check my agenda and ensure I'm not narrowing on a single task and ignoring others.
- More generally, we can use this snippet of code to let Emacs notify us of other things.
- For instance, the message function shows text in the minibuffer, which might be missed when there are multiple incoming messages or focus is on a non-Emacs application (gasp!). Then, my/notify could be used to produce MacOS system-wide notifications.
The text-to-speech tool we'll use is say
; which on a Mac can be activated
in a browser: Select some text, right-click, select Speech
, then Start/Stop Speaking
.
- TODO: Make the command below randomly use an English speaking voice; “tldr say” to learn more.
- TODO: Also finish reading the above mentioned links, with nice examples.
;; Obtain a notifications and text-to-speech utilities (system-packages-ensure "say") ;; Built-into MacOS, but can be downloaded in Ubuntu (system-packages-ensure "terminal-notifier") ;; MacOS specific ;; System Preferences → Notifications → Terminal Notifier → Allow “alerts”. ;; E.g.,: (shell-command "terminal-notifier -title \"Hiya\" -message \"hello\"")
By default, notifications are in banner style —they go away automatically—
we can use alert style —in which they stay until dismissed— in MacOS as
follows: System Preferences → Notifications → terminal-notifier → Alerts
.
(cl-defun my/notify (message &key (titled "") at repeat-every-hour open) "Notify user with both an visual banner, with a beep sound, and a text-to-speech recitation. When the user clicks on the resulting notification, unless a given OPEN url is provided, the Emacs application is brough into focus. MESSAGE and TITLE are strings; AT is a time string as expected of `run-at-time' such as \"11.23pm\" or \"5 sec\"; REPEAT-EVERY-HOUR is a floating-point number of hours to continuously repeat the alert. OPEN is a URL that is opened when the user clicks the notification. This can be a web or file URL, or any custom URL scheme. I initially used optional arguments, but realised that in due time it would be more informative to use named arguments instead. Example uses: ;; In 5 minutes from now, remind me to watch this neato video! (my/notify \"🔔 Get things done! 📎 💻 \" :open \"https://www.youtube.com/watch?v=23tusPiiNZk&ab_channel=Motiversity\" :at \"5 minutes\") ;; Remind me to exercise every 1.5hours; starting at 8:00am. (my/notify \"Take a 5min break and get your blood flowing!\" :titled \"Exercise\" :at \"8:00am\" :repeat-every-hour 1.5) ;; Actually getting things done! (my/notify \"Is what you're doing actually in alignment with your goals? Maybe it's time to do another task?\" :titled \"Check your agenda!\" :at \"10:00am\" :repeat-every-hour 2) " (run-at-time at ;; the time to make the alert (when repeat-every-hour (* 60 60 repeat-every-hour)) #'async-shell-command (format "%s" `(terminal-notifier -title ,(pp-to-string titled) -message ,(s-replace "\\n" "\n" (pp-to-string message)) ;; Play a random sound when the notification appears. See sound names with: ls /System/Library/Sounds ;; Use the special NAME “default” for the default notification sound. -sound ,(progn (require 'seq) (seq-random-elt (s-split "\n" (shell-command-to-string "ls /System/Library/Sounds")))) ;; Don't create duplicates of the notification, just one instance; ;; i.e., each notification belongs to a group and only one alert of the group may be present at any one time. -group ,(pp-to-string titled) ;; Activate the application specified by ID when the user clicks the notification. -activate org.gnu.Emacs ,@(when open `(-open ,(pp-to-string open))) ;; Run the shell command COMMAND when the user clicks the notification. ;; -execute COMMAND & ;; … and then speak! … ;; ;; say ,(s-replace "\\n" " " (pp-to-string message)) ))))
The following two actual uses cases are also mentioned in my/notify docstring, since I want the documentation to be self-contained.
;; (Emojis look terrible in Lisp; but much better when the alert is actually made!) ;; Remind me to exercise every 1.5hours; starting at 8:00am. (my/notify "Take a 5min break and get your blood flowing!\n\t\t🚣 🏃♂️ 🧗♂️ 🧘♂️ 🏊 🏋 🚴♂️" :titled "🤾♀️ Exercise 🚵♂️" :at "8:00am" :repeat-every-hour 1.5 :open "https://www.youtube.com/watch?v=23tusPiiNZk&ab_channel=Motiversity") ;; Actually getting things done! (my/notify "Is what you're doing actually in alignment with your goals? ✔️📉 Maybe it's time to do another task? 📋" :titled "📆 Check your agenda! 🔔" :at "10:00am" :repeat-every-hour 2)
- Here is an approach to triggering audio-visual alarms from Org-mode events
—using
org-agenda-to-appt
. - Emacs's built in appointment notification facility can also be used as a alarm
clock via
M-x appt-add
.
The marquee-header package let's show messages as “horizontal moving text along the top of the Emacs frame”, which is neat.
- Slightly related is logms, which let's us make
message
calls but we can also see the context/source where those calls were made; as well as a clickable link back to the source.
6. Cosmetics
Upon startup, we want to be greeted with a useful, yet unobtrusive, message
briefly detailing major system details. Moreover, the bottom-most area of the
screen should display battery life, data, & time. Likewise, we may have a casual
file explorer —primarily to show-off to newcomers, since great functionality
is found with M-x dired
---dired.
;; Get org-headers to look pretty! E.g., * → ⊙, ** ↦ ◯, *** ↦ ★ ;; https://github.com/emacsorphanage/org-bullets (use-package org-bullets :hook (org-mode . org-bullets-mode))
6.1. Startup message: Emacs & Org versions
Let's always welcome ourselves when Emacs begins with a helpful message. For example, which user account is running and what are the version numbers of our primary tools.
;; Silence the usual message: Get more info using the about page via C-h C-a. (setq inhibit-startup-message t) (defun display-startup-echo-area-message () "The message that is shown after ‘user-init-file’ is loaded." (message (concat "Welcome " user-full-name "! Emacs " emacs-version "; Org-mode " org-version "; System " (symbol-name system-type) "/" (system-name) "; Time " (emacs-init-time))))
Now my startup message is,
Welcome Musa Al-hassy! Emacs 27.1; Org-mode 9.4.4; System darwin/Musas-MacBook-Air.local; Time 13.331914 seconds
Let's change the Emacs frame to mention the name of the buffer in focus, as well as a nice ‘motto’:
;; Keep self motivated! (setq frame-title-format '("" "%b - Living The Dream (•̀ᴗ•́)و"))
6.2. My to-do list: The initial buffer when Emacs opens up
I almost always have Emacs open; I don't need a dashboard, but would like to see my to-do list and my init file, side-by-side.
(unless noninteractive ;; Only run the following when we're in GUI mode; ;; i.e., don't run it in Github Actions when testing. (if my/personal-machine? (find-file "~/Dropbox/todo.org") ;; After startup, if Emacs is idle for 10 seconds, then open my work file; ;; which is a GPG file and so requires passphrase before other things can load. ;; (run-with-idle-timer 10 nil (lambda () (find-file "~/Desktop/work.org.gpg"))) (find-file "~/Desktop/Work-2022-01-01.org")) ;; Org-journal for work (split-window-right) ;; C-x 3 (other-window 1) ;; C-x 0 (let ((enable-local-variables :all) ;; Load *all* locals. (org-confirm-babel-evaluate nil)) ;; Eval *all* blocks. (ignore-errors (find-file "~/.emacs.d/init.org"))))
There is the neat-looking emacs-dashboard package that provides an extensbile yet minimalist splash screen showing recent files, projects, and bookmarks.
6.3. A sleek, informative, & fancy mode line
The ‘modeline’ is a part near the bottom of Emacs that gives information about the current buffer, such as its file-type/‘major-mode’ and enabled extensions/‘minor-modes’. Let's use the doom-modeline, which is a sleek & minimalistic, yet fancy setup with the following notable perks:
- Gives each buffer a nice icon in the modeline (denoting its major mode; e.g., Lisp/JavaScript/Org/etc each get a cool icon).
- Name of file becomes red when unsaved/modified.
- Nice version control icon, with branch name.
- Name of file is of the shape is shown as “project/file.ext”, when a project
is detected using
projectile.el
. - Flycheck error reporting is ugly by default, and one would consider using flycheck-status-emojis to make things look better in a simple modeline, but Doom-modeline gives a nice status indicators for Flycheck.
- Shows “+2” when the text scale is two above usual.
For fine-grained control on what/how things appear, there is doom-modeline-def-modeline and doom-modeline-set-modeline.
;; This package requires the fonts included with all-the-icons to be installed. Run M-x all-the-icons-install-fonts to do so. ;; The modeline looks really nice with doom-themes, e.g., doom-solarised-light. (use-package doom-modeline :config (doom-modeline-mode)) ;; Use minimal height so icons still fit; modeline gets slightly larger when ;; buffer is modified since the "save icon" shows up. Let's disable the icon. ;; Let's also essentially disable the hud bar, a sort of progress-bar on where we are in the buffer. (setq doom-modeline-height 1) (setq doom-modeline-buffer-state-icon nil) (setq doom-modeline-hud t) (setq doom-modeline-bar-width 1) ;; Show 3 Flycheck numbers: “red-error / yellow-warning / green-info”, which ;; we can click to see a listing. ;; If not for doom-modeline, we'd need to use flycheck-status-emoji.el. (setq doom-modeline-checker-simple-format nil) ;; Don't display the buffer encoding, E.g., “UTF-8”. (setq doom-modeline-buffer-encoding nil) ;; Inactive buffers' modeline is greyed out. ;; (let ((it "Source Code Pro Light" )) ;; (set-face-attribute 'mode-line nil :family it :height 100) ;; (set-face-attribute 'mode-line-inactive nil :family it :height 100))
6.3.1. Menu to Toggle Minor Modes: A quick way to see all of my modes, and which are enabled
Enabled minor modes clutter up the modeline with their names, albeit some have useful status information shown. We can either selectively pick which names/status are shown using diminish.el, possibly forgetting which minor modes are enabled or we can use minions.el to “gather up” all enabled minor modes, and recently enabled ones, under a single menu which doom-modeline shows as a simple configurations gear icon. ⚙. :gear:
(setq doom-modeline-minor-modes t) (use-package minions :init (minions-mode)) ;; A quick hacky way to add stuff to doom-modeline is to add to the mode-line-process list. ;; E.g.: (add-to-list 'mode-line-process '(:eval (format "%s" (count-words (point-min) (point-max))))) ;; We likely want to add this locally, to hooks on major modes.
6.3.2. Nice battery icon alongside with percentage, in doom-modeline
;; If not for doom-modeline, we'd need to use fancy-battery-mode.el. (display-battery-mode +1)
[Posterity / Disabled] Fancy Battery Setup
Let's have it also show remaining battery life, coloured green if charging and coloured yellow otherwise. It is important to note that this package is no longer maintained. It works on my machine.
;; Let's use a fancy indicator … (use-package fancy-battery :diminish :custom (fancy-battery-show-percentage t) (battery-update-interval 15) :config (fancy-battery-mode))
6.3.3. Time & date
Let’s display the current time, with updates every second.
;; Show date and time as well. ;; [Simple Approach] ;; (setq display-time-day-and-date t) ;; (display-time) ;; [More Controlled Approach: Set date&time format] ;; a ≈ weekday; b ≈ month; d ≈ numeric day, R ≈ 24hr:minute. (setq display-time-format "%a %b %d ╱ %r") ;; E.g.,: Fri Mar 04 ╱ 03:42:08 pm (setq display-time-interval 1) ;; Please update the time every second. (display-time-mode)
But display-time-mode shows me a bit more info that I actually don't care for; so let's disable those.
;; I don't need the system load average in the modeline. (setq display-time-default-load-average nil) (setq display-time-load-average nil)
6.3.4. Column Numbers
Likewise, let's have the modeline display column numbers, but not line numbers. Instead, let's have line numbers on the side of the buffer; moreover let's have a uniform width for displaying line numbers, rather than having the width grow as necessary.
;; (column-number-mode t) ;; Enabled in doom-modeline by default ;; (line-number-mode t) ;; Not sure I want line numbers in modeline, since I have them in the left margin.
Line numbers are a conventionally expected part of a user interface, but I've realised that I seldom need to see them. I can still jump to a line number provided by a compilation error with M-g g; and toggle line numbers on when I'm pair programming with display-line-numbers-mode.
- In Emacs, there are buffer which exist and contain textual data, but to actually see them one requires a window. In the same vein, there are line numbers but I don't need to always see them.
- If I need an indication of ‘progress’, the modeline contains a percentage of how far I am in a buffer.
;; (setq display-line-numbers-width-start t) ;; (global-display-line-numbers-mode t) (global-linum-mode -1)
6.4. Exquisite Fonts and Themes
Emacs' default theme leaves much to be desired: It does not look sleek and shiny, which usually leaves first-timers with a poor, shallow, impression of the system. Below we install a few themes that make Emacs look exquisite. We cycle between the chosen themes with C-c t, my/toggle-theme.
“my/toggle-theme” Implementation
M-x load-theme RET TAB
shows all themes, including built-in ones, that may be loaded.- Loading multiple themes results in their pallets mixed.
M-x disable-theme
to remove a theme from the current pallet.
;; Treat all themes as safe; no query before use. (setf custom-safe-themes t) ;; Nice looking themes ^_^ (use-package solarized-theme :defer t) (use-package doom-themes :defer t) (use-package spacemacs-common :defer t :ensure spacemacs-theme)
- The Doom Themes also look rather appealing.
- A showcase of many themes can be found here.
;; Infinite list of my commonly used themes. (setq my/themes '(doom-laserwave doom-solarized-light doom-vibrant spacemacs-light solarized-gruvbox-dark solarized-gruvbox-light)) (setcdr (last my/themes) my/themes)
C-c t to toggle between the personal themes.
(cl-defun my/disable-all-themes (&optional (new-theme (pop my/themes))) "Disable all themes and load NEW-THEME, which defaults from ‘my/themes’. When a universal prefix is given, “C-u C-c t”, we load a random theme from all possible themes. Nice way to learn about more themes (•̀ᴗ•́)و" (interactive) (mapc #'disable-theme custom-enabled-themes) (-let [theme (if current-prefix-arg (nth (random (length (custom-available-themes))) (custom-available-themes)) new-theme)] (when theme (load-theme theme) (message "Theme %s" theme)))) (defalias 'my/toggle-theme #' my/disable-all-themes) (global-set-key "\C-c\ t" 'my/toggle-theme) ;; (my/toggle-theme) (use-package solarized-theme) (my/toggle-theme 'doom-laserwave)
Apparently, there's already a package that accomplishes these goals and more: theme-looper. I may switch to it, but for now my simple function above is slightly informative, to me at least, about how themes work and it does what I want.
…Actually, the above learning adventure has made it easy to provide a similar setup for fonts 😁
Likewise, C-c F, my/toggle-font, to quickly change fonts (according to mood 😸). [I already use C-c f, my/org-mode-format, for the more likely operation of formatting text.]
“my/toggle-font” Implementation
;; Infinite list of my commonly used fonts (setq my/fonts '("Roboto Mono Light 14" ;; Sleek "Input Mono 14" "Source Code Pro Light 14" ;; thin, similar to Inconsolata Light "Papyrus 14" "Bradley Hand Light 12" ;; "Chalkduster 14" ;; Laggy! "Courier Light 12" "Noteworthy 9" "Savoye LET 14" "Fantasque Sans Mono 16" )) (setcdr (last my/fonts) my/fonts) ;; Let's ensure they're on our system ;; brew search "/font-/" # List all fonts (shell-command "brew tap homebrew/cask-fonts") (system-packages-ensure "svn") ;; Required for the following font installs (system-packages-ensure "font-roboto-mono") (system-packages-ensure "font-input") (system-packages-ensure "font-source-code-pro") (system-packages-ensure "font-fira-mono") (system-packages-ensure "font-mononoki") (system-packages-ensure "font-monoid") (system-packages-ensure "font-menlo-for-powerline") (system-packages-ensure "font-fantasque-sans-mono") ;; Use “M-x set-face-font RET default RET”, or... ;; (set-face-font 'default "Source Code Pro Light14") ;; See ~2232 fonts ;; (append (fontset-list) (x-list-fonts "*" nil)) (cl-defun my/toggle-font (&optional (new-font (pop my/fonts))) "Load NEW-FONT, which defaults from ‘my/fonts’. When a universal prefix is given, “C-u C-c F”, we load a random font from all possible themes. Nice way to learn about more fonts (•̀ᴗ•́)و" (interactive) (let* ((all-fonts (append (fontset-list) (x-list-fonts "*" nil))) (font (if current-prefix-arg (nth (random (length all-fonts)) all-fonts) new-font))) (set-face-font 'default font) (message "Font: %s" font))) (global-set-key "\C-c\ F" 'my/toggle-font) ;; Default font; the “ignore-⋯” is for users who may not have the font. (ignore-errors (my/toggle-font "Fantasque Sans Mono 12")) (ignore-errors (my/toggle-font "Source Code Pro Light 14"))
In any Org file, type elisp:menu-set-font
; then you can click on this link to
get a nice font selection menu —this can be useful for your own ‘personal startup buffer’.
Let's use the following theme and font, upon startup.
(unless noninteractive (my/toggle-font "Roboto Mono Light 14") (my/toggle-theme 'solarized-gruvbox-light))
6.5. Never lose the cursor
Let's have the entire line containing the cursour be slightly highlighted.
;; Make it very easy to see the line with the cursor. (global-hl-line-mode t)
Moreover, we reduce the mental strain of locating the cursour when navigation happens: When we switch windows or scroll, for example, we get a wave of light near the cursor.
(use-package beacon :diminish :config (setq beacon-color "#666600") :hook ((org-mode text-mode) . beacon-mode))
6.6. Dimming Unused Windows
Let's dim windows, and even the whole Emacs frame, when not in use.
(use-package dimmer :config (dimmer-mode))
A more ‘fine-grained’ tool dims all text except the ‘paragraph’ you're working on. It's nifty, but not for me.
6.7. Flashing when something goes wrong
Enable flashing mode-line on errors. E.g., C-g
, or calling an unbound key
sequence, or misspelling a word.
;; (setq visible-bell 1) ;; On MacOS, this shows a caution symbol ^_^ ;; The doom themes package comes with a function to make the mode line flash on error. (use-package doom-themes) (require 'doom-themes-ext-visual-bell) (doom-themes-visual-bell-config)
A blinking cursor rushes me to type; let's slow down. Recently I'm thinking that
a blinking cursours prompts me to continue upwards and onwards.
(blink-cursor-mode 1)
6.9. Highlight & complete parenthesis pair when cursor is near ;-)
Highlight matching ‘parenthesis’ when near one of them.
(setq show-paren-delay 0) (setq show-paren-style 'mixed) (show-paren-mode)
Colour parens, and other delimiters, depending on their depth. Very useful for parens heavy languages like Lisp.
(use-package rainbow-delimiters :disabled :hook ((org-mode prog-mode text-mode) . rainbow-delimiters-mode))
For example:
(blue (purple (forest (green (yellow (blue))))))
There is a powerful package called ‘smartparens’ for working with pair-able
characters, but I've found it to be too much for my uses. Instead I'll utilise
the lightweight package electric
, which Emacs provides out of the box.
(electric-pair-mode 1)
It supports, by default, ACSII pairs {}, [], ()
and Unicode ‘’, “”, ⟪⟫, ⟨⟩
.
When writing Lisp, it is annoyong to have ‘<’ and ‘>’ be completed and considered as pairs. Let's disassociate them from both notions.
;; The ‘<’ and ‘>’ are not ‘parenthesis’, so give them no compleition. (setq electric-pair-inhibit-predicate (lambda (c) (or (member c '(?< ?> ?~)) (electric-pair-default-inhibit c)))) ;; Treat ‘<’ and ‘>’ as if they were words, instead of ‘parenthesis’. (modify-syntax-entry ?< "w<") (modify-syntax-entry ?> "w>")
Adding Org-emphasise markers for pair completion —Disabled.
Let's add the org-emphasises markers: If we select a word then press *
, it
becomes bold; likewise for /
for emphasise.
(setq electric-pair-pairs '((?~ . ?~) (?* . ?*) (?/ . ?/))) ;; Let's also, for example, avoid obtaining double ‘~’ and ‘/’ when searching for a file. ;; Disable pairs when entering minibuffer (add-hook 'minibuffer-setup-hook (lambda () (electric-pair-mode 0))) ;; Renable pairs when existing minibuffer (add-hook 'minibuffer-exit-hook (lambda () (electric-pair-mode 1)))
I use ‘~’ and ‘/’ too much during file navigation, and ‘*’ when marking numerous Org headers, for which the ‘completed closing pair’ must tiresomely be deleted.
6.10. Proportional fonts for Headlines
Let's have headings stick out a bit.
- The larger headings are cute and reminicint of word processors, but having headings coloured is enough —the larger size is too much.
(set-face-attribute 'org-document-title nil :height 2.0) ;; (set-face-attribute 'org-level-1 nil :height 1.0) ;; Remaining org-level-𝒾 have default height 1.0, for 𝒾 : 1..8. ;; ;; E.g., reset org-level-1 to default. ;; (custom-set-faces '(org-level-1 nil))
Remember you can always use Emacs' Custom utility to get Lisp incantations ;-) —See notes on Custom above.
6.11. Making Block Delimiters Less Intrusive
Let us render Org-mode's #+begin_src
and #+end_src
less obtrusively by,
e.g., having the former render as a pencil marker ✎
and the latter as a
tombstone □
—reminiscent of Halmos' QED end-of-proof marker.
Rasmus’ Incantation
This is from Rasmus Roulund.
(defvar-local rasmus/org-at-src-begin -1 "Variable that holds whether last position was a ") (defvar rasmus/ob-header-symbol ?☰ "Symbol used for babel headers") (defun rasmus/org-prettify-src--update () (let ((case-fold-search t) (re "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*") found) (save-excursion (goto-char (point-min)) (while (re-search-forward re nil t) (goto-char (match-end 0)) (let ((args (org-trim (buffer-substring-no-properties (point) (line-end-position))))) (when (org-string-nw-p args) (let ((new-cell (cons args rasmus/ob-header-symbol))) (cl-pushnew new-cell prettify-symbols-alist :test #'equal) (cl-pushnew new-cell found :test #'equal))))) (setq prettify-symbols-alist (cl-set-difference prettify-symbols-alist (cl-set-difference (cl-remove-if-not (lambda (elm) (eq (cdr elm) rasmus/ob-header-symbol)) prettify-symbols-alist) found :test #'equal))) ;; Clean up old font-lock-keywords. (font-lock-remove-keywords nil prettify-symbols--keywords) (setq prettify-symbols--keywords (prettify-symbols--make-keywords)) (font-lock-add-keywords nil prettify-symbols--keywords) (while (re-search-forward re nil t) (font-lock-flush (line-beginning-position) (line-end-position)))))) (defun rasmus/org-prettify-src () "Hide src options via `prettify-symbols-mode'. `prettify-symbols-mode' is used because it has uncollpasing. It's may not be efficient." (let* ((case-fold-search t) (at-src-block (save-excursion (beginning-of-line) (looking-at "^[ \t]*#\\+begin_src[ \t]+[^ \f\t\n\r\v]+[ \t]*")))) ;; Test if we moved out of a block. (when (or (and rasmus/org-at-src-begin (not at-src-block)) ;; File was just opened. (eq rasmus/org-at-src-begin -1)) (rasmus/org-prettify-src--update)) ;; Remove composition if at line; doesn't work properly. ;; (when at-src-block ;; (with-silent-modifications ;; (remove-text-properties (match-end 0) ;; (1+ (line-end-position)) ;; '(composition)))) (setq rasmus/org-at-src-begin at-src-block))) (defun rasmus/org-prettify-symbols () (mapc (apply-partially 'add-to-list 'prettify-symbols-alist) (cl-reduce 'append (mapcar (lambda (x) (list x (cons (upcase (car x)) (cdr x)))) `(("#+begin_src" . ?✎) ;; ➤ 🖝 ➟ ➤ ✎ ("#+end_src" . ?□) ;; ⏹ ("#+header:" . ,rasmus/ob-header-symbol) ("#+begin_quote" . ?») ("#+end_quote" . ?«))))) (turn-on-prettify-symbols-mode) (add-hook 'post-command-hook 'rasmus/org-prettify-src t t)) ;; Last updated: 2019-06-09
(add-hook 'org-mode-hook #'rasmus/org-prettify-symbols) (org-mode-restart)
His development relies on built-in prettify-symbols-mode, which
disguises strings in a buffer for the sake of readability or
aesthetics. Following the example in the documentation, C-h f
prettify-symbols-mode
, we can quickly approximate his efforts for
example
blocks as follows, however a main issue is that source blocks
have busybodied headers which his setup disguises as ‘≡’.
(global-prettify-symbols-mode) (defvar my/prettify-alist nil "Musa's personal prettifications.") (cl-loop for pair in '(;; Example of how pairs like this to beautify org block delimiters ("#+begin_example" . (?ℰ (Br . Bl) ?⇒)) ;; ℰ⇒ ("#+end_example" . ?⇐) ;; ⇐ ;; Actuall beautifications ("==" . ?≈) ("===" . ?≈) ("=" . ?≔) ;; Programming specific prettifications ("<=" . ?≤) (">=" . ?≥) ("->" . ?→) ("-->". ?⟶) ;; threading operators ("[ ]" . ?□) ("[X]" . ?☑) ("[-]" . ?◐)) ;; Org checkbox symbols do (push pair my/prettify-alist)) ;; Replace all Org [metadata]keywords with the “▷” symbol; e.g., “#+title: Hello” looks like “▷ Hello”. (cl-loop for keyword in '(title author email date description options property startup export_file_name html_head) do (push (cons (format "#+%s:" keyword) ?▷) my/prettify-alist)) (cl-loop for hk in '(text-mode-hook prog-mode-hook org-mode-hook) do (add-hook hk (lambda () (setq prettify-symbols-alist (append my/prettify-alist prettify-symbols-alist)))))
See “Mathematical Notation in Emacs” for how such prettifications can make verbose (Python) scripts much more readable by employing more economical disguises.
A nice sanity:
;; Un-disguise a symbol when cursour is inside it or at the right-edge of it. (setq prettify-symbols-unprettify-at-point 'right-edge)
6.12. Hiding Emphasise Markers, Inlining Images, and LaTeX-as-PNG
Let's make some things prettier than they appear by default.
;; org-mode math is now highlighted ;-) (setq org-highlight-latex-and-related '(latex)) ;; Extra space between text and underline line (setq x-underline-at-descent-line t) ;; Hide the *,=,/ markers (setq org-hide-emphasis-markers t) ;; Let’s limit the width of images inlined in org buffers to 400px. (setq org-image-actual-width 400) ;; Visually, I prefer to hide the markers of macros, so let’s do that: ;; {{{go(here)}}} is shown in Emacs as go(here) (setq org-hide-macro-markers t) ;; On HTML exports, Org-mode tries to include a validation link for the exported HTML. Let’s disable that since I never use it. ;; (setq org-html-validation-link nil) ;; Musa: This is super annoying, in practice. (setq org-pretty-entities nil) ;; Also makes subscripts (x_{sub script}) and superscripts (x^{super script}) appear in org in a WYSIWYG fashion. ;; to have \alpha, \to and others display as utf8 ;; http://orgmode.org/manual/Special-symbols.html ;; ;; Be default, any consectuive string after “_” or “^” will be shown in WYSIWYG fashion; the following requires “^{⋯}” instead. ;; (setq org-use-sub-superscripts (quote {}))
Org pretty entities seems rather impressive ---M-x org-entities-help
to see all
possibilities, or add your own. I'm already using the Agda input method, so I
wont use Org's —Agda's gives me a tiny menu narrowing possibilities as I type.
However, it does make subscripts (xsub script) and superscripts (xsuper script) appear in Org in a WYSIWYG fashion.
Automatically display emphasis markers and links when the cursor is on them.
(c.f. fragtog
below)
(use-package org-appear :hook (org-mode . org-appear-mode) :init (setq org-appear-autoemphasis t org-appear-autolinks nil org-appear-autosubmarkers nil))
The following is now disabled (yet again, as of Dec/31/2020) —it makes my system slower than I'd like.
;; Show inline images when loading a new Org file. (setq org-startup-with-inline-images t) ;; Whenever a src block is run, redisplay images so they're up-to-date. ;; Very useful when using ‘ob-latex-as-png’, below. (add-hook 'org-babel-after-execute-hook #'org-redisplay-inline-images) ;; Automatically convert LaTeX fragments to inline images. (setq org-startup-with-latex-preview t)
Org mode supports inline image previews of LaTeX fragments; e.g., \(e^{i \cdot \pi} - 1 = 0\) or \(\substack{𝔹 \\ ↓ \\ 𝒜}\). These can be toggled with C-c C-x C-l. Org-fragtog automates this, so fragment previews are disabled for editing when your cursor steps onto them, and re-enabled when the cursor leaves.
;; Automatically toggle LaTeX previews when cursour enters/leaves them (use-package org-fragtog :hook (org-mode . org-fragtog-mode))
org-latex-preview, C-c C-x C-l, renders $e^{i \pi} + 1 = 0$
into a
really nice inline image: \(e^{i \pi} + 1 = 0\). It also works for LaTeX
environments —for personal environments, just (add-to-list
'org-latex-packages-alist "LaTeX definitions here")
.
;; Make previews a bit larger (setq org-format-latex-options (plist-put org-format-latex-options :scale 1.5)) ;; I use a lot of Unicode, so let's always include a unicode header. (maybe-clone "https://armkeh.github.io/unicode-sty/") (setq org-format-latex-header (concat org-format-latex-header "\n\\usepackage{\\string~\"/unicode-sty/unicode\"}")) ;; ;; Now this looks nice too! ;; $\substack{𝔹 \\ ↓ \\ 𝒜}$ and $\mathbb{B}$. ;; Always support unicode upon LaTeX export ;; No need to explicitly import armkeh's unicode-sty in each org file. (add-to-list 'org-latex-packages-alist "\n\\usepackage{\\string~\"/unicode-sty/unicode\"}")
This approach does not work well for forming diagrams; I've tried to make tikzcd work this way and failed. Using ob-latex-as-png as a substitute.
;; Support “latex-as-png” src blocks, which show LaTeX as PNGs (use-package ob-latex-as-png)
Use ref:my-stuff
to refer to an Org entity with #+name: my-stuff
; which must
have a #+caption: ⋯
as well. Example entities include tables and source
blocks; as well as figure blocks. For equation blocks, you must use a
\label{⋯}
directly.
;; Use the “#+name” the user provides, instead of generating label identifiers. (setq org-latex-prefer-user-labels t)
6.13. Show off-screen heading at the top of the window
In case we forgot which heading we're under, let's keep the current heading stuck at the top of the window.
(use-package org-sticky-header :hook (org-mode . org-sticky-header-mode) :config (setq-default org-sticky-header-full-path 'full ;; Child and parent headings are seperated by a /. org-sticky-header-outline-path-separator " / "))
6.14. Powerful Directory Editing with dired
⟨ C-x C-v
to open a file or directory in dired, using the current buffer. ⟩
As mentioned earlier, dired
is Emacs' built-in directory editor; it's opened
with C-x d
. Dired let's us treat directories as textual objects! In dired,
press h
to see the many actions available. Here's a few…
Super Terse ‘dired’ Tutorial
(
toggles hiding entry details, such as modification date and ownerships
sort entries; modeline will display “Dired by date” or “Dired by name”.o
to open entry in anOther window; orRET
to open in place.+
to create a new directory; orM-x make-directory
./
to filter entries; withwhich-key
, possible completions pop-up.- E.g.,
/ f
shows only files or/ . png
to obtain all entries with extensionpng
. / i g
to hide git-ignored items ^_^/ /
to remove all filters.
- E.g.,
TAB
to navigate between different groupings of entries.RET
on a drawer heading toggles folding it ^_^
The dired-hacks family of packages lets us, say, get a dired buffer out of a shell incantation that lists files, or use dired to open files with external tools. Below we use three of its packages.
Pressing i
inserts a directory's children under it, indented, in the current
buffer. Useful to see what's there.
(use-package dired-subtree :bind (:map dired-mode-map ("i" . dired-subtree-toggle)))
When directory 𝒳
has only one child 𝒴
, then in dired, instead of 𝒳
, show me 𝒳/𝒴
with 𝒳
greyed out.
(use-package dired-collapse :hook (dired-mode . dired-collapse-mode))
Begin dired with certain entries grouped together, according to some filtering
requirement; and with “garbage” files not shown —i.e., those ending in
.aux, .out
, etc.
(use-package dired-filter :hook (dired-mode . (lambda () (dired-filter-group-mode) (dired-filter-by-garbage))) :custom (dired-garbage-files-regexp "\\(?:\\.\\(?:aux\\|bak\\|dvi\\|log\\|orig\\|rej\\|toc\\|out\\)\\)\\'") (dired-filter-group-saved-groups '(("default" ("Org" (extension "org")) ("Executables" (exexutable)) ("Directories" (directory)) ("PDF" (extension "pdf")) ("LaTeX" (extension "tex" "bib")) ("Images" (extension "png")) ("Code" (extension "hs" "agda" "lagda")) ("Archives"(extension "zip" "rar" "gz" "bz2" "tar"))))))
[Disabled] Neotree: Traditional Directory Tree Navigation
We open a nifty file manager upon startup.
;; Sidebar for project file navigation (use-package neotree :defer t :disabled :config (global-set-key "\C-x\ d" 'neotree-toggle) (setq neo-theme 'icons)) ;; Uses all-the-icons from § Booting Up ;; Open it up upon startup. ;; (neotree-toggle)
By default C-x d
invokes dired
, but I prefer neotree
for file
management.
⟨ Edit: As a naive user, this is what I thought; yet a year later, I've almost never used neotree. ⟩
Useful navigational commands include
U
to go up a directory.C-c C-c
to change directory focus;C-C c
to type the directory out.?
orh
to get help andq
to quit.
As always, to go to the neotree pane when it's the only other window,
execute C-x o
.
I rarely make use of this feature; company mode & Helm together quickly provide an automatic replacement for nearly all of my uses.
- Reminiscent of GUI file managers is ranger; e.g., it has multi-column display of parent directories along with a file preview mechanism.
6.15. Persistent Scratch Buffer
The *scratch*
buffer is a nice playground for temporary data or experiments.
However, by default its contents are not saved –which may be an issue if we have not relocated our playthings to their appropriate files. Whence let's save & restore the scratch buffer by default.
(use-package persistent-scratch :defer t ;; In this mode, the usual save key saves to the underlying persistent file. :bind (:map persistent-scratch-mode-map ("C-x C-s" . persistent-scratch-save)))
We might accidentally close this buffer, so we could utilise the following.
(defun scratch () "Recreate the scratch buffer, loading any persistent state." (interactive) (switch-to-buffer-other-window (get-buffer-create "*scratch*")) (condition-case nil (persistent-scratch-restore) (insert initial-scratch-message)) (org-mode) (persistent-scratch-mode) (persistent-scratch-autosave-mode 1)) ;; This doubles as a quick way to avoid the common formula: C-x b RET *scratch* ;; Upon startup, close the default scratch buffer and open one as specfied above (ignore-errors (kill-buffer "*scratch*") (scratch))
I use Org-mode often, so that's how I want things to appear.
(setq initial-scratch-message (concat "#+Title: Persistent Scratch Buffer" "\n#\n# Welcome! This’ a place for trying things out." "\n#\n# ⟨ ‘C-x C-s’ here saves to ~/.emacs.d/.persistent-scratch ⟩ \n\n"))
6.16. Tabs Disabled
I really like my Helm-supported C-x b
, but the visial appeal of a tab bar for Emacs
is interesting. Let's try it out and see how long this lasts —it may be like Neotree:
Something cute to show to others, but not as fast as the keyboard.
(use-package awesome-tab :disabled :quelpa (awesome-tab :fetcher git :url "https://github.com/manateelazycat/awesome-tab.git") :config (awesome-tab-mode t)) ;; Show me /all/ the tabs at once, in one group. (defun awesome-tab-buffer-groups () (list (awesome-tab-get-group-name (current-buffer))))
It's been less than three days and I've found this utility to be unhelpful, to me anyhow.
An alternative is centaur-tabs.
6.17. Window resizing using the golden ratio Disabled
Let's load the following package, which automatically resizes windows so that the window containing the cursor is the largest, according to the golden ratio. Consequently, the window we're working with is nice and large yet the other windows are still readable.
(use-package golden-ratio :disabled :diminish golden-ratio-mode :init (golden-ratio-mode 1))
After some time this got a bit annoying and I'm no longer using this.
6.18. Org-Emphasise for Parts of Words Disabled
From stackoverflow, the following incantation allows us to have parts of works emphasied with org-mode; e.g., /half/ed, ~half~ed, and right in the m*idd*le! Super cool stuff!
(setcar org-emphasis-regexp-components " \t('\"{[:alpha:]") (setcar (nthcdr 1 org-emphasis-regexp-components) "[:alpha:]- \t.,:!?;'\")}\\") (org-set-emph-re 'org-emphasis-regexp-components org-emphasis-regexp-components)
I've disabled this feature since multiple occurrences of an emphasise marker are sometimes treated as one lengthy phrase being emphasised.
6.19. Preview link under cursor
When cursor sits on a URL/Image/File link, try to preview it in a tooltip.
- Useful to quickly preview files and images.
- See also: https://github.com/jcs-elpa/preview-it
(quelpa '(preview-it :repo "jcs-elpa/preview-it" :fetcher github)) ;; (global-preview-it-mode)
This also works nicely when I'm looking to open a file; e.g., C-x C-f
~/.emacs.d/.as TAB
to preview my .aspell.en.pws
(Emacs personal dictionary) file
in a tooltip.
Likewise, I'd like to preview line when executing the goto-line / M-g M-g command.
(quelpa '(goto-line-preview :repo "jcs-elpa/goto-line-preview" :fetcher github)) (global-set-key [remap goto-line] 'goto-line-preview)
6.20. Replace phrases with nice SVG labels
let's us replase arbitrary regular expressions with beautiful SVG images that can be clicked to produce an action, and may have a tooltip to provide contextual information. Essentially an alternative to the built-in font-lock-mode, which performs arbitrary syntax highlighting.
- For more power, use the
svg-lib
package. - The docs have nice examples. Here are more useful examples.
Below I setup a function, my/svg-tag-declare-badge to declaratively produce SVG badges.
(use-package svg-tag-mode :hook (org-mode prog-mode) ;; :config (global-svg-tag-mode) ;; Nope: Breaks xwidget-webkit-browse-url, issue#28. :config (cl-defun my/svg-tag-declare-badge (template face &optional tooltip-message-upon-hover) ;; Example faces: 'org-level-1 'org-todo 'font-lock-doc-face "Given a TEMPLATE of the shape \"𝑿❙𝒀\", make SVG badge whose tag is 𝑿 and label is 𝒀. When `svg-tags-mode' is enabled, every occurence of \"\\(𝑿\\)\\(𝒀\\)\" is replaced by an SVG image essentially displaying “[𝑿∣𝒀]” using the given FACE. This badge can be clicked to show all instances in the buffer. You can see the badges documentation / intentions / help-message when you hover over it; to see TOOLTIP-MESSAGE-UPON-HOVER. Both 𝑿 and 𝒀 are regeular expressions; “❙” serves as the SVG tag-label delimiter ---i.e., it saves as from writing \"\\(𝑿\\)\\(𝒀\\)\". Moreover, the SVG is only active when regexp \"\\(𝑿\\)\\(𝒀\\)\" matches an instance." ;; Append tooltip message with a notice on what happens upon click. (--> "Click on me to all see occurrences of this badge, in the current buffer!" (if tooltip-message-upon-hover (concat tooltip-message-upon-hover "\n\n" it) it) (setq tooltip-message-upon-hover it)) (-let [(tag label) (s-split "❙" template)] (-let [click-to-show-all-buffer-occurrences `(lambda () (interactive) (occur (concat ,tag ,label)))] ;; Make an SVG for the tag. (push (cons (format "\\(%s\\)%s" tag label) `((lambda (tag) (svg-tag-make (s-chop-suffix ":" (s-chop-prefixes '("[" "<" "/*") tag)) :face (quote ,face) :inverse t :margin 0 :crop-right t :crop-left nil)) ,click-to-show-all-buffer-occurrences ,tooltip-message-upon-hover)) svg-tag-tags) ;; Make an SVG for the label. (push (cons (format "%s\\(%s\\)" tag label) `((lambda (label) (svg-tag-make (s-chop-suffixes '("]" ">" "*/") label) :face (quote ,face) :crop-left t)) ,click-to-show-all-buffer-occurrences ,tooltip-message-upon-hover)) svg-tag-tags)))) ;; Let's start off empty; then declare badges below. (setq svg-tag-tags nil) ;; Using caps so that these stick-out somewhat even when svg-tags-mode is not present. (my/svg-tag-declare-badge " (my/svg-tag-declare-badge " (my/svg-tag-declare-badge " (my/svg-tag-declare-badge " (my/svg-tag-declare-badge " ;; [In]Active Time stamps --- M-x org-time-stamp (my/svg-tag-declare-badge "\\" 'org-done "This is an inactive time stamp. It does not trigger the parent entry to appear in the agenda.") (my/svg-tag-declare-badge "" 'org-todo "This is an active time stamp. It causes the parent Org entry to appear in the agenda.") ;; JavaScript Lint Rules: \* eslint (.*) */ (my/svg-tag-declare-badge "/\\* eslint ❙.* \\*/" 'org-done "It looks like you’ deviating from common conventions: Tread cautiously!") ;; ) ;; If everything is setup, the following examples should look like SVGs. ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; ;; (progn (svg-tag-mode-off) (svg-tag-mode-on)) ;;
7. Prose
Emacs can be setup with a spellchecker and other expected features of a word processing tool —however these features apply Emacs-wide since nearly everything is essentially text (•̀ᴗ•́)و
- Org-mode is a writer's best friend; it's large enough to deserve its own sections.
7.1. Whitespace
Let's start off by cleaning-up any accidental trailing whitespace and in other places upon save.
(add-hook 'before-save-hook 'whitespace-cleanup)
See here for making whitespace visible; including spaces, tabs, and newlines
7.2. Formatting Text
The following incantation, my/org-mode-format, makes it so that we can select some text then press C-c f (to get a list of possible character completions) then press the symbol we want our text to be surrounded with.
Details
(local-set-key (kbd "C-c f") #'my/org-mode-format) (defun my/org-mode-format (&optional text) "Surround selected region with the given Org emphasises marker. E.g., if this command is bound to “C-c f” then the sequence “C-c f b” would make the currenly selected text be bold. Likewise, “C-c f *” would achieve the same goal. When you press “C-c f”, a message is shown with a list of useful single-character completions. Note: “C-c f 𝓍”, for an unrecognised marker 𝓍, just inserts the character 𝓍 before and after the selected text." (interactive "P") ;; Works on a region ; (message "b,* ⟨Bold⟩; i,/ ⟨Italics⟩; u,_ ⟨Underline⟩; c,~ ⟨Monotype⟩") (message "⟨Bold b,*⟩ ⟨Italics i,/⟩ ⟨Underline u,_⟩ ⟨Monotype c,~⟩") (let ((kind (read-char))) ;; Map letters to Org formatting symbols (setq kind (or (plist-get '(b ?\* i ?\/ u ?\_ c ?\~) (intern (string kind))) kind)) (insert-pair text kind kind)))
7.3. Fill-mode —Word Wrapping
In fill mode, when you type past the end of a line, Emacs automatically starts a new line, cleverly formatting paragraphs. This is a powerful form of “word wrap”.
(setq-default fill-column 80 ;; Let's avoid going over 80 columns truncate-lines nil ;; I never want to scroll horizontally indent-tabs-mode nil) ;; Use spaces instead of tabs
Certain variables are sensibly local to a buffer, and so setq
only alters their
value for one buffer. Using setq-default
we change a variable's default value,
in every buffer.
;; Wrap long lines when editing text (add-hook 'text-mode-hook 'turn-on-auto-fill) (add-hook 'org-mode-hook 'turn-on-auto-fill) ;; Do not show the “Fill” indicator in the mode line. (diminish 'auto-fill-function)
We may press M-q
to cleverly redistribute the line breaks within any paragraph,
thereby making it look better. With a prefix argument, it justifies it as well
—i.e., pads extra white space to make the paragraph appear rectangular.
Note that M-o M-s
centres a line of text ;-) Fun stuff!
Fill-mode is also known as “hard word wrapping”, which has the counterpart “soft word wrapping” …
Visual line mode is built-in and provides support for editing by visual lines:
Lines off the screen are visually word wrapped, but logically remain one line.
Moreover C-a,e,k
operate on visual lines rather than logical lines.
;; Bent arrows at the end and start of long lines. (setq visual-line-fringe-indicators '(left-curly-arrow right-curly-arrow)) (diminish 'visual-line-mode) (global-visual-line-mode 1)
Visual line mode is useful when I have way too many windows open or when using smaller frames.
7.4. Pretty Lists Markers
When writing, it's common to use +,-,*
to enumerate unordered lists
—especially so in Org-mode wherein they denote structured text. Let's render
them visually as Unicode bullets.
;; (x y z) ≈ (existing-item replacement-item positivity-of-preceding-spaces) (cl-loop for (x y z) in '(("+" "◦" *) ("-" "•" *) ("*" "⋆" +)) do (font-lock-add-keywords 'org-mode `((,(format "^ %s\\([%s]\\) " z x) (0 (prog1 () (compose-region (match-beginning 1) (match-end 1) ,y)))))))
7.5. Fix spelling as you type —thesaurus & dictionary too!
I would like to check spelling on the fly.
C-;
- Cycle through corrections for word at point.
M-$
- Check and correct spelling of the word at point
M-x ispell-change-dictionary RET TAB
- To see what dictionaries are available.
Install spell-checking application as well as a reliable English dictionary, WordNet.
(system-packages-ensure "aspell") (system-packages-ensure "wordnet")
flyspell-prog-mode
enables spell checking for programming by only considering
comments and strings.
(use-package flyspell :diminish :hook ((prog-mode . flyspell-prog-mode) ((org-mode text-mode) . flyspell-mode)))
Enabling fly-spell for text-mode enables it for org and latex modes since they derive from text-mode.
Flyspell needs a spell checking tool, which is not included in Emacs. We
install aspell
spell checker using, say, homebrew via brew install aspell
. Note
that Emacs' ispell
is the interface to such a command line spelling utility.
(setq ispell-program-name "/usr/local/bin/aspell") (setq ispell-dictionary "en_GB") ;; set the default dictionary
[Disabled] Allow spelling support for CamlCase words like “EmacsIsCool”.
(setq ispell-extra-args '("--sug-mode=ultra" "--run-together" "--run-together-limit=5" "--run-together-min=2"))
Let us select a correct spelling merely by clicking on a word —for the rare days I have a mouse.
(eval-after-load "flyspell" ' (progn (define-key flyspell-mouse-map [down-mouse-3] #'flyspell-correct-word) (define-key flyspell-mouse-map [mouse-3] #'undefined)))
Colour incorrect works; default is an underline.
(global-font-lock-mode t) (custom-set-faces '(flyspell-incorrect ((t (:inverse-video t)))))
Finally, save to user dictionary without asking:
(setq ispell-silently-savep t)
Let's keep track of my personal word set by having it be in my version controlled
.emacs directory. Note that the default location is ~/.[i|a]spell.DICT
for
a specified dictionary DICT
.
(setq ispell-personal-dictionary "~/.emacs.d/.aspell.en.pws")
Nowadays, I very rarely write non-literate programs, but if I do I'd like to check spelling only in comments/strings. E.g.,
(add-hook 'c-mode-hook 'flyspell-prog-mode) (add-hook 'emacs-lisp-mode-hook 'flyspell-prog-mode)
Use the thesaurus Emacs frontend Synosaurus to avoid unwarranted repetition.
(use-package synosaurus :diminish synosaurus-mode :init (synosaurus-mode) :config (setq synosaurus-choose-method 'popup) ;; 'ido is default. (global-set-key (kbd "M-#") 'synosaurus-choose-and-replace))
The thesaurus is powered by the Wordnet wn
tool, which can be invoked without an
internet connection!
;; (shell-command "brew cask install xquartz &") ;; Dependency ;; (shell-command "brew install wordnet &")
Let's use Wordnet as a dictionary via the wordnut package.
(use-package wordnut :bind ("M-!" . wordnut-lookup-current-word)) ;; Use M-& for async shell commands.
Use M-↑,↓
to navigate dictionary results, and wordnut-search
for a new search.
An alternative to wordnut
is to use the lightweight define-word
package; which I
think is not ideal since it provides way less information.
7.6. Using a Grammar & Style Checker
[ A possibly better alternative is Vale. ]
Let's install a grammar and style checker. We get the offline tool from the bottom of the LanguageTool website, then relocate it as follows.
(use-package langtool :defer t :custom (langtool-language-tool-jar "~/Applications/LanguageTool-4.5/languagetool-commandline.jar"))
Now we can run langtool-check
on the subsequent grammatically incorrect
text —which is from the LanguageTool website— which colours errors in red,
when we click on them we get the reason why; then we may invoke
langtool-correct-buffer
to quickly use the suggestions to fix each correction,
and finally invoke langtool-check-done
to stop any remaining red colouring.
LanguageTool offers spell and grammar checking. Just paste your text here and click the 'Check Text' button. Click the colored phrases for details on potential errors. or use this text too see an few of of the problems that LanguageTool can detecd. What do you thinks of grammar checkers? Please not that they are not perfect. Style issues get a blue marker: It's 5 P.M. in the afternoon. The weather was nice on Thursday, 27 June 2017 --uh oh, that's the wrong date ;-)
By looking around the source code, I can do all three stages smoothly (•̀ᴗ•́)و
;; Quickly check, correct, then clean up /region/ with M-^ (eval-after-load 'langtool (progn (add-hook 'langtool-error-exists-hook (lambda () (langtool-correct-buffer) (langtool-check-done))) (global-set-key "\M-^" (lambda () (interactive) (message "Grammar checking begun ...") (langtool-check)))))
The checking command is silent, we added a bit of comforting acknowledgement to the user.
7.7. Lightweight Prose Proofchecking
Let's write good!
(use-package writegood-mode ;; Load this whenver I'm composing prose. :hook (text-mode org-mode) ;; Don't show me the “Wg” marker in the mode line :diminish ;; Some additional weasel words. :config (--map (push it writegood-weasel-words) '("some" "simple" "simply" "easy" "often" "easily" "probably" "clearly" ;; Is the premise undeniably true? "experience shows" ;; Whose? What kind? How does it do so? "may have" ;; It may also have not! "it turns out that"))) ;; How does it turn out so? ;; ↯ What is the evidence of highighted phrase? ↯
Inspired by Matt Might's 3 shell scripts to improve your writing, or "My Ph.D. advisor rewrote himself in bash", this Emacs interface emphasises, via underline, the following weaknesses in writing —so that I can fix them or decide that they are appropriate for the scenario.
Sentences that cut out the following problems may become stronger —by being more terse or precise.
- Weasel Words
Phrases that sound good without conveying information; such as vague precision or subjective phrases.
E.g., a number of, surprisingly, very close.
It's okay not to have exact details, but rather than “I don't know” explain why not and what the next steps will be.
- Passive Voice
Phrases wherein interest is in the object experiencing an action, rather than the subject that performs the action.
- Bad: The house was built by my father.
- Good: My father built this house.
Likewise, including relevant or explanatory information as in “X guarantees Y” is an improvement over “Y is guaranteed”.
Sometimes the subject really is irrelevant, such as “We did X” whereas “X happened” suffices.
👍 If the relevant subject is unclear and, also, the text reads better in the active, then change a phrase.
- Duplicated Words
Occurrences of, say, “the the”.
Harder to catch manually, but easier mechanically ;-)
7.8. Placeholder Text —For Learning & Experimenting
When learning about Emacs formatting commands, such as zap-to-char M-z
or transpose M-t
, it's best to have filler text —even better when
it's automatically generated instead of typing it out ourselves. The
following will give us a series of commands lorem-ipsum-insert-⋯
for
inserting lists, sentences, paragraphs and using a prefix argument,
with C-u
, we can request to generate any number of them.
(use-package lorem-ipsum :defer t)
‘Lorem’ is not a word itself, but it comes from the Latin ‘Dolorem Ipsum’ which means “pain in and of itself”.
See this Emacs Cheat Sheet to try out the textual navigation and formatting bindings on lorem ipsum, gibberish text.
7.9. Some text to make us smile
The dad-joke queries https://icanhazdadjoke.com to bring us some funny.
(use-package dad-joke :defer t :config (defun dad-joke () (interactive) (insert (dad-joke-get))))
For example, M-x dad-joke
now inserts:
What are the strongest days of the week? Saturday and Sunday…the rest are weekdays.
7.10. Unicode Input via Agda Input
Agda is one of my favourite languages, it's like Haskell on steroids. Let's set it up for the main sake of its Unicode input —you may do likewise using TeX input. ( The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!) )
Agda input mode makes it extremely easy to use unicode in documents, something I strongly prefer to do. When I can use symbols directly, instead of (for instance) LaTeX commands, it makes my plaintext far more readable. — Armkeh .emacs config
(system-packages-ensure "agda")
To use the Agda standard library by default
mkdir -p ~/.agda echo /usr/local/lib/agda/standard-library.agda-lib >>~/.agda/libraries echo standard-library >>~/.agda/defaults
Invoke brew info agda
to get these instructions and the version of Agda just
installed.
Get font support for subscripts (, if) need be
- Download and unzip the symbola font
- CMD + SPC ⇒
font book
⇒+
⇒ select the symbola directory you just unzipped
(Note: In the before time, you could brew install this font.)
Executing agda-mode setup
appends the following text to the .emacs
file.
Let's put it here ourselves.
(unless noninteractive (load-file (let ((coding-system-for-read 'utf-8)) (shell-command-to-string "/usr/local/bin/agda-mode locate"))))
I almost always want the agda-mode
input method —it's like the TeX method, but
better.
;; MA: This results in "Package cl is deprecated" !? (unless noninteractive (use-package agda-input :ensure nil ;; I have it locally. :demand t :hook ((text-mode prog-mode) . (lambda () (set-input-method "Agda"))) :custom (default-input-method "Agda"))) ;; Now C-\ or M-x toggle-input-method turn it on and offers ;; TODO add a hook that when the input method becomes Agda, just don't bother showing me in the modeline. ;; E.g., "Π" when using unicode input with Agda ;; Useful to have in the modeline, say when typing in Arabic. ;; (add-variable-watcher ;; 'current-input-method ;; (lambda (_ newvalue 'set _) ;; (setq current-input-method-title ;; (if (equal newvalue "Agda") nil newvalue))))
Unicode doesn't intend to cover things that are achievable with markup, so only a limited subset of the alphabet is available as subscript; but all is available as superscript, except ‘q’.
ₐₑₕᵢⱼₖₗₘₙₒₚᵣₛₜᵤᵥₓ ⁰ ¹ ² ³ ⁴ ⁵ ⁶ ⁷ ⁸ ⁹ ⁺ ⁻ ⁼ ⁽ ⁾ ₀ ₁ ₂ ₃ ₄ ₅ ₆ ₇ ₈ ₉ ₊ ₋ ₌ ₍ ₎ ᵃ ᵇ ᶜ ᵈ ᵉ ᶠ ᵍ ʰ ⁱ ʲ ᵏ ˡ ᵐ ⁿ ᵒ ᵖ ʳ ˢ ᵗ ᵘ ᵛ ʷ ˣ ʸ ᶻ ᴬ ᴮ ᴰ ᴱ ᴳ ᴴ ᴵ ᴶ ᴷ ᴸ ᴹ ᴺ ᴼ ᴾ ᴿ ᵀ ᵁ ⱽ ᵂ ᵅ ᵝ ᵞ ᵟ ᵋ ᶿ ᶥ ᶲ ᵠ ᵡ ᵦ ᵧ ᵨ ᵩ ᵪ
brew cask install font-symbola
⇒ Includes fonts for subscripts; e.g., ₐₙₑₕᵢⱼₖₗₘₙₒₚₜₛ
Below are my personal Agda input symbol translations;
e.g., \set → 𝒮ℯ𝓉
. Note that we could give a symbol new Agda TeX binding
interactively: M-x customize-variable agda-input-user-translations
then
INS
then for key sequence type set
then INS
and for string paste 𝒮ℯ𝓉
.
(unless noninteractive (add-to-list 'agda-input-user-translations '("set" "𝒮ℯ𝓉")))
Better yet, as a loop:
(unless noninteractive (cl-loop for item in '(;; Arabic ornate parenthesis U+FD3E / U+FD3F ("(" "﴾") (")" "﴿") ("cmd" "⌘") ;; categorial ;; ("alg" "𝒜𝓁ℊ") ("split" "▵") ("join" "▿") ("adj" "⊣") (";;" "﹔") (";;" "⨾") (";;" "∘") ;; logic ("if" "⇐") ("onlyif" "⇒") ;; lattices ;; ("meet" "⊓") ("join" "⊔") ;; tortoise brackets, infix relations ("((" "〔") ("))" "〕") ;; residuals ("syq" "╳") ("over" "╱") ("under" "╲") ;; Z-quantification range notation ;; ;; e.g., “∀ x ❙ R • P” ;; ("|" "❙") ("with" "❙") ;; Z relational operators ("domainrestriction" "◁") ("domr" "◁") ("domainantirestriction" "⩤") ("doma" "⩤") ("rangerestriction" "▷") ("ranr" "▷") ("rangeantirestriction" "⩥") ("rana" "⩥") ;; adjunction isomorphism pair ;; ("floor" "⌊⌋") ("lower" "⌊⌋") ("lad" "⌊⌋") ("ceil" "⌈⌉") ("raise" "⌈⌉") ("rad" "⌈⌉") ;; Replies ("yes" "✔") ("no" "❌") ;; Arrows ("<=" "⇐") ;; more (key value) pairs here ) do (add-to-list 'agda-input-user-translations item)))
Also some silly stuff:
(unless noninteractive ;; Add to the list of translations using “emot” and the given, more specfic, name. ;; Whence, \emot shows all possible emotions. (cl-loop for emot in `(;; angry, cry, why-you-no ("whyme" "ლ(ಠ益ಠ)ლ" "ヽ༼ಢ_ಢ༽ノ☂" "щ(゜ロ゜щ)" "‿︵(ಥ﹏ಥ)‿︵" "ಠ_ಠ" "(╬ ಠ益ಠ)" "・゚(*❦ω❦)*・゚" "(╯°□°)╯︵ ┻━┻") ;; flip the table ;; confused, disapprove, dead, shrug, awkward ("what" "「(°ヘ°)" "(ಠ_ಠ)" "(✖╭╮✖)" "¯\\_(ツ)_/¯" "(´°ω°`)" "・✧_✧・") ;; dance, csi ("cool" "┏(-_-)┓┏(-_-)┛┗(-_- )┓" ,(s-collapse-whitespace "•_•) ( •_•)>⌐■-■ (⌐■_■)")) ;; love, pleased, success, yesss, smile, excited, yay ("smile" "♥‿♥" "(─‿‿─)" "(•̀ᴗ•́)و" "ᕦ( ᴼ ڡ ᴼ )ᕤ" "(งಠ_ಠ)ง" "(。◕‿◕。)" "(◕‿◕)" "( ˃ ヮ˂)" "[ ⇀ ‿ ↼ ]" "٩(⁎❛ᴗ❛⁎)۶" "ᴵ’ᵐ ᵇᵉᵃᵘᵗⁱᶠᵘˡ" "(✿◠‿◠)") ;; flower high-5 ("hug" "♡(✿ˇ◡ˇ)人(ˇ◡ˇ✿)♡" "(づ。◕‿◕。)づ" "(づ。◕‿‿‿‿◕。)づ")) do (add-to-list 'agda-input-user-translations emot) (add-to-list 'agda-input-user-translations (cons "emot" (cdr emot)))))
Finally let's effect such translations.
;; activate translations (unless noninteractive (agda-input-setup))
Note that the effect of Emacs unicode input could be approximated using
abbrev-mode
.
7.11. Increase/decrease text size
The ‘usual’ text zoom keys C-±
…
(global-set-key (kbd "C-+") 'text-scale-increase) (global-set-key (kbd "C--") 'text-scale-decrease) ;; C-x C-0 restores the default font size
If thou knowst the ELisp, forgive this shadowing of the negative-argument
… we've still got M--
though.
Curious, this is one of the very first things I did when began using Emacs; yet, perhaps I would not have done it if I was simply told the defaults:
C-x C-=,+
increases text sizeC-x C--
decreases test sizeC-x C-0
restores it to the default size
So, the above snippet seems to save us of the prefix
C-x
and we lose on using ‘=’ for text increase and worse we
need the shift-key to get access to the ‘+’.
I suppose this is just a habit inherited from using other tools. Fortunately, I
did not inherit the need for the common user access bindings C-x
kill, C-c
copy,
C-v
paste, nor C-z
undo of other applications. If you're interested, M-x
cua-mode
to enable CUA Bindings.
7.12. Moving Text Around
This extends Org-mode's M-↑,↓
to other modes, such as when coding.
;; M-↑,↓ moves line, or marked region; prefix is how many lines. (use-package move-text :config (move-text-default-bindings))
7.13. Enabling CamelCase Aware Editing Operations
Subword movement lets us treat “EmacsIsAwesome” as three words
─“Emacs”, “Is”, and “Awesome”─ which is desirable since such naming
is common among coders. Now, for example, M-f
moves along each subword.
(global-subword-mode 1) (diminish 'subword-mode)
7.14. Delete Selection Mode
Delete Selection mode lets you treat an Emacs region much like a typical text selection outside of Emacs: You can replace the active region. We can delete selected text just by hitting the backspace key.
(delete-selection-mode 1)
7.17. C-c e n,p
: Taking a tour of one's edits
This package allows us to move around the edit points of a buffer without actually undoing anything. We even obtain a brief description of what happend at each edit point. This seems useful for when I get interrupted or lose my train of thought: Just press C-c e p to see what I did recently and where —the “e” is for “e”dit.
;; Give me a description of the change made at a particular stop. (use-package goto-chg :defer t :custom (glc-default-span 0)) (my/defhydra "C-c e" "Look at them edits!" bus :\ ("p" goto-last-change "Goto nᵗʰ last change") ("n" goto-last-change-reverse "Goto more recent change"))
Compare this with C-x u
, or undo-tree-visualise
, wherein undos are actually performed.
Notice, as a hydra, I can use C-c e
followed by any combination of p
and n
to
navigate my recent edits without having to supply the prefix each time.
7.18. visual-regexp
;; While constructing the regexp in the minibuffer, get live visual feedback for the (group) matches. ;; E.g., try: M-% use-\(.+?\) \(.+\)\b ENTER woah \1 and \2 ;; ;; C-u M-% do to regexp replace, without querying. (use-package visual-regexp :config (define-key global-map (kbd "M-%") (lambda (&optional prefix) (interactive "P") (call-interactively (if prefix #'vr/replace #'vr/query-replace)))))
7.19. LaTeX ⇐ Org-Mode
In this section we consider the Org-mode export for PDFs (LaTeX). For example, we account for LaTeX citations.
7.19.1. Get LaTeX:
(system-packages-ensure "mactex")
- This is a redistribution of TeX Live specifically for macOS.
- We get the 4GB version since it has everything and so do not need to worry about missing style files.
- This took about 12 minutes on my machine.
Restart Emacs, enter $e^{i \cdot \pi} + 1 = 0$
then press C-c C-x C-l to
have it rendered inline.
Minted: Get tool for colourful code snippets for LaTeX —see “minted” in the main article.
(system-packages-ensure "pygments")
Not anymore: Get a neato PDF presentation console:
brew install pdfpc
- With
pdfpc myfile.pdf
you get a nice timer and multiple views of the current slide and upcoming slides —with support for multiple monitors. - Install ScreenBrush, from the Apple Store, for easily drawing/annotating my screen —e.g., when I'm giving a virtual lecture to my students.
- An alternative is
brew install mupdf
thenmupdf-gl myfile.pdf
and pressf
for fullscreen thena
for adding/adorning drawings —it was too rough to use live.
Finally, within Emacs:
M-x pdf-tools-install
- With
7.19.2. Working with Citations Disabled Not_Used
An exquisite system for handling references.
The following entity will display useful data when the mouse hovers over it (•̀ᴗ•́)و If you click on it, then you're in for a lot of super neat stuff, such as searching for the pdf online!
cite:agdaoverview ( In HTML export, the citation doesn't link anywhere. )
(unless noninteractive (use-package org-ref :custom ;; Files to look at when no “╲bibliography{⋯}” is not present in a file. ;; Most useful for non-LaTeX files. (reftex-default-bibliography '("~/thesis-proposal/papers/References.bib")) (bibtex-completion-bibliography (car reftex-default-bibliography)) (org-ref-default-bibliography reftex-default-bibliography)) ;; Quick BibTeX references, sometimes. (use-package helm-bibtex) (use-package biblio) )
Execute M-x helm-bibtex
or C-c ]
and, say, enter emacs
and you will be
presented with all the entries in the bib database that mention ‘emacs’. Super
cool stuff. Moreover, if no such entries exist, then we can look some up
using the interface!
Read the manual online or better yet as an org-file with M-x org-ref-help
.
This is an Org-mode application since the citations have tooltips and export nicely to LaTeX & HTML via the Org-mode exporter.
7.19.3. Bibliography & Coloured LaTeX using Minted
Execute the following for bibliography references as well as minted Org-mode uses the Minted package for source code highlighting in PDF/LaTeX —which in turn requires the pygmentize system tool.
(setq org-latex-listings 'minted org-latex-packages-alist '(("" "minted")) org-latex-pdf-process '("pdflatex -shell-escape -output-directory %o %f" "biber %b" "pdflatex -shell-escape -output-directory %o %f" "pdflatex -shell-escape -output-directory %o %f"))
For faster pdf generation, possibly with errors, consider invoking:
(setq org-latex-pdf-process '("pdflatex -interaction nonstopmode -output-directory %o %f"))
By default, Org exports LaTeX using the nonstopmode
option, which tries
its best to produce a PDF —which ignores typesetting errors altogether,
which is not necessary ideal when using LaTeX.
7.20. HTML ⇐ Org-mode
In this section we consider the Org-mode exporters for PDFs and HTMLs. For example, we account for LaTeX citations and reliable HTML anchors.
(use-package htmlize :defer t) ;; Main use: Org produced htmls are coloured. ;; Can be used to export a file into a coloured html.
7.20.1. Ensuring Useful HTML Anchors
Upon HTML export, each tree heading is assigned an ID to be used for hyperlinks.
Default IDs are something like org1957a9d
, which does not endure the test of time:
Re-export will produce a different id. Here's a rough snippet to generate
IDs from headings, by replacing spaces with hyphens, for headings without IDs.
(defun my/ensure-headline-ids (&rest _) "Org trees without a All non-alphanumeric characters are cleverly replaced with ‘-’. If multiple trees end-up with the same id property, issue a message and undo any property insertion thus far. E.g., ↯ We'll go on a ∀∃⇅ adventure ↦ We'll-go-on-a-adventure " (interactive) (let ((ids)) (org-map-entries (lambda () (org-with-point-at (point) (let ((id (org-entry-get nil "CUSTOM_ID"))) (unless id (thread-last (nth 4 (org-heading-components)) (s-replace-regexp "[^[:alnum:]']" "-") (s-replace-regexp "-+" "-") (s-chop-prefix "-") (s-chop-suffix "-") (setq id)) (if (not (member id ids)) (push id ids) (message-box "Oh no, a repeated id!\n\n\t%s" id) (undo) (setq quit-flag t)) (org-entry-put nil "CUSTOM_ID" id)))))))) ;; Whenever html & md export happens, ensure we have headline ids. (advice-add 'org-html-export-to-html :before 'my/ensure-headline-ids) (advice-add 'org-md-export-to-markdown :before 'my/ensure-headline-ids)
One may then use [[#my-custom-id]]
to link to the entry with CUSTOM_ID
property my-custom-id
.
Interestingly, org-set-property
, C-c C-x p
, lets us insert a property
from a selection of available ones, then we'll be prompted for a value
for it from a list of values you've used elsewhere. This is useful for
remaining consistent for when trees share similar properties.
7.20.2. Clickable Headlines
By default, HTML export generates ID's to headlines so they may be referenced to, but there is no convenient way to get at them to refer to a particular heading. The following spell fixes this issue: Headlines are now clickable, resulting in a link to the headline itself.
;; Src: https://writepermission.com/org-blogging-clickable-headlines.html (setq org-html-format-headline-function (lambda (todo todo-type priority text tags info) "Format a headline with a link to itself." (let* ((headline (get-text-property 0 :parent text)) (id (or (org-element-property :CUSTOM_ID headline) (ignore-errors (org-export-get-reference headline info)) (org-element-property :ID headline))) (link (if id (format "<a href=\"#%s\">%s</a>" id text) text))) (org-html-format-headline-default-function todo todo-type priority link tags info))))
Warning: The header cannot already be a link! Otherwise you get cyrptic and
unhelpful error (wrong-type-argument plistp :section-number)
; which then
pollutes the current Emacs session resulting in stange nil
errors after C-x C-s
,
thereby forcing a full Emacs restart. Instead, you need at least one portion of
each heading to be not a link.
Need to have a custom id declared.
:PROPERTIES: :CUSTOM_ID: my-header :END:
- Failing headers:
* [[link]]
nor* ~code~
nor* $math$
.- Any non-link text before it will work:
ok [[link]]
.- Using Unicode non-breaking space ‘ ’ is ok.
- Text only after the link is insufficient.
- Any non-link text before it will work:
7.20.3. HTML “Folded Drawers”
(defun my/org-drawer-format (name contents) "Export to HTML the drawers named with prefix ‘fold_’, ignoring case. The resulting drawer is a ‘code-details’ and so appears folded; the user clicks it to see the information therein. Henceforth, these are called ‘fold drawers’. Drawers without such a prefix may be nonetheless exported if their body contains ‘:export: t’ ---this switch does not appear in the output. Thus, we are biased to generally not exporting non-fold drawers. One may suspend export of fold drawers by having ‘:export: nil’ in their body definition. Fold drawers naturally come with a title. Either it is specfied in the drawer body by ‘:title: ⋯’, or otherwise the drawer's name is used with all underscores replaced by spaces. " (let* ((contents′ (replace-regexp-in-string ":export:.*\n?" "" contents)) (fold? (s-prefix? "fold_" name 'ignore-case)) (export? (string-match ":export:\s+t" contents)) (not-export? (string-match ":export:\s+nil" contents)) (title′ (and (string-match ":title:\\(.*\\)\n" contents) (match-string 1 contents)))) ;; Ensure we have a title. (unless title′ (setq title′ (s-join " " (cdr (s-split "_" name))))) ;; Output (cond ((and export? (not fold?)) contents′) (not-export? nil) (fold? (thread-last contents′ (replace-regexp-in-string ":title:.*\n" "") (format "<details class=\"code-details\"> <summary> <strong> <font face=\"Courier\" size=\"3\" color=\"green\"> %s </font> </strong> </summary> %s </details>" title′)))))) (setq org-html-format-drawer-function 'my/org-drawer-format)
With the following invocations we only see the odd indexed ‘hello’s, where the latter two are folded up.
:this-drawer-is-exported: :export: t hello 1 :End: :this-drawer-is-NOT-exported: hello 2 :End: :fold_This_drawer_has_a_title_in_the_body: :title: I am the drawer title 0 hello 3 :End: :fold_This_drawer_is_NOT_exported: :title: Why are we here? :export: nil hello 4 :End: :fold_I_am_the_drawer_title_1: hello 5 :End:
I doubt I could show an example in the Github README, since no HTML export is happening using my setup. In case you're reading this on my blog, which has exported HTML. Here's the example:
Now that I've written this, I'm thinking it may have been preferably to use an org-block…?
7.20.4. Diagrams with Mermaid —Not Reccommended
Let's try out an alternative to PlantUML —covered below in §5.5.
First, let's get the tool.
npm install mermaid.cli
sudo git clone git@github.com:arnm/mermaid-layer.git ~/.emacs.d/private/mermaid
Then, let's get the associated mermaid
package.
(use-package ob-mermaid :custom ob-mermaid-cli-path "~/node_modules/.bin/mmdc")
Then, C-c C-c
on the following:
C-c C-x C-v
⇒ Show images inline- Mermaid supported headers:
file
to name the svg/png/pdf outputwidth
orheight
or the resulting imagetheme
used, such asdefault, forest, dark, neutral
, for foreground entitiesbackground-color
such astransparent, red, #F0F0F0
- The transparent option is nice ^_^
- You can insert new lines using
<br>
and horizontal rules via<hr>
. Similarly you can use other HTML tags such as<center>
; if you have too many you can make CSS file then use the header argument:css-file
. - Add “non-breaking space” with
. This is a forced extra space and it prevents a line break at its location. You can insert it repeatedly, but for two spaces use 
and for four spaces use 
.
If link text cuts off prematurely, use extra space with a newline: A-- text
 <br> -->B
.
Warning: JavaScript has some issues when working with Unicode and so, being a
JavaScript utility, mermaid
hangs when Unicode is used. On the upside, being a
JavaScript utility, mermaid
entities can have arbitrary code attached to them to
be executed upon clicks —for use in browsers.
- However, the Greek letters are supported; e.g., γ and Σ.
See here for possible node shapes.
After forming an intricate diagram of related design patterns, I had to use a number of HTML notions, such as
<i>, <strong>, <em>, <h1>,  , <br>, <pre>, <center>
and it was a bit more than I would have liked. In particular, the only way to change font size was to use the deprecated HTML tag<big>
or heading tags like<h1>
; even worse, the resulting PDF image did not look nice —I had to stretch it out.The command line tool is lacking functionality and so the docs are not helpful. E.g., I cannot produce pie charts using the command line tool.
7.20.5. Reveal.JS – The HTML Presentation Framework
Org-mode documents can be transformed into beautiful slide decks with org-reveal with the following two simple lines.
(use-package ox-reveal :custom (org-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js"))
For example, execute, C-x C-e
after the closing parenthesis of, the
following block to see an example slide-deck (─‿‿─)
(progn (shell-command "curl https://raw.githubusercontent.com/yjwen/org-reveal/696613edef0fe17a9c53146f79933fe7c4101100/Readme.org >> Trying_out_reveal.org") (switch-to-buffer (find-file "Trying_out_reveal.org")) (org-reveal-export-to-html-and-browse))
Org-mode exporting, C-c C-e
, now includes an option R
for such reveal slide decks.
Two dimensional slides may be a bit new to some people, so I like to
give viewers an option, in tiny font, to view the slide-deck
continuously and remind them that ?
provides useful shortcuts.
(setq org-reveal-title-slide "<h1>%t</h1> <h3>%a</h3> <font size=\"1\"> <a href=\"?print-pdf&showNotes=true\"> ⟪ Flattened View ; Press <code>?</code> for Help ⟫ </a> </font>")
One should remove the &showNotes=true
if they do not want to include
speaker notes in the flattened view.
Within the flatenned view, one may wish to CTRL/CMD+P
then save the
resulting PDF locally.
7.20.6. Org-mode ⇐ HTML Disabled
The following let's us copy htlm into org format using eww, Emacs' built-in web browser.
;; See: https://emacs.stackexchange.com/questions/7171/paste-html-into-org-mode (use-package org-eww :quelpa (org-eww :fetcher git :url "https://github.com/Fuco1/org-mode.git"))
It does not work as I'd like, but may prove useful to have around.
- Possibly useful: Open a webpage with
M-x eww
then toggleM-x read-only-mode
to edit the text, say for notes or deletions, as you read! No need to copy-paste.
org-web-tools claims to view, capture, and archive Web pages in Org-mode; this may be a very useful tool.
(use-package org-web-tools :config ;; Insert an Org-mode link to the URL in the clipboard or kill-ring. Downloads ;; the page to get the HTML title. ;; (bind-key* "C-c C-l" #'org-web-tools-insert-link-for-url) ;; Instead, see my/org-insert-link-dwim below. )
Other useful functions, needing pandoc: org-web-tools-insert-web-page-as-entry and org-web-tools-convert-links-to-page-entries.
;; C-u C-c C-l ⇒ Paste URL with title, WITHOUT prompting me for anything. ;; C-c C-l ⇒ Prompt me for title. (bind-key* "C-c C-l" (lambda () (interactive) (call-interactively (if current-prefix-arg #'org-web-tools-insert-link-for-url #'my/org-insert-link-dwim)))) ;; From: (defun my/org-insert-link-dwim () "Like `org-insert-link' but with personal dwim preferences. - When text is selected, use that as the link description --and prompt for link type - When a URL is in the clipboard, use that as the link type - On an existing Org link, prompt to alter the link then to alter the description - With a ‘C-u’ prefix, prompts for a file to link to. - It is relative to the current directory; use ‘C-u C-u’ to get an absolute path. It fallsback to `org-insert-link' when possible. Functin Source: https://xenodium.com/emacs-dwim-do-what-i-mean/" (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)))))
8. Programming
Herein we configure utilites for version control, function and variable lookup, and template expansion for inescapably repetitive scenarios.
TODO: Fix these docs
8.1. Quickly Run Code Snippets
Sometimes we want to quickly run some code without making a dedicated file or
with a file but without remembering the terminal incantation to do so, enter
quickrun
. Anywhere, we can select a snippet of code and run quickrun-region
to execute that snippet after selecting the associated programming language, or
quickrun-replace-region if we want the results in-line. If our language of
choice does not exist, we can easily add support for it.
;; In any programming buffer, “M-x quickrun” to execute that program. ;; Super useful when wanting to quickly test things out, in a playground. ;; ;; E.g., Make a new file named “hello.py” containing “print "hi"”, then “M-x quickrun”. ;; ;; Enable “quickrun-autorun-mode” to run code after every save. (use-package quickrun ;; ⇒ “C-c C-r” to see output, “q” to close output ;; ⇒ “C-u C-c C-r” prompts for a language (Useful when testing snippets different from current programming mode) ;; ⇒ In a non-programming buffer, “C-c C-r” runs selected region. :config (bind-key* "C-c C-r" (lambda (&optional start end) (interactive "r") (if (use-region-p) (quickrun-region start end) (quickrun current-prefix-arg)))))
Example…
(system-packages-ensure "rust") ;; Rust Compiler ;; Select the following then press C-c C-r: fn main() { println!("Hello, World!"); }
Actually, let's get a full Rust development environment for Emacs (which also has great support for Org-babel.)
(use-package rustic) ;; Open any Rust file, and run “M-x lsp” which will then prompt you to install ;; rust-analyzer, the rust LSP. ;; ;; LSP for Rust ⇒ Goto definition (M-. / ⌘-l), code completion with types and ;; docstrings, colourful documentation on hover, “Run [Test] | Debug” overlays, ;; super nice stuff! Run “M-!”/[M-x company-show-doc-buffer] if you want the doc in a colourful buffer. ;; ;; Below, hover over “Vec” and see nice, scrollable, colourful docs on vectors. ;; let v:Vec<_> = vec![1, 2, 3]; ;; The offical Rust toolchain installer (system-packages-ensure "rustup") (shell-command "rustup update")
8.2. C-x C-e ∷ REPL-driven development for ELisp / NodeJS / Python / Etc !
Evaluate code and see the results inline —A feedback loop that is faster than ever! |
Within Emacs, C-x C-e evaluates a Lisp expression anywhere; e.g.,
at the end of (message-box "hello world")
press C-x C-e
to see a greeting.
- We ran some code without explicitly running an interpreter/repl/compiler!
- This is known as “REPL driven development” (RDD):
There is a running REPL server for your language, implicitly in the
background, and your editor (say with
C-x C-e
) will send it a line (or a selected region) of code for evaluation; we then see the result as an overlay in our current buffer.- You choose which code gets (re)evaluated.
- A quick introduction to RDD can be viewed at PurelyFunctional.tv.
8.2.1. ELisp
By default, Emacs Lisp's C-x C-e shows results only in the minibuffer; near the bottom of the screen. Let's also have evaluation results displayed as inline overlays —at the location that the user, us, is actually looking/working; rather than forcing their eyes to shift up&down when writing&evaluating.
- C-u C-x C-e inserts the evaluation result at point; C-u 0 C-x C-e does so without truncating lengthy output.
- Read this Sweet & short blog/GIFs on practical uses of C-x C-e when working with Lisp.
;; Evaluation Result OverlayS for Emacs Lisp (use-package eros :init (eros-mode t))
8.2.2. JavaScript
Let's setup RDD for JavaScript —by having a NodeJS repl server running in the background.
- See skerrick: REPL-driven development for Javascript for animated GIFs; it works with modules as well.
(use-package skerrick :init ;; Needs to be run on the very first install of skerrick. Or when you want to upgrade. (unless (equal (shell-command-to-string "type skerrick") "skerrick not found\n") (skerrick-install-or-upgrade-server-binary))) ;; Should be run in a JS buffer; it is buffer specific. ;; (skerrick-start-server) ;; Now main function, entry point is: ;; M-x skerrick-eval-region
Let's provide a quick keyboard shortcut. E.g., C-x C-e evaluates ELisp, so let's mimic that for JS buffers:
(require 'js) ;; Defines js-mode-map ;; Evaluate a region, if any is selected; otherwise evaluate the current line. (bind-key "C-x C-e" (lambda () (interactive) (if (use-region-p) (skerrick-eval-region) (beginning-of-line) (set-mark-command nil) (end-of-line) (skerrick-eval-region) (pop-mark))) 'js-mode-map)
For instance,
// Start the server... then // On each line and press C-x C-e let a = "hello" let b = "world" (a + ' ' + b).toUpperCase() // The final line should show: HELLO WORLD
- Preserving the context
When testing an application, you might notice a bug in a particular context —i.e., a particular configuration in the app.
- The classic approach is to kill the app; i.e., stop the server that is, well, serving the app.
- Solve the problem.
- Try to get back to the configuration, context, you were in beforehand and check that the problem has been resolved.
A better approach is to ignore the bookkeeping steps, 1&3, and just do step 2. For that, there are numerous packages:
- livenode: Live-code your NodeJS applications ⨾⨾ Video demo ~11min ⨾⨾ Last updated 2013
- skewer-mode: Live web development in Emacs ⨾⨾ Silent video demo ~5min ⨾⨾ Last updated 2020
- swank-js: Swank backend for Node.JS and in-browser JavaScript ⨾⨾ Last updated 2015
8.2.3. Python, etc
It seems to be super easy to add such a support for other languages, such as Python. See the final comment here for the tiny change required.
8.3. devdocs
;; 1. Get docs of a languages: M-x devdocs-install ;; 2. Lookup docs: [C-u] M-x devdocs-lookup ;; 𝟚. Lookup docs: [C-u] C-c d (use-package devdocs :bind ("C-c d" . #'devdocs-lookup) :config (when nil ;; “C-x C-e” the following once. (cl-loop for lang in '(javascript ramda typescript html css sass vue~3 vuex~4 vue_router~4 "angularjs~1.6" nginx webpack~5 web_extensions ;; eslint jest jq jsdoc prettier mocha chai jasmine ;; bash docker~19 git homebrew elisp ;; postgresql~14 redis sqlite ;; rust ruby~3 minitest "rails~7.0") do (devdocs-install (list (cons 'slug (format "%s" lang)))))))
8.4. How do I do something?
When programming, sometimes you just gotta Google “how do I do ⋯”.
- The usual process is (1) open a browser, (2) make a Google query, (3) look at StackOverflow's most upvoted answer for your query, (4) copy-paste the code solution/example to your editor; [(5) get distracted by interesting things you'd like to read].
- Better would be to use the howdoi tool, which gives instant coding answers for common questions via the command line.
Below, my Emacs Lisp function howdoi let's me reduce the 4-step process to just 2 steps: Write your query anywhere then call
M-x howdoi
on it to replace the query with the answer. (OrC-u M-x howdoi
to see the full answer and a link to it on StackOverflow.)⚡ Never open your browser to look for help again ⚡
(system-packages-ensure "howdoi") (cl-defun howdoi (&optional show-full-answer) "Instantly insert coding answers. Replace a query with a code solution; replace it with an entire answer if a prefix is provided. Example usage: On a new line, write a question such as: search and replace buffer Emacs Lisp Then invoke ‘M-x howdoi’ anywhere on the line to get a code snippet; or ‘C-u M-x howdoi’ to get a full answer to your query. " (interactive "P") (let ((query (s-collapse-whitespace (substring-no-properties (thing-at-point 'line)))) (flag (if show-full-answer "-a" ""))) (beginning-of-line) (kill-line) (insert (shell-command-to-string (format "howdoi %s %s" query flag)))))
8.5. Sleek Semantic Selection
Super sleek way to select regions: Anywhere press ⌘-r to select the current word, press it again to select sentence, then again for the current paragraph, then more to get the current section.
(use-package expand-region :bind (("s-r" . #'er/expand-region)))
You can watch an introductory ~3 minute video to expand-region at Emacs Rocks!.
That is, repeated ⌘+r expands the selection to the next logical segment of text: In writing this means “Word, sentence, paragraph”, and in programming this means “identifier, then incrementally larger scopes”.
8.6. Managing Processes/Servers from within Emacs —Work-specific functions
Let's make a few interactive Emacs Lisp functions to reduce the amount of time I
need to be in a terminal. I'll use the prefix “w-”
for work stuff. Example
tasks:
- Start/stop my servers
- Interactively select an app to be opened in the browser
- Do database migrations/rollbacks
⟨ Obfuscated with lorem ipsum text. ⟩
Not using this, a bit too verbose to setup for each service but, more accurately, does not Just Workᵀᴹ for my needs.
;; “M-x prodigy”, then press “s” to start a service; “S” to stop it; “$” to see it; “r”estart (use-package prodigy :disabled t) ;; C-h v prodigy-services ⇒ See possible properties.
8.6.1. my/defaliases
(defalias 'defaliases 'my/defaliases) (defmacro my/defaliases (src &rest tgts) "Provide names TGTS as synonymous aliases for SRC, for discovarability. Often a function SRC can be construed from different perspectives, names, purposes TGTS. Another example is when I define things with the ‘my/’ prefix, but also want to use them without. Example use: (my/defaliases view-hello-file greet-others learn-about-the-world) In particular: (my/defaliases OLD NEW) ≈ (defalias 'NEW 'OLD)." `(--map (eval (quote (defalias `,it (quote ,src)))) (quote ,tgts)))
8.6.2. Making unkillable buffers & shells
Making unkillable buffers & shells
(defun my/declare-unkillable-buffer (name) (add-hook 'kill-buffer-query-functions `(lambda () (or (not (equal (buffer-name) ,name)) (progn (message "Not allowed to kill %s, burying instead; otherwise use “M-x force-kill”" (buffer-name)) (bury-buffer)))))) (my/defaliases my/force-kill force-kill w-force-kill) (cl-defun my/force-kill (&optional buffer-name) (interactive) (-let [kill-buffer-query-functions nil] (if buffer-name (kill-buffer buffer-name) (kill-current-buffer)) (ignore-errors (delete-window)))) (cl-defun my/run-unkillable-shell (command &optional (buffer-name command)) "Example use: (my/run-unkillable-shell \"cd ~/my-noejds-project; npm run dev\" \"my-nodejs-project\")" (-let [it (get-buffer buffer-name)] (if it (switch-to-buffer-other-window it) (async-shell-command command buffer-name) (my/declare-unkillable-buffer buffer-name))))
8.6.3. my/work-links
In my private work.el
file, I have declarations of the form (my/work-links
"REPO" "https://⟨COMPANY⟩.atlassian.net/browse/REPO-%s")
so that I can write
things like REPO:1234
to get a nice green bold link in Org-mode that will take
me to that Jira link. I also have similar links to take me to the Github
repositories, backlogs, and Kanban boards.
(cl-defmacro my/work-links (type url &optional (export-display '(format "%s-%s" type label))) "Given a link of TYPE with a URL, produce the correct org-link. EXPORT-DISPLAY is string-valued term that may mention the symbolic names ‘type’ and ‘label’. This is how the link looks upon export." `(org-link-set-parameters ,type :follow (lambda (label) (browse-url (format ,url label))) :export (lambda (label description backend) (-let [full-url (format ,url label)] (pcase backend ('html (format "<a href=\"%s\">%s</a>" full-url (-let [type ,type] ,export-display))) ('latex (format "\\href{%s}{FM-%s}" full-url label)) (_ full-url)))) :face '(:foreground "green" :weight bold :underline "blue" :overline "blue")))
8.6.4. w-start/stop-services
(defvar my/services nil "List of all services defined; used with `w-start-services' and `w-stop-services'.") (defun w-start-services () (interactive) (cl-loop for 𝑺 in my/services do (funcall (intern (format "w-start-%s" 𝑺))))) (defun w-stop-services () (interactive) (cl-loop for 𝑺 in my/services do (funcall (intern (format "w-stop-%s" 𝑺)))))
8.6.5. w-status-of-services
;; It takes about ~3 seconds to build the Status of Services page, so let's jump to it if it's already built, and the user/me can request a refresh, if need be. (global-set-key (kbd "M-S-SPC") (lambda () (interactive) (-let [buf (get-buffer "Status of Services")] (if buf (switch-to-buffer buf) (w-status-of-services))))) ;; ;; Since M-S-SPC brings up the transient menu, and most commands close the status buffer or are transient, we get the perception that the transient menu is "sticky"; i.e., stuck to the buffer, even though this is not true. I do not yet know how to make a transient menu stuck to a buffer. ;;
(defun w-status-of-services () "Show me status of all servers, including their current git branch, and most recent emitted output." (interactive) (defvar w-status-of-services/branch-name-width 12 "What is the length of the longest branch name? Let's use that to ensure there's enough whitespace.") (--> (-let [ shells (--filter (s-starts-with? "Shell" (process-name it)) (process-list)) ] (cl-loop for 𝑺 in (mapcar #'pp-to-string my/services) for associated-shell = (--find (s-contains? (format "%s" 𝑺) (cl-third (process-command it))) shells) for status = (or (ignore-errors (process-status associated-shell)) '💥) for branch = (-let [default-directory (format "~/%s" 𝑺)] (magit-get-current-branch)) for _ = (setq w-status-of-services/branch-name-width (max (length branch) w-status-of-services/branch-name-width)) for 𝑺-buffer = (--find (s-starts-with? (format "%s" 𝑺) it) (mapcar 'buffer-name (buffer-list))) for saying = (let (most-recent-shell-output (here (current-buffer))) (if (not 𝑺-buffer) " ─Server not started─ " (switch-to-buffer 𝑺-buffer) (end-of-buffer) (beginning-of-line) (setq most-recent-shell-output (or (thing-at-point 'line t) "")) (switch-to-buffer here) ;; (--> (s-truncate 135 (s-trim most-recent-shell-output)) (if (s-contains? "|" it) (cl-second (s-split "|" it)) it) (s-trim it) (if (<= (length it) 3) (s-repeat 70 " ") it)))) for _ = (if (or (s-contains? "Error" saying) (not 𝑺-buffer)) (setq status '💥)) for keymap = (copy-keymap org-mouse-map) do (cl-loop for (key action) on `(;; Checkout branch/PR c (w-pr-checkout (format "~/%s" ,𝑺)) ;; Restart service, remaining on current branch [not switching to “main”!] r (-let [current-prefix-arg t] (funcall (intern (format "w-stop-%s" ,𝑺))) (funcall (intern (format "w-start-%s" ,𝑺)))) ;; See the repo in the web w (--> (format "%s" ,𝑺) (if (s-contains? "/" it) (f-parent it) it) (format "https://github.com/%s/%s" work/gh-user it) (browse-url it)) ;; Visit service shell <return> (when ,𝑺-buffer (delete-other-windows) (split-window-below) (switch-to-buffer ,𝑺-buffer) (end-of-buffer) (other-window 1)) ;; See service magit buffer <tab> (progn (magit-status (format "~/%s" ,𝑺)) (delete-other-windows))) by #'cddr do (define-key keymap (kbd (format "%s" key)) `(lambda () (interactive) ,action))) collect ;; “%𝑾s” ⇒ Print a string with at least width 𝑾: If length(str) ≤ 𝑾, then pad with spaces on the left side. ;; Use “%-𝑾s” to instead pad with spaces to the right. (list keymap (format (format "%%s %%-20s %%-%ss %%s" (+ 5 w-status-of-services/branch-name-width)) status 𝑺 branch saying)))) ;; Setup buffer (-let [buf "Status of Services"] (ignore-errors (kill-buffer buf)) (switch-to-buffer buf) (delete-other-windows) it) ;; Insert out buttons (--each it ;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Overlay-Properties.html (-let [help (s-join "\n" '("Keybindings:" "[C-u] c ∷ Checkout PR [or branch] \t\t\t b ∷ Browse an app" "tab ∷ See service magit buffer \t\t\t i ∷ Inject users" "return ∷ Visit service shell \t\t\t s ∷ SQL buffer" "r ∷ Restart service \t\t\t w ∷ See the repo in the web" "g ∷ Refresh this view \t\t\t q ∷ Quit, and kill, this buffer"))] (insert-text-button (s-replace "\"" "″" (s-replace "run" "✅" (nth 1 it))) 'face nil ;; 'mouse-face '(:box t) ;; I use the cursor more than the mouse, so don't want two distinct views. 'keymap (nth 0 it) ;; ;; When cursor enters the button, we temporarily make it a box and show shortcuts in message area. 'cursor-sensor-functions `((lambda (_ old-pos entered?) (message ,help) (setq entered? (equal entered? 'entered)) (-let [self (button-at (if entered? (point) old-pos))] (read-only-mode 0) ;; Temporarily disable help-mode's read-only-mode setup. (if entered? (button-put self 'face '(:box "yellow" :weight bold)) (button-put self 'face nil))))) 'help-echo help) (insert "\n"))) ;; Do some highlighting, as a cautionary measure. (highlight-regexp ".*crashed.*" 'hi-red-b) ;; Forbid editing (help-mode) ;; This wont do the button face changes I like when cursor moves; so I disable read-only-mode temporarily when making the changes. (cursor-sensor-mode) (visual-line-mode -1) (toggle-truncate-lines) ;; Add some specific work related bindings (local-set-key "b" #'w-browse-app) (local-set-key "i" #'w-inject-users) (local-set-key "s" (lambda () (interactive) (w-sql) (delete-other-windows))) ;; Add general view keys (local-set-key "g" (lambda () "Refresh this view" (interactive) (ignore-errors (kill-buffer-and-window)) (w-status-of-services))) (local-set-key "q" (lambda () "Quit buffer" (interactive) (ignore-errors (kill-buffer-and-window)))) ;; Go to the first entry, so my “homemade echo menu” appears. (beginning-of-buffer)))
8.6.6. my/defservice
;; Even though I'm doing frequent prunes, it helps to give docker some leeway. ;; (cl-defmacro my/defservice (repo &key (main-setup "git checkout main; git pull; git status; hr; npm ci; hr; time docker system prune -af") (cmd "npm run docker:dev") (example "")) "Example use: (my/defservice 𝒟 :cmd 𝒞 :example ℰ) ⇒ (w-start-𝒟) ≈ Unkillable shell: cd 𝒟; 𝒞 (w-is-up-𝒟?) ≈ Open browser at ℰ (w-stop-𝒟) ≈ Kill all emacs-buffers & docker-images containing 𝒟 in their name (w-[start|stop]-services) ⇒ Starts/stops all defined services." (add-to-list 'my/services repo) `(list (cl-defun ,(intern (format "w-start-%s" repo)) () "Start server off of ‘main’, with prefix just start server off of current branch." (interactive) (let ((command (format "cd ~/%s; pwd; hr; %s; hr; %s" (quote ,repo) (if current-prefix-arg "" ,main-setup) ,cmd)) (buf-name (format "%s/%s" (quote ,repo) (if current-prefix-arg "main" (-let [default-directory ,(format "~/%s" repo)] (magit-get-current-branch)))))) (my/run-unkillable-shell (format "echo %s; hr; %s" (pp-to-string command) command) ;; Show command being run in output buffer, then run that command buf-name) (with-current-buffer buf-name (read-only-mode)))) (cl-defun ,(intern (format "w-stop-%s" repo)) () "Force-kill all unkillable buffers that mention REPO in their name. Also stop any docker services mentioning REPO in their name." (interactive) (my/docker-stop ,(pp-to-string repo)) (thread-last (buffer-list) (mapcar 'buffer-name) (--filter (s-contains-p ,(pp-to-string repo) it)) (mapcar #'my/force-kill))) (if ,example (cl-defun ,(intern (format "w-is-up-%s?" repo)) () (interactive) (browse-url ,example) (message "If the URL is busted, then the repo is not up correctly or the server has an error!")))))
In my private work.el
I have declarations of the form (my/defservice ⟨directory⟩
:cmd "npm run dev" :example "Example URL to try out for this server")
.
(when my/work-machine? (load-file "~/Desktop/work.el"))
8.8. TODO Projectile
;; https://cestlaz.github.io/posts/using-emacs-33-projectile-jump/ ;; https://github.com/bbatsov/projectile (use-package projectile :config (projectile-global-mode)) (define-key projectile-mode-map (kbd "s-p") 'projectile-command-map)
8.9. TODO Are there any errors in my code?
Flycheck gives us syntax checking and linting tools to automatically check the
contents of buffers while you type, and reports warnings and errors directly in
the buffer, or in an optional error list. Sadly, the default reporting of errors
looks like FlyC 3
in the modeline, but with flycheck-status-emoji we can see the
status using cute & compact emoji such as 😱3
.
(use-package flycheck-status-emoji :config (load-library "flycheck-status-emoji") (diminish-undo 'flycheck-mode) (flycheck-status-emoji-mode))
Let's also have a nifty modal menu to quickly navigate between errors.
(use-package helm-flycheck) (bind-key* "C-c !" (defhydra my/flycheck-hydra (:color blue :hint nil) "Move around flycheck errors and get info about them" ("n" flycheck-next-error "next" :column "Navigation") ("p" flycheck-previous-error "previous") ("f" flycheck-first-error "first") ("l" flycheck-list-errors "list") ("h" helm-flycheck "helm") ;; Jump to an error / see-errors from a nice interactive menu ("e" flycheck-explain-error-at-point "explain" :column "Current errror") ("c" flycheck-copy-errors-as-kill "copy") ("d" flycheck-describe-checker "Describe checker" :column "More") ("s" flycheck-select-checker "Select checker") ("S" flycheck-verify-setup "Suggest setup") ("m" flycheck-manual "manual")))
8.9.1. On the fly syntax checking
Flycheck is a on-the-fly syntax checker that relies on external programs to check buffers; which must be installed separately.
- E.g., ghc is required for Haskell; whereas Emacs Lisp is checked by Emacs'
own byte compiler,
emacs-lisp
. - Sometimes more than one checking tool applies, use
C-c ! s
to select a different checker. C-c ! n,p,l
takes you to the ‘n’ext or ‘p’revious error, or ‘l’ist all errors in another buffer.C-c ! c
to explicitly recheck the buffer.
(use-package flycheck :diminish :init (global-flycheck-mode) :config ;; There may be multiple tools; I have GHC not Stack, so let's avoid that. (setq-default flycheck-disabled-checkers '(haskell-stack-ghc emacs-lisp-checkdoc)) :custom (flycheck-display-errors-delay .3))
In an org-src block, we press C-c '
to get into the language's mode where
flycheck will provide warnings.
module Main where main :: IO () main = putStrLn $ "nice" ++ f 0 f :: Int -> String f x = x -- show x -- type error
In-general, flycheck is intended for self-contained raw code —not for source
blocks in Org-mode. Whence, the above example is a complete Haskell program,
with a named module and main
method.
I think the built-in flymake syntax checker is better for Emacs Lisp, so let's use that for ELisp.
(use-package flymake :hook ((emacs-lisp-mode . (lambda () (flycheck-mode -1))) (emacs-lisp-mode . flymake-mode)) :bind (:map flymake-mode-map ("C-c ! n" . flymake-goto-next-error) ("C-c ! p" . flymake-goto-prev-error)))
Try it out:
(setq 1 2) ;; Error: ‘1’ is not a variable.
8.10. Jumping to definitions & references
Out-of-the-box Emacs has ‘xref’ utilities M-.
and C-u M-.
to Find Identifier
References; however, tags to source definitions need to be generated using the
etags
program. Nonetheless, the xref utilites are impressive and some just work:
For example, M-?
cleverly finds all references for an identifier in ‘near by’
files; whereas C-u M-. RET my/.*
, for example, uses the given regular expression
to list all identifiers with prefix my/
, thereby listing my personally defined
names ^_^
C-M-. 𝓇𝓮ℊ𝓮𝓍 |
Find all identifiers whose name matches the given pattern |
Let's get dumb-jump, where the ‘dumb’ is possibly due to the fact that it works by brute-force regular-expression lookup of pre-defined ‘definitional template’ rules. It “just works” ^_^
(use-package dumb-jump :bind (("M-g q" . dumb-jump-quick-look) ;; Show me in a tooltip. ("M-g ." . dumb-jump-go-other-window) ("M-g b" . dumb-jump-back) ("M-g p" . dumb-jump-go-prompt) ("M-g a" . xref-find-apropos)) ;; aka C-M-. :config ;; If source file is visible, just shift focus to it. (setq dumb-jump-use-visible-window t))
In Lisp, for binding macros, it lists all possible mentions of the bound
variable —the first is likely what is desired. Alternatively, one could just
add the necessary rule to the variable dumb-jump-find-rules
. Otherwise, it
works fine even for locally bound definitions. It works depending on the
extension of a file.
8.11. Documentation Pop-Ups
Let documentation pop-up when we pause on a completion.
This is very useful when editing in a particular coding language, say via
C-c '
for org-src blocks. Or when working in a language-specific buffer.
(use-package company-quickhelp :config (setq company-quickhelp-delay 0.1) (company-quickhelp-mode) ;; Especially when learning a new language, looking up its definition/docstring can be helpful. ;; Note: I use “M-!” everywhere else to mean “define word at point”. (bind-key "M-!" #'company-show-doc-buffer 'prog-mode-map))
This M-!
is from plain company
, but I wanted to define it here beside the
quickhelp setup since they're related utilities.
8.12. Turbolog: What's the value of this expression? JavaScript
With turbo-log, I can select an expression then press C-x l l to have that expression be part of a console log message, in the next line, that also mentions the line number and buffer's name. I can toggle all these inserted messages to be comments with C-x l c and C-x l u, and when I'm done debugging I can quickly get rid of all of them with C-x l d.
( Before TurboLog, I used a snippet bound to l l that inserted
console.log("%c ******* LOOK HERE *******", "color: green; font-weight: bold;"); console.log({ List the variables here whose values you want to log });
)
8.12.1. TODO ll-debug
ll-debug.el provides commands to support a low level debug style. It features quick insertion of various debug output statements and improved functions for commenting and uncommenting chunks of code.
I don't use debuggers very much. I know they can be a big help in some situations and I tried some of them, but I find it almost always more direct/convenient/enlightening to put a quick 'printf' into a critical area to see what is happening than to fire up a big clumsy extra program where it takes me ages just to step through to the interesting point. In order to avoid repeated typing of 'printf("I AM HERE\n");' and similar stuff, I created `ll-debug-insert'. It inserts a statement into your sourcecode that will display a debug message. It generates unique messages on each invocation (the message consists of a big fat DEBUG together with a counter and the current filename).
—ll-debug.el
;; C-u C-v C-d ⇒ Log a message, printing values of expressions. ;; E.g., in JS this prints, console.log("DEBUG-5-del.js"," 1 + 3:",1 + 3); ;; Note “5” is the fifth debug message, and “del.js” is the name of the buffer. ;; Works with Rust, Java, Lisps, JS, TS, Clojure, C/C++, Ruby, Matlab/Octave, Shell, Perl. (use-package ll-debug :config (bind-key "C-x l" (lambda () (interactive) (ll-debug-insert 1)) #'prog-mode-map)) ;; See variable `ll-debug-statement-alist' if you want to know which ;; modes are currently supported by ll-debug. You can add new modes ;; with `ll-debug-register-mode'. ;; ;; If you want to get rid of the debug messages, use ;; `ll-debug-revert'. It finds and removes the lines with the debug ;; output statements, asking for confirmation before it removes ;; anything.
8.13. Which function are we writing?
In the modeline, show the name of the function we're currently writing.
(add-hook 'prog-mode-hook #'which-function-mode) (add-hook 'org-mode-hook #'which-function-mode)
In Org-mode, this places the current heading in the modeline.
In Lisp mode, ensure we always have matching parens.
(add-hook 'emacs-lisp-mode-hook #'check-parens)
8.14. Highlight defined Lisp symbols
Usually Emacs only highlights macro names, the following incantation makes it
highlight all defined names —as long as we're in Lisp mode, whence in org-src
blocks we use C-c '
.
;; Emacs Lisp specific (use-package highlight-defined :hook (emacs-lisp-mode . highlight-defined-mode))
Super helpful in making my Emacs configuration: If a name is not highlighted, then I've misspelled it or it doesn't exist! :smile:
8.15. Being Generous with Whitespace
The following minor mode automatically adds spacing around operators.
(use-package electric-operator :diminish :hook (c-mode . electric-operator-mode))
I dislike it when users write x=y+1
—whitespace is free and helpful. ⟨ Also,
languages with arbitrary identifiers, like Lisp and Agda, would accept x=y+1
as
an identifier, not an expression! ⟩
8.16. Coding with a Fruit Salad: Semantic Highlighting
What should be highlighted when we write code? Static keywords with fixed uses, or dynamic user-defined names?
Syntax highlighting ⇨ Specific words are highlighted in strong colours so that the structure can be easily gleaned.
- Generally this only includes a language's keywords, such as
if, loop, begin, end, cond
. - User defined names generally share one colour; usually black.
- Hence, an
if
block may be seen as one coloured keyword followed by a blob of black text.
Obvious keywords are highlighted while the rest remains in black!
- Generally this only includes a language's keywords, such as
- Semantic highlighting ⇨ Identifiers obtain unique colouring.
- This makes it much easier to visually spot dependencies with a quick glance.
- One can see how data flows through a function.
- In dynamic languages, this is a visual form of typing: Different colours are
for different names.
- Especially helpful for (library) names that are almost the same.
- This can be accomplished anywhere in Emacs by pressing
M-s h .
on a selected phrase.
- This makes it much easier to visually spot dependencies with a quick glance.
For Emacs, Color Identifiers Mode gives unique highlighting to identifiers.
- It comes with support for a bunch of languages, and one can add support for others.
- It picks colours adaptively to fit the theme; one uses
M-x color-identifiers:regenerate-colors
after a theme change.
(use-package color-identifiers-mode :config (global-color-identifiers-mode)) ;; Sometimes just invoke: M-x color-identifiers:refresh
When writing a new name, after about ~5 seconds it obtains a colour which is then
propagated immediately to any new occurrences. This timeout before recolouring
is to avoid any lag from multithreading and can be changed by altering the following
line (#64) in the source file, changing the 5
to a smaller number.
(run-with-idle-timer 5 t 'color-identifiers:refresh)
Here are further reads:
- Coding in color: How to make syntax highlighting more useful —an excellent, terse, read
- C++ IDE Evolution: From Syntax Highlighting to Semantic Highlighting
- Names with a similar prefix share a colour, and class-local items share a colour.
- Lexical differential highlighting instead of syntax highlighting
- Ideally, the smaller the lexical difference, the greater the color difference should be.
- Colouring by Context —an Emacs package
- A case against syntax highlighting
8.17. Jump between windows using Cmd+Arrow & between recent buffers with Meta-Tab relocate
We can use C-x o
to switch to the ‘o’ther window, and C-u 𝓃 C-x o
to switch to
the 𝓃-th next clockwise window, but using s-↑,↓,←,→
may be faster.
(use-package windmove :config ;; use command key on Mac (windmove-default-keybindings 'super) ;; wrap around at edges (setq windmove-wrap-around t))
The docs, for the following, have usage examples.
(use-package buffer-flip :bind (:map buffer-flip-map ("M-<tab>" . buffer-flip-forward) ("M-S-<tab>" . buffer-flip-backward) ("C-g" . buffer-flip-abort)) :config (setq buffer-flip-skip-patterns '("^\\*helm\\b"))) ;; key to begin cycling buffers. (global-set-key (kbd "M-<tab>") 'buffer-flip)
See buffer-move if you're interested in moving the buffers, and their windows, into new configurations.
8.18. Shell / Terminal relocate
8.18.1. hr: A horizontal for your terminal
When working in the terminal, at least in my day job, it can be helpful to
visually segregate large chunks of output. With the hr
command, I can run hr
'output5`
, for example, to have the same the string output5 repeated horizontal
across one line of my terminal. We can also just run hr
which is the same as hr
'#'
. Finally, you can also run hr '-#-' '-' '-#-'
to have 3 horizontal lines and
more generally hr p₁ p₂ … pₙ
will produce n-many horizontal lines with the
$ith$-line having pattern pᵢ
.
(system-packages-ensure "hr") ;; ≈ brew install hr
8.19. Github Browser Extensions (for Chrome)
- octotree
- Lets you explore the files and folders of a repository with a tree and search bar. Can also quickly browse the files changed in the current PR/branch.
- isometric-contributions
- Renders your contribution graph on your profile in a nicer way: 3D instead of the default 2D.
- OctoLinker
- Turns language-specific statements like
include
,require
orimport
into links. Gives you the ability to go to files by clicking at their paths, not only internal files it also works for imported packages! - github-hovercard
- Show a neat hover card when you hover over an element! No need to open everything in a new page, just hover over it!
- github-file-icons
- Gives different filetypes different icons to GitHub 🚀
- gifs-for-github
- Makes it easy to search GIPHY and add a GIF into any GitHub comment box.
- github-mention-highlighter
- Any GitHub issue you've been mentioned in and your mentions should now be more easily visible.
- :flame: refined-github
Browser extension that simplifies the GitHub interface and adds useful features
- Makes whitespace characters visible
- Adds one-click merge conflict fixers
- Adds reaction avatars showing who reacted to a comment
- Linkifies issue/PR references and URLs in code and conversation titles
- Adds a button to revert all the changes to a file in a PR
- Lets you hide every event except comments or unresolved comments in issues and PRs
- 🔥 Adds a build/CI status icon next to the repo’s name.
- Adds a button to download entire folders, via download-directory.github.io.
- Adds a link to preview HTML files.
- Adds a button to copy a file’s content.
- Shows PRs that touch the current file.
- Enables tab and shift tab for indentation in comment fields.
- Adds a button to insert collapsible content (via <details>)
- 🔥 Highlights the most useful comment in conversations.
- Shows color-coded review counts in PR lists.
- Mark/unmark multiple files as “Viewed” in the PR Files tab. Click on the first checkbox you want to mark/unmark and then shift-click another one; all the files between the two checkboxes will be marked/unmarked as “Viewed”.
- Shows the first Git tag a merged PR was included in.
- Automatically deletes the branch right after merging a PR, if possible.
- Uses the first commit for a new PR’s title and description.
- Adds one-click buttons to change diff style and to ignore the whitespace and a keyboard shortcut to ignore the whitespace: d w. ( The toggle-whitespace-changes is very useful when there are minor linting changes that can be safely ignored in a PR. )
- Adds a keyboard shortcut to visit your own profile: g m.
- Renders `text in backticks` in issue titles, commit titles and more places.
- Shows all of Refined GitHub’s new keyboard shortcuts in the help modal (? hotkey).
8.20. Browse remote files
Sometimes you want to see the current file in Github; e.g., you select a
region and press M-x browse-at-remote-kill
to get the URL for that region in
Github then you can send that URL to your peers when referencing something.
- Or just
M-x browse-at-remote
to see it in Github. - browse-at-remote: Browse target page on github/bitbucket from emacs buffers.
;; Usage: [Optionally select a region then] M-x browse-at-remote[-kill] (use-package browse-at-remote)
8.21. A nice Emacs interface for a portion of the “gh” CLI
;; A nice Emacs interface for the a portion of the “gh” CLI. (my/defaliases my/gh-checkout gh-checkout w-pr-checkout w-branch-checkout) (cl-defun my/gh-checkout (&optional repo) "With prefix, select a branch name; otherwise a Pull Request name. If no REPO is provided, let the user select one from a menu. Example use: (w-pr-checkout \"~/my-repo\") (w-pr-checkout)" (interactive) (let* ((repo (or repo (completing-read "Repo: " (projectile-relevant-known-projects)))) (default-directory repo) ;; temporarily override this global variable, used with magit (current-branch (magit-get-current-branch)) (all-branches (magit-list-local-branch-names)) (status (format "cd %s; gh pr status" repo))) (if current-prefix-arg (-let [branch (completing-read (format "New branch (Currently “%s”): " current-branch) all-branches)] (shell-command-to-string (format "cd %s; git checkout %s" repo branch))) (let* ((PR-list (s-split "\n" (shell-command-to-string (format "cd %s; gh pr list" repo)))) (pr♯ (car (s-split "\t" (completing-read "PR: " PR-list)))) (_ (shell-command-to-string (format "cd %s; gh pr checkout %s" repo pr♯))) (new-branch (magit-get-current-branch))) ;; Show nice status (async-shell-command status) (magit-status repo)))))
8.22. copy-as-format: Emacs function to copy buffer locations as GitHub/Slack/JIRA etc… formatted code.
;; Usage: [C-u] M-x copy-as-format ⇒ Copies selected region, or current line. ;; Also use: copy-as-format-𝒮, to format to a particular 𝒮tyle. ;; Without suffix 𝒮, format defaults to `copy-as-format-default`. ;; With a prefix argument prompt for the format style 𝒮. ;; Easy to add more formats. (use-package copy-as-format)
8.24. SQL —via LSP
When doing ‘serious’ database work, I love using DBeaver. But when I only want
to quickly run a query, to check something, without the fricition of switching
applications, then doing so in Emacs is a no-brainer: I use w-sql to produce
a query buffer, if one doesn't exist, then C-c C-c
to send queries. (This also
serves as a nice reference mechanism for useful queries.)
Let's use LSP for SQL, to get
- Neato tooltips on table definitions and column constraints,
- Write
select * from my-table t
then delete the*
and entert.
to see all valid columns; or just delete the*
and press.
to see them.
- Write
- Completions for tables & column names,
C-c C-c
to execute query at point [My personal setup below],- Run all queries with lsp-execute-code-action
- When sharing with others, maybe execute lsp-format-buffer
- Note: The tokenizer is known to a bit buggy.
- E.g.,
select 1 + 2 as "Numerical, yeah!"
will not be run due to the ‘!’.
- E.g.,
;; Installation: go install github.com/lighttiger2505/sqls (setq lsp-sqls-server "/Users/musa/go/bin/sqls") (setq lsp-sqls-timeout 1) (setq lsp-sqls-workspace-config-path nil) ;; https://emacs-lsp.github.io/lsp-mode/page/lsp-sqls/ (setq lsp-sqls-connections ;; (--map `((driver . "postgresql") (dataSourceName . ,it)) work/sqls-connections)) `(((driver . "postgresql") (dataSourceName . ,(car work/sqls-connections))))) ;; (add-hook 'sql-mode-hook 'lsp) (use-package org-modern) (defun my/execute-query-at-point () "Execute query at point and make resulting table an Org table and modernise it" (interactive) (lsp-sql-execute-paragraph) (other-window 1) (org-modern-global-mode) (org-mode) (read-only-mode -1) (while (re-search-forward "^\+" nil t) (replace-match "|" nil t)) (toggle-truncate-lines) (beginning-of-buffer) (execute-kbd-macro (read-kbd-macro "<tab>")) (read-only-mode) (other-window -1) (local-set-key "q" (lambda () "Quit buffer" (interactive) (ignore-errors (kill-buffer-and-window))))) (bind-key "C-c C-c" #'my/execute-query-at-point 'sql-mode-map) (bind-key "C-c C-<return>" #'my/execute-query-at-point 'sql-mode-map) (defun w-sql () "Quickly run a SQL query, then dispose of the buffer when done. Uses the first connection available, to change connections invoke M-x `lsp-sql-switch-connection'." (interactive) ;; LSP only works on files; not buffers; so I use this file. (find-file "~/.emacs.d/scratch.sql") (insert work/sql-queries) ;; docs and examples (sql-mode) (hs-minor-mode -1) ;; I don't want the above comments to be collapsed away. (beginning-of-buffer))
Previous setup ~ ejc-sql [Disabled]
- ❌
ejc-sql
crashes with updates. - ✔ I like that ejc-sql gives me M-. to jump to definitions of tables/functions/etc.
- “Go to definition”: [M-.] M-x ejc-describe-entity ⇒ Show source code for a stored view, function, table, etc.
- Get entity definition: show creation SQL of view, package, function, procedure or type.
- C-c e t ⇒ See list of tables
- ✔ ejc works with org-mode out of the box; https://github.com/kostafey/ejc-sql#use-with-org-mode
⇒ BEST OF BOTH WORLDS? ⇐ (setq ejc-sql-separator "– /") ;; Otherwise LSP sql server sees ‘/’ as a syntax error.
;; (system-packages-ensure "leiningen") ;; ;; Reason: https://github.com/kostafey/ejc-sql/issues/163 (use-package ejc-sql :config (require 'ejc-company) (push 'ejc-company-backend company-backends) (setq ejc-completion-system 'standard) ;; Use my setup; i.e., Helm. ;; [C-u] C-c C-c ⇒ Evaluate current query [With JSON PP]. (bind-key "C-c C-c" (lambda () (interactive) (setq ejc-sql-complete-query-hook (if current-prefix-arg '(w-ejc-result-pp-json) ;; Defined below '((lambda () ;; Give each line of text just one screen line. (switch-to-buffer-other-window "*ejc-sql-output*") (visual-line-mode -1) (toggle-truncate-lines) (other-window -1))))) (ejc-eval-user-sql-at-point)) 'sql-mode-map)) (defun w-sql () "Quickly run a SQL query, then dispose of the buffer when done. By default uses a connection named “xxxx”, to see a list of other connections call with a prefix argument." (interactive) ;; Get DB credentials. One does “M-x ejc-connect-interactive” once, then ;; “M-x ejc-insert-connection-data” and paste that into your init; then ;; “M-x ejc-connect” provides a completion of possible DBs to connect to. (load-file "~/Desktop/work.el") ;; For the following: Alternatively, we could make a new binding such as “C-c C-j” ;; which temporarily adds to this hook, then calls (add-to-list 'ejc-sql-complete-query-hook 'w-ejc-result-pp-json) ;; Defined below (require 'ejc-sql) (-let [connection-name (if current-prefix-arg (ejc-read-connection-name) "xxxx")] (switch-to-buffer-other-window (format "*SQL/%s*" connection-name)) (thread-last `("\n\n/\n-- DOCS & EXAMPLES\n--" "-- SQL queries should be seperated by “/”" "-- [C-u] C-c C-c ⇒ Evaluate current query [With JSON PP]" "-- In result window, TAB/RET to navigate the columns/rows." "-- C-c e t ⇒ List all tables" "-- C-h t ⇒ ‘H’elp for a ‘t’able" "\nselect 1 + 2 as \"Numerical, yeah!\"" "\n/\n" "-- More examples of useful SQL queries I might need, but don't want to remember" ;; I've moved them out to my private work.el file. ,@work/sql-queries) (s-join "\n") insert) (sql-mode) (hs-minor-mode -1) ;; I don't want the above comments to be collapsed away. (ejc-connect connection-name) (beginning-of-buffer) (message "Connecting to DB... please wait a moment"))) (defun w-ejc-result-pp-json () "Pretty print JSON ejc-result buffer." (interactive) (ignore-errors (switch-to-buffer-other-window "*ejc-sql-output*") (beginning-of-buffer) (re-search-forward "{") (backward-char 1) (delete-region (point-min) (point)) (end-of-buffer) (re-search-backward "|") (kill-line) (json-mode) (json-pretty-print-buffer) (other-window -1) (message-box "hiya")))
;; Add minimal table autocomplete to SQL (require 'ejc-company) (push 'ejc-company-backend company-backends) (add-hook 'ejc-sql-minor-mode-hook (lambda () (company-mode t)))
- Postgres is the Emacs of Databases: It has extensions! E.g., It can be extended to write stored functions in Ruby, Python, and in-particular JavaScript. So neat!
8.25. Docker
;; Usage: M-x docker [RET ?] (use-package docker :config (my/defaliases docker-containers w-show-docker-containers)) (defun w-stop&remove-docker-containers () (interactive) (shell-command "docker stop $(docker ps -a -q)") (shell-command "docker rm $(docker ps -a -q)")) (defun w-postgres-status () (interactive) (display-message-or-buffer (s-replace "healthy" "🆙 healthy 🍏" (shell-command-to-string "docker ps -a | grep postgres")))) (cl-defun w-kill-process-running-on-port (&optional (port (completing-read "Port: " '("3310" "80" "9000" "8000" "8080" "etc, whatever you want")))) "We use ‘lsof’ to list open files; as in: lsof -i :3310 +c0 The +c0 prints the full name of the command rather than truncating it. We then find the PID and kill the process." (interactive) (-let [process (shell-command-to-string (format "lsof -i :%s +c0" 3310))] (-let [pid (ignore-errors (cl-second (s-split " " (cl-second (s-split "\n" process)))))] (shell-command (format "kill %s" pid)) (message process))))
8.25.1. my/docker-stop
(defun my/docker-stop (ctr) "Stop all containers that mention CTR in their name, image, command, or container id" (thread-last (shell-command-to-string "docker ps -a") (s-split "\n") (--filter (s-contains-p ctr it)) (--map (car (s-split " " it))) ;; Get docker container ids (--map (shell-command (concat "docker stop " it)))))
8.26. my/open-in-terminal '⌘
(defalias 'my/open-in-terminal '⌘) (cl-defun ⌘ (&rest cmds) "Run terminal commands CMDS in a new MacOS Terminal instance, and bring it to focus. Example: (⌘ \"echo hello\" \"echo world\") Useful for those cases where I have to interact with non-trivial ‘interactive terminal menus’." (shell-command (format "osascript -e 'tell app \"Terminal\" to activate do script %s'" (pp-to-string (s-join ";" cmds))))) ;; (⌘ "echo hello" "echo world")
8.27. Check if application APP is currently running, in use.
(cl-defun my/application-running? (app) "Check if application APP is currently running, in use." (not (equal "0" (s-trim (shell-command-to-string (format "ps aux | grep -v grep | grep -ci %s" app))))))
8.28. LSP: Making Emacs into a generic full-featured programming IDE
(TLDR: LSP gives Emacs a uniform way to deal with programming languages, such as highlighting and refactoring.)
Language Server Protocol (LSP) was created by Microsoft to define a common standard for providing editor-agnostic code intelligence support. It has become widely popular since its creation. Emacs support for lsp comes with the lsp-mode and the eglot (‘E’macs poly‘glot’) packages; let's use the first one since it comes with all features enabled by default (which increases discoverability). [ See also: A guide on disabling/enabling lsp-mode features - LSP Mode - LSP support for Emacs. ]
Essentially how it works is that LSP defines a bunch of useful requests —such as code completion, types, etc— and our editor (Emacs) makes these requests to a server (a process that knows how to handle them) then we get the results and show them nicely in our editor (Emacs).
Further reading:
- Main features - LSP Mode - LSP support for Emacs ⇒ Reasons (with gifs) to use lsp-mode ;-)
- How Emacs became my awesome Java editing environment [EmacsConf2019] is an excellent ~10min video demoing prime features of LSP.
- Using Text Editors for Programming with LSP | Toptal
- LSP overview
- :flame: React JavaScript Tutorial in Emacs - LSP Mode - Nice walkthrough on setting-up LSP for JavaScript (along with ESlint) and trying it out.
Super simple setup for lsp-mode: Install lsp-mode with then open, say, a JavaScript file and press M-x lsp. It
will ask you to download a server, such as jsts-ls
for “JavaScript-TypeScript
Language Server”. [You may need to restart Emacs; when you open a file, LSP
needs to know what ‘workspace’ it belongs to!] Now you can, for example, perform
s-l g r to see all references of the name under the cursour, or s-l gd to go to its definition, or s-l g t to see its type (which already
shows in the mode line). In your JS file, write the errenous line let a == 1;
then with your cursour on that line you'll see some info to the side about the
error (thanks to lsp-ui-mode
) and if you invoke lsp-treemacs-errors-list
then you get a nice hierarchical, ⟨tab⟩-ful, list of errors. To find symbol
names, with autocompletion of all names in your workspace, use s-l g a
(thanks to helm-lsp
), for the current file lsp-ui-imenu (or more simply,
C-c i) is nice. As the LSP server runs it can even detect JSON objects
and show their values in the mode line when the cursour is above them; which can
also be requested with s-l h h. (lsp-describe-thing-at-point). That
is all!
Find definitions of the symbol under point | |
Find references of the symbol under point | |
Find type definitions of the symbol under point | |
Find all meaningful symbols that match pattern (project wide) | |
:fire: Display the type signature and documentation of the thing at point | |
Trigger display hover information popup and hide it on next typing | |
Rename the symbol (and all references to it) |
Also: lsp-format-buffer makes the code more readable.
(use-package lsp-mode :init ;; Set prefix for lsp commands ;; (setq lsp-keymap-prefix "s-l") ;; default ;; Set how often highlights, lenses, links, etc will be refreshed while you type ;; (setq lsp-idle-delay 0.500) ;; default :hook ;; Every programming mode should enter & start LSP, with which-key support (js-mode . lsp-mode) ;; Enter LSP mode (js-mode . lsp) ;; Start LSP server (lsp-mode . lsp-enable-which-key-integration) ;; For some reason, my usual snippet setup does not work with LSP, so using “C-x y” :bind ("C-x y" . #'yankpad-insert) :commands lsp) ;; If a server crashes, restart it without asking me. (setq lsp-restart 'auto-restart) ;; https://emacs-lsp.github.io/lsp-mode/page/languages/ ;; M-x lsp-install-server ⟨return⟩ jsts-ls ;; M-x lsp-install-server ⟨return⟩ json-ls ;; M-x lsp-install-server ⟨return⟩ eslint ;; M-x lsp-install-server ⟨return⟩ css-ls ;; M-x lsp-install-server ⟨return⟩ html-ls ;; lsp-ui for fancy sideline, popup documentation, VScode-like peek UI, etc. ;; https://emacs-lsp.github.io/lsp-ui/#intro ;; ;; You only have to put (use-package lsp-ui) in your config and the package will ;; work out of the box: By default, lsp-mode automatically activates lsp-ui. (use-package lsp-ui) ;; lsp-treemacs for various tree based UI controls (symbols, errors overview, ;; call hierarchy, etc.) (use-package lsp-treemacs) ;; https://github.com/emacs-lsp/lsp-treemacs ;; M-x lsp-treemacs-errors-list ;; helm-lsp provides “on type completion” alternative of cross-referencing. ;; https://github.com/emacs-lsp/helm-lsp (use-package helm-lsp) (require 'lsp-mode) (define-key lsp-mode-map [remap xref-find-apropos] #'helm-lsp-workspace-symbol) ;; Jump to a symbol's definition in the current workspace with “s-l g a” or “M-g ;; a” (The 'a' stands for apropos, which means appropriate nature) ;; Set the amount of data which Emacs reads from a process. ;; Some LSP responses are in the 8k-3MB range. ;; ⟦ 1 megabyte ≈ 1 million bytes ≈ 1 000 000 bytes ⟧ (setq read-process-output-max (* 1024 1024)) ;; ~1mb; [default 4k] (setq gc-cons-threshold (* 2 8 1000 1024)) ;;; ~16mb; default is: 800 000 ;; A large gc-cons-threshold will cause freezing and stuttering during long-term ;; interactive use. This one seems to be a good default.
:flame: If you leave your cursour on a name, all other occurances are highlighted and you get to see its (type) description in the mode line.
🔥 As you begin to type, all variable/function names loosely matching what you type will be shown, along with their (type) descriptions.
🚁 If you see an error “LSP :: Could not connect to 𝒳 server”
, then run
lsp-doctor and look at the buffer 𝒳::stderr
for hints on what the cause
could have been; FAQ.
The Language Server protocol is used between a tool (the client) and a language smartness provider (the server) to integrate features like auto complete, go to definition, find all references and alike into the tool.
LSP, more or less, reduces the linear problem of language-specific-IDE into a constant problem: Each language implements an LSP program which is then used by an LSP-enabled editor to provide an IDE experience.
LSP mode supports the following Features:
- As you type reporting of parsing and compilation errors (via flycheck/lsp-ui)
- Code completion - using company-mode
- doc hovers - using lsp-ui
- Code actions - using lsp-ui
- Code outline - using builtin imenu
- Code navigation - using builtin xref
- Code lens (references/implementations) - using builtin xref
- Highlights
- Code formatting
- Visual debugger - dap-mode
- Test runner - dap-mode
- Project explorer integration - treemacs
;; Load the various useful utils (require 'lsp-ui-peek) (require 'lsp-ui-sideline) (require 'lsp-ui-doc) (require 'lsp-ui-imenu) ; (setq lsp-mode-hook nil) (add-hook 'lsp-mode-hook (lambda () ;; Locally delete a file needed for work, but it's outdated and clashes with LSP. (shell-command "rm ~/wxPortal/.flowconfig") ;; Load the various useful utils (require 'lsp-ui) (lsp-ui-peek-enable t) (lsp-ui-doc-enable t) (lsp-ui-sideline-enable t) (lsp-ui-imenu-buffer--enable) ;; Set ⌘-l as the main mini-menu for LSP commands (bind-key* "s-l" #'my/lsp-hydra/body))) (defun my/helm-lsp-workspace-symbol-at-point () (interactive) (let ((current-prefix-arg t)) (call-interactively #'helm-lsp-workspace-symbol))) (defun my/helm-lsp-global-workspace-symbol-at-point () (interactive) (let ((current-prefix-arg t)) (call-interactively #'helm-lsp-global-workspace-symbol))) ;; (defhydra my/lsp-hydra (:color blue :hint nil) ;; Xref ("d" xref-find-definitions "Definitions" :column "Xref") ("D" xref-find-definitions-other-window "-> other win") ("r" xref-find-references "References") ("s" my/helm-lsp-workspace-symbol-at-point "Helm search") ("S" my/helm-lsp-global-workspace-symbol-at-point "Helm global search") ;; Peek ("C-d" lsp-ui-peek-find-definitions "Definitions" :column "Peek") ("C-r" lsp-ui-peek-find-references "References") ("C-i" lsp-ui-peek-find-implementation "Implementation") ;; LSP ("p" lsp-describe-thing-at-point "Describe at point" :column "LSP") ("C-a" lsp-execute-code-action "Execute code action") ("R" lsp-rename "Rename") ("t" lsp-goto-type-definition "Type definition") ("i" lsp-goto-implementation "Implementation") ("f" helm-imenu "Filter funcs/classes (Helm)") ("C-c" lsp-describe-session "Describe session") ;; Flycheck ---my “C-c !” flycheck hydra is much better than this simple lsp one. ;; ("l" lsp-ui-flycheck-list "List errs/warns/notes" :column "Flycheck") ("l" my/flycheck-hydra/body "List errs/warns/notes" :column "Flycheck") ;; Misc ("q" nil "Cancel" :column "Misc") ("b" pop-tag-mark "Back"))
8.28.1. Types as comments in JavaScript via LSP
// The following enables typechecking via LSP. // @ts-check /** * * @param {number} x - * @param {number} y - * @returns {number} - */ // function add(x, y) {return "x + y";} // ⚠ Faulty implementation! function add(x, y) {return x + y;} // add("a", 1) // ⚠ Erroneous call! add(1, 2)
For more on this idea on types, see A simple, but non-trivial example of getting the most from JSDoc + tsserver (Type Linting without TypeScript).
8.29. JSON
Since web apps tend to be RESTful, payloads are JSON. To use the JSON, we need to tediously find paths to particular fields; let's do so with automatically, without error..
When we open a JSON file, we are promoted to install an LSP server, which
- Checks that the file is valid JSON.
- Shows the full path to the cursor's location, at the top of the window.
(use-package json-mode)
Useful bindings are in the docstring of json-mode.
- C-c C-f
- Pretty print buffer
- C-c C-p
- Copy path to field at point
Note that there is also json-pretty-print-buffer; which can be used to uglify a JSON buffer if a prefix is provided.
Let's make a hydra for JSON,
(my/defhydra nil "JSON Browser" gamepad :Buffer ("p" #'json-mode-show-path "Copy path to field at point") ;; ("f" #'json-mode-beautify "Format Buffer") ;; ("m" (lambda () (interactive) (json-pretty-print-buffer t)) "Minify/ugligy buffer") ("t" (lambda () (interactive) (if my/json-hydra/pretty-printed? (json-pretty-print-buffer t) (json-mode-beautify (point-min) (point-max))) (setq my/json-hydra/pretty-printed? (not my/json-hydra/pretty-printed?))) "Toggle format/uglify of buffer" :toggle (progn (defvar my/json-hydra/pretty-printed? nil) my/json-hydra/pretty-printed?))) ;; ;;
Interesting, but not for me: json-par: Emacs minor mode for structural editing of JSON.
Also: JSON to X is a website to convert JSON to other formats, such as in Rust or as JSDoc type annotations.
8.30. w-screencapture
(bind-key "C-c s" (cl-defun w-screencapture () "Interactively capture screen and save to clipboard; then paste in Slack, etc, with ⌘-c. After we run this command, we can swipe up on mousepad to select different desktops, then click & drag to select portition of screen to capture. Captured screen is NOT saved to disk, only copied to clipboard. In MacOs, + Command + Shift + 5 ⇒ Select screen record + Command + Shift + 4 ⇒ Selection Screenshot + Command + Shift + 3 ⇒ Screenshot See: https://osxdaily.com/2011/08/11/take-screen-shots-terminal-mac-os-x" (interactive) (async-shell-command "screencapture -i -c"))) (cl-defun w-delete-all-screenshots () "Delete all “Screen Shot ⋯” files in ~/Desktop." (interactive) (thread-last (shell-command-to-string "cd ~/Desktop; ls") (s-split "\n") (--filter (s-starts-with-p "Screen Shot" it)) (--map (f-delete (format "~/Desktop/%s" it)))))
8.31. Screencapturing the Current Emacs Frame
Sometimes an image can be tremendously convincing, or at least sufficiently
inviting. The following incantation is written for MacOS and uses it's native
screencapture
utility, as well as magick
.
(defun my/capture-emacs-frame (&optional prefix output) "Insert a link to a screenshot of the current Emacs frame. Unless the name of the OUTPUT file is provided, read it from the user. If PREFIX is provided, let the user select a portion of the screen." (interactive "p") (defvar my/emacs-window-id (s-collapse-whitespace (shell-command-to-string "osascript -e 'tell app \"Emacs\" to id of window 1'")) "The window ID of the current Emacs frame. Takes a second to compute, whence a defvar.") (let* ((screen (if prefix "-i" (concat "-l" my/emacs-window-id))) (temp (format "emacs_temp_%s.png" (random))) (default (format-time-string "emacs-%m-%d-%Y-%H:%M:%S.png"))) ;; Get output file name (unless output (setq output (read-string (format "Emacs screenshot filename (%s): " default))) (when (s-blank-p output) (setq output default))) ;; Clear minibuffer before capturing screen or prompt user (message (if prefix "Please select region for capture …" "♥‿♥")) ;; Capture current screen and resize (thread-first (format "screencapture -T 2 %s %s" screen temp) (concat "; magick convert -resize 60% " temp " " output) (shell-command)) (f-delete temp) ;; Insert a link to the image and reload inline images. (insert (concat "[[file:" output "]]"))) (org-display-inline-images nil t)) (bind-key* "C-c M-s" #'my/capture-emacs-frame)
Why this way? On MacOS, ImageMagick's import
doesn't seem to work —not at all
for me! Also, I dislike how large the resulting image is. As such, I'm using
MacOS's screencapture
utility, which in-turn requires me to somehow obtain frame
IDs. Hence, the amount of work needed to make this happen on my system was most
simple if I just wrote it out myself rather than tweaking an existing system.
C-c C-x C-v
⇒ Toggle inline images!
8.32. Comment-boxes up to the fill-column
GIF:
(defun my/comment-box (b e) "Draw a box comment around the region but arrange for the region to extend to at least the fill column. Place the point after the comment box. Source: http://irreal.org/blog/?p=374 To do fancy stuff like removing boxes, centering them, etc see https://github.com/lewang/rebox2/blob/master/rebox2.el" (interactive "r") (let ((e (copy-marker e t))) (goto-char b) (end-of-line) (insert-char ? (- fill-column (current-column))) (comment-box b e 1) (goto-char e) (set-marker e nil)))
8.33. Auto-format on Save
Auto-format source code in many languages with one command
Lets you auto-format source code in many languages using the same command for all languages, instead of learning a different Emacs package and formatting command for each language.
Just do M-x format-all-buffer
and it will try its best to do the right thing. To
auto-format code on save, use the minor mode format-all-mode.
- You will need to install external programs to do the formatting. If
format-all-buffer
can't find the right program, it will try to tell you how to install it.
(use-package format-all ;; To enable format on save for most programming language buffers: :hook (prog-mode . format-all-mode) :config ;; Please use the default formatters; I don't care too much. (add-hook 'format-all-mode-hook 'format-all-ensure-formatter)) ;; For JavaScript prettification: It automatically inserts semicolons, forces newlines, inserts parens, etc. ;; Lots of redundant stuff, but stuff to make it easy to work with others. (shell-command "npm install --global prettier") ;; Specific package to do only JS prettification: https://github.com/prettier/prettier-emacs
8.34. Searching Hydra
(my/defhydra "s-f" "\t\tLocate Everything" search :Buffer ;; find all the occurrences of a string, pull out the lines containing the string to another buffer where [F2] I can edit and save, ("e" helm-swoop "Editable") ;; Implicit Regex, colourful ("c" swiper "Classic") :Project ;; “:toggle ℰ”: ℰ is a Boolean expression that is evaluated to tell us whether the state is on-or-off ("t" (lambda () (interactive)) "Ignore specs/jsons" :toggle (let* ((with-hole "ag %s --line-numbers -S --color --nogroup %%s %%s %%s") ;; ≈ original value of ‘helm-grep-ag-command’ (ignores "--ignore=\"*spec.js\" --ignore=\"*.json\" --ignore=\"*.json5\"") (on (equal helm-grep-ag-command (format with-hole ignores)))) (if on (progn (setq helm-grep-ag-command (format with-hole "")) nil) ;; ≈ turn off the toggle (setq helm-grep-ag-command (format with-hole ignores))))) ("f" (lambda () (interactive) (helm-do-grep-ag t)) "File type") ("d" (lambda () (interactive) (-let [default-directory (read-directory-name "Where do you want to search? ")] (helm-do-grep-ag nil))) "Directory") ("D" (lambda () (interactive) (-let [default-directory (read-directory-name "Where do you want to search? ")] (helm-do-grep-ag t))) "Directory & type"))
8.35. Peer Review / Pull Request Template for Work
I run w-pr-template to get a new Org buffer with a review template. I make
notes and check-off boxes with C-c C-c
as usual, and when I'm done I press C-c
C-s
to copy the buffer to clipboard, in markdown format. Then I can paste it
into Github.
(cl-defun w-pr-template () "Hi" (interactive) (-let [buf "PR Template ~ Press “C-c C-s” when done"] (ignore-errors (kill-buffer buf)) (switch-to-buffer buf) (insert "w-pr-template") (yankpad-expand) (org-mode) (beginning-of-buffer) (use-local-map (copy-keymap org-mode-map)) (local-set-key (kbd "C-c C-s") `(lambda () (interactive) (beginning-of-buffer) (replace-string "[X]" "✅") (beginning-of-buffer) (replace-string "[ ]" "❌") (beginning-of-buffer) (replace-string "[-]" "🚧") (-let [org-export-with-toc nil] (org-md-export-as-markdown) (kill-ring-save (point-min) (point-max))) (kill-buffer-and-window) ;; Kills the new org-md-export buffer (kill-buffer ,buf) ;; Kills this temporary PR template buffer (message "PR notes saved to clipboard in Github markdown")))))
** w-pr-template: Peer Review / Pull Request Template for Work I followed the testing instructions and everything look's good 😁 Below is a detailed checklist of what I went through. -------------------------------------------------------------------------------- 1. [ ] Pull Request: The PR template was fully filled out. - [ ] Clear description of the problem and how it was solved. - [ ] I've cross-checked the description with the associated Jira ticket; and everything is implemented. - [ ] I've ticked-off the PR's check-boxes. - [ ] Good use of bullet points (-) and code font (‵, ‷) to make the prose easier to read. - [ ] The commit messages are well-written. - [ ] Travis CI succeeds. - [ ] PR author annotated source code, with Github comments, before the review. - Annotations guide the reviewer through the changes, showing which files to look at first and defending the reason behind each code modification. 2. [ ] Functionality: The code behaves as the author intended. - [ ] I was able to reproduce the bug on ~main~. - [ ] Ran the code and used it as an end-user would. Namely, I made a new form, submitted an instance, checked ~In-Progress~, & ~Form Reports~. - [ ] I've tried all kinds of quotes and unicode, "𝒰"\‘ℕ’/𝒊′ℭ″𝑂؛𝒟⨾'∃', input for text inputs. - [ ] Followed the happy path in the provided testing instructions. - Nope; none provided. - [ ] Also tried the following edge case: ⋯ ⁉ ⋯ 3. [ ] Tests: There are new unit tests (but sadly no E2Es/integration). - [ ] Meaningful: Tests actually test that the code is performing the intended functionality. - [ ] Avoid global test fixtures and seeds, add data per-test. TL;DR: To prevent test coupling and easily reason about the test flow, each test should add and act on its own set of DB rows. 4. [ ] Naming: Clear and informative names were chosen for top-level-items/variables/methods. 5. [ ] Comments: New top-level-items/variables/methods have clear and useful documentation. - [ ] Sometimes the code is clear ---e.g., 5 lines perform a toggle--- but we can improve readability by providing a “comment as function” ---e.g., making a ~toggle~ function, then calling it where it is used; this new function is likely to be smaller than the original inlined use. Some comments-as-functions have been suggested. 6. [ ] Being Neighbourly: There was nearby code that could have been improved/update, and suggestions have been left as to how to do so. - We're likely to touch these files again in the future, so why not leave things better than we found them 🚀 7. [ ] Code Smells: Are there any [[https://blog.codinghorror.com/code-smells/][code smells?]] 8. [ ] Syntax: I've read every line. - Nope, there was some stuff I'm not familair with. If the PR authour can jump on a call and walk me through them, that'd be awesome! - [ ] I've left various suggestions and feedback, against specific lines of code. Happy to discuss these further! - [ ] I took my time while reviewing your code, and I'm not depending on others to catch errors. 9. [ ] Complexity: Another developer can easily understand and use this code when they come across it in the future :-) 10. [ ] Modulairty: Is there any redundant or duplicate code? Is the code as modular as possible? 11. [ ] Backwards Compatiable: I made a form in ~main~, involving the work in this branch, and it worked fine in this branch. - I was able to edit the form, submit it, and checked that it looked find in ~Form Reports~. 12. [ ] Best Practices: The following rules-of-thumb are adhered to, more or less. #+html: <details> - Remove some redundancy using a bit of laws of algorithmics, namely ~[𝒊𝒇-𝒅𝒊𝒔𝒕𝒓𝒊𝒃𝒖𝒕𝒊𝒗𝒊𝒕𝒚] (a ? f(b) : f(c)) ≡ f(a ? b : c)~, which increases readability a tad. # Especially when “f” is a lengthy expression; it may also be ideal to give # “a ? b : c” a local name. - Fail fast, validate arguments [we have some in-house validation util libraries] - Be aware that ~0, "", []~ are all falsey values in JS: If a variable ~x~ can be one of those things, then ~if (x)~ is not always approriate; better may be ~if(typeof x === 'integer')~ since this communicates two things (1) the variable is defined, and (2) what it's type expected is. + Likewise, better use ~typeof~ instead of ~x !== null~. - You have variables declared a bit from their use sites; the distance creates an unnecessary disconnect ---especially since you don't use these variables elsewhere. Please relocate them to be closer to their use sites. - Strings are sanitised - Errors are caught; with ~try/catch~ - Global variables are avoided, when possible. - ~const~ is preferred to ~let~; ~var~ should seldom be used. - Use ~var~ and ~function~ when you want definitions hoisted to the top of their enclosing scope. - ~===~ is preferred to ~==~. - Use default arguments instead of short-circuiting or conditionals - ~f (x) { x = x || defaultValue; ⋯ }~ ≡ ~f (x = defaultValue) { ⋯ }~. - Named parameters can also be optional, with default values: ~f(obj) { let prop = obj.prop || defaultProp; ⋯}~ ≣ ~f ({prop = defaultProp}) { ⋯ }~ - Unless you really need an array, handled an indefinite number of arguments using rest parameters: ~function f(...args) {⋯ // use ‘args’ as an array}~ can be invoked ~f(x₁, x₂, …)~ _without_ array brackets; or as ~f(...arr)~ if you have an array in-hand. - Function arguments: 3 or fewer ideally - If you need to declare an argument but are not using it, prefix it's name with an underscore. - Encapsulate conditionals in a separate _well-named_ function, if possible - Avoid negative conditionals; e.g., by making use of well-choosen names. - Use Demorgan's rules: ~!x && !y ≣ !(x || y)~ and ~!x || !y ≣ !(x && y)~. - Use ~try/catch~ with ~async/await~; or promises with both ~then~ and ~catch~. - Don't ignore rejected promises, log it to external logging service - Related chunks of code are clearly demarcated. - If an anonymous function is too long, more than 2 lines, give it a name: E.g., in JS, ~arr.map(x => ...) ≣ arr.map( function doingSomeComplexStuff(x) { return ...} )~. The name aids in communicating the intent, and is useful for debugging. - [ES6] Braces are used for block scope, and not simulated using IIFEs. - Avoid explicit newlines with ~+ "\n" +~ in-favour of Template Literals, which preserve line breaks. - Use Destructuring instead of explicit projections; aids in readability. - Note∶ ~let y = x.y~ ≡ ~let {y} = x~ only holds when ~x~ is not ~null~ (and so when ~x~ is not a expression involving ~?.~). #+html: </details>
9. Web-Development
First, let's get some useful Cheat Sheets…
;; Get the repos locally, and use: M-x my/cheatsheet to view the pretty HTML sheets. (mapcar #'my/cheatsheet '("JavaScript" "Vue" "AngularJS"))
9.1. Quickly produce HTML from CSS-like selectors
Emmet-mode (outside of Emacs, this is known as Emmet and Zen Coding), is a powerful abbreviation engine that expands CSS selectors into HTML code. It's a neat way to write markup quickly in Emacs.
Watch this demo video. Or here's an example with filler text:
#page>.logo+ul#navigation>li*5>a>lorem3
expands into
<div id="page"> <div class="logo"></div> <ul id="navigation"> <li><a href="">Justo eget magna!</a></li> <li><a href="">Mattis pellentesque id!</a></li> <li><a href="">Turpis massa tincidunt.</a></li> <li><a href="">Sit amet, porttitor.</a></li> <li><a href="">Feugiat nisl pretium.</a></li> </ul> </div>
Likewise, we can make 5 links next to 5 input boxes with:
(a[href=www.google.ca]{Click me!}+label{Name:}>input[value="first name"])*5
.
Anyhow, here's my setup:
;; USAGE: Place point in an emmet snippet and press C-j to expand it to appropriate tag structure; ;; e.g., #q.x>p C-j. Alternatively, press C-j then start typing an emmet snippet to see it preview live. ;; [C-j is just M-x emmet-expand-line] ;; (use-package emmet-mode ;; C-j ! RET === Makes an entire HTML template for you. :hook (web-mode . emmet-mode)) ;; ;; Please show me an HTML expansion preview as I type (setq emmet-preview-default t) ;; Press C-j then start typing; e.g., C-j #q.x.y>p>b RET ;; ;; After expanding, positioned the cursor between first empty quotes. ;; The preview can help with tricky CSS precedence rules; e.g., C-j gives the same thing for: a>b+c>d == a>(b+(c>d)) (setq emmet-move-cursor-between-quotes t) ;; E.g., C-j #q[name] RET ;; (add-hook 'sgml-mode-hook 'emmet-mode) ;; Auto-start on any markup modes ;; (add-hook 'css-mode-hook 'emmet-mode) ;; enable Emmet's css abbreviation.
Notable HTML key bindings (including [[https://www.gnu.org/software/emacs/manual/html_node/emacs/HTML-Mode.html] [built-ins]])
- Wrap with Abbreviation / C-c C-c w
- Select a region, then enter an abbreviation.
- For example, select the phrase
Hello, World!
thenC-c C-c w q>b
to place that phrase in a quote, and make it bold.
- For example, select the phrase
- Go to Edit Point / C-M-{Leftarrow, Rightarrow}
- to move between editable-tags/empty-attributes. (Demo)
- C-c TAB
- Cheaply preview the HTML file by toggling visibility of tags.
- C-c /, C-c C-e
- Insert a close tag for the innermost unterminated tag (sgml-close-tag). If called within a tag or a comment, close it instead of inserting a close tag.
- C-c ? tag RET
- Display a description of the meaning of tag tag (sgml-tag-help). If the argument tag is empty, describe the tag at point. :fire:
- C-c C-d
- Delete the tag at or after point, and delete the matching tag too (sgml-delete-tag). If the tag at or after point is an opening tag, delete the closing tag too; if it is a closing tag, delete the opening tag too.
- C-c C-f/b
- More 'f'orward or 'b'ackward one tag element. —Nice way to quicly check that your tags are balanced as you think they are.
- C-c C-a
- Interactively insert attribute values for the current tag. :fire:
- Super helpful when you don't remember the possible attributes of a tag, or their possible values.
- Interactively specify a tag and its attributes!
Notable Emmet Features & Examples
Example that includes Ids, classes, children, sibling selectors, {content}, 10 random lorem text, parenthesis for grouping; as well as attributes with and without values.
ParentTag#Id.Class1.Class2[Attribute1 Attribute2]>ChildTag{hiya there}>lorem10+(GrandChild1>GreatGrandChild11)+GrandChild2#Id2[attribute=value]
- Nesting (>) can be 'undone' with "climb-up"(^):
a>b>c>d^^e
==a>(b>c>d)+e
- Multiplication is repeated addition:
X*3>Y
==(X>Y)+(X>Y)+(X>Y)
- In general,
loremN
produces \(N\) many random filler text. E.g., make a dummy list:ul>li*3>lorem3
Other notable snippets include
a#neato.x.y.z, input[hola=value], img, p, table+, ul+, ol+
.
- There are also CSS abbreviations; but that's a story for another time.
- This cheat sheet has all the supported abbreviations, with expansions.
- ID and CLASS attributes:
div#page.section.main
div
tag name can be omitted when writing element starting from ID or CLASS:#content>.section
is the same asdiv#content>div.section
.
- Custom attributes:
div[title], a[title="Hello world" rel], td[colspan=2]
- Element multiplication:
li*5
will output<li>
tag five times. - Item numbering with the
$
character:li.item$*3
will output<li>
tag three times, replacing$
character with item number. - Multiple
$
characters in a row are used as zero padding, i.e.:li.item$$$
→li.item001
- Abbreviation groups with unlimited nesting:
div#page>(div#header>ul#nav>li*4>a)+(div#page>(h1>span)+p*2)+div#footer
- You can literally write a full document markup with just a single line.
- Text support:
p>{Click }+a{here}+{ to continue}
.
Good tutorials:
For more on writing HTML within Emacs, see this ~2min yet very informative video by Emacs Rocks!
Let's add a new snippet so that: angular C-j C-c C-v
shows us an interactive web
application.
(cl-defun my/add-emmet-snippet (abbreviation expansion) "Add ABBREVIATION as a snippet in `emmet-mode' to be EXPANSION. Both arguments are strings." (add-hook 'emmet-mode-hook ;; [Should this be added to “emmet-snippets” variable instead?] `(lambda () (puthash ,abbreviation ,expansion emmet-tag-snippets-table)))) (setq emmet-mode-hook nil) (my/add-emmet-snippet "vue" "<!doctype html> <html lang=\"en\"> <head> <title>Salamun Alaykum, world!</title> <script src=\"https://unpkg.com/vue@3\"></script> <!-- <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\" /> --> <style type=\"text/css\"> input, #reply { color: darkcyan; font-size: 14pt } </style> </head> <body> <div id=\"Hola\"> <h1>Number Guessing Game</h1> <input type=\"number\" v-model=\"guess\" style=\"width: 25%;\" v-bind:placeholder=`${prompt}`> <button v-on:click=\"go(guess)\">Learn Something!</button> <div id=\"reply\"> {{reply(guess)}} </div> </div> <!-- <script src=\"myscripts.js\"></script> --> <script type=\"text/javascript\"> let myApp = Vue.createApp({ data() { return { guess: null , prompt: \"Enter a guess between 0 and 100\" , secret: Math.floor(Math.random() * 100) } }, methods: { reply(gs) { return gs == this.secret ? \"You win!\" : (gs < this.secret ? \"Too low\" : \"Too high\"); } , go(number) { window.location.href = \"https://www.wolframalpha.com/input?i=\" + number } } }).mount('#Hola') </script> </body> </html>") (my/add-emmet-snippet "angular" "<!doctype html> <html lang=\"en\" ng-app=\"Hola\"> <head> <title>Salamun Alaykum, world!</title> <script src=\"https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js\"></script> <!-- <script src=\"myscripts.js\"></script> --> <script type=\"text/javascript\"> angular.module(\"Hola\", []) .controller(\"prompt\", ($scope, $window) => { $scope.prompt = \"Enter a guess between 0 and 100\" $scope.secret = Math.floor(Math.random() * 100) $scope.reply = gs => gs == $scope.secret ? \"You win!\" : (gs < $scope.secret ? \"Too low\" : \"Too high\") $scope.go = number => { $window.location.href = \"https://www.wolframalpha.com/input?i=\" + number } }) </script> <!-- <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\" /> --> <style type=\"text/css\"> input, #reply { color: darkcyan; font-size: 14pt } </style> </head> <body> <div ng-controller=\"prompt\"> <h1>Number Guessing Game</h1> <input type=\"number\" ng-model=\"guess\" style=\"width: 25%;\" placeholder=\"{{prompt}}\"> <button ng-click=\"go(guess)\">Learn Something!</button> <div id=\"reply\"> {{reply(guess)}} </div> </div> </body> </html>")
A non-blocking alternative to the built-in alert()
:
;; A way to show results of trying things out ---when not using a reactive framework. (my/add-emmet-snippet "message" " // Append “text” node to the end of tag with “id”. // Example: <button onclick=\"message(\"myID\", \"Hello!\")\"> Speak! </button> function message(id, text = \"Hello, world\") { const tag = document.createElement(\"p\") // <p></p> const textNode = document.createTextNode(text) tag.appendChild(textNode); // <p>Hello, world</p> const element = document.getElementById(id); element.appendChild(tag); }")
A snippet for a form with (1) automatic alignment, and (2) submission handled by local JavaScript.
(my/add-emmet-snippet "form" "<h1> <a href=\"https://www.quackit.com/css/grid/tutorial/form_layout_with_auto_placement.cfm\"> Automatically aligned form items</a> </h1> <form name=\"hola\" onsubmit=\"go(hola.elements);\"> <label>Name</label> <input name=\"name\" type=\"text\" required/> <label>Comments</label> <textarea name=\"comments\" maxlength=\"500\"></textarea> <input type=\"submit\"/> </form> <!-- <script src=\"myscripts.js\"></script> --> <script> let go = form => { alert(`${form.name.value}: “${form.comments.value}”`) } </script> <!-- <link rel=\"stylesheet\" type=\"text/css\" href=\"styles.css\" /> --> <style> form { /* We want the inputs&labels to be thought of as rows in a grid*/ display: grid; grid-auto-flow: row; /* Each row has 2 columns. */ grid-template-columns: [mylabels] auto [myinputs] 1fr; grid-gap: .8em; /* Distance between form elements */ background: beige; padding: 1.2em; } /* Let's attach column names to elements */ form > label { grid-column: mylabels; grid-row: auto; } form > input, form > textarea { grid-column: myinputs; grid-row: auto; } input, textarea { color: darkcyan; font-size: 14pt } </style>")
9.2. LSP for HTML + CSS
Notes: I've tried LSP for HTML but I didn't find it provided much, and even worse, it seemed to ignore the useful information I get from Flycheck!
- When I accidentally duplicate Ids, or miss important tag attributes, Flycheck
(C-c !)
reminds of these things! :-) - This is also LSP for Emmet, which let's you expand any text you write as if it
were an emmet snippet such that you can
TAB
to the various editable regions in the expansion to fill them out. E.g.,ul>li*5 RET
produces a yas-snippet which you TAB through to fill out.- If you don't want some text to be treated as a snippet, press
C-g
. - As above, this ruins flycheck support and highlighting —requiring
web-mode
to get some highlighting.
- If you don't want some text to be treated as a snippet, press
However, LSP for CSS is indispensable!
- It provides auto-completion for properties, and if you pause then a tool tip with useful explanations of properties. :fire:
- Moreover, if you press
ENTER
on a completion candidate, then you get to select a possible value from another list! - When I mouse hover over a property, I get a 1-line tooltip describing the property.
- No need to remember which properties are possible, what they do, and their possible values! Just start typing and see what pops-up!
M-x lsp-install-server RET css-ls
;; When I accidentally duplicate a property in a rule, please report that as an error. (setq lsp-css-lint-duplicate-properties "error") ;; If I accidentally enter an unknown property (e.g., writing Canadian “colour” instead of American “color”), ;; then I'll be notified with an error notice. (setq lsp-css-lint-unknown-properties "error") (use-package lsp-mode :hook ;; Every programming mode should enter & start LSP, with which-key support (css-mode . lsp-mode) ;; Enter LSP mode (css-mode . lsp)) ;; Start LSP server
9.2.1. CSS Property Argument Information in the Echo Area
LSP for CSS shows me a tooltip with a description and the argument syntax of a property; but that's only when entering a new property. What if I'm updating a property; or just browsing a property and want some information on the arguments list? css-eldoc to the rescue.
- Eldoc-mode is a minor-mode which shows you, in the echo area, the argument list of the function call you are currently writing.
;; [USAGE] In a CSS file, place cursor anywhere after the colon (but before ‘;’) ;; in “columns: 0ch;” or in “columns: ” and look at the echo area for how ;; arguments to this property should look like. (use-package css-eldoc :init (progn (require 'css-eldoc) (turn-on-css-eldoc)))
;; [Possibly useful snippet to keep around.] ;; Change priority of a server; useful when multiple servers are running for a buffer. ;; (setf (lsp--client-priority (gethash 'emmet-ls lsp-clients)) 4)
9.3. Show me HTML+CSS Changes Live as I Type!
(use-package impatient-mode) (use-package web-mode :init (add-to-list 'auto-mode-alist '("\\.html?\\'" . web-mode))) ;; C-c C-v: Browse buffer within external browser. ;; C-u C-c C-v: Ensure impatient-mode is enabled for current buffer and browse it WITHIN Emacs. ;; [xwidget-webkit has some bugs; e.g., sometimes buttons that should redirect don't do anything.] ;; [The “C-u” option is useful when I want to “see” the resulting HTML change as I type; e.g., new content or styling.] ;; [Note the “angular” snippet above works beautifully /within/ Emacs; use “b/f” to move backward/forward in the browser.] (bind-key "C-c C-v" (lambda (open-within-emacs) (interactive "P") (if (not open-within-emacs) (browse-url-of-buffer (current-buffer)) (unless (process-status "httpd") (httpd-start)) (unless impatient-mode (impatient-mode)) (let ((browser (car (--filter (s-starts-with? "*xwidget" (buffer-name it)) (buffer-list)))) (file (buffer-name))) (when browser (switch-to-buffer browser) (let (kill-buffer-query-functions) (kill-buffer))) (split-window-below) (other-window -1) (xwidget-webkit-browse-url (concat "http://localhost:8080/imp/live/" file)) (preview-it-mode -1) ;; Looks poor; and I don't need it when writing HTML. (other-window -1)))) 'web-mode-map)
(bind-key "M-q" #'sgml-pretty-print 'web-mode-map)
9.4. Eldoc for Lisp and Haskell —documentation in the mini-buffer
In emacs-lisp-mode
we can enable eldoc-mode
—“Elisp Live Documentation”— to
display information about a function or a variable in the echo area. Likewise
for Haskell.
(use-package eldoc :diminish eldoc-mode :hook (emacs-lisp-mode . turn-on-eldoc-mode) (lisp-interaction-mode . turn-on-eldoc-mode) (haskell-mode . turn-on-haskell-doc-mode) (haskell-mode . turn-on-haskell-indent)) ;; Slightly shorten eldoc display delay. (setq eldoc-idle-delay 0.4) ;; Default 0.5
The less casual Haskeller would likely want to use intero to obtain more support; e.g., obtain suggestions from GHC about redundant imports or type signatures.
9.5. Modern Browsing within Emacs Disabled
# https://github.com/d12frosted/homebrew-emacs-plus $ brew tap d12frosted/emacs-plus $ brew install emacs-plus@29 --with-xwidgets $ /usr/local/Cellar/emacs-plus@29/29.0.50/bin/emacs-29.0.50 & # In ~/.bashrc, put the following at the end: alias emacs="/usr/local/Cellar/emacs-plus@29/29.0.50/bin/emacs-29.0.50"
I like using Chrome —I like the integration of all things Google.
(cl-defun internet (&optional (url (concat "https://www." (read-string "https://www.")))) "Browse to URL using `xwidget-webkit-browse-url'; see also `browse-url'." (interactive) (delete-other-windows) (split-window-right) (xwidget-webkit-browse-url url)) (my/defhydra "C-c p" "Emacs Browser" gamepad :Internet ("m" (internet "https://mail.google.com/mail/u/0/#inbox") "gMail" :exit t) ("c" (internet "https://calendar.google.com/calendar/u/0/r") "gCalendar" :exit t) ("e" (internet "https://www.reddit.com/r/emacs/") "Emacs Forum" :exit t) ("b" (internet) "Browse" :exit t))
9.6. VueJS
(use-package vue-mode)
10. Lisp Helpers / Kill all buffers that are not associated with a file
(cl-defun my/clean-buffers () "Kill all buffers that are not associated with a file. By convention, such files are named in *earmuffs* style." (interactive) (mapcar #'kill-buffer (--filter (s-matches? "\\*.*\\*" it) (mapcar #'buffer-name (buffer-list)))))
11. Toggles Hydra Not_yet_tangled
(my/defhydra "C-c t" "Toggles" toggle-on :Theme ("t t" my/toggle-theme "Theme") ("t s" (thread-last (all-completions "doom" (custom-available-themes)) (completing-read "Load custom theme: ") intern load-theme) "Select Theme") :UI ("i" display-fill-column-indicator-mode :toggle t) ("f" my/toggle-font "font") ("F" writeroom-mode "Focused Work!" :toggle t) ("d" treemacs "directory finder" :toggle t) ("n" display-line-numbers-mode "line number" :toggle t) ("u f" (setq frame-title-format (completing-read "New frame title: " nil)) "New frame title") ;; Useful for random screenshots :Possibly_in_the_way ("e" electric-pair-mode "electric pair" :toggle t) ("c" flyspell-mode "spell check" :toggle t) ("s" prettify-symbols-mode "pretty symbol" :toggle t) ("w" whitespace-cleanup "Clean up whitespace on save" :toggle t) ;; ("a" global-aggressive-indent-mode "aggressive indent" :toggle t) ;; ("d" global-hungry-delete-mode "hungry delete" :toggle t) :Modeline ("m d" doom-modeline-mode "modern mode-line" :toggle t) ("m b" display-battery-mode "battery" :toggle t) ("m t" display-time-mode "time" :toggle t) ("m w" which-function-mode "which function" :toggle t) :Highlight ("h l" global-hl-line-mode "line" :toggle t) ("h p" show-paren-mode "paren" :toggle t) ;; ("h s" symbol-overlay-mode "symbol" :toggle t) ;; ("h r" rainbow-mode "rainbow" :toggle t) ;; ("h w" (setq-default show-trailing-whitespace (not show-trailing-whitespace)) ;; "whitespace" :toggle show-trailing-whitespace) ;; ("h d" rainbow-delimiters-mode "delimiter" :toggle t) ("h i" highlight-indent-guides-mode "indent" :toggle t) ("h t" global-hl-todo-mode "todo" :toggle t) ;; ("x" highlight-sexp-mode "sexp" :toggle t) ;; ("t" hl-todo-mode "todo" :toggle t) :Program ("f" flycheck-mode "flycheck" :toggle t) ;; ("F" flymake-mode "flymake" :toggle t) ("o" origami-mode "folding" :toggle t) ;; ("O" hs-minor-mode "hideshow" :toggle t) ("W" subword-mode "subword" :toggle t) ("E" toggle-debug-on-error "debug on error" :toggle (default-value 'debug-on-error)) ("Q" toggle-debug-on-quit "debug on quit" :toggle (default-value 'debug-on-quit)) ;; ("v" global-diff-hl-mode "gutter" :toggle t) ;; ("V" diff-hl-flydiff-mode "live gutter" :toggle t) ;; ("M" diff-hl-margin-mode "margin gutter" :toggle t) ;; ("D" diff-hl-dired-mode "dired gutter" :toggle t) ("P" (if (profiler-running-p) (progn (profiler-report) (profiler-stop)) (profiler-start 'cpu+mem)) "Profiler start / report" :exit (profiler-running-p))) ;; Places single frame in centre of screen; hides many UI things (e.g., mode line). ;; Nice for focused / immersive work. ;; ;; When in writeroom, let's also dim the font color of text in surrounding ;; paragraphs / code blocks. (use-package writeroom-mode :config (use-package focus) :hook (writeroom-mode . (lambda () (focus-mode 'toggle))))
12. Lost Souls Outdated_Documentation Not_yet_tangled
12.1. Note: M-S-SPC is for my personal servers dashboard.
;; Note: M-S-SPC is for my personal servers dashboard. (global-set-key (kbd "M-SPC") (lambda () (interactive) (setq org-agenda-files (list org-default-notes-file)) (org-agenda nil "a") (delete-other-windows) (beginning-of-buffer))) (use-package ace-jump-mode ;; Already installed above, somewhere. :config (bind-key* "C-c SPC" 'ace-jump-mode))
12.2. Zoom
;; An automatic window-resizing mechanism. ;; A “calmer” alternative to golden-ratio. ;; https://github.com/cyrus-and/zoom (use-package zoom :diminish :config (zoom-mode t))
12.3. Ibuffer
;; Let's use an improved buffer list. (use-package ibuffer ;; This is built-into Emacs. :bind ("C-x C-b" . ibuffer)) ;; It uses similar commands as does dired; e.g., ;; / . org ;; This filters (“/”) the list with extensions (“.”) being “org”. (use-package ibuffer-vc :hook (ibuffer . (lambda () (ibuffer-vc-set-filter-groups-by-vc-root) (unless (eq ibuffer-sorting-mode 'alphabetic) (ibuffer-do-sort-by-alphabetic)))) :custom (ibuffer-formats '((mark modified read-only " " (name 18 18 :left :elide) " " (size 9 -1 :right) " " (mode 16 16 :left :elide) " " (vc-status 16 16 :left) " " (vc-relative-file)))))
- Tips for using Emacs Ibuffer
- (~10 minute video) Using Emacs - 34 - ibuffer and emmet - C'est la Z
- (~10 minute video) Emacs: introduction to IBUFFER - YouTube
12.4. find function at point
Friendly reminder that M-.
takes you to the definition, and M-,
takes you back
to where you originally where.
12.5. “C-x 2” and “C-x 3” now create a new window horizontally/vertically and send cursor there
;; When we split open a new window, we usually want to jump to the new window. (advice-add #'split-window-below :after (lambda (&rest _) (other-window 1))) (advice-add #'split-window-right :after (lambda (&rest _) (other-window 1)))
12.6. Semantic Change
Using ⌘-i and ⌘-o we can quickly, for example, delete a string or its contents; or delete a {}-block or just its contents; or delete a ()-argument list or just its contents, etc.
change-inner
gives you vim's ci
command:
change-inner {
⇒ Delete all text starting from the first ‘{’ delimiter to the next one; but keep the delimiters.change-outer {
⇒ As above, but also delete the delimiters.
(use-package change-inner :diminish :bind (("s-i" . #'change-inner) ("s-o" . #'change-outer)))
12.7. Drag Stuff Disabled
;; Move current word ←/→, or current line ↑/↓. ;; Todo: Compare with org-metaup and org-metadown... (use-package drag-stuff :diminish :config (cl-loop for (key . action) in '(("<M-down>" . drag-stuff-down) ("<M-up>" . drag-stuff-up) ("<M-right>" . drag-stuff-right) ("<M-left>" . drag-stuff-left)) do (bind-key key action org-mode-map)) (drag-stuff-global-mode 1))
Ruins Org-mode's M-↑/↓ for moving entire sections around.
12.8. Indentation Guide
The following is also “OK” in Org-mode ;-)
;; Add a visual indent guide (use-package highlight-indent-guides :hook (prog-mode . highlight-indent-guides-mode) :custom (highlight-indent-guides-method 'character) (highlight-indent-guides-character ?|) (highlight-indent-guides-responsive 'stack))
12.9. Commenting
Let's get some nifty commenting features —the link has nice usage gifs.
repeatedly does (1) comments current line, (2) inserts a comment at the end of the current line, and (3) deletes an existing end-of-line comment.
indents the current enf-of-line comment with any above it.
For use with Org-mode, it's best to use org-edit-src-code —which I've bound to .
(use-package comment-dwim-2 :bind ("M-;" . comment-dwim-2)) ;; Not ideal: M-; comments a parent Org heading and not the current line. ;; (define-key org-mode-map (kbd "M-;") 'org-comment-dwim-2)
12.10. Having a workspace manager in Emacs
I've loved using XMonad as a window tiling manager. I've enjoyed the ability to segregate my tasks according to what ‘project’ I'm working on; such as research, marking, Emacs play, etc. With perspective, I can do the same thing :-)
That is, I can have a million buffers, but only those that belong to a workspace will be visible when I'm switching between buffers, for example. ( The awesome-tab and centaur-tab, mentioned elsewhere here, can be used to achieve the same thing by ‘grouping buffers together’. )
(use-package perspective :defer t :config ;; Activate it. (persp-mode) ;; In the modeline, tell me which workspace I'm in. (persp-turn-on-modestring))
All commands are prefixed by C-x x
; main commands:
s, n/→, p/←
- ‘S’elect a workspace to go to or create it, or go to ‘n’ext one, or go to ‘p’revious one.
c
- Query a perspective to kill.
r
- Rename a perspective.
A
- Add buffer to current perspective & remove it from all others.
As always, since we've installed which-key
, it suffices to press C-x x
then look
at the resulting menu 😃
12.11. Editor Documentation with Contextual Information
Emacs is an extensible self-documenting editor!
Let's use a helpful Emacs documentation system that cleanly shows a lot of
contextual information —then let's extend that to work as we want it to:
C-h o
to describe the symbol at point.
(use-package helpful :defer t) (defun my/describe-symbol (symbol) "A “C-h o” replacement using “helpful”: If there's a thing at point, offer that as default search item. If a prefix is provided, i.e., “C-u C-h o” then the built-in “describe-symbol” command is used. ⇨ Pretty docstrings, with links and highlighting. ⇨ Source code of symbol. ⇨ Callers of function symbol. ⇨ Key bindings for function symbol. ⇨ Aliases. ⇨ Options to enable tracing, dissable, and forget/unbind the symbol! " (interactive "p") (let* ((thing (symbol-at-point)) (val (completing-read (format "Describe symbol (default %s): " thing) (vconcat (list thing) obarray) (lambda (vv) (cl-some (lambda (x) (funcall (nth 1 x) vv)) describe-symbol-backends)) t nil nil)) (it (intern val))) (cond (current-prefix-arg (funcall #'describe-symbol it)) ((or (functionp it) (macrop it) (commandp it)) (helpful-callable it)) (t (helpful-symbol it))))) ;; Keybindings. (global-set-key (kbd "C-h o") #'my/describe-symbol) (global-set-key (kbd "C-h k") #'helpful-key)
I like helpful and wanted it to have the same behaviour as C-h o
, which
helpful-at-point
does not achieve. The incantation above makes C-h o
use helpful
in that if the cursor is on a symbol, then it is offered to the user as a
default search item for help, otherwise a plain search box for help
appears. Using a universal argument lets us drop to the built-in help command.
12.12. README
—From init.org
to init.el
Rather than manually extracting the Lisp code from this literate document each
time we alter it, let's instead add a ‘hook’ —a method that is invoked on a
particular event, in this case when we save the file. More precisely, in this
case, C-x C-s
is a normal save whereas C-u C-x C-s
is a save after forming
init.elc
and README.md
.
- The
my/make-init-el-and-README
function
We ‘hook on’ the following function to the usual save method that is associated with this file only.
(defun my/make-init-el-and-README () "Tangle an el and a github README from my init.org." (interactive "P") ;; Places value of universal argument into: current-prefix-arg (when current-prefix-arg (let* ((time (current-time)) (_date (format-time-string "_%Y-%m-%d")) (.emacs "~/.emacs") (.emacs.el "~/.emacs.el")) ;; Make README.org (save-excursion (org-babel-goto-named-src-block "make-readme") ;; See next subsubsection. (org-babel-execute-src-block)) ;; remove any other initialisation file candidates (ignore-errors (f-move .emacs (concat .emacs _date)) (f-move .emacs.el (concat .emacs.el _date))) ;; Make init.el (org-babel-tangle) ;; (byte-compile-file "~/.emacs.d/init.el") (load-file "~/.emacs.d/init.el") ;; Acknowledgement (message "Tangled, compiled, and loaded init.el; and made README.md … %.06f seconds" (float-time (time-since time)))))) (add-hook 'after-save-hook 'my/make-init-el-and-README nil 'local-to-this-file-please)
- The Org-block named
make-readme
Where the following block has
#+NAME: make-readme
before it. This source block generates theREADME
for the associated Github repository.(save-buffer) (with-temp-buffer (insert "#+EXPORT_FILE_NAME: README.org # Logos and birthday present painting #+HTML:" (s-collapse-whitespace (concat " <p align=\"center\"> <img src=\"images/emacs-logo.png\" width=150 height=150/> </p> <p align=\"center\"> <a href=\"https://www.gnu.org/software/emacs/\"> <img src=\"https://img.shields.io/badge/GNU%20Emacs-" emacs-version "-b48ead.svg?style=plastic\"/></a> <a href=\"https://orgmode.org/\"><img src=\"https://img.shields.io/badge/org--mode-" org-version "-489a9f.svg?style=plastic\"/></a> </p> <p align=\"center\"> <img src=\"images/emacs-birthday-present.png\" width=250 height=250/> </p> ")) ;; My Literate Setup; need the empty new lines for the export " I enjoy reading others' /literate/ configuration files and incorporating what I learn into my own. The result is a sufficiently well-documented and accessible read that yields a stylish and functional system (•̀ᴗ•́)و This ~README.org~ has been automatically generated from my configuration and its contents below are accessible in (outdated) blog format, with /colour/, or as colourful PDF, [[https://alhassy.github.io/init/][here]]. Enjoy :smile: #+INCLUDE: init.org ") ;; No code execution on export ;; ⟪ For a particular block, we use “:eval never-export”. ⟫ (let ((org-export-use-babel nil)) (org-mode) (org-org-export-to-org)))
Alternatively, evaluate the above source block with
C-c C-c
to produce aREADME
file.For the ‘badges’, see https://shields.io/. The syntax above is structured:
https://img.shields.io/badge/<LABEL>-<MESSAGE>-<COLOR>.svg
- ‘Table of Contents’ for Org vs. Github
The above mentioned package toc-org, which creates an up-to-date table of contents in an org file, at any heading tagged
:TOC:
. It's useful primarily for README files on Github. There is also org-make-toc, which is more flexible: The former provides only a top-level TOC; whereas this package allows TOCs at the sibling level, say, to produce a TOC of only the subsections of a particular heading, and other TOC features. Unlike toc-org, org-make-toc uses property drawers to designate TOC matter.(use-package toc-org ;; Automatically update toc when saving an Org file. :hook (org-mode . toc-org-mode) ;; Use both “:ignore_N:” and ":export_N:” to exlude headings from the TOC. :custom (toc-org-noexport-regexp "\\(^*+\\)\s+.*:\\(ignore\\|noexport\\)\\([@_][0-9]\\)?:\\($\\|[^ ]*?:$\\)"))
However, toc-org produces broken links for numbered sections. That is, if we use
#+OPTIONS: num:t
then a section, say** =~/.emacs= vs. =init.org=
as the first subheading of the third heading, then it renders with the text preceeded by3.1
. On the left-most part of the heading, Github provides a a link option; clicking provides a link to this exact location in the README, changing the current URL to something likehttps://github.com/alhassy/emacs.d#31-emacs-vs-initorg
. Now, toc-org produces Github-style anchors from Org headings, but does not account for numbers, and so gives ushttps://github.com/alhassy/emacs.d#emacs-vs-initorg
, which is so close but missing the translated number,31
.I've experimented with using toc-org links using org-style, instead of the default Github style, but it seems that the org-style completely breaks rendering the resulting readme. Likewise, it seems that headings that are links break the TOC link; whence my section on the Reveal slide-deck system has a broken link to it. Perhaps org-make-toc solves these issues —something to look into.
I'm not sure how I feel about actually having the Github-serving TOC in my source file. It's nice to have around, from an essay-perspecive, but it breaks HTML export since its links are not well-behaved; e.g.,
:ignore:
-ed headlines appear in the toc, but do not link to any visible heading in the HTML; likewise, headings with URLS in their names break. As such, below I've developed a way to erase it altogether —alternatively, one could mark the toc as:noexport:
, but this would then, in my current approach, not result in a toc in the resulting README.(cl-defun my/org-replace-tree-contents (heading &key (with "") (offset 0)) "Replace the contents of org tree HEADING with WITH, starting at OFFSET. Clear a subtree leaving first 3 lines untouched ⇐ :offset 3 Deleting a tree & its contents ⇐ :offset -1, or any negative number. Do nothing to a tree of 123456789 lines ⇐ :offset 123456789 Precondition: offset < most-positive-fixnum; else we wrap to a negative number." (interactive) (save-excursion (beginning-of-buffer) (re-search-forward (format "^\\*+ %s" (regexp-quote heading))) ;; To avoid ‘forward-line’ from spilling onto other trees. (org-narrow-to-subtree) (org-mark-subtree) ;; The 1+ is to avoid the heading. (dotimes (_ (1+ offset)) (forward-line)) (delete-region (region-beginning) (region-end)) (insert with) (widen))) ;; Erase :TOC: body ---provided we're using toc-org. ;; (my/org-replace-tree-contents "Table of Contents")
- Alternate approaches to generating a README
Github supports several markup languages, one of which is Org-mode.
- It seems that Github uses org-ruby to convert org-mode to html.
- Here is a repo demonstrating how Github interprets Org-mode files.
- org-ruby supports inline
#+HTML
but not html blocks.
It seems coloured HTML does not render well:
(org-html-export-to-html) (shell-command "mv README.html README.md")
JavaScript supported display of web pages with:
#+INFOJS_OPT: view:info toc:t buttons:t
This looks nice for standalone pages, but doesn't incorporate nicely with github README.org.
Usually, Github readme files are in markdown, which we may obtain from an Org file with
M-x org-md-export-to-markdown
.[ ]
By default, this approach results in grey-coloured source blocks —eek![X]
It allows strategic placement of a table of contents.
Declare
#+options: toc:nil
at the top of the Org file, then have#+TOC: headlines 2
in a strategic position for a table of contents, say after a brief explanation of what the readme is for.[X]
It allows us to preview the readme locally before comitting, using grip.
;; grip looks for README.md (system-packages-ensure "grip") ;; Next: (async-shell-command "cd ~/.emacs.d/; grip")
We can approximate this behaviour for the other approaches:
- Export to markdown.
COMMENT
-out any:TOC:
-tagged sections —their links are not valid markdown links, since they don't refer to any markdown labels.- Rename the exported file to
README.md
. - Run
grip
.
12.13. Org-mode's <𝒳
Block Expansions
In org-mode we type <X TAB
to obtain environment templates, such as <s
for
source blocks or <q
for quote blocks. It seems recent changes to the org-mode
structure template expansion necessitate explicitly loading org-tempo
.
(require 'org-tempo)
To insert source blocks with the assistance of a pop-up: C-c C-v d
;-)
Perhaps more usefully, invoking within a source block splits it up into two
separate blocks! Moreover, if invoked on a selected region, it puts the region
into a new code block! Wow!
C-c C-,
refers toorg-insert-structure-template
, which provides non-source blocks, such as quote<q
, comment<C
, center<c
, notes<n
, examples<e
, and<l
and<h
and<a
for LaTeX and HTML and ASCII export blocks.<X
allows you to obtain the org-block assigned to shortcutX
.- The contents of comment blocks are ignored upon export.
C-c C-v C-d
andC-c C-v d
refer to theorg-babel-demarcate-block
, which provides source blocks.
We shall improve upon this system below using snippets. |
E.g., s_em TAB
to obtain an org-src block marked with emacs-lisp
as the
language. This saves us a few key strokes.
12.14. What's changed & who's to blame?
Let's have, in a fringe, an indicator for altered regions in a version controlled file. The symbols “+, =” appear in a fringe by default for alterations —we may change these if we like.
;; Hunk navigation and commiting. (use-package git-gutter :diminish :config (global-git-gutter-mode)) ;; Diff updates happen in real time according when user is idle.
Let's set a hydra so we can press C-x v n n p n
to move the next two
altered hunks, move back one, then move to the next. This saves me having
to supply the prefix C-x v
each time I navigate among my alterations.
At any point we may also press u 𝕩
to denote C-u ⟪prefix⟫ 𝕩
.
(defhydra hydra-version-control (global-map "C-x v") "Version control" ;; Syntax: (extension method description) ("n" git-gutter:next-hunk "Next hunk") ("p" git-gutter:previous-hunk "Previous hunk") ("d" git-gutter:popup-hunk "Show hunk diff") ("r" git-gutter:revert-hunk "Revert hunk\n") ("c" git-gutter:stage-hunk "Stage hunk") ("s" git-gutter:statistic "How many added & deleted lines"))
Commiting with C-x v c
let's us use C-c C-k
to cancel and C-c C-c
to
submit the given message; C-c C-a
to amend the previous commit.
Alternatively, we may use diff-hl:
;; Colour fringe to indicate alterations. ;; (use-package diff-hl) ;; (global-diff-hl-mode)
A few more helpful version control features:
;; Popup for who's to blame for alterations. (use-package git-messenger :custom ;; Always show who authored the commit and when. (git-messenger:show-detail t) ;; Message menu let's us use magit diff to see the commit change. (git-messenger:use-magit-popup t)) ;; View current file in browser on github. ;; More generic is “browse-at-remote”. (use-package github-browse-file :defer t) ;; Add these to the version control hydra. ;; (defhydra hydra-version-control (global-map "C-x v") ("b" git-messenger:popup-message "Who's to blame?") ;; C-u C-x b ╱ u b ∷ Also show who authored the change and when. ("g" github-browse-file-blame "Show file in browser in github") ("s" magit-status "Git status of current buffer"))
Perhaps C-x v b
will motivate smaller, frequent, commits.
Obtaining URL links to the current location of a file —URLs are added to the kill ring. Usefully, if git-timemachine-mode is active, the generated link points to the version of the file being visited.
(use-package git-link :defer t) (defhydra hydra-version-control (global-map "C-x v") ("l" git-link "Git URL for current location"))
Read here for more about version control in general.
12.15. Emacs keybindings for my browser Disabled
⟨ I was a bit too Emacs-happy at one-point; this' cool, but I rarely use it;
except C-x b
: A buffer approach is far superior to a tab-based one. ⟩
I've downloaded the Vimium extension for Google Chrome,
and have copy-pasted these Emacs key bindings into it.
Now C-h
in my browser shows which Emacs-like bindings
can be used to navigate my browser ^_^
12.16. Using Emacs in any text area on my OS Disabled
⟨ I was a bit too Emacs-happy at one-point; this' cool, but I rarely use it. ⟩
Using the Emacs-Anywhere tool, I can press Cmd Shift e
to have an Emacs frame
appear, produce text with Emacs editing capabilities, then C-x 5 0
to have the
resulting text dumped into the text area I was working in.
This way I can use Emacs literally anywhere for textual input!
For my Mac OSX:
(shell-command "curl -fsSL https://raw.github.com/zachcurry/emacs-anywhere/master/install | bash") (server-start)
The tools that use emacs-anywhere —such as my web browser— and emacs-anywhere itself need to be given sufficient OS permissions:
System Preferences → Security & Privacy → Accessibility
Then check the emacs-anywhere box from the following gui and provide a keyboard shortcut:
System Preferences → Keyboard → Shortcuts → Services
(•̀ᴗ•́)و
I always want to be in Org-mode and input unicode:
(add-hook 'ea-popup-hook (lambda (app-name window-title x y w h) (org-mode) (set-input-method "Agda")))
12.17. Reload buffer with f5
I do this so often it's not even funny.
(global-set-key [f5] '(lambda () (interactive) (revert-buffer nil t nil)))
In Mac OS, one uses Cmd-r
to reload a page and Emacs binds buffer reversion to Cmd-u
–in Emacs, Mac's Cmd
is referred to as the ‘super key’ and denoted s
.
Moreover, since I use Org-mode to generate code blocks and occasionally inspect them, it would be nice if they automatically reverted when they were regenerated –Emacs should also prompt me if I make any changes!
;; Auto update buffers that change on disk. ;; Will be prompted if there are changes that could be lost. (global-auto-revert-mode 1) ;; Auto refreshes every 2 seconds. Don’t forget to refresh the version control status as well. (setq auto-revert-interval 2 auto-revert-check-vc-info t global-auto-revert-non-file-buffers t auto-revert-verbose nil) ;; Don't show me the “ARev” marker in the mode line (diminish 'auto-revert-mode)
12.18. Kill to start of line
Dual to C-k
,
;; M-k kills to the left (global-set-key "\M-k" '(lambda () (interactive) (kill-line 0)) )
12.19. Killing buffers & windows: C-x k
has a family
Let's extend the standard C-x k
with prefix support, so that we can invoke
variations: Kill this buffer, kill other buffer, or kill all other buffers.
By default C-x k
prompts to select which buffer should be selected. I almost
always want to kill the current buffer, so let's not waste time making such a
tedious decision. Moreover, if I've killed a buffer, I usually also don't want
the residual window, so let's get rid of it.
(global-set-key (kbd "C-x k") (lambda (&optional prefix) "C-x k ⇒ Kill current buffer & window C-u C-x k ⇒ Kill OTHER window and its buffer C-u C-u C-x C-k ⇒ Kill all other buffers and windows Prompt only if there are unsaved changes." (interactive "P") (pcase (or (car prefix) 0) ;; C-x k ⇒ Kill current buffer & window (0 (kill-this-buffer) (unless (one-window-p) (delete-window))) ;; C-u C-x k ⇒ Kill OTHER window and its buffer (4 (other-window 1) (kill-this-buffer) (unless (one-window-p) (delete-window))) ;; C-u C-u C-x C-k ⇒ Kill all other buffers and windows (16 (mapc 'kill-buffer (delq (current-buffer) (buffer-list))) (delete-other-windows)))))
The incantation C-u C-x k
will reduce the noise of all the documentation buffers
I tend to consult.
12.20. Switching from 2 horizontal windows to 2 vertical windows
I often find myself switching from a horizontal view of two windows in Emacs to a
vertical view. This requires a variation of C-x 1 RET C-x 3 RET C-x o C-x b RET
.
Instead I now only need to type C-|
to make this switch.
(defun my/ensure-two-vertical-windows () "I used this method often when programming in Coq. When there are two vertical windows, this method ensures the left-most window contains the buffer with the cursour in it." (interactive) (let ((otherBuffer (buffer-name))) (other-window 1) ;; C-x 0 (delete-window) ;; C-x 0 (split-window-right) ;; C-x 3 (other-window 1) ;; C-x 0 (switch-to-buffer otherBuffer) ;; C-x b RET (other-window 1))) (global-set-key (kbd "C-|") 'my/ensure-two-vertical-windows)
12.21. Obtaining Values of #+KEYWORD
Annotations
Org-mode settings are, for the most part, in the form #+KEYWORD: VALUE
. Of notable interest
are the TITLE
and NAME
keywords. We use the following org-keywords
function to obtain
the values of arbitrary #+THIS : THAT
pairs, which may not necessarily be supported by native
Org-mode –we do so for the case, for example, of the CATEGORIES
and IMAGE
tags associated with an article.
;; Src: http://kitchingroup.cheme.cmu.edu/blog/2013/05/05/Getting-keyword-options-in-org-files/ (defun org-keywords () "Parse the buffer and return a cons list of (property . value) from lines like: #+PROPERTY: value" (org-element-map (org-element-parse-buffer 'element) 'keyword (lambda (keyword) (cons (org-element-property :key keyword) (org-element-property :value keyword))))) (defun org-keyword (KEYWORD) "Get the value of a KEYWORD in the form of #+KEYWORD: value" (cdr (assoc KEYWORD (org-keywords))))
Note that capitalisation in a ”#+KeyWord” is irrelevant.
See here on how to see the abstract syntax tree of an org file and how to manipulate it.
12.22. Publishing articles to my personal blog
I try to blog occasionally, so here's a helpful function to quickly publish the current article to my blog.
(define-key global-map "\C-cb" 'my/publish-to-blog) (cl-defun my/publish-to-blog (&optional (draft nil) (local nil)) " Using ‘AlBasmala’ setup to publish current article to my blog. Details of AlBasmala can be found here: https://alhassy.github.io/AlBasmala/ Locally: ~/alhassy.github.io/content/AlBasmala.org A ‘draft’ will be produced in about ~7 seconds, but does not re-produce a PDF and the article has a draft marker near the top. Otherwise, it will generally take ~30 seconds due to PDF production, which is normal. The default is not a draft and it takes ~20 seconds for the live github.io page to update. The ‘local’ optiona indicates whether the resulting article should be viewed using the local server or the live webpage. Live page is default. When ‘draft’ and ‘local’ are both set, the resulting page may momentarily show a page-not-found error, simply refresh. " (load-file "~/alhassy.github.io/content/AlBasmala.el") ;; --MOVE ME TO ALBASMALA-- ;; Sometimes the file I'm working with is not a .org file, so: (setq file.org (buffer-name)) (preview-article :draft draft) (unless draft (publish)) (let ((server (if local "http://localhost:4000/" "https://alhassy.github.io/"))) (async-shell-command (concat "open " server NAME "/") "*blog-post-in-browser*")) )
12.23. Jumping without hassle
(defun my/org-goto-line (line) "Go to the indicated line, unfolding the parent Org header. Implementation: Go to the line, then look at the 1st previous org header, now we can unfold it whence we do so, then we go back to the line we want to be at. " (interactive "nEnter line: ") (goto-line line) (org-previous-visible-heading 1) (org-cycle) (goto-line line))
12.24. The evils of this world
;; “PDF” stands for Portable Document Format, since you should be able to open ;; it anywhere. Disgustingly, fillable PDF's made with Adobe can only be ;; smoothly opened & printed in Adobe ---Chrome can open them, but not print ;; them. Unfortunately, various government & insurance forms are only provided in ;; this format. (system-packages-ensure "adobe-acrobat-reader")
12.25. TODO Hydra Timer
(setq org-clock-sound t) ;; Standard Emacs beep (my/defhydra "C-c x" "Time Tracking" clock-o ;; Org-Clock ---must be on an Org header;; but the timer works from anywhere :Tasks ("n" (my/org-journal-new-entry :work) "New") ("v" (progn (my/org-journal-new-entry :work) (revert-buffer t t) (org-journal-mode)) "View all") :Timer ("s" org-timer-start "Start") ("S" org-timer-stop "Stop") ("x" org-timer-set-timer "Set") ("p" org-timer "Print") :Org-Clock ("i" org-clock-in "in") ("o" org-clock-out "out") ("c" org-clock-cancel "cancel" :color pink :column "Do") ("d" org-clock-display "display") ("e" org-clock-modify-effort-estimate "effort") ("j" org-clock-goto "Jump to task") ;; Jump to the headline of the currently clocked in task. With a C-u prefix argument, select the target task from a list of recently clocked tasks. ("r" org-clock-report "Insert clocktable"))
12.26. Makes Org/Markdown previewabvle as we type!!! ♥
;; Shows up as a magnifying glass in doom-modeline. (use-package grip-mode) ;; :hook ((markdown-mode org-mode) . grip-mode) ;; Pretty annyoning actually; instead we should call it as needed. )
12.27. Draw pretty unicode tables in org-mode to_include
This turns the “—” and other ASCII for tables into ‘smooth’ lines ^_^
a | b |
---|---|
1 | 2 |
(quelpa '(org-pretty-table :repo "Fuco1/org-pretty-table" :fetcher github)) (add-hook 'org-mode-hook 'org-pretty-table-mode)
Being an ‘on the fly replacement mechanism’, we get that “C-u 80 -” also results in one smooth horizontal rule and vertical sequences of ‘|’ results in a smooth vertical line.
12.28. Cucumber
;; Emacs mode for editing Cucumber plain text stories ;; “.feature” files now open up with nice colouring. (use-package feature-mode) ;; ;; C-c ,g Go to step-definition under point (requires ruby_parser gem >= 3.14.2) ;; ;; ;; (use-package cucumber-goto-step)
Other packages to consider looking into include:
12.29. Syntax highlighting —numbers and escape characters
Lightweight syntax highlighting improvement for numbers and escape sequences (e.g. \n, \t) within quotes.
(use-package highlight-numbers :hook ((prog-mode) . highlight-numbers-mode)) ;; Not text-mode , look Bad (use-package highlight-escape-sequences :hook (prog-mode . hes-mode))
12.30. shell-command-and-run
(defalias 'my/shell-command-and-run 'shell-command-and-run) (defun shell-command-and-run (cmd name &rest more-commands) "Run shell command CMD (possibly opening a new repl/terminal) and then MORE-COMMANDS. When to use this function? Whenever you're finding yourself in the situation: (1) Open a terminal, (2) start an interactive repl, (3) rename the buffer name to be informative, (4) run some default/initial commands. See also the `term' function. CMD and NAME are strings; MORE-COMMANDS is an arbitrary number of strings. The name of this function does not contain my personal prefix ‘my’, since I'd like it to show up as a possible completion when I type ‘shell-command’. For example, (shell-command-and-run \"ghci\" \"Playing with Haskell\" \"let x = 4\" \":t x\") This results in an interactive shell buffer named “*Playing with Haskell*” with contents: GHCi, version 8.10.7: https://www.haskell.org/ghc/ :? for help Prelude> let x = 4 Prelude> :t x x :: Num p => p Prelude> ❙" (interactive) (let* ((default-directory "~/") (proc (get-buffer-process (ansi-term cmd name)))) (term-send-string proc (concat (s-join "\n" more-commands) "\n"))))
12.31. Keeping my system up to date
Let's ensure our system is always up to date.
(defun my/stay-up-to-date () "Ensure that OS and Emacs package listings are up to date. Takes ~5 seconds when everything is up to date." (async-shell-command "brew update && brew upgrade") (other-window 1) (rename-buffer "Keeping-system-up-to-date") (package-refresh-contents 'please-do-so-in-the-background) (message "Updated Emacs package manager.") (other-window 1)) (add-hook 'after-init-hook 'my/stay-up-to-date) ;; For now, doing this since I'm also calling my/stay-up-to-date with ;; after-init-hook which hides the startup message. (add-hook 'after-init-hook 'display-startup-echo-area-message)
12.32. Compile posterity
# Local Variables: # eval: (message "Load file specific stuffs here") # compile-command: (async-shell-command (concat "open " (org-latex-export-to-pdf))) # End:
Since nearly every file I work with is ─or can be coerced into being─ in org mode,
I usually have a section * footer
that contains something like the above.
Let's remove repeated matter.
;; Silently save before compiling. (setq compilation-ask-about-save nil) ;; Silently kill previous compilation process before starting a new one. (setq compilation-always-kill t) ;; Scroll as compilation output is procuded in *Compilation* buffer; e.g., pdflatex ;; Use 'first-error to stop scrolling on the first error encountered; otherwise ‘t’. (setq compilation-scroll-output 'first-error) ;; Don't stop on informaiton messages or warnings; only on errors. (setq compilation-skip-threshold 2)
;; My global compile command (setq compile-command '(async-shell-command (concat "open " (org-latex-export-to-pdf)))) ;; Bind ‘recompile’ to ‘C-c C-m’ ─“m” for “m”ake (global-set-key (kbd "C-c C-m") 'recompile) ;; Also a helpful quick f-key. (global-set-key (kbd "<f7>") 'recompile)
12.33. Let's jump to a current Chrome browser tab, or one from our Chrome history, from within Emacs.
- Browse your Chrome history with Helm.
- M-x helm-chrome-control displays chrome tabs and let you control them by pressing C-z: e.g., you can focus tab, close tab, copy the title & URL of the tab, etc.
;; M-x helm-chrome-history ;; [Your Chrome History SQLite database file: helm-chrome-history-file] (use-package helm-chrome-history) ;; M-x helm-chrome-control (use-package helm-chrome-control)
There is helm-taskswitch for switching between X-windows: Whereas ⌘-tab
gives a
list of most recently used program, helm-taskswitch
gives a completing read of
all OS windows. I'm using a MacOS right now, so X-windows are not the default
—but can be setup.
12.34. Get Shell history within Emacs via Completing Read with Helm
;; Usage: M-x helm-shell-history (use-package helm-shell-history :config (setq helm-shell-history-file "~/.zsh_history") (bind-key "M-r" #'helm-shell-history shell-mode-map))
12.35. Launch macOS apps with Helm
;; MacOS's default ⌘-SPC does not let us do either of the following scenarios: ;; Usage: M-x helm-osx-app RET preferences bat RET ⇒ See battery preferences settings ;; Another Usage: M-x helm-osx-app RET ⇒ See all apps, maybe we forgot about one of them from an install a long time ago, and open it ;; See https://www.alfredapp.com/ as an alternative (for non-Emacs users), which can do more. (use-package helm-osx-app) ;; For non-MacOS, we can use counsel-osx-app, whose name is misleading.
12.36. Use Org Mode links in other modes: Links can be opened and edited like in Org Mode.
;; E.g., in ELisp mode, the following is clickable and looks nice: Read the docs! ;; ;; In particular, when I tangle my init.org into a Lisp file, init.el, it has Org links ;; back to the original source section in Org, which I can then click to jump to, quickly. ;; (use-package orglink :config (global-orglink-mode) ;; Only enable this in Emacs Lisp mode, for now. (setq orglink-activate-in-modes '(emacs-lisp-mode)))
12.37. Let's make working with Emacs Lisp even better!
(use-package elisp-demos :config ;; Show demos when I do a `C-h o'. (advice-add 'helpful-update :after #'elisp-demos-advice-helpful-update) ;; Show demos in tooltips when I pause to select a completion, in Emacs Lisp mode. (advice-add 'describe-function-1 :after #'elisp-demos-advice-describe-function-1))
12.38. A butler for your buffers. Group buffers into workspaces with programmable rules, and easily switch to and manipulate them.
(use-package bufler :config (bind-key "C-x C-b" #'bufler-list)) ;; I still prefer “C-x b” to be “helm-mini”, since when looking for a buffer it also shows me recently visited files.
12.39. Let's try out this dope theme and this one too!
(use-package stimmung-themes :quelpa (stimmung-themes :fetcher github :repo "motform/stimmung-themes") :config (load-theme 'stimmung-themes-light)) (use-package shanty-themes) (load-theme 'shanty-themes-light) (setq-default cursor-type 'bar)
13. Conclusion —Why Configuration Files Should be Literate
A configuration file sets up various features for a tool —and serves as an essential learning point. In order to remember them, what they do, and possibly where you learned about them —which may include additional resources— it is pertinent to document such facts. Benefits of documentating features include:
- A list of the features with human readable names! —In case you forget what you invested time on!
- Personal documentation! —Reduce wasting time Googling things that you knew in the past!
- Convincing Need
Making notes with decriptive text, as suggested below, will make it clear whether you actually need the feature or “just threw it becuase it looks cool” —which leads to ‘init bankruptcy’.
Moreover, actually documenting a feature may make it more to recall that you have the feature and have notes for it.
Programs are meant to be read by humans and only incidentally for computers to execute. —Donald Knuth
Alongside a feature's installation, I've tried to provide the following:
- Why would I want this? Motivation!
- Example scenerios and use-cases.
- How do I actually use it? Super terse usage details to “get going”!
- Where is the offical documentation page, or repository? Discovarability!
- Comparisions: Are there other similar features, builtin or otherwise? How do they compare? Why have I decided for this one instead of another one?
- Additional comments and reminders related to the feature.
- E.g., why the feature is now disabled, ‘commented out’, when before it was useful.
Programs without documentation have little value; it's like a claim without evidence! —Me
Here are some benefits of having a tool's configurations written literately as an Org-mode file, then tangeling as appropriate.
Modularity! —or “In Praise of the Monolith”
It may not be feasible, or practical, to split a tool's configuration file into multiple file hierarchy. Yet, with Org-mode we may reify the hierarchical structure as ‘sections’ and have the resulting configuration read more like a novel, easily folding and navigating, between sections.
- Section headers provide organisation and they're collapsable.
Even if you can make multiple files, using one monolithic file allows:
- Really easy to quickly re-organise code!
- Use
w
to move content almost instanteously! - In contrast, it's harder to review an entire project, when it's in pieces.
- Use
- Many files requires coming up with descriptive file names; instead prefer descriptive org headings ^_^
- Easily navigatable hierarchy with a nested directory/org-heading structure.
- Have headings with an introducttory paragraph that explains the kind of features being considered —or, lazily, look at the outlined view of subheadings to see what's there.
- Easy search & review of features since they're in one file.
- Multiple files makes it harder to remember which features live where.
- One file is easy to distribute & share!
Many small files are great for collobaration —there'll likely be less merge conflicts. However, configuration files are usually a one-person project.
Toggle feature selection without altering any code!
With a single
#
key press, we can comment out a section, thereby disabling the features it provides. The features are neither deleted nor forgotten, but we can experiment with having them there or not without altering any code! Alternatively, one mays use the:noexport:
tag on a section header.In contrast, an illiterate setup would have us commenting out large chunks of code, which is not as easy to manage.
Really easy to delete content!
After a while, I come back and realise I've implemented something silly or that is available via some external package, I can quickly delete it.
Can quickly export to different mediums!
If you want to share your configuration with others, then an HTML rendition with a table of contents and text sprinkled everywhere is more likely to attract onlookers since they can easily jump to the sections they're interested in.
Easily digestible chunks of code!
With a literate approach, one is empowered to have short source blocks; e.g., not exceeding 30 lines —read more here. This is more likely to ensure (possibly by extracting code into its own functions): The listing fits on one screen, avoiding deeply nested control structures, non-repeating common logical patterns, increased confidence that the implementation meets the stated purpose.
The only reason I would use multiple files or raw code for setting up a tool would be if I did not have a literate programming environment; i.e., Org-mode.
Emacs is fun (•̀ᴗ•́)و
Bye!
Thanks to friends for “replacing me” while I was away ♥‿♥ ᴵ’ᵐ ᵇᵉᵃᵘᵗⁱᶠᵘˡ