kimim / kimim-emacs

my emacs init configuration files

Home Page:http://kimi.im/kimim-emacs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

README

https://travis-ci.org/kimim/kimim-emacs.svg

Introduction

This Emacs configuration file is written in literate programming method [fn:1]. The emacs-lisp code is extracted from this orgmode [fn:2] file, and then compiled to binary elc file for fast loading in the future.

Installation

First of all, you need to get emacs and install it in your machine.

Windows/MSYS2

On Windows, we have three options: 1) MSYS2, 2) Cygwin, 3) WSL2.

If your msys2 [fn:3] is installed in C:\msys64, you can open C:\msys64\mingw64.exe, and run following command to install Emacs:

pacman -S git mingw-w64-x86_64-emacs

Windows/Cygwin

Since 2020, I use MSYS2 MingW64 Emacs for most of the time, Cygwin Emacs is not actively tested now, but Emacs should work in Cygwin environment smoothly.

Cygwin project[fn:4] is a large collection of GNU and Open Source tools provide functionality similar to a Linux distribution on Windows.

You can download and install Cygwin setup [fn:5] in your machine, for example, at C:\cygwin64. Then you can use mintty.exe to work with bash.

Then start emacs from cygwin terminal, and right click the emacs icon to pin it to taskbar, then click the property of this icon to add bellow to the target field. This way will hide the black terminal.

C:\cygwin64\bin\run.exe emacs-w32

You can also add emacs --daemon in your scheduled task, and change the target field as:

C:\cygwin64\bin\run.exe emacsclient -c

This will attach the client to the server daemon. You can get a running emacs very quick.

Windows/WSL2

Nowadays, Windows is embracing Linux tightly. You can try emacs on Windows subsystem for Linux version 2. You can get Ubuntu Linux from Windows App Store or Manjaro Linux for WSL2 [fn:6].

Commands to install Emacs is same as on Linux in next section.

Linux

Most of the external tools I used in this Emacs configuration should be easily installed or already available in main Linux distributions.

For example, in Ubuntu, you can install this way:

sudo snap install emacs --classic

It is also very quick to add emacs in ArchLinux/Manjaro:

pacman -S emacs

macOS

For Apple macOS, most UNIX tools are installed already. You can use homebrew [fn:7] to install additional application if it is missing.

/bin/bash -c \
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew tap railwaycat/emacsmacport
brew install emacs-mac

Preparation

Get emacs config

You can follow below shell commands to prepare the emacs configuration files and folders:

# backup existing emacs config
cd ~ && mv .emacs .emacs-backup && mv .emacs.d .emacs.d-backup
# clone this config
git clone --recurse-submodules https://github.com/kimim/kimim-emacs
# copy default .emacs to ~
cp kimim-emacs/.emacs ~

This configuration uses several path to keep different information, you need to define them in ~/.emacs:

  • kimim/path-sync is the root of sync folder
  • kimim/path-emacs to sync emacs settings
  • kimim/path-org to sync todo list and journals
  • kimim/path-notes to sync notes
  • kimim/path-docs to keep reference documents
  • kimim/path-kimim-emacs where kimim-emacs you clone to

Let’s tell emacs, if the path is not set, report error, if the path is set, but not exists, create it.

(mapc (lambda (path)
        (if (not (boundp path))
            (error (concat "please set " (symbol-name path) " in ~/.emacs"))
          (if (not (file-exists-p (symbol-value path)))
              (make-directory (symbol-value path)))))
      '(kimim/path-sync
        kimim/path-emacs
        kimim/path-org
        kimim/path-notes
        kimim/path-docs
        kimim/path-kimim-emacs))

Then you can execute emacs to bootstrap itself.

chemacs2

Another way to try this configure out is to use chemacs2.

Clone to ~~/kimim-emacs~

git clone https://github.com/kimim/kimim-emacs ~/kimim-emacs

Add to chemacs2 profile file ~~/.emacs.profile.el~

(("default" . ((user-emacs-directory . "~/.emacs.default")))
 ("kimim" . ((user-emacs-directory . "~/kimim-emacs"))))

Start emacs with this config:

emacs --with-profile kimim

If you want use ~~/.emacs.kimim~ as the user-emacs-directory, you need to modify the value of variable kimim/path-kimim-emacs in early-init.el.

(defvar kimim/path-kimim-emacs "~/.emacs.kimim/")

PATH and exec-path

Environment variable PATH is the the searching path of executables by the shell running in Emacs while exec-path is the search path of Emacs itself. So we should set both of them to almost the same paths.

As I have a Windows box in the office, and an Apple macOS at home, so I need to specify these variables in different way.

