Fixing Emacs Dired Defaults: Settings for Better File Management

This article presents a set of Emacs Dired configuration changes to enhance file management. The configurations sort directories before files for easier navigation, hide dotfiles, reduce unnecessary confirmation prompts, prevent UI lockups caused by network latency, among other improvements. Each configuration explains the behavior of the relevant setting, its benefits, and its tradeoffs, making it easier to determine which changes are appropriate.

Keeping Dired clean by hiding dotfiles

Using dired-omit-mode helps hide hidden files and directories, such as .git or .DS_Store, by applying a specific regular expression. The tradeoff is that required configuration files are hidden by default, forcing you to toggle dired-omit-mode off to see them. Disabling dired-omit-verbose prevents the mode from displaying status messages when omitting files.

(setq dired-omit-verbose nil)
(setq dired-omit-files (concat "\\`[.]\\'"
                               "\\|\\.\\(?:elc\\|a\\|o\\|pyc\\|pyo\\|swp\\|class\\)\\'"
                               "\\|^\\.DS_Store\\'"
                               "\\|^\\.\\(?:svn\\|git\\)\\'"
                               "\\|^flycheck_.*"
                               "\\|^flymake_.*"))

(add-hook 'dired-mode-hook #'dired-omit-mode)

To hide all hidden files whose names begin with a dot, add the following regular expression to the dired-omit-files variable:

(setq dired-omit-files (concat dired-omit-files "\\|^\\."))

Hiding details

Enabling dired-hide-details-mode automatically hides file details such as permissions, size, and modification dates.

(add-hook 'dired-mode-hook #'dired-hide-details-mode)

Sorting directories first

The following code snippet configures Dired to sort directories before files. The benefit is improved navigation, as folders are grouped at the top of the buffer, matching standard file managers. The tradeoff is a deviation from strict alphabetical sorting, which can disorient users accustomed to raw UNIX or Linux ls output.

(setq ls-lisp-verbosity nil
      ls-lisp-dirs-first t)

