UP | HOME

Emacs Notes

目录

DATE: 2019-02-03

1 Style

查看 worg 排版格式。

2 Introduction

2.1 What is Emacs

Emacs (EDitor MACros, ˈiːmæks)是一个文本编辑器家族,具备可扩展特征。 GNU Emacs是使用最为广泛的Emacs实现,具备可扩展、可定制、自文档化、实时显示特点, 支持Linux、Windows、Mac平台。GNU Emacs主页的介绍是:

An extensible, customizable, free/libre text editor  and more.

At its core is an interpreter for Emacs Lisp, a dialect of the
Lisp programming language with extensions to support text editing.

Emacs在终端与X视窗(图形界面/Windows窗口)下都有良好的使用体验。对于经常在终端 工作的群体,有着更高的回报。

2.2 Emacs and VI

20世纪70年代是编辑器的井喷期,1972年发布的Emacs与1976年发布的VI使用最为广泛。 Emacs与VI(VIM)都有较强的可定制性、趣味性,熟练掌握后可改善工作效率。然而,对比 其他编辑器,Emacs与VIM都有着更为陡峭的学习曲线与高昂的学习成本。

VI是Visual缩写,VIM是VI Improved。VI是POSIX Standard的一部分,几乎在所有的LINUX 发行版上都存在。VIM主页对自己的描述是:

Vim is a highly configurable text editor built to make creating and changing any kind of text very efficient. It is included as "vi" with most UNIX systems and with Apple OS X.

Vim is rock stable and is continuously being developed to become even better. Among its features are:

  • persistent, multi-level undo tree
  • extensive plugin system
  • support for hundreds of programming languages and file formats
  • powerful search and replace
  • integrates with many tools

Emacs与VIM有不同的设计理念:

  • 基于Lisp解释器的有着无限的可扩展性,Emacs可以用来编辑文本、调试程序、 浏览网页、管理日程、收发邮件、阅读文档(info/man/PDF/EPUB)、播放音乐视频, 一切应有尽有;对于Emacser而言,Emacs是一个的操作系统,原本的操作系统是它的 启动程序。
  • VIM持续专注在成为更好的编辑器上,支持命令模式、编辑模式、拷贝模式,有非常 高效的输入模式。

Emacs对比VIM有三个劣势:

  • 启动速度慢:一个配置好的Emacs可能要数秒钟才能拉起来,而VIM的启动时间基本 无感知;
  • VIM命令模式的高效输入:VIM的命令输入模式太高效了,梦想中的编辑器是VIM输入 与Emacs可扩展的集合;
  • 普遍性:几乎所有Linux系统都默认安装VIM(至少安装VI);

启动速度和VIM命令模式的高效输入比较好解决:

  • 启动速度慢:Emacs支持Daemon模式,后台启动一个常驻服务,编辑时调用emacsclient 编辑文件,这样操作还能带来额外的优势:编辑过的文件一直在内存中,下次启动 emacsclient时,还可以接着做undo/redo操作;给emacsclient起个别名e,调用 e filename 高效启动,还可胜过vim;
  • VIM命令模式的高效输入:Emacs可以通过定义一套keymap模拟VIM的命令模式,我的配置 Browse模式(github y-browse.el)激活map,和VIM命令模式的输入效果完全一致,例如, 输入 f 前移, b 后移, n 到下一行, p 到前一行;

普遍性是个无解难题。虽然可以用emacs远程编辑应付,但事实上很难处理多级跳板的 场景。EMACSer偶尔还是需要使用VI做些简单的修改。

相对EMACS而言,VIM在可扩展性方面的短板,基本无解。

我是在使用VIM 10年之后,因为Org mode切换到Emacs的。对我而言,Emacs除了可扩展性 优势,更重要的是好玩:

  • 在Emacs下写代码/提交;
  • 用Lisp写各种花样的批处理任务;
  • 用Emacs Orgmode写个人主页并发布;
  • 用Emacs Orgmode GTD维护TODO任务;
  • 用Emacs看PDF;
  • 随自己的心意定制Emacs的行为;
  • 尝试用Emacs浏览网页、听音乐、收发邮件(因为不好用之后放弃了,但很好玩);

在猿界,猿们称Emacs为神之编辑器,称VIM为编辑器之神。Emacs与VI之间的对抗称为 编辑器之战(参见WIKIWIKI CN)。

2.3 Goals

对于我而言,对编辑器核心需求,按照重要性排序如下:

  • 稳定:不会Crash退出,不会破坏文件,不会丢失文件内容;
  • 快速:输入立即响应,不卡顿;
  • 兼容:X视窗与终端操作兼容;
  • 绑定:对常用操作绑定快捷键,文本内进行各种移动与编辑,同时,对越高频的操作, 赋予越方便的快捷键;
  • 语法:支持多种编程语言语法识别与检查,并进行着色显示;
  • 补齐:在输入少量前缀字符时,聪明的识别补齐,或者提供多个候选;
  • 主题:根据主题(theme)进行显示颜色定制;

本系列文档(视频)展示我是怎样配置Emacs达成上述需求的。在Emacs下做成一件事情的 方法不止一种,欢迎读者留言给出更好的建议。:)

2.4 Install

2.4.2 Source Install

Download source from git then compile:

~$ git clone git://git.savannah.gnu.org/emacs.git
~$ cd emacs; git checkout emacs-26.1.91
~$ ./autogen.sh
~$ ./configure --without-pop --with-mailutils
~$ make -j && sudo make install

2.5 Modifier Key

Emacs重度依赖各种修饰键(Modifier Key)的组合,因此被调侃Emacs是 Esc, Meta, Alt, Ctrl, Shift/Super 的首字母缩写1

Meta与Alt是历史产物,当前的标准PC键盘中没有Meta,Emacs默认把Alt映射为Meta。

考虑终端与X视窗兼容性,基本不使用Shift, Super(Win), Hyper(Mac Command)

2.6 Config

Emacs默认配置文件是 $HOME/.emacs.d/init.el ,启动时没有 $HOME/.emacs.d/ 目录 则自动创建。配置文件随时间逐渐变多,有时会引入一些错误需要回滚,建议用git进行 版本管理。GitHub或GitLab都提供免费的托管服务,我当前使用的配置文件在Github上: https://github.com/yygcode/.emacs.d

2.7 Start

终端执行 emacs 或者在启动菜单查找 emacs 启动,按下 C-x C-c 退出。

For Newbie:

启动Emacs后键入 C-h t 打开Emacs tutorial,仔细阅读,并按照文档内容提示进行 一些输入。重新键入 C-h t 恢复。如果Emacs提示如下信息,输入y:

You have changed the Tutorial buffer.  Revert it? (y or n)

3 Packagement

3.1 Package system

3.2 org-babel

  • 使用org-babel-load-file加载org文件中的lisp代码;
  • 使用org mode literate programming(文学编程)配置emacs;

3.3 Config

See https://github.com/yygcode/.emacs.d/blob/master/lisp/y-package.el. 相关环境变量:

Env Description Example
EMACS_NO_MIRROR 不使用镜像站点;如果使用proxy,或不在GFW范围,设置为1 export EMACS_NO_MIRROR=1
EMACS_MIRROR_HTTP 如果系统不支持HTTPS,设置为1 export EMACS_MIRROR_HTTP=1
http_proxy, https_proxy 代理地址 export https_proxy=http://127.0.0.1:8888

4 Keybinding

4.1 Keybind Basic

本文不描述emacs keybind的基础知识,参阅Elisp KeyMapsEmacs Manual Keybinding, 以及Mastering-key-bindings-emacs等资料。

4.2 Keybind Rules

Emacs在图形界面(GUI)和终端(Terminal)下对修饰键(Modifier Key)的支持差别巨大:

  • GUI界面下使用28位的字符,Meta/Control/Shift/Hyper/Super/Alt分别占据 \(2^{27} ~ 2^{22}\) 位,详细参见Elisp Keyboard Events
  • Terminal有多个标准(例如VT100/VT102),这些标准在ASCII字符集的基础上,定义 特定的扩展序列(转义序列),实现颜色设置、光标移动等功能。其中Ctrl与字母的组合 编码为ASCII控制字符 0x00~0x1F ,Meta与字母的组合转义为 ESC character 。 例如, C-a 实际是ASCII码 SOH HEX 01=,而 =M-xESC x 等效,都是序列 0x1B 0x78 。Shift/Hyper/Super/Alt都无法传送到终端。

上述描述中提到的Meta,在键盘上标注为Alt,而这里提到的Alt,在一般键盘上不存在。

好消息是Emacs隐藏了GUI和Terminal下修饰键带来的差异,例如,用户只需要定义 C-a ,而不用关注究竟是GUI输入的Code \(0x2000061\) 还是Terminal下的 \(0x01\) 。 考虑我的主要工作内容是连接到远程终端写代码,配置要满足GUI和Terminal的兼容性, 因此只使用 Ctrl/Meta/ESC 修饰键。

部分键序(Key Sequence)会被终端拦截,例如默认配置 C-z 终端挂起程序,我习惯的 tmux 配置 C-z 是tmux prefix key, C-z C-n 切换到下一个panel。这些键序都 无法到达emacs被终端直接消费。

快捷键的绑定要遵循最小立异原则,这里有两点考虑:要相信大多数的默认配置是经过 长时间考量的合适配置;最小差异化配置可以让其他人更容易上手。

键盘输入与手掌位置、键所在位置相关。部分区域键输入更快,连续两次输入同一个键 比输入两个不同的键更快。例如,输入 C-x C-xC-g C-g 要更快,输入 C-f C-fC-f C-v 要更快。

现在可以整理出keybind的基本原则了:

  • 保持GUI与Terminal下的兼容;
  • 不使用终端拦截的键序;
  • 高频输入提供输入更快、更短的键序(key sequence)。
  • 尽量与默认配置保持一致;

上述规则按照优先级排序。

4.3 Keybind Description

  • Reserved C-z
  • Change C-o to switch window, rebind open-line(C-o) with C-x C-x C-o, Rebind exchange-point-and-mark(C-x C-x) to C-x C-x C-x

4.3.1 与默认配置

4.4 ASCII Controll Characters

