kimim-emacs
kimim-emacs copied to clipboard
my emacs init configuration files
#+TITLE: README #+LATEX_CLASS: article #+OPTIONS: toc:nil #+STARTUP: show2levels
[[https://travis-ci.org/kimim/kimim-emacs][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:
#+begin_src shell pacman -S git mingw-w64-x86_64-emacs #+end_src
** 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.
#+begin_src bat C:\cygwin64\bin\run.exe emacs-w32 #+end_src
You can also add =emacs --daemon= in your scheduled task, and change the target field as:
#+begin_src bat C:\cygwin64\bin\run.exe emacsclient -c #+end_src
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:
#+begin_src shell sudo snap install emacs --classic #+end_src
It is also very quick to add emacs in ArchLinux/Manjaro:
#+begin_src shell pacman -S emacs #+end_src
** macOS
For Apple macOS, most UNIX tools are installed already. You can use homebrew [fn:7] to install additional application if it is missing.
#+begin_src shell
/bin/bash -c
"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew tap railwaycat/emacsmacport
brew install emacs-mac
#+end_src
- Preparation ** Get emacs config
You can follow below shell commands to prepare the =emacs= configuration files and folders:
#+begin_src shell
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 ~ #+end_src
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.
#+begin_src emacs-lisp (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)) #+end_src
Then you can execute =emacs= to bootstrap itself.
** chemacs2
Another way to try this configure out is to use [[https://github.com/plexus/chemacs2][chemacs2]].
Clone to ~~/kimim-emacs~
#+begin_src shell git clone https://github.com/kimim/kimim-emacs ~/kimim-emacs #+end_src
Add to chemacs2 profile file ~~/.emacs.profile.el~
#+begin_src emacs-lisp :tangle no (("default" . ((user-emacs-directory . "~/.emacs.default"))) ("kimim" . ((user-emacs-directory . "~/kimim-emacs")))) #+end_src
Start emacs with this config:
#+begin_src shell emacs --with-profile kimim #+end_src
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~.
#+begin_src emacs-lisp :tangle no (defvar kimim/path-kimim-emacs "~/.emacs.kimim/") #+end_src
** 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.
#+begin_src emacs-lisp (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")) #+end_src
Then append exec-path to PATH:
#+begin_src emacs-lisp (setenv "PATH" (concat (mapconcat #'identity exec-path path-separator) (getenv "PATH"))) #+end_src
For Windows/MSYS64, we need to modify =executable-find= to locate shell scripts:
#+begin_src emacs-lisp
(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))))
#+end_src
** Language
I prefer to use English/UTF-8 as default language environment.
#+begin_src emacs-lisp (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))) #+end_src
** global key map
Define new command prefix for keys such as "C-x m f", "C-x m v".
#+begin_src emacs-lisp (define-prefix-command 'ctl-x-m-map) (global-set-key "\C-xm" 'ctl-x-m-map) #+end_src
- 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.
#+begin_src emacs-lisp ;; 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) #+end_src
- 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.
#+begin_src emacs-lisp (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 (or (eq window-system 'w32) (eq window-system 'x) (eq window-system 'pgtk)) (menu-bar-mode 0))) ((eq window-system nil) (menu-bar-mode 0)))) #+end_src
** 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=.
#+begin_src emacs-lisp (use-package cnfonts :bind (("C-+" . cnfonts-increase-fontsize) ("C--" . cnfonts-decrease-fontsize) ("C-=" . cnfonts-increase-fontsize) ("C-0" . cnfonts-reset-fontsize))) #+end_src
#+begin_src emacs-lisp (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) #+end_src
** Frames
Customize the frame title to display buffer file name.
#+begin_src emacs-lisp (setq frame-title-format '((:eval (buffer-name)))) ;; don't expand minibuffer in other frame (setq minibuffer-follows-selected-frame nil) #+end_src
** Mode Line
Display date and time, but do not display system load.
#+begin_src emacs-lisp (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)) #+end_src
Show (line, column) numbers in mode line:
#+begin_src emacs-lisp (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)) #+end_src
** COMMENT Celestial modeline
#+begin_quote 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 #+end_quote
#+begin_src emacs-lisp (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 ""))) #+end_src
Add sunrise/sunset info to modeline text description.
#+begin_src emacs-lisp (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)) #+end_src
** awesome tray
~awesome-tray~ adds an overlay to minibuffer and hides mode line to save window space.
#+begin_src emacs-lisp (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")))))) #+end_src
** path in headerline
When minimalistic emacs is on with awesome tray, we sometimes need to lookup buffer file name with header line.
#+begin_src emacs-lisp (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)))) #+end_src
** 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.
#+begin_src emacs-lisp (use-package rainbow-mode :diminish rainbow-mode) #+end_src
Rainbow-delimiters is a "rainbow parentheses"-like mode which highlights parentheses, brackets, and braces according to their depth.
#+begin_src emacs-lisp (use-package rainbow-delimiters :diminish rainbow-delimiters :hook (prog-mode . rainbow-delimiters-mode)) #+end_src
Toggle Font-Lock mode in all buffers.
#+begin_src emacs-lisp (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)) #+end_src
Use kimim-light as default theme.
#+begin_src emacs-lisp (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))) #+end_src
** Image
Always set white background color for transparent images to display clearly in dark color theme.
#+begin_src emacs-lisp (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)) #+end_src
** posframe
#+begin_src emacs-lisp (use-package posframe) #+end_src
** Other Visual Element
#+begin_src emacs-lisp (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) #+end_src
#+begin_src emacs-lisp (use-package hi-lock :custom (hi-lock-auto-select-face t)) #+end_src
- 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.
#+begin_src emacs-lisp
(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
(("
- Help & References ** Info
#+begin_src emacs-lisp (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")) #+end_src
** Youdao dictionary
Search dictionary with Ctrl+F3 by youdao dictionary.
#+begin_src emacs-lisp
(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)
("
(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))))) #+end_src
** sdcv
A local dictionary tool is useful when network connection has problems. You can get dictionary files from [[https://github.com/manateelazycat/lazycat-emacs/tree/master/site-lisp/sdcv-dict][lazycat-emacs]], or [[http://download.huzheng.org/dict.org/][huzheng.org]].
#+begin_src emacs-lisp (use-package sdcv :load-path (lambda () (concat kimim/path-kimim-emacs "site-lisp/sdcv")) :bind ("C-M-z" . sdcv-search-input) ("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 '("xdict-en-zh" "xdict-zh-en")) (setq sdcv-dictionary-complete-list '("oxford" "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)) #+end_src
Need to install ~mpg123~ or ~mpv~ to play the pronunciations.
#+begin_src shell pacman -S mingw-w64-ucrt-x86_64-mpg123 #+end_src
** fanyi.el
A translator extension written by [[https://condy0919.github.io][Youmu]].
I added ~kimim/fanyi-dwim-add-sdcv~ as ~:after~ advice for ~fanyi-dwim~ to look up local dictionary with help from ~sdcv~.
#+begin_src emacs-lisp
(use-package fanyi
:ensure t
:bind
(("C-x m ." . kimim/fanyi-dwim)
("C-x m ," . kimim/fanyi-dwim-local)
("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)
("
(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-dwim-local () (interactive) (setq-local fanyi-providers nil) (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)) #+end_src
** Wordreference
#+begin_src emacs-lisp (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)))) #+end_src
** dict.leo.org Translate word between English and German.
#+begin_src emacs-lisp (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")))) #+end_src
** reverso Translate between almost any languages.
#+begin_src emacs-lisp (use-package reverso :commands (reverso reverso-translate)) #+end_src
** eldoc-box
Show eldoc in a childframe box.
#+begin_src emacs-lisp (use-package eldoc-box :diminish eldoc-box-hover-mode ;;:hook (eglot-managed-mode . eldoc-box-hover-mode) :custom ((eldoc-box-offset '(10 40 10)) (eldoc-box-max-pixel-width 1200) (eldoc-box-max-pixel-height 800))) #+end_src
Increase right margin from default 16 to 40 to remove some overlap.
- Editing ** emacs-rime
#+begin_src emacs-lisp (use-package rime :bind (:map rime-mode-map ("C-SPC" . 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)) (rime-share-data-dir "~/Library/Rime") :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))))) #+end_src
** sis Emacs smart input source ([[https://github.com/laishulu/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-
#+begin_src emacs-lisp :results none (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)) #+end_src
** General
#+begin_src emacs-lisp (use-package autorevert :ensure nil :diminish auto-revert-mode) #+end_src
#+begin_src emacs-lisp (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) ;; enable clipboard btw for wslg linux and host ;; https://www.lukas-barth.net/blog/emacs-wsl-copy-clipboard/ (setq select-active-regions 'only) (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" "~")) #+end_src
** 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.
#+begin_src emacs-lisp (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)) #+end_src
** Highlight
Highlight current line in window systems, but disable this in terminal. Because the line highlight will cause the terminal blinking.
#+begin_src emacs-lisp (use-package hl-line :if window-system :config (global-hl-line-mode -1)) #+end_src
** 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.
#+begin_src emacs-lisp (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 xref-find-definitions xref-go-back)) (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)) #+end_src
** ispell
[[https://git.savannah.gnu.org/cgit/emacs.git/tree/etc/NEWS.26#n1155][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 [[https://github.com/Eason0210][Eason0210]] at [[https://emacs-china.org/t/emacs-enchant-spell-checker/24035][Emacs China]] on [2023-07-02 Sun].
Install ~enchant~.
Windows/msys64:
#+begin_src shell pacman -S mingw64/mingw-w64-x86_64-enchant #+end_src
macOS
#+begin_src shell sudo port install enchant2
or
brew install enchant #+end_src
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 [[https://abiword.github.io/enchant/src/enchant.html][enchant manual]].
#+begin_src shell export ENCHANT_CONFIG_DIR=~/.config/enchant/ #+end_src
#+begin_src emacs-lisp (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)))) #+end_src
** flyspell
Check spell on the fly.
#+begin_src emacs-lisp (use-package flyspell :diminish flyspell-mode :hook (;;(prog-mode . flyspell-prog-mode) (org-mode . flyspell-mode))) #+end_src
** olivetti
#+begin_src emacs-lisp
(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
magit-diff-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))))))
#+end_src
** Chinese word segmentation
#+begin_src emacs-lisp (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")) ((eq system-type 'gnu/linux) (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)) #+end_src
** wraplish
Automatically add space between Chinese and English, and more.
#+begin_src emacs-lisp (use-package wraplish :commands wraplish-mode :after text-mode :load-path (lambda () (concat kimim/path-kimim-emacs "site-lisp/wraplish"))) #+end_src
Before enable wraplish, you need to install python, pip and epc.
#+begin_src shell pacman -S mingw-w64-x86_64-python mingw-w64-x86_64-python3-pip pip install epc #+end_src
** tempel
Use ~tempel~ to expand template.
#+begin_src emacs-lisp
;; Configure Tempel
(use-package tempel
:bind (("M-+" . tempel-complete)
("M-=" . tempel-complete)
("M-*" . tempel-insert)
:map tempel-map
("M-RET" . tempel-done)
("S-
(use-package tempel-collection) #+end_src
** ediff
#+begin_src emacs-lisp (use-package ediff :ensure nil :custom (ediff-split-window-function 'split-window-horizontally) :config ;; https://emacs.stackexchange.com/questions/9322 (setq ediff-split-window-function (quote split-window-horizontally))
(defun disable-y-or-n-p (orig-fun &rest args) (cl-letf (((symbol-function 'y-or-n-p) (lambda (prompt) t))) (apply orig-fun args)))
(advice-add 'ediff-quit :around #'disable-y-or-n-p)) #+end_src
- Controlling ** Window and Frame
By enabling ~winner-mode~, you can restore to previous window
configuration by typing ~C-c M-
#+begin_src emacs-lisp
(use-package winner
;; restore windows configuration, built-in package
:commands winner-mode
:bind
("C-x M-
When type ~C-x m w~ it will create a new frame with the default frame configuration.
#+begin_src emacs-lisp (use-package frame :ensure nil :defer 1 :bind ("C-x m w" . make-frame)) #+end_src
preserve the point in screen during scrolling looks nice (see [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Scrolling.html][scrolling]]). scroll slowly with touchpad, thus we adjust the scroll amount.
#+begin_src emacs-lisp (setq scroll-preserve-screen-position t) (setq mouse-wheel-scroll-amount '(0.01)) #+end_src
** Move Frame
Move frame to one of the nine grids on the screen with ~C-x y <N>~ keys.
#+begin_src emacs-lisp (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)) #+end_src
** Windmove
#+begin_src emacs-lisp
(use-package windmove
:bind
("C-x
** Command
Display key candidates when you typed part key prefix with ~which-key-mode~.
#+begin_src emacs-lisp ;; 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)) #+end_src
List recent used commands with ~smex~:
#+begin_src emacs-lisp
;; smex will list the recent function on top of the cmd list (use-package smex :commands (smex) :config (smex-initialize)) #+end_src
** Key Frequency
We will use =keyfreq= to record the frequency of the key typing, and get a frequency report by =M-x keyfreq-show=.
#+begin_src emacs-lisp (use-package keyfreq :custom (keyfreq-file "~/.emacs.d/emacs.keyfreq") :config (keyfreq-mode +1) (keyfreq-autosave-mode +1)) #+end_src
** eshell
#+begin_src emacs-lisp (use-package eshell) #+end_src
** vterm #+begin_src emacs-lisp (use-package vterm :bind (:map vterm-mode-map ("C-h" . vterm-send-backspace))) #+end_src
** Navigation
#+begin_src emacs-lisp (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)))) #+end_src
~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~.
#+begin_src emacs-lisp
(use-package bm
:bind (("C-x m t" . bm-toggle)
("C-x m s" . bm-show-all)
("C-x m
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~).
#+begin_src emacs-lisp (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))) #+end_src
** vundo
#+begin_src emacs-lisp (use-package vundo :bind ("C-x u" . vundo)) #+end_src
** image-mode scrolling #+begin_src emacs-lisp (use-package image-mode :ensure nil :bind (:map image-mode-map ("j" . (lambda ()(interactive) (image-next-line 10))) ("k" . (lambda ()(interactive) (image-previous-line 10))))) #+end_src
- Search and Finding ** isearch in minibuffer
Emacs default ~isearch~ does not allow key binding in minibuffer, ~isearch-mb~ enhances at this aspect.
#+begin_src emacs-lisp (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)) #+end_src ** imenu & imenu-anywhere
=imenu= is used to navigate the function definitions in current buffer.
#+begin_src emacs-lisp (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)) #+end_src
** ripgrep: a fast command line search tool #+begin_src emacs-lisp (use-package ripgrep :bind ("C-x g" . ripgrep-regexp)) #+end_src
** search from web
#+begin_src emacs-lisp (use-package eww :custom (eww-search-prefix "https://cn.bing.com/search?q=")) #+end_src
** recentf
#+begin_src emacs-lisp (use-package recentf :config (recentf-mode)) #+end_src ** avy #+begin_src emacs-lisp (use-package avy :bind ("C-x m g" . avy-goto-word-or-subword-1)) #+end_src
- File Management ** delete files
To avoid accidentally delete files, let emacs move the deleted file to trash.
#+begin_src emacs-lisp (setq delete-by-moving-to-trash t) #+end_src
** dired
#+begin_src emacs-lisp
(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)
("
(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"))) #+end_src
** dired-recent
Keep a list of recently visited directories. Then we can quickly revisit them.
#+begin_src emacs-lisp (use-package dired-recent :config (dired-recent-mode 1)) #+end_src
** dired-efap
dired-efap, Edit file at point, can be used to rename file name at the point:
#+begin_src emacs-lisp (use-package dired-efap :commands dired-efap) #+end_src
** dired-narrow
~M-n~ will prompt for strings to narrow the files in current dired buffer.
#+begin_src emacs-lisp (use-package dired-narrow :commands dired-narrow) #+end_src
** dired-filter
#+begin_src emacs-lisp (use-package dired-filter :diminish dired-filter-mode) #+end_src
** 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~.
#+begin_src emacs-lisp
(use-package ibuffer
:bind (("C-x C-b" . ibuffer-other-window)
:map ibuffer-mode-map
("
- Completion ** marginalia Rich annotations in the minibuffer.
#+begin_src emacs-lisp (use-package marginalia :init (marginalia-mode)) #+end_src
** consult
#+begin_src emacs-lisp
(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
("
;; 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)) #+end_src
** abbrev
#+begin_src emacs-lisp (diminish 'abbrev-mode) #+end_src
** corfu
#+begin_src emacs-lisp (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)) #+end_src
#+begin_src emacs-lisp
(use-package corfu
:defer 1
:bind
(:map
corfu-map
("
#+begin_src emacs-lisp (use-package corfu-prescient :commands (corfu-prescient-mode) :config (setq corfu-prescient-override-sorting t)) #+end_src
** cape
#+begin_src emacs-lisp
(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)) #+end_src
- Programming General ** Project
~project-find-file~ (~C-x p f~) can find files of current project, indicated by git or other version control information.
#+begin_src emacs-lisp :results none (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"))))) #+end_src
** Compiling
#+begin_src emacs-lisp
(setq next-error-recenter 20)
(setq compilation-scroll-output t)
(bind-key "C-
** Version Control
Bind ~magit~ to ~C-x p m~ with the same prefix of ~project~, as they have strong relationship.
#+begin_src emacs-lisp :results none
(use-package magit
:bind (("C-x p m" . magit-status-here)
(:map magit-revision-mode-map)
("C-
Following error will reported when using magit to commit changes:
#+BEGIN_QUOTE server-ensure-safe-dir: The directory ‘~/.emacs.d/server’ is unsafe #+END_QUOTE
The solution is to change the owner of =~/.emacs.d/server= [fn:9]
#+BEGIN_QUOTE 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. #+END_QUOTE
** Use tempel for LSP
#+begin_src emacs-lisp (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))) #+end_src
** LSP with Eglot
~eglot~ is simple and built-in LSP support in emacs.
#+begin_src emacs-lisp (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-implementation) ("C-x l b" . eldoc) ("C-x l l" . eldoc-box-help-at-point)) :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))))) #+end_src
** Parenthesis
~smartparens-mode~[fn:10] is a general purpose mode for dealing with parenthesis. We define some keys for it:
#+begin_src emacs-lisp :results none
(use-package smartparens
:bind (:map
smartparens-mode-map
("C-" 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))))
#+end_src
** COMMENT parinfer-rust-mode #+begin_src emacs-lisp (use-package parinfer-rust-mode :commands parinfer-rust-mode :hook ((clojure-mode clojurec-mode clojurescript-mode) . parinfer-rust-mode)) #+end_src
** tree-sitter
#+begin_src emacs-lisp (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)) :config ;; assoc bb-mode to clojure (setq tree-sitter-major-mode-language-alist (put-alist 'bb-mode 'clojure tree-sitter-major-mode-language-alist)))
#+end_src
** ts-fold
#+begin_src emacs-lisp
(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-
** COMMENT Code folding
With ~yafolding-mode~, you can:
- toggle the code folding with ~yafolding-toggle-element~ (~C-
~) - toggle global folding with ~yafolding-toggle-all~ (~C-M-
~)
#+begin_src emacs-lisp (use-package yafolding :hook (prog-mode . yafolding-mode)) #+end_src
** static code analysis
#+begin_src emacs-lisp (use-package flycheck :commands (global-flycheck-mode) :custom (flycheck-global-modes '(not org-mode))) #+end_src
** eldoc
#+begin_src emacs-lisp (use-package eldoc :hook (prog-mode . eldoc-mode) :diminish eldoc-mode :custom (eldoc-echo-area-use-multiline-p nil)) #+end_src
** cmake mode
#+begin_src emacs-lisp (use-package cmake-mode :mode ("CMakeLists\.txt\'" . cmake-mode)) #+end_src
** xref
#+begin_src emacs-lisp (use-package xref :ensure nil :custom (xref-after-return-hook '(xref-pulse-momentarily recenter)) :bind (:map prog-mode-map ("C-." . xref-find-references))) #+end_src
- Programming Language ** C and C++
#+begin_src emacs-lisp (use-package clang-format) #+End_src
#+begin_src emacs-lisp (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)) #+end_src
#+begin_src emacs-lisp
(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-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 . /)))))
#+end_src
#+begin_src emacs-lisp (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))) #+end_src
#+begin_src emacs-lisp (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)) #+end_src
** C#
#+begin_src emacs-lisp (use-package csharp-mode :ensure nil :hook ((csharp-mode . tree-sitter-mode) (csharp-mode . tree-sitter-hl-mode)) :mode ("\.cs\'" . csharp-mode)) #+end_src
** Clojure
Clojure[fn:11] is a lisp over JVM. Emm, I like it.
#+begin_src emacs-lisp (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)) #+end_src
#+begin_src emacs-lisp (use-package clojure-mode-extra-font-locking) #+end_src
*** clj-kondo
Install with npm:
#+begin_src shell npm install -g clj-kondo #+end_src
#+begin_src emacs-lisp (use-package flycheck-clj-kondo) #+end_src
*** Cider
Cider[fn:12] extends Emacs with support for interactive programming in Clojure.
#+begin_src emacs-lisp (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"")))))) #+end_src
#+begin_src emacs-lisp (use-package ob-clojure :ensure org :custom (org-babel-clojure-backend 'cider) :config (add-to-list 'org-src-lang-modes '("clojure" . clojure))) #+end_src
** Babashka Download ~bb~ from https://github.com/babashka/babashka/releases and put ~bb~ to execute PATH.
#+begin_src emacs-lisp (use-package ob-bb :load-path (lambda () (concat kimim/path-kimim-emacs "site-lisp/ob-bb"))) #+end_src
** Python
Python development configuration is quite easy. =elpy= [fn:13] is used here:
#+begin_src emacs-lisp ;; (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")) #+end_src
Following =python= package is required according to =elpy= mannual:
#+begin_src shell 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 #+end_src
** Rust
The easiest way to install rust is to run following script:
#+begin_src shell curl https://sh.rustup.rs -sSf | sh #+end_src
#+begin_src emacs-lisp (use-package rustic :hook (rustic-mode . (lambda () (set (make-local-variable 'compile-command) "cargo run"))) :custom (rustic-lsp-client 'eglot)) #+end_src
** R ESS #+begin_src emacs-lisp (use-package ess :custom (inferior-ess-r-program "r")) #+end_src
** Swift
#+begin_src emacs-lisp (use-package swift-mode :mode ("\.swift\'" . swift-mode)) #+end_src
** TypeScript
#+begin_src emacs-lisp (use-package typescript-mode :hook ((typescript-mode . tree-sitter-mode) (typescript-mode . tree-sitter-hl-mode) (typescript-mode . eglot-ensure))) #+end_src
** json
#+begin_src emacs-lisp :results none (use-package json-mode :mode ("\.json\'" . json-mode) :hook ((json-mode . tree-sitter-mode) (json-mode . tree-sitter-hl-mode))) #+end_src
** Golang Open =.go= file with go-mode.
#+begin_src emacs-lisp (use-package go-mode :mode ("\.go\'" . go-mode)) #+end_src
** Docker file
Some dockerfile is not end with =.dockerfile=, so lets guess:
#+begin_src emacs-lisp (use-package dockerfile-mode :mode ("\dockerfile\'" . dockerfile-mode)) #+end_src
** Emacs lisp
#+begin_src emacs-lisp (use-package elisp-mode :ensure nil :mode ("\.el\'" . emacs-lisp-mode) :config (define-derived-mode lisp-interaction-mode emacs-lisp-mode "ᴧ")) #+end_src
** AutoHotKey
=ahk-mode= developed by Rich Alesi[fn:14]
#+begin_src emacs-lisp (use-package ahk-mode :mode ("\.ahk\'" . ahk-mode)) #+end_src
** yaml mode
#+begin_src emacs-lisp (use-package yaml-mode :mode ("\.yml\'" . yaml-mode) :bind (:map yaml-mode-map ("\C-m" . newline-and-indent))) #+end_src
** shell
#+begin_src emacs-lisp (use-package shell :mode ("\.sh\'" . shell-script-mode)) #+end_src
#+begin_src emacs-lisp (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))) #+end_src
** powershell
#+begin_src emacs-lisp (use-package powershell :mode ("\.ps1\'" . powershell-mode)) #+end_src
** solidity
Major mode to edit ethereum solidity smart contract code.
#+begin_src emacs-lisp (use-package solidity-mode :mode ("\.sol\'" . solidity-mode)) #+end_src
** lua and fennel
#+begin_src emacs-lisp (use-package lua-mode) (use-package fennel-mode) #+end_src
- 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: #+begin_src shell brew install graphviz #+end_src
Download =plantuml.jar= from https://plantuml.com/download, and put it to some place and assign ~plantuml-jar-path~ to there.
#+begin_src emacs-lisp (use-package plantuml-mode :mode ("\.puml\'" . plantuml-mode) :custom (plantuml-output-type "svg") (plantuml-default-exec-mode 'jar) (plantuml-jar-path (or ;; use system plantuml.jar when exists, path in my gentoo (when (f-exists? "/usr/share/plantuml/lib/plantuml.jar") "/usr/share/plantuml/lib/plantuml.jar") (expand-file-name (concat kimim/path-kimikit "plantuml/plantuml.jar")))) (plantuml-executable-args '("-charset" "utf-8"))) #+end_src
#+begin_src emacs-lisp (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))) #+end_src
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.
#+begin_src emacs-lisp (use-package image-mode :ensure nil :custom (image-auto-resize 'fit-window) :config (add-to-list 'auto-mode-alist '("\.svg\'" . image-mode))) #+end_src
** graphviz & dot
~dot~ is used to draw simple diagram in code. You need to install ~graphviz~:
#+begin_src shell pacman -S mingw-w64-ucrt-x86_64-graphviz #+end_src
#+begin_src emacs-lisp (use-package graphviz-dot-mode :ensure t :custom (graphviz-dot-preview-extension "svg")) #+end_src
** ditaa
#+begin_src emacs-lisp (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))) #+end_src
** Mermaid
Mermaid [fn:15] is another js based diagramming and charting tool. To use it inside orgmode, mermaid-cli should be installed:
#+begin_src shell yarn add @mermaid-js/mermaid-cli #+end_src
~mmdc~ will be installed at ~~/node_modules/.bin/mmdc~. Then we just setup ~ob-mermaid~ and ~mermaid-mode~ for babel evaluation and editing.
#+begin_src emacs-lisp (use-package ob-mermaid :custom (ob-mermaid-cli-path "~/node_modules/.bin/mmdc.cmd") :config (add-to-list 'org-babel-load-languages '(mermaid . t))) #+end_src
#+begin_src emacs-lisp (use-package mermaid-mode :mode ("\.mermaid\'" . mermaid-mode)) #+end_src
** iscroll
scroll images line by line.
#+begin_src emacs-lisp (use-package iscroll :diminish iscroll-mode :hook ((org-mode markdown-mode) . iscroll-mode)) #+end_src
** chatu
#+begin_src emacs-lisp (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)) #+end_src
- Music ** lilypond
#+begin_src emacs-lisp (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)) #+end_src
#+begin_src emacs-lisp (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))
#+end_src
- 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.
#+begin_src emacs-lisp :results value silent
(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-isearch-minor-mode)
(pdf-view-mode . pdf-view-themed-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)
("
(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))) #+end_src
** pdf-view-store
To remember visited pages of PDF files.
#+begin_src emacs-lisp (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)) #+end_src
** pdf-view-pagemark
Add indicator of remaining text when scrolling PDF pages.
#+begin_src emacs-lisp (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)) #+end_src
** org-ref
#+begin_src emacs-lisp (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"))))) #+end_src
#+begin_src emacs-lisp (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"))) #+end_src
There is a built-in =bibtex-mode= to manage references. We can extend it to support more functions from =org-ref=:
#+begin_src emacs-lisp (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)) #+end_src
** nov: reading epub books
#+begin_src emacs-lisp
(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
("
(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)))) #+end_src
- Orgmode ** org general setting
#+begin_src emacs-lisp (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-export-with-tags nil) (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))) #+end_src
** org-appear
#+begin_src emacs-lisp (use-package org-appear :commands (org-appear-mode) :ensure t :custom (org-appear-autolinks nil) :hook (org-mode . org-appear-mode)) #+end_src
** org-indent
Add advice to ~org-indent--compute-prefixes~ to remove needless indent spaces before plain text line.
#+begin_src emacs-lisp (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)) #+end_src
** org-modern
#+begin_src emacs-lisp
(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))))))))
#+end_src
** pretty-symbols-in-org-mode
#+begin_src emacs-lisp (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))) #+end_src
** 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.
#+begin_src emacs-lisp (use-package org-inline-pdf :hook (org-mode . org-inline-pdf-mode)) #+end_src
** orgalist
#+begin_src emacs-lisp (use-package orgalist :commands (orgalist-mode)) #+end_src
** org for writing
#+begin_src emacs-lisp (use-package org-download :commands (org-download-enable) :custom (org-download-heading nil) :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 () ".")) #+end_src
#+begin_src emacs-lisp (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)) #+end_src
** org with source code
#+begin_src emacs-lisp (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))) #+end_src
** org exporting
When exporting, do not export with author and date.
#+begin_src emacs-lisp (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-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") ("o" . "corollary") ("?" . "question") ("q" . "quote") ("Q" . "quotation") ("r" . "result") ("s" . "src") ("t" . "theorem") ("v" . "verse"))) (org-html-head "") :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)))))))))))))
#+end_src
*** org to pdf
LaTeX is required to convert =org-mode= to PDF.
For MacOS:
#+begin_src shell brew cask install mactex-no-gui #+end_src
For Windows, there are three options:
- download and install CTEX from http://www.ctex.org
- install texlive-collection in cygwin
#+begin_src shell
apt-cyg install texlive-collection-xetex
texlive-collection-latex
texlive-collection-fontsrecommended #+end_src - download and install texlive from [[http://tug.org/texlive/acquire-netinstall.html][tug.org]]
For Linux, download texlive install pacakge from [[http://mirror.ctan.org/systems/texlive/tlnet/install-tl-unx.tar.gz][ctan.org]]
#+begin_src shell tar zxvf install-tl-unx.tar.gz cd install-tl-20200908/ sudo ./install-tl #+end_src
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=):
#+begin_src shell
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
#+end_src
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.=
#+begin_src shell curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py python get-pip.py #+end_src
Get =pygments= with =pip=:
#+begin_src shell pip install pygments #+end_src
For Ubuntu Linux:
#+begin_src shell sudo apt install python3-pygments #+end_src
#+begin_src emacs-lisp (use-package latex-classes :load-path (lambda () (concat kimim/path-kimim-emacs "site-lisp")) :config (require 'bibtex-completion)) #+end_src
#+begin_src emacs-lisp
(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}"))
;; emacs cannot handle relative path correctly in OneDrive folder
;; use full-name instead. %f -> %F
(org-babel-latex-pdf-svg-process
"inkscape
--pdf-poppler
--export-area-drawing
--export-text-to-path
--export-plain-svg
--export-filename=%O
%F")
: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 (cond ((eq system-type 'darwin) (add-hook 'cnfonts-set-font-finish-hook (lambda (args) (setq org-format-latex-options (plist-put org-format-latex-options :scale (/ (frame-char-height) 9.0)))))) ((eq system-type 'windows-nt) (add-hook 'cnfonts-set-font-finish-hook (lambda (args) (setq org-format-latex-options (plist-put org-format-latex-options :scale (/ (frame-char-height) 30.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)) #+end_src
*** org to html page
#+begin_src emacs-lisp (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" 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)))) #+end_src
*** org to html slides
#+begin_src emacs-lisp (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/[email protected]/") (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")) #+end_src
** org link: page in pdf
#+begin_src emacs-lisp (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))))))))) #+end_src
** org link: onenote
New link to use Office Onenote.
#+begin_src emacs-lisp (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))))))) #+end_src
** org publish to jekyll
#+begin_src emacs-lisp (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-source)) :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))))))))))) #+end_src
- 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:
#+begin_src shell pacman -S mingw-w64-x86_64-sqlite3 #+end_src
Windows/Cygwin:
#+begin_src shell apt-cyg install sqlite3 #+end_src
sqlite3 is shipped in macOS by default.
#+begin_src emacs-lisp (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:100}${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) ("C-c M-z" . kimim/org-roam-open-ref-pdf))) :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))))
(defun kimim/org-roam-open-ref-pdf () (interactive) (kimim/org-ref-open-pdf-action (car (cdr (split-string (cdr (assoc "ROAM_REFS" (org-roam-node-properties (org-roam-node-at-point)))) ":")))))) #+end_src
** 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.
#+begin_src emacs-lisp (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)) #+end_src
** org-transclusion
Add a virtual piece of other org file to current one.
#+begin_src emacs-lisp
(use-package org-transclusion
:after org
:custom (org-transclusion-include-first-section nil)
:hook (org-mode . org-transclusion-add-all)
:bind ("
** 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 [[https://github.com/jgm/pandoc/releases/latest][latest version]] and put the binary file to system path, such as ~/usr/local/bin~, and then set ~markdown-command~ to ~pandoc~.
#+begin_src emacs-lisp
(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)))))))))))) #+end_src
- Task Management ** Calendar
#+begin_src emacs-lisp (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))) #+end_src
#+begin_src emacs-lisp (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))) #+end_src
** org as GTD system
#+begin_src emacs-lisp (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)))) #+end_src
#+begin_src emacs-lisp
(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)))))) #+end_src
** 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.
#+begin_src emacs-lisp (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)) #+end_src
- Mail and Contacts ** EBDB - a replacement for BBDB, as contact management
#+begin_src emacs-lisp (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))))) #+end_src
** erc
#+begin_src emacs-lisp ;; 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")) #+end_src
** GNUS dired
#+begin_src emacs-lisp (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)) #+end_src
** mu4e
#+begin_src emacs-lisp (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"))) #+end_src
#+begin_src emacs-lisp (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 '(" > "))) #+end_src
#+begin_src emacs-lisp (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
("
(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))) #+end_src
- Utilities ** restclient
#+begin_src emacs-lisp (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))) #+end_src
** COMMENT Reading News
#+begin_src emacs-lisp
(use-package elfeed
:functions elfeed-show-refresh
:commands (elfeed)
:custom
(elfeed-curl-timeout 100)
(browse-url-browser-function 'browse-url-default-browser)
(elfeed-feeds
'(("http://kimi.im/atom.xml" blog)
("https://blog.tecosaur.com/tmio/rss.xml" Org)
("www.chinadaily.com.cn/rss/bizchina_rss.xml" CN)
;; from https://www.foxnews.com/story/foxnews-com-rss-feeds
("http://feeds.foxnews.com/foxnews/world" EN)
("http://feeds.foxnews.com/foxnews/scitech" EN Tech)
;; from https://www.lefigaro.fr/rss
("https://www.lefigaro.fr/rss/figaro_secteur_high-tech.xml" FR Hightech)
("https://www.lefigaro.fr/rss/figaro_management.xml" FR Management)
;; from https://www.zeit.de/hilfe/hilfe#rss
("http://newsfeed.zeit.de/wissen/index" DE Wissen)
("http://newsfeed.zeit.de/kultur/index" DE Kultur)))
:bind (:map elfeed-search-mode-map
("<SPC>" . scroll-up-command)
("
** 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 #+begin_src emacs-lisp (use-package epa :ensure nil :custom (epa-pinentry-mode 'loopback) (epa-file-cache-passphrase-for-symmetric-encryption t)) #+end_src
** Viewing Documents
doc-view-mode can view many kind of documents, such as PDF, PS and images. You should install postscript in cygwin.
#+begin_src emacs-lisp (use-package doc-view :custom (doc-view-continuous t) (doc-view-image-width 500) (doc-view-resolution 300)) #+end_src
- 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] https://github.com/syl20bnr/spacemacs/issues/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/