在emacs中如何将行/区域上下移动

102

在Emacs中,移动选定区域或向上/向下移动行(如果没有选择)的最简单方法是什么?我希望能够像在Eclipse中那样使用功能(绑定到M-up和M-down)。


2
我相信这个惯用语是直接删除你想要移动的内容,使用常规导航功能(包括isearch等)将光标移动到代码所在的位置,然后粘贴。记住,有一个kill堆栈,所以你甚至不必立即找到位置;你可以收集其他片段以便稍后粘贴。作为一个重度Emacs用户,我个人无法想象任何情况下手动上下移动单行会更有用。 - jrockway
10
谢谢你的评论,@jrockway。我认为每个人都有自己做事情的方式。我经常使用Eclipse工具,并且非常习惯移动代码行/区域。Emacs非常可扩展的一个很好的功能是可以很容易地添加这种功能。 - fikovnik
4
同意。就像你所描述的那样,我经常希望可以在编辑器中或者不在编辑代码时随意移动行。Word支持通过使用Alt+Shift+上下箭头来实现(按段落),因此我会尽可能地将其映射到我的编辑器中。 - harpo
是的,每个人都不同,情境也不同。然而,我想讲一个故事。很久很久以前,在我开始使用Emacs之前,我也有同样的习惯。我用一个(好的)编辑器有这些功能键,那就是我习惯的方式。在转到Emacs后,我写了一些代码来实现同样的功能,即上下移动行或区域文本。最终,我停止使用这个“功能”,只是像大多数Emacs用户一样使用剪切和粘贴。我从未回头。这仅是一个人的经历,供参考。 - Drew
10
@Drew和jrockway:我认为妥协不是Emacs的方法。当你想移动行时,移动行比kill/yankk更快。当然,org-mode也有这个功能的实现,原因是确实有很多情况下需要移动一行,如将错误放置的语句移出/移入块范围,改变语句顺序,改变文档顺序(见org-mode)等。很多人在Eclipse中使用它。你不用的东西往往不会让你感到遗漏,但移动行的有用性对于很多程序员来说是事实。 - Peter
3
这在编辑包含提交记录列表的 git-rebase 文件时非常有用,您可能希望重新排序这些提交记录。 - Ken Williams
9个回答

57

更新:安装来自Marmalade或MELPAmove-text包,以获得以下代码。

这是我使用的内容,它可以用于区域和单独的行:

(defun move-text-internal (arg)
  (cond
   ((and mark-active transient-mark-mode)
    (if (> (point) (mark))
        (exchange-point-and-mark))
    (let ((column (current-column))
          (text (delete-and-extract-region (point) (mark))))
      (forward-line arg)
      (move-to-column column t)
      (set-mark (point))
      (insert text)
      (exchange-point-and-mark)
      (setq deactivate-mark nil)))
   (t
    (let ((column (current-column)))
      (beginning-of-line)
      (when (or (> arg 0) (not (bobp)))
        (forward-line)
        (when (or (< arg 0) (not (eobp)))
          (transpose-lines arg)
          (when (and (eval-when-compile
                       '(and (>= emacs-major-version 24)
                             (>= emacs-minor-version 3)))
                     (< arg 0))
            (forward-line -1)))
        (forward-line -1))
      (move-to-column column t)))))

