我也说说Emacs吧(4)
来源:互联网 发布:淘宝网广场舞长裤 编辑:程序博客网 时间:2024/06/06 00:17
在说基本编辑命令之前,我们先加一个小tip,说说如何将函数和键绑定在一起。
(define-key global-map [?\C-l] 'recenter-top-bottom)
define-key函数需要三个参数,第一个是绑定表的名称,不同的模式下的描述表是不同的。第二个参数是键名,第三个参数是键要绑定的函数名。
移动光标
无模式和有模式概述
emacs是一种无模式的编辑器,这也是除了vi之外大部分编辑器的标准做法。每个输入的字符都会直接输入到缓冲区里。编辑要用到的功能函数,就只好绑定到组合键上,主要是Ctrl键,Esc或Alt键的组合键。
比如,最基本的光标移动。如果有上下左右键,就用上下左右键。没有的话,emacs会用C-f向右,C-b向左,C-n向下一行,C-p向上一行。C-a移动到行首,C-e移动到行尾。
大量使用Ctrl和Alt,Esc键,使得手需要经常移动,小指被过度使用。
而vi的采用正常模式和编辑模式分离来解决这个问题,在正常模式下,不能输入字符,所有的字符都被当成命令来执行。此时,j是下一行,k是上一行,h是向左,l向右。效率要比C-n,C-p,C-b,C-f要高。0到行首,$到行尾。
但是vi的问题就是,需要经常在正常模式和编辑模式来回切换。
spacemacs使用evil来模拟vi的这种模式,而且有些键的绑定与标准emacs有所不同。
光标左右移动
移动光标是最基本的命令了,这其中最基本的是光标左右移动,和上下移动。
我们先学习emacs的标准方式:
* 向右一个字符: C-f (forward-char)
* 向左一个字符: C-b (backward-char)
这两个函数都是用C语言实现的,所以没有lisp源码,目前我们暂时先关注lisp部分。
但是,在spacemacs的默认情况下,这两个绑定已经被取消了。因为spacemacs默认是用vi的模式方案,在正常模式下,使用h键左移,l键右移。
l键和右箭头键,绑定到evil-forward-char函数上. 最终,evil-forward-char还是会调用到forward-char来实现移动的功能的:
(evil-define-motion evil-forward-char (count &optional crosslines noerror) :type exclusive (interactive "<c>" (list evil-cross-lines (evil-kbd-macro-suppress-motion-error))) (cond (noerror (condition-case nil (evil-forward-char count crosslines nil) (error nil))) ((not crosslines) ;; for efficiency, narrow the buffer to the projected ;; movement before determining the current line (evil-with-restriction (point) (save-excursion (evil-forward-char (1+ (or count 1)) t t) (point)) (condition-case err (evil-narrow-to-line (evil-forward-char count t noerror)) (error ;; Restore the previous command (this one never happend). ;; Actually, this preserves the current column if the ;; previous command was `evil-next-line' or ;; `evil-previous-line'. (setq this-command last-command) (signal (car err) (cdr err)))))) (t (evil-motion-loop (nil (or count 1)) (forward-char) ;; don't put the cursor on a newline (when (and evil-move-cursor-back (not evil-move-beyond-eol) (not (evil-visual-state-p)) (not (evil-operator-state-p)) (eolp) (not (eobp)) (not (bolp))) (forward-char))))))
而左箭头和h键,则是调用的evil-backward-char函数:
(evil-define-motion evil-backward-char (count &optional crosslines noerror) :type exclusive (interactive "<c>" (list evil-cross-lines (evil-kbd-macro-suppress-motion-error))) (cond (noerror (condition-case nil (evil-backward-char count crosslines nil) (error nil))) ((not crosslines) ;; restrict movement to the current line (evil-with-restriction (save-excursion (evil-backward-char (1+ (or count 1)) t t) (point)) (1+ (point)) (condition-case err (evil-narrow-to-line (evil-backward-char count t noerror)) (error ;; Restore the previous command (this one never happened). ;; Actually, this preserves the current column if the ;; previous command was `evil-next-line' or ;; `evil-previous-line'. (setq this-command last-command) (signal (car err) (cdr err)))))) (t (evil-motion-loop (nil (or count 1)) (backward-char) ;; don't put the cursor on a newline (unless (or (evil-visual-state-p) (evil-operator-state-p)) (evil-adjust-cursor))))))
上下移动
就是行间移动,标准emacs的方式是:
* 向下一行:C-n (next-line)
* 向上一行:C-p (previous-line)
这两种方式在spacemacs中,在编辑模式下仍然可以使用。但是正常模式下已经被绑定到其他函数上了,因为有更方便的j和k可以用。
(defun next-line (&optional arg try-vscroll) (declare (interactive-only forward-line)) (interactive "^p\np") (or arg (setq arg 1)) (if (and next-line-add-newlines (= arg 1)) (if (save-excursion (end-of-line) (eobp)) ;; When adding a newline, don't expand an abbrev. (let ((abbrev-mode nil)) (end-of-line) (insert (if use-hard-newlines hard-newline "\n"))) (line-move arg nil nil try-vscroll)) (if (called-interactively-p 'interactive) (condition-case err (line-move arg nil nil try-vscroll) ((beginning-of-buffer end-of-buffer) (signal (car err) (cdr err)))) (line-move arg nil nil try-vscroll))) nil)
spacemacs支持在普通模式下使用j来移动到下一行,k来移动到上一行。j绑定的是evil-next-line函数,k绑定的是evil-previous-line函数。
(evil-define-motion evil-next-line (count) :type line (let (line-move-visual) (evil-line-move (or count 1))))(evil-define-motion evil-previous-line (count) :type line (let (line-move-visual) (evil-line-move (- (or count 1)))))
上面两个函数都是对evil-line-move的封装,evil-next-line的参数是正的,evil-previous-line是负的。
(defun evil-line-move (count &optional noerror) (cond (noerror (condition-case nil (evil-line-move count) (error nil))) (t (evil-signal-without-movement (setq this-command (if (>= count 0) #'next-line #'previous-line)) (let ((opoint (point))) (condition-case err (with-no-warnings (funcall this-command (abs count))) ((beginning-of-buffer end-of-buffer) (let ((col (or goal-column (if (consp temporary-goal-column) (car temporary-goal-column) temporary-goal-column)))) (if line-move-visual (vertical-motion (cons col 0)) (line-move-finish col opoint (< count 0))) ;; Maybe we should just `ding'? (signal (car err) (cdr err))))))))))
移动到行首或行尾
很多时候,我们需要移动到行首或行尾,而不是向左或向右一点一点移动。
我们还是先看emacs的标准实现方式:
* 到行首:C-a (move-beginning-of-line) spacemacs支持
* 到行尾:C-e (move-end-of-line) spacemacs不支持
move-beginning-of-line的实现如下:
(defun move-beginning-of-line (arg) (interactive "^p") (or arg (setq arg 1)) (let ((orig (point)) first-vis first-vis-field-value) ;; Move by lines, if ARG is not 1 (the default). (if (/= arg 1) (let ((line-move-visual nil)) (line-move (1- arg) t))) ;; Move to beginning-of-line, ignoring fields and invisible text. (skip-chars-backward "^\n") (while (and (not (bobp)) (invisible-p (1- (point)))) (goto-char (previous-char-property-change (point))) (skip-chars-backward "^\n")) ;; Now find first visible char in the line. (while (and (< (point) orig) (invisible-p (point))) (goto-char (next-char-property-change (point) orig))) (setq first-vis (point)) ;; See if fields would stop us from reaching FIRST-VIS. (setq first-vis-field-value (constrain-to-field first-vis orig (/= arg 1) t nil)) (goto-char (if (/= first-vis-field-value first-vis) ;; If yes, obey them. first-vis-field-value ;; Otherwise, move to START with attention to fields. ;; (It is possible that fields never matter in this case.) (constrain-to-field (point) orig (/= arg 1) t nil)))))
最终会调用到我们后面要学的goto-char函数,通过goto-char跳到真正的位置上。
spacemacs支持vi的方式,在普通模式下,0移动到行首,
evil-end-of-line其实还是要调用move-end-of-line函数来实现功能的。
(evil-define-motion evil-end-of-line (count) :type inclusive (move-end-of-line count) (when evil-track-eol (setq temporary-goal-column most-positive-fixnum this-command 'next-line)) (unless (evil-visual-state-p) (evil-adjust-cursor) (when (eolp) ;; prevent "c$" and "d$" from deleting blank lines (setq evil-this-type 'exclusive))))
移动到缓冲区的头或尾
emacs的标准方式:
* 到缓冲区头 A-< (beginning-of-buffer)
* 到缓冲区尾 A-> (end-of-buffer)
spacemacs支持这两种方式,在正常模式下,还支持”<”键绑定beginning-of-buffer,”>”绑定end-of-buffer的方式。
我们先看下beginning-of-buffer,虽然也是goto-char的封装,但是确实不只是(goto-char 0)这么简单:
(defun beginning-of-buffer (&optional arg) (declare (interactive-only "use `(goto-char (point-min))' instead.")) (interactive "^P") (or (consp arg) (region-active-p) (push-mark)) (let ((size (- (point-max) (point-min)))) (goto-char (if (and arg (not (consp arg))) (+ (point-min) (if (> size 10000) ;; Avoid overflow for large buffer sizes! (* (prefix-numeric-value arg) (/ size 10)) (/ (+ 10 (* size (prefix-numeric-value arg))) 10))) (point-min)))) (if (and arg (not (consp arg))) (forward-line 1)))
end-of-buffer的话,除了goto-char之外,还得考虑recenter的问题
(defun end-of-buffer (&optional arg) (declare (interactive-only "use `(goto-char (point-max))' instead.")) (interactive "^P") (or (consp arg) (region-active-p) (push-mark)) (let ((size (- (point-max) (point-min)))) (goto-char (if (and arg (not (consp arg))) (- (point-max) (if (> size 10000) ;; Avoid overflow for large buffer sizes! (* (prefix-numeric-value arg) (/ size 10)) (/ (* size (prefix-numeric-value arg)) 10))) (point-max)))) ;; If we went to a place in the middle of the buffer, ;; adjust it to the beginning of a line. (cond ((and arg (not (consp arg))) (forward-line 1)) ((and (eq (current-buffer) (window-buffer)) (> (point) (window-end nil t))) ;; If the end of the buffer is not already on the screen, ;; then scroll specially to put it near, but not at, the bottom. (overlay-recenter (point)) (recenter -3))))
移动到任意位置
emacs提供了两个函数,可以跳到任意一行,或者是任意一个字符。
* A-g g 或 A-g A-g (goto-line n) :跳转到第n行
* A-g c (goto-char n): 跳转到第n个字符
spacemacs还支持vi的方式来跳转行
* 行号 G (evil-goto-line),如果没有行号,则跳到缓冲区末尾
goto-char不出意料的,是用C实现的。
我们先来看看goto-line:
(defun goto-line (line &optional buffer) (declare (interactive-only forward-line)) (interactive (if (and current-prefix-arg (not (consp current-prefix-arg))) (list (prefix-numeric-value current-prefix-arg)) ;; Look for a default, a number in the buffer at point. (let* ((default (save-excursion (skip-chars-backward "0-9") (if (looking-at "[0-9]") (string-to-number (buffer-substring-no-properties (point) (progn (skip-chars-forward "0-9") (point))))))) ;; Decide if we're switching buffers. (buffer (if (consp current-prefix-arg) (other-buffer (current-buffer) t))) (buffer-prompt (if buffer (concat " in " (buffer-name buffer)) ""))) ;; Read the argument, offering that number (if any) as default. (list (read-number (format "Goto line%s: " buffer-prompt) (list default (line-number-at-pos))) buffer)))) ;; Switch to the desired buffer, one way or another. (if buffer (let ((window (get-buffer-window buffer))) (if window (select-window window) (switch-to-buffer-other-window buffer)))) ;; Leave mark at previous position (or (region-active-p) (push-mark)) ;; Move to the specified line number in that buffer. (save-restriction (widen) (goto-char (point-min)) (if (eq selective-display t) (re-search-forward "[\n\C-m]" nil 'end (1- line)) (forward-line (1- line)))))
evil-goto-line写得简短一些:
(evil-define-motion evil-goto-line (count) :jump t :type line (if (null count) (with-no-warnings (end-of-buffer)) (goto-char (point-min)) (forward-line (1- count))) (evil-first-non-blank))
高效移动
重复执行命令
如果一行一行的移动,实在是太慢了,我们可以使用重复命令,给函数传递一个参数。
标准emacs的做法是Esc + 数字和C-u加数字两种方式:
* Esc n + 命令:执行n次命令。如果无法执行完n次,就尽最大的努力。比如向下移动n行,到是没到n行就到文件末尾了。那么就停在文件末尾。
例:
Esc 10 C-n,向下移动10行
* (universal-argument)函数,它绑定在C-u键上。
universal-argument如果不指定参数的话,默认执行4次。
但是在spacemacs上,universal-argument函数绑定在”空格 u”和”Alt-m u”两个键上。
C-u在spacemacs中被移做绑定到evil-scroll-up上,用于翻屏。
居中重绘屏幕
有的时候,需要重新绘制一下屏幕,让我们移动到的那行变为中心:
C-l (recenter-top-bottom)
(defun recenter-top-bottom (&optional arg) "Move current buffer line to the specified window line.With no prefix argument, successive calls place point accordingto the cycling order defined by `recenter-positions'.A prefix argument is handled like `recenter': With numeric prefix ARG, move current line to window-line ARG. With plain `C-u', move current line to window center." (interactive "P") (cond (arg (recenter arg)) ; Always respect ARG. (t (setq recenter-last-op (if (eq this-command last-command) (car (or (cdr (member recenter-last-op recenter-positions)) recenter-positions)) (car recenter-positions))) (let ((this-scroll-margin (min (max 0 scroll-margin) (truncate (/ (window-body-height) 4.0))))) (cond ((eq recenter-last-op 'middle) (recenter)) ((eq recenter-last-op 'top) (recenter this-scroll-margin)) ((eq recenter-last-op 'bottom) (recenter (- -1 this-scroll-margin))) ((integerp recenter-last-op) (recenter recenter-last-op)) ((floatp recenter-last-op) (recenter (round (* recenter-last-op (window-height))))))))))
undo
做错了,撤销是很关键的操作。
在标准emacs中,使用undo函数来进行这个操作。它绑定到C-_或C-/或C-x u三个键上。
在spacemacs中,C-x u被绑定到undo-tree-visualize函数上。 还可以用”空格 a u”来访问它。
(defun undo-tree-visualize () "Visualize the current buffer's undo tree." (interactive "*") (deactivate-mark) ;; throw error if undo is disabled in buffer (when (eq buffer-undo-list t) (user-error "No undo information in this buffer")) ;; transfer entries accumulated in `buffer-undo-list' to `buffer-undo-tree' (undo-list-transfer-to-tree) ;; add hook to kill visualizer buffer if original buffer is changed (add-hook 'before-change-functions 'undo-tree-kill-visualizer nil t) ;; prepare *undo-tree* buffer, then draw tree in it (let ((undo-tree buffer-undo-tree) (buff (current-buffer)) (display-buffer-mark-dedicated 'soft)) (switch-to-buffer-other-window (get-buffer-create undo-tree-visualizer-buffer-name)) (setq undo-tree-visualizer-parent-buffer buff) (setq undo-tree-visualizer-parent-mtime (and (buffer-file-name buff) (nth 5 (file-attributes (buffer-file-name buff))))) (setq undo-tree-visualizer-initial-node (undo-tree-current undo-tree)) (setq undo-tree-visualizer-spacing (undo-tree-visualizer-calculate-spacing)) (make-local-variable 'undo-tree-visualizer-timestamps) (make-local-variable 'undo-tree-visualizer-diff) (setq buffer-undo-tree undo-tree) (undo-tree-visualizer-mode) ;; FIXME; don't know why `undo-tree-visualizer-mode' clears this (setq buffer-undo-tree undo-tree) (set (make-local-variable 'undo-tree-visualizer-lazy-drawing) (or (eq undo-tree-visualizer-lazy-drawing t) (and (numberp undo-tree-visualizer-lazy-drawing) (>= (undo-tree-count undo-tree) undo-tree-visualizer-lazy-drawing)))) (when undo-tree-visualizer-diff (undo-tree-visualizer-show-diff)) (let ((inhibit-read-only t)) (undo-tree-draw-tree undo-tree))))
而C-_,C-/,在spacemacs中,被绑定在undo-tree-undo上。
小结
- 我也说说Emacs吧(4)
- 我也说说Emacs吧(1)
- 我也说说Emacs吧(2)
- 我也说说Emacs吧(3)
- 我也说说Emacs吧(5)
- 我也说说Emacs吧(6)
- 我也说说Emacs吧(7)
- 我也说说Emacs吧(2)_-_Emacs其实就是函数的组合
- 我也说说房价
- 我也说说盗版
- 我也说说“商业模式”
- 我也说说“商业模式”
- 我也说说大盘!
- 我也说说CSDN
- 我也说说c++
- 我也说说bogomips
- 我也说说ThreadLocal
- 我也说说汉语言编程
- iOS AVAudioPlayer vs. AVPlayer AVPlayerViewController vs.MPMoviePlayerViewController
- CDN内容分发网络架构与四大关键技术
- 【总结】Fiddler Script Api
- 增益dB释义
- Java统计文件中每个字符出现的个数
- 我也说说Emacs吧(4)
- WebView的使用
- 安卓各个版本系统的源码github下载地址
- Python学习之旅-11
- csdn如何转载别人的文章
- hdu 2112 HDU Today
- HBuilder更改为自定义的背景颜色
- SSH框架搭建和整合(struts2、spring4、hibernate5)
- python:syntaxerror :missing parenteses in call to print