;; core-ui.el --- draw me like one of your French editors (defvar doom-ui-fringe-size '4 "Default fringe width") (setq-default bidi-display-reordering nil ; disable bidirectional text for tiny performance boost blink-matching-paren nil ; don't blink--too distracting cursor-in-non-selected-windows nil ; hide cursors in other windows frame-inhibit-implied-resize t ;; remove continuation arrow on right fringe fringe-indicator-alist (delq (assq 'continuation fringe-indicator-alist) fringe-indicator-alist) highlight-nonselected-windows nil image-animate-loop t indicate-buffer-boundaries nil indicate-empty-lines nil jit-lock-defer-time nil jit-lock-stealth-nice 0.1 jit-lock-stealth-time 0.2 jit-lock-stealth-verbose nil max-mini-window-height 0.3 mode-line-default-help-echo nil ; disable mode-line mouseovers mouse-yank-at-point t ; middle-click paste at point, not at click resize-mini-windows 'grow-only ; Minibuffer resizing show-help-function nil ; hide :help-echo text split-width-threshold nil ; favor horizontal splits uniquify-buffer-name-style 'forward use-dialog-box nil ; always avoid GUI visible-cursor nil x-stretch-cursor nil ;; `pos-tip' defaults pos-tip-internal-border-width 6 pos-tip-border-width 1 ;; no beeping or blinking please ring-bell-function #'ignore visible-bell nil ;; Ask for confirmation on quit only if real buffers exist confirm-kill-emacs (lambda (_) (if (doom-real-buffers-list) (y-or-n-p "››› Quit?") t))) (fset #'yes-or-no-p #'y-or-n-p) ; y/n instead of yes/no ;; auto-enabled in Emacs 25+; I'd rather enable it manually (global-eldoc-mode -1) ;; show typed keystrokes in minibuffer (setq echo-keystrokes 0.02) ;; ...but hide them while isearch is active (add-hook! isearch-mode (setq echo-keystrokes 0)) (add-hook! isearch-mode-end (setq echo-keystrokes 0.02)) ;; 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. Bind it in `let' 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)) (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) (defun doom|hide-modeline-mode-reset () "Sometimes, a major-mode is activated after `doom-hide-modeline-mode' is activated, thus disabling it (because changing major modes invokes `kill-all-local-variables' and specifically seems to kill `mode-line-format's local value, whether or not it's permanent-local. Therefore, we cycle `doom-hide-modeline-mode' to fix this." (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 (require 'winner) (add-hook 'window-setup-hook #'winner-mode) ;; highlight matching delimiters (setq show-paren-delay 0.1 show-paren-highlight-openparen t show-paren-when-point-inside-paren t) (show-paren-mode +1) ;; like diminish, but for major-modes. [pedantry intensifies] (defvar doom-ui-mode-names '((sh-mode . "sh") (emacs-lisp-mode "Elisp")) "An alist mapping major modes to alternative names, which will be set when the mode is detected.") (defun doom|set-mode-name () "Set the major mode's `mode-name', as dictated by `doom-ui-mode-names'." (let ((name (assq major-mode doom-ui-mode-names))) (if name (setq mode-name (cdr name))))) (add-hook 'after-change-major-mode-hook #'doom|set-mode-name) ;; ;; Bootstrap ;; (tooltip-mode -1) ; relegate tooltips to echo area only (menu-bar-mode -1) (tool-bar-mode -1) (when (display-graphic-p) (scroll-bar-mode -1) ;; buffer name in frame title (setq-default frame-title-format '("DOOM Emacs")) ;; standardize fringe width (push (cons 'left-fringe doom-ui-fringe-size) default-frame-alist) (push (cons 'right-fringe doom-ui-fringe-size) default-frame-alist) ;; no fringe in the minibuffer (add-hook! (emacs-startup minibuffer-setup) (set-window-fringes (minibuffer-window) 0 0 nil))) ;; ;; Plugins ;; (def-package! hideshow ; built-in :commands (hs-minor-mode hs-toggle-hiding hs-already-hidden-p) :config (setq hs-hide-comments-when-hiding-all nil)) ;; Show uninterrupted indentation markers with some whitespace voodoo. (def-package! highlight-indentation :commands (highlight-indentation-mode highlight-indentation-current-column-mode) :config (defun doom|inject-trailing-whitespace (&optional start end) "The opposite of `delete-trailing-whitespace'. Injects whitespace into buffer so that `highlight-indentation-mode' will display uninterrupted indent markers. This whitespace is stripped out on save, as not to affect the resulting file." (interactive (progn (barf-if-buffer-read-only) (if (use-region-p) (list (region-beginning) (region-end)) (list nil nil)))) (unless indent-tabs-mode (save-match-data (save-excursion (let ((end-marker (copy-marker (or end (point-max)))) (start (or start (point-min)))) (goto-char start) (while (and (re-search-forward "^$" end-marker t) (< (point) end-marker)) (let (line-start line-end next-start next-end) (save-excursion ;; Check previous line indent (forward-line -1) (setq line-start (point) line-end (save-excursion (back-to-indentation) (point))) ;; Check next line indent (forward-line 2) (setq next-start (point) next-end (save-excursion (back-to-indentation) (point))) ;; Back to origin (forward-line -1) ;; Adjust indent (let* ((line-indent (- line-end line-start)) (next-indent (- next-end next-start)) (indent (min line-indent next-indent))) (insert (make-string (if (zerop indent) 0 (1+ indent)) ? ))))) (forward-line 1))))) (set-buffer-modified-p nil)) nil) (add-hook! (highlight-indentation-mode highlight-indentation-current-column-mode) (if (or highlight-indentation-mode highlight-indentation-current-column-mode) (progn (doom|inject-trailing-whitespace) (add-hook 'before-save-hook #'delete-trailing-whitespace nil t) (add-hook 'after-save-hook #'doom|inject-trailing-whitespace nil t)) (remove-hook 'before-save-hook #'delete-trailing-whitespace t) (remove-hook 'after-save-hook #'doom|inject-trailing-whitespace t) (delete-trailing-whitespace)))) ;; For modes that don't adequately highlight numbers (def-package! highlight-numbers :commands highlight-numbers-mode) ;; Line highlighting (def-package! hl-line ; built-in :init (add-hook! (linum-mode nlinum-mode) #'hl-line-mode) :config ;; stickiness doesn't play nice with emacs 25+ (setq hl-line-sticky-flag nil global-hl-line-sticky-flag nil) ;; acts weird with evil visual mode, so disable it temporarily (defun doom|hl-line-off () (hl-line-mode -1)) (after! evil (add-hook! 'hl-line-mode-hook (when hl-line-mode (add-hook 'evil-visual-state-entry-hook #'doom|hl-line-off nil t) (add-hook 'evil-visual-state-exit-hook #'hl-line-mode nil t))))) ;; Line number column. A faster (or equivalent, in the worst case) line number ;; plugin than the built-in `linum'. (def-package! nlinum :commands nlinum-mode :preface (defvar linum-format "%3d ") (defvar nlinum-format "%4d ") :init (add-hook! (prog-mode text-mode) (unless (eq major-mode 'org-mode) (nlinum-mode +1))) :config (defun doom-nlinum-flush-window (&optional window) (let ((window (or window (selected-window))) (orig-win (selected-window))) (with-selected-window window (when nlinum-mode (if (not (eq window orig-win)) (nlinum--flush) ;; done in two steps to leave current line number highlighting alone (nlinum--region (point-min) (max 1 (1- (line-beginning-position)))) (nlinum--region (min (point-max) (1+ (line-end-position))) (point-max))))))) ;; nlinum has a tendency to lose line numbers over time; a known issue. These ;; hooks/advisors attempt to stave off these glitches. (defun doom*nlinum-flush-all-windows (&rest _) "Fix nlinum margins after major UI changes (like a change of font)." (mapc #'doom-nlinum-flush-window (doom-visible-windows)) nil) (advice-add #'set-frame-font :after #'doom*nlinum-flush-all-windows) (defun doom*nlinum-flush (&optional _ norecord) ;; norecord check is necessary to prevent infinite recursion in ;; `select-window' (when (not norecord) (doom-nlinum-flush-window))) (advice-add #'select-window :before #'doom*nlinum-flush) (advice-add #'select-window :after #'doom*nlinum-flush) (add-hook '+evil-esc-hook #'doom*nlinum-flush-all-windows t) (add-hook 'focus-in-hook #'doom*nlinum-flush-all-windows) (add-hook 'focus-out-hook #'doom*nlinum-flush-all-windows) ;; (after! web-mode (advice-add #'web-mode-fold-or-unfold :after #'doom*nlinum-flush)) ;; Optimization: calculate line number column width beforehand (add-hook! nlinum-mode (setq nlinum--width (length (save-excursion (goto-char (point-max)) (format-mode-line "%l")))))) ;; Helps us distinguish stacked delimiter pairs. Especially in parentheses-drunk ;; languages like Lisp. (def-package! rainbow-delimiters :commands rainbow-delimiters-mode :config (setq rainbow-delimiters-max-face-count 3) :init (add-hook 'lisp-mode-hook #'rainbow-delimiters-mode)) ;; indicators for empty lines past EOF (def-package! vi-tilde-fringe :when (display-graphic-p) :demand t :config (global-vi-tilde-fringe-mode t)) ;; For a distractions-free-like UI, that dynamically resizes margets and can ;; center a buffer. (def-package! visual-fill-column :commands visual-fill-column-mode :config (setq-default visual-fill-column-center-text nil visual-fill-column-width fill-column)) ;; ;; Modeline ;; (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)))))) (defsubst doom--prepare-modeline-segments (segments) (let (segs) (dolist (seg segments (nreverse segs)) (push (if (stringp seg) seg (list (intern (format "doom-modeline-segment--%s" (symbol-name seg))))) segs)))) (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))) (list lhs (propertize " " 'display `((space :align-to (- (+ right right-fringe right-margin) ,(+ 1 (string-width (format-mode-line rhs))))))) rhs))) ,(unless (bound-and-true-p byte-compile-current-file) `(let (byte-compile-warnings) (byte-compile #',sym)))))) (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." (let ((modeline (doom-modeline key))) (when modeline (setf (if default (default-value 'mode-line-format) (buffer-local-value 'mode-line-format (current-buffer))) modeline)))) (provide 'core-ui) ;;; core-ui.el ends here