HomeAboutPostsTagsProjectsRSS

emacs

Updated
Words105
TagsRead1 minute

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

doom-emacs packages.el

(package! org-fragtog)

doom-emacs config.el

(use-package! org-fragtog
  :config
  (add-hook 'org-mode-hook 'org-fragtog-mode))

(after! org
  (setq org-preview-latex-default-process 'dvisvgm)
  (setq org-startup-with-latex-preview t))

Updated
Words410
TagsRead2 minutes

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.

(after! org
  (defun org-babel-execute:python-with-pytest (body params)
    "Execute a python source block with pytest if :pytest is specified."
    (if (assq :pytest params)
        (let* ((temporary-file-directory ".")
               (temp-file (make-temp-file "pytest-" nil ".py")))
          (with-temp-file temp-file
            (insert body))
          (unwind-protect
              (org-babel-eval (format "pytest -v -s %s" temp-file) "")
            (delete-file temp-file)))
      (org-babel-execute:python-default body params)))

  (advice-add 'org-babel-execute:python :override #'org-babel-execute:python-with-pytest))

usage example

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

Updated
Words160
TagsRead1 minute

I use Emacs with Vertico for completion and want to center the minibuffer content on my large monitor to avoid constantly looking at the left corner. After trying various solutions unsuccessfully, I finally found GitHub - mpwang/perfect-margin: [emacs] auto center emacs windows, work with minimap and/or linum-mode , which allows me to set the minibuffer margin effectively.

(add-to-list 'perfect-margin-force-regexps "*Minibuf")
(add-to-list 'perfect-margin-force-regexps "*which-key")

Updated
Words857
TagsRead2 minutes

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

Updated
Words403
TagsRead1 minute

Recently discovered Gleam language, and totally fell in love with it shortly!

Here is how you can setup Gleam in doom-emacs

packages.el

We just need the gleam-ts-mode.el, do not download gleam-mode.el as when it gets compiled it requires tree-sitter which causes problem.

(package! gleam-ts-mode
  :recipe (:host github
           :repo "gleam-lang/gleam-mode"
           :branch "main"

           :files ("gleam-ts-*.el")))

config.el

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

(after! gleam-ts-mode
  (setq treesit-extra-load-path (list (expand-file-name "~/.local/tree-sitter/")))
  (unless (treesit-language-available-p 'gleam)
    ;; hack: change `out-dir' when install language-grammar'
    (let ((orig-treesit--install-language-grammar-1 (symbol-function 'treesit--install-language-grammar-1)))
      (cl-letf (((symbol-function 'treesit--install-language-grammar-1)
                 (lambda (out-dir lang url)
                   (funcall orig-treesit--install-language-grammar-1
                            "~/.local/tree-sitter/" lang url))))
        (gleam-ts-install-grammar)))))

Updated
Words37
TagsRead1 minute

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.

symbol
\alphaα
\thetaθ
\delta
Δ
\nabla

Updated
Words613
TagsRead2 minutes

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:

(defun my-update-file-mod-time ()
  (when buffer-file-name
    (let ((mod-time (file-attribute-modification-time (file-attributes buffer-file-name))))
      (set-visited-file-modtime mod-time))))

(add-hook 'after-save-hook 'my-update-file-mod-time)

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.

Emacs rocks!

Updated
Words212
TagsRead1 minute

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)

Updated
Words234
TagsRead1 minute

different height for Org mode headings

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

doom-emacs config example

(after! org
  ;; Adjust the height of org headings upon org-mode startup
  (add-hook 'org-mode-hook (lambda ()
    (dolist (face '((org-level-1 . 1.75)
                    (org-level-2 . 1.5)
                    (org-level-3 . 1.25)
                    (org-level-4 . 1.1)
                    (org-level-5 . 1.05)
                    (org-level-6 . 1.0)
                    (org-level-7 . 0.95)
                    (org-level-8 . 0.9)))
      (set-face-attribute (car face) nil :height (cdr face)))))

Updated
Words469
TagsRead1 minute

Emacs-obsidian

might be too overkill for me , just opening note in Emacs for intensive editing is enough.

from [[Emacs]]: quickly open Obsidian note

use Deft Mode

from [[Obsidian]]: open current file in Emacs

open file and go to line command for emacsclient to open a.txt at line 4 column 3

emacsclient +4:3 a.txt

use obsidian open current file in emacs

emacsclient -c -s work +{{caret_position}} {{file_path:absolute}}

Use shell-command plugin to create a obsidian command to run the shell command

Updated
Words482
TagsRead1 minute

Modern Emacs: all those new tools that make Emacs better and faster - YouTube

Optimization

  • 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: vertico consult orderless marginalia embark

Language Server Protocol (LSP)

  • eglot: Emacs’ build-in LSP client

I personally use lsp-bridge for completion and LSP

Updated
Words707
TagsRead2 minutes

built-in use-package

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-letf to 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.

https://codelearn.me/2023/05/28/emacs_thread_macros.html

Summary

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.

Updated
Words819
TagsRead2 minutes

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.

Tree-sitter Language Grammar

Finally use this config-emacs.nix in your home.nix using home-manager

imports = [
  ./config-emacs.nix
]

Updated
Words475
TagsRead1 minute

I use Emacs --with-no-title-bar and use yabai to auto layout window, sketchybar to 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)))))

Updated
Words151
TagsRead1 minute

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.

X

Updated
Words172
TagsRead1 minute

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:

  1. 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.
rm -rf ~/.emacs.d/.local/straight/repos/<package-name>

Replace with the actual name of the package directory you want to remove.

  1. 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.
rm -rf ~/.emacs.d/.local/straight/build/<package-name>
  1. Run Doom Sync: Run doom sync in your terminal to synchronize your configuration. This will re-download and rebuild any missing or deleted packages.
doom sync
  1. Restart Emacs: After running doom sync, restart Emacs to ensure that all changes take effect.

Remember that you should re-enable the package your config.el before running doom sync if you previously disabled it.

Updated
Words199
TagsRead1 minute

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.

Rewrap - Visual Studio Marketplace