Rewriting My Configuration with Rational Emacs


Rewriting my configuration with Rational Emacs

Rational Emacs has been out for about 5 months now and in that time, a number of System Crafters community members have contributed!

Thanks to Jeff Bowman’s help, we’ve been able to merge around 80 PRs adding new features, documentation, and bug fixes!

Today I’m finally going to start a new personal Emacs configuration from the ground up, but this time I’m going to use Rational Emacs as the basis.

The goal is to see how much of my own configuration I can replicate using what’s already there and figure out places where we might be able to add some additional functionality!

What do I use regularly?

Here are some of the things I want to set up:

  • General theme and mode line config
  • Straight.el and Guix package sources
  • Completion system (Vertico) with Consult and Embark
  • Magit
  • Project.el
  • Evil Mode
  • Org Mode settings
  • Eglot for software development
  • Eshell and vterm

Things we may need to add to Rational Emacs:

  • Lispy or some other Lisp editing mode (paredit, etc)
  • org-present
  • compile (in IDE module?)
  • Dired

What should we do next?

I’d really like to make Rational Emacs more useful to more people!

I’d like to get your feedback on the following:

  • Have you tried it? What kept it from being your daily driver?
  • Tweak the principles to make things more convenient or effective?
  • Should we make modules more granular?
  • Rename the project?
  • What else?

Potential expansion areas:

  • Email (mu4e? Notmuch? Gnus?)
  • RSS
  • More IDE languages

Name Ideas:

  • crafted-emacs
  • us-emacs

Final config:


;;; Personal Rational Emacs Config
;;; Author: David Wilson