DEC HEX OCT SHOW KEY Function or Key
0 0 0 ^@ C-2, [email protected], C-SPC set-mark-command
1 1 1 ^A C-a move-beginning-of-line
2 2 2 ^B C-b backward-char
3 3 3 ^C C-c <Key-prefix-custom>
4 4 4 ^D C-d delete-char
5 5 5 ^E C-e move-end-of-line
6 6 6 ^F C-f forward-char
7 7 7 ^G C-g keyboard-quit
8 8 10 ^H C-h <Key-prefix-help>
9 9 11 ^I C-i TAB Key
10 A 12 ^J C-j LF key <line feed>
11 B 13 ^K C-k kill-line
12 C 14 ^L C-l recenter-top-bottom
13 D 15 ^M C-m CR Key <carriage return>
14 E 16 ^N C-n next-line
15 F 17 ^O C-o other-window
16 10 20 ^P C-p previous-line
17 11 21 ^Q C-q quoted-insert
18 12 22 ^R C-r backward-search
19 13 23 ^S C-s forward-search
20 14 24 ^T C-t transpose-char
21 15 25 ^U C-u universal-argument
22 16 26 ^V C-v scroll-up-command
23 17 27 ^W C-w kill-region
24 18 30 ^X C-x <Key-prefix-executor>
25 19 31 ^Y C-y yank
26 1A 32 ^Z C-z suspend-frame
27 1B 33 ^[ C-[ ESC key the same with Alt+key
28 1C 34 ^\ C-\ toggle-input-method
29 1D 35 ^] C-] abort-recusive-edit
30 1E 36 ^^ C-^  
31 1F 37 ^_ C-_ undo-tree-undo

*

4.5 How Computer process Keyboard Input

  • Scan code: hardware generates one scan code when a physical key press, and generate another scan code when the key released. Use showkey -s to get the detail:
~$ sudo showkey -s
0x01 0x9c

0x1c and 0x9c will be generated if ESC press and release.

  • Key Code: translate scan code to key press and release. e.g., ESC will generate keycode 1 press and release. Use showkey -k to get the detail:
