Mova a linha / região para cima e para baixo no emacs


85

Qual é a maneira mais fácil de mover a região ou linha selecionada (se não houver seleção) para cima ou para baixo no emacs? Estou procurando a mesma funcionalidade do eclipse (limitada a M-up, M-down).


1
Tenho certeza de que o idioma é apenas matar o que você deseja mover, usar as funções de navegação usuais (incluindo isearch, etc.) para mover o ponto onde você deseja que o código esteja e, em seguida, arrancar. Há uma pilha de mortes, lembre-se, então você nem precisa encontrar o local imediatamente; você pode juntar outras partes para puxar mais tarde. Pessoalmente, como um usuário pesado do Emacs, não consigo imaginar nenhuma situação em que prefira mover manualmente uma única linha para cima ou para baixo. Simplesmente não seria útil.
jrockway

7
@jrockway: obrigado pelo seu comentário. Acho que cada um tem sua maneira de fazer as coisas. Trabalho muito com eclipse e estou muito acostumado a mover linhas / regiões. Uma das grandes vantagens de ter o emacs muito extensível é que essa funcionalidade pode ser facilmente adicionada.
fikovnik

3
acordado. Freqüentemente, desejo mover as linhas para cima e para baixo conforme você descreve, esteja ou não em um editor que apóia isso. Ou, por falar nisso, se estou ou não trabalhando em código. O Word suporta isso (por parágrafo) com Alt + Shift + Up e Down, e mapeio isso em meus editores sempre que posso.
Harpo

6
@Drew e jrockway: Acho que contentar-se com menos dificilmente é a maneira do emacs. Mover linhas é apenas mais rápido quando você deseja movê-las do que kill / yankk. E o modo org, é claro, o implementou também por uma razão: na verdade, há muitos casos em que você deseja mover uma linha, mover uma instrução colocada incorretamente para dentro / fora de um escopo de bloco é um, alterar a ordem da instrução, alterar a ordem do documento (ver org- modo), .. Muitas pessoas usam em eclipse. O que você não usa, muitas vezes não perde, mas a utilidade de mover linhas é um fato para muitos programadores
Pedro,

2
Isso é útil ao editar arquivos git-rebase, contendo listas de commits que você pode querer reordenar.
Ken Williams

Respostas:


47

Uma linha pode ser movida usando linhas de transposição ligadas aC-x C-t . Não sei sobre as regiões embora.

Eu encontrei este snippet elisp que faz o que você deseja, exceto que você precisa alterar as ligações.

(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)

Obrigado pelo snippet. Isso é exatamente o que eu estava procurando!
fikovnik

Há um pequeno problema com essa solução e com a quase idêntica postada por @sanityinc: se eu usar o atalho definido para mover uma região para cima e / ou para baixo, use imediatamente o atalho para desfazer (C-_ por padrão), a região simplesmente desaparece e a mensagem "Desfazer na região!" é exibido na área de eco. Outro comando de desfazer produz a mensagem "Nenhuma informação adicional de desfazer para a região". No entanto, se eu mover o cursor e o comando desfazer, o desfazer começa a funcionar novamente. Uma irritação relativamente pequena, mas todos os outros comandos do Emacs podem ser desfeitos imediatamente, sem recorrer a um truque.
Teemu Leisti

1
Esta é exatamente a maneira certa de fazer isso. A linha de transposição padrão no Emacs NÃO é a funcionalidade correta, visto que o cursor se move para a próxima linha após a transposição, tornando difícil repeti-la passo a passo.
mythicalcoder

51

Atualização: Instale o move-textpacote do Marmalade ou MELPA para obter o seguinte código.

Aqui está o que eu uso, que funciona tanto em regiões quanto em linhas individuais:

