关于文本:如何在Emacs中复制整行?

How do I duplicate a whole line in Emacs?

我在Vim身上看到了同样的问题,这也是我自己想知道如何为Emacs做的事情。在Resharper中,我使用ctrl-d执行此操作。在Emacs中执行此命令的最少命令数是多少?


我用

1
C-a C-SPACE C-n M-w C-y

可分解为

  • C-a:将光标移动到行首
  • C-SPACE:开始选择("设置标记")。
  • C-n:将光标移到下一行
  • M-w:复制区
  • C-y:糊状物("扬克")。

上述

1
C-a C-k C-k C-y C-y

相当于相同的东西(tmtowtdi)

  • C-a:将光标移动到行首
  • C-k:切断("杀死")线路
  • C-k切新线
  • (我们回到一号广场)
  • C-y:再粘贴一次(现在我们有两份该行的副本)

与您的编辑器中的C-d相比,这两个版本都是令人尴尬的冗长,但在emacs中总是有一个定制。默认情况下,C-ddelete-char绑定,那么C-c C-d如何?只需在您的.emacs中添加以下内容:

1
(global-set-key"\C-c\C-d""\C-a\C- \C-n\M-w\C-y")

(@nathan的elisp版本可能更好,因为如果更改了任何键绑定,它都不会断开。)

注意:有些Emacs模式可能会回收C-c C-d来做其他事情。


除了前面的答案,您还可以定义自己的函数来复制一行。例如,将以下内容放入.emacs文件将使C-D复制当前行。