;; TODO: Override this macro?  Make it configurable?
;; (defmacro rational-package-install-package (package)
;;   "Only install the package if it is not already installed."
;;   `(unless (package-installed-p ,package) (package-install ,package)))

;;; Core Settings

(require 'rational-defaults)

;;; Set up C-SPC prefix map

(defalias 'ctl-spc-keymap (make-sparse-keymap))

(defvar ctl-spc-map (symbol-function 'ctl-spc-keymap)
  "Global keymap for characters following C-SPC.")

(define-key global-map (kbd "C-SPC") 'ctl-spc-keymap)

;;; User Interface

(require 'rational-ui)

;; Install minions
(rational-package-install-package 'minions)
(add-hook 'doom-modeline-mode-hook 'minions-mode)

;; Install doom-themes and set the theme
(rational-package-install-package 'doom-themes)
(disable-theme 'deeper-blue)
(load-theme 'doom-palenight t)

(custom-set-faces '(mode-line ((t (:height 200))))
                  '(mode-line-inactive ((t (:height 200)))))

;; Set configuration variables
(custom-set-variables '(rational-ui-display-line-numbers t)
                      '(doom-modeline-height 35))

;;; Evil Mode

(require 'rational-evil)

;; Set configuration variables
(custom-set-variables '(rational-evil-discourage-arrow-keys t)
                      '(evil-want-C-u-scroll t))

;; Set preferred key bindings
(global-set-key (kbd "M-/") 'evilnc-comment-or-uncomment-lines)
(global-set-key (kbd "C-M-u") 'universal-argument)

;;; Completions and Actions

(require 'rational-completion)

(define-key vertico-map (kbd "C-f") 'vertico-exit)
(define-key minibuffer-local-map (kbd "C-d") 'embark-act)
(define-key project-prefix-map (kbd "g") 'consult-ripgrep)

(global-set-key (kbd "C-M-j") 'consult-buffer)

;;; Project Management

(require 'rational-project)

;;; Source Control

(rational-package-install-package 'magit)
(setq magit-display-buffer-function #'magit-display-buffer-same-window-except-diff-v1)

(global-set-key (kbd "C-M-;") #'magit-status)

;;; Org Mode

;; Turn on variable pitch for non-monospace fonts
(variable-pitch-mode 1)

;;; Presentations

(require 'rational-screencast)

;;; IDE

(require 'rational-ide)

(rational-package-install-package 'typescript-mode)

;;; Lisp Editing

(require 'rational-lisp)

;;; Shells

(rational-package-install-package 'vterm)
(rational-package-install-package 'xterm-color)

(setq vterm-max-scrollback 10000)

;; Configure vterm for evil-mode
(with-eval-after-load 'evil
  ;; Make sure that entering insert mode positions the cursor correctly
  (advice-add 'evil-collection-vterm-insert :before #'vterm-reset-cursor-point))

(defun read-file (file-path)
    (insert-file-contents file-path)

(defun dw/get-current-package-version ()
  (let ((package-json-file (concat (eshell/pwd) "/package.json")))
    (when (file-exists-p package-json-file)
      (let* ((package-json-contents (read-file package-json-file))
             (package-json (ignore-errors (json-parse-string package-json-contents))))
        (when package-json
          (ignore-errors (gethash "version" package-json)))))))

(defun dw/map-line-to-status-char (line)
  (cond ((string-match "^?\\? " line) "?")))

(defun dw/get-git-status-prompt ()
  (let ((status-lines (cdr (process-lines "git" "status" "--porcelain" "-b"))))
    (seq-uniq (seq-filter 'identity (mapcar 'dw/map-line-to-status-char status-lines)))))

(defun dw/get-prompt-path ()
  (let* ((current-path (eshell/pwd))
         (git-output (shell-command-to-string "git rev-parse --show-toplevel"))
         (has-path (not (string-match "^fatal" git-output))))
    (if (not has-path)
        (abbreviate-file-name current-path)
      (string-remove-prefix (file-name-directory git-output) current-path))))

;; This prompt function mostly replicates my custom zsh prompt setup
;; that is powered by
(defun dw/eshell-prompt ()
  (let ((current-branch (magit-get-current-branch))
        (package-version (dw/get-current-package-version)))
     (propertize (system-name) 'face `(:foreground "#62aeed"))
     (propertize " ॐ " 'face `(:foreground "white"))
     (propertize (dw/get-prompt-path) 'face `(:foreground "#82cfd3"))
     (when current-branch
        (propertize " • " 'face `(:foreground "white"))
        (propertize (concat " " current-branch) 'face `(:foreground "#c475f0"))))
     (when package-version
        (propertize " @ " 'face `(:foreground "white"))
        (propertize package-version 'face `(:foreground "#e8a206"))))
     (propertize " • " 'face `(:foreground "white"))
     (propertize (format-time-string "%I:%M:%S %p") 'face `(:foreground "#5a5b7f"))
     (if (= (user-uid) 0)
         (propertize "\n#" 'face `(:foreground "red2"))
       (propertize "\nλ" 'face `(:foreground "#aece4a")))
     (propertize " " 'face `(:foreground "white")))))

(add-hook 'eshell-banner-load-hook
          (lambda ()
            (setq eshell-banner-message
                  (concat "\n" (propertize " " 'display (create-image "~/.dotfiles/.emacs.d/images/flux_banner.png" 'png nil :scale 0.2 :align-to "center")) "\n\n"))))

(defun dw/eshell-configure ()
  ;; Make sure magit is loaded
  (require 'magit)

  (require 'evil-collection-eshell)

  (require 'xterm-color)

  (push 'eshell-tramp eshell-modules-list)
  (push 'xterm-color-filter eshell-preoutput-filter-functions)
  (delq 'eshell-handle-ansi-color eshell-output-filter-functions)

  ;; Save command history when commands are entered
  (add-hook 'eshell-pre-command-hook 'eshell-save-some-history)

  (add-hook 'eshell-before-prompt-hook
            (lambda ()
              (setq xterm-color-preserve-properties t)))

  ;; Truncate buffer for performance
  (add-to-list 'eshell-output-filter-functions 'eshell-truncate-buffer)

  ;; We want to use xterm-256color when running interactive commands
  ;; in eshell but not during other times when we might be launching
  ;; a shell command to gather its output.
  (add-hook 'eshell-pre-command-hook
            (lambda () (setenv "TERM" "xterm-256color")))
  (add-hook 'eshell-post-command-hook
            (lambda () (setenv "TERM" "dumb")))

  ;; Use completion-at-point to provide completions in eshell
  (define-key eshell-mode-map (kbd "<tab>") 'completion-at-point)

  ;; Initialize the shell history

  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "C-r") 'consult-history)
  (evil-define-key '(normal insert visual) eshell-mode-map (kbd "<home>") 'eshell-bol)

  (setenv "PAGER" "cat")

  (setq eshell-prompt-function      'dw/eshell-prompt
        eshell-prompt-regexp        "^λ "
        eshell-history-size         10000
        eshell-buffer-maximum-lines 10000
        eshell-hist-ignoredups t
        eshell-highlight-prompt t
        eshell-scroll-to-bottom-on-input t
        eshell-prefer-lisp-functions nil))

(add-hook 'eshell-first-time-mode-hook #'dw/eshell-configure)
(setq eshell-directory-name "~/.dotfiles/.emacs.d/eshell/"
      eshell-aliases-file (expand-file-name "~/.dotfiles/.emacs.d/eshell/alias"))

;; (setup (:pkg exec-path-from-shell)
;;        (setq exec-path-from-shell-check-startup-files nil)
;;        (when (memq window-system '(mac ns x))
;;          (exec-path-from-shell-initialize)))

(defun dw/switch-to-eshell ()
  (if (project-current)
      (call-interactively #'project-eshell)
    (call-interactively #'eshell)))

(define-key ctl-spc-map (kbd "SPC") #'dw/switch-to-eshell)

;; TODO: These may not be needed
;; (add-hook 'c-mode-hook 'eglot-ensure)
;; (add-hook 'typescript-mode-hook 'eglot-ensure)

;; TODO: Bring over org-present config


;;; early-config.el --- Early Emacs configuration    -*- lexical-binding: t; -*-

;; Copyright (C) 2022  David Wilson

;; Author: David Wilson <daviwil@phantom>
;; Keywords:

;; Set the font faces early
(custom-set-variables '(rational-ui-default-font '(:font "JetBrains Mono" :height 230)))
(set-face-attribute 'variable-pitch nil :font "Iosevka Aile" :weight 'light)

;; Set frame transparency and maximize frame by default before the first frame loads
(set-frame-parameter (selected-frame) 'alpha '(90 . 90))
(add-to-list 'default-frame-alist '(alpha . (90 . 90)))
(set-frame-parameter (selected-frame) 'fullscreen 'maximized)
(add-to-list 'default-frame-alist '(fullscreen . maximized))
Subscribe to the System Crafters Newsletter!
Stay up to date with the latest System Crafters news and updates! Read the Newsletter page for more information.
Name (optional)
Email Address