Major rewrite of core-ui

Phases out dependence on the doom-kill-buffer workflow, which ensured
the user would always land on a real buffer (or the fallback buffer)
after killing a buffer. This required all quit-window, kill-this-buffer
and various buffer-killing commands be advised or remapped. There was no
need, kill-buffer-query-functions is better.

Also introduces a new hook: doom-init-theme-hook. doom-init-ui-hook
still exists, but is used for UI initialization (surprise), while theme
hooks should be moved over.
This commit is contained in:
Henrik Lissner 2018-01-03 13:54:51 -05:00
parent 8e875bfa0e
commit 69385fac1a
No known key found for this signature in database
GPG key ID: 5F6C0EA160557395
2 changed files with 237 additions and 200 deletions

View file

@ -52,18 +52,6 @@ modes are active and the buffer is read-only.")
ediff-split-window-function #'split-window-horizontally
ediff-window-setup-function #'ediff-setup-windows-plain)
(defun doom|dont-kill-scratch-buffer ()
"Don't kill the scratch buffer."
(or (not (string= (buffer-name) "*scratch*"))
(ignore (bury-buffer))))
(add-hook 'kill-buffer-query-functions #'doom|dont-kill-scratch-buffer)
;; temporary windows often have q bound to `quit-window', which only buries the
;; contained buffer. I rarely don't want that buffer killed, so...
(defun doom*quit-window (orig-fn &optional kill window)
(funcall orig-fn (not kill) window))
(advice-add #'quit-window :around #'doom*quit-window)
(defun doom|check-large-file ()
"Check if the buffer's file is large (see `doom-large-file-size'). If so, ask
for confirmation to open it literally (read-only, disabled undo and in
@ -218,7 +206,6 @@ extension, try to guess one."
(def-package! command-log-mode
:commands (command-log-mode global-command-log-mode)
:config
(set! :popup "*command-log*" :size 40 :align 'right :noselect t)
(setq command-log-mode-auto-show t
command-log-mode-open-log-turns-on-mode t))

View file

@ -30,8 +30,11 @@ shorter major mode name in the mode-line. See `doom|set-mode-name'.")
;; Hook(s)
(defvar doom-init-ui-hook nil
"List of hooks to run when the theme and font is initialized (or reloaded with
`doom//reload-theme').")
"List of hooks to run when core-ui is initialized.")
(defvar doom-init-theme-hook nil
"List of hooks to run when the theme (and font) is initialized (or reloaded
with `doom//reload-theme').")
(setq-default
@ -72,45 +75,36 @@ shorter major mode name in the mode-line. See `doom|set-mode-name'.")
(fset #'yes-or-no-p #'y-or-n-p) ; y/n instead of yes/no
(defun doom-quit-p (&optional prompt)
"Return t if this session should be killed. Prompts the user for
confirmation."
(if (ignore-errors (doom-real-buffer-list))
(or (yes-or-no-p (format " %s" (or prompt "Quit Emacs?")))
(ignore (message "Aborted")))
t))
(setq confirm-kill-emacs nil)
(add-hook 'kill-emacs-query-functions #'doom-quit-p)
;; show typed keystrokes in minibuffer
(defun doom|enable-ui-keystrokes () (setq echo-keystrokes 0.02))
(defun doom|disable-ui-keystrokes () (setq echo-keystrokes 0))
(doom|enable-ui-keystrokes)
;; ...but hide them while isearch is active
(add-hook 'isearch-mode-hook #'doom|disable-ui-keystrokes)
(add-hook 'isearch-mode-end-hook #'doom|enable-ui-keystrokes)
;;
;; A minor mode for hiding the mode-line
;;
;; A minor mode for toggling the mode-line
(defvar-local doom--modeline-format nil
"The modeline format to use when `doom-hide-modeline-mode' is active. Don't
set this directly. Let-bind it instead.")
(defvar-local doom--old-modeline-format nil
"The old modeline format, so `doom-hide-modeline-mode' can revert when it's
disabled.")
(define-minor-mode doom-hide-modeline-mode
"Minor mode to hide the mode-line in the current buffer."
:init-value nil
:global nil
(if doom-hide-modeline-mode
(if (not doom-hide-modeline-mode)
(setq mode-line-format doom--old-modeline-format
doom--old-modeline-format nil)
(setq doom--old-modeline-format mode-line-format
mode-line-format doom--modeline-format)
(setq mode-line-format doom--old-modeline-format
doom--old-modeline-format nil))
(add-hook 'after-change-major-mode-hook #'doom|hide-modeline-mode-reset nil t))
(force-mode-line-update))
;; Ensure major-mode or theme changes don't overwrite these variables
(put 'doom--modeline-format 'permanent-local t)
(put 'doom--old-modeline-format 'permanent-local t)
(put 'doom-hide-modeline-mode 'permanent-local t)
(put 'doom-hide-modeline-mode 'permanent-local-hook t)
(defun doom|hide-modeline-mode-reset ()
"Sometimes, a major-mode is activated after `doom-hide-modeline-mode' is
@ -121,121 +115,72 @@ local value, whether or not it's permanent-local. Therefore, we cycle
(when doom-hide-modeline-mode
(doom-hide-modeline-mode -1)
(doom-hide-modeline-mode +1)))
(add-hook 'after-change-major-mode-hook #'doom|hide-modeline-mode-reset)
;; no modeline in completion popups
(add-hook 'completion-list-mode-hook #'doom-hide-modeline-mode)
;; undo/redo changes to Emacs' window layout
(defvar winner-dont-bind-my-keys t) ; I'll bind keys myself
(autoload 'winner-mode "winner" nil t)
(add-hook 'doom-init-ui-hook #'winner-mode)
;; highlight matching delimiters
(setq show-paren-delay 0.1
show-paren-highlight-openparen t
show-paren-when-point-inside-paren t)
(add-hook 'doom-init-ui-hook #'show-paren-mode)
;;; More reliable inter-window border
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
(setq-default window-divider-default-places t
window-divider-default-bottom-width 0
window-divider-default-right-width 1)
(add-hook 'doom-init-ui-hook #'window-divider-mode)
;; like diminish, but for major-modes. [pedantry intensifies]
(defun doom|set-mode-name ()
"Set the major mode's `mode-name', as dictated by `doom-major-mode-names'."
(when-let* ((name (cdr (assq major-mode doom-major-mode-names))))
(setq mode-name
(cond ((functionp name) (funcall name))
((stringp name) name)
(t (error "'%s' isn't a valid name for %s" name major-mode))))))
(add-hook 'after-change-major-mode-hook #'doom|set-mode-name)
;;
;; Themes & fonts
;; Modeline library
;;
;; Getting themes to remain consistent across GUI Emacs, terminal Emacs and
;; daemon Emacs is hairy.
;;
;; + Running `doom|init-ui' directly sorts out the initial GUI frame.
;; + Attaching it to `after-make-frame-functions' sorts out daemon Emacs.
;; + Waiting for 0.1s in `doom|reload-ui-in-daemon' fixes daemon Emacs started
;; with `server-start' in an interactive session of Emacs AND in tty Emacs.
(defun doom|init-ui (&optional frame)
"Set the theme and load the font, in that order."
(when doom-theme
(load-theme doom-theme t))
(condition-case-unless-debug ex
(when (display-graphic-p)
(when (fontp doom-font)
(set-frame-font doom-font nil (if frame (list frame) t))
(set-face-attribute 'fixed-pitch frame :font doom-font))
;; Fallback to `doom-unicode-font' for Unicode characters
(when (fontp doom-unicode-font)
(set-fontset-font t 'unicode doom-unicode-font frame))
;; ...and for variable-pitch-mode:
(when (fontp doom-variable-pitch-font)
(set-face-attribute 'variable-pitch frame :font doom-variable-pitch-font)))
('error
(if (string-prefix-p "Font not available: " (error-message-string ex))
(lwarn 'doom-ui :warning
"Could not find the '%s' font on your system, falling back to system font"
(font-get (caddr ex) :family))
(lwarn 'doom-ui :error
"Unexpected error while initializing fonts: %s"
(error-message-string ex)))))
(run-hooks 'doom-init-ui-hook))
(defmacro def-modeline-segment! (name &rest forms)
"Defines a modeline segment and byte compiles it."
(declare (indent defun) (doc-string 2))
(let ((sym (intern (format "doom-modeline-segment--%s" name))))
`(progn
(defun ,sym () ,@forms)
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
(defun doom|reload-ui-in-daemon (frame)
"Reload the theme (and font) in an daemon frame."
(when (or (daemonp) (not (display-graphic-p)))
(with-selected-frame frame
(run-with-timer 0.1 nil #'doom|init-ui))))
(defsubst doom--prepare-modeline-segments (segments)
(cl-loop for seg in segments
if (stringp seg)
collect seg
else
collect (list (intern (format "doom-modeline-segment--%s" (symbol-name seg))))))
;; register UI init hooks
(add-hook 'doom-post-init-hook #'doom|init-ui)
(add-hook! 'after-make-frame-functions #'(doom|init-ui doom|reload-ui-in-daemon))
(defmacro def-modeline! (name lhs &optional rhs)
"Defines a modeline format and byte-compiles it. NAME is a symbol to identify
it (used by `doom-modeline' for retrieval). LHS and RHS are lists of symbols of
modeline segments defined with `def-modeline-segment!'.
Example:
(def-modeline! minimal
(bar matches \" \" buffer-info)
(media-info major-mode))
(doom-set-modeline 'minimal t)"
(let ((sym (intern (format "doom-modeline-format--%s" name)))
(lhs-forms (doom--prepare-modeline-segments lhs))
(rhs-forms (doom--prepare-modeline-segments rhs)))
`(progn
(defun ,sym ()
(let ((lhs (list ,@lhs-forms))
(rhs (list ,@rhs-forms)))
(let ((rhs-str (format-mode-line rhs)))
(list lhs
(propertize
" " 'display
`((space :align-to (- (+ right right-fringe right-margin)
,(+ 1 (string-width rhs-str))))))
rhs-str))))
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
;;
;; Bootstrap
;;
(defun doom-modeline (key)
"Returns a mode-line configuration associated with KEY (a symbol). Throws an
error if it doesn't exist."
(let ((fn (intern (format "doom-modeline-format--%s" key))))
(when (functionp fn)
`(:eval (,fn)))))
;; prompts the user for confirmation when deleting a non-empty frame
(define-key global-map [remap delete-frame] #'doom/delete-frame)
;; simple name in frame title
(setq-default frame-title-format '("DOOM Emacs"))
;; auto-enabled in Emacs 25+; I'll do it myself
(global-eldoc-mode -1)
;; a good indicator that Emacs isn't frozen
(add-hook 'doom-post-init-hook #'blink-cursor-mode)
;; standardize default fringe width
(if (fboundp 'fringe-mode) (fringe-mode doom-fringe-size))
;; draw me like one of your French editors
(tooltip-mode -1) ; relegate tooltips to echo area only
(menu-bar-mode -1)
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
(defun doom|no-fringes-in-minibuffer ()
"Disable fringes in the minibuffer window."
(set-window-fringes (minibuffer-window) 0 0 nil))
(add-hook! '(doom-post-init-hook minibuffer-setup-hook)
#'doom|no-fringes-in-minibuffer)
(defun doom|protect-visible-buffers ()
"Don't kill the current buffer if it is visible in another window (bury it
instead)."
(not (delq (selected-window)
(get-buffer-window-list nil nil t))))
(add-hook! doom-post-init
(add-hook 'kill-buffer-query-functions #'doom|protect-visible-buffers))
(defun doom-set-modeline (key &optional default)
"Set the modeline format. Does nothing if the modeline KEY doesn't exist. If
DEFAULT is non-nil, set the default mode-line for all buffers."
(when-let* ((modeline (doom-modeline key)))
(setf (if default
(default-value 'mode-line-format)
(buffer-local-value 'mode-line-format (current-buffer)))
modeline)))
;;
@ -331,6 +276,38 @@ instead)."
fill-column)))
;;
;; Built-in packages
;;
;; show typed keystrokes in minibuffer
(defun doom|enable-ui-keystrokes () (setq echo-keystrokes 0.02))
(defun doom|disable-ui-keystrokes () (setq echo-keystrokes 0))
(doom|enable-ui-keystrokes)
;; ...but hide them while isearch is active
(add-hook 'isearch-mode-hook #'doom|disable-ui-keystrokes)
(add-hook 'isearch-mode-end-hook #'doom|enable-ui-keystrokes)
;; undo/redo changes to Emacs' window layout
(defvar winner-dont-bind-my-keys t) ; I'll bind keys myself
(autoload 'winner-mode "winner" nil t)
(add-hook 'doom-init-ui-hook #'winner-mode)
;; highlight matching delimiters
(setq show-paren-delay 0.1
show-paren-highlight-openparen t
show-paren-when-point-inside-paren t)
(add-hook 'doom-init-ui-hook #'show-paren-mode)
;;; More reliable inter-window border
;; The native border "consumes" a pixel of the fringe on righter-most splits,
;; `window-divider' does not. Available since Emacs 25.1.
(setq-default window-divider-default-places t
window-divider-default-bottom-width 0
window-divider-default-right-width 1)
(add-hook 'doom-init-ui-hook #'window-divider-mode)
;;
;; Line numbers
;;
@ -366,8 +343,6 @@ See `doom-line-numbers-style' to control the style of line numbers to display."
"Disable the display of line numbers."
(doom|enable-line-numbers -1))
(add-hook! (prog-mode text-mode conf-mode) #'doom|enable-line-numbers)
;; Emacs 26+ has native line number support.
;; Line number column. A faster (or equivalent, in the worst case) line number
;; plugin than `linum-mode'.
@ -442,69 +417,144 @@ character that looks like a space that `whitespace-mode' won't affect.")
;;
;; Modeline
;; Theme & font
;;
(defmacro def-modeline-segment! (name &rest forms)
"Defines a modeline segment and byte compiles it."
(declare (indent defun) (doc-string 2))
(let ((sym (intern (format "doom-modeline-segment--%s" name))))
`(progn
(defun ,sym () ,@forms)
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
(defun doom|init-theme (&optional frame)
"Set the theme and load the font, in that order."
(when doom-theme
(load-theme doom-theme t))
(condition-case-unless-debug ex
(when (display-graphic-p)
(when (fontp doom-font)
(set-frame-font doom-font nil (if frame (list frame) t))
(set-face-attribute 'fixed-pitch frame :font doom-font))
;; Fallback to `doom-unicode-font' for Unicode characters
(when (fontp doom-unicode-font)
(set-fontset-font t 'unicode doom-unicode-font frame))
;; ...and for variable-pitch-mode:
(when (fontp doom-variable-pitch-font)
(set-face-attribute 'variable-pitch frame :font doom-variable-pitch-font)))
('error
(if (string-prefix-p "Font not available: " (error-message-string ex))
(lwarn 'doom-ui :warning
"Could not find the '%s' font on your system, falling back to system font"
(font-get (caddr ex) :family))
(lwarn 'doom-ui :error
"Unexpected error while initializing fonts: %s"
(error-message-string ex)))))
(defsubst doom--prepare-modeline-segments (segments)
(cl-loop for seg in segments
if (stringp seg)
collect seg
else
collect (list (intern (format "doom-modeline-segment--%s" (symbol-name seg))))))
(run-hooks 'doom-init-theme-hook))
(defmacro def-modeline! (name lhs &optional rhs)
"Defines a modeline format and byte-compiles it. NAME is a symbol to identify
it (used by `doom-modeline' for retrieval). LHS and RHS are lists of symbols of
modeline segments defined with `def-modeline-segment!'.
;; Getting themes to remain consistent across GUI Emacs, terminal Emacs and
;; daemon Emacs is hairy.
;;
;; + Running `doom|init-theme' directly sorts out the initial GUI frame.
;; + Attaching it to `after-make-frame-functions' sorts out daemon Emacs.
;; + Waiting for 0.1s in `doom|reload-ui-in-daemon' fixes daemon Emacs started
;; with `server-start' in an interactive session of Emacs AND in tty Emacs.
(add-hook 'doom-init-ui-hook #'doom|init-theme)
Example:
(def-modeline! minimal
(bar matches \" \" buffer-info)
(media-info major-mode))
(doom-set-modeline 'minimal t)"
(let ((sym (intern (format "doom-modeline-format--%s" name)))
(lhs-forms (doom--prepare-modeline-segments lhs))
(rhs-forms (doom--prepare-modeline-segments rhs)))
`(progn
(defun ,sym ()
(let ((lhs (list ,@lhs-forms))
(rhs (list ,@rhs-forms)))
(let ((rhs-str (format-mode-line rhs)))
(list lhs
(propertize
" " 'display
`((space :align-to (- (+ right right-fringe right-margin)
,(+ 1 (string-width rhs-str))))))
rhs-str))))
,(unless (bound-and-true-p byte-compile-current-file)
`(let (byte-compile-warnings)
(byte-compile #',sym))))))
(defun doom|reload-ui-in-daemon (frame)
"Reload the theme (and font) in an daemon frame."
(when (or (daemonp) (not (display-graphic-p)))
(with-selected-frame frame
(run-with-timer 0.1 nil #'doom|init-ui))))
(add-hook! 'after-make-frame-functions #'(doom|init-theme doom|reload-ui-in-daemon))
(defun doom-modeline (key)
"Returns a mode-line configuration associated with KEY (a symbol). Throws an
error if it doesn't exist."
(let ((fn (intern (format "doom-modeline-format--%s" key))))
(when (functionp fn)
`(:eval (,fn)))))
(defun doom-set-modeline (key &optional default)
"Set the modeline format. Does nothing if the modeline KEY doesn't exist. If
DEFAULT is non-nil, set the default mode-line for all buffers."
(when-let* ((modeline (doom-modeline key)))
(setf (if default
(default-value 'mode-line-format)
(buffer-local-value 'mode-line-format (current-buffer)))
modeline)))
;;
;; Bootstrap
;;
;; auto-enabled in Emacs 25+; I'll do it myself
(global-eldoc-mode -1)
;; simple name in frame title
(setq-default frame-title-format '("DOOM Emacs"))
;; draw me like one of your French editors
(tooltip-mode -1) ; relegate tooltips to echo area only
(menu-bar-mode -1)
(if (fboundp 'tool-bar-mode) (tool-bar-mode -1))
(if (fboundp 'scroll-bar-mode) (scroll-bar-mode -1))
;; standardize default fringe width
(if (fboundp 'fringe-mode) (fringe-mode doom-fringe-size))
;; prompts the user for confirmation when deleting a non-empty frame
(define-key global-map [remap delete-frame] #'doom/delete-frame)
;; a good indicator that Emacs isn't frozen
(add-hook 'doom-init-ui-hook #'blink-cursor-mode)
;; no modeline in completion popups
(add-hook 'completion-list-mode-hook #'doom-hide-modeline-mode)
;; line numbers in most modes
(add-hook! (prog-mode text-mode conf-mode) #'doom|enable-line-numbers)
;; Customized confirmation prompt for quitting Emacs
(defun doom-quit-p (&optional prompt)
"Return t if this session should be killed. Prompts the user for
confirmation."
(if (ignore-errors (doom-real-buffer-list))
(or (yes-or-no-p (format " %s" (or prompt "Quit Emacs?")))
(ignore (message "Aborted")))
t))
(setq confirm-kill-emacs #'doom-quit-p)
(defun doom|no-fringes-in-minibuffer ()
"Disable fringes in the minibuffer window."
(set-window-fringes (minibuffer-window) 0 0 nil))
(add-hook! '(doom-init-ui-hook minibuffer-setup-hook)
#'doom|no-fringes-in-minibuffer)
(defun doom|set-mode-name ()
"Set the major mode's `mode-name', as dictated by `doom-major-mode-names'."
(when-let* ((name (cdr (assq major-mode doom-major-mode-names))))
(setq mode-name
(cond ((functionp name) (funcall name))
((stringp name) name)
(t (error "'%s' isn't a valid name for %s" name major-mode))))))
(defun doom|protect-visible-buffers ()
"Don't kill the current buffer if it is visible in another window (bury it
instead)."
(not (delq (selected-window) (get-buffer-window-list nil nil t))))
(defun doom|protect-fallback-buffer ()
"Don't kill the scratch buffer."
(not (eq (current-buffer) (doom-fallback-buffer))))
(defun doom*switch-to-fallback-buffer-maybe (orig-fn)
"Advice for `kill-this-buffer'. If there are no real buffers left, switch to
`doom-fallback-buffer'."
(let ((buf (current-buffer)))
(cond ((window-at-side-p)
(quit-window))
((eq buf (doom-fallback-buffer))
(bury-buffer))
(t
(let ((real-p (doom-real-buffer-p buf)))
(funcall orig-fn)
(cond ((eq buf (current-buffer))
(doom--cycle-real-buffers nil))
((and real-p (not (doom-real-buffer-p)))
(doom--cycle-real-buffers -1))
(t
(message "Nowhere to go!"))))))))
(defun doom|init-ui ()
"Initialize Doom's user interface by applying all its advice and hooks."
;; Switch to `doom-fallback-buffer' if on last real buffer
(advice-add #'kill-this-buffer :around #'doom*switch-to-fallback-buffer-maybe)
;; Don't kill the fallback buffer
(add-hook 'kill-buffer-query-functions #'doom|protect-fallback-buffer)
;; Don't kill buffers that are visible else, only bury them
(add-hook 'kill-buffer-query-functions #'doom|protect-visible-buffers)
;; Renames major-modes [pedantry intensifies]
(add-hook 'after-change-major-mode-hook #'doom|set-mode-name)
;;
(run-hooks 'doom-init-ui-hook))
(add-hook 'doom-init-hook #'doom|init-ui)
(provide 'core-ui)
;;; core-ui.el ends here