(when (eq system-type 'darwin)
  (setq dired-use-ls-dired nil)) ; macOS/BSD ls

(let ((args "--group-directories-first -ahlv"))
  (when (or (eq system-type 'darwin) (eq system-type 'berkeley-unix))
    (if-let* ((gls (executable-find "gls")))
        (setq insert-directory-program gls)
      (setq args nil)))
  (when args
    (setq dired-listing-switches args)))

Killing the current Dired buffer upon navigating into a different directory

Setting dired-kill-when-opening-new-dired-buffer to t (Emacs >= 28.1) causes Dired to automatically kill the current Dired buffer upon navigating into a different directory. The benefit is a cleaner buffer list, as it prevents leaving behind a trail of open buffers when traversing deep directory hierarchies. The tradeoff is the loss of buffer state. Navigating back to a parent directory requires generating a new buffer, which erases previous cursor positions, active file marks, and modified subdirectory visibility.

(setq dired-kill-when-opening-new-dired-buffer t)

Version control integration

Enabling dired-vc-rename-file causes Dired to perform file renames through the underlying version control system when supported, using the vc-rename-file function. The benefit is accurate version control history during Dired rename operations. The tradeoff is that VC renames can introduce slight latency on very large repositories.

(setq dired-vc-rename-file t)

Simplifying deletion confirmations

This configuration accelerates file cleanup by reducing the number of manual prompts required to delete files and directories.

(setq dired-deletion-confirmer 'y-or-n-p
      dired-recursive-deletes 'top
      dired-clean-confirm-killing-deleted-buffers nil)
  • Setting dired-deletion-confirmer to 'y-or-n-p: Changes the deletion prompt to accept a single 'y' or 'n' keypress instead of requiring you to type the full word 'yes'.
  • Setting dired-recursive-deletes to 'top: By default, deleting a non-empty directory in Dired can be a tedious process because Emacs will prompt you to confirm the deletion of every single nested subdirectory as it traverses down the tree. Setting this variable to 'top establishes a practical balance between speed and safety. Emacs will ask for your confirmation exactly once for the top-level directory you have marked. Once you confirm that initial prompt, it silently and recursively deletes all nested files and folders inside that tree without any further interruptions.
  • Setting dired-clean-confirm-killing-deleted-buffers to nil: Suppresses the prompt that asks for permission to close Dired buffers associated with directories you just deleted, closing them automatically instead. This makes sense because once a directory is removed from the disk, any open buffer pointing to it becomes orphaned and unusable. Automatically killing these dead buffers keeps your buffer list clean, as deleting the directory already establishes your intent to remove it from your workflow entirely.

The benefit is faster file management and fewer repetitive keystrokes. The tradeoff is an elevated risk of accidental data loss, as the safety barrier to executing destructive operations is significantly lowered.

Removing disk space indicator

Disabling dired-free-space (Emacs >= 29.1) removes disk space indicator from Dired buffers. The tradeoff is that you lose quick visibility into remaining disk capacity.

(setq dired-free-space nil)

Restricting vertical cursor movement

Setting dired-movement-style to 'bounded-files (Emacs >= 29.1) restricts vertical movement to file lines in the Dired buffer. When point reaches the first or last file line, further vertical movement stops instead of moving to other non-file lines. The benefit is increased navigation efficiency. When scrolling rapidly to the top or bottom of a directory, the cursor stops exactly on the first or last file or directory, avoiding empty space or metadata lines. The tradeoff is that placing the cursor on the directory header to copy its path requires alternative navigation commands, as standard vertical movement keys will no longer reach it.

(setq dired-movement-style 'bounded-files)

Managing recursive copies and destination directories

These variables force Dired to copy directories recursively by default without prompting, while explicitly asking for confirmation before creating new, non-existent destination directories. The benefit is bulk copying combined with a safety check against typos in destination paths. The tradeoff is that large, deeply nested directory structures might be copied unintentionally since the recursive copying happens automatically.

(setq dired-recursive-copies 'always
      dired-create-destination-dirs 'ask)

Efficient auto-reverting for dired buffers

Configuring dired-auto-revert-buffer to use dired-directory-changed-p causes Dired to automatically revert the buffer when the listed directory has changed. The benefit is improved efficiency because Dired does not unconditionally rebuild the listing. The tradeoff is that changes that do not modify the directory itself, such as in-place edits to existing files, might not trigger an automatic refresh.

(setq dired-auto-revert-buffer 'dired-directory-changed-p)

Note: On Unix-like filesystems, a directory's mtime (modification time) is updated when its directory entries change. This occurs when a file or subdirectory is created, deleted, or renamed within the directory. Modifying the data inside an existing file updates the file's mtime, but does not update the mtime of its parent directory.

Reverting destination buffers after file operations

This configuration automatically updates destination Dired buffers after file operations like copying or renaming, but uses a custom predicate to skip remote directories. The benefit is automatic synchronization of local Dired buffers without performing the same operation on remote directories, avoiding unnecessary TRAMP network activity and associated latency. The tradeoff is that remote directory buffers require a manual refresh to reflect recent file transfers.

(setq dired-do-revert-buffer (lambda (dir)
                               (not (file-remote-p dir))))

Enabling mouse drag-and-drop

Setting dired-mouse-drag-files to t (Emacs >= 29.1) allows you to click and drag files directly from a Dired buffer into external desktop applications, such as graphical file managers or web browsers.

(setq dired-mouse-drag-files t)

Cross-platform file associations

The following snippet configures Dired to open specific file extensions using the default application handler of the host operating system. It identifies the environment (macOS, Linux, or Windows) and assigns the corresponding system command (open, xdg-open, or start) to the dired-guess-shell-alist-user variable for documents, images, and media files. The main benefit is that it delegates file association management to the operating system, removing the need to configure individual applications within Emacs for every file type.

(defvar my-dired-xdg-open-cmd nil)
(with-eval-after-load 'dired
  (when-let* ((cmd (cond
                    ((eq system-type 'darwin)
                     "open")
                    ((memq system-type '(gnu gnu/linux gnu/kfreebsd
                                             berkeley-unix))
                     "xdg-open")
                    ((memq system-type '(cygwin windows-nt ms-dos))
                     "start"))))
    (setq dired-guess-shell-alist-user
          `(("\\.\\(?:docx\\|pdf\\|odt\\|odg\\|ods\\|djvu\\|eps\\)\\'" ,cmd)
            ("\\.\\(?:jpe?g\\|webp\\|png\\|gif\\|xpm\\)\\'" ,cmd)
            ("\\.\\(?:xcf\\)\\'" ,cmd)
            ("\\.tex\\'" ,cmd)
            ("\\.\\(?:mp4\\|mkv\\|m4a\\|avi\\|flv\\|rm\\|rmvb\\|ogv\\)\\(?:\\.part\\)?\\'" ,cmd)
            ("\\.\\(?:mp3\\|flac\\)\\'" ,cmd)))
    (when cmd
      (setq my-dired-xdg-open-cmd cmd))))

How to use it: To use this configuration in practice, navigate to a directory using Dired and place your cursor over a file. For example, if your cursor is on a video file, press ! (which invokes dired-do-shell-command) or & (for asynchronous execution). Dired will prompt you in the minibuffer with the appropriate system command already populated based on your operating system, such as xdg-open. Press RET to confirm, and the video will open in your operating system's default media player.

If you prefer forcing all file types to open asynchronously via the operating system handler (e.g., xdg-open), the following snippet routes every file through the system default application.

(defvar my-dired-xdg-open-cmd nil)
(with-eval-after-load 'dired
  (when-let* ((cmd (cond
                    ((eq system-type 'darwin)
                     "open")
                    ((memq system-type '(gnu gnu/linux gnu/kfreebsd
                                             berkeley-unix))
                     "xdg-open")
                    ((memq system-type '(cygwin windows-nt ms-dos))
                     "start"))))
    (setq dired-guess-shell-alist-user
          `((".*" ,cmd)))
    (when cmd
      (setq my-dired-xdg-open-cmd cmd))))

Measuring Emacs Startup Time More Accurately than the Built-in emacs-init-time

As an Emacs configuration grows, startup time can gradually increase. Measuring that increase accurately makes it easier to identify regressions. The majority of Emacs users use emacs-init-time. However, this built-in function does not measure all the work performed during startup.

Emacs startup proceeds through several stages: it loads the early init and regular init files, processes command-line options, sets after-init-time, runs emacs-startup-hook, performs additional initial-frame setup such as applying frame parameters from the configuration, and then runs window-setup-hook after the initial frame parameters have been configured. This makes window-setup-hook a useful place to record a broader startup-time measurement.

For a more accurate startup-time measurement, add the following Emacs Lisp code to early-init.el or init.el:

;; Emacs startup proceeds through several stages: it loads the early init and
;; regular init files, processes command-line options, sets after-init-time,
;; runs emacs-startup-hook, performs additional initial-frame setup such as
;; applying frame parameters from the configuration, and then runs
;; window-setup-hook after the initial frame parameters have been configured.
;; This makes window-setup-hook a useful place to record a broader startup-time
;; measurement.
;;
;; URL: https://www.jamescherti.com/measuring-emacs-startup-time/
(defvar my-recorded-startup-time-message nil
  "Stores the formatted string of the Emacs startup metrics.")

(defun my-record-startup-time ()
  "Calculate and record the elapsed startup time.
This function records the time when `window-setup-hook' runs."
  (setq my-recorded-startup-time-message
        (format "Emacs loaded in %.2f seconds (Init time: %.2fs) with %d garbage collections."
                (float-time (time-since before-init-time))
                (float-time (time-subtract after-init-time before-init-time))
                gcs-done))
  ;; Output to the *Messages* buffer during the initial launch
  (message "%s" my-recorded-startup-time-message))

(defun my-display-startup-time ()
  "Display the previously recorded Emacs startup time in the echo area."
  (interactive)
  (if my-recorded-startup-time-message
      (message "%s" my-recorded-startup-time-message)
    (message "Startup time was not recorded.")))

;; Read startup summary:
;; https://www.gnu.org/software/emacs/manual/html_node/elisp/Startup-Summary.html
(add-hook 'window-setup-hook #'my-record-startup-time 99)

This provides a broader measurement than M-x emacs-init-time because it continues measuring after after-init-time has been set, including subsequent startup processing through window-setup-hook.

A depth of 99 places the function near the end of window-setup-hook. This makes the measurement less likely to omit work performed by other functions on the same hook.

The variable gcs-done tracks the total number of garbage collections during the session. Because this function formats and saves the message string during window-setup-hook, it locks in the exact number of garbage collections triggered during the startup phase, giving you a metric of memory allocation bottlenecks during initialization. Ideally, it should be 0 or 1.

Capturing the time the UI finishes rendering provides a reliable metric to track down performance regressions. Once Emacs has finished loading, you can retrieve the recorded startup time at any point during your session. Execute M-x my-display-startup-time to print the exact initialization metrics in the echo area. This interactive command ensures the startup data remains accessible even if the initial message is cleared from the screen by subsequent buffer activity.

Configuring Eglot for Python Development in Emacs: Integrating python-lsp-server (pylsp) with Linters and Formatters

Eglot provides built-in LSP support in modern Emacs, giving developers a native interface for autocompletion, linting, and formatting. When paired with python-lsp-server (pylsp), creating a Python development environment comes down to managing the LSP server configuration properly.

Historically, setting up this toolchain required wiring together a stack of individual utilities such as flake8 and isort. Now, ruff stands entirely apart from that legacy ecosystem by consolidating all of those separate checks into a single, high-performance Rust binary. This article demonstrates how to configure Eglot to detect and use Ruff dynamically when it is present on your system, while retaining a clean fallback to the older stack of tools like Flake8, pycodestyle, and pydocstyle when Ruff is missing.

The configuration strategy

The configuration provided in this article begins by checking for the presence of ruff and flake8 in your system path. Based on these checks:

  1. If Ruff is installed, the configuration relies on the python-lsp-ruff plugin. Ruff consolidates linting, formatting, and import sorting.
  2. If Ruff is missing but Flake8 is present, Flake8 takes over linting duties alongside Pylint.
  3. If neither is installed, the system falls back to the individual pylsp plugins (pyflakes, pycodestyle, mccabe, etc.).

Dependencies

The dependencies can be installed locally pip or directly via your system's package manager.

The complete configuration

Below is the complete Emacs Lisp code to achieve this dynamic configuration. You can place this snippet in one of your init files:

;; Use ruff when it is available because it is fast (written in Rust). When ruff
;; is not available, fall back to flake8 and its individual underlying tools.
;;
;; URL: https://www.jamescherti.com/emacs-python-dev-using-eglot-pylsp-ruff-pylint-flake8/
;;
;; To sup up:
;; - When ruff is available: Ruff, and Pylint.
;; - When Ruff is not available: Flake8, isort, and Pylint.
;;
;; Documentation:
;; https://github.com/python-lsp/python-lsp-server/blob/develop/CONFIGURATION.md
;; https://github.com/python-lsp/python-lsp-ruff
;; https://github.com/chantera/python-lsp-isort
(let* ((has-ruff (executable-find "ruff"))
       (has-flake8 (executable-find "flake8")))
  ;; Target ONLY the 'pylsp key in the global configuration alist safely
  (setf (alist-get 'pylsp (default-value 'eglot-workspace-configuration))
        `(:pylsp
          (:plugins
           (;; Plugin: https://github.com/python-lsp/python-lsp-ruff
            :ruff (;; Ruff configuration
                   :enabled ,(if has-ruff t :json-false)

                   :formatEnabled ,(if has-ruff t :json-false)

                   ;; Add 'W' (pycodestyle warnings), 'UP' (pyupgrade),
                   ;; and 'D' (pydocstyle).
                   :extendSelect ["W" "UP" "D"]

                   ;; Ignore specific rules
                   ;;   D213: Multi-line docstring summary should start on
                   ;;         the second line.
                   ;;   D202: No blank lines allowed after function
                   ;;         docstring.
                   ;; :ignore ["D213" "D202"]
                   )

            ;; Pylint remains enabled regardless of whether Ruff
            ;; or Flake8 is active because it serves
            ;; complementary role.
            :pylint (:enabled t)

            ;; Flake8 is a wrapper tool that bundles pyflakes,
            ;; pycodestyle, and mccabe.
            :flake8 (:enabled ,(if (and (not has-ruff) has-flake8)
                                   t
                                 :json-false))

            ;; When Flake8 or Ruff runs, they execute these under
            ;; the hood. If we enable either, we must explicitly
            ;; disable the individual pylsp plugins for them,
            ;; otherwise the language server will run the exact
            ;; same checks twice and duplicate all editor
            ;; diagnostics.
            :mccabe (:enabled ,(if (or has-ruff has-flake8)
                                   :json-false
                                 t))
            :pyflakes (;; pyflakes catches logical errors
                       ;; (unused imports, undefined names...)
                       :enabled ,(if (or has-ruff has-flake8)
                                     :json-false
                                   t))

            :pycodestyle (;; pycodestyle catches style/formatting
                          ;; violations (PEP 8)
                          :enabled ,(if (or has-ruff has-flake8)
                                        :json-false
                                      t)

                          ;; Ignore specific rules
                          ;; :ignore ["W293"]
                          )

            :pydocstyle (;; pydocstyle enforces PEP 257 docstring
                         ;; conventions
                         :enabled ,(if (or has-ruff has-flake8)
                                       ;; Use flake8-docstrings
                                       ;; https://github.com/pycqa/flake8-docstrings
                                       :json-false
                                     t)

                         ;; Ignore specific rules
                         ;;   D213 Multi-line docstring summary should start on
                         ;;        the second line.
                         ;;   D202 No blank lines allowed after function
                         ;;        docstring.
                         ;; :ignore ["D213" "D202"]
                         )

            ;; Formatting: isort
            ;; https://github.com/chantera/python-lsp-isort
            :isort (:enabled ,(if has-ruff :json-false t))

            ;; Formatting: autopep8
            :autopep8 (:enabled ,(if has-ruff :json-false t))
            :yapf (:enabled :json-false)

            ;; Code completion
            :jedi_completion (;; jedi configuration
                              :enabled t

                              ;; Disable resolving documentation details eagerly
                              ;; :eager t

                              ;; Add class objects as a separate completion item
                              ;; :include_class_objects t

                              ;; Add function objects as a separate completion item
                              ;; :include_function_objects t

                              ;; Auto-complete methods and classes for each parameter
                              ;; :include_params t

                              ;; Fuzzy matching for typos/abbreviations
                              ;; :fuzzy t

                              ;; Modules for which labels and snippets should be cached.
                              ;; :cache_for ["pandas", "numpy", "tensorflow", "matplotlib"]

                              ;; How many labels and snippets should be resolved?
                              ;; :resolve_at_most 25
                              ))))))

This Eglot configuration prioritizes Ruff to handle linting and formatting when it is available in the environment. If Ruff is missing, it falls back to Flake8 and the default pylsp plugins. This keeps Emacs Eglot adaptable across different machines, guaranteeing consistent diagnostics and autocompletion without demanding a strict set of dependencies on every system you use.

Configuring Emacs Scrolling for Better Usability

By default, scrolling in Emacs recenters the window when point moves off-screen, and rapid scrolling through large, heavily fontified files can introduce noticeable input lag. This article outlines configurations that make scrolling more predictable and responsive.

Customizing scroll recentering

Adjusting the automatic scrolling behavior can prevent Emacs from making large jumps when point moves beyond the visible portion of the window:

;; Scroll by up to 20 lines to bring point back into view before falling back to
;; the normal automatic scrolling behavior.
(setq scroll-conservatively 20)

When point moves off-screen or into the scroll margin, setting scroll-conservatively to a moderate value like 20 allows Emacs to scroll the text by up to 20 lines in either direction to bring point back into view.

Note: Setting scroll-conservatively to a value above 100 prevents automatic scrolling from centering point, regardless of how far point moves. Emacs instead scrolls only far enough to bring point into view, placing it at the top or bottom of the window depending on the direction of scrolling.

Maintaining vertical context

While scroll-conservatively controls how Emacs reacts when point leaves the window, you can also define a boundary to force scrolling before point reaches the absolute edge:

;; Keep 3 lines of context visible above and below point.
(setq scroll-margin 3)

Setting scroll-margin to 3 establishes a boundary of three lines at both the top and bottom of the Emacs window, forcing the buffer to scroll automatically as soon as your cursor enters this area instead of waiting for it to reach the absolute edge of the screen.

This keeps point away from the top and bottom edges of the window whenever automatic scrolling can move it out of the margin.

Note: Leaving scroll-margin at the default of 0 ensures the cursor can sit directly on the top or bottom line before triggering a scroll. Many users prefer this default setting because it maximizes usable vertical screen space and mirrors the edge-scrolling behavior found in most other modern text editors.

Horizontal scrolling

When line truncation is enabled (truncate-lines is non-nil), Emacs automatically scrolls horizontally when point approaches the left or right edge of the window. hscroll-margin controls how close point can get to an edge, while hscroll-step controls how far the window moves when automatic horizontal scrolling occurs.

;; Horizontal scrolling
(setq hscroll-margin 2
      hscroll-step 1)

Setting hscroll-margin to 2 causes horizontal scrolling to trigger when point comes within two columns of the left or right edge, while setting hscroll-step to 1 forces the window to pan exactly one column at a time.

This transforms horizontal movement into a column-by-column scroll, replacing the default behavior where Emacs jumps the view by half a screen horizontally and forces you to visually search for your cursor.

Deferring fontification during input

Fontification (syntax highlighting) requires CPU time to parse and colorize text. In large or complex buffers, this process can block the main thread, leading to input latency. Setting redisplay-skip-fontification-on-input to t causes Emacs to prioritize user input over immediate syntax highlighting:

;; Skip some fontification when input is pending.
(setq redisplay-skip-fontification-on-input t)

The tradeoff is that syntax highlighting may temporarily lag behind the underlying text while your input is actively being processed. When scrolling rapidly into an unseen section of a file or pasting a large block of code, the text might appear uncolored or incorrectly colored for a fraction of a second. However, the delay is virtually unnoticeable, and the correct colors will render as soon as the input queue clears.

Preserving screen position

Setting scroll-preserve-screen-position to t fixes a common visual annoyance when paging up or down through a file (using C-v or M-v). If your cursor is in the middle of the screen and you hit Page Down, the cursor stays exactly in the middle of your monitor:

;; Preserve point's vertical screen position when scrolling.
(setq scroll-preserve-screen-position t)

Note: There is one exception involving next-screen-context-lines (default 2), which controls how many lines of text are repeated from your previous screen when you scroll by a full window. If your cursor is resting on one of these repeated lines at the edge of the window when you jump, Emacs does not preserve its vertical screen position. Instead, it moves the cursor into the newly revealed text. This prevents the cursor from getting stranded at the extreme edge of the window and ensures you have enough surrounding text to read comfortably.

Disabling automatic vertical scrolling

Setting auto-window-vscroll to nil prevents movement and scrolling functions from automatically modifying the window's vertical scroll position when they encounter display rows taller than the window:

;; Do not automatically adjust vertical scrolling through tall display rows.
(setq auto-window-vscroll nil)

This makes vertical movement through buffers containing large elements, such as inline images, much more predictable by avoiding sudden partial-screen shifts. The tradeoff is that tall display rows become more cumbersome to navigate because Emacs no longer automatically scrolls through their partially visible portions to reveal the rest of the element.

Top and bottom scroll errors

By default, Emacs immediately signals an error if you attempt to scroll past the top or bottom of a buffer. Setting scroll-error-top-bottom to t changes this behavior so that your first attempt to scroll past the boundary safely moves the cursor to the exact beginning or end of the document:

;; Move point to the buffer boundary before signaling a scrolling error.
(setq scroll-error-top-bottom t)

Instead of throwing an error immediately, it moves your cursor directly to the first or last character of the buffer. If you attempt to scroll again while your cursor is already resting on that final position, Emacs will then signal the standard scrolling error.

Enabling faster scrolling

Rapid scrolling can become sluggish when Emacs encounters previously unfontified text. Setting fast-but-imprecise-scrolling to t prevents Emacs from becoming unresponsive when you move rapidly through large buffers:

;; Avoid fontifying unfontified text while scrolling rapidly.
(setq fast-but-imprecise-scrolling t)

The primary tradeoff is that scrolling can become visually imprecise. However, this behavior is generally nothing to worry about. For typical programming tasks using a standard monospaced font, line heights remain consistent, meaning you will likely never experience this imprecision at all. Even in modes that do use variable font sizes, the visual jump is minor and temporary. Once you stop scrolling and the input queue clears, Emacs finishes fontifying the visible text and corrects the layout, ensuring your final view is accurate.

Scroll aggressiveness

By default, when point moves beyond the top or bottom of the window, Emacs scrolls the buffer and places point around the middle of the screen. Setting scroll-up-aggressively and scroll-down-aggressively to 0.01 keeps point near the edge of the screen instead:

;; Provide a "stick-to-edge" scrolling experience.
(setq-default scroll-up-aggressively 0.01
              scroll-down-aggressively 0.01)

This results in minimal, predictable scrolling increments.

Permitting scrolling during search

By default, attempting to scroll the window while in an active isearch session (using C-s or C-r) cancels the search. You can allow scrolling without losing your search context.

;; Allow scrolling actions while remaining inside a search block.
(setq isearch-allow-scroll 'unlimited)

This allows you to scroll away from your current match to check another part of the file for reference, and then resume your search by pressing C-s to jump to the next match.

Shell and compilation buffers

When executing long-running processes in interactive shells or REPLs (comint-mode), new output arriving at the bottom of the buffer will frequently scroll your view downward. Setting comint-scroll-to-bottom-on-input to t and comint-scroll-to-bottom-on-output to nil causes Emacs to snap point to the bottom of the buffer only when you supply keyboard input:

;; Auto-scroll to bottom only when you type, not when background output arrives.
(setq-default comint-scroll-to-bottom-on-input t
              comint-scroll-to-bottom-on-output nil)

This allows you to scroll up through compilation logs or terminal history to read errors without the screen aggressively jumping to the bottom every time a new line is printed.

The mouse wheel scrolling

The default mouse wheel behavior in Emacs can feel jumpy, as it scrolls multiple lines per click and accelerates based on scroll speed. You can easily modify this and assign modifier keys to perform horizontal scrolling or text scaling:

;; Scroll one line at a time and map modifier keys to specific actions.
(setq mouse-wheel-scroll-amount
      '(1
        ((shift) . hscroll) ((meta))
        ((control meta) . global-text-scale)
        ((control) . text-scale)))

;; Disable acceleration of scrolling.
(setq mouse-wheel-progressive-speed nil)

Setting mouse-wheel-scroll-amount to 1 configures the mouse wheel to scroll exactly one line vertically, while mapping modifier keys like Shift for horizontal scrolling and Control for text scaling turns your mouse into a navigation multi-tool. Paired with setting mouse-wheel-progressive-speed to nil, this prevents rapid wheel movements from dynamically accelerating the scroll distance.

The result is a predictable, line-by-line scrolling experience that stops you from losing your place, with the tradeoff being that navigating very long files with a physical scroll wheel requires more physical effort since the view will not jump in large increments.

Note: If you prefer a keyboard-driven workflow and want to disable mouse input entirely, check out the inhibit-mouse package.

Modern pixel-precise scrolling

Emacs 29 introduced pixel-precise scrolling for pointing devices that support suitable high-resolution scrolling events.

;; Enable pixel-precise scrolling for supported pointing devices.
(pixel-scroll-precision-mode 1)

;; (setq pixel-scroll-precision-use-momentum nil) ; Optional: disable momentum

Enabling pixel-scroll-precision-mode activates pixel-resolution scrolling for supported mouse and touchpad input, removing the traditional limitation of scrolling by whole text lines.

Configuring Emacs Eglot for Better Performance and Latency

Eglot ships with Emacs as a built-in, lightweight LSP client, and its default configuration is sufficient for most projects. However, working with large codebases can cause latency. When Eglot becomes sluggish, the problem may involve work performed by Eglot and Emacs as well as the language server itself. Event logging, filesystem watching, diagnostics, completion, and other editor activity can all contribute to latency.

This article covers practical changes for keeping Eglot responsive when working with large projects.

Auto-shutting down idle Eglot servers

This reduces resource usage when switching between multiple projects. The eglot-autoshutdown variable shuts down the LSP server after the last managed buffer has been killed.

(setq eglot-autoshutdown t)

This is useful when many separate language servers would otherwise remain running after their buffers have been closed.

(If you find yourself routinely leaving unused buffers open, the buffer-terminator package can automate this cleanup. It quietly monitors your buffer activity and terminates idle file buffers, ensuring eglot-autoshutdown can trigger reliably without requiring you to manually execute kill-buffer.)

Preventing Eglot from blocking the Emacs UI on connection

Opening a project can cause Emacs to block while waiting for the LSP server to initialize. Setting eglot-sync-connect to nil prevents Eglot from blocking on the initial connection, allowing the connection to continue in the background:

(setq eglot-sync-connect nil)

This prevents the connection attempt from unnecessarily delaying interactive editing.

Disabling LSP event logging

Eglot maintains an events buffer containing JSON-RPC activity. Large event logs consume memory and add string allocation costs, especially when retaining complete JSON payloads is not needed during normal editing.

Configuring eglot-events-buffer-config (or eglot-events-buffer-size on Emacs 29 and earlier) reduces the volume of retained data:

;; Disable event logging completely (Emacs >= 30)
(setq eglot-events-buffer-config '(:size 0 :format short))

;; For Emacs <= 29
;; (setq eglot-events-buffer-size 0)

Setting :size 0 disables the events buffer.

Reducing file watchers

Allowing a language server to monitor large portions of a repository can consume OS file descriptors, increase memory usage, and add startup delay. To reduce these resource costs, limit the number of file that Eglot watches using:

(setq eglot-max-file-watches 5000)

In addition, where supported by the language server, configure it to ignore large generated directories such as node_modules, .git, or build output folders. Because these exclusion rules are specific to the language server, they must be passed through the eglot-workspace-configuration variable. (The standard method is to define these rules per-project using a .dir-locals.el file at the root of your repository.)

Disabling progress reporting

Whenever a server reports progress (e.g., during indexing or builds), this can trigger mode-line redisplays. The following suppress mode-line progress reports:

;; Suppress mode-line progress animations
(setq eglot-report-progress nil)

Disabling automatic code action probing

By default, Eglot can asynchronously query the language server for available code actions at point when the cursor is idle. Disabling these automatic indications reduces continuous process communication and background activity during editing:

;; Disable automatic code action indicators to reduce background polling
(setq eglot-code-action-indications nil)

Note: Disabling this feature only removes the automatic UI indicators. You retain the ability to manually request and execute code actions at any time by invoking M-x eglot-code-actions.

Disabling unneeded LSP server capabilities

Note: You do not need to disable all of these capabilities, as some of them are very useful. Only disable the ones you do not use.

LSP servers can provide optional capabilities. Disabling features that are not needed can reduce processing and visual clutter, although the actual performance benefit depends on the language server and project.

Eglot handles on-type formatting by evaluating every single keystroke to check if the connected language server supports the feature and if the typed character is a registered trigger character. If a match occurs, Eglot issues an RPC request and applies the asynchronous edits, which can conflict with external formatters and introduce typing latency. To prevent these background requests and bypass the per-keystroke evaluation entirely, use:

(add-to-list 'eglot-ignored-server-capabilities :documentOnTypeFormattingProvider)

Inlay hints (eglot-inlay-hints-mode) display automatically determined types and parameter names as annotations in the buffer. Disabling them removes the associated visual annotations and any work required to maintain them:

(add-to-list 'eglot-ignored-server-capabilities :inlayHintProvider)

When the cursor rests on a symbol, the LSP server can highlight other occurrences of that symbol (eglot-highlight-eldoc-function). Disabling it will stop Eglot from highlighting other occurrences of the symbol under the cursor:

(add-to-list 'eglot-ignored-server-capabilities :documentHighlightProvider)

For major modes with Tree-sitter support, Tree-sitter already provides syntax-aware fontification locally and can be sufficient for many users. When the language server supports semantic tokens, Eglot can request semantic-token information over LSP and use it for additional syntax highlighting. Processing these responses requires JSON-RPC communication, decoding the token data, and applying the corresponding fontification. On large or frequently changing buffers, this can add CPU overhead. Ignoring the server's semantic-token capability prevents Eglot from using this feature:

(add-to-list 'eglot-ignored-server-capabilities :semanticTokensProvider)

Note: The tradeoff is that some semantic highlighting supplied by the language server may be lost. Tree-sitter fontification and LSP semantic tokens are not identical, so disabling semantic tokens can result in less precise or less detailed highlighting for some languages.

Garbage collection, native compilation, and read process output max

  • The Emacs garbage collector can cause pauses during heavy workloads. LSP activity can increase allocation through diagnostics, completion, JSON processing, and other editor integrations. GC tuning can therefore help in some workloads, but the effect is workload-dependent. Increasing gc-cons-threshold permanently can reduce garbage collection frequency:
    (setq gc-cons-threshold (* 100 1024 1024))
    (Alternatively, several users rely on the gcmh package, which raises the GC threshold during active editing and forces a collection when Emacs becomes idle.)
  • Enable Native Compilation: Ensure that Emacs is built with native compilation support enabled. Native compilation can improve the execution speed of Emacs Lisp code, including code used by Eglot and other packages. (Recommendation: Use the compile-angel package to ensure that all packages are natively compiled.)
  • read-process-output-max: read-process-output-max controls the maximum amount of data Emacs reads from a subprocess in a single operation. Since Eglot communicates with language servers through subprocesses, increasing this value can improve performance when a server sends large bursts of JSON-RPC data, such as during initialization, workspace indexing, or large file updates, by reducing the number of read operations required to consume the output. Raising it can help workloads that regularly receive large bursts of process output:
    (setq read-process-output-max (* 1024 1024))
    (This raises the limit to 1 MiB. It does not reserve 1 MiB of memory for each process or increase the amount of data a server can send; it only allows Emacs to consume more output per read operation. On GNU/Linux systems, the value should not exceed /proc/sys/fs/pipe-max-size)

Freeing up the main Emacs thread with tree-sitter

Emacs executes Lisp, handles asynchronous process output, and updates the UI on a single main thread. In traditional major modes, typing triggers complex regular expression evaluations for syntax highlighting (font-locking). This CPU-bound work competes directly with Eglot, which relies on the exact same thread to deserialize incoming JSON-RPC payloads and render diagnostics. If the main thread is busy computing regexes, Eglot is forced to wait in the queue, resulting in input lag even if the external language server responds instantly.

Major modes built on Tree-sitter (the *-ts-mode variants, such as c-ts-mode, python-ts-mode, and rust-ts-mode) use the Tree-Sitter C library for incremental syntax parsing. If a stable *-ts-mode exists for your programming language, enabling it can improve Eglot's responsiveness.

Copy-paste without Emacs org-mode or markdown-mode Formatting Bleeding Into Other Buffers

When text is copied from an org-mode or markdown-mode buffer and pasted into another Emacs buffer, its visual formatting can sometimes follow it. For instance, text displayed with a particular color, background, or font weight in an Org or Markdown buffer can retain that appearance after being inserted into a Python buffer.

This can be undesirable because the inserted text may then display with the source buffer's face text property instead of relying on the destination buffer's normal font-lock or syntax highlighting.

Understanding the face text property

In Emacs, visual formatting can be represented by the face text property. Some major modes, including org-mode and markdown-mode, make extensive use of text properties, including face, to control how text is displayed. When text containing these properties is yanked (pasted), Emacs can preserve them unless they are explicitly excluded.

Solution 1: Strip faces universally on paste

Emacs provides the yank-excluded-properties variable for specifying text properties that should not be retained when text is yanked (pasted) into a buffer. To prevent face properties from being carried into the destination buffer, add the following to your configuration:

(add-to-list 'yank-excluded-properties 'face)

With this configuration, the face property is removed from the inserted yanked text. The inserted text can therefore be displayed according to the destination buffer's own font-lock and other display rules.

Tradeoff: Once face is included in yank-excluded-properties, yanked text does not retain its face text property. This is generally desirable when the intention is for inserted text to adopt the appearance of its destination buffer, but it may be undesirable for workflows that intentionally use rich text. Examples of workflows and modes where you might want to preserve rich text include enriched-mode, mu4e, or gnus.

For the majority of users, adding 'face to yank-excluded-properties is a worthwhile configuration change. This makes copy and paste more predictable and prevents formatting from leaking into unrelated buffers.

Solution 2: Strip faces per mode on copy

While appending face to the global yank-excluded-properties variable sanitizes pasted text, it applies universally across Emacs. For workflows that require rich text in specific applications like mu4e or gnus, a global configuration is too aggressive.

An alternative way is to strip the visual formatting at the source during the copy operation, rather than at the destination during the paste operation. This can be achieved by applying buffer-local advice to filter-buffer-substring-function:

;; Copy-paste without org-mode or markdown-mode formatting bleeding into other
;; buffers. Alternative to: (push 'face yank-excluded-properties)
;; URL: https://www.jamescherti.com/emacs-fix-org-mode-copy-paste-yank-bleed/
(defun my-strip-face-properties-from-string (string)
  "Remove visual face properties from STRING."
  (remove-text-properties 0 (length string)
                          '(face nil font-lock-face nil)
                          string)
  string)

(defun my-enable-plain-text-copy ()
  "Strip visual face properties from copied text in the current buffer."
  (add-function :filter-return
                (local 'filter-buffer-substring-function)
                #'my-strip-face-properties-from-string))

(add-hook 'markdown-mode-hook #'my-enable-plain-text-copy)
(add-hook 'markdown-ts-mode-hook #'my-enable-plain-text-copy)
(add-hook 'org-mode-hook #'my-enable-plain-text-copy)

;; Other modes
;; (add-hook 'prog-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'text-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'conf-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'diff-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'help-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'info-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'compilation-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'shell-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'eshell-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'magit-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'dired-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'term-mode-hook #'my-enable-plain-text-copy)
;; (add-hook 'vterm-mode-hook #'my-enable-plain-text-copy)

Instead of filtering the text when it is pasted, this solution cleans the text as it is copied, making sure only plain text enters the Emacs clipboard history (the kill ring).

Configuring Emacs Spell Checking with Flyspell, Ispell, and Aspell to Minimize False Positives in Source Code and Prose

The ispell package serves as the underlying interface in Emacs for communicating with external spell checking programs. Building upon this, the flyspell package is a built-in minor mode that provides on-the-fly spell checking. It highlights misspelled words as you type and offers interactive corrections. This article presents a practical configuration for Emacs that sets up ispell to use Aspell for both programming and standard text editing.

Additionally, it addresses a common issue for developers. While writing code, variables such as filepath or buffername in comments or docstrings are often flagged as errors, generating visual noise in your programming buffers. This article contains a specific configuration to reduce these false positives.

Setting the backend and dictionary

The first step is to configure Emacs to use Aspell as the spell-checking engine, set the default dictionary, and reduce unnecessary messages when checking individual words:

;; Set the ispell program name to aspell
;; (switching to aspell will generally offer better performance than ispell.)
(setq ispell-program-name "aspell")

;; Set the global default dictionary for the Ispell process.
(setq ispell-dictionary "en_US")

;; Reduce unnecessary messages when checking individual words.
(setq ispell-quietly t)

Note that this configuration explicitly targets the US English dictionary "en_US". If you write in a different language or require a different regional dialect, such as "en_GB" or "fr_FR", replace "en_US" with your preferred language code.

Configuring aspell flags

The following configures Aspell's suggestion mode to "ultra", which favors very close spelling and phonetic matches when generating suggestions:

;; Configure Aspell's suggestion mode to "ultra", which favors very close
;; spelling and phonetic matches when generating suggestions.
(setq ispell-extra-args '("--sug-mode=ultra"))

Handling compound words in source code

Comments and string literals in source code are heavily populated with compound variable names and technical terms, such as filepath, buffername, or checkbox. Aspell may flag these as errors, generating visual noise in programming buffers.

To resolve this, we pass the --run-together flag to Aspell. This flag allows Aspell to recognize words formed by combining valid dictionary words without spaces. (It only affects words in the comments and strings that Flyspell checks; it does not cause Flyspell to check arbitrary identifiers elsewhere in the source buffer.)

To apply this cleanly, we can define a custom wrapper function that we will later attach to our programming modes using add-hook. This function activates flyspell-prog-mode and then safely appends the required flags buffer-locally using add-to-list, which inherently prevents duplicate arguments if the mode is toggled off and on:

(defun my-flyspell-prog-mode (&rest _args)
  "Enable `flyspell-prog-mode' with buffer-local Aspell arguments."
  ;; The --run-together flag instructs Aspell to accept words formed by
  ;; combining two or more valid dictionary words without spaces, treating the
  ;; resulting string as valid.
  ;;
  ;; This is excellent for source code. Code is heavily populated with
  ;; compound variable names and technical terms (e.g., filepath, buffername,
  ;; checkbox).
  ;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
  (make-local-variable 'ispell-extra-args)
  (dolist (item '("--run-together"
                  ;; "--ignore=2"
                  ;; "--run-together-min=3"
                  ;; "--run-together-limit=4"
                  ;; "--camel-case"
                  ))
    (add-to-list 'ispell-extra-args item))
  (flyspell-prog-mode))

Here are four additional flags worth uncommenting in the my-flyspell-prog-mode function above. Each provides specific advantages for programming workflows, along with some tradeoffs to consider:

  • --run-together-limit=4: Increases the maximum number of combined words allowed in a single string from the default limit of two up to 4. (A higher limit increases the probability that a genuine typo will be silently ignored because Aspell manages to break the misspelling down into a sequence of several shorter valid words. It also adds a slight processing overhead.)
  • --run-together-min=3 sets the minimum length of the individual words that Aspell is allowed to use when recognizing a run-together compound. For example, with a value of 3, Aspell can recognize filepath as file + path, but it is less likely to accept a compound that depends on very short components such as a or in. Increasing this value can therefore reduce false positives in source code by preventing Aspell from accepting suspicious compounds made up of short, common words.
  • --ignore=2: Configures Aspell to completely skip words containing two characters or fewer. Tradeoff: You lose spell checking for actual two-letter English words (such as "is", "an", "to", "of").
  • --camel-case: Enables Aspell's camel-case word handling, allowing camelCase identifiers to be checked more appropriately. It can reduce false positives for identifiers such as myHttpRequest that appear in docstrings and comments. Tradeoff: It can occasionally mask a typo if its components are individually recognized as valid words.

Initializing the Packages

Finally, we use add-hook to bind the spell checking functions to the appropriate Emacs hooks:

;; The flyspell package is a built-in Emacs minor mode that provides on-the-fly
;; spell checking. It highlights misspelled words as you type, offering
;; interactive corrections.
;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
(defun my-flyspell-enable-appropriate-mode ()
  "Enable the appropriate Flyspell mode based on the current major mode."
  (if (or (derived-mode-p 'conf-mode)
          (derived-mode-p 'yaml-mode)
          (derived-mode-p 'yaml-ts-mode)
          (derived-mode-p 'ansible-mode)
          (derived-mode-p 'nxml-mode)
          (derived-mode-p 'sgml-mode))
      (my-flyspell-prog-mode)
    (flyspell-mode 1)))

(add-hook 'prog-mode-hook #'my-flyspell-prog-mode)
(add-hook 'conf-mode-hook #'my-flyspell-enable-appropriate-mode)
(add-hook 'text-mode-hook #'my-flyspell-enable-appropriate-mode)

Note: Configuration files such as YAML and Ansible playbooks can present an edge case because some source-like modes are based on text-mode rather than prog-mode. Without an explicit override, such modes may receive ordinary Flyspell behavior rather than programming-mode behavior. Since these files behave more like source code, we use a custom dispatcher function to apply my-flyspell-prog-mode to them instead.

A few additional interesting options

Several lesser-known variables can be adjusted to change Flyspell's behavior and, in some cases, reduce the amount of work it performs. These options involve tradeoffs: reducing spell-checking activity can improve responsiveness, but may also reduce the amount of text that is checked or the feedback that Flyspell provides.

  • Setting flyspell-issue-message-flag and flyspell-issue-welcome-flag to nil suppresses Flyspell's checking messages and its startup welcome message. This removes minibuffer messages generated by Flyspell without changing the actual spell-checking operation.
  • Setting flyspell-check-changes to a non-nil value causes Flyspell to check only words that have been typed or edited, instead of also checking words that point moves across. This can reduce spell-checking activity when navigating through existing text. The tradeoff is that existing misspellings are not checked because point moves across them, so they may remain undetected until the text is edited or checked explicitly.
  • Setting flyspell-delay-use-timer to a non-nil value makes Flyspell use a timer instead of sit-for when waiting after delayed commands. This allows idle timers and other Emacs code to run during the delay. The option therefore changes how Flyspell waits before performing a check rather than disabling the delay itself.
  • Setting flyspell-mark-duplications-flag to nil prevents Flyspell from reporting repeated words as errors. This eliminates duplicate-word detection, so accidental repetitions such as "the the" are no longer reported.
  • Setting ispell-silently-savep to a non-nil value causes new words added to the personal dictionary to be saved without an interactive confirmation prompt. This avoids the prompt when saving personal-dictionary additions, but also removes that opportunity to review each addition before it is saved.
  • Setting flyspell-highlight-properties to nil prevents Flyspell from highlighting incorrect words when the relevant text already has text properties. The spelling check can still identify the word as incorrect, but Flyspell will not add its normal misspelling highlight when an applicable text property is present. This can be useful when existing text properties should take precedence over Flyspell's visual indication.
  • Lowering flyspell-large-region tells Flyspell to use its faster bulk-checking method for smaller sections of text. Instead of checking each word individually, Flyspell sends the larger section to the external Ispell/Aspell program to check it in one operation. For example, changing the value from 1000 to 200 makes Flyspell use this method for regions containing 200 or more characters. This can improve performance when checking larger sections, but repeated words such as "the the" are not detected when this bulk-checking method is used.
  • Increasing flyspell-delay increases the number of seconds Flyspell waits before checking after a command classified as delayed. This can reduce how frequently spell checking is triggered during rapid editing, at the cost of delaying the appearance of misspelling highlights.

Emacs startup - Why setq beats setopt, customize-set-variable, and use-package :custom?

Every millisecond counts during Emacs initialization. A potential performance cost in modern Emacs configurations is defaulting to setopt, customize-set-variable, or the use-package :custom keyword when a direct assignment with setq is sufficient.

Disclaimer: This article assumes you have a solid understanding of Emacs Lisp and know what you are doing when switching to setq. For users who prefer a hands-off experience, defaulting to setopt or the built-in customization interface is often better for convenience, even though these options are not the fastest. Using setq is mainly intended for users who want to optimize startup performance. Always check the Elisp definition of each defcustom to ensure that switching to setq does not introduce unintended side effects.

What is the difference between setq and other ways to assign values to variables?

setq (and setq-default)

The setq and setq-default functions directly assigns a value to a variable, making it the fastest way to set a variable. (FYI: The following projects prioritize using setq and setq-default: minimal-emacs.d, Doom Emacs, Protesilaos Emacs config, and Spacemacs.)

(The setq function updates a variable's value within the current scope; if the target variable is buffer-local, the assignment only affects the current active buffer while the global state remains unchanged. Conversely, setq-default explicitly modifies the global default value of a variable. Although both functions produce the same result for purely global variables, setq-default must be used to apply system-wide changes to buffer-local variables like tab-width or indent-tabs-mode.)

setopt, customize-set-variable, and use-package :custom

The setopt, customize-set-variable, and use-package :custom functions can be slower than setq. Depending on the variable, they can:

  • Call the variable's defcustom :set function.
  • Validate the value against the variable's declared type.
  • In some cases, load the library defining an autoloaded customizable variable, potentially causing a package to load earlier during startup.

Example of an expansive defcustom :set property

To understand the latency penalty, look at how a package author might write a defcustom with an expensive :set property:

(defcustom my-global-visual-indicator t
  "Toggle a heavy visual indicator across all open buffers."
  :type 'boolean
  :group 'my-ui-package
  :set (lambda (symbol value)
         ;; Update the variable's value
         (set-default symbol value)
         
         ;; The slow part: Iterate through every open buffer
         ;; and trigger a costly visual update or cache rebuild.
         (dolist (buffer (buffer-list))
           (with-current-buffer buffer
             ;; This simulated function might parse the buffer,
             ;; apply text properties, or query a language server.
             (my-heavy-visual-update-function value)))
             
         ;; Force Emacs to immediately redraw all frames
         (redraw-display)))

Using setopt, customize-set-variable, or the use-package :custom to configure my-global-visual-indicator uses executes the associated defcustom :set function during startup:

;; Example 1: SLOW: This triggers the expensive :set function during startup
(setopt my-global-visual-indicator nil)

;; Example 2: This triggers the expensive :set function
(use-package my-ui-package
  :commands (my-ai-package-cmd1 my-ai-package-cmd2) ; defer
  :custom
  (my-global-visual-indicator nil))

In many cases, the Emacs startup phase is simply not the appropriate time to execute those :set functions.

On the other hand, if the variable does not require its Custom setter to establish associated state, using setq inside a use-package :init block bypasses the Emacs Customization setter entirely:

;; Example 1: FAST: This bypasses the :set function for a faster startup
(setq my-global-visual-indicator nil)

;; Example 2: FAST: This bypasses the :set function
(use-package my-ui-package
  :commands (my-ai-package-cmd1 my-ai-package-cmd2) ; defer
  :init
  (setq my-global-visual-indicator nil))

Note: Code placed in the :init block executes before the package loads into memory. Setting variables here ensures your customized values are already established in the global environment before the package evaluates its internal definitions, bypassing the need for a defcustom setter to update its internal state.

Trade-offs when using setq

Here are the trade-offs to consider when using setq:

  • During startup, a direct assignment can avoid UI updates or other side effects performed by a Custom setter. However, setq should only be used when the package does not require that setter to establish associated state.
  • setq does not validate types. If you pass a string where a boolean is expected, Emacs will not warn you at evaluation time.

Rule of thumb

Use setopt, customize-set-variable, or use-package :custom when a variable's Custom :set setter or type validation is required, or if you notice that the option is not being applied as intended. Otherwise, use setq by default.

(To determine whether a specific variable requires a custom :set setter for correct state initialization, inspect its properties directly within Emacs. Execute M-x describe-variable or C-h v and provide the variable's name to view its documentation. In the resulting help buffer, look for a :set attribute. While the presence of a :set function indicates that the package defines initialization logic for when the variable is modified, it does not necessarily mean that using setopt or use-package :custom is required. The necessity depends on the specific implementation, as the majority of major and minor modes automatically execute these initialization functions by default when the mode is activated.)

Improving the Performance of Samsung Galaxy Phones and Tablets

Out of the box, Samsung Galaxy Phones and Tablets are highly responsive, but long-term performance often degrades into noticeable lag. As background services, cached data, and memory paging overhead consume available CPU cycles and RAM, the entire system slows down. This article covers reducing storage I/O bottlenecks by disabling virtual memory paging, controlling process lifecycles through targeted app hibernation, accelerating UI rendering, and automating routine system maintenance.

Storage and I/O Overhead

Disabling RAM Plus

RAM Plus borrows a portion of your devices's regular storage space (NAND flash or UFS) to hold apps you are not actively using so your faster physical memory (RAM) stays clear for current tasks. The main benefit is that it allows you to use more applications in the background without the system forcing them to close due to low memory. However, the performance tradeoff is severe. Regular storage is much slower than actual RAM. Constantly moving data back and forth (paging) creates an I/O bottleneck that causes noticeable interface stutters, delayed app launches, and reduced system responsiveness. Disabling this feature forces the system to rely strictly on high-speed physical RAM, immediately removing a major source of system lag.

To disable RAM Plus:

  • Navigate to Settings > Device care (or Battery and device care).
  • Tap 'Memory'.
  • Tap 'RAM Plus' and toggle it to 'Off'.

Note: On older One UI versions lacking an "Off" toggle, you will need to select the lowest available gigabyte value instead.

Process Lifecycle Management

Background execution does not just drain the battery. It actively steals CPU time from the application you are currently trying to use, leading to sluggish behavior. Samsung Galaxy devices and Android provide ways to limit this background activity, freeing up processing power for foreground tasks.

App Hibernation Tiers and Deep Sleeping Apps

Samsung provides built-in mechanisms to restrict background execution.

  • Sleeping Apps: Apps placed in this category are restricted from running freely in the background, and some background activity may still occur. Notifications can be delayed.
  • Deep Sleeping Apps: Apps in this category do not run in the background. App updates and notifications are delivered only when the app is opened. Assign rarely used utilities, travel, and shopping apps here.

Manually routing infrequently used applications to Deep Sleeping Apps guarantees they cannot cause background slowdowns. Adding unused apps to the sleeping-app categories can help conserve battery and reduce background activity. Here is how to manage this:

  • Navigate to Settings > Device care > Battery > Background usage limits.
  • Add unused or rarely used apps to 'Deep sleeping apps'.

Add only apps that do not need immediate background activity or notifications. Keep apps such as messaging, email, or other notification-dependent applications out of Deep Sleeping Apps when timely notifications are required. For apps that stream content in the background like Spotify or YouTube, change their battery optimization setting back to Optimized when necessary for background playback.

Display Hardware

Reduce Animation Durations in Developer Options

Reducing animation scales shortens window transitions, which can make the interface feel more responsive:

  • Enable Developer Options: Go to Settings > About phone > Software information and tap 'Build number' 7 times.
  • Navigate to Settings > Developer options.
  • Set 'Window animation scale', 'Transition animation scale', and 'Animator duration scale' to 'off'.

System Maintenance and Debloating

Enable Automated Restarts

Memory leaks and orphaned background processes accumulate over days of uptime, severely degrading performance. Rebooting periodically flushes the system memory (RAM) and kills hanging processes, preventing the gradual slowdown that plagues devices with long uptimes.

To configure automated device restarts:

  • Navigate to Settings > Device care > Auto optimization > Auto restart.
  • Enable 'Restart when needed'. This allows the operating system to trigger a reboot automatically when it detects performance degradation.
  • Select 'Restart on schedule' to enforce a routine. Configuring a daily reboot during off-hours, such as 5:00 AM, ensures your device starts fresh every morning without interrupting your workflow.

Remove unnecessary pre-installed packages

Removing pre-installed carrier and manufacturer apps directly targets hidden background activity that silently consumes CPU cycles and RAM. For example, third-party partner applications like Facebook, LinkedIn, Outlook, and Microsoft 365 are completely safe to uninstall or disable. Similarly, you can safely remove unused Samsung-specific services such as AR Zone, Samsung Free, Galaxy Shop, Gaming Hub, and Global Goals without affecting the core operating system.

However, the strict rule of thumb for removing or disabling built-in software is to never touch a package unless you can verify its exact function. Blindly disabling unknown system-level processes can cause issues.

Removing or disabling applications on a Samsung Galaxy device is a straightforward process:

  • Navigate to Settings > Apps.
  • Scroll through the list, tap on the application you want to remove, and select Uninstall at the bottom of the screen. If the application is part of the pre-installed system software, the uninstall option might be missing; in this scenario, tap Disable instead to prevent the app from running in the background and remove it from your app drawer.

(Alternatively, for a faster method, you can simply long-press any app icon directly on your home screen or within the app drawer and tap the Uninstall or Disable button that appears in the pop-up menu.)

single-window.el - Always Open Emacs Buffers in the Current Active Window

Build Status
License

The single-window package forces Emacs to open buffers in the current active window.

It keeps your carefully arranged layouts intact, reduces visual clutter, and provides a much more predictable workflow.

To ensure it does not break standard Emacs functionality, the package is built to handle the following edge cases and integrations out of the box:

  • Transient and Magit: Excludes Transient buffers by default, which ensures that Magit popup menus render correctly and manage their own window placement.
  • Ediff control panel: Excludes the Ediff control interface so it can maintain its specific layout requirements without breaking.
  • Temporary and utility buffers: Ignores the minibuffer, asynchronous Emacs warnings, Org capture popups, and built-in *Completions* buffers so they do not hijack your active workspace.
  • Dedicated windows: Safely handles dedicated windows in the background (e.g., grep-mode or embark-export), temporarily un-dedicating them to load the buffer without throwing errors or breaking the layout.
  • Org-mode integrations: Configures Org-mode to open source blocks, the agenda, and indirect buffers directly in the active window
  • Manual overrides: Allows you to temporarily bypass the single-window enforcement by passing a prefix argument (e.g., C-u) before running a command.

The package also provides the following customization options:

  • Custom window rules: Provides a setting (single-window-respect-display-buffer-alist) that lets you prioritize your own custom display rules for specific buffers, while falling back to the single-window behavior for everything else.
  • Customizable exclusions: Allows you to define additional exclusions via the single-window-exclude-regexps variable, which accepts a list of regular expressions to match ignored buffer names.
  • Popper integration: Provides single-window-exclude-popper (disabled by default) to allow popper to bypass the single-window package enforcement for its popups.

If this project helps your workflow, please consider supporting it by ⭐ starring single-window on GitHub and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms so other Emacs users can discover its benefits.

Installation and Usage

Emacs: use-package and straight (Emacs version < 30)

To install single-window with straight.el:

Step 1: It if hasn't already been done, add the straight.el bootstrap code to your init file.

Step 2: Add the following code to the Emacs init file:

(use-package single-window
  :straight (single-window
             :type git
             :host github
             :repo "jamescherti/single-window.el")
  :config
  (single-window-mode 1))

Alternative installation: use-package and :vc (Built-in feature in Emacs version >= 30)

To install single-window with use-package and :vc (Emacs >= 30):

(use-package single-window
  :vc (:url "https://github.com/jamescherti/single-window.el"
       :rev :newest)
  :config
  (single-window-mode 1))

Alternative installation: Doom Emacs

Here is how to install single-window on Doom Emacs:

Step 1: Add to the ~/.doom.d/packages.el file:

(package! single-window
  :recipe
  (:host github :repo "jamescherti/single-window.el"))

Step 2: Add to ~/.doom.d/config.el:

(after! single-window
  (single-window-mode 1))

Step 3: Run the doom sync command:

doom sync

Frequently Asked Questions

What does this provide over display-buffer-alist?

The single-window package provides a minor mode that handles edge cases.

While similar behavior can be replicated by adding a catch-all rule to display-buffer-alist, this often disrupts standard Emacs functionality.

For instance, forcing all buffers into the active window via display-buffer-alist interferes with packages that rely on specific user interface layouts, such as Ediff, Transient (which includes Magit), Org Agenda, and many others. This package resolves this by providing default configurations that natively exclude these specific edge cases.

Additionally, the package manages dedicated windows. For example, if a user attempts to open a file directly from a dedicated grep-mode or embark-export search results buffer, standard display rules will often cause Emacs to split the frame. The single-window package intercepts this action, temporarily removes the dedicated flag so the buffer can load in the active window, and then restores the original state.

It also includes a fallback mechanism. If Emacs is forbidden from using the current window (e.g., you invoke a command while inside the minibuffer), Emacs usually splits the frame. This package catches that edge case and safely uses another existing window instead.

Finally, the package allows bypassing the strict window enforcement for individual commands by supplying a prefix argument (C-u).

What is the difference with popper?

The single-window and popper packages are not mutually exclusive and can be used simultaneously.

The popper package displays temporary buffers in a dedicated popup area, typically at the bottom of the screen, which allows for quick dismissal.

In contrast, single-window prevents automated window splitting. It also provides a mechanism for exceptions, allowing specific packages, such as Transient, and popper itself, to bypass the strict single-window enforcement and split the frame as originally intended.

Author and License

The single-window Emacs package has been written by James Cherti and is distributed under terms of the GNU General Public License version 3, or, at your choice, any later version.

Copyright (C) 2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program.

See also

Links

Other Emacs packages by the same author:

  • compile-angel.el: Speed up Emacs! This package guarantees that all .el files are both byte-compiled and native-compiled, which significantly speeds up Emacs.
  • outline-indent.el: An Emacs package that provides a minor mode that enables code folding and outlining based on indentation levels for various indentation-based text files, such as YAML, Python, and other indented text files.
  • easysession.el: Easysession is lightweight Emacs session manager that can persist and restore file editing buffers, indirect buffers/clones, Dired buffers, the tab-bar, and the Emacs frames (with or without the Emacs frames size, width, and height).
  • vim-tab-bar.el: Make the Emacs tab-bar Look Like Vim's Tab Bar.
  • elispcomp: A command line tool that allows compiling Elisp code directly from the terminal or from a shell script. It facilitates the generation of optimized .elc (byte-compiled) and .eln (native-compiled) files.
  • tomorrow-night-deepblue-theme.el: The Tomorrow Night Deepblue Emacs theme is a beautiful deep blue variant of the Tomorrow Night theme, which is renowned for its elegant color palette that is pleasing to the eyes. It features a deep blue background color that creates a calming atmosphere. The theme is also a great choice for those who miss the blue themes that were trendy a few years ago.
  • Ultyas: A command-line tool designed to simplify the process of converting code snippets from UltiSnips to YASnippet format.
  • dir-config.el: Automatically find and evaluate .dir-config.el Elisp files to configure directory-specific settings.
  • flymake-bashate.el: A package that provides a Flymake backend for the bashate Bash script style checker.
  • flymake-ansible-lint.el: An Emacs package that offers a Flymake backend for ansible-lint.
  • inhibit-mouse.el: A package that disables mouse input in Emacs, offering a simpler and faster alternative to the disable-mouse package.
  • quick-sdcv.el: This package enables Emacs to function as an offline dictionary by using the sdcv command-line tool directly within Emacs.
  • enhanced-evil-paredit.el: An Emacs package that prevents parenthesis imbalance when using evil-mode with paredit. It intercepts evil-mode commands such as delete, change, and paste, blocking their execution if they would break the parenthetical structure.
  • stripspace.el: Ensure Emacs Automatically removes trailing whitespace before saving a buffer, with an option to preserve the cursor column.
  • persist-text-scale.el: Ensure that all adjustments made with text-scale-increase and text-scale-decrease are persisted and restored across sessions.
  • pathaction.el: Execute the pathaction command-line tool from Emacs. The pathaction command-line tool enables the execution of specific commands on targeted files or directories. Its key advantage lies in its flexibility, allowing users to handle various types of files simply by passing the file or directory as an argument to the pathaction tool. The tool uses a .pathaction.yaml rule-set file to determine which command to execute. Additionally, Jinja2 templating can be employed in the rule-set file to further customize the commands.
  • kirigami.el: The kirigami Emacs package offers a unified interface for opening and closing folds across a diverse set of major and minor modes in Emacs, including outline-mode, outline-minor-mode, outline-indent-minor-mode, org-mode, markdown-mode, vdiff-mode, vdiff-3way-mode, hs-minor-mode, hide-ifdef-mode, origami-mode, yafolding-mode, folding-mode, and treesit-fold-mode. With Kirigami, folding key bindings only need to be configured once. After that, the same keys work consistently across all supported major and minor modes, providing a unified and predictable folding experience.
  • buffer-guardian.el: Automatically saves Emacs buffers without requiring manual intervention. By default, it triggers a save when the user switches to another buffer, switches to another window or frame, Emacs loses focus, or the minibuffer is opened. Beyond standard file buffers, buffer-guardian also manages specialized editing buffers such as org-src and edit-indirect. Additional features, disabled by default, include periodic or idle-time saving of all buffers, automatic exclusion of remote, nonexistent, or large files, and support for custom exclusion rules via regular expressions or predicate functions.

jc-gentoo-portage - An opinionated, performance-oriented Gentoo Portage /etc/portage configuration

The jc-gentoo-portage repository houses an opinionated, performance-oriented Gentoo Portage (/etc/portage) configuration.

This repository can be used as an inspiration to build a lean and fast Gentoo operating system.

Features:

  • Maximizes execution performance across the system by globally enabling xs, asm, orc, jit, and threads, forcing packages to use hand-written assembly routines and JIT compilation loops.
  • Disables telemetry and remote RPC calls by globally setting -telemetry, -geoclue, -geolocation, -cloudproviders, -google, and -gnome-online-accounts.
  • Optimizes Rust compiler outputs by enforcing modern instruction sets via target-cpu=native, opt-level=3, and strip=symbols.
  • Links long-running daemons and high-throughput parsing utilities against jemalloc to guarantee flat memory profiles and predictable multi-threaded performance during extended uptimes.
  • Prunes legacy hardware probing and obsolete I/O interfaces by globally disabling optical media (-cdrom, -dvd, -dvdr, -css) and deprecated X11 video rendering (-xv).
  • Removes documentation bloat by globally disabling -doc, -gtk-doc, and -handbook. This instructs the build system to skip the generation of extraneous HTML manuals and localized help files, reducing compilation times and the final disk footprint.
  • Optimizes the linker aggressively using -Wl,-O2 -Wl,--sort-common -Wl,--as-needed -Wl,-z,pack-relative-relocs to compress relocation tables, drop unused dependencies, and group global variables by alignment for faster binary loads.
  • Standardizes the multimedia stack exclusively on PipeWire while enforcing hardware-accelerated video decoding.
  • Restricts the GNOME desktop by stripping network discovery protocols from file managers (-samba, -ads, -acl).
  • Isolates high-load compilation processes to background resources using PORTAGE_SCHEDULING_POLICY="idle", PORTAGE_IONICE_COMMAND="ionice -c 3 \${PID}", and a custom notmpfs.conf to prevent Out of Memory errors on massive packages.
  • Compiles binary packages and man pages using maximum compression via BINPKG_COMPRESS_FLAGS_ZSTD="-19 -T0", utilizing all CPU threads.

Installation

Step 1: Ensure the system is set to the compatible 23.0 systemd desktop profile

eselect profile set default/linux/amd64/23.0/desktop/systemd

(This ensures that the base system dependencies, compiler configurations, and default USE flags are aligned for a modern systemd-based graphical desktop environment.)

Step 2: Install requirements

emerge -av app-portage/cpuid2cpuflags app-arch/zstd dev-vcs/git

(cpuid2cpuflags is required to detect your host processor's hardware capabilities. zstd is installed to provide high-speed compression for Portage build operations and binary packages, and git is required to clone this repository.)

Step 3: Clone the Repository

git clone https://github.com/jamescherti/jc-gentoo-portage /etc/portage

Step 4: Run /etc/portage/scripts/init-portage

/etc/portage/scripts/init-portage

(This script creates /var/portage-notmpfs to prevent compilation failures for massive packages that run out of space when building in RAM. It also generates /etc/portage/make-local.conf, which establishes a safe, untracked location for machine-specific overrides. By default, it populates this file with a MAKEOPTS setting configured to use half of your system's processors. Finally, the script uses the cpuid2cpuflags command to dynamically query your hardware for supported instruction sets, such as AVX2 or SSE4, and writes them to /etc/portage/package.use/00my-cpu-flags. This ensures that all subsequently compiled software is fully optimized for your specific processor.)

Step 5: Create make.profile

cd /etc/portage
ln -sf ../../var/db/repos/gentoo/profiles/default/linux/amd64/23.0/desktop/systemd make.profile

(Portage relies on the make.profile symlink to determine which system profile is currently active. Creating this link manually ensures Portage resolves the dependency graph and default variables accurately from the downloaded Gentoo repository tree.)

Step 6: Recompile GCC using this Portage configuration, which enables Profile-Guided Optimization (PGO) and Link-Time Optimization (LTO) to maximize compilation throughput

emerge -av sys-devel/gcc

Step 7: Begin customizing /etc/portage

Begin customizing /etc/portage to fit your specific requirements and install packages using emerge.

Repository Structure

To effectively customize this configuration, you need to understand its layout:

  • make.conf: The primary configuration file. It contains global compiler flags (CFLAGS, CXXFLAGS), MAKEOPTS, global USE flags, and FEATURES.
  • package.use/: A directory containing modular files that define USE flags on a per-package basis. Files are categorized logically (e.g., gnome, sound-server, optimize).
  • package.accept_keywords/: Allows the installation of specific testing or unstable packages on a stable system.
  • package.mask/ and package.unmask/: Used to block or allow specific package versions.

Customizing USE Flags (package.use)

The package.use/ directory is modular. You should read through the files and remove entries for software you do not intend to install. If you need a feature that is disabled globally in make.conf (like nls or bluetooth), do not enable it globally. Instead, enable it only for the specific package that requires it by creating a new entry in package.use/.

Force English

For users who don't need localization, setting -nls, -cjk, and -ibus globally forces interfaces to English, which skips the compilation of thousands of unneeded localization files:

File: /etc/portage/package.use/00my-just-english

# Global exclusion of the Intelligent Input Bus (IBUS).
#
# Justification:
# - Performance: Prevents unnecessary background daemon processes.
# - Footprint: Eliminates complex dependencies and reduces system bloat.
# - Security: Minimizes the attack surface by removing unused system services.
#
# Note:
# This assumes that no specialized Input Method Editors (IME) are required for
# non-Latin script input. If localized language support is needed in the future,
# this flag must be re-evaluated.
*/* -ibus

# The exclusion of nls (Native Language Support) is a deliberate choice to
# simplify the dependency graph and simplify the package installation process.
# By setting USE="-nls", you instruct Portage to ignore internationalization
# libraries and omit the compilation of localized message files, ensuring that
# all software interfaces default to English. This configuration is particularly
# beneficial on Gentoo because it prevents unnecessary interactions with the
# gettext utility and significantly reduces the total number of files installed
# across your system. Consequently, your updates will finish faster, and you
# will regain valuable disk space that would otherwise be occupied by dozens of
# translation files you do not need, resulting in a cleaner and more efficient
# OS environment.
#
# cjk: cjk (Chinese, Japanese, Korean), safe to disable globally.
*/* -nls -cjk

Disable smartcard

File: /etc/portage/package.use/00my-no-smartcard

# Disabling smartcard support prevents packages like app-crypt/gnupg and
# net-misc/openssh from linking against smartcard-reading libraries.
#
# Warning: Do not apply this mask if you rely on a physical hardware token (such
# as a YubiKey or Nitrokey) for SSH authentication, GPG commit signing, or LUKS
# disk decryption.
*/* -smartcard

NVIDIA GPU

File: /etc/portage/package.use/00my-hw-gpu-nvidia

*/* VIDEO_CARDS: -* nvidia

# Opts into native NVIDIA hardware video encoding/decoding
*/* nvenc nvdec vdpau

# Enables VA-API support across applications.
*/* vaapi

Intel CPU

File: /etc/portage/package.use/00my-hw-cpu-intel

# Tells Portage to only install the microcode files necessary for the host CPU.
sys-firmware/intel-microcode hostonly

# Enables VA-API support across applications.
*/* vaapi

# Enable Intel Quick Sync Video
# Ensure every media application on the system compiles with Intel Quick Sync
# Video support if the package supports it. For a dual-GPU setup containing an
# Intel iGPU and an NVIDIA discrete card, the primary benefit is systematic
# workload isolation. It allows offloading everyday video decoding and
# background encoding tasks across all applications (such as media players,
# transcoders, and broadcasting tools) directly to the Intel processor. This
# strategy keeps the NVIDIA card completely free from media processing overhead,
# reserving its full hardware capacity for demanding tasks like 3D rendering or
# compute workloads, while avoiding the need to configure flags on a per-package
# basis.
*/* qsv

Intel Integrated graphics

File: /etc/portage/package.use/00my-hw-intel-integrated-graphics

*/* VIDEO_CARDS: -* intel

# Enables VA-API support across applications. This allows the Intel integrated GPU
# to handle hardware acceleration paths natively via Intel Quick Sync.
*/* vaapi

# Enable Intel Quick Sync Video
# Ensure every media application on the system compiles with Intel Quick Sync
# Video support if the package supports it. For a dual-GPU setup containing an
# Intel iGPU and an NVIDIA discrete card, the primary benefit is systematic
# workload isolation. It allows offloading everyday video decoding and
# background encoding tasks across all applications (such as media players,
# transcoders, and broadcasting tools) directly to the Intel processor. This
# strategy keeps the NVIDIA card completely free from media processing overhead,
# reserving its full hardware capacity for demanding tasks like 3D rendering or
# compute workloads, while avoiding the need to configure flags on a per-package
# basis.
*/* qsv

# Disable VDPAU backend
*/* -vdpau

Intel audio + USB audio

File: /etc/portage/package.use/00my-hw-audio-intel

# Intel audio + USB audio
*/* ALSA_CARDS: -* hda-intel usb-audio

Scanner: Disabling all sane backends

For users who use scanners requiring proprietary drivers, such as those from Brother, it is recommended to disable all SANE backends.

File: /etc/portage/package.use/00my-hw-scanner

# Disable all sane backends
*/* SANE_BACKENDS: -*

systemd-boot and dracut for sys-kernel/gentoo-kernel or sys-kernel/gentoo-kernel-bin users

The systemd-boot makes installkernel manage bootctl entries dynamically using the Boot Loader Specification (BLS). This creates individual menu options for each installed kernel version, providing an automatic fallback if a new kernel fails to boot.

First, edit /etc/kernel/install.conf and add the following lines to define the kernel installation layout:

layout=bls
initrd_generator=dracut
uki_generator=none

Next, edit /etc/portage/package.use/00my-systemd-boot to apply the required USE flags. This enables dracut and systemd-boot for installkernel and sets the dist-kernel flag globally. Add the following lines:

sys-kernel/installkernel dracut systemd-boot
sys-apps/systemd boot

# When dist-kernel is set, Portage will automatically trigger Dracut to
# build the initramfs and automatically rebuild out-of-tree modules
# (like nvidia-drivers) whenever a new sys-kernel/gentoo-kernel is installed.
*/* dist-kernel

Dracut + NVIDIA: Force NVIDIA Driver Inclusion in the Early Boot Sequence

By default, Dracut may omit out-of-tree drivers during initramfs generation. Force the inclusion of the essential NVIDIA kernel components, including the core driver, modesetting controls, unified virtual memory, and direct rendering manager layers, directly into the early boot image. This step also ensures the open-source Nouveau driver is explicitly omitted to prevent conflicts.

First, edit /etc/dracut.conf.d/nvidia.conf and add the following lines to bind the required drivers:

add_drivers+=" nvidia nvidia_modeset nvidia_uvm nvidia_drm "
omit_drivers+=" nouveau "

Next, regenerate all of your initramfs images to apply the configuration. Run your bootloader or kernel management tool to trigger the rebuilding process, or execute your system's standard initramfs generation utility with the force flag to ensure the changes are baked into the early boot sequence.

Customizing /etc/portage/make-local.conf

The jc-gentoo-portage repository tracks the primary make.conf file via Git. Modifying make.conf directly to add hardware specifics will cause merge conflicts whenever git pull is executed to update the repository with upstream changes.

To prevent this, the provided make.conf automatically sources /etc/portage/make-local.conf at the end of its execution. By placing all system-specific overrides (such as GOAMD64, MAKEOPTS, or CFLAGS) inside make-local.conf, these settings successfully override the global defaults while keeping the Git working tree clean. This allows upstream updates to be applied without manual conflict resolution.

Open /etc/portage/make-local.conf and modify the variables to match your system resources. For example, modify MAKEOPTS based on your CPU core count and available RAM. A common rule is -jN -lN where N is your logical CPU core count.

Go Compiler Optimizations (GOAMD64)

The GOAMD64 environment variable specifies the microarchitecture level of the amd64 (x86-64) architecture that the Go compiler targets. Setting GOAMD64="v3" inside /etc/portage/make-local.conf forces the Go compiler to generate machine code leveraging newer CPU instructions, such as AVX2, BMI1, BMI2, F16C, FMA, LZCNT, MOVBE, and OSXSAVE.

While C and C++ compiler optimizations are managed via CFLAGS and CXXFLAGS (e.g., -march=x86-64-v3), the Go compiler ignores these flags. Many modern utilities packaged in Gentoo are written in Go (including Docker, Kubernetes, and Terraform). When Portage builds these packages from source, the ebuilds read Go-specific environment variables.

To determine the correct target level for a specific hardware setup, processor capabilities must be inspected. CPU flags can be checked by running grep -m 1 '^flags' /proc/cpuinfo and matching them against the following levels:

  • v1: The baseline x86-64 architecture. This is appropriate for distributing compiled binaries to unknown hardware or for processors older than 2008.
  • v2: Requires popcnt and sse4_2. This is intended for older processors released around 2008 to 2013, such as Intel Nehalem or AMD Jaguar.
  • v3: Requires avx2. This is intended for modern processors released after 2014, such as Intel Haswell or AMD Excavator.
  • v4: Requires avx512f. This is intended for the latest enterprise or high-end desktop processors, such as Intel Skylake-X or AMD Zen 4.

Once the highest supported level is identified, the variable can be appended to the local configuration:

echo 'GOAMD64="v3"' >> /etc/portage/make-local.conf

Explicitly declaring GOAMD64="v3" in /etc/portage/make-local.conf ensures Portage applies hardware-specific optimizations to all compiled Go binaries. If this variable is omitted, the Go compiler defaults to v1, generating universally compatible but unoptimized code. A higher tier should only be set if the target processor explicitly supports the required instruction sets.

FEATURES: buildpkg

If you manage multiple identical or similar Gentoo machines, use FEATURES="buildpkg" on your fastest machine to compile binaries once, then distribute them to your other machines using emerge --usepkg.

echo 'FEATURES="$FEATURES buildpkg"' >> /etc/portage/make-local.conf

Other customizations

Install the latest testing kernel

Unmask the testing versions (~amd64) of the Gentoo distribution kernels. This allows Portage to look past the stable tree and fetch the latest upstream kernel updates.

First, edit /etc/portage/package.accept_keywords/00my-latest-gentoo-kernel and add the following lines to accept the testing keywords:

sys-kernel/gentoo-kernel-bin ~amd64
sys-kernel/gentoo-kernel ~amd64
virtual/dist-kernel ~amd64

Next, run your standard package upgrade command to pull in the new kernel version, and use your system's kernel selection utility to set the newly installed kernel as the default.

Then run:

emerge --ask --with-bdeps=y --update --deep --changed-use @world

Finally, use eselect kernel list and eselect kernel set <version> to select the newly installed kernel.

Temporary File Systems (tmpfs) Optimization

Gentoo compiles the majority of software from source code via Portage, a process that generates a significant volume of intermediate build artifacts. By default, Portage writes these temporary files to the physical storage device at /var/tmp/portage.

Shifting high-volume compilation I/O operations into memory substantially reduces solid-state drive wear while exploiting the superior read and write speeds of RAM to eliminate storage bottlenecks.

To extend hardware longevity and minimize extraneous disk writes across the entire operating environment, offload the standard system temporary directories to RAM. Append these corresponding entries to /etc/fstab:

tmpfs    /tmp        tmpfs    rw,nodev,nosuid,size=8G    0 0
tmpfs    /var/tmp    tmpfs    rw,nodev,nosuid,size=8G    0 0

Configuring the following entry in /etc/fstab mounts the directory as a tmpfs allocation. The size parameter establishes a hard limit on memory consumption to prevent resource exhaustion during the build process.

tmpfs    /var/tmp/portage     tmpfs     size=16G,uid=portage,gid=portage,mode=775,nosuid,noatime,nodev    0  0

Storage Optimization & Encryption (LUKS / SSD)

For systems utilizing an encrypted root filesystem on solid-state storage (SSD/NVMe), specialized kernel parameters are required to maintain storage performance.

By default, dm-crypt/LUKS containers block TRIM requests for security reasons, which can degrade SSD performance and longevity over time. If sys-kernel/genkernel is used to manage the initramfs, append the following parameter to the bootloader kernel command line (e.g., in grub.cfg or refind.conf):

root_trim=yes

This parameter instructs the initramfs script to pass the --allow-discards option to cryptsetup during the initial phase of the boot sequence. This allows the root filesystem to successfully pass discard/TRIM commands through the encryption layer down to the underlying physical controller.

This parameter is unique to genkernel. If the initramfs is built using sys-kernel/dracut, this flag will be ignored; standard Dracut configuration or the rd.luks.options=discard kernel parameter must be used instead.

Enabling TRIM on an encrypted device exposes disk usage patterns and filesystem layout to an attacker with physical access to the drive. For standard operational profiles, the performance and hardware longevity benefits outweigh this minor metadata leakage.

Using the Ninja Build System

Ninja is a efficient build system that evaluates dependencies rapidly and execute multiple build tasks concurrently. Replacing standard make with Ninja provides performance improvements during the compilation phase, particularly for large C and C++ projects.

Benefits of using Ninja:

  • Faster dependency resolution and startup time.
  • Improved management of parallel build processes.
  • Noticeably reduced compilation times for heavy packages.

To configure Portage to use Ninja globally for CMake-based ebuilds, you must install the package and declare it as the default generator.

Install Ninja:

emerge -av dev-build/ninja

Append the generator variable to your local configuration:

echo 'CMAKE_MAKEFILE_GENERATOR="ninja"' >> /etc/portage/make-local.conf

This configuration ensures that any package using CMake will use Ninja instead of traditional make to process the build.

Maintenance

After applying this configuration or making your own modifications, you must instruct Portage to evaluate the dependency tree and apply the changes to your live system.

Apply the new USE flags and update the system:

emerge --ask --verbose --update --deep --newuse @world

Then remove orphaned dependencies that are no longer required:

emerge --ask --depclean

License

The jc-gentoo-portage files were written by James Cherti and are distributed under terms of the MIT license.

Copyright (C) 2022-2026 James Cherti.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Links

Other projects by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-gnome-settings: GNOME customizations that can be applied programmatically.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-xfce-settings: GNOME customizations that can be applied programmatically.
  • watch-xfce-xfconf: A command-line tool that can be used to configure XFCE 4 programmatically using the xfconf-query commands displayed when XFCE 4 settings are modified.

git-rexec: Find Git Repositories and Execute Commands Against Them, either Sequentially or in Parallel

The git-rexec command-line tool recursively locates Git repositories within a directory and executes commands against them, either sequentially or in parallel.

Here are examples demonstrating how to use git-rexec:

Example 1: Execute git status -s across all discovered Git repositories (found by searching recursively under the current working directory) in parallel (-p or --parallel):

git-rexec -p -- git status -s

Example 2: Fetch updates across all discovered repositories, limiting the concurrency to 5 background jobs (-j 5), which helps avoid network congestion or server rate limits when communicating with upstream Git remotes:

git-rexec -j 5 --parallel -- git fetch

Example 3: Include sub-repositories (e.g., Git worktrees and submodules) alongside standard Git repositories during discovery (-s or --include-sub-repos), and execute git status against them:

git-rexec -s -p -- git status

Example 4: Target a specific base directory (~/projects) using the -C flag to recursively discover repositories within it, while explicitly excluding a specific subfolder (~/projects/archive). This example executes git status -s in parallel for all discovered repositories except those within the excluded path:

git-rexec -C ~/projects --exclude-dir ~/projects/archive --parallel -- git status -s

Example 5: Evaluate whether a README.md file exists in the repository (sh -c "test -f README.md"). If the condition returns an exit status of 0 (success), it counts the number of lines in that file (wc -l README.md):

git-rexec --if-exec 'sh -c "test -f README.md"' --parallel -- wc -l README.md

Example 6: Print the paths of all discovered Git repositories:

git-rexec --print

If this helps your workflow, please support the project by ⭐ starring git-rexec on GitHub and sharing it on your website, blog, Mastodon, Reddit, X, LinkedIn, or other social media platforms to help more Git users discover its benefits.

Features

  • Recursively discover Git repositories starting from a specified root directory.
  • Optional flag to include sub-repositories (e.g., Git worktrees and submodules) in the execution target list.
  • Execute shell commands across multiple repositories in parallel using worker threads.
  • Filter target repositories based on the exit code of a conditional check (--if-exec).
  • Exclude specific directories from the search path.
  • Export discovered repository paths for integration with other shell tools (using --print or --print0).

Installation

Method 1: Manual Installation (System-wide)

Download the git-rexec script, make it executable, and copy it to a directory in your system PATH (e.g., /usr/local/bin):

sudo cp git-rexec /usr/local/bin/

Method 2: Installation via pip

Install the package directly from the Git repository using pip:

pip install --user git-rexec

Dependencies

System Dependencies

  • git: Required for repository validation and execution.

Python Dependencies (Optional)

  • colorama: Provides color-coded terminal output.
  • setproctitle: Sets the process title for process monitoring tools.

You can install the optional Python dependencies via pip:

pip install colorama setproctitle

Usage

git-rexec [OPTIONS] -- [exec_cmd ...]

(Assuming the git-rexec script is executable and in your PATH.)

Positional Arguments

  • exec_cmd: The shell command to execute within each discovered Git repository. You can use -- to pass options directly to the command. If omitted, the script simply prints the paths of the discovered repositories.

Options

usage: git-rexec [-h] [-C DIRECTORY] [--exclude-dir EXCLUDE_DIR] [-p] [-i IF_EXEC] [-j MAX_WORKERS] [-q] [-s] [--print] [--print0] [exec_cmd ...]

Find Git repositories and execute commands against them in parallel.

positional arguments:
  exec_cmd              The command to execute. You can use -- to pass options.

options:
  -h, --help            show this help message and exit
  -C, --directory DIRECTORY
                        Root directory to search (defaults to current directory)
  --exclude-dir EXCLUDE_DIR
                        Exclude a specific directory and all of its subdirectories
  -p, --parallel        Execute the command in parallel using threads
  -i, --if-exec IF_EXEC
                        Execute commands only if this check returns exit code 0.
  -j, --jobs MAX_WORKERS
                        Maximum number of processors/workers to use
  -q, --quiet           Quiet mode. Suppresses the informational log prefixes ([EXEC] and [EXEC-P]) that precede execution output.
  -s, --include-sub-repos
                        Include sub-repositories (e.g., Git worktrees and submodules)
  --print               Print the paths (only when no command is provided)
  --print0              Separate the paths with a null character (only when no command is provided)

License

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

Copyright (C) 2019-2026 James Cherti.

Links

ansible-cleanup - Cleanup an Ansible project

Ansible-cleanup provides a command line tool to find and remove unused playbooks, tasks, group variables, and host variables. It maintains a clean codebase by recursively scanning your Ansible repository and listing files that are safe to delete.

Features

  • Identify unused playbooks and tasks: Scans the repository to find all unused playbooks and tasks. It analyzes the codebase and determines which playbooks and tasks are no longer referenced or used.
  • Find unused YAML files in group_vars and host_vars: Parses the "hosts" file, load all hosts and groups into a data structure. It then scans the group_vars and host_vars directories, identifying any YAML files that correspond to hosts or groups that no longer exist. This ensures that your variable files remain relevant and up-to-date.

Installation

Here is how to install ansible-cleanup using pip:

pip install --user git+https://github.com/jamescherti/ansible-cleanup

The pip command above will install the executable files in ~/.local/bin/.

Command Line Interface

The ansible-cleanup executable routes execution to specific cleanup modules using subcommands.

ansible-cleanup imports

This subcommand acts as a static code analyzer for your Ansible execution paths. It takes a root playbook (or multiple playbooks) as an argument and recursively traces every import_playbook, include_tasks, import_role, and related Ansible includes. It then compares the files it successfully resolved against all the YAML files in your repository to find the orphans.

As infrastructure evolves, old task files and sub-playbooks are often disconnected from the main execution tree but are left behind in the repository. Manually tracing YAML includes across dozens of files is tedious and prone to human error. This command automates the discovery of dead code, ensuring your repository only contains files that are actually executed.

Usage:

Pass your primary entry-point playbook (e.g., site.yml or main.yml) as an argument. The script will output the absolute paths of any .yml or .yaml files that are not referenced anywhere in the execution tree.

$ ansible-cleanup imports site.yaml
/path/to/repo/playbooks/old_deployment_tasks.yml
/path/to/repo/playbooks/deprecated_setup.yaml

ansible-cleanup vars

This subcommand manages your variable definitions. It reads your local hosts inventory file and builds a comprehensive list of all active hosts and groups. It then cross-references this active list against the files located in your host_vars and group_vars directories to find files named after hosts or groups that are not defined in the inventory.

When servers are decommissioned or host groups are renamed, engineers frequently remove them from the hosts file but forget to delete the corresponding variable files in host_vars/ or group_vars/. Over time, this leads to significant repository bloat and confusion over which variables are actually applied. This tool securely flags those forgotten files for deletion.

Execute the command in the directory containing your hosts file, host_vars directory, and group_vars directory. It requires no arguments.

Usage:

$ ansible-cleanup vars
/path/to/repo/host_vars/decommissioned-db-server-01.yml
/path/to/repo/group_vars/legacy-web-nodes.yaml

License

Copyright (c) 2009-2026 James Cherti

This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.

Links

A Shell Script that Configures the GNOME Desktop Programmatically

License

The jc-gnome-settings repository provides the jc-gnome-settings.sh script, which holds James Cherti's settings to customize the GNOME desktop environment, including window management, notifications, desktop behavior, keyboard settings, and more, to enhance the user experience.

Requirements

  • gsettings

Usage

Clone the repository:

git clone https://github.com/jamescherti/jc-gnome-settings

Navigate to the repository directory:

cd jc-gnome-settings

Run the script to configure GNOME:

./jc-gnome-settings.sh

Author and License

The jc-gnome-settings tool has been written by James Cherti and is distributed under terms of the MIT license.

Links

Other projects by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-gentoo-portage @GitHub: Provides configuration files for customizing Gentoo Linux Portage, including package management, USE flags, and system-wide settings.
  • jc-xfce-settings: GNOME customizations that can be applied programmatically.
  • watch-xfce-xfconf: A command-line tool that can be used to configure XFCE 4 programmatically using the xfconf-query commands displayed when XFCE 4 settings are modified.

A Shell Script that Automates XFCE Desktop Configuration

License

The jc-xfce-settings project provides the jc-xfce-settings.sh script, which holds James Cherti's settings to customize the XFCE desktop environment, including window management, notifications, desktop behavior, keyboard settings, and more, to enhance the user experience.

(The jc-xfce-settings.sh script was created with the help of watch-xfce-xfconf)

Requirements

  • The XFCE Desktop Environment,
  • and xfconf-query utility that is part of XFCE.

Usage

Clone the repository:

git clone https://github.com/jamescherti/jc-xfce-settings

Navigate to the repository directory:

cd jc-xfce-settings

Run the script to configure XFCE:

./jc-xfce-settings.sh

Features

  • Title Bar Customization: Simplifies button layout for easier window management.
  • Font and Display Settings: Enables anti-aliasing, hinting, and configures RGBA rendering.
  • File Manager (Thunar): Optimizes behavior for thumbnailing, single-click navigation, and directory-specific settings.
  • Keyboard Tweaks: Adjusts key repeat delay and rate for a smoother typing experience.
  • Notifications: Sets notification theme, position, and timeout duration.
  • Desktop Behavior: Disables unnecessary desktop icons and menus for a cleaner workspace.
  • Session Management: Disables session saving for a faster logout experience.
  • Window Management: Configures snapping, shadow effects, focus behavior, and workspace interactions.
  • Compositor Settings: Adjusts transparency and disables unneeded effects.

Author and License

The jc-xfce-settings tool has been written by James Cherti and is distributed under terms of the MIT license.

Links

Other projects by the same author:

  • jc-dotfiles @GitHub: A collection of UNIX/Linux configuration files. You can either install them directly or use them as inspiration your own dotfiles.
  • bash-stdops @GitHub: A collection of Bash helper shell scripts.
  • jc-gnome-settings: GNOME customizations that can be applied programmatically.
  • jc-firefox-settings @GitHub: Provides the user.js file, which holds settings to customize the Firefox web browser to enhance the user experience and security.
  • jc-gentoo-portage @GitHub: Provides configuration files for customizing Gentoo Linux Portage, including package management, USE flags, and system-wide settings.