Optimize configuration reload to prevent UI freezing

Implemented non-blocking reload mechanism that processes files
incrementally during idle time to prevent Emacs from freezing.

Changes:
- New default reload-emacs-config: Non-blocking with idle timers
- reload-emacs-config-blocking: Original blocking version
- reload-emacs-config-fast: Uses byte-compiled files for speed
- reload-emacs-config-smart: Only reloads recently changed files
- reload-current-file: Quick reload of current .el file only

Keybindings:
- C-c C-r: Non-blocking reload (new default)
- C-u C-c C-r: Blocking reload (old behavior)
- C-c r: Reload current file
- C-c R: Fast byte-compiled reload

This fixes the lag issue during configuration reload by loading
files one at a time with 0.01s idle gaps between them.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jens Luedicke
2025-09-10 14:59:43 +02:00
parent 54d6fd3cce
commit 8644b5c469
3 changed files with 192 additions and 6 deletions

View File

@@ -10,10 +10,20 @@ This is a modular Emacs configuration with 33+ configuration modules in the `lis
### Configuration Management
```elisp
M-x reload-emacs-config ; Reload entire configuration
M-x reload-emacs-config ; Non-blocking reload (won't freeze UI)
M-x reload-emacs-config-blocking ; Original blocking reload
M-x reload-emacs-config-fast ; Fast reload using byte-compiled files
M-x reload-emacs-config-smart ; Smart reload (only changed files)
M-x reload-current-file ; Reload only current .el file
M-x byte-compile-config ; Byte-compile all config files for faster loading
M-x clean-byte-compiled-files ; Remove all .elc files
M-x byte-compile-init-files ; Compile only lisp/ directory files
;; Keybindings:
C-c C-r ; Non-blocking reload (default)
C-u C-c C-r ; Blocking reload (old behavior)
C-c r ; Reload current file only
C-c R ; Fast reload (byte-compiled)
```
### Emergency Fixes

View File

@@ -8,7 +8,14 @@
(global-set-key (kbd "C-x k") 'kill-current-buffer-no-confirm)
;;; Configuration reload
;; Default: Non-blocking reload
(global-set-key (kbd "C-c C-r") 'reload-emacs-config)
;; C-u prefix: Blocking reload (old behavior)
(global-set-key (kbd "C-u C-c C-r") 'reload-emacs-config-blocking)
;; Quick reload for current file only
(global-set-key (kbd "C-c r") 'reload-current-file)
;; Fast reload using byte-compiled files
(global-set-key (kbd "C-c R") 'reload-emacs-config-fast)
;;; Portfolio tracker
(global-set-key (kbd "C-c $") 'portfolio-tracker)

View File

@@ -10,8 +10,9 @@
(interactive)
(kill-buffer (current-buffer)))
(defun reload-emacs-config ()
"Reload the Emacs configuration file and all dependent configs."
;; Keep original function but rename it
(defun reload-emacs-config-blocking ()
"Reload the Emacs configuration file and all dependent configs (blocking version)."
(interactive)
;; First reload the main init.el
(load-file (expand-file-name "init.el" user-emacs-directory))
@@ -41,6 +42,174 @@
(message "Emacs configuration fully reloaded!"))
(defvar reload-emacs-config-timer nil
"Timer for non-blocking configuration reload.")
(defvar reload-emacs-config-files nil
"List of files to reload.")
(defvar reload-emacs-config-index 0
"Current index in the reload process.")
(defun reload-emacs-config-process-next ()
"Process the next file in the reload queue."
(when (< reload-emacs-config-index (length reload-emacs-config-files))
(let ((file (nth reload-emacs-config-index reload-emacs-config-files)))
(condition-case err
(progn
(load-file file)
(message "[%d/%d] Loaded %s"
(1+ reload-emacs-config-index)
(length reload-emacs-config-files)
(file-name-nondirectory file)))
(error
(message "[%d/%d] Error loading %s: %s"
(1+ reload-emacs-config-index)
(length reload-emacs-config-files)
(file-name-nondirectory file)
err)))
(cl-incf reload-emacs-config-index)
;; Schedule next file
(setq reload-emacs-config-timer
(run-with-idle-timer 0.01 nil #'reload-emacs-config-process-next)))
;; All done
(when (>= reload-emacs-config-index (length reload-emacs-config-files))
(message "✓ Configuration reload complete!")
(setq reload-emacs-config-timer nil
reload-emacs-config-files nil
reload-emacs-config-index 0))))
(defun reload-emacs-config ()
"Reload Emacs configuration non-blocking with incremental updates.
This version loads configuration files one by one during idle time
to prevent UI freezing."
(interactive)
;; Cancel any ongoing reload
(when reload-emacs-config-timer
(cancel-timer reload-emacs-config-timer)
(setq reload-emacs-config-timer nil))
;; Prepare list of files to reload
(setq reload-emacs-config-files
(cl-remove-if-not
#'file-exists-p
(list (expand-file-name "init.el" user-emacs-directory)
(expand-file-name "emacs-dev-config.el" user-emacs-directory)
(expand-file-name "shr-config.el" user-emacs-directory)
(expand-file-name "elfeed-config.el" user-emacs-directory)
(expand-file-name "mu4e-config.el" user-emacs-directory))))
(setq reload-emacs-config-index 0)
(message "Starting configuration reload (%d files)..."
(length reload-emacs-config-files))
;; Start the reload process
(reload-emacs-config-process-next))
(defun reload-emacs-config-async ()
"Reload Emacs configuration asynchronously with progress feedback."
(interactive)
(let* ((start-time (current-time))
(config-files '())
(total-files 0)
(loaded-files 0)
(progress-reporter nil))
;; Collect all config files to reload
(setq config-files
(append
;; Main init.el
(list (expand-file-name "init.el" user-emacs-directory))
;; Optional configs
(cl-remove-if-not
#'file-exists-p
(list
(expand-file-name "emacs-dev-config.el" user-emacs-directory)
(expand-file-name "shr-config.el" user-emacs-directory)
(expand-file-name "elfeed-config.el" user-emacs-directory)
(expand-file-name "mu4e-config.el" user-emacs-directory)))))
(setq total-files (length config-files))
(setq progress-reporter
(make-progress-reporter "Reloading configuration..." 0 total-files))
;; Load files with progress updates
(dolist (file config-files)
(condition-case err
(progn
(load-file file)
(cl-incf loaded-files)
(progress-reporter-update progress-reporter loaded-files))
(error
(message "Error loading %s: %s" (file-name-nondirectory file) err))))
(progress-reporter-done progress-reporter)
(message "Configuration reloaded in %.2f seconds"
(float-time (time-subtract (current-time) start-time)))))
(defun reload-emacs-config-smart ()
"Smart reload that only reloads changed files since last load."
(interactive)
(let* ((lisp-dir (expand-file-name "lisp" user-emacs-directory))
(init-file (expand-file-name "init.el" user-emacs-directory))
(changed-files '())
(reload-all nil))
;; Check if init.el has changed
(when (file-newer-than-file-p init-file (current-time))
(setq reload-all t))
(if reload-all
;; If init.el changed, do full reload
(reload-emacs-config-async)
;; Otherwise, reload only changed files
(progn
;; Find changed files in lisp directory
(dolist (file (directory-files lisp-dir t "\\.el$"))
(when (and (not (string-match-p "\\.elc$" file))
(not (string-match-p "#" file))
(file-newer-than-file-p file (time-subtract (current-time) 60)))
(push file changed-files)))
(if changed-files
(progn
(message "Reloading %d changed file(s)..." (length changed-files))
(dolist (file changed-files)
(condition-case err
(progn
(load-file file)
(message "Reloaded %s" (file-name-nondirectory file)))
(error
(message "Error reloading %s: %s"
(file-name-nondirectory file) err))))
(message "Smart reload complete!"))
(message "No files changed recently. Use C-u C-c C-r for full reload.")))))
(defun reload-current-file ()
"Reload only the current file if it's an Emacs Lisp file."
(interactive)
(when (and (buffer-file-name)
(string-match-p "\\.el$" (buffer-file-name)))
(save-buffer)
(load-file (buffer-file-name))
(message "Reloaded %s" (file-name-nondirectory (buffer-file-name)))))
(defun reload-emacs-config-fast ()
"Fast reload using byte-compiled files when available.
This is the fastest reload method but requires byte-compilation."
(interactive)
(let* ((init-file (expand-file-name "init" user-emacs-directory))
(start-time (current-time)))
;; Try to load byte-compiled version first, fall back to source
(if (file-exists-p (concat init-file ".elc"))
(progn
(load init-file t t)
(message "Fast reload complete (byte-compiled) in %.2f seconds"
(float-time (time-subtract (current-time) start-time))))
;; Fall back to non-blocking reload
(reload-emacs-config))))
;;; Package management helpers
(defun package-refresh-without-proxy ()
"Temporarily disable proxy and refresh packages."