1
2
3
4
5
6
7
8
9
10
(defun duplicate-line()
  (interactive)
  (move-beginning-of-line 1)
  (kill-line)
  (yank)
  (open-line 1)
  (next-line 1)
  (yank)
)
(global-set-key (kbd"C-d") 'duplicate-line)


将光标放在行上,如果不是在开始处,则执行CtrL ,然后:

CtrL KBd

CtrL KBd

ctrl y

ctrl y


我的函数版本可以复制一行,它可以很好地处理撤消操作,并且不会影响光标的位置。这是1997年11月在gnu.emacs.sources上讨论的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(defun duplicate-line (arg)
 "Duplicate current line, leaving point in lower line."
  (interactive"*p")

  ;; save the point for undo
  (setq buffer-undo-list (cons (point) buffer-undo-list))

  ;; local variables for start and end of line
  (let ((bol (save-excursion (beginning-of-line) (point)))
        eol)
    (save-excursion

      ;; don't use forward-line for this, because you would have
      ;; to check whether you are at the end of the buffer
      (end-of-line)
      (setq eol (point))

      ;; store the line and disable the recording of undo information
      (let ((line (buffer-substring bol eol))
            (buffer-undo-list t)
            (count arg))
        ;; insert the line arg times
        (while (> count 0)
          (newline)         ;; because there is no newline in 'line'
          (insert line)
          (setq count (1- count)))
        )

      ;; create the undo information
      (setq buffer-undo-list (cons (cons eol (point)) buffer-undo-list)))
    ) ; end-of-let

  ;; put the point in the lowest line and return
  (next-line arg))

然后可以定义ctrl-d来调用此函数:

1
(global-set-key (kbd"C-d") 'duplicate-line)


kill-whole-line命令代替kill-line(C-kC-aC-kC-kC-yC-ykill-whole-line命令:

1
2
3
C-S-Backspace
C-y
C-y

C-k相比的优点包括,点在线路上的位置并不重要(不像C-k要求在线路的起点),它还杀死了新线(这也是C-k没有做到的)。


这里还有另一个功能可以实现这一点。我的版本没有接触到杀戮环,光标最后出现在原来的新行上。如果区域处于活动状态(瞬时标记模式),它将复制该区域,否则默认为复制行。如果给定前缀arg,它还将复制多个副本,如果给定负前缀arg,则注释原始行(这对于在保留旧命令/语句的同时测试不同版本的命令/语句很有用)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(defun duplicate-line-or-region (&optional n)
 "Duplicate current line, or region if active.
With argument N, make N copies.
With negative N, comment out original line and use the absolute value."
  (interactive"*p")
  (let ((use-region (use-region-p)))
    (save-excursion
      (let ((text (if use-region        ;Get region if active, otherwise line
                      (buffer-substring (region-beginning) (region-end))
                    (prog1 (thing-at-point 'line)
                      (end-of-line)
                      (if (< 0 (forward-line 1)) ;Go to beginning of next line, or make a new one
                          (newline))))))
        (dotimes (i (abs (or n 1)))     ;Insert N times, or once if not specified
          (insert text))))
    (if use-region nil                  ;Only if we're working with a line (not a region)
      (let ((pos (- (point) (line-beginning-position)))) ;Save column
        (if (> 0 n)                             ;Comment out original with negative arg
            (comment-region (line-beginning-position) (line-end-position)))
        (forward-line 1)
        (forward-char pos)))))

我把它绑定到C-c d上:

1
(global-set-key [?\C-c ?d] 'duplicate-line-or-region)

不能通过模式或任何方式重新分配,因为C-c后跟一个(未修改的)字母是为用户绑定保留的。


Nathan添加到.emacs文件中是可行的,但是可以通过替换来稍微简化。

1
2
  (open-line 1)
  (next-line 1)

具有

1
  (newline)

顺从的

1
2
3
4
5
6
7
8
9
(defun duplicate-line()
  (interactive)
  (move-beginning-of-line 1)
  (kill-line)
  (yank)
  (newline)
  (yank)
)
(global-set-key (kbd"C-d") 'duplicate-line)


我不太记得在其他地方行复制是如何工作的,但是作为一个以前的scite用户,我喜欢scite方式的一个方面:它不接触光标位置!所以上面所有的食谱对我来说都不够好,这是我的嬉皮士版本:

1
2
3
4
5
6
7
8
9
(defun duplicate-line ()
   "Clone line at cursor, leaving the latter intact."
    (interactive)
    (save-excursion
        (let ((kill-read-only-ok t) deactivate-mark)
            (toggle-read-only 1)
            (kill-whole-line)
            (toggle-read-only 0)
            (yank))))

请注意,在这个过程中并没有实际杀死任何东西,所以标记和当前选择是完整的。

顺便问一句,为什么你们这么喜欢在有这么漂亮的"不干净的杀戮"整行thingy(c-s-backspace)的时候晃动光标?


从melpa安装复制品:

M-X软件包安装RET副本

并将此键绑定添加到init文件:

(全局设置键(kbd"m-c")'重复项)


我把copy-from-above-command绑定到一把钥匙上并使用它。它提供了Xemacs,但我不知道GNU Emacs。

`copy-from-above-command' is an
interactive compiled Lisp function
-- loaded from"/usr/share/xemacs/21.4.15/lisp/misc.elc"
(copy-from-above-command &optional
ARG)

Documentation: Copy characters from
previous nonblank line, starting just
above point. Copy ARG characters, but
not past the end of that line. If no
argument given, copy the entire rest
of the line. The characters copied are
inserted in the buffer before point.


我写了自己的版本的duplicate-line,因为我不想搞砸杀人戒指。

1
2
3
4
5
6
7
8
9
10
11
  (defun jr-duplicate-line ()
   "EASY"
    (interactive)
    (save-excursion
      (let ((line-text (buffer-substring-no-properties
                        (line-beginning-position)
                        (line-end-position))))
        (move-end-of-line 1)
        (newline)
        (insert line-text))))
  (global-set-key"\C-cd" 'jr-duplicate-line)

因为我不知道,我会以最慢的速度开始这一轮的高尔夫运动:

CTRL- K,Y,Y


我喜欢fraggod的版本,除了两件事:(1)它不检查缓冲区是否已经在(interactive"*")中是只读的;(2)如果最后一行是空的,它在缓冲区的最后一行失败(因为在这种情况下,您不能终止该行),而使缓冲区保持只读。

我做了以下更改以解决此问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(defun duplicate-line ()
 "Clone line at cursor, leaving the latter intact."
  (interactive"*")
  (save-excursion
    ;; The last line of the buffer cannot be killed
    ;; if it is empty. Instead, simply add a new line.
    (if (and (eobp) (bolp))
        (newline)
      ;; Otherwise kill the whole line, and yank it back.
      (let ((kill-read-only-ok t)
            deactivate-mark)
        (toggle-read-only 1)
        (kill-whole-line)
        (toggle-read-only 0)
        (yank)))))

违约是可怕的。但是,您可以将Emacs扩展到类似slickedit和textmate的工作方式,即在未选择文本时复制/剪切当前行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(transient-mark-mode t)
(defadvice kill-ring-save (before slick-copy activate compile)
 "When called interactively with no active region, copy a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (message"Copied line")
     (list (line-beginning-position)
           (line-beginning-position 2)))))
(defadvice kill-region (before slick-cut activate compile)
 "When called interactively with no active region, kill a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (list (line-beginning-position)
           (line-beginning-position 2)))))

将上述内容放入.emacs中。然后,为了复制一行,M-w。删除一行,C-w。为了复制一行,C-a M-w C-y C-y C-y ...


1
C-a C-k C-k C-y C-y

你可能想在.emacs中包含的内容是

1
(setq kill-whole-line t)

它基本上会在调用kill line(即通过c-k)时杀死整行加上换行符。然后,如果没有额外的代码,您只需执行c-a c-k c-y c-y来复制行。它可以分解为

1
2
3
4
C-a go to beginning of line
C-k kill-line (i.e. cut the line into clipboard)
C-y yank (i.e. paste); the first time you get the killed line back;
    second time gives the duplicated line.

但是如果您经常使用这个,那么一个专用的密钥绑定可能是一个更好的主意,但是仅仅使用C-A C-K C-Y C-Y的好处是您可以在其他地方复制该行,而不是仅仅在当前行下面。


不管怎样,我看到了非常复杂的解决方案…

1
2
3
4
5
6
7
(defun duplicate-line ()
 "Duplicate current line"
  (interactive)
  (kill-whole-line)
  (yank)
  (yank))
(global-set-key (kbd"C-x M-d") 'duplicate-line)


有一个叫做avy的包,它有命令avy copy line。使用该命令时,窗口中的每一行都会得到字母组合。然后你只需要输入组合就可以得到那条线。这也适用于地区。然后你只需要输入两个组合。

在这里您可以看到界面:

http://i68.tinypic.com/24fk5eu.png


使用最新的emacs,您可以在行中的任何位置使用m-w来复制它。于是它变成:

1
M-w C-a RET C-y


在没有活动区域的情况下以交互方式调用时,请复制(m-w)一行:

1
2
3
4
5
6
7
(defadvice kill-ring-save (before slick-copy activate compile)
 "When called interactively with no active region, COPY a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (message"Copied line")
     (list (line-beginning-position)
           (line-beginning-position 2)))))

当在没有活动区域的情况下以交互方式调用时,应改为杀死(c-w)一行。

1
2
3
4
5
6
7
(defadvice kill-region (before slick-cut activate compile)
 "When called interactively with no active region, KILL a single line instead."
  (interactive
   (if mark-active (list (region-beginning) (region-end))
     (message"Killed line")
     (list (line-beginning-position)
           (line-beginning-position 2)))))

另外,在相关说明中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defun move-line-up ()
 "Move up the current line."
  (interactive)
  (transpose-lines 1)
  (forward-line -2)
  (indent-according-to-mode))

(defun move-line-down ()
 "Move down the current line."
  (interactive)
  (forward-line 1)
  (transpose-lines 1)
  (forward-line -1)
  (indent-according-to-mode))

(global-set-key [(meta shift up)]  'move-line-up)
(global-set-key [(meta shift down)]  'move-line-down)

@据我所知,非常接近。唯一要考虑的另一件事是打开kill-whole-line,将换行符包含在c-k中。


我写一封信是为了我的喜好。

1
2
3
4
5
6
7
8
9
(defun duplicate-line ()
 "Duplicate current line."
  (interactive)
  (let ((text (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
        (cur-col (current-column)))
    (end-of-line) (insert"
" text)
    (beginning-of-line) (right-char cur-col)))
(global-set-key (kbd"C-c d l") 'duplicate-line)

但我发现当当前行包含多字节字符(如CJK字符)时,这会有一些问题。如果遇到此问题,请尝试以下方法:

1
2
3
4
5
6
7
8
9
(defun duplicate-line ()
 "Duplicate current line."
  (interactive)
  (let* ((text (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
         (cur-col (length (buffer-substring-no-properties (point-at-bol) (point)))))
    (end-of-line) (insert"
" text)
    (beginning-of-line) (right-char cur-col)))
(global-set-key (kbd"C-c d l") 'duplicate-line)

CtrL KBd,CtrL KBd,(位置到新位置)CtrL Y

如果不是从行首开始,请添加CtrL A。第二个CtrLKBBD是抓取换行符。如果只需要文本,可以将其删除。


该功能应与JetBrains的实现相匹配,即按行或区域复制,然后按预期离开点和/或活动区域:

只是一个围绕交互表单的包装:

1
2
3
4
5
6
7
8
9
(defun wrx/duplicate-line-or-region (beg end)
 "Implements functionality of JetBrains' `Command-d' shortcut for `duplicate-line'.
   BEG & END correspond point & mark, smaller first
   `use-region-p' explained:
   http://emacs.stackexchange.com/questions/12334/elisp-for-applying-command-to-only-the-selected-region#answer-12335"
  (interactive"r")
  (if (use-region-p)
      (wrx/duplicate-region-in-buffer beg end)
    (wrx/duplicate-line-in-buffer)))

称之为,

1
2
3
4
5
6
7
8
9
10
11
12
13
(defun wrx/duplicate-region-in-buffer (beg end)
 "copy and duplicate context of current active region
   |------------------------+----------------------------|
   |        before          |           after            |
   |------------------------+----------------------------|
   | first <MARK>line here  | first line here            |
   | second item<POINT> now | second item<MARK>line here |
   |                        | second item<POINT> now     |
   |------------------------+----------------------------|
   TODO: Acts funky when point < mark"
  (set-mark-command nil)
  (insert (buffer-substring beg end))
  (setq deactivate-mark nil))

或者这个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(defun wrx/duplicate-line-in-buffer ()
 "Duplicate current line, maintaining column position.
   |--------------------------+--------------------------|
   |          before          |          after           |
   |--------------------------+--------------------------|
   | lorem ipsum<POINT> dolor | lorem ipsum dolor        |
   |                          | lorem ipsum<POINT> dolor |
   |--------------------------+--------------------------|
   TODO: Save history for `Cmd-Z'
   Context:
   http://stackoverflow.com/questions/88399/how-do-i-duplicate-a-whole-line-in-emacs#answer-551053"
  (setq columns-over (current-column))
  (save-excursion
    (kill-whole-line)
    (yank)
    (yank))
  (let (v)
    (dotimes (n columns-over v)
      (right-char)
      (setq v (cons n v))))
  (next-line))

然后我把它绑定到meta+shift+d

1
(global-set-key (kbd"M-D") 'wrx/duplicate-line-or-region)


最简单的方法是ChrisConway的方法。

1
C-a C-SPACE C-n M-w C-y

这是Emacs规定的默认方式。在我看来,使用这个标准更好。在Emacs中,我总是小心定制自己的密钥绑定。Emacs已经足够强大了,我认为我们应该尽力适应它自己的键绑定。

虽然有点长,但当你习惯了它,你可以做得快,会发现这是有趣的!


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
;; http://www.emacswiki.org/emacs/WholeLineOrRegion#toc2
;; cut, copy, yank
(defadvice kill-ring-save (around slick-copy activate)
 "When called interactively with no active region, copy a single line instead."
  (if (or (use-region-p) (not (called-interactively-p)))
      ad-do-it
    (kill-new (buffer-substring (line-beginning-position)
                                (line-beginning-position 2))
              nil '(yank-line))
    (message"Copied line")))
(defadvice kill-region (around slick-copy activate)
 "When called interactively with no active region, kill a single line instead."
  (if (or (use-region-p) (not (called-interactively-p)))
      ad-do-it
    (kill-new (filter-buffer-substring (line-beginning-position)
                                       (line-beginning-position 2) t)
              nil '(yank-line))))
(defun yank-line (string)
 "Insert STRING above the current line."
  (beginning-of-line)
  (unless (= (elt string (1- (length string))) ?
)
    (save-excursion (insert"
")))
  (insert string))

(global-set-key (kbd"<f2>") 'kill-region)    ; cut.
(global-set-key (kbd"<f3>") 'kill-ring-save) ; copy.
(global-set-key (kbd"<f4>") 'yank)           ; paste.

将上面的elisp添加到init.el中,现在可以剪切/复制整行函数,然后可以使用f3 f4复制一行。


M-cShift+Insertx2(或任何粘贴快捷方式)都可以。


加上前缀参数,以及(我希望)直观的行为:

1
2
3
4
5
6
7
8
9
10
11
(defun duplicate-line (&optional arg)
 "Duplicate it. With prefix ARG, duplicate ARG times."
  (interactive"p")
  (next-line
   (save-excursion
     (let ((beg (line-beginning-position))
           (end (line-end-position)))
       (copy-region-as-kill beg end)
       (dotimes (num arg arg)
         (end-of-line) (newline)
         (yank))))))

光标将保留在最后一行。或者,您可能希望指定一个前缀来同时复制接下来的几行:

1
2
3
4
5
6
7
8
9
10
11
(defun duplicate-line (&optional arg)
 "Duplicate it. With prefix ARG, duplicate ARG times."
  (interactive"p")
  (save-excursion
    (let ((beg (line-beginning-position))
          (end
           (progn (forward-line (1- arg)) (line-end-position))))
      (copy-region-as-kill beg end)
      (end-of-line) (newline)
      (yank)))
  (next-line arg))

我发现自己经常同时使用这两种方法,使用包装函数来切换前缀参数的行为。

和键绑定:(global-set-key (kbd"C-S-d") 'duplicate-line)


这是一个复制当前行的函数。对于前缀参数,它将重复行多次。例如,C-3 C-S-o将复制当前行三次。不会改变杀人戒指。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(defun duplicate-lines (arg)
  (interactive"P")
  (let* ((arg (if arg arg 1))
         (beg (save-excursion (beginning-of-line) (point)))
         (end (save-excursion (end-of-line) (point)))
         (line (buffer-substring-no-properties beg end)))
    (save-excursion
      (end-of-line)
      (open-line arg)
      (setq num 0)
      (while (< num arg)
        (setq num (1+ num))
        (forward-line 1)
        (insert-string line))
      )))

(global-set-key (kbd"C-S-o") 'duplicate-lines)

我不能相信所有这些复杂的解决方案。这是两个按键:

运行命令kill整行

C-/运行命令undo

所以 C-/要"复制"整行(kill和undo)。

当然,您可以将其与数字和负参数结合使用,以终止向前或向后的多行。


这感觉更自然,关于克里斯康威选择的答案。

(全局设置键"c-cc-d""c-ac-c-nm-wc-yc-pc-e")

这允许您通过简单地重复c-cc-d键的笔画,多次复制一行。


如其他答案中所述,将键笔画绑定到Lisp代码比将它们绑定到其他键笔画更好。对于@mw的答案,代码将复制该行并将标记移动到新行的末尾。此修改将标记位置保持在新行的同一列中:

1
2
3
4
5
6
7
8
9
fun duplicate-line ()
  (interactive)
  (let ((col (current-column)))
    (move-beginning-of-line 1)
    (kill-line)
    (yank)
    (newline)
    (yank)
    (move-to-column col)))

我通常使用:

1
2
3
4
Ctl-Space (set the mark)
move to end of line
Ctl-K kill line
Ctl-Y * 2 (yank the line back)

不过,也许还有更好的办法:p