~$ sudo showkey -k
press any key (program terminates 10s after last keypress)...
keycode   1 press
keycode   1 release
^[
  • ascii code translate to ascii code. e.g.: ESC to  27 0x33 0x1b.
~$ sudo showkey -a
Press any keys - Ctrl-D will terminate this program

^[       27 0033 0x1b

4.6 Most Important Keybinds

Keybind Default Function Description
C-x C-c save-buffers-kill-terminal Offer to save each buffer, then exit
M-x execute-extended-command Execute Command
C-g keyboard-quit Abort current command
C-u universal-argument  
C-x C-f find-file Switch to a buffer visiting file
C-x C-s save-buffer Save current buffer in visited file if modified
C-x C-w write-file write current buffer to filename
C-f forward-char  
C-b backward-char  
M-f forward-word  
M-b backward-word  
C-p previous-line  
C-n next-line  
C-x C-h describe-prefix-bindings Describe the bindings of the prefi
C-h C-c describe-copying Copyright
C-h C-f view-emacs-FAQ  
C-h C-h help-for-help Help command description
C-h b describe-bindings Display a buffer showing a list of all all defined keys
C-h c describe-key-briefly Print the name of the function KEY invokes
C-h k describe-key  
C-h l view-lossage Display last few input keystrokes and the commands run
C-h m describe-mode  
C-h o describe-symbols  
C-h i info Enter documentation browser
C-h r info-emacs-manual Emacs manual
C-h t help-with-tutorial Emacs tutorial
C-h w where-is Print message listing key sequences that invoke the command
C-h p finder-by-keyword Find packages matching a given keyword
C-h s describe-syntax Describe the syntax specifications in the syntax table of BUFFER
C-h v describe-variable Display the full documentation of VARIABLE

4.7 Customize keybind

(global-set-key "\C-b" #'backward-char)
(define-key global-map (kbd "M-f") #'forward-word)

4.8 Lookup keybinds

  • 查看所有的keybinds: C-h b
  • 查看以 C-x 为前缀的keybinds: C-x C-h
  • 查看C-f的详细描述: C-h k C-f
  • 查看M-x的概要描述: C-h c M-x
Key Seq Description
C-h b List all defined keys
C-h c Print the function name KEY invokes
C-h k Print the documentation of the function invoked by KEY
C-x C-h Describe the bindings of the prefix used to reach this command.

4.9 Cheatsheet

5 FAQ

5.1 Show pressed key

  • M-x: command-log-mode
  • M-x: clm/open-command-log-buffer

5.2 GUI下无法输入中文

  • 启动需要设置支持的term和LC_CTYPE;
  • TERM=xterm LC_CTYPE=zh_CN.UTF-8 emacs

5.3 启动daemon,terminal下拉起emacsclient,输入中文时光标跳跃,且会把左边框弄乱

  • 同上,是因为语言支持的原因
  • TERM=xterm LC_CTYPE=zh_CN.UTF-8 emacs –daemon

6 My Config

6.2 init.el

;;; ~/.emacs.d/init.el --- Emacs Initialization Entry

;; Copyright (C) 2017-2020 yonggang.yyg<[email protected]>

;; Author: yonggang.yyg<[email protected]>
;; Maintainer: yonggang.yyg<[email protected]>
;; Keyword: Emacs Initialization Entry
;; Homepage: https://ycode.org; http://ycode.org;
;; URL: http://github.com/yygcode/.emacs.d

;; SPDX-License-Identifier: GPL-2.0-or-later

;;; Commentary:

;;; Code:

(defconst y/startup-begin-seconds (float-time))

(when (version< emacs-version "26.3")
  (warn "The config was not tested before emacs 26.3"))

(setq load-prefer-newer t
      gc-cons-threshold (* 128 1024 1024))

(prefer-coding-system 'utf-8-unix)
(when (string-equal current-language-environment "Chinese-GBK")
  (setq default-process-coding-system '(utf-8 . chinese-gbk)))

(defconst y/lisp-directory
  (expand-file-name "lisp" user-emacs-directory)
  "Custom LISP file directory.")
(add-to-list 'load-path y/lisp-directory)

(require 'y-auxiliary)
(require 'y-package)

(defconst y/user-init-config
  (expand-file-name "config.org" user-emacs-directory)
  "File name, including directory, of initialization file by org-babel.")

(y/make-directory y/autofiles-directory)

;;; Proxy environment config. e.g.: https_proxy=https://127.0.0.1:8888
(y/env-sync-partner "https_proxy" "http_proxy")
(y/add-no-proxy-sites "*.cn"
                      "*.aliyun.com"
                      "*.tmall.com"
                      "*.youku.com"
                      "*.jd.com"
                      "*.bing.com"
                      "*.baidu.com"
                      "*.csdn.net"
                      "*.qq.com")

;; prevent emacs mess up init.el.
(setq custom-file (concat y/autofiles-directory "custom-auto.el"))
;; NOTE: All config is maintained manally. If you want some temporary
;; configuration to take effect permanently, open the below comment:
;; (load custom-file t)

(byte-recompile-directory y/lisp-directory 0)

;;; Emacs deep config with Org-mode literate programming

;; If you want to use the latest org, use the follows config:
;; 1. Download latest package or clone repo.
;;    URL: http://orgmode.org/
;;    REPO:
;;      ~$ git clone git://orgmode.org/org-mode.git
;;      ~$ make autoloads
;; 2. add load-path
;;    (add-to-list 'load-path "~/path/to/orgdir/lisp")
;; 3. If you want contributed libraries
;;    (add-to-list 'load-path "~/path/to/orgdir/contrib/lisp" t)
;; See homepage http://orgmode.org/ for more details.

;; use-package use 'package-installed-p' to check package installed or not
;; and org is a built-in package, so use-package would ignore org package
;; but org-plus-contrib is not installed default, so I think I can force install
;; org by routine package-install but failed.
(y/packages-install 'org 'org-plus-contrib)
(require 'org)
(require 'ob-tangle)

;; use env @EMACS_Y_INTERNAL_ESUP_PROFILER to prevent esub reload recursively.
(when (and (file-exists-p y/user-init-config)
           (not (string= (getenv "EMACS_Y_INTERNAL_ESUP_PROFILER") "y/esup")))
  (let* ((config-el (y/file-replace-extension y/user-init-config "el")))
    (when (file-newer-than-file-p y/user-init-config config-el)
      (require 'ob-tangle)
      (org-babel-tangle-file y/user-init-config config-el))
    (byte-recompile-file config-el nil 0 t)
    ))

(defconst y/startup-end-seconds (float-time))
(defconst y/startup-duration-seconds (- y/startup-end-seconds
                                        y/startup-begin-seconds))
(message "==> Emacs startup takes %.2f seconds." y/startup-duration-seconds)

;;; init.el ends here

6.3 auxiliary

;;; y-auxiliary.el --- Misc Auxiliary Routines -*- lexical-binding:t -*-

;; Copyright (C) 2018 yanyg<[email protected]>

;; Author: yonggang.yyg<[email protected]>
;; Maintainer: yonggang.yyg<[email protected]>
;; Keyword: Emacs Enhanced Macros
;; URL: https://ycode.org

;;; Commentary:

;; Common Routines.
;; Do not use external package (not builtin) functions in this file.
;; Package management MAY not initialized here.

;;; Code:

(require 'ielm)
(require 'derived)

(require 'eldoc)
(require 'elisp-mode)
(require 'lisp-mode)
(require 'ielm)
(require 'subr-x)

(defconst y/autofiles-directory
  (expand-file-name ".autofiles/" user-emacs-directory)
  "Directory for auto generated files from misc features.
Such as auto-save, bookmark, undo-tree.")

(defmacro y/inc(n)
  "Increment N."
  `(setq ,n (1+ ,n)))

;; language enhancement
(defmacro y/for(initial condition change &rest body)
  "Loop if condition is satisfied.
Execute INITIAL, then check CONDITION, after BODY is executed, do CHANGE,
and then check CONDITION again."
  `(let ,initial
     (while ,condition
       ,@body
       ,change)))

(defmacro y/count-loop(var from to &optional inc &rest body)
  "Loop from FROM to TO, execute BODY and INC VAR each time."
  `(y/for ((,var ,from)) ;; initial
          (< ,var ,to) ;; condition
          (,(or inc 'y/inc) ,var) ;; change
          ,@body))

;; integer/math
(defmacro y/nzerop(arg)
  "Check whether ARG is zero number.
Returns t if is zero number, otherwise return nil"
  `(not (zerop ,arg)))

(defmacro y/delete(elt seq)
  "Delete ELT in SEQ, and restore the result to SEQ.
Use `delete' to remove from SEQ."
  `(setq ,seq (delete ,elt ,seq)))

(defmacro y/first-non-nil(&rest args)
  "Return first non nil in ARGS."
  (catch 'final
    (dolist (l args)
      (when l
        (throw 'final l)))))

(defun y/slash-to-dash(str &optional prefix)
  "Replace \"/\" to \"-\" in string STR.
Keep the \"/\" in PREFIX."
  (or prefix (setq prefix ""))
  (setq str (string-remove-prefix prefix str))
  (setq str (replace-regexp-in-string "/" "-" str))
  (concat prefix str))

(defun y/make-directory(dir)
  "Make directory DIR if not exist.
Return t if success, otherwise return nil."
  (or (file-exists-p dir)
      (make-directory dir t))
  (or (file-directory-p dir)
      (warn "Directory `%s' exists but is not a directory" dir)))

(defmacro y/file-replace-extension(filename extension)
  "Replace FILENAME suffix(extension) to EXTENSION."
  `(concat (file-name-sans-extension ,filename) "." ,extension))

(defmacro y/define-find-file-function(filename)
  "Define a function Y/FIND-FILE--FILENAME which is used to open FILENAME."
  `(defun ,(intern (format "y/find-file--%s" filename)) ()
     ,(format "Call `find-file' `%s'." filename)
     (interactive)
     (find-file ,filename)))

(defmacro y/define-find-file-function-and-bind(filename map keyseq)
  "Define a `find-file' FILENAME function and bind to KEYSEQ in MAP.
Use `global-map' if MAP is nil."
  `(define-key
     (y/first-non-nil ,map global-map) ,(kbd keyseq)
     (y/define-find-file-function ,filename)))

(defmacro y/define-switch-to-buffer-function(buffername &rest body)
  "Define a function Y/SWITCH-TO--BUFFERNAME to switch to BUFFERNAME.
Eval BODY if create a new buffer and BODY is non nil."
  `(defun ,(intern (format "y/switch-to--%s" buffername)) ()
     ,(format "Call `switch-to-buffer' `%s'.\nEval %s" buffername
              body)
     (interactive)
     (let ((newcreate (not (get-buffer ,buffername))))
      (when (and (switch-to-buffer ,buffername) newcreate)
        ,@body))))

(defmacro y/define-switch-to-buffer-function-and-bind
    (buffername map keyseq &rest body)
  "Define a switch to BUFFERNAME function and bind to KEYSEQ in MAP.
Use `global-map' if MAP is nil.  Eval BODY if non nil."
  `(define-key
     (y/first-non-nil ,map global-map) ,(kbd keyseq)
     (y/define-switch-to-buffer-function ,buffername ,@body)))

(defmacro y/define-kill-buffer-function(buffername)
  "Define a function Y/KILL-BUFFER--BUFFERNAME to kill BUFFERNAME."
  `(defun ,(intern (format "y/switch-to--%s" buffername)) ()
     ,(format "Call `kill-buffer' `%s'." buffername)
     (interactive)
     (save-excursion
       (and (switch-to-buffer ,buffername)
            (kill-buffer)))))

(defmacro y/define-kill-buffer-function-and-bind(buffername map keyseq)
  "Define a `kill-buffer' BUFFERNAME function and bind to KEYSEQ in MAP.
Use `global-map' if MAP is nil."
  `(define-key
     (y/first-non-nil ,map global-map) ,(kbd keyseq)
     (y/define-kill-buffer-function ,buffername)))

(defmacro y/define-function(func doc &rest body)
  "Define a function FUNC with docstring DOC and body BODY."
  `(defun ,func ()
     ,doc
     (interactive)
     ,@body))

(defmacro y/define-function-and-bind(func doc map keyseq &rest body)
  "Define a function FUNC with DOC and BODY, then bind to KEYSEQ in MAP."
    `(define-key
       (y/first-non-nil ,map global-map) ,(kbd keyseq)
       (y/define-function ,func ,doc ,@body)))

(defmacro y/define-lambda-bind(map keyseq &rest body)
  "Bind KEYSEQ to MAP, use `global-map' if MAP is nil.  BODY is lambda body."
  `(define-key
     (y/first-non-nil ,map global-map) ,(kbd keyseq)
     #'(lambda()(interactive) ,@body)))

(defun y/append-to-list(listdest listsrc &optional append compare-fn)
  "Add each member of LISTSRC to LISTDEST.
Call `add-to-list' for each member of LISTSRC.  APPEND and COMPARE-FN
transfer to `add-to-list'."
  (dolist (m listsrc)
    (add-to-list listdest m append compare-fn)))

(defun y/delete-word (arg)
  "Delete characters forward until encountering the end of a word.
With argument ARG, do this that many times."
  (interactive "p")
  (delete-region (point) (progn (forward-word arg) (point))))

(defun y/backward-delete-word (arg)
  "Delete characters backward until encountering the beginning of a word.
With argument ARG, do this that many times."
  (interactive "p")
  (y/delete-word (- arg)))

(defun y/comment-or-uncomment()
  "Call `comment-or-uncomment-region'.
Use current line if no active region."
  (interactive)
  (if (use-region-p)
      (comment-or-uncomment-region (region-beginning) (region-end))
    (comment-or-uncomment-region (line-beginning-position)
                                 (line-end-position))))

;; lisp mode
(defconst y/lisp-modes
  '(emacs-lisp-mode lisp-mode ielm-mode lisp-interaction-mode)
  "Major modes relate to LISP.")

(defvar y/lisp-modes-hook nil
  "Hook for all LISP mode.")

(defun y/lisp-modes--hook-function()
  "Run `y/lisp-mode-hook' for all LISP mode."
  (run-hooks 'y/lisp-modes-hook))

(dolist (hook (mapcar #'derived-mode-hook-name y/lisp-modes))
  (add-hook hook 'y/lisp-modes--hook-function))

(defmacro y/define-lisp-modes-foreach(func)
  "Execute FUNC with argument mode for each LISP mode."
  `(dolist (mode y/lisp-modes)
     (funcall ,func mode)))

(defmacro y/define-set-key-function(setfuncname unsetfuncname map)
  "Define a pair functions to `define-key' in MAP.
SETFUNCNAME to set key, UNSETFUNCNAME to remove key."
     `(list
    (defun ,setfuncname (key command)
      ,(format "Give KEY a `%s' binding as COMMAND.
Like as `global-set-key' but use map `%s'." map map)
      (interactive
       (let* ((menu-prompting nil)
              (key (read-key-sequence (format "Set key in %s: " ,map))))
         (list key
               (read-command (format "Set key %s to command: "
                                     (key-description key))))))
      (or (vectorp key) (stringp key)
          (signal 'wrong-type-argument (list 'arrayp key)))
      (define-key ,map key command))

    (defun ,unsetfuncname (key)
      ,(format "Remov KEY binding from keymap `%s'.
Like as `global-unset-key' but use map `%s'." map map)
      (interactive ,(format "kUnset key from %s: " map))
      (,setfuncname key nil))))

;; (defmacro y/define-set-key-function(setfuncname unsetfuncname map)
;;   "Define a pair functions to `define-key' in MAP.
;; SETFUNCNAME to set key, UNSETFUNCNAME to remove key."
;;   `(list
;;     (defun ,setfuncname (key command)
;;       ,(format "Give KEY a `%s' binding as COMMAND.
;; Like as `global-set-key' but use map `%s'." map map)
;;       (interactive
;;        (let* ((menu-prompting nil)
;;               (key (read-key-sequence (format "Set key in %s: " ,map))))
;;          (list key
;;                (read-command (format "Set key %s to command: "
;;                                      (key-description key))))))
;;       (or (vectorp key) (stringp key)
;;           (signal 'wrong-type-argument (list 'arrayp key)))
;;       (define-key ,map key command))

;;     (defun ,unsetfuncname (key)
;;       ,(format "Remov KEY binding from keymap `%s'.
;; Like as `global-unset-key' but use map `%s'." map map)
;;       (interactive ,(format "kUnset key from %s: " map))
;;       (,setfuncname key nil))))

(defun y/add-after-init-or-make-frame-hook(func)
  "Add FUNC to after-init or after-make-frame hook depends on daemon mode."
  (if (daemonp)
      (add-hook 'after-make-frame-functions func)
    (add-hook 'after-init-hook func)))

(defun y/date(&optional insert)
  "Show Today Date in echo area.  Insert to current buffer if INSERT."
  (interactive "P")
  (message (format-time-string "%Y-%m-%d"))
  (and insert
       (if buffer-read-only
           (error "Could not insert to read-only buffer")
         (insert (format-time-string "%Y-%m-%d")))))

;; source: http://steve.yegge.googlepages.com/my-dot-emacs-file
(defun y/rename-file-and-buffer (new-name)
  "Renames both current buffer and file it's visiting to NEW-NAME."
  (interactive "sNew name: ")
  (let ((name (buffer-name))
        (filename (buffer-file-name)))
    (if (not filename)
        (message "Buffer '%s' is not visiting a file!" name)
      (if (get-buffer new-name)
          (message "A buffer named '%s' already exists!" new-name)
        (progn
          (rename-file filename new-name 1)
          (rename-buffer new-name)
          (set-visited-file-name new-name)
          (set-buffer-modified-p nil))))))

;; source: https://emacs.stackexchange.com/a/24461
(defun y/revert-all-file-buffers()
  "Refresh all open file buffers without confirmation.
Buffers in modified (not yet saved) state in Emacs will not be reverted.
They will be reverted though if they were modified outside Emacs.
Buffers visiting files which do not exist any more or are no longer readable
will be killed."
  (interactive)
  (dolist (buf (buffer-list))
    (let ((filename (buffer-file-name buf)))
      ;; Revert only buffers containing files, which are not modified;
      ;; do not try to revert non-file buffers like *Messages*.
      (when (and filename
                 (not (buffer-modified-p buf)))
        (if (file-readable-p filename)
            ;; If the file exists and is readable, revert the buffer.
            (with-current-buffer buf
              (revert-buffer :ignore-auto :noconfirm :preserve-modes))
          ;; Otherwise, kill the buffer.
          (let (kill-buffer-query-functions) ; No query done when killing buffer
            (kill-buffer buf)
            (message "Killed non-existing/unreadable file buffer: %s" filename))))))
  (message "Finished reverting buffers containing unmodified files."))

(defun y/view-lossage-timer()
  "Show `view-lossage' periodically."
  (interactive)
  (defvar y/--view-lossage-timer nil)
  (if y/--view-lossage-timer
      (progn
        (cancel-timer y/--view-lossage-timer)
        (setq y/--view-lossage-timer nil))
    (setq y/--view-lossage-timer (run-at-time nil .5 #'view-lossage))))

;; env process
(defun y/env-set(env val &optional force)
  "Set enviroment ENV to VAL if ENV unset or FORCE is t."
  (when (or (not (getenv env)) force)
    (setenv env val)))
(defun y/env-sync-partner(env1 env2)
  "Sync enviroment ENV1/ENV2 to another if one is nil and another is non-nil."
  (or (getenv env1) (y/env-set env1 (getenv env2)))
  (or (getenv env2) (y/env-set env2 (getenv env1))))

(defun y/add-no-proxy-sites(&rest sites)
  "Add SITES to environment `no_proxy'."
  (let* ((no-proxy-sites
          (let ((env (getenv "no_proxy")))
            (and env (split-string env ","))))
         ;; dummy var only for eliminating free variable warning.
         add-sites)
    (cl-labels
        ((add-sites (sites)
          (dolist (s sites)
            (pcase s
              ('nil t)
              ((pred stringp)
               (and (> (length s) 0)
                    (add-to-list 'no-proxy-sites s t)))
              ((pred listp)
               (funcall add-sites (car s))
               (funcall add-sites (cdr s)))
              (_ (warn "Unknown type(%s) object: %S" (type-of s) s))))
          no-proxy-sites))
      (add-sites sites))
    (y/env-set "no_proxy" (mapconcat 'identity no-proxy-sites ",") t)))

(defun y/string-from-file(filename)
  "Return FILENAME content in string."
  (let ((s nil))
    (when (and (stringp filename)
               (file-readable-p filename)
               (file-regular-p filename))
      (with-temp-buffer
        (insert-file-contents filename)
        (setq s (buffer-string))))
    s))

(defun y/string-from-file-safe(filename)
  "Return FILENAME contents if file readable.
Otherwise return \"\""
  (if (and filename (stringp filename) (file-readable-p filename))
      (y/string-from-file filename)
    ""))

(provide 'y-auxiliary)

;;; y-auxiliary.el ends here

6.4 package

;;; y-package.el --- Package management  -*- lexical-binding:t -*-

;; Copyright (C) 2017-2020 yonggang.yyg<[email protected]>

;; Author: yonggang.yyg<[email protected]>
;; Maintainer: yonggang.yyg<[email protected]>
;; Keyword: Emacs Behavior Autosave Backup Bookmark
;; Homepage: https://ycode.org
;; URL: http://github.com/yygcode/.emacs.d

;; SPDX-License-Identifier: GPL-2.0-or-later

;;; Commentary:

;; Package management.

;; Enviroment Variable Description
;;
;; EMACS_NO_MIRROR:
;;   Set env to 1 to disable package-archives mirror, and install packages
;;   from official sites.  The config uses tsinghua mirror by default because
;;   the official melpa sites are blocked by GFW.
;;   e.g.: export EMACS_NO_MIRROR=1
;;
;; EMACS_MIRROR_HTTP
;;   Set env to 1 install packages from the mirror site by http protocol.
;;   Do not set this env unless your system does not support SSL/TLS(https).
;;   e.g.: export EMACS_MIRROR_HTTP=1
;;
;; If both env EMACS_NO_MIRROR and EMACS_MIRROR_HTTP are set, EMACS_NO_MIRROR
;; will override EMACS_MIRROR_HTTP.

;; http_proxy, https_proxy
;;   Proxy environment.
;;   e.g.: export https_proxy=http://127.0.0.1:8888

;;; Code:

(require 'package)

(defconst y/package-archives-melpa
  '(("melpa-stable" . "https://stable.melpa.org/packages/")
    ("melpa" . "https://melpa.org/packages/")
    ("gnu" . "https://elpa.gnu.org/packages/")
    ("org" . "http://orgmode.org/elpa/")
    ("marmalade" . "https://marmalade-repo.org/packages/"))
  "MELPA package source from official site.")
(defconst y/package-archives-mirror
  '(("melpa-stable" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa-stable/")
    ("melpa" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")
    ("gnu" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
    ("org" . "https://mirrors.tuna.tsinghua.edu.cn/elpa/org/"))
  "Mirror package source from tsinghua.")
(defconst y/package-archives-mirror-http
  '(("melpa-stable" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa-stable/")
    ("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/")
    ("gnu" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/gnu/")
    ("org" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/org/"))
  "Mirror package source using http protocol from tsinghua.")

(setq package-archives
      (cond ((equal (getenv "EMACS_NO_MIRROR") "1") y/package-archives-melpa)
            ((equal (getenv "EMACS_MIRROR_HTTP") "1")
             y/package-archives-mirror-http)
            (t y/package-archives-mirror)))

(package-initialize)

;; Downloads archive contents if not exists for startup performance. But the
;; old archive contents may cause the package install to fail, then try execute
;; function package-refresh-contents manually.
(unless package-archive-contents (package-refresh-contents))

;; use-package simplifies emacs packages install and config.
;; GitHub: https://github.com/jwiegley/use-package
;; HomePage: https://jwiegley.github.io/use-package/
(unless (or (package-installed-p 'use-package)
            (package-install 'use-package))
  (error "Install use-package failed"))
(eval-and-compile
  (require 'use-package)
  (setq use-package-always-ensure t
        use-package-always-pin "melpa-stable"
        use-package-always-defer t))

;; quelpa - Install Emacs Lisp packages from source code.
;; https://github.com/quelpa/quelpa
;; disable auto-upgrade for startup performance
;; call y/upgrade-quelpa manually if necessary
(use-package quelpa
  :pin melpa
  :init
  (setq quelpa-checkout-melpa-p t
        quelpa-self-upgrade-p nil
        quelpa-update-melpa-p nil
        quelpa-stable-p t))

;; Provide quelpa option to use-package
;; https://github.com/quelpa/quelpa-use-package
(use-package quelpa-use-package
  :pin melpa
  :init
  (require 'quelpa-use-package)
  ;; I set use-package-always-ensure, so need advice here
  ;; https://github.com/quelpa/quelpa-use-package#overriding-use-package-always-ensure
  (quelpa-use-package-activate-advice))

(defun y/package-quelpa-upgrade()
  "Upgrade quelpa package."
  (interactive)
  (unless (package-installed-p 'quelpa) (package-install 'quelpa))
  (require 'quelpa)
  (quelpa-self-upgrade)
  (quelpa-upgrade)
  (quelpa-checkout-melpa)
  (with-temp-buffer
    (url-insert-file-contents
     "https://raw.github.com/quelpa/quelpa/master/bootstrap.el")
    (eval-buffer)))

(defun y/package-install(pkg &optional dont-select)
  "Execute `package-install' PKG DONT-SELECT to install package.
Auto execute `package-refresh-contents' then `package-install' if failed."
  (if (package-installed-p pkg)
      t
    (condition-case err
        (package-install pkg dont-select)
      (error
       (message "Install `%s' failed(err:%s), try refresh archive contents"
                pkg err)
       (package-refresh-contents)
       (package-install pkg dont-select)))))

(defun y/packages-install(&rest pkgs)
  "Install PKGS one by one.  See `y/package-install' for more details."
  (let ((ret t))
    (dolist (pkg pkgs)
      (unless (y/package-install pkg)
        (warn "Install package `%s' failed" pkg)
        (setq ret nil)))
    ret))

(defun y/packages-require(&rest pkgs)
  "Require PKGS one by one.  See `require' for more details."
  (let ((ret t))
    (dolist (pkg pkgs)
      (unless (and (package-installed-p pkg) (require pkg nil t))
        (warn "Require `%s' failed." pkg)
        (setq ret nil)))
    ret))

(defmacro y/packages-install-and-require(&rest pkgs)
  "Install and require PKGS one by one."
  `(unless (and (y/packages-install ,pkgs)
               (y/packages-require ,pkgs))
    (warn "Packages(%s) install or require failed." ,pkgs)
    nil)
  t)

(use-package diminish)

(provide 'y-package)

;;; y-package.el ends here

6.5 config.org

This section has no actual effect, you can remove this section safely. It's just used to generate some format code snippets to help auto-generated file config.el satisfing the elisp file format requirement. Refer to follow links:

https://www.gnu.org/software/emacs/manual/html_node/elisp/Simple-Packages.html

;;; ~/.emacs.d/config.el --- Emacs Configuration File

;; Copyright (C) 2017-2020 yanyg<[email protected], [email protected]>

;; Author: yonggang.yyg<[email protected]>
;; Maintainer: yonggang.yyg<[email protected]>
;; Keyword: Emacs Customize Org Literate
;; Homepage: https://ycode.org; http://ycode.org
;; URL: http://github.com/yygcode/.emacs.d

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING, if not see
;; <http://www.gnu.org/licenses/>.

;;; Commentary:

;; This file is auto-generated with org-babel.  The source is config.org.
;; DO NOT modify this file(~/.emacs.d/config.el) directly.
;; Please modify source file ~/.emacs.d/config.org.

;;; Code:

;; package management
(require 'y-package)

6.5.2 Profiler

6.5.2.1 Wrapper function
Function Description
y/profile-esup Show esup startup profiler
y/profile-tabulated Show benchmark result in a sorted table
y/profile-tree Show benchmark result in a call-tree
6.5.2.2 esup - startup profiler

init.el only set package archive and org, then use org-babel load config.org to complete the rest(vast majority, main part) configurations. Esup provides esup-child-profile-require-level deep to profile require statement, but it can not deep to profile org-babel file. I write wrapper function y/esup to analyze config.org time proportion.

;; When call esup function, esup would start a new emacs process
;; with option -L/-l to load esup, but until now init.el does not
;; loaded, so we need load init.el first.
;; Load if in esup.
(unless (boundp 'y/user-init-config)
  (let ((file (expand-file-name "init.el" user-emacs-directory)))
    (message "y/esup: Esup profiling, load %s now" file)
    (load-file file)))

(use-package esup
  ;; :quelpa (esup :fetcher github :repo "jschaf/esup" :stable nil)
  :pin melpa
  :init
  ;; Current version only support 1, see bug
  ;; https://github.com/jschaf/esup/issues/53
  (setq esup-depth 1)
  (setq esup-user-init-file
        (y/file-replace-extension y/user-init-config "el")))

(defun y/esup-advice-before(&optional init-file)
  "Set env EMACS_Y_INTERNAL_ESUP_PROFILER."
  (setenv "EMACS_Y_INTERNAL_ESUP_PROFILER" "y/esup"))

(defun y/esup-advice-after(&optional init-file)
  "Clear env EMACS_Y_INTERNAL_ESUP_PROFILER."
  (setenv "EMACS_Y_INTERNAL_ESUP_PROFILER" nil)
  t)
;; set env before esup, and clear env after esup
;; because we esup config.el but we need load init.el too.
;;          (add-function :override completing-read-function
;;                        #'helm--completing-read-default)
(advice-add 'esup :before #'y/esup-advice-before)
(advice-add 'esup :after #'y/esup-advice-after)

(defalias 'y/profile-esup 'esup
  "Profiling emacs startup time.")
6.5.2.3 benchmark - profile execution time

Notice Import benchmark-init after package esup for esup enhancement.

  • GitHub: https://github.com/dholm/benchmark-init-el
  • Execute function in emacs to query result
    • benchmark-init/show-durations-tree
    • benchmark-init/show-durations-tabulated
  • Default disable data collection after init. Execute to enable or disbale:
    • benchmark-init/activate
    • benchmark-init/deactivate
(use-package benchmark-init
  ;; complains error with 'void function benchmark-init/activate'
  ;; when first run just after install, if use init.
  :defer nil
  :config
  (require 'benchmark-init)
  (benchmark-init/activate)
  :hook
  ;; To disable collection of benchmark data after init is done.
  (after-init . benchmark-init/deactivate))

(defalias 'y/profile-tabulated 'benchmark-init/show-durations-tabulated
  "Profiling emacs startup time. Show result as a table.")
(defalias 'y/profile-tree 'benchmark-init/show-durations-tree
  "Profiling emacs startup time. Show result as a tree.")

6.5.3 Auxiliary

Misc helper routines.

(require 'y-auxiliary)

6.5.4 keybinds

6.5.4.1 introduction
  1. principle
    • compat both in terminal and X windows
    • High frequency operation gives a shorter key sequence
    • Use default keybind if possible
6.5.4.2 basic keybind

Turn off minor mode y/keymap-global-mode if you don't like.

(require 'y-keymap)
(require 'y-browse)
6.5.4.3 browse code keybind
6.5.4.4 command-frequencey analysis

See Ergo stastics: http://ergoemacs.org/emacs/command-frequency.html.

keyfreq and command-log-mode are helpful packages:

open-dribble-file is used to record all user input. view-lossage is used to display last few input keystrokes and the command run.

view-lossage does not update when user input, write a wrapper to update contents dynamically.

(use-package keyfreq
  :init
  (setq keyfreq-file (concat y/autofiles-directory
                             ".emacs.keyfreq")
        keyfreq-file-lock (concat y/autofiles-directory
                                  ".emacs.keyfreq.lock"))
  (keyfreq-mode 1)
  (keyfreq-autosave-mode 1))
(use-package command-log-mode
  :pin melpa
  :init
  ;; workaround for global-command-log-mode
  (command-log-mode -1)
  ;; Log all keystroke except self-insert-command
  (setq clm/log-command-exceptions* '(nil self-insert-command)))

(defvar y/view-lossage--state nil "View lossage state.")
(defun y/toggle-view-lossage()
  "Toggle auto update view-lossage."
  (interactive)
  (if y/view-lossage--state
      (remove-hook 'pre-command-hook #'view-lossage)
    (add-hook 'pre-command-hook #'view-lossage))
  (setq y/view-lossage--state (not y/view-lossage--state)))
(define-key
  y/keymap-mode-map (kbd "C-x C-x l")
  #'y/toggle-view-lossage)
6.5.4.5 which key - keybinds help

which-key is a minor mode for Emacs that displays the key bindings following your currently entered incomplete command (a prefix) in a popup. For example, after enabling the minor mode if you enter C-x and wait for the default of 1 second the minibuffer will expand with all of the available key bindings that follow C-x (or as many as space allows given your settings). Github url is: https://github.com/justbur/emacs-which-key

describe-bindings are used to list all defined keys. describe-prefix-bindings Describe the bindings of the prefix used to reach this command.

(use-package which-key
  :init
  ;; Do not auto start, I almost don't need it
  (which-key-mode -1)
  ;; (which-key-setup-side-window-right)
  (setq which-key-use-C-h-commands nil
        which-key-idle-delay 2.0
        which-key-popup-type 'minibuffer)
  :bind
  (:map which-key-mode-map
        ("C-x h" . which-key-C-h-dispatch)
        ("C-c M-h" . which-key-C-h-dispatch)))

6.5.5 Editor

Editor behaviors.

(require 'y-editor)

6.5.6 Display

6.5.6.1 behavior
(require 'display-line-numbers)
(defun y/line-numbers--face(&optional theme-unused  no-confirm-unused
                                      no-enable-unused)
  "Line numbers config."
  (interactive)
  (setq display-line-numbers-grow-only t)
  (set-face-attribute 'line-number nil
                      :inherit 'linum
                      :height 110
                      :weight 'normal
                      :slant 'italic)
  (set-face-attribute 'line-number-current-line nil
                      :inherit 'line-number
                      :foreground "#FF7F00"
                      :background "#1A1A1A"))

;; advice after load-theme because theme will reset it
(advice-add 'load-theme :after #'y/line-numbers--face)
;; run directly if no load-theme explicitly
(y/line-numbers--face)

(add-hook 'after-change-major-mode-hook #'display-line-numbers-mode)

6.5.6.2 font

elisp Chapter 39 section 39.12 describes more technology about faces. Read it for more details:

39.12.9 Font Selection
https://www.gnu.org/software/emacs/manual/html_node/elisp/Font-Selection.html#Font-Selection
39.12.11 Fontsets
https://www.gnu.org/software/emacs/manual/html_node/elisp/Fontsets.html#Fontsets
39.12.12 Low-Level Font Representation
https://www.gnu.org/software/emacs/manual/html_node/elisp/Low_002dLevel-Font.html#Low_002dLevel-Font

Font depends on specific platform (Linux/Mac/Windows). Here according to different platform to set beautiful/properly font as much as possible.

Monospace: Code always use monospace font. See wiki
https://en.wikipedia.org/wiki/List_of_monospaced_typefaces

Set different font for different major mode. See https://emacs.stackexchange.com/a/3044.

(defconst y/font-mono-size-x 15
  "Monospace font size under graphic.")

(defconst y/font-mono-size-c 15
  "Monospace font size under console.")

;; FIXME: support for different frame by make-variable-frame-local
(defvar y/font-cjk-name nil "Fill when set for CJK fonts.")
;; (make-variable-frame-local 'y/font-cjk-name)
(defvar cjk-charsets '(kana han symbol cjk-misc bopomofo))

(defconst y/font-mono-name-list-default
  `(("Source Code Variable" . ,y/font-mono-size-x)
    ("Source Code Pro" . ,y/font-mono-size-x)
    ("PragmataPro" . ,y/font-mono-size-x)
    ("ProFont" . ,y/font-mono-size-x)
    ("Lucida Sans" . ,y/font-mono-size-x)
    ("Courier New" . ,y/font-mono-size-x)
    ("Consolas" . ,y/font-mono-size-x)
    ("DejaVu Sans Mono" . ,y/font-mono-size-x)
    ("FreeMono" . ,y/font-mono-size-x)
    ("Liberation Mono" . ,y/font-mono-size-x))
  "Monospace font name assoc default value.")

(defconst y/font-monocjk-size-x 15
  "MonospaceCJK font size under graphic.")

(defconst y/font-monocjk-size-c 15
  "MonospaceCJK font size under console.")

(defconst y/font-monocjk-name-list-default
  `(("YouYuan"             . ,y/font-monocjk-size-x)
    ("Microsoft YaHei UI"  . ,y/font-monocjk-size-x)
    ("Microsoft YaHei"     . ,y/font-monocjk-size-x)
    ("FangSong"            . ,y/font-monocjk-size-x)
    ("SimSun"              . ,y/font-monocjk-size-x)
    ("AR PL SungtiL GB"    . ,y/font-monocjk-size-x)
    ("AR PL Mingti2L Big5" . ,y/font-monocjk-size-x))
  "MonospaceCJK font name assoc default value.")

(defvar y/font-mono-name-list-x nil
  "Monospace font candidates under graphic.
Format is ((name . size) ...).")
(defvar y/font-mono-name-list-c nil
  "Monospace font candidates under console.
Format is ((name . size) ...).")

(defvar y/font-monocjk-name-list-x nil
  "MonospaceCJK font candidates under graphic.
Format is ((name . size) ...).")
(defvar y/font-monocjk-name-list-c nil
  "MonospaceCJK font candidates under console.
Format is ((name . size) ...).")

;; Customize the name list to satisfy your taste.
(cond ((string= system-type "gnu/linux")  ;; Linux
       (setq y/font-mono-name-list-x y/font-mono-name-list-default
             y/font-mono-name-list-c y/font-mono-name-list-default)
       (setq y/font-monocjk-name-list-x y/font-monocjk-name-list-default
             y/font-monocjk-name-list-c y/font-monocjk-name-list-default))
      ((string= system-type "darwin")     ;; Mac prepend ?
       (setq y/font-mono-name-list-x
             (cons `("Apple Color Emoji" . ,y/font-mono-size-x)
                   y/font-mono-name-list-default))
       (setq y/font-mono-name-list-c y/font-mono-name-list-x)
       (setq y/font-monocjk-name-list-x y/font-monocjk-name-list-default
             y/font-monocjk-name-list-c y/font-monocjk-name-list-default))
      ((string= system-type "windows-nt") ;; Windows
       (setq y/font-mono-name-list-x y/font-mono-name-list-default
             y/font-mono-name-list-c y/font-mono-name-list-default)
       (setq y/font-monocjk-name-list-x y/font-monocjk-name-list-default
             y/font-monocjk-name-list-c y/font-monocjk-name-list-default))
      (t
       (setq y/font-mono-name-list-x y/font-mono-name-list-default
             y/font-mono-name-list-c y/font-mono-name-list-default)
       (setq y/font-monocjk-name-list-x y/font-monocjk-name-list-default
             y/font-monocjk-name-list-c y/font-monocjk-name-list-default)))

(defun y/font-is-exist(namesize)
  "Check font exist or not. The font property :name is NAME."
  (if (and (stringp (car namesize))
           (integerp (cdr namesize))
           (find-font (font-spec :name (car namesize)
                                 :size (cdr namesize))))
      t
    nil))

(defun y/font-set-frame-font-if-exist(frame charset namesize &optional fontset)
  "For FRAME, Set CHARSET's font to NAMESIZE if that font exists.
If FONTSET is non-nil, then call set-fontset-font set default font."
  (if (y/font-is-exist namesize)
      (progn
        ;; (message "Set Font Frame(%s) Charset(%s) to %s" frame charset namesize)
        (if fontset
            (set-frame-font (font-spec :name (car namesize)
                                       :size (cdr namesize)) nil nil)
          (set-fontset-font nil charset (font-spec :name (car namesize)
                                                   :size (cdr namesize))
                            frame))
        (and (memq charset cjk-charsets)
             (setq y/font-cjk-name (car namesize)))
        t)
    nil))

(defun y/font-set-frame-try-list(frame charset namesizeassoc &optional fontset)
  "For FRAME, from front to back in NAMESIZEASSOC, try to set CHARSET's font."
  (let ((r nil))
    (dolist (namesize namesizeassoc)
      (unless r
        (and (y/font-set-frame-font-if-exist
              frame charset namesize fontset)
             (setq r t))))))

(defun y/font-set-frame-font-by-display
    (frame charset namesizeassocx namesizeassocc &optional fontset)
  "For FRAME, from front to back in namesizeassoc, try to set CHARSET's font.
If frame run in graphic, use NAMESIZEASSOCX, otherwise use NAMESIZEASSOCC"
  (if (display-graphic-p)
      (y/font-set-frame-try-list frame charset namesizeassocx fontset)
    (y/font-set-frame-try-list frame charset namesizeassocc fontset)))

(defun y/font-set(&optional frame)
  "For FRAME set properly font."
  (or frame (setq frame (selected-frame)))
  (with-selected-frame frame
      (y/font-set-frame-font-by-display
       frame nil y/font-mono-name-list-x y/font-mono-name-list-c t)
      (dolist (charset cjk-charsets)
        (y/font-set-frame-font-by-display
         frame charset y/font-monocjk-name-list-x y/font-monocjk-name-list-c))))

(y/add-after-init-or-make-frame-hook #'y/font-set)
6.5.6.3 modeline
  • Smart mode line. Try sml/apply-theme if want more.
  • Diminish used to hide minor info
(require 'time)
(require 'battery)
(setq display-time-default-load-average nil
      display-time-format "%k:%M %a" ;; remove %b %d
      display-time-mode t)
(setq system-time-locale "C") ;; show english even LANG to zh_CN.UTF-8
(display-time)

(setq battery-mode-line-format " [%L %b%p%% %t]" ;; sml will override it
      battery-update-interval 5)
(display-battery-mode)

(use-package smart-mode-line
  :init
  (setq sml/col-number-format "%02c"
        sml/battery-format " [%L %b%p%% %t]"
        sml/name-width 15
        sml/no-confirm-load-theme t
        ;; sml/theme 'dark ;; others: light, respectful
        sml/theme 'respectful)
  (sml/setup)
  (add-to-list 'sml/replacer-regexp-list '(".*/linux" ":LK:")))
6.5.6.4 theme

Theme is another important display aspect. Manual https://www.gnu.org/software/emacs/manual/html_node/emacs/Custom-Themes.html, https://www.gnu.org/software/emacs/manual/html_node/emacs/Creating-Custom-Themes.html and wiki https://www.emacswiki.org/emacs/CustomThemes introduce some theme knowledge.

Emacsthemes(https://emacsthemes.com/) and Emacs Theme Gallary(https://pawelbx.github.io/emacs-theme-gallery/) lists typical emacs theme.

Theme will gradually increase as time goes, put all liked theme package here and select zenburn as default.

;; my favorite theme
(defvar y/theme-packages
  '(zenburn-theme monokai-theme anti-zenburn-theme
    solarized-theme moe-theme)
  "Themes package list.")
(defvar y/theme-day-of-week
  '(zenburn monokai anti-zenburn
    solarized-dark solarized-light moe-dark moe-light)
  "Themes for each day of week.")

(dolist (theme y/theme-packages)
  (or (package-installed-p theme)
      (package-install theme)))

(defun y/timer-scheme-day-of-week()
  "Timer function to switch scheme per day."
  (let* ((dow (string-to-number (format-time-string "%w")))
         (n (% dow (length y/theme-day-of-week)))
         (theme (nth n y/theme-day-of-week)))
    (message "Enable theme %s" theme)
    (load-theme theme t)))
;; (run-at-time "00:00" (* 24 60 60) #'y/timer-scheme-day-of-week)
;; (y/timer-scheme-day-of-week)
(load-theme 'zenburn t)

6.5.6.5 ui

Config for emacs daemon and non-daemon.

;; half width fringe
(when (fboundp 'fringe-mode)
  (set-fringe-mode 4))

;; A light that follows your cursor around so you don't lose it!
;; https://github.com/Malabarba/beacon
(use-package beacon
  :diminish
  :hook
  (after-init . beacon-mode))

(use-package hl-todo
  :hook
  (after-init . global-hl-todo-mode))

(defun y/frame-init-ui-basic(&optional frame)
  "Init FRAME user-interface after created."
  (interactive)
  (or frame
      (setq frame (selected-frame)))
  (with-selected-frame frame
    ;; Hide menu, tool, scroll bar, auto fullscreen for X
    (menu-bar-mode -1)
    (when (display-graphic-p)
      (set-frame-parameter nil 'fullscreen 'fullboth)
      (scroll-bar-mode -1))
    (when (fboundp 'tool-bar-mode)
      (tool-bar-mode -1))
    ;; cursor: bar with width 3, OrangeRed color, Steady mode
    (if (display-graphic-p)
        (progn
          (setq-default cursor-type 'box)
          (setq-default cursor-in-non-selected-windows nil)
          (blink-cursor-mode -1)
          (set-cursor-color "DarkOrange1"))
      (progn
        ;; Only support xterm.
        ;; FIXME: restore after exit.
        ;; need terminal support. 6 for steady bar, 2 for box
        ;; \e: ESC; \a: BELL; man ascii for more details.
        (send-string-to-terminal "\e[2 q\e]12;DarkOrange1\a")))

    ;; disable bell
    (setq visible-bell nil)
    (setq ring-bell-function 'ignore)

    (set-face-attribute 'isearch nil
                        :bold t
                        :italic t
                        :foreground "#FF7F00"
                        :background "#1A1A1A")

    ;; show column and size in the mode line
    (column-number-mode)
    (size-indication-mode t)))

(y/add-after-init-or-make-frame-hook #'y/frame-init-ui-basic)
6.5.6.6 window
(use-package hydra
  :init
  (defhydra y/window-hydra
    (y/keymap-mode-map "C-x C-x C-w")
    "window hydra"
    ("=" enlarge-window-horizontally "enlarge-window-horizontally")
    ("+" enlarge-window-horizontally "enlarge-window-horizontally")
    ("-" shrink-window-horizontally "shrink-window-horizontally")
    ("_" shrink-window-horizontally "shrink-window-horizontally")
    (">" enlarge-window "enlarge-window-vertically")
    ("." enlarge-window "enlarge-window-vertically")
    ("<" shrink-window "shrink-window-vertically")
    ("," shrink-window "shrink-window-vertically")
    ("q" nil "quit from hydra")
    ("C-g"  nil "quit from hydra")
    ("RET" nil "quit from hydra")))

6.5.7 Efficiency

6.5.7.1 abbrev
(require 'abbrev)
(diminish 'abbrev-mode)
6.5.7.2 company

company is a text completion framework. it means complete anything. gitub https://github.com/company-mode/company-mode.

the company configuration varies greatly for different major modes, and when use emacs, company config will always be adjusted or optimized. the company configurations are complex and huge, put it in a separate file y-company.el.

material:

(require 'y-company)
6.5.7.3 dictionary
  1. youdao
    (use-package youdao-dictionary
      :init
      (setq url-automatic-caching t)
      :bind
      (("C-c y t" . youdao-dictionary-search-at-point)
       ("C-c y s" . youdao-dictionary-play-voice-at-point)))
    
6.5.7.4 eldoc - minor mode for lisp

eldoc mode is a buffer-local minor mode. when enabled, the echo area displays information about a function or variable in the text where point is. if point is on a documented variable, it displays the first line of that variable’s doc string. otherwise it displays the argument list of the function called in the expression point is on.

emacswiki: https://www.emacswiki.org/emacs/eldoc

eldoc is a builtin package.

;; builtin
(require 'eldoc)
(setq eldoc-idle-delay 0)
(diminish 'eldoc-mode)
(add-hook 'y/lisp-modes 'eldoc-mode)
6.5.7.5 expand-region
(use-package expand-region
  :init
  (setq expand-region-smart-cursor t) ;; cursor put to region tail
  :bind
  ("<f6>"   . er/mark-symbol)
  ("<f7>"   . er/expand-region)
  ("C-c r +"   . er/expand-region)
  ("C-c r ="   . er/expand-region)
  ("C-c r w"   . er/mark-word)
  ("C-c r s"   . er/mark-symbol)
  ("C-c r f"   . er/mark-method-call)
  ("C-c r q i" . er/mark-inside-quotes)
  ("C-c r q o" . er/mark-outside-quotes)
  ("C-c r p i" . er/mark-inside-pairs)
  ("C-c r p o" . er/mark-outside-pairs)
  ("C-c r t i" . er/mark-inner-tag)
  ("C-c r t o" . er/mark-outer-tag))
6.5.7.6 helpful

Helpful is an alternative to the built-in Emacs help that provides much more contextual information.

(use-package helpful
  :pin melpa
  :bind
  ("C-c h f" . helpful-callable)
  ("C-c h k" . helpful-key)
  ("C-c h v" . helpful-variable)
  ("C-c h c" . helpful-command)
  ("C-c h s" . helpful-symbol)
  ("C-c h p" . helpful-at-point))
6.5.7.7 flycheck

flycheck is a modern on-the-fly syntax checking package. homepage is https://www.flycheck.org/en/latest/.

flycheck use external specific system tool to check syntax. see https://www.flycheck.org/en/latest/languages.html#flycheck-languages, so need properly exec-path to search it. install package exec-path-from-shell for mac compatiblity: https://github.com/purcell/exec-path-from-shell.

(use-package flycheck
  :diminish
  :init
  (setq flycheck-emacs-lisp-load-path 'inherit
        flycheck-checker-error-threshold 20000)
  :config
  (add-to-list 'flycheck-clang-warnings
               "no-pragma-once-outside-header")
  :hook
  (after-init    . global-flycheck-mode)
  ;; too many errors, disabled.
  (c-mode-common . (lambda() (flycheck-mode -1))))
6.5.7.8 highlight parenthesis
(use-package highlight-parentheses
  :diminish
  :config
  (setq hl-paren-colors
        '("orange1" "skyblue" "darkseagreen" "goldenrod"))
  (setq hl-paren-background-colors
        '("DarkOliveGreen4"))
  :hook
  (prog-mode . highlight-parentheses-mode))
6.5.7.9 highlight-symbol
;; Close highlight-symbol-mode, do it manually
(use-package highlight-symbol
  :diminish highlight-symbol-mode
  :init
  :config
  ;; (setq highlight-symbol-idle-delay .1)
  ;; The original func always print ugly string '<N> occurrences in buffer'
  ;; Replace with dummy empty function
  ;; (setq highlight-symbol-occurrence-message nil)
  ;; (advice-add 'highlight-symbol-count :override #'(lambda() nil))
  :bind
  (([f8] . highlight-symbol-at-point)
   ([S-f8] . highlight-regexp)
   ([f9] . highlight-symbol-query-replace))
  ;; :hook
  ;; disable auto high-light
  ;; (prog-mode . highlight-symbol-mode)
  )
6.5.7.10 helm

Helm is an Emacs framework for incremental completions and narrowing selections.

(use-package helm
  :diminish
  :config
  ;; always use english input in helm minibuffer
  ;; use C-\ (toggle-input-method) to toggle to other(e.g. pyim)
  (helm-set-local-variable 'current-input-method nil)
  :bind
  (:map global-map
        ("M-x" . helm-M-x)
        ("C-x b" . helm-mini)))
6.5.7.11 hungry delete
(use-package hungry-delete
  :diminish
  :hook
  (after-init . global-hungry-delete-mode))
6.5.7.12 iedit
(use-package iedit
  :bind
  (("C-c ;" . iedit-mode)))
6.5.7.13 mouse

Disable mouse globally.

;; disable mouse at all
(use-package disable-mouse
  :diminish disable-mouse-global-mode
  :init
  (disable-mouse-global-mode))
6.5.7.14 smartparens

Smartparens is a minor mode for dealing with pairs in Emacs.

(use-package smartparens
  :diminish
  :config
  (setq sp-base-key-bindings 'paredit)
  (setq sp-autoskip-closing-pair 'always)
  (setq sp-hybrid-kill-entire-symbol nil)
  (sp-use-paredit-bindings)
  ;; use eval-when-compile or with-eval-after-load can eliminate warning:
  ;; sp-local-pair might not be defined at runtime
  ;; But when start daemon cause a new error:
  ;;  Eager macro-expansion failure: (void-function sp-local-pair)
  (y/define-lisp-modes-foreach
   #'(lambda(mode)
       (sp-local-pair mode "'" nil :actions nil)
       (sp-local-pair mode "`" nil :actions nil)))
  :bind
  ("C-x C-x C-a" . sp-beginning-of-sexp)
  ("C-x C-x C-e" . sp-end-of-sexp)
  ("M-f" . sp-forward-symbol)
  ("M-b" . sp-backward-symbol)
  (:map y/browse-mode-map
        ("TAB" . sp-forward-symbol))
  :hook
  (after-init     . smartparens-global-mode)
  (after-init     . show-smartparens-global-mode)
  (c-mode-common  . turn-on-smartparens-strict-mode))
6.5.7.15 swiper

Swiper is a flexible, simple tools for minibuffer completion in Emacs.

;; short bindings with a common prefix
;; https://github.com/abo-abo/hydra
(use-package ivy
  :diminish
  ;; :after (hydra swiper counsel) ;; swiper internal use, compile error if absent
  :init
  (setq ivy-use-virtual-buffers t
        ivy-count-format "%d/%d > ")
  :hook
  (after-init . ivy-mode))

(use-package swiper
  :bind
  ;; ([remap isearch-backward] . swiper-isearch-backward)
  ;; ([remap isearch-forward] . swiper-isearch)
  (:map global-map
        ("C-s" . swiper-isearch)
        ("C-r" . swiper-isearch-backward)))

(use-package counsel
  :diminish
  :init
  (setq counsel-describe-function-function #'describe-function
        counsel-find-file-ignore-regexp
        (concat
         ;; filename begins with #
         "\\(?:\\`[#.]\\)"
         ;; filename ends with # or ~
         "\\|\\(?:\\`.+?[#~]\\'\\)"
         ))
  :bind
  ("C-x C-f" . counsel-find-file)
  ("C-h f"   . counsel-describe-function)
  ("C-h v"   . counsel-describe-variable)
  ("C-c g f" . counsel-git)
  ("C-c g g" . counsel-git-grep)
  ("C-c g l" . counsel-git-log)
  ("C-c g a" . counsel-ag)
  ("C-c g g" . counsel-grep)
  :hook
  (after-init . counsel-mode))
6.5.7.16 undo tree
(use-package undo-tree
  :pin gnu
  :diminish
  :hook
  (after-init . global-undo-tree-mode))
6.5.7.17 zop-to-char

Visual zap-to-char.

(use-package zop-to-char
  :bind
  ([remap zap-to-char] . zop-to-char))

6.5.8 Development

6.5.8.1 prog
(require 'prog-mode)
(define-key prog-mode-map (kbd "C-c C-c") #'y/comment-or-uncomment)
6.5.8.2 asm
6.5.8.3 cmake

cmake-mode is major-mode for editing CMake sources.

(use-package cmake-mode)
6.5.8.5 c/c++
  1. keybind
    (require 'cc-mode)
    (define-key c-mode-map (kbd "C-c C-c") #'y/comment-or-uncomment)
    (define-key c++-mode-map (kbd "C-c C-c") #'y/comment-or-uncomment)
    
  2. c++ modern font

    modern-cpp-font-lock Syntax highlighting support for "Modern C++" - until C++20 and Technical Specification.

    (use-package modern-cpp-font-lock
      :diminish modern-c++-font-lock-mode
      :hook
      (c++-mode . modern-c++-font-lock-mode))
    
    (add-to-list 'auto-mode-alist '("\\.cpp\\'" . c++-mode))
    (add-to-list 'auto-mode-alist '("\\.hh\\'" . c++-mode))
    (add-to-list 'auto-mode-alist '("\\.hpp\\'" . c++-mode))
    
  3. c/c++ style

    Use c-guess-no-install and c-guess-view to generate style template. Read variable c-offsets-alist for more details.

    (defconst y/c-style-basic
      '((c-tab-always-indent . nil)
        (c-basic-offset . 4)
        (c-offsets-alist
         (block-close . 0)       ; Guessed value
         (brace-list-close . 0)  ; Guessed value
         (brace-list-entry . 0)  ; Guessed value
         (brace-list-intro . +)  ; Guessed value
         (class-close . 0)       ; Guessed value
         (defun-block-intro . +) ; Guessed value
         (defun-close . -)       ; Guessed value
         (defun-open . -)        ; Guessed value
         (else-clause . 0)       ; Guessed value
         (inclass . +)           ; Guessed value
         (statement . 0)         ; Guessed value
         (statement-block-intro . +) ; Guessed value
         (statement-cont . +)    ; Guessed value
         (substatement . +)      ; Guessed value
         (topmost-intro . 0)     ; Guessed value
         (access-label . -)
         (annotation-top-cont . 0)
         (annotation-var-cont . +)
         (arglist-close . c-lineup-close-paren)
         (arglist-cont c-lineup-gcc-asm-reg 0)
         (arglist-cont-nonempty . c-lineup-arglist)
         (arglist-intro . +)
         (block-open . 0)
         (brace-entry-open . 0)
         (brace-list-open . 0)
         (c . c-lineup-C-comments)
         (case-label . 0)
         (catch-clause . 0)
         (class-open . 0)
         (comment-intro . c-lineup-comment)
         (composition-close . 0)
         (composition-open . 0)
         (cpp-define-intro c-lineup-cpp-define +)
         (cpp-macro . -1000)
         (cpp-macro-cont . +)
         (do-while-closure . 0)
         (extern-lang-close . 0)
         (extern-lang-open . 0)
         (friend . 0)
         (func-decl-cont . +)
         (incomposition . +)
         (inexpr-class . +)
         (inexpr-statement . +)
         (inextern-lang . +)
         (inher-cont . c-lineup-multi-inher)
         (inher-intro . +)
         (inlambda . c-lineup-inexpr-block)
         (inline-close . 0)
         (inline-open . 0)
         (inmodule . +)
         (innamespace . 0)
         (knr-argdecl . 0)
         (knr-argdecl-intro . 0)
         (label . 0)
         (lambda-intro-cont . +)
         (member-init-cont . c-lineup-multi-inher)
         (member-init-intro . +)
         (module-close . 0)
         (module-open . 0)
         (namespace-close . 0)
         (namespace-open . 0)
         (objc-method-args-cont . c-lineup-ObjC-method-args)
         (objc-method-call-cont c-lineup-ObjC-method-call-colons c-lineup-ObjC-method-call +)
         (objc-method-intro .
                            [0])
         (statement-case-intro . +)
         (statement-case-open . 0)
         (stream-op . c-lineup-streamop)
         (string . -1000)
         (substatement-label . 0)
         (substatement-open . 0)
         (template-args-cont c-lineup-template-args +)
         (topmost-intro-cont . c-lineup-topmost-intro-cont)))
      "y/c-basic")
    (c-add-style "y/c-basic" y/c-style-basic)
    
    (defconst y/c-style-linux
      '((c-tab-always-indent . nil) ; manualy added
        (c-basic-offset . 8)     ; Guessed value
        (c-offsets-alist
         (block-close . 0)       ; Guessed value
         (brace-list-close . 0)  ; Guessed value
         (brace-list-entry . 0)  ; Guessed value
         (brace-list-intro . +)  ; Guessed value
         (class-close . 0)       ; Guessed value
         (defun-block-intro . +) ; Guessed value
         (defun-close . -)       ; Guessed value
         (defun-open . -)        ; Guessed value
         (else-clause . 0)       ; Guessed value
         (inclass . +)           ; Guessed value
         (statement . 0)         ; Guessed value
         (statement-block-intro . +) ; Guessed value
         (statement-cont . +)    ; Guessed value
         (substatement . +)      ; Guessed value
         (topmost-intro . 0)     ; Guessed value
         (access-label . -)
         (annotation-top-cont . 0)
         (annotation-var-cont . +)
         (arglist-close . c-lineup-close-paren)
         (arglist-cont c-lineup-gcc-asm-reg 0)
         (arglist-cont-nonempty . c-lineup-arglist)
         (arglist-intro . c-lineup-arglist-intro-after-paren)
         (block-open . 0)
         (brace-entry-open . 0)
         (brace-list-open . 0)
         (c . c-lineup-C-comments)
         (case-label . 0)
         (catch-clause . 0)
         (class-open . 0)
         (comment-intro . c-lineup-comment)
         (composition-close . 0)
         (composition-open . 0)
         (cpp-define-intro c-lineup-cpp-define +)
         (cpp-macro . -1000)
         (cpp-macro-cont . +)
         (do-while-closure . 0)
         (extern-lang-close . 0)
         (extern-lang-open . 0)
         (friend . 0)
         (func-decl-cont . +)
         (incomposition . +)
         (inexpr-class . +)
         (inexpr-statement . +)
         (inextern-lang . +)
         (inher-cont . c-lineup-multi-inher)
         (inher-intro . +)
         (inlambda . c-lineup-inexpr-block)
         (inline-close . 0)
         (inline-open . 0)
         (inmodule . +)
         (innamespace . +)
         (knr-argdecl . 0)
         (knr-argdecl-intro . 5)
         (label . 0)
         (lambda-intro-cont . +)
         (member-init-cont . c-lineup-multi-inher)
         (member-init-intro . +)
         (module-close . 0)
         (module-open . 0)
         (namespace-close . 0)
         (namespace-open . 0)
         (objc-method-args-cont . c-lineup-ObjC-method-args)
         (objc-method-call-cont c-lineup-ObjC-method-call-colons c-lineup-ObjC-method-call +)
         (objc-method-intro . [0])
         (statement-case-intro . +)
         (statement-case-open . +)
         (stream-op . c-lineup-streamop)
         (string . -1000)
         (substatement-label . 0)
         (substatement-open . 0)
         (template-args-cont c-lineup-template-args +)
         (topmost-intro-cont first c-lineup-topmost-intro-cont c-lineup-gnu-DEFUN-intro-cont)))
      "y/c-linux")
    (c-add-style "y/c-linux" y/c-style-linux)
    
    (defconst y/c-style-alibaba
      '((c-tab-always-indent . nil) ; manualy added
        (c-basic-offset . 4)     ; Guessed value
        (c-offsets-alist
         (block-close . 0)       ; Guessed value
         (brace-list-close . 0)  ; Guessed value
         (brace-list-entry . 0)  ; Guessed value
         (brace-list-intro . +)  ; Guessed value
         (class-close . 0)       ; Guessed value
         (defun-block-intro . +) ; Guessed value
         (defun-close . -)       ; Guessed value
         (defun-open . -)        ; Guessed value
         (else-clause . 0)       ; Guessed value
         (inclass . +)           ; Guessed value
         (statement . 0)         ; Guessed value
         (statement-block-intro . +) ; Guessed value
         (statement-cont . +)    ; Guessed value
         (substatement . +)      ; Guessed value
         (topmost-intro . 0)     ; Guessed value
         (access-label . -)
         (annotation-top-cont . 0)
         (annotation-var-cont . +)
         (arglist-close . c-lineup-close-paren)
         (arglist-cont c-lineup-gcc-asm-reg 0)
         (arglist-cont-nonempty . c-lineup-arglist)
         (arglist-intro . +)
         (block-open . 0)
         (brace-entry-open . 0)
         (brace-list-open . 0)
         (c . c-lineup-C-comments)
         (case-label . 0)
         (catch-clause . 0)
         (class-open . 0)
         (comment-intro . c-lineup-comment)
         (composition-close . 0)
         (composition-open . 0)
         (cpp-define-intro c-lineup-cpp-define +)
         (cpp-macro . -1000)
         (cpp-macro-cont . +)
         (do-while-closure . 0)
         (extern-lang-close . 0)
         (extern-lang-open . 0)
         (friend . 0)
         (func-decl-cont . +)
         (incomposition . +)
         (inexpr-class . +)
         (inexpr-statement . +)
         (inextern-lang . +)
         (inher-cont . c-lineup-multi-inher)
         (inher-intro . +)
         (inlambda . c-lineup-inexpr-block)
         (inline-close . 0)
         (inline-open . 0)
         (inmodule . +)
         (innamespace . 0)
         (knr-argdecl . 0)
         (knr-argdecl-intro . 0)
         (label . 0)
         (lambda-intro-cont . +)
         (member-init-cont . c-lineup-multi-inher)
         (member-init-intro . +)
         (module-close . 0)
         (module-open . 0)
         (namespace-close . 0)
         (namespace-open . 0)
         (objc-method-args-cont . c-lineup-ObjC-method-args)
         (objc-method-call-cont c-lineup-ObjC-method-call-colons c-lineup-ObjC-method-call +)
         (objc-method-intro .
                            [0])
         (statement-case-intro . +)
         (statement-case-open . 0)
         (stream-op . c-lineup-streamop)
         (string . -1000)
         (substatement-label . 0)
         (substatement-open . 0)
         (template-args-cont c-lineup-template-args +)
         (topmost-intro-cont . c-lineup-topmost-intro-cont)))
      "y/c-alibaba")
    (c-add-style "y/c-alibaba" y/c-style-alibaba)
    
    (defun y/c-style-hook()
      "Config c/c++ style depends on file pathname"
      (when (buffer-file-name)
        (cond ((or (string-match "/pangu/" (buffer-file-name))
                   (string-match "/apsara/" (buffer-file-name))
                   (string-match "/stone/" (buffer-file-name)))
               (c-set-style "y/c-alibaba"))
              ((or (string-match "/linux.*/" (buffer-file-name)))
               (c-set-style "y/c-linux")
               ;; Linux use real tab. Auto buffer-local.
               (setq indent-tabs-mode t))
              (t ;; all default to y/c-basic
               (c-set-style "y/c-basic")))))
    (add-hook 'c-mode-common-hook 'y/c-style-hook)
    
  4. c/c++ call-graph

    C++ call graph: https://github.com/beacoder/call-graph

    (use-package call-graph
      :bind
      (:map c-mode-base-map
            ("C-x c g" . call-graph)))
    
  5. cwarn
    (use-package cwarn
      :diminish cwarn)
    
6.5.8.6 projectile

Projectile is a project interaction library for Emacs.

Counsel-projectile is a IVY ui for projectile.

(require 'y-project)
6.5.8.7 sr-speedbar
(use-package sr-speedbar
  :init
  (setq sr-speedbar-auto-refresh t
        speedbar-use-images nil
        sr-speedbar-width 10
        sr-speedbar-max-width 20
        sr-speedbar-skip-other-window-p t)
  :bind
  ("C-c w b" . sr-speedbar-toggle)
  ("C-c b" . sr-speedbar-select-window))
(use-package function-args
  :config
  ;; (fa-config-default)
  )
6.5.8.8 whitespace

whitespace render a space, tabs, newlines to a visible glyph.

(defun y/whitespace-color(&optional theme)
  "Set whitespace color depends on current theme THEME."
  (custom-set-faces
   '(whitespace-newline ((t (:foreground "#75715E" :background nil))))
   ;; '(whitespace-newline ((t (:foreground "#424242"))))
   '(whitespace-tab ((t (:foreground "#75715E" :background nil))))
   '(whitespace-space ((t (:foreground "#75715E" :background nil))))))

(use-package whitespace
  :diminish
  :config
  (progn
    (setq whitespace-line-column 80) ;; limit line length
    (setq whitespace-style
          '(face trailing spaces tabs lines-tail newline
                 space-before-tab space-before-tab::tab
                 space-before-tab::space space-after-tab::tab
                 space-after-tab::space space-after-tab
                 newline-mark space-mark tab-mark))
    (setq whitespace-display-mappings
          '((space-mark 32 [183] [46])
            (newline-mark 10 [182 10])
            ;; (tab-mark 9 [?. 9] [92 9])
            (tab-mark   ?\t   [?\xBB ?\t] [?\\ ?\t])))
    (y/whitespace-color))
  :hook
  (prog-mode . whitespace-mode)
  (text-mode . whitespace-mode)
  (protobuf-mode . whitespace-mode)
  (before-save . whitespace-cleanup))

;; theme has no hook. use advice.
6.5.8.9 yasnippet

YASnippet is a template system for Emacs. It allows you to type an abbreviation and automatically expand it into function templates. Bundled language templates include: C, C++, C#, Perl, Python, Ruby, SQL, LaTeX, HTML, CSS and more.

(use-package yasnippet
  :diminish yas-minor-mode
  :hook
  (prog-mode . yas-minor-mode))

(use-package yasnippet-snippets)

6.5.9 Orgmode

6.5.9.1 basic
(require 'y-org)
6.5.9.2 pomodoro
(use-package org-pomodoro
  :pin melpa
  :init
  (setq org-pomodoro-length 30
        org-pomodoro-format "%s"))

(use-package redtick)
6.5.9.3 publish
(require 'y-publish)

6.5.10 Platform

6.5.10.1 windows
(when (string-equal system-type "windows-nt")
  (unless (getenv "HOME")
    (warn "Maybe you forgot to set environment variable HOME."))

  ;; M-w: paste, bind to kill-ring-save
  (w32-register-hot-key [M-w])
  ;; C-M-n: sp-up-sexp
  (w32-register-hot-key [C-M-n]))
6.5.10.2 mac
(use-package exec-path-from-shell
  :demand t
  :init
  (when (memq window-system '(mac ns x))
    (exec-path-from-shell-initialize)))

;; Copy from https://github.com/bbatsov/prelude/blob/master/core/prelude-macos.el
(defvar mac-command-modifier)
(defvar mac-option-modifier)
(defun y/swap-meta-and-super()
  "Swap the mapping of Meta and Super.
Very useful for people using their Mac with a
Windows external keyboard from time to time."
  (interactive)
  (if (eq mac-command-modifier 'super)
      (progn
        (setq mac-command-modifier 'meta)
        (setq mac-option-modifier 'super)
        (message "Command is now bound to META and Option is bound to SUPER."))
    (setq mac-command-modifier 'super)
    (setq mac-option-modifier 'meta)
    (message "Command is now bound to SUPER and Option is bound to META.")))

;; (define-key global-map (kbd "C-c m s") 'y/swap-meta-and-super)

;; map super to meta
(setq mac-command-modifier 'meta)
6.5.10.3 linux

Optimize fcitx behavior.

(when (executable-find "fcitx-remote")
  (use-package fcitx
    :init
    (fcitx-aggressive-setup)
    (fcitx-prefix-keys-turn-on)
    (fcitx-prefix-keys-add "C-x" "C-c" "C-h" "M-s" "M-o")
    ;; (setq fcitx-use-dbus t)
    ))

6.5.11 File mode

6.5.11.1 protobuf
(use-package protobuf-mode)
6.5.11.2 log view
(use-package logview)

6.5.12 Footer

Refer to header for more details.

;; mode line cleanup
(diminish 'y/keymap-mode)

;;; config.el ends here

6.5.13 Appendix

Gook Luck, Guys.

脚注:

1

Emacs是Editor MACroS的缩写,见这里