HomeAboutPostsTagsProjectsRSS

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
Words735
TagsRead2 minutes

Issue

When I decided to manage my sketchybar configuration as a git submodule within my Nix Flakes setup, I encountered an unexpected obstacle. Attempting to run darwin-rebuild was met with failure as the system couldn’t locate the source of the local directory. It’s a known issue as discussed in the Submodules of flakes are not working thread.

Overcoming the Submodule Challenge

I devised a workaround that resolved the problem and ensured proper file execution permissions. Before diving into the implementation details, I must emphasize the necessity of updating the flake.lock file. This is a critical step to ensure submodule is recognized by nix:

nix flake lock --update-input config-sketchybar-src

Solution

In flake.nix define the submodule directory as input

{
    inputs = {
      # submodule directory
      config-sketchybar-src = {
        url = "git+file:./config-sketchybar";
        flake = false;
      };
    };

    outputs = inputs@{ self, nixpkgs, darwin, home-manager, ... }:
    let username = "nohzafk";
    in {
      darwinConfigurations."MacBook-Pro" = darwin.lib.darwinSystem {
        system = "aarch64-darwin";
        specialArgs = { inherit inputs username; };
        modules = [
          ./configuration.nix
          ./system-preferences.nix
          {
            users.users."${username}" = {
              name = "${username}";
              home = "/Users/${username}";
            };
          }
          home-manager.darwinModules.home-manager
          {
            home-manager.useGlobalPkgs = true;
            home-manager.useUserPackages = true;
            home-manager.users."${username}".imports = [ ./home.nix ];
            home-manager.extraSpecialArgs = { inherit inputs username; };
          }
        ];
      };
    };
}

in home.nix

{ inputs, username, config, pkgs, ... }: {
    imports = [
       ./config-window-manager.nix
    ];
}

in config-window-manager.nix

{ inputs, lib, pkgs, ... }: {
let
  sketchybar-config =
    pkgs.callPackage ./pkg-sketchybar-config.nix { inherit inputs pkgs; };
in {
  home.packages =
    lib.mkBefore = [ sketchybar-config ];

  home.file.".config/sketchybar".source = sketchybar-config;

}

finally in pkg-sketchybar-config.nix

{ inputs, pkgs, ... }:
pkgs.stdenv.mkDerivation {
  name = "sketchybar-config";

  dontConfigure = true;
  dontUnpack = true;
  src = inputs.config-sketchybar-src;

  installPhase = ''
    mkdir -p $out
    cp -r $src/config/sketchybar/* $out

    # Find all .sh files and substitute 'jq' with the full path to the jq binary
    find $out -type f -name "*.sh" | while read script; do
      substituteInPlace "$script" \
        --replace 'jq' '${pkgs.jq}/bin/jq'
    done

    chmod -R +x $out
  '';
}

In the end, the satisfaction of having your local directory seamlessly integrated as a git submodule and functioning perfectly within the Nix ecosystem is well worth the effort.

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.