Here are my current Emacs settings from ~/.emacs.d/init.el.
First, I declare package repositories.
(require 'package)
(setq package-archives
'(("gnu" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/")
("nongnu" . "https://elpa.nongnu.org/nongnu/")
("org" . "http://orgmode.org/elpa/")))
(package-initialize)
(when (not package-archive-contents)
(package-refresh-contents))
Next is for use-package. It has been default since Emacs 29. I wonder if I still need this…
;; for use-package
(unless (package-installed-p 'use-package)
(package-install 'use-package))
(require 'use-package)
(setq use-package-always-ensure t)
(setq use-package-compute-statistics t) # to see packges that took long
Appearance
material-theme has been my favorite theme for some time now. It’s dark but warm, and easy on the eyes.
;; Material theme
(use-package material-theme
:ensure t ;; require material-theme
:load-path "~/.emacs.d/elpa/material-theme-20210904.1226"
:config
(load-theme 'material t))
Doom modeline makes Emacs modern-looking and cool. I like the minimul design.
;; doom modeline
(use-package doom-modeline
:ensure t
:hook (after-init . doom-modeline-mode))
;; hide menu/tool bar
(menu-bar-mode 0)
(tool-bar-mode 0)
Misc settings
;; ====== misc. ============================================
;; No startup message
(setq inhibit-startup-message t)
;; no backup files
(setq make-backup-files nil)
(setq auto-save-default nil)
;; Delete auto-save files
(setq delete-auto-save-files t)
;; 1-line scroll
(setq scroll-conservatively 101)
;; silence bell
(setq ring-bell-function 'ignore)
;; yes or no to y or n
(fset 'yes-or-no-p 'y-or-n-p)
;; add to load-path
(add-to-list 'load-path "~/.emacs.d/site-lisp")
Visualize various info
Show column and line number in modeline.
;; columm and line number
(column-number-mode t)
rainbow-delimiters highlights corresponding brackets with color. I think it is a must-have when coding especially in emacs lisp. show-paren-mode makes bracket pairs stand out.
;; rainbow-delimiters
(use-package rainbow-delimiters
:ensure t
:hook (prog-mode . rainbow-delimiters-mode))
;; Blink corresponding paren
(show-paren-mode 1)
highlight-indent-guides package is also convenient when coding. It visualizes indent widths with vertical lines/rectangles.
;; visualize indent
(use-package highlight-indent-guides
:ensure t
:hook
(prog-mode . highlight-indent-guides-mode)
:custom
(highlight-indent-guides-method 'column))
Emacs has some complex key sequences such as C-x r l (bookmark-bmenu-list).
which-key shows a hint for the next available key options. It will be of some help when you rember which key to start.
;; key binding hint
(use-package which-key
:ensure t
:config
(which-key-mode)
(setq which-key-idle-delay 0.3))
Not sure if this is working…
(pixel-scroll-precision-mode)
vundo visualizes the undo-tree and can operate on it. It is of great help, for example, when you did too many undos/redos and forgot where you are.
;; ====== undo ==========================================
;; visual undo-tree for emacs-28
(use-package vundo
:ensure t
:bind (("C-x u" . vundo)
("C-/" . undo-only)
("C-?" . undo-redo))
:config
(setq vundo-glyph-alist vundo-ascii-symbols))
It shows undo-tree like this:
○──○──○
│ └──●
├──○
└──○
You can move around in the undo-tree with cursor keys.
;; nerd-icons
(use-package nerd-icons ;; M-x nerd-icons-install-fonts required
:ensure t)
(use-package nerd-icons-dired
:ensure t
:hook (dired-mode . nerd-icons-dired-mode))
(use-package nerd-icons-ibuffer
:ensure t
:hook (ibuffer-mode . nerd-icons-ibuffer-mode))
Next are my calendar settings. I set it to show only my company holidays.
;; ====== calendar ======================================
(setq calendar-week-start-day 1) ;; starts with Monday
(setq calendar-date-style 'iso) ;; default to YYYY/MM/DD
(require 'solar)
(setq holiday-general-holidays nil
holiday-local-holidays t
holiday-solar-holidays nil
holiday-bahai-holidays nil
holiday-christian-holidays nil
holiday-hebrew-holidays nil
holiday-islamic-holidays nil
holiday-oriental-holidays nil
holiday-other-holidays nil)
(setq holiday-local-holidays
'((holiday-fixed 1 1 "New Years Day")
(holiday-fixed 1 20 "MLK Jr. Day")
(holiday-fixed 2 17 "Presidents Day")
(holiday-fixed 4 22 "Earth Day")
(holiday-fixed 5 26 "Memorial day")
(holiday-fixed 6 19 "Juneteenth")
(holiday-fixed 7 4 "Forth of July")
(holiday-fixed 9 1 "Labor Day")
(holiday-fixed 11 27 "Thanksgiving")
(holiday-fixed 11 28 "Thanksgiving")
(holiday-fixed 12 24 "Christmas Eve")
(holiday-fixed 12 25 "Christmas")))
For efficient editing
These are for efficient cursor movements. Many are borrowed from Mastering Emacs book (you should buy a copy!) and Emacs blogs. Thank you for sharing great info!
other-window is usually bound to C-x o, but you would be glad if you change it to M-o as you use it all the time.
ace-window is useful when you have more than two windows in a frame. I assigned C-x o for this instead.
;; ====== key bindings ==================================
;; for faster movements between windows
(global-set-key (kbd "M-o") 'other-window)
(use-package ace-window
:ensure t
:bind (("C-x o" . 'ace-window)))
ibuffer is a better buffer list assinged to C-x C-b.
;; ibuffer-mode
(global-set-key [remap list-buffers] 'ibuffer)
Assigning oneline scroll to C-z and C-q is a good customization. You often find yourself in situations that you only want to scroll one or two lines.
;; scroll window with one key-stroke
(defun scroll-n-lines-up (&optional n)
"Scroll up N lines (1 by default)."
(interactive "P")
(scroll-up (prefix-numeric-value n)))
(defun scroll-n-lines-down (&optional n)
"Scroll down N lines (1 by default)."
(interactive "P")
(scroll-down (prefix-numeric-value n)))
(global-set-key "\C-z" 'scroll-n-lines-up)
(global-set-key "\C-q" 'scroll-n-lines-down)
With avy package, you can jump your cursor on Emacs just like pointing location with mouse. I think avy is revolutionary in that it’s not restricted by window/buffer boundaries, and it has great potential. It’s just that my fingers can’t use avy in an automatic fashion yet.
;; ====== avy ==============================================
(use-package avy
:ensure t
:bind (("C-:" . avy-goto-char-2)))
I have started using yasnippet recently, and it is great. It is a more powerful version of org-temp, which I have been using quite often.
;; ====== yasnippet ========================================
(use-package yasnippet
:ensure t
:hook (org-mode . yas-minor-mode)
:config
(setq yas-snippet-dirs '("~/.emacs.d/snippets"))
(yas-reload-all))
One of my snippets is for a ox-hugo blog entry:
# -*- mode: snippet -*-
# name: blogtitle
# key: <bt
# --
#** <TODO> ${1:Title} # remove '#'
:PROPERTIES:
:EXPORT_FILE_NAME: ${2:Filename}
:EXPORT_DATE: 202${3:6-01-01}
:END:
$0
If I type <bt + TAB, ** to :END: is inserted. Title, Filename and the date are parameters that I will type.
embark
embark has great potential. It can execute actions against an item on which your cursor is located or selected items. For example, if your cursor is on a link, you can open it with your default browser, eww, etc.
;; ====== embark ===========================================
(use-package embark
:ensure t
:bind
(("C-." . embark-act)
("C-;" . embark-dwim)
("C-h B" . embark-bindings))
:init
(setq prefix-help-command #'embark-prefix-help-command))
(use-package embark-consult
:ensure t
:hook
(embark-collect-mode . consult-preview-at-point-mode))
I have a special use case with embark which I learned from AI lately. When I am in dired of a remote directory (w/ tramp), and I want to view a remote PDF file, the function below downloads to local at /tmp, and open it with local PDF viewer. I don’t have to go to the remote desktop on Proxmox anymore to view remote PDF files.
(defun my-embark-open-remote-pdf-locally (file)
"Copy remote FILE locally and open with system PDF viewer."
(interactive "fPDF file: ")
(let* ((local-copy (concat "/tmp/" (file-name-nondirectory file))))
(copy-file file local-copy t) ;; Copy from TRAMP path to local /tmp
(start-process "open-pdf" nil
(cond
((eq system-type 'darwin) "open") ;; macOS
((eq system-type 'gnu/linux) "xdg-open") ;; Linux
((eq system-type 'windows-nt) "start")) ;; Windows
local-copy)))
;; Add to Embark file actions
(require 'embark)
(define-key embark-file-map (kbd "L") #'my-embark-open-remote-pdf-locally)
dired - the filer app for Emacs
Long time ago, I regularly used “filer” which is an old file manager app on pre-Windows era PCs. dired is a modern filer app exclusive(?) to Emacs. It’s intuitive, convenient and useful as it gets.
;; ====== dired =========================================
(require 'dired-x)
;; if git controlled, use git mv for rename
(setq dired-vc-rename-file t)
(use-package dired-subtree
:ensure t
:after dired
:bind (:map dired-mode-map
("TAB" . dired-subtree-toggle)))
With dired-rsync, you can use rsync (instead of scp/ssh) when copying file(s) to a remote machine. A nice thing about dired-rsync is that it’s asynchronous and non-blocking. Copy status is shown in the modeline.
;; use rsync for copying
(use-package dired-rsync
:ensure t
:bind (:map dired-mode-map
("C-c C-r" . dired-rsync))
:config (add-to-list 'mode-line-misc-info
'(:eval dired-rsync-modeline-status 'append)))
troubleshooting I did
If you get a below error invoking rsync,
rsync: --info=progress2: unknown option
Your rsync might be very old. Install a newer version with brew:
brew install rsync
If your Mac still runs the old version although /opt/homebrew/bin comes earlier than /usr/bin, do:
hash -r
This fixed my issue.
Settings for Mac
I used to use exec-path-from-shell package but below is a workaround as the package added more than 5 seconds when starting Emacs up. The PATH value below was manually copied from echo $PATH, assuming it shouldn’t change often. If it’s changed, I need to update here, too.
(when (memq window-system '(mac ns x))
(progn
(setenv "PATH" "/Users/kachiwa/.pyenv/shims:/Users/kachiwa/.pyenv/bin:/Users/kachiwa/.emacs.d/share/eclipse.jdt.ls/bin:/Users/kachiwa/bin/apache-maven-3.9.5/bin:/Users/kachiwa/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/opt/X11/bin:/Library/Apple/usr/bin:/Users/kachiwa/Library/Application Support/JetBrains/Toolbox/scripts")
(setq exec-path (split-string (getenv "PATH") path-separator))))
These are for Mac, too. I think using command key as meta is a must.
;; set command keys as meta key
(setq mac-command-modifier 'meta)
;; font settings for MacBook Pro 16
(if (display-graphic-p)
(set-face-attribute 'default nil :height 160))
(if (window-system)
(progn
(set-frame-height (selected-frame) 44)
(add-hook 'after-make-frame-functions
#'(lambda (f)
(with-selected-frame f
(set-frame-height f 44))))))
Completion packages
Completion is one of Emacs’s killer features. It has rejuvenated and modernized Emacs for a few decades.
;; ====== completion ========================================
;; orderless
(use-package orderless
:ensure t
:custom
(completion-styles '(orderless basic))
(completion-category-overrides '((file (styles partial-completion))))
(completion-category-defaults nil) ;; Disable defaults, use our settings
)
;; vertico - vertial completion
(use-package vertico
:ensure t
:init (vertico-mode 1)
:config (setq vertico-count 14))
(use-package savehist
:ensure t
:defer t
:init (savehist-mode 1))
;; marginalia - rich annotations
(use-package marginalia
:ensure t
:init (marginalia-mode))
(use-package nerd-icons-completion
:ensure t
:after marginalia
:config
(nerd-icons-completion-mode)
(add-hook 'marginalia-mode-hook #'nerd-icons-completion-marginalia-setup))
;; consult - preview everything
(use-package consult
:ensure t
:bind (("C-x b" . consult-buffer)
;; ("M-g g" . consult-goto-line) ;; line number
("M-g o" . consult-outline)
("M-s f" . consult-find)
("M-s l" . consult-line) ;; key word
("M-s g" . consult-grep)))
;; corfu - in-buffer completion with a small popup
(use-package corfu
:ensure t
:custom ((corfu-auto t)
(corfu-auto-prefix 2)
(corfu-auto-delay 1.2)
(corfu-cycle t))
:init
(global-corfu-mode)
(corfu-popupinfo-mode))
;; completion-at-point extensions
(use-package cape
:ensure t
:defer t
:init
;; Add `completion-at-point-functions', used by `completion-at-point'.
(add-to-list 'completion-at-point-functions #'cape-file)
(add-to-list 'completion-at-point-functions #'cape-dabbrev)
;; (add-to-list 'completion-at-point-functions #'cape-history)
(add-to-list 'completion-at-point-functions #'cape-keyword)
)
Org-mode
org-mode is the reason why I still use Emacs today like many Emacs fans. org-mode is extremely versatile. org-mode can be:
- note-taking app
- outline editor
- planner/scheduler
- small spreadsheet
- blog editor
- wikipage editor
The world of org-mode is huge. I think you should start small, for example, just as a note-taking app. Otherwise, you’d get overwhelmed by the vast and deep features. This is Emacs. Take your time. The great thing about org-mode is that org files are just in plain text.
;; ====== org-mode settings =================================
;; Modern-looking org-mode (requires installing org-modern)
(use-package org-modern
:ensure t
:hook
(org-mode . org-modern-mode))
;; hide emphasis markup
(setq org-hide-emphasis-markers t)
;; disable org-mode truncate-lines
(setq org-startup-truncated nil)
;; Suppress auto-indent for org-mode
(add-hook 'org-mode-hook (lambda () (electric-indent-local-mode -1)))
;; org-mode export github-flavored markdown
;;(eval-after-load "org"
;; '(require 'ox-gfm nil t))
;; enable shortcuts such as <s + TAB
(require 'org-tempo)
;; speed command
(setq org-use-speed-commands t)
;; Add to TODO status
(setq org-todo-keywords
'((sequence "<TODO>(t)" "<WAIT>(w)" "|" "<DONE>(d)" "<CANCEL>(c)")))
speed-commands are single-key shortcuts available in org-mode. When your cursor is at the first ‘*’ on a heading line, you can use those short-cut keys. Type ‘?’ will show help. I found these keys quite helpful:
n/p: jump to next/previous headingf/b: jump to next/previous heading (same level)u: jump to upper level heading
org-journal and org-agenda are for your daily memo and scheule management.
;; ====== org-journal ======================================
(use-package org-journal
:ensure t
:bind (("C-c j" . org-journal-new-entry))
:custom
(org-journal-dir "~/org/memo/journal")
(org-journal-date-format "%A, %d %B %Y")
(org-journal-file-format "%Y%m%d.org")
(org-journal-file-type 'monthly))
;; ====== org-agenda =======================================
(setq org-agenda-files '("~/org/sched/todo.org"))
(global-set-key (kbd "C-c a") 'org-agenda)
jinx is a real time spell checker. It automatically checks visible portion of the buffer and highlights incorrect words. It’s most useful in org-mode. I don’t have to manually perform ispell-region anymore. It’s very convenient.
;; ====== jinx =============================================
;; auto spell-checker
(use-package jinx
:ensure t
:hook (emacs-startup . global-jinx-mode)
:config
(add-to-list 'jinx-exclude-regexps '(t ".*[^[:ascii:]].*"))
:bind (("M-$" . jinx-correct)
("C-M-$" . jinx-languages)))
You need to install the prerequisites:
brew install enchant pkgconf
For Python programming.
;; ====== eglot ============================================
;; Flycheck
(use-package flycheck
:ensure t
:defer t
:init
(add-hook 'after-init-hook 'global-flycheck-mode))
(use-package flycheck-inline
:ensure t
:init
(with-eval-after-load 'flycheck
(add-hook 'flycheck-mode-hook #'flycheck-inline-mode)))
(use-package flycheck-eglot
:ensure t
:after (flyckeck eglot)
:config
(global-flycheck-eglot-mode 1))
;; black
(use-package blacken
:ensure t
:config
(add-hook 'python-mode-hook 'blacken-mode))
;; suppress warnings (is this needed?)
(setq python-indent-guess-indent-offset-verbose nil)
;; pyvenv
(use-package pyvenv
:ensure t
:defer t
:config
(pyvenv-mode 1))
;; eglot - lsp client
(setq lsp-java-server-install-dir "/Users/kachiwa/.emacs.d/share/eclipse.jdt.ls")
(use-package eglot
:ensure t
:hook
(python-mode . eglot-ensure)
(js-mode . eglot-ensure))
;; need this for eglot to find pylsp in a remote server
(add-to-list 'tramp-remote-path 'tramp-own-remote-path)
For Java.
Note: I’m a beginner.
;; ====== for java =========================================
(add-hook 'java-mode-hook 'eglot-java-mode)
(add-hook 'eglot-java-mode-hook
(lambda ()
(define-key eglot-java-mode-map (kbd "C-c l n") #'eglot-java-file-new)
(define-key eglot-java-mode-map (kbd "C-c l x") #'eglot-java-run-main)
(define-key eglot-java-mode-map (kbd "C-c l t") #'eglot-java-run-test)
(define-key eglot-java-mode-map (kbd "C-c l N") #'eglot-java-project-new)
(define-key eglot-java-mode-map (kbd "C-c l T") #'eglot-java-project-build-task)
(define-key eglot-java-mode-map (kbd "C-c l R") #'eglot-java-project-build-refresh)))
(setq lsp-java-server-install-dir "/Users/kachiwa/.emacs.d/share/eclipse.jdt.ls")
I use Emacs for email only when I write a long email. Other times, I just use Gmail web interface. I use notmuch as email client. My mail box is in my remote homelab server. To sync mail box with Gmail, I use mbsync.
;; ====== email =============================================
(use-package notmuch
:ensure t
:commands notmuch-hello
:bind (("C-c m" . notmuch-hello)))
(setq notmuch-command "~/bin/remote-notmuch.sh")
(setq message-send-mail-function 'smtpmail-send-it
smtpmail-starttls-credentials '(("smtp.gmail.com" 587 nil nil))
smtpmail-auth-credentials '(("smtp.gmail.com" 587 "achiwa912@gmail.com" nil))
smtpmail-default-smtp-server "smtp.gmail.com"
smtpmail-smtp-server "smtp.gmail.com"
smtpmail-smtp-service 587
starttls-use-gnutls t
)
(setq notmuch-search-oldest-first nil)
(defun mbsync ()
"Run mbsync -a remotely at remote machine. Will block for some time."
(interactive)
(let ((default-directory (expand-file-name "/ssh:act:~/")))
(process-file "mbsync" nil nil nil "-a")))
remote-notmuch.sh:
#!/bin/bash
printf -v ARGS "%q " "$@"
exec ssh notmuch notmuch ${ARGS}
from ~/.ssh/config:
Host notmuch
HostName 127.0.0.1
Port 2222
User kachiwa
ControlMaster auto
ControlPath ~/.ssh/master-%h@%p:%r
ControlPersist 15m
IdentityFile ~/.ssh/<sshkey>
For reading
You can view and annotate PDF files within Emacs frames with pdf-tools. It’s great for note-taking while reading. I configured this as deferred-loading as it had added more than 5 sec to starting up Emacs.
;; ====== pdf tools ========================================
;; this will crash emacs...
;; https://stackoverflow.com/questions/70202413/configure-pdf-tools-in-emacs-running-on-macos
(use-package pdf-tools
:ensure t
:config
(setenv "PKG_CONFIG_PATH" "/opt/homebrew/Cellar/zlib/1.3.1/lib/pkgconfig")
:hook (after-init . pdf-tools-install))
eww is a simple text-based web browser. It can also show images, but CSS or JS is not supported. Though not for daily use, it’s great for long reading. Typing “R” will (usually) navigates you straight to the page content.
;; ==== eww ================================================
;; https://futurismo.biz/archives/2950/
;;(setq browse-url-browser-function 'eww-browse-url)
(defun eww-hide-images ()
"eww hide images"
(interactive)
(setq-local shr-put-image-function 'shr-put-image-alt)
(eww-reload))
(defun eww-show-images ()
"eww show images"
(interactive)
(setq-local shr-put-image-function 'shr-put-image)
(eww-reload))
(defun shr-put-image-alt (spec alt &optional flags)
(insert alt))
;; hide images from the beginning
(defun eww-mode-hook--hide-image ()
(setq-local shr-put-image-function 'shr-put-image-alt))
(add-hook 'eww-mode-hook 'eww-mode-hook--hide-image)
(setq eww-search-prefix "https://duckduckgo.com/html/?k1=-1&kc=1&kf=-1&q=")
;; (setq eww-search-prefix "https://www.google.com/search?q=")
(setq browse-url-browser-function 'eww-browse-url) ;; default to eww
(ace-link-setup-default) ;; ace-link - click 'o' on eww
Although I set up elfeed, I barely use it.
;; ====== elfeed ===========================================
(use-package elfeed
:ensure t
:custom
(setq elfeed-db-directory "~/.elfeed")
:bind
("C-c e" . elfeed))
(setq elfeed-feeds '("http://feeds.feedburner.com/Techcrunch"
"https://www.japantimes.co.jp/feed/topstories/"
"https://www.techradar.com/uk/feeds/articletype/feature"
))
Shell/terminal
I tried a few shells and terminal emulators for Emacs, and for now, I like eat the most. It’s fast and fits to my workflow.
;; ====== eat ==============================================
;; https://www.reddit.com/r/emacs/comments/1cowif8/override_key_in_emacseats_semichar_mode/
(use-package eat
:load-path "~/.emacs.d/site-lisp/emacs-eat"
:commands eat
:config
(customize-set-variable ;; has :set code
'eat-semi-char-non-bound-keys
(append
(list (vector meta-prefix-char ?o))
eat-semi-char-non-bound-keys))
(setq eat-tramp-shells '(("ssh" . "/bin/bash")))
(setq eat-term-name "xterm-256color"))
Blog
This blog is written using hugo and ox-hugo. ox-hugo allows you to write blog articles in org-mode. hugo is extremely fast static site generator. I also use Pelican package, but it would take 1-2 min to compile. With hugo, it barely takes a second. (ox-hugo takes a few seconds)
;; ====== ox-hugo ==========================================
(use-package ox-hugo
:ensure t
:pin melpa
:after ox)
(defun hugo ()
"Run hugo remotely at remote machine (act). Will block for some time."
(interactive)
(let ((default-directory (expand-file-name "/ssh:act:~/hugo/couk/")))
(process-file "hugo" nil nil nil)))
csv-mode
I manage friends’ and relatives’ addresses for holiday cards with a csv file. C-c C-a will give me a spreadsheet-like view, aligning the columns.
;; ====== csv-mode =========================================
(use-package csv-mode
:ensure t
:config
(setq csv-separators '(?\, ?\; ?\t))
(setq csv-align-max-width 24)
:bind (:map csv-mode-map
("C-c C-a" . csv-align-mode)))