(defun move-text-down (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines down."
  (interactive "*p")
  (move-text-internal arg))

(defun move-text-up (arg)
  "Move region (transient-mark-mode active) or current line
  arg lines up."
  (interactive "*p")
  (move-text-internal (- arg)))


(global-set-key [M-S-up] 'move-text-up)
(global-set-key [M-S-down] 'move-text-down)

1
我已经为您将此添加到EmacsWiki,链接在这里:http://www.emacswiki.org/emacs/MoveText - ocodo
1
@mcb 是的,任何ELPA包都可以通过el-get安装。(请注意,即使是el-get的作者现在也正在转向package.el。)为了选择整行,我建议编写一个单独的函数,快速选择当前行或扩展当前区域以包括整行。然后在移动文本函数之前调用该函数即可。 - sanityinc
@sanityinc,package.el相对于el-get有什么优势? - Lenar Hoyt
@mcb 这是一个官方标准,在最近的Emacs中内置,并且开发人员正在积极地为其设计代码。现在在许多情况下,el-get只是委托给了package.el。任何el-get可能支持的安装后设置代码都可以使用像use-package这样的包来处理。 - sanityinc
1
@sanityinc 谢谢你的分享。第一次使用它时,我就像迷上了Emacs。很不错的东西! - JS.
显示剩余13条评论

55

通过绑定C-x C-ttranspose-lines,可以移动一行。不过我不确定如何移动区域。

我找到了this个elisp片段,它可以实现你想要的功能,但你需要更改绑定。

(defun move-text-internal (arg)
   (cond
    ((and mark-active transient-mark-mode)
     (if (> (point) (mark))
            (exchange-point-and-mark))
     (let ((column (current-column))
              (text (delete-and-extract-region (point) (mark))))
       (forward-line arg)
       (move-to-column column t)
       (set-mark (point))
       (insert text)
       (exchange-point-and-mark)
       (setq deactivate-mark nil)))
    (t
     (beginning-of-line)
     (when (or (> arg 0) (not (bobp)))
       (forward-line)
       (when (or (< arg 0) (not (eobp)))
            (transpose-lines arg))
       (forward-line -1)))))

(defun move-text-down (arg)
   "Move region (transient-mark-mode active) or current line
  arg lines down."
   (interactive "*p")
   (move-text-internal arg))

(defun move-text-up (arg)
   "Move region (transient-mark-mode active) or current line
  arg lines up."
   (interactive "*p")
   (move-text-internal (- arg)))

(global-set-key [\M-\S-up] 'move-text-up)
(global-set-key [\M-\S-down] 'move-text-down)

这个解决方案和@sanityinc发布的几乎相同,但存在一个小问题:如果我使用定义的快捷键将区域向上和/或向下移动,然后立即使用撤销的快捷键(默认为C-_),则该区域会简单地消失,并在回显区显示“在区域中撤销!”的消息。另一个撤销命令会产生“没有更多的区域撤销信息”的消息。但是,如果我移动光标,然后执行撤销命令,撤销就会重新开始工作。这是一个相对较小的烦恼,但所有其他Emacs命令都可以立即撤销,而无需诀窍。 - Teemu Leisti
3
这正是正确的做法。 在Emacs中,默认的转置行功能是不正确的,因为在转置后光标会移动到下一行,使得逐步重复该操作变得困难。 - mythicalcoder

42

你应该尝试使用drag-stuff

它的功能与 eclipse 中的 Alt+Up/Down 相同,用于单行以及选定区域的多行!

此外,它还允许您使用 Alt+Left/Right 移动单词。
这正是你在寻找的!而且它甚至可以从ELPA repos中获取!

其他解决方案对我都没有用。其中一些有缺陷(交换行时改变它们的顺序,真是神奇?);其中一些则仅移动被选定的部分,留下未选定的部分在原位置上。但是,drag-stuff 的工作方式与 Eclipse 完全相同!

更棒的是!你可以尝试选择一个区域并使用 Alt+Left/Right!这将把所选区域向左或向右转置一个字符。太神奇了!

要全局启用它,只需运行此命令:

(drag-stuff-global-mode)

它确实需要左右移动的选项,因为它覆盖了默认的emacs键映射。 - ocodo
@Slomojo 或许可以在 emacswiki 上提出这个建议? :) - Aleks-Daniel Jakimenko-A.
1
我过去常用这个,现在我使用smart-shift。我喜欢它的DWIM水平移动功能。 - jpkotta
1
在按键绑定开始工作之前,需要在 init 文件中添加 (drag-stuff-define-keys)。这在以下 GitHub 中有解释:https://github.com/rejeep/drag-stuff.el - agent18

14

我写了几个交互式函数来上下移动线:

;; move line up
(defun move-line-up ()
  (interactive)
  (transpose-lines 1)
  (previous-line 2))

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

;; move line down
(defun move-line-down ()
  (interactive)
  (next-line 1)
  (transpose-lines 1)
  (previous-line 1))

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

这些按键绑定采用的是IntelliJ IDEA风格,但你可以使用任何你想要的。 我应该实现一些也能在区域上运行的函数。


6

这是一个移动当前行或活动区域跨越的多行的代码片段。它考虑了光标位置和高亮区域,并且当区域不从行边界开始/结束时,不会打断行。此方法受到eclipse启发,我发现eclipse方法比“transpose-lines”更方便。

;; move the line(s) spanned by the active region up/down (line transposing)
;; {{{
(defun move-lines (n)
  (let ((beg) (end) (keep))
    (if mark-active 
        (save-excursion
          (setq keep t)
          (setq beg (region-beginning)
                end (region-end))
          (goto-char beg)
          (setq beg (line-beginning-position))
          (goto-char end)
          (setq end (line-beginning-position 2)))
      (setq beg (line-beginning-position)
            end (line-beginning-position 2)))
    (let ((offset (if (and (mark t) 
                           (and (>= (mark t) beg)
                                (< (mark t) end)))
                      (- (point) (mark t))))
          (rewind (- end (point))))
      (goto-char (if (< n 0) beg end))
      (forward-line n)
      (insert (delete-and-extract-region beg end))
      (backward-char rewind)
      (if offset (set-mark (- (point) offset))))
    (if keep
        (setq mark-active t
              deactivate-mark nil))))

(defun move-lines-up (n)
  "move the line(s) spanned by the active region up by N lines."
  (interactive "*p")
  (move-lines (- (or n 1))))

(defun move-lines-down (n)
  "move the line(s) spanned by the active region down by N lines."
  (interactive "*p")
  (move-lines (or n 1)))

2
对我来说,move-text包是有问题的。你的解决方案在我的系统上完美运行。谢谢! - Rune Kaagaard

4

1

transpose-paragraph函数可以帮助你。

你也可以查看Emacs手册中的transpose部分。 本质上:

C-t
Transpose two characters (transpose-chars).
M-t
Transpose two words (transpose-words).
C-M-t
Transpose two balanced expressions (transpose-sexps).
C-x C-t
Transpose two lines (transpose-lines).

1

1
顺便说一下,如果你决定使用链接的代码片段,你可能需要将let语句更改为(let ((col (current-column)) (line-move-visual nil))),否则在换行时会出现奇怪的行为。 - Leo Alekseyev
你可以像这样重复转置行,C-x C-t 向上移动行进行转置,C-x z z z 将重复。向下移动一行是 C-u 1 C-x C-t,再次重复是 C-x z z z。当然可以做到,但不可否认有些繁琐。 - undefined

0

我使用 Melpa 中的 smart-shift 软件包来完成这个任务。默认情况下,它将 C-C <arrow> 重新绑定为移动一行或区域。它会按照主模式特定的数量水平移动(例如 c-basic-offset 或 python-indent-offset)。它也可以处理区域。

;; binds C-C <arrows>
(when (require 'smart-shift nil 'noerror)
  (global-smart-shift-mode 1))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接