diff --git a/core/core-editor.el b/core/core-editor.el index 92344479c..1592efdc2 100644 --- a/core/core-editor.el +++ b/core/core-editor.el @@ -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)) diff --git a/core/core-ui.el b/core/core-ui.el index ecf053cae..ccf600037 100644 --- a/core/core-ui.el +++ b/core/core-ui.el @@ -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 - (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)) + (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) + (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