Mover linhas selecionadas para cima e para baixo

7

Estou usando o eclipse há algum tempo e achei alguns atalhos muito úteis, em particular a capacidade de mover uma seleção retangular de linhas para cima e para baixo usando Alt + Up / Down. Eu tenho procurado por essa funcionalidade no emacs. Até agora, encontrei o seguinte script:

(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-up] 'move-text-up)
(global-set-key [\M-down] 'move-text-down)

Isso funciona, exceto pelo fato de que isso move a seleção . Prefiro mover todas as linhas (incluindo a nova linha à direita) incluídas na seleção. Existe uma maneira de perceber isso também?

hfhc2
fonte
Uma pergunta relacionada ao stackoverflow: stackoverflow.com/q/2423834 .
Nome
Foi daí que eu obtive o código. Eu estava perguntando novamente, porque essa solução não foi para minha satisfação.
Hfhc2

Respostas:

14

drag-stuff

Confira o drag-stuffpacote (também disponível no Melpa).

Você pode selecionar uma região e usar drag-stuff-up/ drag-stuff-downpara mover a região para cima / baixo.


Comportamento alternativo ao arrastar linhas

Por padrão, os drag-stuffcomandos também arrastam a linha pointem que está (mesmo que o ponto esteja na primeira coluna). Se você quiser selecionar, digamos 2 linhas inteiras C-a C-SPC C-n C-n, a seleção será algo como isto

 line 1
▯line 2
 line 3
▮line 4 
 line 5

Observe que aqui pretendo mover apenas as linhas 2 e 3, não a linha 4 . Mas drag-stuffmoverá essa terceira linha também por padrão.

Essa foi a minha irritação (e provavelmente não se aplica a mais ninguém) e, portanto, solicitei o pacote dev para uma solução . Aqui está um truque que você pode colocar na sua configuração do emacs depois de solicitar, drag-stuffse você não quiser esse comportamento padrão. O hack não moverá a linha atual SE o ponto estiver na coluna 0 (primeira coluna).

;; https://github.com/kaushalmodi/.emacs.d/blob/master/setup-files/setup-drag-stuff.el
;; https://github.com/rejeep/drag-stuff.el/issues/4
(defvar modi/drag-stuff--point-adjusted nil)
(defvar modi/drag-stuff--point-mark-exchanged nil)

(defun modi/drag-stuff--adj-pt-pre-drag ()
  "If a region is selected AND the `point' is in the first column, move
back the point by one char so that it ends up on the previous line. If the
point is above the mark, exchange the point and mark temporarily."
  (when (region-active-p)
    (when (< (point) (mark)) ; selection is done starting from bottom to up
      (exchange-point-and-mark)
      (setq modi/drag-stuff--point-mark-exchanged t))
    (if (zerop (current-column))
        (progn
          (backward-char 1)
          (setq modi/drag-stuff--point-adjusted t))
      ;; If point did not end up being on the first column after the
      ;; point/mark exchange, revert that exchange.
      (when modi/drag-stuff--point-mark-exchanged
        (exchange-point-and-mark) ; restore the original point and mark loc
        (setq modi/drag-stuff--point-mark-exchanged nil)))))

(defun modi/drag-stuff--rst-pt-post-drag ()
  "Restore the `point' to where it was by forwarding it by one char after
the vertical drag is done."
  (when modi/drag-stuff--point-adjusted
    (forward-char 1)
    (setq modi/drag-stuff--point-adjusted nil))
  (when modi/drag-stuff--point-mark-exchanged
    (exchange-point-and-mark) ; restore the original point and mark loc
    (setq modi/drag-stuff--point-mark-exchanged nil)))

(add-hook 'drag-stuff-before-drag-hook #'modi/drag-stuff--adj-pt-pre-drag)
(add-hook 'drag-stuff-after-drag-hook  #'modi/drag-stuff--rst-pt-post-drag)

Demonstração de como as linhas de arrasto funcionam antes e depois do hack acima

Antes de hackear

 line 1                                      line 1
▯line 2                                      line 5
 line 3    --(M-x drag-stuff-down)-->       ▯line 2   MOVED LINE
▮line 4                                      line 3   MOVED LINE
 line 5                                     ▮line 4   MOVED LINE

Após o hack

 line 1                                      line 1
▯line 2                                      line 4
 line 3    --(M-x drag-stuff-down)-->       ▯line 2   MOVED LINE
▮line 4                                      line 3   MOVED LINE
 line 5                                     ▮line 5

Combinações de teclas

Para obter o comportamento semelhante ao eclipse, adicione as combinações de teclas apropriadas:

(global-set-key (kbd "M-<up>")   #'drag-stuff-up)
(global-set-key (kbd "M-<down>") #'drag-stuff-down)
Kaushal Modi
fonte
Hm ... por mais irritante que seja a linha 4 em movimento, é definitivamente mais consistente. Por exemplo, se você movesse as linhas, mas o seu ponto era na EOL, o novo comportamento "hackeado" pareceria muito estranho.
PythonNut
@ PythonNut Sim, se o ponto não estivesse na coluna 0, toda a linha seria movida. Mas é por isso que isso é um hack; ele se encaixa no meu fluxo de trabalho. Acredito em 99% das seleções da minha região ao mover linhas, meu ponto está na coluna 0 porque tenho a tendência de selecionar linhas inteiras ( exemplo C-a C-SPC C-4 C-n ) ou parágrafos inteiros ( exemplo C-a C-SPC M-} ). Eu sei que existe M-h, ainda não me acostumei com isso ainda. Imagine como seria irritante se cada vez que eu acabasse arrastando meu parágrafo selecionado mais a próxima linha! :)
Kaushal Modi
Seu hack não fará a coisa errada se o ponto estiver no início da região e não no final?
Harald Hanche-Olsen
@ HaraldHanche-Olsen Correto. Surpreendentemente, eu nunca fiz a seleção dessa maneira (começando de baixo e subindo) e, portanto, nunca peguei esse problema :). Está consertado agora (como pude ver em uma verificação rápida). Deixe-me saber se isso funciona para você. Obrigado por perceber isso.
Kaushal Modi
2

Eu codifiquei isso há muito tempo, use-o todos os dias .

(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)
yPhil
fonte
11
isso é fantástico, obrigado por postar isso, eu não tenho idéia de por que você não tem mais votos positivos!
iFunction
1

Aqui está uma maneira de fazer isso usando macros do teclado:

  • C-x ( comece a gravar a macro.
  • C-aSPCC-pSPCC-wC-pC-y corte uma linha e cole-a uma linha anteriormente no buffer.
  • C-x ) termine de gravar a macro.
  • C-x e moverá a linha para cima quantas vezes você pressionar.
wvxvw
fonte
Bem, há uma função transpose-linesque funciona desta maneira, mas isso só funciona para linhas simples como faz a sua sugestão ...
hfhc2
@ hfhc2 a macro do teclado é uma solução ad hoc de qualquer maneira. No mesmo ritmo, você pode fazer com que ela mova mais de uma linha (basta adicionar um argumento numérico a C-p).
Wdxvw