(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
Eu adicionei isso ao EmacsWiki para você, está aqui: emacswiki.org/emacs/MoveText
ocodo

Obrigado! Acho que o código já estava lá ( emacswiki.org/emacs/basic-edit-toolkit.el ), mas essa nova página será mais fácil para as pessoas encontrarem.
sanityinc

Ahh, legal, eu não vi isso antes, parece que o basic-edit-toolkit precisa de uma limpeza.
ocodo

1
@mcb Sim, qualquer pacote ELPA pode ser instalado via el-get. (Observe que até mesmo o autor el-get está mudando para package.el atualmente.) Para selecionar linhas completas, eu sugeriria escrever uma função separada que seleciona rapidamente a linha atual ou estende a região atual para incluir linhas completas. Em seguida, basta invocar essa função antes das funções de movimento de texto.
Sanityinc

1
@sanityinc Obrigado por isso. Acabei de comer um mini Emacs 'O' na primeira vez que o usei. Coisa boa!
JS.

31

Você deveria tentar drag-stuff !

Funciona exatamente como eclipse Alt+ Up/ Downpara linhas simples, bem como para linhas de regiões selecionadas!

Além disso, permite que você mova as palavras com Alt+ Left/. Right
Isso é exatamente o que você está procurando! E está até disponível nos repositórios do ELPA !

Outras soluções nunca funcionaram para mim. Alguns deles estavam com erros (transpondo linhas ao mudar sua ordem, wtf?) E alguns deles estavam se movendo exatamente na região selecionada, deixando partes não selecionadas das linhas em suas posições. Masdrag-stuff funciona exatamente como no eclipse!

E ainda mais! Você pode tentar selecionar uma região e usar Alt+ Left/Right ! Isso irá transpor a região selecionada em um caractere à esquerda ou à direita. Surpreendente!

Para ativá-lo globalmente, basta executar o seguinte:

(drag-stuff-global-mode)

Ele realmente precisa ter movimento para a esquerda e para a direita opcionais, já que está sobrescrevendo o mapa de teclado padrão do emacs.
ocodo

@Slomojo talvez! Por que você não sugere isso no emacswiki? :)
Aleks-Daniel Jakimenko-A.

1
Eu costumava usar isso, estou usando o smart-shift agora. Gosto que seja DWIM para movimento horizontal.
jpkotta

1
Precisa (drag-stuff-define-keys)no arquivo init antes que as combinações de teclas comecem a funcionar. Isso é explicado neste github: github.com/rejeep/drag-stuff.el
agent18

11

Eu escrevi algumas funções interativas para mover as linhas para cima / para baixo:

;; 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)

As combinações de teclas são no estilo IntelliJ IDEA, mas você pode usar o que quiser. Provavelmente, devo implementar algumas funções que operam em regiões também.


5

Aqui está meu trecho para mover a linha atual ou as linhas estendidas pela região ativa. Ele respeita a posição do cursor e a região destacada. E não quebrará linhas quando a região não começar / terminar na (s) borda (s) da linha. (É inspirado no eclipse; achei o eclipse muito mais conveniente do que 'linhas de transposição'.)

;; 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)))

1
Para mim, o pacote mover-texto estava quebrado. Sua solução funciona perfeitamente no meu sistema. Obrigado por isso!
Rune Kaagaard


1

Não há embutido. Você pode usar linhas de transposição (Cx Ct), mas não pode repetidamente. Veja as funções em http://www.schuerig.de/michael/blog/index.php/2009/01/16/line-movement-for-emacs/ .

Deve ser fácil adaptá-lo às regiões também.


1
A propósito, se você decidir usar o snippet vinculado, você pode querer alterar a instrução let para (let ((col (coluna atual)) (line-move-visual nil)), caso contrário, você obterá um comportamento engraçado com linhas quebradas .
Leo Alekseyev

1

o transpose-paragraph função pode ajudá-lo.

Você também pode querer dar uma olhada na seção de transposição no manual do Emacs. Essencialmente:

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).

0

Eu uso o pacote smart-shift (em Melpa) para isso. Por padrão, ele é religado C-C <arrow>para mover uma linha ou região. Ele se move horizontalmente por uma quantidade específica do modo principal (por exemplo, c-basic-offset ou python-indent-offset). Funciona também em regiões.

;; binds C-C <arrows>
(when (require 'smart-shift nil 'noerror)
  (global-smart-shift-mode 1))
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.