基于emacs打造自己的编辑器

2022-11-17  本文已影响0人  扶海狐

emacs 学习

[TOC]

0 准备工作

0.1 首先修改镜像源

修改.emacs.d/init.el文件,在文件开始添加如下内容

(require 'package)
(add-to-list 'package-archives
         '("melpa" . "http://mirrors.tuna.tsinghua.edu.cn/elpa/melpa/") t)

(setq package-archives '(("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/")))

0.2 帮助命令

1 where-is 命令 C-h w 函数名称

2 describe-key 命令 C-h k查看快捷键绑定的函数

3 describe-function 命令 C-h f查看函数的详细信息

4 describe-variable 查看变量的命令

5 describe-mode 查看当前mode的详细信息和快捷键

6 M-x decribe-face :: 查看某个mode的文档

7 describe-bindings 命令 列出当前mode组合键开头的快捷键

8 view-lossage :: 查看最近100个按键组合

9 info–emacs :: emacs的详细信息

1 编辑器

1.1 标签页

emacs(27.1之后)内置了两种类似的mode,tab-line-modetab-bar-mode,两种minor mode都可以实现。
!tab bar参考地址
!tab line参考地址
tab-bar-mode更简单一些,先用这个:

M-x tab-line-mode
C-x LEFT (previous-buffer)
C-x RIGHT (next-buffer)
(global-tab-line-mode t)

1.2 行号

'''linum-mode显示太丑了
M-x display-line-numbers-mode
(global-display-line-numbers-mode t)

1.3 目录树

使用插件sr-speedbar实现目录树,目录树在window中显示,嵌在frame中

M-x package-install RET sr-speedbar
sr-speedbar-open    Open sr-speedbar window.
sr-speedbar-close   Close sr-speedbar window.
sr-speedbar-toggle  Toggle sr-speedbar window.
sr-speedbar-select-window   select sr-speedbar window.
sr-speedbar-refresh-turn-on Turn on refresh speedbar content.
sr-speedbar-refresh-turn-off    Turn off refresh speedbar content.
sr-speedbar-refresh-toggle  Toggle refresh speedbar content.
(require 'sr-speedbar)
(setq speedbar-show-unknown-files t) 
(setq speedbar-use-images nil) 
(setq speedbar-tag-hierarchy-method nil)
(setq sr-speedbar-right-side nil)
(setq sr-speedbar-width 25)
(setq dframe-update-speed t)
(global-set-key (kbd "<f5>") (lambda()
          (interactive)
          (sr-speedbar-toggle)))

1.4 markdown

参考文档1
参考文档2

M-x package-install RET markdown-mode RET
M-x package-install RET markdown-preview-mode RET
C-c C-s l   [文本](url)
C-c C-s L   [文本](label)
C-c C-s u   直接插入链接
C-c C-s e   斜体
C-c C-s s   粗体
C-c C-s b   缩进
C-c C-s p   跳格
C-c C-t [1-6]   1-6级标题
C-c C-c m   显示对应的html
C-c C-c p   显示对应的html并显示效果(需配置)
(setq markdown-command "/usr/bin/pandoc")

1.5 补全

使用company-modecompany-mode是Emacs的文本补全框架。这个名字代表“完成任何事情”。它使用可插拔的后端和前端来检索和显示完成候选。它附带几个后端支持,如Elisp、Clang、Semantic、Eclim、Ropemacs、Ispell、CMake、BBDB、Yasnippet、dabbrev、etags、gtags、文件、关键字等。

通过MELPA安装:

M-x package-install RET company 

在全局激活:

(require 'company)
(add-hook 'after-init-hook 'global-company-mode)

补全功能会在键入几个字母后将自动触发。使用M-nM-p进行上下选择,<return>完成选择或<tab>完成公共部分。用C-sC-rC-o搜索完成项。按M-(数字)快速完成前10位候选人中的一位。候选项显示出来后,按<f1>显示所选候选项的文档,或按C-w查看其来源。并非所有后端都支持这一点。

变量company-backends指定company-mode模式用于为您检索完成候选项的后端列表。

1.6 分屏

横向分屏

;; 向右分屏
C-x 3
;;增加宽度
C-x }
;;减少宽度 
C-x { 

纵向分屏

;;向上分屏
C-x 2
C-x ^

关闭分屏

C-x 1
C-x 0

1.7 高亮标记

参考文档1
参考文档2

M-x package-install RET symbol-overlay RET
# M-x package-install RET highlight-symbol RET
(require 'symbol-overlay)
(global-set-key (kbd "M-i") 'symbol-overlay-put)
(global-set-key (kbd "M-n") 'symbol-overlay-switch-forward)
(global-set-key (kbd "M-p") 'symbol-overlay-switch-backward)
(global-set-key (kbd "<f7>") 'symbol-overlay-mode)
(global-set-key (kbd "<f8>") 'symbol-overlay-remove-all)
"i" -> symbol-overlay-put                ; 高亮或取消高亮当前symbol
"n" -> symbol-overlay-jump-next          ; 跳转到下一个位置
"p" -> symbol-overlay-jump-prev          ; 跳转到上一个位置
"w" -> symbol-overlay-save-symbol        ; 复制当前symbol
"t" -> symbol-overlay-toggle-in-scope    ; 切换高亮范围到作用域
"e" -> symbol-overlay-echo-mark          ; 撤销上一次跳转
"d" -> symbol-overlay-jump-to-definition ; 跳转到定义
"s" -> symbol-overlay-isearch-literally  ; 切换为isearch并搜索当前symbol
"q" -> symbol-overlay-query-replace      ; 查找替换当前symbol
"r" -> symbol-overlay-rename             ; 对symbol直接重命名

2 IDE

2.1 直接使用最流行的配置方案

参考文档

M-x package-list-package
git clone https://github.com/tuhdo/emacs-c-ide-demo.git ~/.emacs.d
(require 'setup-helm-gtags)
;; (require 'setup-ggtags)
M-x load-file RET init.el. 

2.2 在自己的编辑器上改造

2.2.1 代码目录

参考1.3

2.2.2 代码阅读

2.2.2.1 安装插件

可以选择ggtagshelm-gtags,两者都是基于gtags的,helm-gtags更高级更复杂一些。先从简单的ggtags入手。

(require 'ggtags)
(add-hook 'c-mode-common-hook
          (lambda ()
            (when (derived-mode-p 'c-mode 'c++-mode 'java-mode 'asm-mode)
              (ggtags-mode 1))))
(define-key ggtags-mode-map (kbd "C-c g s") 'ggtags-find-other-symbol)
(define-key ggtags-mode-map (kbd "C-c g h") 'ggtags-view-tag-history)
(define-key ggtags-mode-map (kbd "C-c g r") 'ggtags-find-reference)
(define-key ggtags-mode-map (kbd "C-c g f") 'ggtags-find-file)
(define-key ggtags-mode-map (kbd "C-c g c") 'ggtags-create-tags)
(define-key ggtags-mode-map (kbd "C-c g u") 'ggtags-update-tags)

(define-key ggtags-mode-map (kbd "M-,") 'pop-tag-mark)

在主机上安装gtags

sudo yum install -y global

给工程生成数据库

# 切到工程根目录
cd ~/vpp;
gtags
ls G*
GPATH   GRTAGS  GTAGS

tag是源代码中实体的名称。一个实体可以是一个变量、一个方法定义、一个include运算符……一个标记包含诸如标记的名称(变量的名称、类、方法)、源代码中该标记的位置及其所属的文件等信息。

2.2.2.2 在当前文件中查找定义

Imenu工具提供了一种在文件中按名称查找主要定义的方法,如函数定义、变量定义等。ggtags可以集成Imenu:

(setq-local imenu-create-index-function #'ggtags-build-imenu-index)

TODO

2.2.2.3 在工程中查找定义

默认的,在 ggtags-mode模式下M-. 运行ggtags-find-tag-dwim . 而命令ggtags-find-tag-dwim会根据上下文跳转到对应的tag:

可以通过M-,触发pop-tag-mark跳回原来的位置(在那里调用了ggtags-find-tag-dwim)。

还可以在在空白处调用M-.时查找任意tag定义。在空白处。提示会要求您输入tag匹配,这里支持正则表达式。

如果ggtags给出一个候选列表,可以使用M-n移动到下一个候选,使用M-p移动回上一个候选。使用M-g s在候选缓冲区列表上调用Isearch

2.2.2.4 在工程中查找引用

运行ggtags-find-tag-dwim(通过快捷键M-.触发),跳转到定义或ggtags-find-reference(通过快捷键C-c g r触发),后者只查找引用。

ggtags-find-reference会给出一个候选列表,可以使用M-n移动到下一个候选,使用M-p移动回上一个候选,buffer内容会随之变化,非常好用。

2.2.2.5 在工程中查找文件

运行ggtags-find-file(通过快捷键C-c g f触发),需要输入文件名或关键字,也可以查找标识符。

2.2.2.6 查看浏览过的tag

一种是参考2.2.2.3,通过M-,触发pop-tag-mark,向前跳转;一种是通过C-c g h触发ggtags-view-tag-history,会列出所有访问过的tag,包括文件和tag。可以通过M-nM-p上下移动进行选择。

2.2.3 代码补全

2.2.3.1 记忆补全

参考1.5

2.2.3.2 源码补全

使用Clang

TODO

2.2.4 源码统计

TODO

3 emacs 定制和扩展

参考文档

emacs提供一部分默认配置,一些很好的特性是被警用或者被影藏的,比如:ibuffer、语义分析等。可能为了对新用户来说更加友好让他看起来想一个普通的编辑器,比如普通用户不需要自动匹配括号、花括号这些符号。

Emacs天生是一个可扩展的系统,大家编写插件来提高Emacs并分享给别人。这些插件提高了Emacs的各个方面:提供已有的和新出现的一些编辑特性,集成第三方工具,增加第三方编程语言支持,改善Emacs外观等等。如果没有扩展能力,Emacs仅仅只是另一个能提供几个有用特性的有马马虎虎的编辑器而已,但无法满足人们的需求,因为不同的人有不同的需求,而Emacs维护者无法将这些需求全部集成到Emacs中。但是通过扩展能力,人们可以将Emacs改造成他们自己想要的样子,非常类似于Lisp。

不同于其他编辑器鼓励用户尽量保持默认配置,Emacs鼓励用户尽量自己定制和扩展Emacs。

过去有一段时间,Emacs没有包管理工具,并且没有这么多学习资源,要定制Emacs非常不容易,因为定制Emacs可能需要你阅读大量的Emacs Lisp手册。大部分没有那么多空闲时间。我学习定制Emacs的办法是,当我遇到一些问题的时候,拷贝少量代码来解决这些特定问题,然后将它们都粘贴到我的~/.emacs文件中。我也必须手动下载插件包,然后在~/.emacs中手动加载它们。而现在有了包管理工具,就可以不用花那么多时间去收集配置,阅读大量Emacs lisp手册,也能比较轻松的定制自己需要的Emacs

3.1 Lisp基本语法

3.1.1 表达式

被执行的代码是一对括号和括号里的表达式:(...)。第一个元素肯定会被计算执行,Lisp环境(这里就是Emacs)会判断第一个标识符是3种表达式的哪一个:

总之,那些不同的表达式其实都是一种类型:函数表达式。函数表达式是一种通用表达式,特殊表达式中一种有自己规则的函数表达式;宏表达式是一种能生成代码的函数表达式。

3.1.2 数据:有两种形式的数据

3.2 常用函数

介绍一些常用函数,其他函数可以通过C-h fC-h v获取帮助文档学习。

3.2.1 函数:(setq [ SYM VAL ]...)

Comment: A really fundamental function for customizing Emacs settings. An Emacs setting is really just a variable. Emacs has GUI for changing setting, but setq a variable is also equivalent.

(setq global-mark-ring-max 50000)

给每个SYM赋值。标识符SYM是变量,不会被计算。值VAL是表达式,是会被计算的。所以(setq x (1+y))是将1+y的值赋给x。直到第一个SYM被赋值之后,第二个VAL才会被计算,以此类推;每个VAL都可以使用前期在setq-中赋值的变量值

3.2.2 函数:(load FILE &optional NOERROR NOMESSAGE NOSUFFIX MUST-SUFFIX)

(load (substitute-in-file-name "$HOME/.emacs.d/module")) ;; first try to load module.elc; if not found, try to load module.el
(load (substitute-in-file-name "$HOME/.emacs.d/module.el")) ;; only load module.el
(load (substitute-in-file-name "$HOME/.emacs.d/module.elc")) ;; only load module.elc
(load "module") ; search for the file module.el or module.elc in variable load-path

3.2.3 函数:(require FEATURE &optional FILENAME NOERROR)

(require 'volatile-highlights)

加载已安装的volatile-highlights包。

If feature FEATURE is not loaded, load it from FILENAME. If FEATURE is not a member of the list `features', then the feature is not loaded; so load the file FILENAME. If FILENAME is omitted, the printname of FEATURE is used as the file name, and `load' will try to load this name appended with the suffix `.elc' or `.el', in that order. The name without appended suffix will not be used. See `get-load-suffixes' for the complete list of suffixes. If the optional third argument NOERROR is non-nil, then return nil if the file is not found instead of signaling an error. Normally the return value is FEATURE. The normal messages at start and end of loading FILENAME are suppressed.

3.2.4 函数:(provide FEATURE &optional SUBFEATURES)

(provide 'setup-editing)

然后,即使您使用加载功能加载它,它也不会被激活。要激活,您必须执行(provide 'setup-editing)

Announce that FEATURE is a feature of the current Emacs. The optional argument SUBFEATURES should be a list of symbols listing particular subfeatures supported in this version of FEATURE.

3.2.5 函数:(add-to-list LIST-VAR ELEMENT &optional APPEND COMPARE-FN)

(add-to-list 'load-path "~/.emacs.d/personal") ; add personal to load-path,
                                               ; so "load" function can search for files in it
Add ELEMENT to the value of LIST-VAR if it isn't there yet. The test for presence of ELEMENT is done with `equal', or with COMPARE-FN if that's non-nil. If ELEMENT is added, it is added at the beginning of the list, unless the optional argument APPEND is non-nil, in which case ELEMENT is added at the end.

The return value is the new value of LIST-VAR.

This is handy to add some elements to configuration variables, but please do not abuse it in Elisp code, where you are usually better off using `push' or `cl-pushnew'.

If you want to use `add-to-list' on a variable that is not defined until a certain package is loaded, you should put the call to `add-to-list' into a hook function that will be run only after loading the package. `eval-after-load' provides one way to do this. In some cases other hooks, such as major mode hooks, can do the job.

3.2.6 函数:(add-hook HOOK FUNCTION &optional APPEND LOCAL)

(add-hook 'prog-mode-hook 'linum-mode)

添加linum-mode函数之后,每次你进入一个prog-mode(所有编程主模式都会继承),都会触发在emacs左侧显示行号。编程模式包括c-mode、asm-mode、java-mode等

Add to the value of HOOK the function FUNCTION. FUNCTION is not added if already present. FUNCTION is added (if necessary) at the beginning of the hook list unless the optional argument APPEND is non-nil, in which case FUNCTION is added at the end.

The optional fourth argument, LOCAL, if non-nil, says to modify the hook's buffer-local value rather than its global value. This makes the hook buffer-local, and it makes t a member of the buffer-local value. That acts as a flag to run the hook functions of the global value as well as in the local value.

HOOK should be a symbol, and FUNCTION may be any valid function. If HOOK is void, it is first set to nil. If HOOK's value is a single function, it is changed to a list of functions.

3.2.7 函数:(global-set-key KEY COMMAND)

(global-set-key (kbd "C-x C-b") 'ibuffer) ;; 将“C-x C-b”绑定到ibuffer命令
(global-set-key "\C-x\C-b" 'ibuffer)  ;; 将“C-x C-b”绑定到ibuffer命令,但修改键必须用反斜杠转义 
(global-set-key [?\C-x?\C-b] 'ibuffer) ;; 使用vector而不是字符串

推荐使用(kbd…)函数,因为我们可以使用我们熟悉的键符号编写键绑定,而无需添加不必要的字符。Vector在其他语言中就是数组,用于映射功能键,如[left]、[right]、[up]、[down]、[f1]…[f12]。但现在,您还可以在(kbd…)函数中使用尖括号(<>)映射功能键:

(global-set-key (kbd "<f3>") 'kmacro-start-macro-or-insert-counter)

以下是常用的功能键(请记住用一对尖括号将它们括起来):

按键 描述
left, up, right, down 光标箭头
begin, end, home, next, prior 其他光标重新定位键 prior=PageUp,next=PageDown
select, print, execute, backtab 其他键 backtab=S-TAB,C-iso-tab
f1, f2, ... F35 键盘顶部的编号功能键
kp-add, kp-subtract, kp-multiply, kp-divide 带名称或标点符号的键盘键(在普通键盘的右侧)
begin, end, home, next, prior 其他光标重新定位键 prior=PageUp,next=PageDown
Give KEY a global binding as COMMAND. COMMAND is the command definition to use; usually it is a symbol naming an interactively-callable function. KEY is a key sequence; noninteractively, it is a string or vector of characters or event types, and non-ASCII characters with codes above 127 (such as ISO Latin-1) can be included if you use a vector.

Note that if KEY has a local binding in the current buffer, that local binding will continue to shadow any global binding that you make with this function.

3.2.8 函数:(define-key KEYMAP KEY DEF)

;; Dired uses "e", "f" or RET to open a file
;; you can reuse one of these keys for different purpose
;; for example, you can bind it to wdired-change-to-wdired-mode
;; wdired-change-to-wdired-mode allows you to edit your Dired buffer
;; like a normal text buffer, such as edit file/directory names,
;; permission bits.. and then commit the changes to disk.
;;
;; "e" is short for "edit"
;; After finish your editing, "C-c C-c" to commit, "C-c C-k" to abort
(define-key dired-mode-map (kbd "e") 'wdired-change-to-wdired-mode)
In KEYMAP, define key sequence KEY as DEF. KEYMAP is a keymap.

KEY is a string or a vector of symbols and characters, representing a sequence of keystrokes and events. Non-ASCII characters with codes above 127 (such as ISO Latin-1) can be represented by vectors. Two types of vector have special meanings: [remap COMMAND] remaps any key binding for COMMAND. [t] creates a default definition, which applies to any event with no other definition in KEYMAP.

DEF is anything that can be a key's definition: nil (means key is undefined in this keymap), a command (a Lisp function suitable for interactive calling), a string (treated as a keyboard macro), a keymap (to define a prefix key), a symbol (when the key is looked up, the symbol will stand for its function definition, which should at that time be one of the above, or another symbol whose function definition is used, etc.), a cons (STRING . DEFN), meaning that DEFN is the definition (DEFN should be a valid definition in its own right), or a cons (MAP . CHAR), meaning use definition of CHAR in keymap MAP, or an extended menu item definition. (See info node `(elisp)Extended Menu Items'.)

If KEYMAP is a sparse keymap with a binding for KEY, the existing binding is altered. If there is no binding for KEY, the new pair binding KEY to DEF is added at the front of KEYMAP.

3.2.9 函数:(defalias SYMBOL DEFINITION &optional DOCSTRING)

(defalias 'yes-or-no-p 'y-or-n-p) ; y or n is enough
(defalias 'list-buffers 'ibuffer) ; always use ibuffer

                                        ; elisp
(defalias 'eb 'eval-buffer)
(defalias 'er 'eval-region)
(defalias 'ed 'eval-defun)

                                        ; minor modes
(defalias 'wsm 'whitespace-mode)
Set SYMBOL's function definition to DEFINITION. Associates the function with the current load file, if any. The optional third argument DOCSTRING specifies the documentation string for SYMBOL; if it is omitted or nil, SYMBOL uses the documentation string determined by DEFINITION.

Internally, this normally uses `fset', but if SYMBOL has a `defalias-fset-function' property, the associated value is used instead.

The return value is undefined.

3.2.10 函数:(mapc FUNCTION SEQUENCE)

;; load every .el file inside ~/.emacs.d/custom/
(mapc 'load (directory-files "~/.emacs.d/custom" t ".*\.el"))
Apply FUNCTION to each element of SEQUENCE for side effects only. Unlike `mapcar', don't accumulate the results. Return SEQUENCE. SEQUENCE may be a list, a vector, a bool-vector, or a string.

3.2.11 宏:(defun NAME ARGLIST &optional DOCSTRING DECL &rest BODY)

(defun demo ()
  (message "Hello World" number string))

创建一个命令 (在 M-x可用):

(defun demo ()
  (interactive)
  (message "Hello World"))

interactive是一种特殊的表达式,它将函数转换为命令,并允许命令接受各种类型的前缀参数,例如数字、字符串、符号、缓冲区名称…您可以使用C-h f键入interactive来了解更多信息。

Define NAME as a function. The definition is (lambda ARGLIST [/DOCSTRING/] BODY…). See also the function `interactive'. DECL is a declaration, optional, of the form (declare DECLS…) where /DECLS is a list of elements of the form (PROP . VALUES). These are interpreted according to `defun-declarations-alist'. The return value is undefined.

3.3 常用内置快捷键

3.3.1 C-M-f

forward-sexp绑定,向前移动到一个对应的表达式。

3.3.2 C-M-b

backward-sex绑定,向后移动到一个对应的表达式。

3.3.3 C-M-k

kill-sexp绑定,向前删除表达式内全部内容。

3.3.4 C-M-t

transpose-sex绑定,转置表达式。

3.3.5 C-M-<SPC> or C-M-@

mark-sexp绑定,标记整个表达式内的内容。

3.4 Emacs包管理工具

TODO

上一篇 下一篇

猜你喜欢

热点阅读