It’s so pleasant to use [[nix]] to install and config complex software packages.
Here is how to make emacs org work with latex
config-latex.nix
# https://nixos.wiki/wiki/TexLive
# For a minimal set of packages needed for Emacs Orgmode
{ pkgs, lib, ... }:
let
tex = (pkgs.texlive.combine {
inherit (pkgs.texlive)
scheme-basic dvisvgm dvipng # for preview and export as html
wrapfig amsmath ulem hyperref capt-of fontspec;
});
in { home.packages = lib.mkBefore [ tex ]; }
I recently wanted to practice some LeetCode and write documents and code in an org file. To quickly test the code, I wanted to use C-c C-c on a src-block to run pytest. I created this snippet to enable that functionality.
#+begin_src python :pytest
def test():
assert Solution().mergeAlternately("abc", "pqr") == "apbqcr"
assert Solution().mergeAlternately("ab", "pqrs") == "apbqrs"
assert Solution().mergeAlternately("abcd", "pq") == "apbqcd"
class Solution:
def mergeAlternately(self, word1: str, word2: str) -> str:
longest = max(len(word1), len(word2))
def get_char(i, chs):
return chs[i] if i < len(chs) else ""
r = []
for i in range(0, longest):
r.append(get_char(i, word1))
r.append(get_char(i, word2))
return "".join(r)
#+end_src
I used the built-in tempo to create a template. This allows me to run M-x insert-leetcode-solution, which inserts the template content and places the cursor on the line below “Problem”.
#+begin_src elisp :tangle config.el
(require 'tempo)
(tempo-define-template
"leetcode-solution"
'("* Problem"
n
p
n
"* Note"
n
"* Solution"
n
"#+begin_src python :pytest"
n
"#+end_src"
n))
(defun insert-leetcode-solution ()
(interactive)
(tempo-template-leetcode-solution))
#+end_src
Here is the most valuable part of my emacs
vTerm
config with fish shell.
add source vterm.fish to your fish shell configuration file
use edit <file> in vTerm buffer will open file at the new window above vTerm buffer, similar to using code <file> in VSCode embedded terminal.
vterm.fish
# https://github.com/akermu/emacs-libvterm
function vterm_printf
if begin
[ -n "$TMUX" ]; and string match -q -r "screen|tmux" "$TERM"
end
# tell tmux to pass the escape sequences through
printf "\ePtmux;\e\e]%s\007\e\\" "$argv"
else if string match -q -- "screen*" "$TERM"
# GNU screen (screen, screen-256color, screen-256color-bce)
printf "\eP\e]%s\007\e\\" "$argv"
else
printf "\e]%s\e\\" "$argv"
end
end
function vterm_cmd --description 'Run an Emacs command among the ones been defined in vterm-eval-cmds.'
set -l vterm_elisp ()
for arg in $argv
set -a vterm_elisp (printf '"%s" ' (string replace -a -r '([\\\\"])' '\\\\\\\\$1' $arg))
end
vterm_printf '51;E'(string join '' $vterm_elisp)
end
if [ "$INSIDE_EMACS" = vterm ]
set -gx EDITOR code
function clear
vterm_printf "51;Evterm-clear-scrollback"
tput clear
end
# used by vterm buffer name
function fish_title
hostname
echo ":"
prompt_pwd
end
function vterm_prompt_end --on-variable PWD
vterm_printf '51;A'(whoami)'@'(hostname)':'(pwd)
end
function edit
set -q argv[1]; or set argv[1] "."
vterm_cmd vterm-edit-file (realpath "$argv")
end
end
doom emacs config
config.el
(after! vterm
(defun vterm-edit-file (file)
"Open a file from vterm in another window, keeping only the vterm window and the new file window."
(interactive)
(let ((current-vterm-window (catch 'found
(dolist (win (window-list))
(when (and (window-live-p win)
(eq 'vterm-mode (buffer-local-value 'major-mode (window-buffer win))))
(throw 'found win)))))
new-file-window)
(when current-vterm-window
;; Open file in a new window from current VTerm window
(select-window current-vterm-window)
(setq new-file-window (split-window-below)) ; Adjust split direction to preference
(set-window-buffer new-file-window (find-file-noselect file))
;; Delete all other windows except for VTerm and the new file window
(mapc (lambda (win)
(unless (or (eq win current-vterm-window)
(eq win new-file-window))
(delete-window win)))
(window-list))
(select-window new-file-window))))
(add-to-list 'vterm-eval-cmds '("vterm-edit-file" vterm-edit-file)))
(use-package! gleam-ts-mode
:config
;; setup formatter to be used by `SPC c f`
(after! apheleia
(setf (alist-get 'gleam-ts-mode apheleia-mode-alist) 'gleam)
(setf (alist-get 'gleam apheleia-formatters) '("gleam" "format" "--stdin"))))
(after! treesit
(add-to-list 'auto-mode-alist '("\\.gleam$" . gleam-ts-mode)))
(after! gleam-ts-mode
(unless (treesit-language-available-p 'gleam)
;; compile the treesit grammar file the first time
(gleam-ts-install-grammar)))
hack
If you, like me, use Treesitter grammar files from Nix, the tree-sitter subdirectory within the directory specified by user-emacs-directory is linked to Nix’s read-only filesystem, meaning gleam-ts-install-grammar is unable to install grammar files there.
Here’s how you can adjust treesit-extra-load-path and install the grammar file.
Just learned that using [[Emacs]], press C-\ to set input method to TeX, then input Greek symbols will be very easy, for example type \theta will display θ
Here are some frequently used symbols when writing math equations.
Using lsp-bridge for Language Server Protocol in Emacs
Emacs 29 introduced built-in TRAMP Docker support for editing files within containers. However, enabling auto-completion inside a container remains challenging.
After discovering
lsp-bridge
, I realized the benefit of a Python process interacting with the language server, rather than relying solely on Emacs Lisp.
I added a feature for automatic reconnection to remote SSH servers and started supporting devcontainers. Using nix-darwin to configure my MacBook, I implemented the
devcontainer feature
to install lsp-bridge and the language server within the devcontainer. This was facilitated by the
Nix ecosystem
. To make it work, I needed to start with patching the official Nix devcontainer feature and create separate projects — a long journey, but worthwhile.
Container File Buffer Handling
When opening files in a container, lsp-bridge creates an lsp-bridge buffer, inserts the file content, and uses it for editing and rendering diagnostic information. The remote file content on the container is maintained by the lsp-bridge server running inside the container.
I completed the handling of the visited file modification time for the buffer and enabled auto-revert mode to reload file content automatically when the formatter updates the file.
Using the created lsp-bridge buffer, I used set-visited-file-name to associate a buffer with a file path. However, saving the buffer after the first save prompts a warning: “File has been changed since visited.” This occurs due to discrepancies in how file timestamps are handled by Emacs through TRAMP. To resolve this, Emacs’ record of the file’s modification time must be manually updated with something like this:
Emacs’ “auto-revert” mode, which automatically reverts buffers when the displayed files change externally, is also useful when files are updated by the formatter. Therefore, the file buffer must enable auto-revert-mode.
With these pieces in place, I now have a smooth editing experience with auto-completion inside the devcontainer.
I use
vertico-postframe
to place my completion window at the center of screen, however when doing incremental search like doom-emacs SPC s s, it will block the man window.
Here is how to temporarily disable vertico-posframe-mode in Emacs before executing a function, like an incremental search, and then re-enable it afterward.
(defun my-search-without-posframe ()
"Perform a search without `vertico-posframe-mode' temporarily."
(interactive)
;; Disable vertico-posframe-mode if it's enabled
(when (bound-and-true-p vertico-posframe-mode)
(vertico-posframe-mode -1)
(unwind-protect
;; Perform the search
(call-interactively '+default/search-buffer)
;; Re-enable vertico-posframe-mode
(vertico-posframe-mode 1))))
in config.el
(map! :leader
:desc "Search without posframe"
"s s" #'my-search-without-posframe)
modify only the height of headings in Org mode without affecting other attributes like color
Instead of using custom-theme-set-faces which replaces the whole face definition (and could unintentionally override theme-specific settings like color), use set-face-attribute
Lexical Binding: Before Emacs 24 emacs-lisp is dynamic binding. lexical binding is introduced in Emacs 24, enabling compile-time optimizations.
Native Compilation: Introduced in Emacs 28, native compilation used the GCC JIT compile to convert Emacs Lisp code to native machine code, dramatically improving execution speed.
Package Management
Lazy loading with use-package: use-package is now build into Emacs 29
Straight Package Manager
: works with use-package to handle package installations and version control.
Syntax Highlighting and Parsing
Tree-sitter Integration: Emacs 29 includes support for Tree-sitter, a parser generator that improves syntax highlighting and code navigation by using precise parse trees instead of regular expressions. This results in more accurate and faster syntax highlighting.
tree-sitter mode: new major modes that utilize tree-sitter for enhanced code parsing and highlighting, for example, use python-ts-mode to replace traditional python-mode.
Completion Frameworks
Historical Context: Initially, Emacs used ido for interactive file and buffer navigation, followed by heavier frameworks like helm and ivy for more advanced completions.
Modern Solutions: verticoconsultorderlessmarginaliaembark
Language Server Protocol (LSP)
eglot: Emacs’ build-in LSP client
I personally use
lsp-bridge
for completion and LSP
As a seasoned Emacs user, I’ve been eagerly anticipating the built-in arrival of use-package in version 29.1. And now it’s finally here! This
declarative configuration tool
has already become my go-to for confining all the chaotic Emacs configurations, making everything more organized and manageable. So, if you haven’t already, I’d wholeheartedly recommend upgrading your Emacs to the latest version 29.1.
Tips for Checking Package Installation
When you’re neck-deep in code, it’s quite common to forget whether you’ve installed a particular package or not. Emacs has got you covered with several commands:
featurep: Use this if a package ends with provide.
fboundp: This comes in handy when you need to check if a certain function is defined.
bound-and-true-p: Use this to confirm whether a global minor mode is both installed and activated.
The Power of cl-letf
I’ve found
cl-letfto be incredibly useful when I need to dynamically and temporarily override functions and values defined externally. It’s particularly handy when paired with advice, allowing me to alter the behavior of third-party packages without meddling with their source code.
Here’s a practical example of how to override a function defined in a package. The code modifies the behavior of original-split-window-horizontally inside create-window so that no matter what argument it receives, a fixed width is used:
(defun my-create-window-advice (orig-fun &rest args)
"Advice to modify the behavior of `split-window-horizontally' in `create-window'."
(let ((original-split-window-horizontally (symbol-function 'split-window-horizontally))
(fixed-width 20)))
(cl-letf (((symbol-function 'split-window-horizontally)
(lambda (&optional size)
(funcall original-split-window-horizontally fixed-width))))
(apply orig-fun args))))
(advice-add 'create-window :around #'my-create-window-advice)
Embracing thread-first and thread-last Macros
One of my favorite features of Emacs version 25 and onwards is the built-in thread-first and thread-last macros. These can prove immensely useful when dealing with complex data transformations - they help maintain clean and readable code.
In Emacs Lisp, the thread-first and thread-last are powerful tools for improving the readability of function call sequences. They allow for a more intuitive and linear style of writing nested function calls, especially useful in situations where you have multiple operations that need to be applied in sequence.
After a decade of using Emacs, it continues to be an indispensable part of my programming arsenal. Once one has really recognized the extensibility of emacs, it’s hard to not miss it every time using another editor.
I’ve been searching for a good solution to use Emacs29 with tree-sitter config in nix-darwin for quite some time.
Emacs29 comes with built-in support for Tree-Sitter, a parser generator tool and an incremental parsing library, the only requirement is the grammar files, detail in this
masteringemacs article
.
While there are some proposed solutions out there, such as the one found on the
nixos discourse
, they didn’t quite hit the mark for me. The issue was that I’m using an overridden package definition of pkgs.emacs29-macport, which complicated things a bit.
let
myEmacs = (emacsPackagesFor emacs).emacsWithPackages (epkgs: with epkgs; [
vterm
treesit-grammars.with-all-grammars
];)
in
The above treesit-grammars.with-all-grammars definition only installs the dynamic libraries. However, Emacs was still unable to find the files. The crux of the problem was that I needed to link the directory to a location that Tree-Sitter could locate.
After many trials and tribulations, I finally managed to come up with a functional solution.
Solution
pkg-emacs29-macport.nix: package definition of emacs
{ pkgs ? (import <nixpkgs> { }) }:
let
# https://github.com/railwaycat/homebrew-emacsmacport/blob/master/Formula/emacs-mac.rb
emacs29-macport = (pkgs.emacs29-macport.overrideAttrs (prevAttrs: {
patches = (prevAttrs.patches) ++ [
# patch for multi-tty support, see the following links for details
# https://bitbucket.org/mituharu/emacs-mac/pull-requests/2/add-multi-tty-support-to-be-on-par-with/diff
# https://ylluminarious.github.io/2019/05/23/how-to-fix-the-emacs-mac-port-for-multi-tty-access/
(pkgs.fetchpatch {
url =
"https://raw.githubusercontent.com/railwaycat/homebrew-emacsmacport/8b06f75ea28a68f9a490d9001ce33fd1b0d426aa/patches/emacs-mac-29-multi-tty.diff";
sha256 = "sha256-OpSYG5ew8A1IL5rW/wPwmG2bzZa8iFF+xTYQGiWjzKg=";
})
# no-title-bars
(pkgs.fetchpatch {
url =
"https://raw.githubusercontent.com/railwaycat/homebrew-emacsmacport/667f0efc08506facfc6963ac1fd1d5b9b777e094/patches/emacs-26.2-rc1-mac-7.5-no-title-bar.patch";
sha256 = "sha256-f2DRcUZq8Y18n6MJ6vtChN5hLGERduMB8B1mrrds6Ns=";
})
];
})).override {
# not necessary, but enforce these options to be true
withNativeCompilation = true;
withTreeSitter = true;
};
buildEmacs = (pkgs.emacsPackagesFor emacs29-macport).emacsWithPackages;
treesitGrammars =
(pkgs.emacsPackagesFor emacs29-macport).treesit-grammars.with-all-grammars;
in {
emacs = buildEmacs (epkgs: with epkgs; [ vterm treesitGrammars ]);
treesitGrammars = treesitGrammars;
}
config-emacs.nix: configuraiton of emacs
{ pkgs, config, lib, ... }:
let
emacs29 = (pkgs.callPackage ./pkg-emacs29-macport.nix { });
in {
home.packages = lib.mkBefore ([ emacs29.emacs ]);
home.file = {
# tree-sitter subdirectory of the directory specified by user-emacs-directory
".config/emacs/.local/cache/tree-sitter".source =
"${emacs29.treesitGrammars}/lib";
};
}
Tree-sitter language grammars are distributed as dynamic libraries. In order to use a language grammar in Emacs, you need to make sure that the dynamic library is installed on the system. Emacs looks for language grammars in several places, in the following order:
first, in the list of directories specified by the variable treesit-extra-load-path;
then, in the tree-sitter subdirectory of the directory specified by user-emacs-directory (see The Init File);
and finally, in the system’s default locations for dynamic libraries.
I use Emacs --with-no-title-bar and use
yabaito auto layout window,
sketchybarto display current window title at the menu bar.
Due to the limited screen size on a laptop, it can be challenging to view the entire file path. Therefore, I created a function that shows a shortened version of the file path.
If you are editing a file, the function will display the file path. Otherwise, it will display the buffer name.
for example, ~/projects/blog/content/posts/emacs-frame-title-format.md will be display as ~/p/b/c/posts/emacs-frame-title-format.md
(defun shorten-path-for-title (path)
"Shorten a file PATH to be displayed in the frame title.
Only the last directory's name is fully displayed; upper-layer directories are represented by their first letters."
(let* ((components (split-string (or path "") "/" t))
(filename (or (car (last components)) ""))
(lastdir (if (> (length components) 1) (nth (- (length components) 2) components) ""))
(dirs (butlast components 2))
(shortened-dirs (mapcar (lambda (dir) (substring dir 0 1)) dirs)))
(concat (string-join shortened-dirs "/")
(if shortened-dirs "/")
lastdir
"/"
filename)))
(setq frame-title-format
'((:eval (if (buffer-file-name)
(shorten-path-for-title (abbreviate-file-name (buffer-file-name)))))
(:eval (if (not (buffer-file-name)) (buffer-name)))))
Mac OS intercepts and handles certain key combinations (e.g., - for switching input languages). These will not be passed to Emacs. One can disable this interception by setting mac-pass-command-to-system or mac-pass-control-to-system to nil.
I’m quite fond of this saying—it captures the essence of Emacs perfectly.
The core idea of Emacs is to have an expressive digital material and an open-ended, user-extensible set of commands that manipulate that material and can be quickly invoked.
Obviously, this model can be fruitfully applied to any digital material, not just plain text.
If you are experiencing issues with a Doom Emacs package that has been cloned from GitHub, and you want to force a re-download or re-clone of the package, you can use the following steps to address the issue:
Delete the Problematic Package: Navigate to your .emacs.d/.local/straight/repos/ directory (or wherever your packages are stored; this path is the default for Doom Emacs). Locate the directory corresponding to the problematic package and delete it.
Replace with the actual name of the package directory you want to remove.
Clear Build Cache: Clear the build cache that might contain failed or partial build information. The build cache is typically located in the .emacs.d/.local/straight/build/ directory.
In Emacs, the fill-paragraph function neatly reflows a block of text, wrapping lines to fit within a specified column width. VS Code offers an extension called “Rewrap,” which you can install to enjoy similar functionality.
Selecting the text you want to rewrap and pressing Alt+Q, or
Placing your cursor within a comment block and pressing Alt+Q to rewrap that comment.
This feature can increase your productivity significantly if you’re editing or writing a considerable amount of text. It streamlines the process of formatting paragraphs, ensuring that they look neat and are easier to read, very much like Emacs’ fill-paragraph.