(cond
 ((eq system-type 'cygwin)
  (setq kimim/path-root "/"))
 ((eq system-type 'darwin)
  (setq kimim/path-root "/")
  (add-to-list 'exec-path "/Library/TeX/texbin"))
 ((eq system-type 'gnu/linux)
  (setq kimim/path-root "/")
  (add-to-list 'exec-path "/usr/local/texlive/2020/bin/x86_64-linux/")))

(add-to-list 'exec-path (concat kimim/path-root "bin"))
(add-to-list 'exec-path (concat kimim/path-root "usr/bin"))
(add-to-list 'exec-path (concat kimim/path-root "usr/local/bin"))

Then append exec-path to PATH:

(setenv "PATH"
        (concat
         (mapconcat #'identity exec-path path-separator)
         (getenv "PATH")))

For Windows/MSYS64, we need to modify executable-find to locate shell scripts:

(defun executable-find (command &optional remote)
  "Search for COMMAND in `exec-path' and return the absolute file name.
Return nil if COMMAND is not found anywhere in `exec-path'.  If
REMOTE is non-nil, search on the remote host indicated by
`default-directory' instead."
  (if (and remote (file-remote-p default-directory))
      (let ((res (locate-file
                  command
                  (mapcar
                   (lambda (x) (concat (file-remote-p default-directory) x))
                   (exec-path))
                  exec-suffixes 'file-executable-p)))
        (when (stringp res) (file-local-name res)))
    ;; Use 1 rather than file-executable-p to better match the
    ;; behavior of call-process.
    (let ((default-directory (file-name-quote default-directory 'top)))
      (locate-file command exec-path exec-suffixes))))

Language

I prefer to use English/UTF-8 as default language environment.

(setenv "LANG" "en_US.UTF-8")
(setenv "LC_ALL" "en_US.UTF-8")
;; remove svn log LC_TYPE not defined warning.
(setenv "LC_CTYPE" "en_US.UTF-8")
(setenv "LC_TIME" "en_US.UTF-8")
(set-locale-environment "en_US.UTF-8")
(set-language-environment 'English)
(prefer-coding-system 'utf-8)
(set-buffer-file-coding-system 'utf-8-unix)
(set-keyboard-coding-system 'utf-8)
(set-selection-coding-system 'utf-8)
(set-file-name-coding-system 'utf-8)
(set-terminal-coding-system 'utf-8)
(set-clipboard-coding-system 'utf-8)
(cond
 ((member system-type '(windows-nt cygwin))
  (set-clipboard-coding-system 'utf-16le)))

global key map

Define new command prefix for keys such as “C-x m f”, “C-x m v”.

(define-prefix-command 'ctl-x-m-map)
(global-set-key "\C-xm" 'ctl-x-m-map)

Package

package [fn:8] is the modern elisp package management system, which let you easily download and install packages that implement additional features. Each package is a separate Emacs Lisp program, sometimes including other components such as an Info manual.

All the extensions used in this file are installed and managed by package.

Here I use use-package to defer the package loading and even installation, When you use the :commands keyword, it creates autoloads for those commands and defers loading of the module until they are used.

When I want to upgrade elpa packages, I just call list-packages, and let emacs refresh the package lists, then I just need to press U to upgrade all new packages.

If some weird things happen, for example compat-current-version error, a byte-force-recompile in ~/.emacs.d could work.

;; temporary disable signature check
(setq package-check-signature nil)
(setq package-user-dir "~/.emacs.d/elpa")
(setq package-archives
      '(;;("elpa" . "https://elpa.gnu.org/packages/")
        ;;("melpa" . "https://melpa.org/packages/")
        ("gnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
        ("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")))
(mapc
 (lambda (package)
   (unless (package-installed-p package)
     (progn (message "installing %s" package)
            (package-refresh-contents)
            (package-install package))))
 '(use-package diminish bind-key))

(require 'use-package)
(require 'diminish)
(require 'bind-key)
;; install package if missing
(setq use-package-always-ensure t)
(setq use-package-always-defer t)
(setq use-package-verbose t)

Look & Feel

Menu Bar and Tool Bar

Don’t display menu-bar, tool-bar, tooltip and scroll-bar. Because sometimes, they may catch your attention.

(defun kimim/menu-and-bar ()
  (cond
   (window-system
    ;; Enable copy and paste in Win32
    (setq select-enable-clipboard t)
    (tool-bar-mode -1)
    (tooltip-mode -1)
    (scroll-bar-mode -1)
    (when (eq window-system 'w32)
      (menu-bar-mode 0)))
   ((eq window-system nil)
    (menu-bar-mode 0))))

Font and Frame Size

Set default font and frame size for both window system. You should set-default-font first, otherwise, the frame height and width will be calculated with original default font height and width: frame-char-height and frame-char-width.

(use-package cnfonts
  :bind (("C-+" . cnfonts-increase-fontsize)
         ("C--" . cnfonts-decrease-fontsize)
         ("C-=" . cnfonts-increase-fontsize)
         ("C-0" . cnfonts-reset-fontsize)))
(defun kimim/set-frame-alist (top left height width)
  (let ((frame (selected-frame)))
    (set-frame-position frame left top)
    (set-frame-height frame height)
    (set-frame-width frame width))
  (setq default-frame-alist
        `((top . ,top)
          (left . ,left)
          (height . ,height)
          (width . ,width)
          (vertical-scroll-bars)
          (tool-bar-lines))))

(defun kimim/screen-width ()
  (/ (display-pixel-width)
     (frame-char-width)))

(defun kimim/screen-height ()
  (/ (display-pixel-height)
     (frame-char-height)))

(defun kimim/frame-and-font ()
  (interactive)
  (when window-system
    (require 'cnfonts)
    (cnfonts-enable)
    (cnfonts-set-font)
    ;; top, left ... must be integer
    (let* ((width (ceiling (* 0.8 (kimim/screen-width))))
           (height (ceiling (* 0.8 (kimim/screen-height))))
           (top (/ (display-pixel-height) 10))
           (left (/ (display-pixel-width) 10)))
      (kimim/menu-and-bar)
      (kimim/set-frame-alist
       top left height width))))

(defun kimim/frame-and-font-phone ()
  (interactive)
  (when window-system
    (require 'cnfonts)
    (cnfonts-enable)
    (cnfonts-set-font)
    ;; top, left ... must be integer
    (let* ((width (ceiling (* 0.36 (kimim/screen-width))))
           (height (ceiling (* 0.9 (kimim/screen-height))))
           (top (/ (display-pixel-height) 20))
           (left (/ (* 34 (display-pixel-width)) 100)))
      (kimim/menu-and-bar)
      (kimim/set-frame-alist
       top left height width))))

(defun kimim/frame-and-font-cast ()
  (interactive)
  (when window-system
    (require 'cnfonts)
    (cnfonts-enable)
    (cnfonts-set-font)
    ;; top, left ... must be integer
    (let* ((width (ceiling (* 0.36 (kimim/screen-width))))
           (height (ceiling (* 0.34 (kimim/screen-height))))
           (top (/ (display-pixel-height) 10))
           (left (/ (* 33 (display-pixel-width)) 100)))
      (kimim/set-frame-alist
       top left height width))))

(defun kimim/frame-and-font-mini ()
  (interactive)
  ;; top, left ... must be integer
  (let* ((width (ceiling (* 0.8 (kimim/screen-width))))
         (height (ceiling (* 0.4 (kimim/screen-height))))
         (top (/ (display-pixel-height) 2))
         (left (/ (display-pixel-width) 10))
         (frame (selected-frame)))
    (kimim/menu-and-bar)
    (kimim/set-frame-alist
     top left height width)))

(defun kimim/new-mini-frame-bottom ()
  "Create a small frame at bottom for write note."
  (interactive)
  ;; after-make-frame-hook make this no work, maybe change that hook
  (let ((frm (make-frame
              `((height . ,(ceiling (* 0.4 (kimim/screen-height))))
                (width . ,(ceiling (* 0.8 (kimim/screen-width))))))))
    (set-frame-position
     frm
     (/ (display-pixel-width) 10)
     (/ (display-pixel-height) 2))))

(kimim/frame-and-font)

Frames

Customize the frame title to display buffer file name.

(setq frame-title-format
      '((:eval (buffer-name))))
;; don't expand minibuffer in other frame
(setq minibuffer-follows-selected-frame nil)

Mode Line

Display date and time, but do not display system load.

(use-package time
  :ensure nil
  :defer 0
  :custom
  (display-time-24hr-format t)
  (display-time-day-and-date t)
  (display-time-interval 10)
  (display-time-default-load-average nil)
  :config
  (display-time-mode t))

Show (line, column) numbers in mode line:

(use-package simple
  :ensure nil
  :defer 3
  :bind
  ;; cycling from one space, zero space and original space
  ("M-SPC" . cycle-spacing)
  :custom
  ;; put pastebin content to kill ring before kill others
  (save-interprogram-paste-before-kill t)
  :config
  (line-number-mode 1)
  (column-number-mode 1)
  (toggle-word-wrap -1))

Celestial modeline

Zwei Dinge erfüllen das Gemüt mit immer neuer und zunehmender Bewunderung und Ehrfurcht, je öfter und anhaltender sich das Nachdenken damit beschäftigt: Der bestirnte Himmel über mir, und das moralische Gesetz in mir.

– Immanuel Kant

(use-package celestial-mode-line
  :defer 1
  :custom (celestial-mode-line-update-interval 300)
  :config
  ;; use smaller symbols, the original one expand the modeline
  (setq celestial-mode-line-sunrise-sunset-alist
        '((sunrise . "☼ꜛ") (sunset . "☼ꜜ")))
  (setq celestial-mode-line-phase-representation-alist
        '((0 . "") (1 . ">") (2 . "") (3 . "<")))
  (setq global-mode-string '("" celestial-mode-line-string
                             "  " display-time-string "")))

Add sunrise/sunset info to modeline text description.

(use-package celestial-mode-line
  :config
  (defun kimim/celestial-mode-line--sunrise-sunset-string (time)
    (cl-destructuring-bind (sun-time location)
        time
      (let* ((h (truncate sun-time))
             (m (truncate (* 60 (- sun-time h)))))
        (concat (assoc-default
                 (if (< h 12)
                     'sunrise
                   'sunset)
                 celestial-mode-line-sunrise-sunset-alist
                 'equal celestial-mode-line-polar-representation)
                (format "%d:%02d" h m)))))

  (defun kimim/celestial-mode-line--sun-description (orig-fun &rest args)
    (concat
     (kimim/celestial-mode-line--sunrise-sunset-string
      (car (solar-sunrise-sunset (car args))))
     " "
     (kimim/celestial-mode-line--sunrise-sunset-string
      (cadr (solar-sunrise-sunset (car args))))
     ", "
     (apply orig-fun args)))

  ;; add sun rise/set info to modeline text description
  (advice-add 'celestial-mode-line--text-description
              :around
              #'kimim/celestial-mode-line--sun-description)

  (celestial-mode-line-start-timer))

awesome tray

awesome-tray adds an overlay to minibuffer and hides mode line to save window space.

(use-package awesome-tray
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp/awesome-tray"))
  :commands (awesome-tray-mode)
  :custom
  (awesome-tray-date-format "%-m-%-e %H:%M")
  (awesome-tray-active-modules '("changed" "org-pomodoro"
                                 "location-or-page" "belong"
                                 "celestial" "date" "input-method"))
  (awesome-tray-mode-line-default-height 0.85)
  (awesome-tray-location-info-top "")
  (awesome-tray-location-info-bottom "")
  (awesome-tray-input-method-local-style "Ѱ")
  :custom-face
  (awesome-tray-grey-face
   ((((background light)) :foreground "dim grey" :bold nil)
    (t :foreground "dark grey" :bold nil)))
  (awesome-tray-module-buffer-name-face
   ((t :inherit awesome-tray-grey-face)))
  (awesome-tray-module-location-or-page-face
   ((t :inherit awesome-tray-grey-face)))
  (awesome-tray-module-git-face
   ((t :inherit awesome-tray-grey-face)))
  (awesome-tray-module-pdf-view-page-face
   ((t :inherit awesome-tray-grey-face)))
  (awesome-tray-module-input-method-face
   ((t :inherit awesome-tray-grey-face)))
  (org-mode-line-clock
   ((t :inherit unspecified)))
  (org-mode-line-clock-overrun
   ((t :inherit unspecified)))
  :config
  (advice-add
   'awesome-tray-enable
   :before
   (lambda ()
     (setq awesome-tray-mode-line-colors nil)
     (setq awesome-tray-mode-line-active-color
           (face-attribute 'mode-line :background))
     (setq awesome-tray-mode-line-inactive-color
           (face-attribute 'default :background))))

  (advice-add
   'awesome-tray-enable
   :after
   (lambda ()
     (set-face-attribute
      'mode-line-inactive nil
      :underline (face-attribute 'mode-line :foreground))))

  (advice-add
   'awesome-tray-disable
   :after
   (lambda ()
     (set-face-attribute 'mode-line-inactive nil
                         :underline 'unspecified)))

  (defun kimim/awesome-tray-module-buffer-changed-info ()
    (if (and (buffer-modified-p)
             (not (eq buffer-file-name nil)))
        ""))
  (add-to-list
   'awesome-tray-module-alist
   '("changed" . (kimim/awesome-tray-module-buffer-changed-info
                  awesome-tray-grey-face)))

  (defvar kimim/awesome-tray-original-modules nil)
  (defun kimim/awesome-tray-minimalistic ()
    (interactive)
    (if kimim/awesome-tray-original-modules
        (progn
          (message "restore awesome tray modules")
          (setq awesome-tray-active-modules
                kimim/awesome-tray-original-modules)
          (setq kimim/awesome-tray-original-modules nil))
      (progn
        (message "awesome tray minimalistic")
        (setq kimim/awesome-tray-original-modules
              awesome-tray-active-modules)
        (setq awesome-tray-active-modules
              '("buffer-name" "changed" "location-or-page"
                "date" "input-method"))))))

path in headerline

When minimalistic emacs is on with awesome tray, we sometimes need to lookup buffer file name with header line.

(use-package path-headerline-mode
  :bind ("C-x /" . kimim/toggle-path-header)
  :config
  (defun kimim/toggle-path-header ()
    "Toggle display path header"
    (interactive)
    (if header-line-format
        (path-header-line-off)
      (path-header-line-on))))

Color Theme

Use rainbow-mode to edit colorful color string and symbol. In the beginning, I add :hook prog-mode, that means to enable rainbow-mode for all programming mode, but later, I find that #def part of #define in C is changed to gray color. Then I remove the this hook. So I will turn on rainbow-mode manually, if I want to see the color.

(use-package rainbow-mode
  :diminish rainbow-mode)

Rainbow-delimiters is a “rainbow parentheses”-like mode which highlights parentheses, brackets, and braces according to their depth.

(use-package rainbow-delimiters
  :diminish rainbow-delimiters
  :hook (prog-mode . rainbow-delimiters-mode))

Toggle Font-Lock mode in all buffers.

(use-package font-lock
  :ensure nil
  :custom ((font-lock-maximum-decoration t)
           (font-lock-global-modes '(not shell-mode text-mode))
           (font-lock-verbose t))
  :config
  (global-font-lock-mode 1))

Use kimim-light as default theme.

(use-package hippo-themes
  :ensure nil
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp/emacs-hippo-theme"))
  :custom (;; do not warning when load new theme
           (custom-safe-themes t)))

Image

Always set white background color for transparent images to display clearly in dark color theme.

(use-package image
  :ensure nil
  :config
  (setq auto-mode-alist
        (append '(("\\.svg\\'" . image-mode))
                auto-mode-alist))
  (defun create-image-advice (im)
    (when im
      ;; only add property when im is non-nil
      (nconc im (list :background "#f8f8f8"))))
  (advice-add 'create-image :filter-return #'create-image-advice))

posframe

(use-package posframe)

Other Visual Element

(setq inhibit-startup-message t)
(setq initial-scratch-message nil)
(setq visible-bell t)
(setq ring-bell-function #'ignore)
(fset 'yes-or-no-p 'y-or-n-p)
(show-paren-mode 1)
(setq blink-cursor-blinks 3)
(blink-cursor-mode 1)
(tooltip-mode -1)
;; mark highlight in other windows also
(setq highlight-nonselected-windows nil)
(use-package hi-lock
  :custom (hi-lock-auto-select-face t))

kimim utils

In Windows environment, kimim/xterm and kimim/dc will look up the program from system PATH, so you should set these to system PATH.

(use-package kimim
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp"))
  :ensure nil
  :commands (kimim/mail-setup
             kimim/open-external
             kimim/open-external-pdf)
  :defines (mac-option-modifier
            mac-command-modifier)
  :bind
  (("<f9>" . kimim/xterm)
   ("S-<f9>" . kimim/cmd)
   ;; when deploy clojars, need msys2 term to use pinetry
   ("C-<f9>" . kimim/msys2-term)
   ("C-c r" . kimim/rename-file-and-buffer)
   ("C-x m o" . kimim/open-external)
   ("C-x m O" . kimim/open-external-pdf)
   ("C-x m d" . kimim/dc)
   ("C-c d" . kimim/lookinsight)
   ("<f1>" . delete-other-windows)
   ("C-<f1>" . nuke-other-buffers)
   ("M-<f1>" . kimim/switch-to-scratch-and-nuke-others)
   ("<f2>" . other-window)
   ("<f5>" . kimim/switch-to-scratch-buffer)
   ("C-<f5>" . kimim/switch-to-scratch-other-window)
   ("<f7>" . bury-buffer)
   ("<f8>" . unbury-buffer)
   ("C-h" . delete-backward-char)
   ("M-h" . backward-kill-word)
   ("M-?" . mark-paragraph)
   ("C-x k" . kill-current-buffer)
   ("C-x C-v" . view-file-other-window)
   ("C-c C-o" . occur)
   ("C-z" . set-mark-command)
   ("C-c C-/" . comment-or-uncomment-region)
   ("RET" . newline-and-indent)
   ("C-x m h" . help)
   ("C-x ," . bury-buffer)
   ("C-x ." . unbury-buffer)
   ("C-x  ," . bury-buffer)
   ("C-x  。" . unbury-buffer)
   ("C-x  ‘" . hippie-expand)
   ("C-x  ’" . hippie-expand)
   ("C-x M-s" . kimim/save-buffer-no-tws)
   ("C-x m 0" . kimim/frame-and-font)
   ("C-x m -" . kimim/frame-and-font-mini)
   ("C-x m SPC" . kimim/shrink-down)
   ("C-x m ]" . kimim/shrink-right)
   ("C-x m [" . kimim/shrink-left)
   ("C-x m ^" . kimim/shrink-up)
   ("C-x m '" . kimim/top-right-mouse)
   ("C-x m r" . kimim/rename-file-and-buffer)
   ("C-<pause>" . iconify-frame)
   ("C-x S" . eshell)
   ("C-x M-q" . kimim/unfill-paragraph-or-region)
   ("C-x 7 a" . (lambda() (interactive) (insert "ā")))
   ("C-x 7 A" . (lambda() (interactive) (insert "Ā")))
   ("C-x 7 e" . (lambda() (interactive) (insert "ē")))
   ("C-x 7 E" . (lambda() (interactive) (insert "Ē")))
   ("C-x 7 i" . (lambda() (interactive) (insert "ī")))
   ("C-x 7 I" . (lambda() (interactive) (insert "Ī")))
   ("C-x 7 o" . (lambda() (interactive) (insert "ō")))
   ("C-x 7 O" . (lambda() (interactive) (insert "Ō")))
   ("C-x 7 u" . (lambda() (interactive) (insert "ū")))
   ("C-x 7 U" . (lambda() (interactive) (insert "Ū"))))
  :config
  (unbind-key "C-x C-z")
  (when (eq system-type 'darwin) ;; mac specific settings
    (setq mac-option-modifier 'meta)
    (setq mac-command-modifier 'super)
    ;; sets fn-delete to be right-delete
    (global-set-key [kp-delete] 'delete-char)
    ;; get back hide other key
    (bind-key "M-s-h" (lambda () (interactive)
                        (mac-send-action 'hideOtherApplications))))
  (defun kimim/save-buffer-no-tws (&optional arg)
    (interactive "p")
    (delete-trailing-whitespace)
    (save-buffer arg))
  (defun kimim/switch-to-scratch-and-nuke-others ()
    (interactive)
    (switch-to-buffer "*scratch*") (nuke-other-buffers))
  (defun kimim/switch-to-scratch-buffer ()
    (interactive)
    (switch-to-buffer "*scratch*")
    (delete-other-windows))
  (defun kimim/switch-to-scratch-other-window ()
    (interactive)
    (switch-to-buffer-other-window "*scratch*"))
  (defun kimim/restore-text-scale ()
    (interactive)
    (text-scale-increase 0)))

Help & References

Info

(use-package info
  :commands (info)
  :hook (Info-mode . (lambda ()
                      (setq line-spacing 0.5)))
  :config
  (add-to-list 'Info-additional-directory-list
               (concat kimim/path-root "usr/share/info"))
  (add-to-list 'Info-additional-directory-list
               (concat kimim/path-root "usr/local/share/info"))
  (add-to-list 'Info-additional-directory-list
               (concat kimim/path-root "ucrt64/share/info"))
  ;; additional info, collected from internet
  (add-to-list 'Info-additional-directory-list
               "~/info"))

Youdao dictionary

Search dictionary with Ctrl+F3 by youdao dictionary.

(use-package youdao-dictionary
  :bind (
         ("C-x m 1" . youdao-dictionary-search-at-point-posframe)
         ("C-x m 2" . youdao-dictionary-search)
         :map youdao-dictionary-mode-map
         ("C-y" . youdao-dictionary-search-yanked)
         ("<mouse-3>" . youdao-dictionary-def-copied)
         ("f" . youdao-dictionary-search-from-input))
  :config
  ;; redefine youdao-format-result
  (defun youdao-dictionary--format-result (json)
    "Format result in JSON."
    (let* ((query        (assoc-default 'query       json)) ; string
           (translation  (assoc-default 'translation json)) ; array
           (_errorCode    (assoc-default 'errorCode   json)) ; number
           (web          (assoc-default 'web         json)) ; array
           (basic        (assoc-default 'basic       json)) ; alist
           ;; construct data for display
           (phonetic (assoc-default 'phonetic basic))
           (translation-str (mapconcat
                             (lambda (trans) (concat "- " trans))
                             translation "\n"))
           (basic-explains-str (mapconcat
                                (lambda (explain) (concat "- " explain))
                                (assoc-default 'explains basic) "\n"))
           (web-str (mapconcat
                     (lambda (k-v)
                       (format "- %s :: %s"
                               (assoc-default 'key k-v)
                               (mapconcat 'identity (assoc-default 'value k-v) "; ")))
                     web "\n")))
      (let ((result-str
             (if basic
                 (format "%s [%s]\n\nBasic Explains\n%s\n\nWeb References\n%s\n"
                         query phonetic basic-explains-str web-str)
               (format "%s\n\nTranslation\n%s\n"
                       query translation-str))))
        (kill-new
         (mapconcat
          (lambda (x)
            (concat x "\n"))
          (cdr (split-string result-str "\n"))))
        (kill-new (format "%s [%s]" query phonetic))
        result-str)))

  (defun youdao-dictionary-def-copied ()
    (interactive)
    (youdao-dictionary-search (gui-get-selection)))
    (defun youdao-dictionary-search-yanked ()
    (interactive)
    (youdao-dictionary-search
     (if (gui-get-selection)
         (gui-get-selection)
       (car kill-ring)))))

sdcv

A local dictionary tool is useful when network connection has problems. You can get dictionary files from lazycat-emacs, or huzheng.org.

(use-package sdcv
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/sdcv"))
  :bind
  ("C-M-z" . sdcv-search-input)
  ("C-x m ," . sdcv-search-pointer)
  ("C-`" . sdcv-search-pointer+)
  :config
  (require 'sdcv)
  (setq sdcv-program "sdcv")
  (setq sdcv-say-word-p t)
  ;; set your local dictionaries path
  (setq sdcv-dictionary-data-dir (file-truename
                                  (concat kimim/path-kimim-emacs
                                          "site-lisp/sdcv-dict")))
  (setq sdcv-dictionary-simple-list
        '("lazyworm-zh-en"
          "lazyworm-en-zh"))
  (setq sdcv-dictionary-complete-list
        '("oxford"
          "lazyworm-en-zh"
          "lazyworm-zh-en"
          "xdict-en-zh"
          "xdict-zh-en"
          "stardict-en-zh"
          "WordNet"
          "Jargon"
          "langdao-zh-en"
          "cdict-en-zh"
          "georges-de-lat"
          "georges-lat-de"
          "etymonline"))
  (defun kimim/sdcv-call-process-advice (orig-fun &rest args)
    (let ((default-process-coding-system '(utf-8 . utf-8)))
      (apply orig-fun args)))

  (advice-add 'sdcv-call-process
              :around
              #'kimim/sdcv-call-process-advice))

Need to install mpg123 or mpv to play the pronunciations.

pacman -S mingw-w64-ucrt-x86_64-mpg123

fanyi.el

A translator extension written by Youmu.

I added kimim/fanyi-dwim-add-sdcv as :after advice for fanyi-dwim to look up local dictionary with help from sdcv.

(use-package fanyi
  :ensure t
  :bind
  (("C-x m ." . kimim/fanyi-dwim)
   ("C-x m y" . kimim/fanyi-current-kill))
  :custom-face
  (fanyi-word-face
   ((t :height 1.1 :weight bold :foreground "dark cyan")))
  :custom
  (fanyi-providers '(;; 海词
                     fanyi-haici-provider
                     ;; 有道同义词词典
                     fanyi-youdao-thesaurus-provider
                     ;; Etymonline
                     fanyi-etymon-provider
                     ;; Longman
                     fanyi-longman-provider))
  :bind (:map fanyi-mode-map
              ("n" . outline-next-visible-heading)
              ("p" . outline-previous-visible-heading)
              ("o" . other-window)
              ("<mouse-3>" . quit-window))
  :config
  (require 'sdcv)

  (defun kimim/fanyi-dwim ()
    (interactive)
    (if-let ((word (thing-at-point 'word)))
        (progn
          (fanyi-dwim word)
          (cl-pushnew word fanyi-history))
      (call-interactively #'fanyi-dwim)))

  (defun kimim/fanyi-current-kill ()
    (interactive)
    (fanyi-dwim (current-kill 0)))

  (defun kimim/sdcv-translate-result-advice (word dictionary-list)
    (let* ((arguments
            (cons word
                  (mapcan
                   (lambda (d) (list "-u" d)) dictionary-list)))
         (result (mapconcat
                  (lambda (result)
                    (let-alist result
                      (format
                       "## %s\n%s\n\n" .dict .definition)))
                  (apply #'sdcv-call-process arguments)
                  "")))
    (if (string-empty-p result)
        sdcv-fail-notify-string
      result)))

  (advice-add 'sdcv-translate-result
              :override
              #'kimim/sdcv-translate-result-advice)

  (defun kimim/fanyi-dwim-add-sdcv (word)
    (let ((buf (get-buffer fanyi-buffer-name)))
    (with-current-buffer buf
      (let ((inhibit-read-only t)
            (inhibit-point-motion-hooks t))
        ;; Clear the previous search result.
        (point-max)
        (insert "# SDCV\n\n")
        (insert
         (sdcv-search-with-dictionary
          word sdcv-dictionary-complete-list))
        (insert "\n\n")
        (beginning-of-buffer)
        ;;(window-resize nil (- 35 (window-size nil t)) t)
        ))))

  (advice-add 'fanyi-dwim :after
              #'kimim/fanyi-dwim-add-sdcv))

Wordreference

(use-package wordreference
  :commands (wordreference-search
             kimim/wr-fren
             kimim/wr-enfr
             kimim/wr-deen
             kimim/wr-ende
             kimim/wr-sven
             kimim/wr-ensv)
  :config
  (defun kimim/wr-ende ()
    (interactive)
    (let ((wordreference-source-lang "en")
          (wordreference-target-lang "de"))
      (wordreference-search)))

  (defun kimim/wr-ensv ()
    (interactive)
    (let ((wordreference-source-lang "en")
          (wordreference-target-lang "sv"))
      (wordreference-search)))

  (defun kimim/wr-enfr ()
    (interactive)
    (let ((wordreference-source-lang "en")
          (wordreference-target-lang "fr"))
      (wordreference-search)))

  (defun kimim/wr-deen ()
    (interactive)
    (let ((wordreference-source-lang "de")
          (wordreference-target-lang "en"))
      (wordreference-search)))

  (defun kimim/wr-sven ()
    (interactive)
    (let ((wordreference-source-lang "sv")
          (wordreference-target-lang "en"))
      (wordreference-search)))

  (defun kimim/wr-fren ()
    (interactive)
    (let ((wordreference-source-lang "fr")
          (wordreference-target-lang "en"))
      (wordreference-search))))

dict.leo.org

Translate word between English and German.

(use-package leo
  :bind
  (:map leo-mode-map
        ("s" . leo-translate-word))
  :custom-face
  (leo-auxiliary-face
   ((t :inherit default)))
  (leo-match-face
   ((t :inherit font-lock-keyword-face)))
  (leo-link-face
   ((t :inherit font-lock-keyword-face
       :foreground "tomato"))))

reverso

Translate between almost any languages.

(use-package reverso
  :commands (reverso
             reverso-translate))

eldoc-box

Show eldoc in a childframe box.

(use-package eldoc-box
  :diminish eldoc-box-hover-mode
  :hook (eglot-managed-mode . eldoc-box-hover-mode)
  :custom (eldoc-box-offset '(10 40 10))
  :bind (:map
         eglot-mode-map
         ("C-x l d" . eldoc-box-help-at-point)))

Increase right margin from default 16 to 40 to remove some overlap.

Editing

emacs-rime

(use-package rime
  :bind
  (:map
   rime-mode-map
   ("C-|" . rime-force-enable)
   :map
   rime-active-mode-map
   ("C-h" . rime--backspace)
   ("M-h" . rime--backspace))
  :custom
  (default-input-method "rime")
  (rime-title "Ѱ")
  (rime-disable-predicates
   '(kimim/rime-predicate-p))
  (rime-show-candidate 'posframe)
  (rime-posframe-properties
   (list :internal-border-width 14))
  :config

  (when (eq (window-system) 'mac)
    (setq rime-librime-root "~/.emacs.d/librime/dist"))

  (defun kimim/rime-predicate-space-after-ascii-p ()
    "If cursor is after only ONE whitespace which follow a ascii character."
    (and (> (point) (save-excursion (back-to-indentation) (point)))
         (let ((string (buffer-substring
                        (point)
                        (max (line-beginning-position) (- (point) 80)))))
           (and (string-match-p " $" string)
                ;; -+* bullet items
                (not (string-match-p "[\x2a\x2b\x2d] $" string))
                (not (string-match-p "\\cc +$" string))
                (not (string-match-p "  $" string))))))

  (defun kimim/rime-predicate-current-input-punctuation-p ()
    "If the current charactor entered is a punctuation."
    (and rime--current-input-key
         (or (and (<= #x21 rime--current-input-key)
                  (<= rime--current-input-key #x2c))
             (= rime--current-input-key #x2e)
             (and (<= #x3a rime--current-input-key)
                  (<= rime--current-input-key #x40))
             (and (<= #x5b rime--current-input-key)
                  (<= rime--current-input-key #x5e))
             (and (<= #x7b rime--current-input-key)
                  (<= rime--current-input-key #x7f)))))

  (defun kimim/rime-predicate-p ()
    "Always use rime punctuation."
    (if (kimim/rime-predicate-current-input-punctuation-p)
        nil
      (or (rime-predicate-current-uppercase-letter-p)
          (rime-predicate-space-after-cc-p)
          (rime-predicate-after-ascii-char-p)
          (kimim/rime-predicate-space-after-ascii-p)))))

sis

Emacs smart input source (sis) can automatically switch input method for buffers and contexts.

When sis is enabled, C-x C-c a emacs client frame will crash, so, add an advice to disable sis when save-buffers-kill-terminal.

By the way, you need to use C-<space> to toggle between IME and NonIME, otherwise, sis will bring IME back after some keycords.

(use-package sis
  :functions (kimim/sis-enable
              kimim/sis-disable)
  :hook (server-after-make-frame . kimim/sis-enable)
  :config
  (set-face-attribute
   'sis-inline-face nil
   :foreground (face-attribute 'font-lock-constant-face :foreground)
   :underline t
   :inverse-video 'unspecified)
  (setq sis-other-cursor-color "tomato")
  (cond ((eq system-type 'windows-nt)
         (sis-ism-lazyman-config nil t 'w32))
        ((eq system-type 'darwin)
         (sis-ism-lazyman-config
          "com.apple.keylayout.ABC"
          "im.rime.inputmethod.Squirrel.Hans")))

  (defun kimim/sis-enable ()
    ;; enable the /cursor color/ mode
    (sis-global-cursor-color-mode t)
    ;; enable the /respect/ mode
    (sis-global-respect-mode t)
    ;; enable the /context/ mode for all buffers
    (sis-global-context-mode t)
    ;; enable the /inline english/ mode for all buffers
    (sis-global-inline-mode t))

  (defun kimim/sis-disable ()
    (sis-global-cursor-color-mode -1)
    (sis-global-respect-mode -1)
    (sis-global-context-mode -1)
    (sis-global-inline-mode -1))

  (advice-add 'save-buffers-kill-terminal
              :before
              (lambda (&optional arg)
                (if (eq 1 (length server-clients))
                    (kimim/sis-disable))))
  (add-hook 'server-after-make-frame-hook 'kimim/sis-enable))

General

(use-package autorevert
  :ensure nil
  :diminish auto-revert-mode)
(setq inhibit-eol-conversion nil)
(setq-default fill-column 70)
;; see discussion https://emacs-china.org/t/topic/2616/30
(setq word-wrap-by-category t)
(setq require-final-newline t)

(use-package drag-stuff
  :diminish drag-stuff-mode
  :config
  (drag-stuff-global-mode 1))

(delete-selection-mode 1)
(setq kill-ring-max 200)
(setq kill-whole-line t)
(setq-default tab-width 4)
(setq tab-stop-list
      (number-sequence 4 120 4))
;; stretch to tab width when on tab
(setq x-stretch-cursor t)
(setq track-eol t)
(setq backup-directory-alist '(("." . "~/temp")))
(setq version-control t)
(setq kept-old-versions 10)
(setq kept-new-versions 20)
(setq delete-old-versions t)
(setq backup-by-copying t)

(setq auto-save-interval 50)
(setq auto-save-timeout 60)
(setq auto-save-default nil)
(setq auto-save-list-file-prefix "~/temp/auto-saves-")
(setq auto-save-file-name-transforms `((".*"  , "~/temp/")))
(setq create-lockfiles nil)

(use-package time-stamp
  :config
  (setq time-stamp-active t)
  (setq time-stamp-warn-inactive t)
  (setq time-stamp-format "%:y-%02m-%02d %3a %02H:%02M:%02S Kimi MA")
  (add-hook 'write-file-functions 'time-stamp))

(defun kimim/save-buffer-advice (orig-fun &rest arg)
  (when (not (memq major-mode '(org-mode markdown-mode text-mode)))
      (delete-trailing-whitespace))
  (apply orig-fun arg))

(advice-add 'save-buffer :around #'kimim/save-buffer-advice)
(setq save-silently t)

(diminish 'visual-line-mode)
(add-hook 'text-mode-hook
          (lambda ()
            (when (derived-mode-p 'org-mode 'markdown-mode
                                  'text-mode 'info-mode)
              (visual-line-mode)
              (setq line-spacing 0.4))))
(setq-default indent-tabs-mode nil)

(setq uniquify-buffer-name-style 'forward)
(setq suggest-key-bindings 5)

(add-to-list 'auto-mode-alist '("\\.css\\'" . css-mode))
(add-to-list 'auto-mode-alist '("\\.S\\'" . asm-mode))
(add-to-list 'auto-mode-alist '("\\.pas\\'" . delphi-mode))

(require 'saveplace)
(setq-default save-place t)
(setq save-place-file (expand-file-name "saveplace" "~"))

multi cursors

You can mark a region, and C-S-c C-S-c to start edit every line in this region. That’s amazing.

(use-package multiple-cursors
  :bind
  ("C-S-c C-S-c" . mc/edit-lines)
  ("C->" . mc/mark-next-like-this)
  ("C-<" . mc/mark-previous-like-this)
  ("C-c C-<" . mc/mark-all-like-this)
  ("C-c C->" . mc/mark-all-dwim))

Highlight

Highlight current line in window systems, but disable this in terminal. Because the line highlight will cause the terminal blinking.

(use-package hl-line
  :if window-system
  :config
  (global-hl-line-mode -1))

pulsar

Frequently, you may lose your point in multiple windows. pulsar can show a transient highlight line for you to find your point after specified functions.

(use-package pulsar
  :defer 5
  :custom
  (pulsar-pulse t)
  (pulsar-face 'pulsar-green)
  (pulsar-highlight-face 'pulsar-yellow)
  :config
  (setq pulsar-pulse-functions
        '(recenter-top-bottom
          move-to-window-line-top-bottom
          reposition-window
          bookmark-jump
          consult-bookmark
          other-window
          delete-window
          delete-other-windows
          forward-page
          backward-page
          scroll-up-command
          scroll-down-command
          windmove-right
          windmove-left
          windmove-up
          windmove-down
          windmove-swap-states-right
          windmove-swap-states-left
          windmove-swap-states-up
          windmove-swap-states-down
          org-next-visible-heading
          org-previous-visible-heading
          org-forward-heading-same-level
          org-backward-heading-same-level
          org-agenda-goto
          markdown-next-visible-heading
          markdown-previous-visible-heading
          markdown-forward-heading-same-level
          markdown-backward-heading-same-level
          markdown-outline-next-same-level
          markdown-outline-previous-same-level
          outline-backward-same-level
          outline-forward-same-level
          outline-next-visible-heading
          outline-previous-visible-heading
          outline-up-heading
          ace-window))
  (advice-add 'switch-to-buffer :after
              (lambda (_ &optional _ _)
                (pulsar-pulse-line)))
  ;; turn on visual line, pulsar will high light only visual line, but
  ;; this mode will mess up some buffer, such as org agenda.
  ;; (global-visual-line-mode)
  (pulsar-global-mode 1))

ispell

Emacs 26 supports Enchant, which is a meta-spell-checker that uses providers such as Hunspell to do the actual checking. With it, users can use spell-checkers not directly supported by Emacs, such as Voikko, Hspell and AppleSpell, more easily share personal word-lists with other programs, and configure different spelling-checkers for different languages.

The benefit is that you can use the same personal word-lists for different spell checkers in Windows, Linux or macOS.

I get this idea from Eason0210 at Emacs China on [2023-07-02 Sun].

Install enchant.

Windows/msys64:

pacman -S mingw64/mingw-w64-x86_64-enchant

macOS

sudo port install enchant2
# or
brew install enchant

Add ENCHANT_CONFIG_DIR to your .bashrc, if you want to keep enchant settings in your home folder. Otherwise, it keeps personal dictionary at Local Settings. see enchant manual.

export ENCHANT_CONFIG_DIR=~/.config/enchant/
(use-package ispell
  :custom
  ;; enchant-2 becomes very slow in Windows 11
  (ispell-program-name "hunspell")
  (ispell-silently-savep t)
  :config
  (cond ((eq system-type 'windows-nt)
         (setq ispell-alternate-dictionary
               "~/.emacs.d/dict/words.txt")))
  ;; remove minibuffer message "Starting look.exe process ...",
  ;; which is annoying
  (advice-add
   'ispell-lookup-words
   :around
   (lambda (orig-fun &rest args)
     (advice-add
      'message :override (lambda (format-string &rest args) ""))
     (let ((result (apply orig-fun args)))
       (advice-remove 'message (lambda (format-string &rest args) ""))
       result))))

flyspell

Check spell on the fly.

(use-package flyspell
  :diminish flyspell-mode
  :hook (;;(prog-mode . flyspell-prog-mode)
         (org-mode . flyspell-mode)))

olivetti

(use-package olivetti
  :diminish olivetti-mode
  :custom
  (olivetti-body-width (+ fill-column 10))
  :hook ((elfeed-show-mode
          eww-mode
          Info-mode
          woman-mode
          fanyi-mode)
	     . olivetti-mode)
  :config
  (add-hook 'cnfonts-set-font-finish-hook
            (lambda (args)
              (let ((frame-width (* (+ fill-column 7)
                                    (frame-char-width))))
                (setq org-image-actual-width `(,frame-width))
                (setq markdown-max-image-size
                      `(,frame-width . ,(* 2 frame-width)))
                (when (bound-and-true-p org-inline-image-overlays)
                  (org-redisplay-inline-images))
                (when (bound-and-true-p markdown-inline-image-overlays)
                  (markdown-remove-inline-images)
                  (markdown-display-inline-images))))))

Chinese word segmentation

(use-package cns
  :after text-mode
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp/cns"))
  :hook (text-mode . cns-mode)
  :bind ((:map cns-mode-map)
         ("M-h" . cns-backward-kill-word))
  :config
  (setq cns-process-buffer "*cns*")
  (setq cns-prog
        (cond ((eq system-type 'windows-nt)
               (concat kimim/path-kimim-emacs
                       "site-lisp/cns/cnws.exe"))
              ((eq system-type 'darwin)
               (concat kimim/path-kimim-emacs
                       "site-lisp/cns/cnws"))))
  (setq cns-dict-directory
        (concat kimim/path-kimim-emacs
                "site-lisp/cns/cppjieba/dict"))
  (setq cns-process-shell-command
        (format "%s %s" cns-prog (cns-set-prog-args cns-dict-directory)))
  (setq cns-recent-segmentation-limit 20) ; default is 10
  (setq cns-debug nil))

wraplish

Automatically add space between Chinese and English, and more.

(use-package wraplish
  :commands wraplish-mode
  :after text-mode
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/wraplish")))

Before enable wraplish, you need to install python, pip and epc.

pacman -S mingw-w64-x86_64-python mingw-w64-x86_64-python3-pip
pip install epc

tempel

Use tempel to expand template.

;; Configure Tempel
(use-package tempel
  :bind (("M-+" . tempel-complete)
         ("M-=" . tempel-complete)
         ("M-*" . tempel-insert)
         :map tempel-map
         ("M-RET" . tempel-done)
         ("S-<tab>" . tempel-previous)
         ("<tab>" . tempel-next)
         ("M-<up>" . tempel-previous)
         ("M-<down>" . tempel-next)))

(use-package tempel-collection)

Controlling

Window and Frame

By enabling winner-mode, you can restore to previous window configuration by typing C-c M-<left>.

(use-package winner
  ;; restore windows configuration, built-in package
  :commands winner-mode
  :bind
  ("C-x M-<left>" . winner-undo)
  ("C-x M-<right>" . winner-redo)
  :config
  (winner-mode t))

When type C-x m w it will create a new frame with the default frame configuration.

(use-package frame
  :ensure nil
  :defer 1
  :bind ("C-x m w" . make-frame))

preserve the point in screen during scrolling looks nice (see scrolling). scroll slowly with touchpad, thus we adjust the scroll amount.

(setq scroll-preserve-screen-position t)
(setq mouse-wheel-scroll-amount '(0.01))

Move Frame

Move frame to one of the nine grids on the screen with C-x y <N> keys.

(use-package nine-grid
  :diminish nine-grid-minor-mode
  :commands (nine-grid)
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/nine-grid"))
  :bind (("C-c 5 1" . (lambda () (interactive) (nine-grid 1)))
         ("C-c 5 2" . (lambda () (interactive) (nine-grid 2)))
         ("C-c 5 3" . (lambda () (interactive) (nine-grid 3)))
         ("C-c 5 4" . (lambda () (interactive) (nine-grid 4)))
         ("C-c 5 5" . (lambda () (interactive) (nine-grid 5)))
         ("C-c 5 6" . (lambda () (interactive) (nine-grid 6)))
         ("C-c 5 7" . (lambda () (interactive) (nine-grid 7)))
         ("C-c 5 8" . (lambda () (interactive) (nine-grid 8)))
         ("C-c 5 9" . (lambda () (interactive) (nine-grid 9))))
  :config
  (require 'nine-grid)
  (nine-grid-mode))

Windmove

(use-package windmove
  :bind
  ("C-x <left>" . windmove-swap-states-left)
  ("C-x <right>" . windmove-swap-states-right)
  ("C-x <up>" . windmove-swap-states-up)
  ("C-x <down>" . windmove-swap-states-down))

Command

Display key candidates when you typed part key prefix with which-key-mode.

;; https://github.com/justbur/emacs-which-key
(use-package which-key
  :defer 3
  :diminish which-key-mode
  :custom (which-key-popup-type 'side-window)
  :config
  (which-key-mode 1))

List recent used commands with smex:

;; smex will list the recent function on top of the cmd list
(use-package smex
  :commands (smex)
  :config
  (smex-initialize))

Key Frequency

We will use keyfreq to record the frequency of the key typing, and get a frequency report by M-x keyfreq-show.

(use-package keyfreq
  :custom (keyfreq-file "~/.emacs.d/emacs.keyfreq")
  :config
  (keyfreq-mode +1)
  (keyfreq-autosave-mode +1))

eshell

(use-package eshell)

Navigation

(use-package bookmark
  :custom
  (bookmark-default-file "~/.emacs.d/emacs.bmk")
  (bookmark-save-flag 1)
  (bookmark-fontify nil)
  :config
  (add-hook 'bookmark-after-jump-hook
            (lambda ()
              (recenter 'top))))

bm is used to temporally toggle buffer local bookmarks with C-x m t, then you can view all the local temporally bookmarks with C-x m s.

(use-package bm
  :bind (("C-x m t" . bm-toggle)
         ("C-x m s" . bm-show-all)
         ("C-x m <left>" . bm-previous)
         ("C-x m <right>" . bm-next)))

You can jump to any character by triggering ace-jump-mode (C-x m c), and jump to any window by triggering ace-window (C-x m w).

(use-package ace-window
  :bind
  (("C-x o" . other-window)
   ("M-o" . ace-window)
   ("C-x m w" . ace-swap-window)
   ("C-x m x" . ace-delete-window))
  :custom
  (aw-keys '(?a ?s ?d ?f ?g ?h ?j ?k ?l)))

vundo

(use-package vundo
  :bind ("C-x u" . vundo))

Search and Finding

isearch in minibuffer

Emacs default isearch does not allow key binding in minibuffer, isearch-mb enhances at this aspect.

(use-package isearch-mb
  :defer 1
  :bind
  (:map
   isearch-mb-minibuffer-map
   ("C-l" . recenter-other-window)
   ("C-n" . isearch-repeat-forward)
   ("C-p" . isearch-repeat-backward)
   ("C-." . (lambda () (interactive)
              (isearch-exit)
              (sdcv-search-pointer+))))
  :custom
  (isearch-repeat-on-direction-change t)
  ;; Show match count next to the minibuffer prompt
  (isearch-lazy-count t)
  ;; Don't be stingy with history; default is to keep just 16 entries
  (search-ring-max 200)
  (search-invisible nil)
  (regexp-search-ring-max 200)
  :config
  (add-hook 'isearch-mode-hook #'display-line-numbers-mode)
  (add-hook 'isearch-mode-end-hook
            (lambda () (display-line-numbers-mode -1)))
  (defun isearch-mb--update-prompt (&rest _)
    "Update the minibuffer prompt according to search status."
    (when isearch-mb--prompt-overlay
      (let ((count (isearch-lazy-count-format))
            (len (or (overlay-get isearch-mb--prompt-overlay
                                  'isearch-mb--len)
                     0)))
        (overlay-put isearch-mb--prompt-overlay
                     'isearch-mb--len (max len (length count)))
        (overlay-put isearch-mb--prompt-overlay
                     'before-string
                     (concat count ;; Count is padded so that it only grows.
                             (make-string (max 0 (- len (length count))) ?\ )
                             (capitalize
                              (or
                               (isearch--describe-regexp-mode
                                isearch-regexp-function)
                               "")))))))
  (isearch-mb-mode))

imenu & imenu-anywhere

imenu is used to navigate the function definitions in current buffer.

(use-package imenu
  :functions kimim/imenu-default-goto-function-advice
  :bind ("C-x i" . imenu)
  :config
  (advice-add 'imenu-default-goto-function
              :around
              #'kimim/imenu-default-goto-function-advice))

(use-package imenu-anywhere
  :bind ("C-x m i" . imenu-anywhere))

ripgrep: a fast command line search tool

(use-package ripgrep
  :bind ("C-x g" . ripgrep-regexp))

search from web

(use-package eww
  :custom
  (eww-search-prefix "https://cn.bing.com/search?q="))

recentf

(use-package recentf
  :config
  (recentf-mode))

avy

(use-package avy
  :bind ("C-x m g" . avy-goto-word-or-subword-1))

File Management

delete files

To avoid accidentally delete files, let emacs move the deleted file to trash.

(setq delete-by-moving-to-trash t)

dired

(use-package dired
  :ensure nil
  :defines (dired-omit-localp
            dired-omit-files)
  :functions (dired-omit-mode
              dired-dwim-target-directory
              kimim/drawio-to)
  :custom
  (dired-listing-switches "-AGhlgov")
  (dired-recursive-copies t)
  (dired-recursive-deletes t)
  (ls-lisp-dirs-first t)
  (dired-create-destination-dirs 'ask)
  (dired-dwim-target t)
  :bind
  (("C-x C-j" . dired-jump)
   :map dired-mode-map
   ("C-c l" . kimim/dired-get-org-link)
   ("<left>" . dired-up-directory)
   ("<right>" . dired-find-file)
   ("b" . dired-up-directory)
   ("e" . dired-efap)
   ("o" . kimim/open-external)
   ("M-n" . dired-narrow)
   ("M-c" . compose-attach-marked-files)
   ("C-q" . kill-dired-buffers)
   ("<tab>" . kimim/dired-other-window))
  :config
  (require 'dired-filter)
  (require 'dired-recent)
  (require 'dired-x)
  (require 'dired-efap)
  (require 'kimim) ;; for kimim/open-external
  (add-hook 'dired-mode-hook
            (lambda ()
              (turn-on-gnus-dired-mode)
              ;; Set dired-x buffer-local variables here.  For example:
              ;;(dired-omit-mode 1)
              (dired-filter-mode 1)
              (hl-line-mode 1)
              (setq dired-omit-localp t)
              (setq dired-omit-files
                    (concat "_minted[.]*\\|desktop.ini"
                            "\\|NTUSER\\|ntuser"
                            "\\|Cookies\\|AppData"
                            "\\|Contacts\\|Links"
                            "\\|Intel\\|NetHood"
                            "\\|PrintHood\\|Recent"
                            "\\|Start\\|SendTo"
                            "\\|^\\.DS_Store"
                            "\\|qms-bmh"))))
  (if (eq system-type 'darwin)
      (setq dired-listing-switches "-Avhlgo"))

  (defun compose-attach-marked-files ()
    "Compose mail and attach all the marked files from a dired buffer."
    (interactive)
    (let ((files (dired-get-marked-files))
          (file-names (dired-copy-filename-as-kill)))
      (compose-mail nil (concat "Attachments: " file-names) nil t)
      (dolist (file files)
        (if (file-regular-p file)
            (mml-attach-file file
                             (mm-default-file-type file)
                             nil "attachment")
          (message "skipping non-regular file %s" file)))))

  (defadvice dired-next-line (after dired-next-line-advice (arg) activate)
    "Move down lines then position at filename, advice"
    (interactive "p")
    (if (eobp)
        (progn
          (goto-char (point-min))
          (forward-line 1)
          (dired-move-to-filename))))

  (defadvice dired-previous-line (before dired-previous-line-advice (arg) activate)
    "Move up lines then position at filename, advice"
    (interactive "p")
    (if (= 2 (line-number-at-pos))
        (goto-char (point-max))))

  (defun kimim/dired-other-window ()
    (interactive)
    (let ((other-dired-buffer (dired-dwim-target-directory)))
      (if other-dired-buffer
          (dired-other-window other-dired-buffer)
        (dired-jump-other-window))))

  (defun kimim/dired-get-org-link ()
    "get a link from dired for org"
    (interactive)
    (let ((filename (dired-get-filename)))
      (kill-new (concat
                 "[["
                 (concat "~/" (file-relative-name filename "~"))
                 "]["
                 (file-name-nondirectory filename)
                 "]]"))))

  (defun kimim/open-with-inkscape ()
    (interactive)
    (let* ((filename (dired-get-filename)))
      (cond
       ((string-equal system-type "windows-nt")
        (w32-shell-execute "open" "inkscape" filename))
       ((string-equal system-type "darwin")
        (start-process "" nil "open" "-a" "inkscape" filename))
       ((string-equal system-type "gnu/linux")
        (start-process "" nil "xdg-open" "inkscape" filename))
       ((string-equal system-type "cygwin")
        (start-process "" nil "xdg-open" "inkscape" filename)))))

  (defun kimim/pdf2svg ()
    (interactive)
    (let* ((filename (dired-get-filename))
           (exportname (replace-regexp-in-string ".pdf$" ".svg" filename))
           (counter 10))
      (w32-shell-execute
       "open" "inkscape"
       (format "--export-filename=\"%s\" \"%s\""
               exportname filename))
      (while (and (not (file-exists-p exportname))
                  (> counter 0))                    ; true-or-false-test
        (sleep-for 1)
        (setq counter (1- counter)))
      (dired-revert)))

  (defun kimim/drawio-to (ext)
    (let* ((filename (dired-get-filename))
           (exportname (replace-regexp-in-string "drawio$" ext filename))
           (counter 10))
      (w32-shell-execute
       "open" (concat (getenv "MSYS64_PATH")
                      "\\kimikit\\draw.io\\draw.io.exe")
       (format "--crop -b 5 -x \"%s\" -o \"%s\""
               filename exportname))
      (while (and (not (file-exists-p exportname))
                  (> counter 0))                    ; true-or-false-test
        (sleep-for 1)
        (setq counter (1- counter)))
      (dired-revert)))

  (defun kimim/drawio2png ()
    (interactive)
    (kimim/drawio-to "png"))

  (defun kimim/drawio2svg ()
    (interactive)
    (kimim/drawio-to "svg"))

  (defun kimim/drawio2pdf ()
    (interactive)
    (kimim/drawio-to "pdf")))

dired-recent

Keep a list of recently visited directories. Then we can quickly revisit them.

(use-package dired-recent
  :config
  (dired-recent-mode 1))

dired-efap

dired-efap, Edit file at point, can be used to rename file name at the point:

(use-package dired-efap
  :commands dired-efap)

dired-narrow

M-n will prompt for strings to narrow the files in current dired buffer.

(use-package dired-narrow
  :commands dired-narrow)

dired-filter

(use-package dired-filter
  :diminish dired-filter-mode)

ibuffer

M-o is bound to ibuffer-visit-buffer-1-window to visit the buffer on this line, and delete other windows. This conflicts with global key binding to ace-window. Unbind M-o from ibuffer-mode-map.

(use-package ibuffer
  :bind (("C-x C-b" . ibuffer-other-window)
         :map ibuffer-mode-map
         ("<right>" . ibuffer-visit-buffer))
  :custom
  (ibuffer-formats
   '((mark modified read-only " "
           (name 32 32 :left :elide)
           " "
           (size-h 9 -1 :right)
           " "
           (mode 14 14 :left :elide)
           " "
           filename-and-process)))
  :config
  (unbind-key "M-o" 'ibuffer-mode-map)
  ;; Use human readable Size column instead of original one
  (define-ibuffer-column size-h
    (:name "Size" :inline t)
    (cond
     ((> (buffer-size) 1000000)
      (format "%7.1fM" (/ (buffer-size) 1000000.0)))
     ((> (buffer-size) 100000)
      (format "%7.0fk" (/ (buffer-size) 1000.0)))
     ((> (buffer-size) 1000)
      (format "%7.1fk" (/ (buffer-size) 1000.0)))
     (t (format "%8d" (buffer-size))))))

Completion

marginalia

Rich annotations in the minibuffer.

(use-package marginalia
  :init
  (marginalia-mode))

consult

(use-package consult
  :defines (xref-show-xrefs-function
            xref-show-definitions-function)
  :bind (;; C-c bindings (mode-specific-map)
         ("C-c h" . consult-history)
         ("C-c m" . consult-mode-command)
         ("C-c k" . consult-kmacro)
         ;; C-x bindings (ctl-x-map)
         ("C-x M-:" . consult-complex-command)     ;; orig. repeat-complex-command
         ("C-x b" . consult-buffer)                ;; orig. switch-to-buffer
         ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window
         ("C-x 5 b" . consult-buffer-other-frame)  ;; orig. switch-to-buffer-other-frame
         ("C-x M-b" . consult-buffer-other-window)
         ("C-x m j" . consult-bookmark)            ;; orig. bookmark-jump
         ("C-x m v" . find-variable)
         ("C-x p b" . consult-project-buffer)      ;; orig. project-switch-to-buffer
         ;; Custom M-# bindings for fast register access
         ("M-#" . consult-register-load)
         ("M-'" . consult-register-store)          ;; orig. abbrev-prefix-mark (unrelated)
         ("C-M-#" . consult-register)
         ;; Other custom bindings
         ("M-y" . consult-yank-pop)                ;; orig. yank-pop
         ("<help> a" . consult-apropos)            ;; orig. apropos-command
         ;; M-g bindings (goto-map)
         ("M-g e" . consult-compile-error)
         ("M-g f" . consult-flymake)               ;; Alternative: consult-flycheck
         ("M-g g" . consult-goto-line)             ;; orig. goto-line
         ("M-g M-g" . consult-goto-line)           ;; orig. goto-line
         ("M-g o" . consult-outline)               ;; Alternative: consult-org-heading
         ("M-g m" . consult-mark)
         ("M-g k" . consult-global-mark)
         ("M-g i" . consult-imenu)
         ("M-g I" . consult-imenu-multi)
         ;; M-s bindings (search-map)
         ("M-s d" . consult-find)
         ("M-s D" . consult-locate)
         ("M-s g" . consult-grep)
         ("M-s G" . consult-git-grep)
         ("M-s r" . consult-ripgrep)
         ("M-s p" . kimim/consult-ripgrep-current)
         ("M-s l" . consult-line)
         ("M-s L" . consult-line-multi)
         ("M-s m" . consult-multi-occur)
         ("M-s k" . consult-keep-lines)
         ("M-s u" . consult-focus-lines)
         ;; Isearch integration
         ("M-s e" . consult-isearch-history)
         :map isearch-mode-map
         ("M-e" . consult-isearch-history)         ;; orig. isearch-edit-string
         ("M-s e" . consult-isearch-history)       ;; orig. isearch-edit-string
         ("M-s l" . consult-line)                  ;; needed by consult-line to detect isearch
         ("M-s L" . consult-line-multi)
         :map minibuffer-local-map
         ("M-s" . consult-history)                 ;; orig. next-matching-history-element
         ("M-r" . consult-history))           ;; needed by consult-line to detect isearch

  ;; Enable automatic preview at point in the *Completions* buffer. This is
  ;; relevant when you use the default completion UI.
  ;;:hook (completion-list-mode . consult-preview-at-point-mode)

  ;; The :init configuration is always executed (Not lazy)
  :init
  (global-set-key [remap repeat-complex-command] #'consult-complex-command)
  (use-package recentf)
  ;; Optionally configure the register formatting. This improves the register
  ;; preview for `consult-register', `consult-register-load',
  ;; `consult-register-store' and the Emacs built-ins.
  (setq register-preview-delay 0.5
        register-preview-function #'consult-register-format)

  ;; Optionally tweak the register preview window.
  ;; This adds thin lines, sorting and hides the mode line of the window.
  (advice-add #'register-preview :override #'consult-register-window)

  ;; Use Consult to select xref locations with preview
  (setq xref-show-xrefs-function #'consult-xref
        xref-show-definitions-function #'consult-xref)

  ;; Configure other variables and modes in the :config section,
  ;; after lazily loading the package.
  :config

  (defun kimim/consult-ripgrep-current ()
    (interactive)
    (consult-ripgrep "."))

  ;; Optionally configure preview. The default value
  ;; is 'any, such that any key triggers the preview.
  ;; (setq consult-preview-key '(:debounce 1.5 any))
  ;; (setq consult-preview-key 'any)
  ;; (setq consult-preview-key "M-.")
  ;; (setq consult-preview-key (list "<S-down>") (kbd "<S-up>"))
  ;; For some commands and buffer sources it is useful to configure the
  ;; :preview-key on a per-command basis using the `consult-customize' macro.
  (consult-customize
   consult-xref
   :preview-key '(:debounce 0 any)
   consult-theme consult--source-buffer
   :preview-key '(:debounce 5 any)
   consult-ripgrep consult-git-grep consult-grep
   consult-bookmark consult-recent-file
   consult--source-bookmark consult--source-recent-file
   consult--source-project-recent-file
   :preview-key "C-.")

  ;; Optionally configure the narrowing key.
  ;; Both < and C-+ work reasonably well.
  (setq consult-narrow-key "<")
)

;; Enable vertico
(use-package vertico
  :init
  (vertico-mode)

  ;; Different scroll margin
  ;; (setq vertico-scroll-margin 0)

  ;; Show more candidates
  ;; (setq vertico-count 20)

  ;; Grow and shrink the Vertico minibuffer
  ;; (setq vertico-resize t)

  ;; Optionally enable cycling for `vertico-next' and `vertico-previous'.
  ;; (setq vertico-cycle t)
  )

(use-package orderless
  :init
  (setq completion-styles '(orderless)
        completion-category-defaults nil
        completion-category-overrides '((file (styles partial-completion)))))

;; Persist history over Emacs restarts. Vertico sorts by history position.
(use-package savehist
  :init
  (savehist-mode))

;; A few more useful configurations...
(use-package emacs
  :init
  ;; Add prompt indicator to `completing-read-multiple'.
  ;; Alternatively try `consult-completing-read-multiple'.
  (defun crm-indicator (args)
    (cons (concat "[CRM] " (car args)) (cdr args)))
  (advice-add #'completing-read-multiple :filter-args #'crm-indicator)

  ;; Do not allow the cursor in the minibuffer prompt
  (setq minibuffer-prompt-properties
        '(read-only t cursor-intangible t face minibuffer-prompt))
  (add-hook 'minibuffer-setup-hook #'cursor-intangible-mode)

  ;; Emacs 28: Hide commands in M-x which do not work in the current mode.
  ;; Vertico commands are hidden in normal buffers.
  ;; (setq read-extended-command-predicate
  ;;       #'command-completion-default-include-p)

  ;; Enable recursive minibuffers
  (setq enable-recursive-minibuffers t))

abbrev

(diminish 'abbrev-mode)

corfu

(use-package emacs
  :init
  ;; TAB cycle if there are only few candidates
  (setq completion-cycle-threshold 2)

  ;; Enable indentation+completion using the TAB key.
  ;; `completion-at-point' is often bound to M-TAB.
  (setq tab-always-indent t))
(use-package corfu
  :defer 1
  :bind
  (:map
   corfu-map
   ("M-h" . backward-kill-word)
   ("<escape>" . corfu-quit))
  :config
  (require 'cape)
  (setq corfu-auto t
        corfu-fancy t
        corfu-count 5
        corfu-min-width 30
        corfu-max-width 60
        corfu-auto-delay 0.01
        corfu-preview-current t
        corfu-auto-prefix 2
        corfu-quit-no-match 'separator
        corfu-popupinfo-delay '(1.0 . 0.5))
  (global-corfu-mode)
  (corfu-popupinfo-mode)
  (corfu-prescient-mode)
  (corfu-history-mode 1)
  (savehist-mode 1)
  (add-to-list 'savehist-additional-variables 'corfu-history))
(use-package corfu-prescient
  :commands (corfu-prescient-mode)
  :config
  (setq corfu-prescient-override-sorting t))

cape

(use-package cape
  :init
  ;; Add `completion-at-point-functions', used by `completion-at-point'.
  (add-to-list 'completion-at-point-functions #'cape-dabbrev)
  (add-to-list 'completion-at-point-functions #'cape-elisp-symbol)
  :custom
  (cape-dabbrev-check-other-buffers nil)
  (cape-dict-grep nil)
  (cape-dict-file
   (concat kimim/path-kimim-emacs
           "site-lisp/english-words/words_alpha.txt"))
  :hook
  (emacs-lisp-mode
   .
   (lambda ()
     (setq-local
      completion-at-point-functions
      (list
       (cape-capf-super
        #'tempel-expand
        #'cape-elisp-symbol
        ;;#'cape-yasnippet
        #'cape-dabbrev)))))

  ((text-mode org-mode markdown-mode)
   .
   (lambda ()
     (setq-local
      completion-at-point-functions
      (list
       (cape-capf-super
        #'tempel-expand
        #'cape-dabbrev
        #'cape-dict
        ;;#'cape-yasnippet
        )))))

  (plantuml-mode
   .
   (lambda ()
     (setq-local
      completion-at-point-functions
      (list
       (cape-capf-super
        ;;#'cape-yasnippet
        #'cape-dabbrev)))))

  (eshell-mode
   .
   (lambda ()
     (setq-local corfu-auto nil)
     (corfu-mode)))
  :config
  (defvar cape--dict-all-words nil)
  (defvar cape-dict-limit 100)
  (defun kimim/cape--dict-list (input)
    "Return all words from `cape-dict-file' matching INPUT without grep."
    (unless (equal input "")
      (let* ((inhibit-message t)
             (message-log-max nil)
             (files (ensure-list
                     (if (functionp cape-dict-file)
                         (funcall cape-dict-file)
                       cape-dict-file)))
             (_ (unless cape--dict-all-words
                  (setq cape--dict-all-words
                        (split-string (with-temp-buffer
                                        (mapc #'insert-file-contents
                                              files)
                                        (buffer-string))
                                      "\n" 'omit-nulls))))
             (words (let ((completion-ignore-case t)
                          (completion-regexp-list
                           (list (regexp-quote input))))
                      (all-completions "" cape--dict-all-words))))
        (cons
         (apply-partially
          (if (and cape-dict-limit (length= words cape-dict-limit))
              #'equal #'string-search)
          input)
         (cape--case-replace-list cape-dict-case-replace input words)))))

  (advice-add 'cape--dict-list :override #'kimim/cape--dict-list))

Programming General

Project

project-find-file (C-x p f) can find files of current project, indicated by git or other version control information.

(use-package project
  :bind (("C-x p r" . project-find-ripgrep-regexp)
         ("C-x p s" . kimim/magit-stage-file))
  :functions (magit-stage-1
              magit-with-toplevel
              magit-untracked-files
              magit-unstaged-files
              magit-file-relative-name
              project--read-regexp)
  :config
  (require 'magit)
  (defun project-find-ripgrep-regexp (regexp)
    "Find all matches for REGEXP in the current project's roots."
    (interactive (list (project--read-regexp)))
    (require 'ripgrep)
    (let* ((caller-dir default-directory)
           (pr (project-current t))
           (default-directory (project-root pr))
           (dir
            (if (not current-prefix-arg)
                default-directory
              (read-directory-name "Base directory: "
                                   caller-dir nil t))))
      (ripgrep-regexp regexp dir)))

  (defun kimim/magit-stage-file ()
    (interactive)
    (let* ((current (magit-file-relative-name))
           (choices (nconc (magit-unstaged-files)
                           (magit-untracked-files)))
           (default (car (member current choices))))
      (if default
          (magit-with-toplevel
            (magit-stage-1 nil (list default)))
        (message "Already staged")))))

Compiling

(setq next-error-recenter 20)
(setq compilation-scroll-output t)
(bind-key "C-<f11>" 'compile)

Version Control

Bind magit to C-x p m with the same prefix of project, as they have strong relationship.

(use-package magit
  :bind (("C-x p m" . magit-status-here)
         (:map magit-revision-mode-map)
         ("C-<return>" . magit-diff-visit-file-other-window)
         ("<SPC>" . magit-diff-visit-worktree-file-other-window)
         (:map magit-diff-mode-map)
         ("C-<return>" . magit-diff-visit-file-other-window)
         ("<SPC>" . magit-diff-visit-worktree-file-other-window))
  :custom (magit-log-show-refname-after-summary t))

Following error will reported when using magit to commit changes:

server-ensure-safe-dir: The directory ‘~/.emacs.d/server’ is unsafe

The solution is to change the owner of ~/.emacs.d/server [fn:9]

Click R-mouse on ~/.emacs.d/server and select “Properties” (last item in menu). From Properties select the Tab “Security” and then select the button “Advanced”. Then select the Tab “Owner” and change the owner from “Administrators (\Administrators)” into “ (\”. Now the server code will accept this directory as secure because you are the owner.

Use tempel for LSP

(use-package lsp-snippet-tempel
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp/lsp-snippet"))
  :config
  (when (featurep 'lsp-mode)
    ;; Initialize lsp-snippet -> tempel in lsp-mode
    (lsp-snippet-tempel-lsp-mode-init))
  (when (featurep 'eglot)
    ;; Initialize lsp-snippet -> tempel in eglot
    (lsp-snippet-tempel-eglot-init)))

LSP with Eglot

eglot is simple and built-in LSP support in emacs.

(use-package eglot
  :bind
  (:map
   eglot-mode-map
   ("C-x l a" . eglot-code-actions)
   ("C-x l t" . eglot-find-typeDefinition)
   ("C-x l d" . eglot-find-declaration)
   ("C-x l i" . eglot-find-implemetation)
   ("C-x l b" . eldoc))
  :hook
  ((c-mode c++-mode rust-mode python-mode r-mode) . eglot-ensure)
  (eglot-managed-mode
   . kimim/eglot-completion)
  :config
  (require 'cape)
  (require 'lsp-snippet-tempel)
  (defun kimim/eglot-completion ()
    (setf (alist-get
           'styles
           (alist-get 'eglot completion-category-defaults))
          '(orderless basic flex substring partial-completion))
    (setq-local completion-at-point-functions
                (list
                 (cape-capf-super
                  #'eglot-completion-at-point
                  #'cape-dabbrev)))))

Parenthesis

~smartparens-mode~[fn:10] is a general purpose mode for dealing with parenthesis. We define some keys for it:

(use-package smartparens
  :bind (:map
         smartparens-mode-map
         ("C-<right>" . sp-forward-slurp-sexp)
         ("C-<left>" . sp-forward-barf-sexp)
         ("M-<right>" . sp-backward-barf-sexp)
         ("M-<left>" . sp-backward-slurp-sexp)
         ("M-<up>" . sp-splice-sexp-killing-backward)
         ("M-<down>" . sp-splice-sexp-killing-forward)
         ("C-k" . sp-kill-hybrid-sexp)
         ("M-k" . sp-kill-sexp)
         ("<backspace>" . sp-backward-delete-char)
         ("C-d" . sp-delete-char)
         ("C-M-<backspace>" . sp-backward-copy-sexp)
         ("C-M-w" . sp-copy-sexp))
  :functions (sp-local-pair)
  :hook (prog-mode . smartparens-mode)
  :diminish smartparens-mode
  :config
  (defalias 'sp--syntax-class-to-char 'syntax-class-to-char)
  (sp-with-modes '(c-mode c++-mode)
    (sp-local-pair "<" ">" :actions '(wrap autoskip navigate)))
  (sp-with-modes sp-lisp-modes
    ;; disable ', it's the quote character!
    (sp-local-pair "'" nil :actions nil)
    ;; disable ', it's the backquote character!
    (sp-local-pair "`" nil :actions nil)
    ;; also only use the pseudo-quote inside strings where it
    ;; serves as hyperlink.
    (sp-local-pair "`" "'" :when '(sp-in-string-p sp-in-comment-p))))

tree-sitter

(use-package tree-sitter-langs)
(use-package tree-sitter
  :ensure nil
  :hook (((c-mode c++-mode clojure-mode rust-mode) . tree-sitter-mode)
         ((c-mode c++-mode clojure-mode rust-mode) . tree-sitter-hl-mode)))

ts-fold

(use-package ts-fold
  :init
  (use-package fringe-helper)
  (require 'ts-fold)
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp/ts-fold"))
  :bind ("C-<return>" . ts-fold-toggle))

static code analysis

(use-package flycheck
  :commands (global-flycheck-mode)
  :custom
  (flycheck-global-modes '(not org-mode)))

eldoc

(use-package eldoc
  :hook (prog-mode . eldoc-mode)
  :diminish eldoc-mode)

cmake mode

(use-package cmake-mode
  :mode ("CMakeLists\\.txt\\'" . cmake-mode))

xref

(use-package xref
  :ensure nil
  :bind
  (:map
   prog-mode-map
   ("C-." . xref-find-references)))

Programming Language

C and C++

(use-package clang-format)
(use-package ggtags
  :bind
  (:map
   ggtags-navigation-mode-map
   ("M-o" . other-window)
   ("M-<" . beginning-of-buffer)
   ("M->" . end-of-buffer))
  :hook ((c-mode c++-mode) . ggtags-mode)
  :config
  ;; eglot use M-. for code navigation
  (unbind-key "M-." ggtags-mode-map)
  (setq ggtags-mode-line-project-name nil)
  (setq ggtags-global-ignore-case t)
  (setq ggtags-sort-by-nearness t))
(use-package cc-mode
  :ensure nil
  :custom   (c-default-style
             '((java-mode . "java")
               (awk-mode . "awk")
               (c-mode . "cc-mode")
               (c++-mode . "stroustrup++")
               (other . "k&r")))
  :defines c++-mode-map
  :bind (:map
         c++-mode-map
         ("C-<tab>" . clang-format))
  :config
  (require 'clang-format)
  (add-to-list 'auto-mode-alist '("\\.c\\'" . c-mode))
  (add-to-list 'auto-mode-alist '("\\.h\\'" . c++-mode))
  (add-hook 'c-mode-common-hook
            (lambda ()
              ;;(c-set-style "gnu")
              ;;(c-toggle-auto-newline 0)
              ;;(c-toggle-auto-hungry-state 0)
              ;;(c-toggle-syntactic-indentation 1)
              ;;(highlight-indentation-mode 1)
              (local-set-key "\C-co" 'ff-find-other-file)))
  ;; +   `c-basic-offset' times 1
  ;; -   `c-basic-offset' times -1
  ;; ++  `c-basic-offset' times 2
  ;; --  `c-basic-offset' times -2
  ;; *   `c-basic-offset' times 0.5
  ;; /   `c-basic-offset' times -0.5
  (c-add-style "stroustrup++"
               '("stroustrup"
                 (c-basic-offset . 4)
                 (c-offsets-alist
                  (topmost-intro . 0)
                  (inclass . +)
                  (innamespace . -)
                  (access-label . /)))))
(use-package ob-C
  :ensure nil
  :config
  (add-to-list 'org-src-lang-modes '("C" . c))
  (add-to-list 'org-babel-load-languages '(C . t)))
(use-package hideif
  :hook ((c-mode c++-mode) . hide-ifdef-mode)
  :config
  (when (eq system-type 'gnu/linux)
    (add-to-list 'hide-ifdef-env '(__linux__ . 1))
    (add-to-list 'hide-ifdef-env '(__GNUC__ . 11)))
  (when (eq system-type 'darwin)
    (add-to-list 'hide-ifdef-env '(__APPLE__ . 1))
    (add-to-list 'hide-ifdef-env '(__clang__ . 1))
    (add-to-list 'hide-ifdef-env '(__llvm__ . 1)))
  (when (eq system-type 'windows-nt)
    (add-to-list 'hide-ifdef-env '(__MINGW32__ . 1))
    (add-to-list 'hide-ifdef-env '(_WIN32 . 1))
    (add-to-list 'hide-ifdef-env '(__GNUC__ . 1)))
  :custom
  (hide-ifdef-initially nil)
  (hide-ifdef-shadow t))

C#

(use-package csharp-mode
  :ensure nil
  :hook ((csharp-mode . tree-sitter-mode)
         (csharp-mode . tree-sitter-hl-mode))
  :mode ("\\.cs\\'" . csharp-mode))

Clojure

Clojure[fn:11] is a lisp over JVM. Emm, I like it.

(use-package clojure-mode
  :mode (("\\.cljs\\'" . clojurescript-mode)
         ("\\.\\(clj\\|dtm\\|edn\\|bb\\)\\'" . clojure-mode)
         ("\\.cljc\\'" . clojurec-mode)
         ("\\(?:build\\|profile\\)\\.boot\\'" . clojure-mode))
  :config
  (require 'cider)
  (require 'flycheck)
  (require 'flycheck-clj-kondo)
  (require 'clojure-mode-extra-font-locking))
(use-package clojure-mode-extra-font-locking)

clj-kondo

Install with npm:

npm install -g clj-kondo
(use-package flycheck-clj-kondo)

Cider

Cider[fn:12] extends Emacs with support for interactive programming in Clojure.

(use-package cider
  :functions tramp-dissect-file-name
  :custom ((cider-clojure-cli-command "clojure")
           (nrepl-use-ssh-fallback-for-remote-hosts t)
           (nrepl-sync-request-timeout 100))
  :config
  ;;(setq cider-interactive-eval-output-destination 'output-buffer)
  (defun nrepl--ssh-tunnel-command (ssh dir port)
    "Command string to open SSH tunnel to the host associated with DIR's PORT."
    (with-parsed-tramp-file-name dir v
      ;; this abuses the -v option for ssh to get output when the port
      ;; forwarding is set up, which is used to synchronise on, so that
      ;; the port forwarding is up when we try to connect.
      (format-spec
       "%s -v -N -L %p:localhost:%p %u'%h' %x"
       `((?s . ,ssh)
         (?p . ,port)
         (?h . ,v-host)
         (?u . ,(if v-user (format "-l '%s' " v-user) ""))
         (?x . "-o \"ProxyCommand=nc -X connect -x 127.0.0.1:1080 %h %p\""))))))
(use-package ob-clojure
  :ensure org
  :custom (org-babel-clojure-backend 'cider)
  :config
  (add-to-list 'org-src-lang-modes '("clojure" . clojure)))

Babashka

Download bb from https://github.com/babashka/babashka/releases and put bb to execute PATH.

(use-package ob-bb
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/ob-bb")))

Python

Python development configuration is quite easy. elpy [fn:13] is used here:

;; (use-package elpy
;;   :config
;;   (elpy-enable))

(use-package python
  :ensure nil
;;  :defines elpy-rpc-backend
  :mode ("\\.py\\'" . python-mode)
  :interpreter ("python" . python-mode)
  :config
  ;; (add-hook 'python-mode-hook
  ;;           (lambda ()
  ;;             (setq yas-indent-line nil)))
  (add-to-list
   'python-shell-completion-native-disabled-interpreters "python"))

;;  (use-package company-jedi
;;    :config
;;    (setq elpy-rpc-backend "jedi"))

Following python package is required according to elpy mannual:

pip install rope
pip install jedi
# flake8 for code checks
pip install flake8
# importmagic for automatic imports
pip install importmagic
# and autopep8 for automatic PEP8 formatting
pip install autopep8
# and yapf for code formatting
pip install yapf
# install virtualenv for jedi
pip install virtualenv

Rust

The easiest way to install rust is to run following script:

curl https://sh.rustup.rs -sSf | sh
(use-package rustic
  :hook
  (rustic-mode . (lambda ()
	               (set (make-local-variable 'compile-command)
		                "cargo run")))
  :custom
  (rustic-lsp-client 'eglot))

R ESS

(use-package ess
  :custom
  (inferior-ess-r-program "r"))

Swift

(use-package swift-mode
  :mode ("\\.swift\\'" . swift-mode))

TypeScript

(use-package typescript-mode
  :hook ((typescript-mode . tree-sitter-mode)
         (typescript-mode . tree-sitter-hl-mode)
         (typescript-mode . eglot-ensure)))

json

(use-package json-mode
  :mode ("\\.json\\'" . json-mode)
  :hook ((json-mode . tree-sitter-mode)
         (json-mode . tree-sitter-hl-mode)))

Golang

Open .go file with go-mode.

(use-package go-mode
  :mode ("\\.go\\'" . go-mode))

Docker file

Some dockerfile is not end with .dockerfile, so lets guess:

(use-package dockerfile-mode
  :mode ("\\dockerfile\\'" . dockerfile-mode))

Emacs lisp

(use-package elisp-mode
  :ensure nil
  :mode ("\\.el\\'" . emacs-lisp-mode)
  :config
  (define-derived-mode lisp-interaction-mode emacs-lisp-mode ""))

AutoHotKey

ahk-mode developed by Rich Alesi[fn:14]

(use-package ahk-mode
  :mode ("\\.ahk\\'" . ahk-mode))

yaml mode

(use-package yaml-mode
  :mode ("\\.yml\\'" . yaml-mode)
  :bind (:map
         yaml-mode-map
         ("\C-m" . newline-and-indent)))

shell

(use-package shell
  :mode ("\\.sh\\'" . shell-script-mode))
(use-package ob-shell
  :ensure nil
  :config
  (require 'shell)
  (add-to-list 'org-src-lang-modes '("shell" . shell))
  (add-to-list 'org-babel-load-languages '(shell . t)))

powershell

(use-package powershell
  :mode ("\\.ps1\\'" . powershell-mode))

solidity

Major mode to edit ethereum solidity smart contract code.

(use-package solidity-mode
  :mode ("\\.sol\\'" . solidity-mode))

lua

(use-package lua-mode)

Diagram

PlantUML

That’s fun to draw UML with ob-plantuml inside orgmode:

For Windows Cygwin, install graphviz in cygwin setup tool

For macOS, install graphviz with homebrew:

brew install graphviz

Download plantuml.jar from https://plantuml.com/download, and put it to some place and assign plantuml-jar-path to there.

(use-package plantuml-mode
  :mode ("\\.puml\\'" . plantuml-mode)
  :custom
  (plantuml-output-type "svg")
  (plantuml-default-exec-mode 'jar)
  (plantuml-jar-path (expand-file-name
                      (concat kimim/path-kimikit "plantuml/plantuml.jar")))
  (plantuml-executable-args '("-charset" "utf-8")))
(use-package ob-plantuml
  :ensure nil
  :config
  (require 'plantuml-mode)
  ;; WARNING: if variables are from other package, setq them at :config
  (setq org-plantuml-jar-path plantuml-jar-path)
  (setq org-plantuml-args plantuml-executable-args)
  (add-to-list 'org-src-lang-modes '("plantuml" . plantuml)))

I want to preview plantuml result in a full window, so I set image-auto-resize to fit-window. If the image is still to small, when it is a long sequence diagram, I can use key s i to invoke image-transform-fit-to-width to maximize the width of the image.

(use-package image-mode
  :ensure nil
  :custom
  (image-auto-resize 'fit-window)
  :config
  (add-to-list 'auto-mode-alist '("\\.svg\\'" . image-mode)))

graphviz & dot

dot is used to draw simple diagram in code. You need to install graphviz:

pacman -S mingw-w64-ucrt-x86_64-graphviz
(use-package graphviz-dot-mode
  :ensure t
  :custom
  (graphviz-dot-preview-extension "svg"))

ditaa

(use-package ob-ditaa
  :ensure nil
  :custom
  (org-ditaa-jar-path
        (expand-file-name
         (concat kimim/path-kimikit "ditaa/ditaa.jar")))
  :config
  (add-to-list 'org-src-lang-modes '("ditaa" . artist)))

Mermaid

Mermaid [fn:15] is another js based diagramming and charting tool. To use it inside orgmode, mermaid-cli should be installed:

yarn add @mermaid-js/mermaid-cli

mmdc will be installed at ~~/node_modules/.bin/mmdc~. Then we just setup ob-mermaid and mermaid-mode for babel evaluation and editing.

(use-package ob-mermaid
  :custom
  (ob-mermaid-cli-path "~/node_modules/.bin/mmdc.cmd")
  :config
  (add-to-list 'org-babel-load-languages '(mermaid . t)))
(use-package mermaid-mode
  :mode ("\\.mermaid\\'" . mermaid-mode))

iscroll

scroll images line by line.

(use-package iscroll
  :diminish iscroll-mode
  :hook ((org-mode markdown-mode) . iscroll-mode))

chatu

(use-package chatu
  :load-path (lambda ()
               (concat
                kimim/path-kimim-emacs "site-lisp/chatu"))
  :commands (chatu-add
             chatu-open)
  :hook ((org-mode markdown-mode) . chatu-mode))

Music

lilypond

(eval-and-compile
  (defun lilypond-load-path ()
    (cond ((eq system-type 'darwin)
           "/opt/homebrew/share/emacs/site-lisp/lilypond")
          ((eq system-type 'windows-nt)
           (concat kimim/path-kimikit
                   "lilypond-2.24.3/share/emacs/site-lisp"))
          ((eq system-type 'gnu/linux)
           "/usr/local/share/emacs/site-lisp/lilypond"))))

(use-package lilypond-mode
  :load-path (lambda () (list (lilypond-load-path)))
  :mode ("\\.ly\\'" . LilyPond-mode))
(use-package ob-lilypond
  :ensure nil
  :config
  (require 'lilypond-mode)
  (cond ((eq system-type 'darwin)
         (setq org-babel-lilypond-ly-command
               "/opt/homebrew/bin/lilypond")))
  (defun org-babel-lilypond-process-basic (body params)
    "Execute a lilypond block in basic mode."
    (let* ((out-file (cdr (assq :file params)))
           (cmdline (or (cdr (assq :cmdline params))
                        ""))
           (in-file (org-babel-temp-file "lilypond-")))

      (with-temp-file in-file
        (insert (org-babel-expand-body:generic body params)))
      (org-babel-eval
       (concat
        org-babel-lilypond-ly-command
        " -dcrop --loglevel=ERROR "
        (or (cdr (assoc (file-name-extension out-file)
                        '(("pdf" . "--pdf ")
                          ("ps" . "--ps ")
                          ("svg" . "--svg ")
                          ("png" . "--png "))))
            "--png ")
        "--output="
        (file-name-sans-extension out-file)
        " "
        cmdline
        in-file
        ;; copy .preview file to sans .preview file
        " && mv -f "
        (file-name-sans-extension out-file)
        ".cropped."
        (file-name-extension out-file)
        " "
        out-file) "")) nil))

Reference management

pdf-tools

Hook pdf-view-themed-minor-mode to pdf-view-mode and add pdf-view-refresh-themed-buffer to enable-theme-functions to follow emacs theme color.

(use-package pdf-tools
  :diminish pdf-view-themed-minor-mode
  :functions (pdf-view-refresh-themed-buffer)
  :defines (pdf-view-themed-minor-mode)
  :mode ("\\.pdf\\'" . pdf-view-mode)
  :hook ((pdf-view-mode . pdf-view-themed-minor-mode)
         (pdf-view-mode . pdf-isearch-minor-mode))
  :custom (pdf-view-display-size 3.85)
  :bind
  (:map
   pdf-view-mode-map
   ("C-s" . isearch-forward)
   ("C-r" . isearch-backward)
   ("C-o" . pdf-occur)
   ("C-l" . pdf-view-center-in-window)
   ("j" . (lambda ()
            "Next line within page"
            (interactive)
            (image-next-line 10)))
   ("k" . (lambda ()
            "Previous line within page"
            (interactive)
            (image-next-line -10)))
   ("n" . kimim/pdf-view-next-page)
   ("p" . kimim/pdf-view-previous-page)
   ("o" . other-window)
   ("<home>". (lambda ()
                (interactive)
                (image-scroll-up -999)))
   ("<end>". (lambda ()
               (interactive)
               (image-scroll-up 999)))
   ("C-c C-z" . pdf-view-open-bibtex-notes)
   ("/" . (lambda ()
            (interactive)
            (let ((filename (file-name-nondirectory
                             (buffer-file-name))))
              (kill-new
               filename)
              (message "Copied %s" filename))))
   ("t" . kimim/pdf-view-pagetext)
   ("`" . kimim/fanyi-in-pdf)
   ("<mouse-3>" . kimim/fanyi-in-pdf))
  :config
  (require 'pdf-tools)
  (require 'fanyi)
  (defun kimim/fanyi-in-pdf ()
    "Invoke fanyi in pdf-view."
    (interactive)
    (pdf-view-assert-active-region)
    (if-let ((word (car (pdf-view-active-region-text))))
        (progn
          (fanyi-dwim word)
          (cl-pushnew word fanyi-history))
      (call-interactively #'fanyi-dwim)))

  (advice-add 'enable-theme :after
              (lambda (&rest _)
                (when pdf-view-themed-minor-mode
                  (pdf-view-refresh-themed-buffer t))))

  (require 'bibtex-completion)
  (defun kimim/pdf-view-next-page (&optional n)
    (interactive "p")
    (pdf-view-next-page n)
    (image-bob))

  (defun kimim/pdf-view-previous-page (&optional n)
    (interactive "p")
    (pdf-view-previous-page n)
    (image-bob))

  (defun pdf-view-open-bibtex-notes ()
    "From PDF file, open the notes if they exist."
    (interactive)
    (bibtex-completion-edit-notes (list (pdf-view-get-bibtex-key))))

  (defun pdf-view-get-bibtex-key ()
    "Get bibtex key from PDF buffer name in pdf-view mode"
    (car (string-split (buffer-name) "[-\\(_\\.]+")))

  (defun kimim/pdf-view-pagetext ()
    "Show pdf text in a buffer."
    (interactive)
    (pdf-view-mark-whole-page)
    (pdf-view-kill-ring-save)
    (switch-to-buffer "*pdf-view-pagetext*")
    (yank)))

pdf-view-store

To remember visited pages of PDF files.

(use-package pdf-view-restore
  :after pdf-tools
  :custom (pdf-view-restore-filename "~/.emacs.d/.pdf-view-restore")
  :hook (pdf-view-mode . pdf-view-restore-mode))

pdf-view-pagemark

Add indicator of remaining text when scrolling PDF pages.

(use-package pdf-view-pagemark
  :after pdf-tools
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/pdf-view-pagemark"))
  :hook (pdf-view-mode . pdf-view-pagemark-mode))

org-ref

(use-package bibtex-completion
  :defines bibtex-completion-bibliography
  :custom
  (bibtex-completion-display-formats
   '((t . "${=key=:30} ${author:36} ${title:*} ${year:4} ${=has-pdf=:1}${=has-note=:1} ${=type=:7}")))
  (bibtex-completion-bibliography (concat kimim/path-docs "references.bib"))
  (bibtex-completion-library-path kimim/path-docs)
  (bibtex-completion-notes-path (concat kimim/path-notes "org-ref-notes.txt"))
  :bind ("C-x m b" .
         (lambda ()
           (interactive)
           (find-file
            (concat kimim/path-docs "references.bib")))))
(use-package org-ref
  :functions (-flatten
              f-join
              org-ref-get-bibtex-key-and-file
              bibtex-completion-key-at-point
              bibtex-completion-candidates
              bibtex-completion-init
              bibtex-completion-edit-notes
              org-ref-cite-hydra/body
              org-ref-find-bibliography
              kimim/org-ref-open-pdf
              kimim/org-ref-open-pdf-in-dired
              kimim/org-ref-open-notes
              kimim/org-ref-open-notes-action
              kimim/org-ref-get-pdf-filename
              kimim/org-ref-open-pdf-action
              kimim/org-ref-open-pdf-in-dired-action)
  :bind (("C-x m p" . kimim/org-ref-open-pdf-at-point)
         ("C-x m P" . kimim/org-ref-open-pdf-in-dired-at-point)
         ("C-x m n" . kimim/org-ref-open-notes-at-point)
         :map org-mode-map
         ("C-c ]" . org-ref-insert-cite-link)
         ("C-c o" . kimim/org-roam-ref-open-pdf))
  :config
  (require 'bibtex-completion)
  (require 'org-roam-bibtex)
  ;; use MS word to open office file
  (add-to-list 'org-file-apps
               '("\\.docx?\\'" . default))
  (add-to-list 'org-file-apps
               '("\\.pptx?\\'" . default))
  (add-to-list 'org-file-apps
               '("\\.xlsx?\\'" . default))

  ;; dont put [ inside file name
  (defun kimim/org-ref-get-pdf-filename (key)
    (if bibtex-completion-library-path
        (let* ((pdf-dirs (if (listp bibtex-completion-library-path)
                             bibtex-completion-library-path
                           (list bibtex-completion-library-path)))
               (pdfs
                (-flatten
                 (--map (file-expand-wildcards
                         (f-join it (format "%s*" key)))
                        (-flatten
                         (append pdf-dirs
                                 (--map (directory-files-recursively it "" t)
                                        pdf-dirs)))))))
          (cond
           ((= 0 (length pdfs))
            (expand-file-name (format "%s.pdf" key) bibtex-completion-library-path))
           ((= 1 (length pdfs))
            (car pdfs))
           ((> (length pdfs) 1)
            (completing-read "Choose: " pdfs))))
      ;; No bibtex-completion-library-path defined so return just a file name.
      (format "%s.pdf" key)))

  (defun kimim/org-ref-open-pdf-action (key)
    "Open the pdf for bibtex key under point if it exists."
    (let* ((pdf-file (kimim/org-ref-get-pdf-filename key)))
      (if (file-exists-p pdf-file)
          (find-file pdf-file)
        (message "no pdf found for %s" key))))

  (defun kimim/org-ref-open-bibtex-pdf ()
    (interactive)
    (kimim/org-ref-open-pdf-action (bibtex-completion-get-key-bibtex)))

  (defun kimim/org-ref-open-pdf (&optional arg)
    (interactive)
    (kimim/org-ref-open-pdf-action (org-ref-read-key)))

  (defun kimim/org-ref-open-pdf-at-point ()
    "Open the pdf for bibtex key under point if it exists."
    (interactive)
    (let* ((results (condition-case nil
                        (if (eq 'org-mode
                                (buffer-local-value
                                 'major-mode (current-buffer)))
                            (org-ref-get-bibtex-key-and-file))
                      (error nil))))
      (if (or (null results)
              (string= "" (car results))
              (null (car results)))
          (kimim/org-ref-open-pdf)
        (let ((pdf-file (kimim/org-ref-get-pdf-filename (car results))))
          (if (file-exists-p pdf-file)
              (find-file pdf-file)
            (kimim/org-ref-open-pdf))))))

  (defun kimim/org-ref-open-pdf-in-dired-action (key)
    "Open the pdf dired for bibtex key under point if it exists."
    (let* ((pdf-file (kimim/org-ref-get-pdf-filename key)))
      (if (file-exists-p pdf-file)
          (dired-jump nil pdf-file)
        (message "no pdf found for %s" key))))

  (defun kimim/org-ref-open-pdf-in-dired (&optional arg)
    (interactive)
    (kimim/org-ref-open-pdf-in-dired-action (org-ref-read-key)))

  (defun kimim/org-ref-open-pdf-in-dired-at-point ()
    "Open the pdf dired for bibtex key under point if it exists."
    (interactive)
    (let* ((results (condition-case nil
                        (org-ref-get-bibtex-key-and-file)
                      (error nil)))
           (key (car results)))
      (if (or (string= "" key) (null key))
          (kimim/org-ref-open-pdf-in-dired)
        (let ((pdf-file (kimim/org-ref-get-pdf-filename key)))
          (if (file-exists-p pdf-file)
              (dired-jump nil pdf-file)
            (message "no pdf found for %s" key))))))

  (defun kimim/org-ref-open-notes-action (key)
    "Open the notes for bibtex key under point if it exists."
    (bibtex-completion-edit-notes (list key)))

  (defun kimim/org-ref-open-notes (&optional arg)
    (interactive)
    (kimim/org-ref-open-notes-action (org-ref-read-key)))

  (defun kimim/org-ref-open-notes-at-point ()
    "Open the notes of a reference if they exist."
    (interactive)
    (let* ((results (condition-case nil
                        (org-ref-get-bibtex-key-and-file)
                      (error nil)))
           (key (car results)))
      (if (or (string= "" key) (null key))
          (kimim/org-ref-open-notes)
        (kimim/org-ref-open-notes-action key))))

  (defun kimim/org-roam-ref-open-pdf ()
    "Open pdf from ROAM_REFS of a note."
    (interactive)
    (kimim/org-ref-open-pdf-action
     (cadr
      (string-split
       (car (org-property-values "ROAM_REFS"))
       ":"))))


  (defun kimim/bibtex-completion-get-title ()
    "Copy the title by KEY."
    (interactive)
    (kill-new
     (s-format
      "${title}"
      'bibtex-completion-apa-get-value
      (bibtex-completion-get-entry
       (org-ref-get-bibtex-key-under-cursor)))))

  (defhydra+ org-ref-citation-hydra ()
    "Add copy action to `org-ref-citation-hydra'."
    ("y" kimim/bibtex-completion-get-title
     "Copy title" :column "Copy")))

There is a built-in bibtex-mode to manage references. We can extend it to support more functions from org-ref:

(use-package bibtex
  :ensure nil
  :bind (:map bibtex-mode-map
         ("C-x m p" . kimim/org-ref-open-bibtex-pdf)
         ("C-x m n" . org-ref-open-bibtex-notes)
         ("C-x m d" . kimim/org-ref-open-bibtex-in-dired)
         ("C-c C-z" . org-ref-open-bibtex-notes))
  :config
  (require 'org-ref)
  (require 'org-roam-bibtex))

nov: reading epub books

(use-package nov
  :mode ("\\.epub\\'" . nov-mode)
  :hook ((nov-mode . olivetti-mode)
         (nov-mode . (lambda ()
                       (visual-line-mode)
                       (setq line-spacing 0.5))))
  :custom
  (nov-header-line-format nil)
  :bind
  (:map
   nov-mode-map
   ("<home>" . beginning-of-line)
   ("<end>" . end-of-line)
   ("<mouse-3>" . kimim/fanyi-in-epub))
  :config
  (require 'olivetti)
  (require 'fanyi)
  (setq nov-text-width (- olivetti-body-width 2))

  (defun kimim/fanyi-in-epub ()
    "Invoke fanyi in epub view."
    (interactive)
    ;;(mouse-set-point last-input-event)
    (mark-thing-at-mouse last-input-event 'word)
    (if-let ((word (thing-at-point 'word)))
        (progn
          (fanyi-dwim word)
          (cl-pushnew word fanyi-history))
      (call-interactively #'fanyi-dwim))))

Orgmode

org general setting

(use-package org
  :mode (("\\.txt\\'" . org-mode)
         ("\\.org\\'" . org-mode))
  :hook (org-mode . olivetti-mode)
  :bind
  (:map org-mode-map
   ("C-c b" . org-iswitchb)
   ("C-c l" . org-store-link)
   ("C-c C-x l" . org-toggle-link-display)
   ("C-c  !" . org-time-stamp-inactive)
   ("C-c  。" . org-time-stamp)
   ("M-." . org-open-at-point)
   ("M-*" . org-mark-ring-last-goto)
   ("M-h" . nil)
   ("C-'" . org-emphasize)
   ("C-c p" . kimim/preview-babel-image)
   ("C-c C-w" . org-refile-reverse)
   ("C-c w" . org-refile)
   ("M-," . org-mark-ring-goto))
  :custom
  (org-modules '(org-habit
                 ol-w3m ol-bbdb ol-bibtex
                 ol-docview ol-gnus ol-info
                 ol-irc ol-mhe ol-rmail ol-eww))
  (org-export-with-sub-superscripts '{})
  (org-startup-folded 'showall)
  (org-startup-with-inline-images t)
  (org-tags-column (- fill-column))
  ;; image width in preview
  (org-image-actual-width `(,(* (+ fill-column 10)
                                (frame-char-width))))
  :config
  ;; no use for me, I always press this key accidentally
  (unbind-key "C-'" 'org-mode-map)
  (setq org-hide-emphasis-markers t)
  (setq org-support-shift-select t)
  ;; no empty line after collapsed
  (setq org-cycle-separator-lines 0)
  (if window-system
      (setq org-startup-indented t)
    (setq org-startup-indented nil)))

org-appear

(use-package org-appear
  :commands (org-appear-mode)
  :ensure t
  :custom (org-appear-autolinks nil)
  :hook (org-mode . org-appear-mode))

org-indent

Add advice to org-indent--compute-prefixes to remove needless indent spaces before plain text line.

(use-package org-indent
  :ensure nil
  :custom (org-indent-mode-turns-on-hiding-stars nil)
  :hook (org-mode . org-indent-mode)
  :diminish org-indent-mode
  :config
  (defun kimim/org-indent-adjust-indent ()
    (dotimes (n org-indent--deepest-level)
      (let ((indentation (* (1- org-indent-indentation-per-level)
                            n)))
        ;; Text line prefixes.
        (aset org-indent--text-line-prefixes
              n
              (org-add-props
                  (concat (make-string indentation ?\s)
                          (and (> n 0)
                               (char-to-string
                                org-indent-boundary-char)))
                  nil 'face 'org-indent)))))
    (advice-add 'org-indent--compute-prefixes :after
              #'kimim/org-indent-adjust-indent))

org-modern

(use-package org-modern
  :custom
  ;; https://github.com/minad/org-modern/issues/134
  (org-modern-star '("" "" ""))
  (org-modern-list
   '((?- . "")
     (?+ . "")
     (?* . "")))
  (org-modern-checkbox
   '((?X . "")
     (?- . #("☐–" 0 2 (composition ((2)))))
     (?\s . "")))
  (org-modern-timestamp nil)
  (org-modern-tag nil)
  (org-modern-todo nil)
  (org-modern-block-name nil)
  (org-modern-block-fringe nil)
  (org-modern-keyword nil)
  (org-modern-priority nil)
  :hook
  (org-mode . org-modern-mode)
  :config
  (advice-add
   'org-modern--make-font-lock-keywords
   :filter-return
   (lambda (result)
     (append
      result
      (when-let ((bullet (alist-get ?- org-modern-list)))
        `(("^\\(-\\)[ \t]" 1 '(face nil display ,bullet))))
      (when-let ((bullet (alist-get ?+ org-modern-list)))
        `(("^[ ]\\{2\\}\\(-\\)[ \t]" 1 '(face nil display ,bullet))))
      (when-let ((bullet (alist-get ?* org-modern-list)))
        `(("^[ ]\\{4\\}\\(-\\)[ \t]" 1 '(face nil display ,bullet))))))))

pretty-symbols-in-org-mode

(use-package org
  :ensure nil
  :hook
  (org-mode . prettify-symbols-in-org-mode)
  :config
  (defun prettify-symbols-in-org-mode ()
    "Beautify Org Symbols"
    (push '(":category:" . "") prettify-symbols-alist)
    (push '(":PROPERTIES:" . "🗅") prettify-symbols-alist)
    (push '(":END:" . "") prettify-symbols-alist)
    (push '("#+TITLE:" . "🗎") prettify-symbols-alist)
    (push '("#+SUBTITLE:" . "") prettify-symbols-alist)
    (push '(":SETTINGS:" . "") prettify-symbols-alist)
    (push '("#+begin_src" . "«" ) prettify-symbols-alist)
    (push '("#+end_src" . "»" ) prettify-symbols-alist)
    (push '("#+begin_comment" . "" ) prettify-symbols-alist)
    (push '("#+end_comment" . "" ) prettify-symbols-alist)
    (prettify-symbols-mode)))

org-inline-pdf

Sometimes, SVG images are not properly converted in LaTeX PDF file. Including a PDF image can solve this problem. Then we need a method to toggle PDF image display. org-inline-pdf solves this issue by using pdf2svg to convert PDF to SVG on-the-fly.

(use-package org-inline-pdf
  :hook (org-mode . org-inline-pdf-mode))

orgalist

(use-package orgalist
  :commands (orgalist-mode))

org for writing

(use-package org-download
  :commands (org-download-enable)
  :custom
  (org-download-heading nil)
  (org-structure-template-alist '(("a" . "export ascii")
                                  ("c" . "center")
                                  ("C" . "comment")
                                  ("d" . "definition")
                                  ("e" . "example")
                                  ("E" . "export")
                                  ("h" . "export html")
                                  ("l" . "export latex")
                                  ("L" . "lemma")
                                  ("p" . "proof")
                                  ("q" . "quote")
                                  ("Q" . "quotation")
                                  ("s" . "src")
                                  ("t" . "theorem")
                                  ("v" . "verse")))
  :functions kimim/org-download-annotate
  :config
  (setq org-download-timestamp "")
  (setq-default org-download-image-dir "./images")
  (setq org-download-method 'directory)

  (defun kimim/org-download-annotate (link)
    "Annotate LINK with the time of download."
    (format "#+NAME: fig:%s\n#+CAPTION: %s\n"
            (file-name-base link) (file-name-base link)))
  (setq org-download-annotate-function #'kimim/org-download-annotate)
  (setq org-download-display-inline-images nil)
  (setq image-file-name-extensions
        (quote
         ("png" "jpeg" "jpg" "gif" "tiff" "tif" "xbm"
          "xpm" "pbm" "pgm" "ppm" "pnm" "svg" "pdf" "bmp")))
  (defun org-download--dir-2 () "."))
(use-package org
  :custom (org-num-skip-footnotes t)
  :config
  (require 'org-download)
  (setq org-hide-leading-stars t)
  (setq org-footnote-auto-adjust t)
  (setq org-footnote-define-inline nil))

org with source code

(use-package org
  :config
  (setq org-src-window-setup 'current-window)
  (setq org-src-fontify-natively t)
  (setq org-src-preserve-indentation t)
  (setq org-edit-src-content-indentation 0)
  (setq org-confirm-babel-evaluate nil)
  (add-hook 'org-babel-after-execute-hook 'org-display-inline-images)
  ;; get the idea from John Kitchen(author of org-ref)
  (defadvice org-babel-execute-src-block (around load-language nil activate)
    "Load language if needed"
    (let* ((language (org-element-property :language (org-element-at-point)))
           (language-to-load (if (string= language "C++")
                                 "C"
                               language)))
      (unless (cdr (assoc (intern language-to-load) org-babel-load-languages))
        (require (intern (concat "ob-" language-to-load)))
        (add-to-list 'org-babel-load-languages (cons (intern language-to-load) t))
        (org-babel-do-load-languages 'org-babel-load-languages
                                     org-babel-load-languages))
      ad-do-it)))

org exporting

When exporting, do not export with author and date.

(use-package org
  :bind ("C-c C-'" . org-insert-structure-template)
  :functions (org-export--collect-headline-numbering
              org-export--get-min-level)
  :custom
  (org-export-allow-BIND t)
  (org-export-html-validation-link nil)
  ;;(org-export-with-sub-superscripts '{})
  (org-export-with-author nil)
  (org-export-with-date t)
  (org-html-head "<style>
  pre.src-bad { background: wheat; }
  pre { margin: 0.0em;
        padding: 4pt;
  }
</style>")
  :config
  (require 'ox-latex)

  (defun kimim/org-html-src-block-advice (oldfun src-block contents info)
    (let* ((class-tag (org-export-read-attribute
                       :attr_html src-block :class))
           (html-block (funcall oldfun src-block contents info)))
      (if (string-empty-p class-tag)
          html-block
        (string-replace
         ;; add src-bad class
         "class=\"src" (concat "class=\"src src-" class-tag) html-block))))

  (advice-add 'org-html-src-block :around
              #'kimim/org-html-src-block-advice)

  (defmacro by-backend (&rest body)
    `(pcase
         (if (boundp 'backend)
             (org-export-backend-name backend) nil)
       ,@body))

  (defun org-export--collect-tree-properties (data info)
    "Extract tree properties from parse tree.

DATA is the parse tree from which information is retrieved.  INFO
is a list holding export options.

Following tree properties are set or updated:

`:headline-offset' Offset between true level of headlines and
     local level.  An offset of -1 means a headline
     of level 2 should be considered as a level
     1 headline in the context.

`:headline-numbering' Alist of all headlines as key and the
        associated numbering as value.

`:id-alist' Alist of all ID references as key and associated file
            as value.

Return updated plist."
    ;; Install the parse tree in the communication channel.
    (setq info (plist-put info :parse-tree data))
    ;; Compute `:headline-offset' in order to be able to use
    ;; `org-export-get-relative-level'.
    (setq info
          (plist-put info
                     :headline-offset
                     (- 1 (org-export--get-min-level data info))))
    ;; From now on, properties order doesn't matter: get the rest of the
    ;; tree properties.
    (org-combine-plists
     info
     (list :headline-numbering (org-export--collect-headline-numbering data info)
           :id-alist
           (org-element-map data 'link
             (lambda (l)
               (and (string= (org-element-property :type l) "id")
                    (let* ((id (org-element-property :path l))
                           (file (car (org-id-find id))))
                      ;; replace txt extension with exported PDF file
                      (and file
                           (let ((pdf-file (concat (file-name-sans-extension file) ".pdf")))
                             (if (file-exists-p pdf-file)
                                 (cons id (file-relative-name pdf-file))
                               (cons id (file-relative-name file)))))))))))))

org to pdf

LaTeX is required to convert org-mode to PDF.

For MacOS:

brew cask install mactex-no-gui

For Windows, there are three options:

  1. download and install CTEX from http://www.ctex.org
  2. install texlive-collection in cygwin
    apt-cyg install texlive-collection-xetex    \
            texlive-collection-latex            \
            texlive-collection-fontsrecommended
        
  3. download and install texlive from tug.org

For Linux, download texlive install pacakge from ctan.org

tar zxvf install-tl-unx.tar.gz
cd install-tl-20200908/
sudo ./install-tl

Then for all the OS platforms, use tlmgr to install user level tex packages (notes that, in windows, you may need to run tlmgr.bat):

tlmgr init-usertree
tlmgr --usermode install ctex titlesec enumitem ms fontspec abstract    \
                         zhnumber fandol lastpage pdftexcmds infwarerr  \
                         minted fvextra etoolbox fancyvrb upquote       \
                         lineno catchfile xstring framed float          \
                         grffile wrapfig ulem lettrine minifp           \
                         capt-of xcolor svg koma-script trimspaces      \
                         titling layaureo parskip extsizes pgf          \
                         moderncv microtype
fmtutil-sys --all

Recently, I adopted to mainly use texlive on Windows. It works fine and provide a GUI tool to maintain packages: tlshell.exe. You can use it to install and update latex packages.

To export org-mode to PDF, with code style highlight, you need to install python and pygments. Because pygmentize from pygments is used to generate latex markups for font highlighting.

For MacOS, the OS shipped python2.7 does not accompanied with pip package management script. So you need to install pip, and then add pygments, acc. https://pip.pypa.io/en/stable/installing/ , pygmentize will be installed under $HOME/Library/Python/2.7/bin, which is added to exec-path and PATH.

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python get-pip.py

Get pygments with pip:

pip install pygments

For Ubuntu Linux:

sudo apt install python3-pygments
(use-package latex-classes
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                "site-lisp"))
  :config
  (require 'bibtex-completion))
(use-package ox-latex
  :ensure nil
  :defines (kimim/latex-classes
            org-beamer-frame-level
            org-latex-minted-langs)
  :functions (org-html--make-attribute-string)
  :commands org-latex-publish-to-pdf
  :diminish org-beamer-mode
  :custom
  (org-latex-listings 'minted)
  (org-latex-minted-options
   '(("frame" "lines") ("fontsize" "\\scriptsize")))
  (org-latex-pdf-process
   '("latexmk -xelatex -shell-escape -output-directory=%o %F"))
  ;; avoid warning when preview tikz in org babel
  (org-babel-latex-preamble
   (lambda (_)
     "\\documentclass[preview]{standalone}"))
  :config
  (require 'ox)
  (require 'ox-beamer)
  (require 'latex-classes)
  (setq org-startup-with-beamer-mode t)
  (setq org-beamer-frame-level 2)
  ;; export quotes match
  (setq org-export-with-smart-quotes t)
  (add-to-list 'org-latex-minted-langs
               '(plantuml "text"))
  (add-to-list 'org-latex-minted-langs
               '(ditaa "text"))
  (add-to-list 'org-latex-minted-langs
               '(bb "clojure"))
  (add-to-list 'org-latex-minted-langs
               '(conf "text"))
  (add-to-list 'org-latex-minted-langs
               '(mermaid "text"))
  (add-to-list 'org-latex-minted-langs
               '(lilypond "text"))
  (defun ref-headline-removal (backend)
    "Remove reference headline with tag: ref"
    (org-map-entries
     (lambda ()
       (when (member "ref" org-scanner-tags)
         (delete-region (point) (line-beginning-position 2))))))
  (add-hook 'org-export-before-parsing-functions 'ref-headline-removal)

  ;;;;; Nicolas Goaziou, http://article.gmane.org/gmane.emacs.orgmode/67692
  ;; (defun org-latex-ignore-heading-filter-headline (headline backend info)
  ;;   "Strip headline from HEADLINE. Ignore BACKEND and INFO."
  ;;   (when (and (org-export-derived-backend-p backend 'latex)
  ;;              (string-match "\\`.*ignoreheading.*\n" headline))
  ;;     (replace-match "" nil nil headline)))
  ;; (add-to-list 'org-export-filter-headline-functions
  ;;              'org-latex-ignore-heading-filter-headline)

  ;; most of the time, I do not need table of contents
  (setq org-latex-toc-command nil)
  ;; https://www.tuicool.com/articles/ZnAnym
  ;; remove error: ! LaTeX Error: Command \nobreakspace unavailable in encoding T1.
  ;; add: \DeclareRobustCommand\nobreakspace{\leavevmode\nobreak\ }
  ;; put long latex classes in a separate file
  (require 'latex-classes)
  (setq org-latex-classes kimim/latex-classes)
  (setq org-latex-default-class "article")
  ;; removed, it will make small image too large!
  ;;(setq org-latex-image-default-option
  ;;      "height=0.8\\textheight,width=\\textwidth,keepaspectratio")
  (setq org-latex-image-default-width "")
  ;; remove fontenc, and AUTO in front of inputenc,
  ;; then francais can be processed
  (setq org-latex-default-packages-alist
        (quote
         (("" "inputenc" t ("pdflatex"))
          ("" "minted" t nil)
          ("" "amsfonts" t nil)
          ("" "graphicx" t nil)
          ("inkscapeopt = -C --export-ignore-filters, inkscapelatex=false" "svg" t nil)
          ("" "grffile" t nil)
          ("" "longtable" nil nil)
          ("" "wrapfig" nil nil)
          ("" "rotating" nil nil)
          ("normalem" "ulem" t nil)
          ("" "amsmath" t nil)
          ("" "textcomp" t nil)
          ("" "lettrine" t nil)
          ("" "capt-of" nil nil))))
  ;; latex preview report exception with bibref
  ;; (setq org-latex-packages-alist
  ;;       `(,(concat "\\addbibresource{"
  ;;                  (expand-file-name bibtex-completion-bibliography)
  ;;                  "}")))
  ;; (mapconcat
  ;;  (lambda (it)
  ;;    (concat "\addbibresource{" (expand-file-name it) "}\n"))
  ;;  bibtex-completion-bibliography)

  ;; increase latex preview size.
  ;;(setq org-format-latex-options
  ;;    (plist-put org-format-latex-options :scale 2.3))
  ;; scale latex equation preview according to frame char height
  (add-hook 'cnfonts-set-font-finish-hook
            (lambda (args)
              (setq org-format-latex-options
                    (plist-put org-format-latex-options :scale
                               (* 1.1 (/ (frame-char-height) 36.0))))))
  ;; preview latex equation with SVG image
  ;; [2024-04-25 Thu] kimim: use %F for input file,
  ;; because the relative path is not correct when in a symbol link folder.
  (add-to-list
   'org-preview-latex-process-alist
   '(xdvsvgm
     :progams
     ("xelatex" "dvisvgm")
     :discription "xdv > svg"
     :message
     "you need install the programs: xelatex and dvisvgm."
     :image-input-type "xdv"
     :image-output-type "svg"
     :image-size-adjust (1.3 . 1.3)
     :latex-compiler
     ("xelatex -no-pdf -shell-escape -output-directory=%o %F")
     :image-converter ("dvisvgm %F -n -b min -c %S -o %O")))
  (setq org-preview-latex-default-process 'xdvsvgm))

org to html page

(use-package ox-html
  :ensure org
  :functions (org-html-encode-plain-text
              org-html-close-tag
              f-read)
  :commands (org-html-publish-to-html)
  :config
  (setq org-html-validation-link nil)
  (defadvice org-html-paragraph (before fsh-org-html-paragraph-advice
                                        (paragraph contents info) activate)
    "Join consecutive Chinese lines into a single long line without
  unwanted space when exporting org-mode to html."
    (let ((fixed-contents)
          (orig-contents (ad-get-arg 1))
          (reg-han "[[:multibyte:]]"))
      (setq fixed-contents (replace-regexp-in-string
                            (concat "\\(" reg-han "\\) *\n *\\(" reg-han "\\)")
                            "\\1\\2" orig-contents))
      (ad-set-arg 1 fixed-contents)))
  ;; embed svg image to html file
  (defun org-html--format-image (source attributes info)
    "Return \"img\" tag with given SOURCE and ATTRIBUTES.
SOURCE is a string specifying the location of the image.
ATTRIBUTES is a plist, as returned by
`org-export-read-attribute'.  INFO is a plist used as
a communication channel."
    (if (string= "svg" (file-name-extension source))
        ;;(plist-get attributes :embed-svg)
        ;; remove width and height information, to fix image to the parent div
        (let ((svg-txt (replace-regexp-in-string
                         "<svg.*\\(width=\"\\w+\"\s+height=\"\\w+\"\\).*>"
                        ""
                        (f-read source)
                        nil nil 1)))
          (format
           "<div align=\"center\">%s</div>" svg-txt))
      (org-html-close-tag
       "img"
       (org-html--make-attribute-string
        (org-combine-plists
         (list :src source
               :alt (if (string-match-p
                         (concat "^" org-preview-latex-image-directory) source)
                        (org-html-encode-plain-text
                         (org-find-text-property-in-string 'org-latex-src source))
                      (file-name-nondirectory source)))
         (if (string= "svg" (file-name-extension source))
             (org-combine-plists '(:class "org-svg") attributes '(:fallback nil))
           attributes)))
       info))))

org to html slides

(use-package org-re-reveal
  :bind ("C-x r v" . org-re-reveal-export-to-html-and-browse)
  :config
  (use-package htmlize :ensure t)
  (setq org-re-reveal-root "https://cdn.jsdelivr.net/npm/reveal.js@3.9.2/")
  (setq org-re-reveal-theme "none")
  (setq org-re-reveal-width 1000)
  (setq org-re-reveal-height 750)
  (setq org-re-reveal-transition "none")
  (setq org-re-reveal-hlevel 2)
  (setq org-re-reveal-extra-css "./kimim.css"))

org link: page in pdf

(use-package org
  :functions org-supdf-open
  :config
  (org-link-set-parameters "supdf"
                           :follow #'org-supdf-open)

  (defun org-supdf-open (path)
    "Visit the pdf page"
    (let ((file-page (split-string path "::")))
      (cond
       ((eq system-type 'windows-nt)
        (progn
          (w32-shell-execute
           "open" (concat kimim/path-kimikit "sumatrapdf/sumatrapdf.exe")
           (concat "\"" (expand-file-name (car file-page))
                   "\" -page " (cadr file-page)))))))))

org link: onenote

New link to use Office Onenote.

(use-package org
  :functions org-onenote-open
  :config
  (org-link-set-parameters "onenote"
                           :follow #'org-onenote-open)

  (defun org-onenote-open (path)
    "Visit the onenote link"
    (cond
     ((eq system-type 'windows-nt)
      (progn
        (w32-shell-execute
         "open" (concat "onenote:" path))))
     ((eq window-system 'ns)
      (shell-command
       (replace-regexp-in-string
        "&" "\\\\&" (format "open onenote:%s" path)))))))

org publish to jekyll

(use-package o2jk
  :load-path (lambda ()
               (concat kimim/path-kimim-emacs
                       "site-lisp/o2jk"))
  :functions (o2jk-input-directory
              org-publish-cache-get-file-property
              org-ref-export-to-file-nomarks-noopen
              org-export-output-file-name
              org-ref-process-buffer
              org-export-expand-include-keyword)
  :commands (o2jk-publish
             o2jk-list-drafts
             o2jk-create-draft)
  :bind (("C-x m k" . o2jk-create-draft)
         ("C-x m l" . o2jk-list-posts))
  :custom ((o2jk-blog-author "kimim")
           (o2jk-source-directory "~/notes/kimi.im/_notes/_posts")
           (o2jk-jekyll-directory "~/notes/kimi.im/_posts")
           (o2jk-jekyll-drafts-dir "~/notes/_draft")
           (o2jk-jekyll-posts-dir ""))
  :config

  (require 'org-ref)

  (setq org-publish-project-alist
        `(("post"  ;; dynamic pages like blog articles
           :base-directory ,(o2jk-input-directory)
           :base-extension "org\\|txt"
           :publishing-directory ,(o2jk-output-directory)
           :publishing-function org-ref-html-publish-to-html
           :headline-levels 4
           :html-preamble t
           :recursive t
           :make-index t
           :html-extension "html"
           :body-only t)))
  ;; Very simplified version of org-ref-export-to from org-ref-export.el
  ;; that export to filename
  (defun org-ref-export-to-file-nomarks-noopen
      (backend filename &optional async subtreep visible-only body-only info)
    (org-export-with-buffer-copy
     (org-export-expand-include-keyword)
     (org-ref-process-buffer backend subtreep)
     (org-export-to-file backend filename
       async subtreep visible-only
       body-only info)))

  ;; org-html-publish-to-html from ox-html.el adapted to org-ref
  ;; Instead of org-export-to-file calls org-ref-export-to-file-nomarks-noopen
  (defun org-ref-html-publish-to-html (plist filename pub-dir)
    (unless (or (not pub-dir)
                (file-exists-p pub-dir))
      (make-directory pub-dir t))
    ;; Check if a buffer visiting FILENAME is already open.
    (let* ((org-inhibit-startup t)
           (visiting (find-buffer-visiting filename))
           (work-buffer (or visiting (find-file-noselect filename))))
      (unwind-protect
          (with-current-buffer work-buffer
            (let ((output (org-export-output-file-name ".html" nil pub-dir)))
              (org-ref-export-to-file-nomarks-noopen
               'html output
               nil nil nil (plist-get plist :body-only)
               (org-combine-plists
                plist
                `(:crossrefs
                  ,(org-publish-cache-get-file-property
                    ;; Normalize file names in cache.
                    (file-truename filename) :crossrefs nil t)
                  :filter-final-output
                  (org-publish--store-crossrefs
                   org-publish-collect-index
                   ,@(plist-get plist :filter-final-output)))))))))))

Note Taking

org-roam

Org-roam implements zettelkasten method [fn:16] used by famous German socialogist Niklas Luhmann[fn:17].

First you should install sqlite3, which is used to index the links.

Windows/MSYS2:

pacman -S mingw-w64-x86_64-sqlite3

Windows/Cygwin:

apt-cyg install sqlite3

sqlite3 is shipped in macOS by default.

(use-package org-roam
  :commands (kimim/non-cite-filter)
  :ensure t
  :custom
  (org-roam-directory kimim/path-notes)
  (org-roam-db-location (file-truename
                         (concat user-emacs-directory
                                 "org-roam.db")))
  (org-roam-link-auto-replace nil)
  (org-roam-file-extensions '("txt" "org"))
  (org-roam-node-display-template "${title:70}${tags:30}${refs}")
  (org-roam-capture-templates
   '(("d" "default" plain "%?"
      :if-new
      (file+head
       "%(concat (kimim/genfile-timestamp) \"${slug}.txt\")"
       "#+TITLE: ${title}\n")
      :unnarrowed t)))
  (org-roam-dailies-capture-templates
   '(("d" "default" plain "- /%<%H:%M>/ %?" :target
      (file+datetree "%<%Y>.org" :day))))
  (orb-preformat-keywords '("citekey"))
  :bind
  (("C-c n f" . (lambda ()
                  (interactive)
                  (org-roam-node-find
                   nil nil 'kimim/non-cite-filter)))
   ("C-c n F" . (lambda ()
                  (interactive)
                  (org-roam-node-find
                   nil nil 'kimim/cite-filter)))
   ("C-c n o" . kimim/ebdb-link-open-note)
   ("C-c n c" . org-roam-capture)
   ("C-c n j" . org-roam-dailies-capture-today)
   ("C-c n ." . org-roam-dailies-goto-today)
   ("C-c n r" . org-roam-ref-add)
   ("C-c n x" . org-roam-node-random)
   :map org-roam-mode-map
   (("C-c n l" . org-roam)
    ("C-c n g" . org-roam-graph))
   :map org-mode-map
   (("C-c n i" . (lambda ()
                   (interactive)
                   (org-roam-node-insert 'kimim/non-cite-filter)))
    ("C-c n I" . (lambda ()
                   (interactive)
                   (org-roam-node-insert 'kimim/cite-filter)))
    ("C-c n t" . org-roam-tag-add)
    ("C-c n a" . org-roam-alias-add)
    ("C-c n g" . org-id-get-create)
    ("C-c n b" . org-roam-buffer-toggle)))
  :config
  (add-to-list 'load-path
               (concat kimim/path-kimim-emacs
                       "site-lisp/"))
  (require 'org-roam-dailies)
  (require 'org-roam-bibtex)
  (require 'emacsql)
  (require 'kimim)
  ;; open org link in current window
  (add-to-list
   'org-link-frame-setup
   '(file . find-file))
  ;;(setq org-roam-v2-ack t)
  (setq emacsql-global-timeout 60) ;; default 30 seconds will timeout
  ;;(org-roam-db-autosync-enable)
  (defvar orb-templates
    '(("r" "reference" plain
       "#+ROAM_KEY: %^{citekey}\n\n%?"
       :target
       (file+head
        "references/%(concat (kimim/genfile-timestamp) \"${citekey}.txt\")"
        "#+title: ${title}\n")
       :unnarrowed t)))
  (defun kimim/cite-filter (node)
    (orb-get-node-citekey node))

  (defun kimim/non-cite-filter (node)
    (or
     (not (orb-get-node-citekey node))
     (-contains? (org-roam-node-tags node) "standard")
     (-contains? (org-roam-node-tags node) "terminology")))
  (advice-add 'org-roam-node-visit
              :after (lambda (&rest r) (reposition-window)))

  (advice-add
   #'orb--new-note :around
   (lambda (origin-fun &rest args)
     (let ((org-roam-capture-templates orb-templates))
       (apply origin-fun args))))

  (defun kimim/ebdb-link-open-note ()
    (interactive)
    (let* ((context (org-element-context))
           (name (buffer-substring
                  (org-element-property :contents-begin context)
                  (org-element-property :contents-end context)))
      (path (org-element-property :path context))
      ;; TODO: extend to citeref in the future
      (node (kimim/ebdb-note-exists-p path)))
    (if node
        (org-roam-node-open node)
      (kimim/ebdb-note-new uuid name)))))

org-roam-bibtex

It is useful to create reference notes with org-roam-bibtex. C-c C-z used in org-ref is calling orb-org-ref-edit-note to edit org-roam note.

(use-package org-roam-bibtex
  :functions (orb-get-node-citekey
              orb--new-note)
  :hook (bibtex-mode . org-roam-bibtex-mode)
  :diminish org-roam-bibtex-mode
  ;; use the original title captalization
  :custom (orb-bibtex-entry-get-value-function
           #'bibtex-completion-get-value))

org-transclusion

Add a virtual piece of other org file to current one.

(use-package org-transclusion
  :after org
  :custom (org-transclusion-include-first-section nil)
  :hook (org-mode . org-transclusion-add-all)
  :bind ("<f12>" . org-transclusion-add-all))

markdown mode

Markdown is widely used as plain text file format. Pandoc [fn:18] can be used to convert markdown file to html and other formats. We can download the latest version and put the binary file to system path, such as /usr/local/bin, and then set markdown-command to pandoc.

(use-package markdown-mode
  :mode ("\\.\\(?:md\\|markdown\\)\\'" . markdown-mode)
  :functions (s-starts-with?
              markdown--get-remote-image)
  :custom ((markdown-hide-urls t)
           (markdown-command "pandoc")
           (markdown-max-image-size
            `(,(* (+ fill-column 10)
                  (frame-char-width))
              . ,(* 2 (+ fill-column 10)
                    (frame-char-width)))))
  :hook ((markdown-mode . markdown-toggle-inline-images)
         (markdown-mode . olivetti-mode))
  :bind(:map
        markdown-mode-map
        ("C-<tab>" . outline-hide-entry)
        ("M-<up>" . markdown-move-up)
        ("M-<down>" . markdown-move-down)
        ("C-c C-x C-v" . markdown-toggle-inline-images))
  :config
  (setq markdown-fontify-code-blocks-natively t)
  (setq markdown-list-item-bullets
        '("" "" ""))
  (setq markdown-list-indent-width 2)
  (advice-add
   'markdown-fontify-list-items :override
   (lambda (last)
     (when (markdown-match-list-items last)
       (when (not (markdown-code-block-at-point-p (match-beginning 2)))
         (let* ((indent (length (match-string-no-properties 1)))
                (level (/ indent markdown-list-indent-width))
                ;; level = 0, 1, 2, ...
                (bullet (nth (mod level (length markdown-list-item-bullets))
                             markdown-list-item-bullets)))
           (add-text-properties
            (match-beginning 2) (match-end 2) '(face markdown-list-face))
           (cond
            ;; Unordered lists
            ((string-match-p "[\\*\\+-]" (match-string 2))
             (add-text-properties
              (match-beginning 2) (match-end 2) `(display ,bullet)))
            ;; Definition lists
            ((string-equal ":" (match-string 2))
             (let ((display-string
                    (char-to-string (markdown--first-displayable
                                     markdown-definition-display-char))))
               (add-text-properties (match-beginning 2) (match-end 2)
                                    `(display ,display-string)))))))
       t)))

  ;; to support azure markdown format with =WxH after file link
  (setq markdown-regex-link-inline
        "\\(?1:!\\)?\\(?2:\\[\\)\\(?3:\\^?\\(?:\\\\\\]\\|\
[^]]\\)*\\|\\)\\(?4:\\]\\)\\(?5:(\\)\\s-*\\(?6:[^)]*?\\)\\\
(?:\\s-+\\(?7:\"[^\"]*\"\\|=.*\\)\\)?\\s-*\\(?8:)\\)")

  (defun markdown--browse-url (url)
    (let* ((struct (url-generic-parse-url url))
           (full (url-fullness struct))
           (file url))
      (message file)
      ;; Parse URL, determine fullness, strip query string
      (setq file (car (url-path-and-query struct)))
      (let ((file (if (and (s-starts-with? "/" file)
                           (not (file-exists-p file)))
                      (concat (project-root (project-current t))
                              file)
                    file)))
        ;; Open full URLs in browser, files in Emacs
        (if full
            (browse-url url)
          (when (and file (> (length file) 0))
            (let ((link-file
                   (funcall markdown-translate-filename-function file)))
              (if (and markdown-open-image-command
                       (string-match-p
                        (image-file-name-regexp) link-file))
                  (if (functionp markdown-open-image-command)
                      (funcall markdown-open-image-command link-file)
                    (process-file
                     markdown-open-image-command nil nil nil link-file))
                (find-file-other-window link-file))))))))

  (defun markdown-display-inline-images ()
    "Add inline image overlays to image links in the buffer.
This can be toggled with `markdown-toggle-inline-images'
or \\[markdown-toggle-inline-images]."
    (interactive)
    (unless (display-images-p)
      (error "Cannot show images"))
    (save-excursion
      (save-restriction
        (widen)
        (goto-char (point-min))
        (while (re-search-forward markdown-regex-link-inline nil t)
          (let* ((start (match-beginning 0))
                 (imagep (match-beginning 1))
                 (end (match-end 0))
                 (file (match-string-no-properties 6))
                 ;; make file path if starts with /
                 (file (if (s-starts-with? "/" file)
                           (concat (project-root (project-current t))
                                   file)
                         file)))
            (when (and imagep
                       (not (zerop (length file))))
              (unless (file-exists-p file)
                (let* ((download-file
                        (funcall
                         markdown-translate-filename-function file))
                       (valid-url
                        (ignore-errors
                          (member
                           (downcase
                            (url-type
                             (url-generic-parse-url download-file)))
                           markdown-remote-image-protocols))))
                  (if (and markdown-display-remote-images valid-url)
                      (setq file (markdown--get-remote-image download-file))
                    (when (not valid-url)
                      ;; strip query parameter
                      (setq file (replace-regexp-in-string "?.+\\'" "" file))
                      (unless (file-exists-p file)
                        (setq file (url-unhex-string file)))))))
              (when (file-exists-p file)
                (let* ((abspath (if (file-name-absolute-p file)
                                    file
                                  (concat default-directory file)))
                       (image
                        (cond ((and markdown-max-image-size
                                    (image-type-available-p 'imagemagick))
                               (create-image
                                abspath 'imagemagick nil
                                :max-width (car markdown-max-image-size)
                                :max-height (cdr markdown-max-image-size)))
                              (markdown-max-image-size
                               (create-image
                                abspath nil nil
                                :max-width (car markdown-max-image-size)
                                :max-height (cdr markdown-max-image-size)))
                              (t (create-image abspath)))))
                  (when image
                    (let ((ov (make-overlay start end)))
                      (overlay-put ov 'display image)
                      (overlay-put ov 'face 'default)
                      (push ov markdown-inline-image-overlays))))))))))))

Task Management

Calendar

(when (not (boundp 'kimim/file-diary))
  (defvar kimim/file-diary (concat kimim/path-org "diary"))
  (if (not (file-exists-p kimim/file-diary))
      (write-region "" nil kimim/file-diary)))
(use-package calendar
  :defines (calendar-chinese-celestial-stem
            calendar-chinese-terrestrial-branch)
  :custom
  (diary-file kimim/file-diary)
  (calendar-latitude +30.16)
  (calendar-longitude +120.12)
  (calendar-location-name "Hangzhou")
  (calendar-remove-frame-by-deleting t)
  (calendar-week-start-day 1)
  (calendar-mark-holidays-flag t)
  (holiday-christian-holidays nil)
  (holiday-hebrew-holidays nil)
  (holiday-islamic-holidays nil)
  (holiday-solar-holidays nil)
  (holiday-bahai-holidays nil)
  (holiday-general-holidays
   '((holiday-fixed 1 1 "元旦")
     (holiday-float 5 0 2 "父親節")
     (holiday-float 6 0 3 "母親節")))
  (calendar-mark-diary-entries-flag t)
  (calendar-view-holidays-initially-flag nil)
  (calendar-chinese-celestial-stem
   ["" "" "" "" "" "" "" "" "" ""])
  (calendar-chinese-terrestrial-branch
   ["" "" "" "" "" "" "" "" "" "" "" ""])
  :config
  (require 'ebdb)
  ;; redefine Chinese sexagesimal format
  (defun calendar-chinese-sexagesimal-name (n)
    "The N-th name of the Chinese sexagesimal cycle.
N congruent to 1 gives the first name, N congruent to 2 gives the
second name, ..., N congruent to 60 gives the sixtieth name."
    (format
     "%s%s 年"
     (aref calendar-chinese-celestial-stem (% (1- n) 10))
     (aref calendar-chinese-terrestrial-branch (% (1- n) 12))))
  ;; https://www.emacswiki.org/emacs/CalendarWeekNumbers
  (defface calendar-iso-week-face
    '((t :inherit 'calendar-weekend-header
        :height 0.7 :bold t))
    "Week number.")
  (setq calendar-week-start-day 1
        calendar-intermonth-header
        '(propertize
          "  "
          'font-lock-face 'calendar-iso-week-face)
        calendar-intermonth-text
        '(propertize
          (format "%2d"
                  (car
                   (calendar-iso-from-absolute
                    (calendar-absolute-from-gregorian
                     (list month day year)))))
          'font-lock-face 'calendar-iso-week-face)))

org as GTD system

(use-package org-capture
  :ensure nil
  :bind (:map
         org-capture-mode-map
         ("C-c C-w" . kimim/org-capture-refile-reverse)
         ("C-c w" . org-capture-refile))
  :custom
  (org-capture-templates
   '(("c" "Capture" entry (file+headline "capture.org" "Inbox")
      "* %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")
     ("t" "TODO Task"    entry (file+headline "capture.org" "Inbox")
      "* TODO %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")
     ("s" "SCHED Task"    entry (file+headline "capture.org" "Inbox")
      "* SCHED %?\nSCHEDULED: %t\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")
     ("o" "OPEN Issue"  entry (file+headline "capture.org" "Inbox")
      "* OPEN %?\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")
     ("w" "WAIT Task"    entry (file+headline "capture.org" "Inbox")
      "* WAIT %?\nSCHEDULED: %t\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")
     ("h" "Habit"   entry (file+headline "global.org"   "Habit")
      "* %?  :habit:\n:PROPERTIES:\n:CAPTURED: %U\n:END:\n")))
  :config
  (defun kimim/org-capture-refile-reverse ()
    (interactive)
    (let ((org-reverse-note-order t))
      (org-capture-refile))))
(use-package org
  :functions (org-agenda-kill-all-agenda-buffers
              org-agenda-todo
              org-agenda-error
              org-agenda-check-no-diary)
;  :hook (org-agenda-mode . hl-line-mode)
  :defines org-agenda-mode-map
  :commands (org-toggle-office org-toggle-home org-toggle-home-or-office)
  :bind (("C-c a" . org-agenda)
         ("C-c c" . org-capture)
         :map org-agenda-mode-map
         ("o" . other-window)
         ("C-c M-s" . kimim/org-agenda-unschedule)
         ("C-c M-d" . kimim/org-agenda-undeadline)
         ("C-c C-x C-p" . org-previous-link)
         ("C-c C-x C-n" . org-next-link)
         ("C-c C-k" . org-agenda-kill-files)
         ("<C-left>"  . org-agenda-do-date-earlier)
         ("<C-right>" . org-agenda-do-date-later)
         ("<S-left>" . (lambda ()
                         (interactive)
                         (org-agenda-todo 'left)))
         ("<S-right>" . (lambda ()
                          (interactive)
                          (org-agenda-todo 'right)))
         ("C-c C-w" . org-agenda-refile-reverse)
         ("C-c w" . org-agenda-refile))
  :custom
  (org-directory kimim/path-org)
  (org-agenda-files
   (file-expand-wildcards (concat kimim/path-org "*.org*")))
  (org-tags-exclude-from-inheritance '("project" "category" "info"))
  (org-log-done t)
  (org-fontify-done-headline nil)
  (org-todo-repeat-to-state "REPEAT")
  (org-deadline-warning-days 2)
  (org-todo-keywords
   '(
     ;; for tasks
     (sequence "TODO(t!)" "SCHED(s)" "|" "DONE(d@/!)")
     ;; for risks, actions, problems
     (sequence "OPEN(o!)" "WAIT(w@/!)" "|" "CLOSE(c@/!)")
     (sequence "|" "SOMEDAY(m)")
     (sequence "|" "ABORT(a@/!)")
     (sequence "REPEAT(r)" "|")))
  (org-tag-alist
   '(("@office" . ?o) ("@home" . ?h)
     ("team" . ?t) ("leader" . ?l) ("risk" . ?k)
     ("reading" . ?r) ("writing" . ?w)
     ("project" . ?p) ("category" . ?c) ("info" . ?i)))
  (org-stuck-projects
   '("+LEVEL>=2-category-habit-info"
     ("TODO" "SCHED"  "DONE"
      "OPEN" "WAIT" "CLOSE"
      "ABORT" "SOMEDAY" "REPEAT")
     nil nil))
  (org-agenda-include-diary t)
  (org-agenda-span 2)
  (org-agenda-skip-scheduled-if-done t)
  (org-agenda-skip-deadline-if-done t)
  (org-agenda-custom-commands
   '(("j" "agenda"
      ((agenda "" ((org-agenda-span 2)
                   (org-agenda-show-log t)))
       (todo "TODO|OPEN"
             ((org-agenda-sorting-strategy '(priority-down))))
       (tags "+LEVEL>=2-category-habit-info
                /-TODO-SCHED-DONE-OPEN-WAIT-CLOSE-ABORT-SOMEDAY-REPEAT"
                ((org-agenda-sorting-strategy '(priority-down))))))
     ("t" todo "TODO|OPEN"
      ((org-agenda-sorting-strategy '(priority-down))))
     ("w" todo "SCHED|WAIT"
      ((org-agenda-sorting-strategy '(priority-down))))
     ("d" todo "TODO|SCHED|OPEN|WAIT"
      ((org-agenda-sorting-strategy '(priority-down))))
     ("f" todo "SOMEDAY"
      ((org-agenda-sorting-strategy '(priority-down))))
     ("h" tags "habit/-ABORT-CLOSE"
      ((org-agenda-sorting-strategy '(todo-state-down))))
     ("c" tags "clock"
      ((org-agenda-sorting-strategy '(priority-down))))))
  (org-refile-targets
   '(;; refile to maxlevel 1 of current file
     (nil . (:maxlevel . 1))
     ;; refile to maxlevel 1 of org-agenda-files
     (org-agenda-files . (:maxlevel . 1))
     ;; refile to item with `project' or `category'
     ;; tag in org-agenda-files
     (org-agenda-files . (:tag . "project"))
     (org-agenda-files . (:tag . "category"))))
  (org-habit-show-all-today t)
  :config
  (require 'org-capture)
  (require 'org-agenda)
  (add-hook 'kill-emacs-hook
            (lambda ()
              (org-clock-out nil t nil)
              (org-save-all-org-buffers)))
  ;; kill diary when exit agenda
  (advice-add 'org-agenda-exit
              :after (lambda () (kill-buffer "diary")))
  (diminish 'auto-fill-function)

  (defadvice org-schedule (after add-todo activate)
    (if (or (string= "OPEN" (org-get-todo-state))
            (string= "WAIT" (org-get-todo-state))
            (string= "CLOSE" (org-get-todo-state)))
        (org-todo "WAIT")
      (org-todo "SCHED")))

  (defadvice org-deadline (after add-todo activate)
    (if (or (string= "OPEN" (org-get-todo-state))
            (string= "WAIT" (org-get-todo-state))
            (string= "CLOSE" (org-get-todo-state)))
        (org-todo "WAIT")
      (org-todo "SCHED")))

  (defun kimim/org-agenda-unschedule ()
    (interactive)
    (org-agenda-schedule `(4))
    (org-agenda-todo 'left))

  (defun kimim/org-agenda-undeadline ()
    (interactive)
    (org-agenda-deadline `(4))
    (org-agenda-todo 'left))

  (defun org-agenda-kill-files ()
    (interactive)
    (org-agenda-kill-all-agenda-buffers)
    (mapcar (lambda (file)
              (if-let (buf (get-file-buffer file))
                  (kill-buffer buf)))
            org-agenda-files))

  (defun org-agenda-refile-reverse (&optional goto rfloc no-update)
    "Refile the item at point, reversely."
    (interactive "P")
    (cond
     ((member goto '(0 (64)))
      (org-refile-cache-clear))
     ((equal goto '(16))
      (org-refile-goto-last-stored))
     (t
      (let* ((buffer-orig (buffer-name))
             (marker (or (org-get-at-bol 'org-hd-marker)
                         (org-agenda-error)))
             (buffer (marker-buffer marker))
             ;; (pos (marker-position marker))
             (rfloc (or rfloc
                        (org-refile-get-location
                         (if goto "Goto" "Refile to") buffer
                         org-refile-allow-creating-parent-nodes))))
        (with-current-buffer buffer
          (org-with-wide-buffer
           (goto-char marker)
           (let ((org-agenda-buffer-name buffer-orig))
             (org-remove-subtree-entries-from-agenda))
           (org-refile-reverse goto buffer rfloc))))
      (unless no-update (org-agenda-redo))))))

org-pomodoro

Type C-x m m on agenda task to invoke org-pomodoro. When an pomodoro is completed, a posframe showing break countdown in large font.

(use-package org-pomodoro
  :after org
  :custom ((org-pomodoro-audio-player "mpg123")
           (org-pomodoro-format "(*) %s"))
  :bind ("C-x m m" . org-pomodoro)
  :config
  (require 'posframe)

  (defun kimim/org-pomodoro-tick-hook ()
    (if (or (eq org-pomodoro-state :short-break)
            (eq org-pomodoro-state :long-break))
        (posframe-show
         "*pomodoro*"
         :string (propertize
                  (cadr org-pomodoro-mode-line)
                  'face
                  '(:height 400 :inherit org-pomodoro-mode-line-break))
         :poshandler 'posframe-poshandler-frame-center
         :timeout 10)
      (posframe-delete "*pomodoro*")))

  (add-hook 'org-pomodoro-tick-hook
            #'kimim/org-pomodoro-tick-hook)

  (defun kimim/org-pomodoro-finished-action ()
    (raise-frame))

  (add-hook 'org-pomodoro-finished-hook
            #'kimim/org-pomodoro-finished-action))

Mail and Contacts

EBDB - a replacement for BBDB, as contact management

(use-package ebdb
  :functions ebdb-gethash
  :commands ebdb ebdb-mail-aliases
  :custom
  (ebdb-mua-pop-up nil)
  :bind (:map
         ebdb-mode-map
         ("C-c C-z" . kimim/ebdb-note-find)
         ("w i" . kimim/ebdb-citekey))
  :config
  (setq ebdb-sources (concat kimim/path-org "ebdb.gpg"))
  (setq ebdb-i18n-countries-pref-scripts
        '(("**" . chn)))
  (require 'ebdb-gnus)
  (require 'ebdb-message)
  (require 'ebdb-org)
  (add-hook 'message-setup-hook 'ebdb-mail-aliases)
  (setq org-link-make-description-function
        (lambda (link desc)
          (let* ((link-content (split-string link ":"))
                 (key (car link-content))
                 (link-str (cadr link-content))
                 (link-uuid (cadr (split-string link-str "/"))))
            (if desc
                desc
              (if (string= "ebdb" key)
                  (ebdb-record-name-string
                   (ebdb-gethash link-uuid 'uuid)))))))

  (defvar kimim/ebdb-notes-cache nil
    "Cache of EBDB notes.")

  (defun kimim/ebdb-get-db-cite-refs ()
    "Get a list of `cite` refs from Org Roam database."
    (let* ((types "ebdb")
           (refs (org-roam-db-query
                  [:select [ref nodes:file id pos title type]
                           :from refs
                           :left-join nodes
                           :on (= refs:node-id nodes:id)
                           :where (= type $s1)]
                  types))
           result)
      (dolist (ref refs result)
        (push (-interleave '(:ref :file :id :pos :title :type) ref) result))))

  (defun kimim/ebdb-make-notes-cache ()
    "Update ORB notes hash table `kimim/ebdb-notes-cache'."
    (let* ((db-entries (kimim/ebdb-get-db-cite-refs))
           (size (round (/ (length db-entries) 0.8125))) ;; ht oversize
           (ht (make-hash-table :test #'equal :size size)))
      (dolist (entry db-entries)
        (puthash (plist-get entry :ref)
                 (org-roam-node-create
                  :id (plist-get entry :id)
                  :file (plist-get entry :file)
                  :title (plist-get entry :title)
                  :point (plist-get entry :pos))
                 ht))
      (setq kimim/ebdb-notes-cache ht)))

  (defun kimim/ebdb-note-exists-p (uuid)
    "Check if a note exists whose :ROAM_REFS is uuid.
Return Org Roam node or nil."
    (gethash uuid (kimim/ebdb-make-notes-cache)))

  (defun kimim/ebdb-citekey ()
    "Get people citekey"
    (interactive)
    (if-let* ((record (ebdb-current-record))
              (name (ebdb-record-name-string record))
              (uuid (ebdb-record-uuid record))
              (citekey-ref (format "[[ebdb:uuid/%s][%s]]" uuid name)))
        (kill-new citekey-ref)))

  (defun kimim/ebdb-note-new (uuid name)
    "Create people note."
    (if-let* ((citekey-ref (format "[[ebdb:uuid/%s][%s]]" uuid name))
              (title name)
              (node (org-roam-node-create
                     :title title
                     :file (concat kimim/path-notes
                                   "people/"
                                   (kimim/genfile-timestamp)
                                   (s-downcase
                                    (s-join
                                     "_"
                                     (s-split-words title)))
                                    ".txt"))))
        (org-roam-capture-
         :node node
         :info (list :ref citekey-ref))
      (user-error "Abort")))

  (defun kimim/ebdb-note-find ()
    "Find or create people note."
    (interactive)
    (let* ((record (ebdb-current-record))
           (name (ebdb-record-name-string record))
           (uuid (ebdb-record-uuid record))
           (ebdb-ref (concat "uuid/" uuid))
           (node (kimim/ebdb-note-exists-p ebdb-ref)))
      (if node
          (org-roam-node-open node)
        (kimim/ebdb-note-new uuid name)))))

erc

;; erc settings
(use-package erc
  :functions erc-autojoin-enable
  :commands erc
  :custom
  (erc-autojoin-channels-alist
   '(("irc.freenode.net" "#emacs")))
  (erc-hide-list '("JOIN" "PART" "QUIT"))
  :config
  (require 'erc-join)
  (erc-autojoin-enable)
  (setq erc-default-server "irc.freenode.net"))

GNUS dired

(use-package gnus-dired
  :ensure nil
  :commands (turn-on-gnus-dired-mode)
  :config
  ;; make the `gnus-dired-mail-buffers' function also work on
  ;; message-mode derived modes, such as mu4e-compose-mode
  (defun gnus-dired-mail-buffers ()
    "Return a list of active message buffers."
    (let (buffers)
      (save-current-buffer
        (dolist (buffer (buffer-list t))
          (set-buffer buffer)
          (when (and (derived-mode-p 'message-mode)
                     (null message-sent-message-via))
            (push (buffer-name buffer) buffers))))
      (nreverse buffers)))
  (setq gnus-dired-mail-mode 'mu4e-user-agent))

mu4e

(use-package sendmail
  :ensure nil
  :custom
  (mail-user-agent 'sendmail-user-agent)
  (mail-signature nil)
  (mail-self-blind t)
  (mail-signature-file (concat kimim/path-emacs "signature.txt")))
(use-package mu-cite
  :commands (mu-cite-original)
  :config
  (setq mu-cite-top-format '("On " date ", " from " wrote:\n\n"))
  (setq mu-cite-prefix-format '(" > ")))
(eval-and-compile
  (defun mu4e-load-path ()
    (cond ((eq system-type 'darwin)
           "/usr/local/Cellar/mu/1.0_1/share/emacs/site-lisp/mu/mu4e")
          ((eq system-type 'windows-nt)
           "/usr/local/share/emacs/site-lisp/mu4e")
          ((eq system-type 'gnu/linux)
           "/usr/local/share/emacs/site-lisp/mu4e/"))))

(use-package mu4e
  :ensure nil
  :functions (mu4e-compose-reply
              mu4e~view-quit-buffer)
  :defines (mu4e-html2text-command
            mu4e-mu-binary
            mu4e-get-mail-command
            mu4e-update-interval
            mu4e-hide-index-messages
            mu4e-use-fancy-chars
            mu4e-view-show-images
            mu4e-view-fields
            mu4e-headers-fields
            mu4e-compose-cite-function
            mu4e-compose-reply-recipients
            mu4e-headers-mode-map
            mu4e-compose-mode-map
            mu4e-view-mode-map
            shr-color-visible-luminance-min
            shr-color-visible-distance-min)
  :custom
  (mu4e-compose-reply-recipients 'sender)
  (mu4e-compose-signature-auto-include nil)
  :commands (mu4e mu4e-compose-new)
  :bind (
         :map mu4e-headers-mode-map
         ("r" . kimim/mu4e-compose-reply-sender)
         ("R" . kimim/mu4e-compose-reply-all)
         ("f" . kimim/mu4e~view-quit-buffer)
         :map mu4e-compose-mode-map
         ("<C-tab>" . message-tab)
         :map mu4e-view-mode-map
         ("<home>" . move-beginning-of-line)
         ("<end>" . move-end-of-line)
         ("r" . kimim/mu4e-compose-reply-sender)
         ("R" . kimim/mu4e-compose-reply-all))
  :load-path (lambda () (list (mu4e-load-path)))
  :config
  (require 'sendmail)
  ;; turn html email to lighter color in dark theme
  (require 'mu4e-contrib)
  (setq mu4e-html2text-command 'mu4e-shr2text)
  (setq shr-color-visible-luminance-min 60)
  (setq shr-color-visible-distance-min 5)
  (setq shr-use-colors nil)
  (advice-add #'shr-colorize-region :around (defun shr-no-colourise-region (&rest ignore)))

  (require 'org-mu4e) ;; capture link
  (add-to-list 'Info-additional-directory-list "/usr/local/share/info")
  (setq mu4e-mu-binary "/usr/local/bin/mu")
  ;; (cond ((eq system-type 'gnu/linux)
  ;;        (setq mu4e-mu-binary "/snap/bin/mu")))
  (setq mail-user-agent 'mu4e-user-agent)
  ;; Fetch mail by offlineimap
  (setq mu4e-get-mail-command "offlineimap -c ~/.offlineimaprc -u quiet")
  ;; Fetch mail in 60 sec interval
  (setq mu4e-update-interval 300)
  ;; hide indexing messages from minibuffer
  (setq mu4e-hide-index-messages t)
  (setq mu4e-use-fancy-chars nil)
  (setq mu4e-view-show-images t)
  (setq mu4e-view-fields
        '(:subject :from :to :cc :date :mailing-list
                   :attachments :signature :decryption))
  (setq mu4e-headers-fields
        '( (:human-date    .   12)
           (:flags         .    6)
           (:from          .   22)
           (:subject       .   nil)))
  (setq mu4e-compose-cite-function 'mu-cite-original)
  (add-hook 'mu4e-view-mode-hook 'visual-line-mode)
  (add-hook 'mu4e-compose-mode-hook 'kimim/mail-setup)
  (add-hook 'mu4e-compose-mode-hook 'orgalist-mode)
  (add-hook 'mu4e-compose-mode-hook (lambda ()
                                      (auto-fill-mode -1)))
  (defun kimim/mu4e~view-quit-buffer ()
    (interactive)
    (when (get-buffer "*mu4e-view*")
      (switch-to-buffer "*mu4e-view*")
      (mu4e~view-quit-buffer)))

  (defun kimim/mu4e-compose-reply-sender ()
    (interactive)
    (set (make-local-variable 'mu4e-compose-reply-recipients) 'sender)
    (mu4e-compose-reply))

  (defun kimim/mu4e-compose-reply-all ()
    (interactive)
    (set (make-local-variable 'mu4e-compose-reply-recipients) 'all)
    (mu4e-compose-reply)))

Utilities

restclient

(use-package restclient
  :mode ("\\.http\\'" . restclient-mode)
  :bind (:map restclient-mode-map
         ("C-c C-c" . restclient-http-send-current-stay-in-window)
         ("C-c C-v" . restclient-http-send-current)))

Encryption

Sometimes, you need to encrypt some secret files, setting epa-pinentry-mode to loopback will prompt password inside minibuffer, while not show a dialog for it.

And we also cache the symmetric key in the same

(use-package epa
  :ensure nil
  :custom
  (epa-pinentry-mode 'loopback)
  (epa-file-cache-passphrase-for-symmetric-encryption t))

Viewing Documents

doc-view-mode can view many kind of documents, such as PDF, PS and images. You should install postscript in cygwin.

(use-package doc-view
  :custom
  (doc-view-continuous t)
  (doc-view-image-width 500)
  (doc-view-resolution 300))

Footnotes

[fn:1] http://www.literateprogramming.com/

[fn:2] https://orgmode.org/

[fn:3] https://www.msys2.org/

[fn:4] http://cygwin.com/

[fn:5] https://cygwin.com/setup-x86_64.exe

[fn:6] http://kimi.im/2021-01-28-emacs-inside-manjaro-wsl2-windows

[fn:7] http://brew.sh/

[fn:8] https://www.gnu.org/software/emacs/manual/html_node/emacs/Packages.html

[fn:9] syl20bnr/spacemacs#381

[fn:10] https://github.com/Fuco1/smartparens

[fn:11] https://clojure.org/

[fn:12] https://cider.mx/

[fn:13] https://github.com/jorgenschaefer/elpy

[fn:14] https://github.com/ralesi/ahk-mode

[fn:15] https://mermaid-js.github.io/

[fn:16] https://zettelkasten.de/

[fn:17] https://en.wikipedia.org/wiki/Niklas_Luhmann

[fn:18] https://pandoc.org/

About

my emacs init configuration files

http://kimi.im/kimim-emacs

License:GNU General Public License v2.0


Languages

Language:Emacs Lisp 99.1%Language:Makefile 0.5%Language:Batchfile 0.4%