Mover línea / región hacia arriba y hacia abajo en emacs

85

¿Cuál es la forma más fácil de mover la región o línea seleccionada (si no hay selección) hacia arriba o hacia abajo en emacs? Estoy buscando la misma funcionalidad que en eclipse (limitada a M-up, M-down).

fikovnik
fuente
1
Estoy bastante seguro de que el idioma es simplemente eliminar lo que desea mover, usar las funciones de navegación habituales (incluida isearch, etc.) para mover el punto donde desea que esté el código y luego tirar. Hay una pila de muertes, recuerda, por lo que ni siquiera tienes que encontrar la ubicación de inmediato; puedes juntar otros pedazos para tirarlos más tarde. Personalmente, como un gran usuario de Emacs, no puedo imaginar ninguna situación en la que prefiera mover manualmente una línea hacia arriba o hacia abajo. Simplemente no sería útil.
jrockway
7
@jrockway: gracias por tu comentario. Creo que cada uno tiene su propia forma de hacer las cosas. Trabajo mucho con eclipse y estoy muy acostumbrado a mover líneas / regiones. Una de las ventajas de tener emacs muy extensible es que esta funcionalidad se puede agregar fácilmente.
fikovnik
3
convenido. Con frecuencia quiero mover las líneas hacia arriba y hacia abajo como usted describe, esté o no en un editor que lo admita. O, para el caso, si estoy trabajando en código o no. Word admite esto (por párrafo) con Alt + Shift + Arriba y Abajo, y lo mapeo en mis editores siempre que puedo.
harpo
6
@Drew y jrockway: Creo que conformarse con menos no es lo que hace emacs. Mover líneas es más rápido cuando quieres moverlas que matar / tirar. Y el modo org, por supuesto, también lo ha implementado por una razón: de hecho, hay muchos casos en los que desea mover una línea, mover una declaración mal colocada dentro / fuera de un alcance de bloque es uno, alterar el orden de las declaraciones, alterar el orden del documento (ver org- modo), .. Mucha gente lo usa en eclipse. Lo que no usa, a menudo no se pierde, pero la utilidad de las líneas en movimiento es un hecho para muchos programadores
Peter
2
Esto es útil cuando se editan archivos git-rebase, que contienen listas de confirmaciones que quizás desee reordenar.
Ken Williams

Respuestas:

47

Una línea se puede mover usando líneas de transposición enlazadas C-x C-t. Aunque no sé sobre regiones.

Encontré este fragmento de elisp que hace lo que quieres, excepto que necesitas cambiar los enlaces.

(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)
Martin Wickman
fuente
Gracias por el fragmento. ¡Esto es exactamente lo que estaba buscando!
fikovnik
Hay un pequeño problema tanto con esta solución como con la casi idéntica publicada por @sanityinc: si uso el atajo definido para mover una región hacia arriba y / o hacia abajo, entonces uso inmediatamente el atajo para deshacer (C-_ por defecto), la región simplemente desaparece y aparece el mensaje "¡Deshacer en la región!" se muestra en el área de eco. Otro comando de deshacer produce el mensaje "No hay más información de deshacer para la región". Sin embargo, si muevo el cursor, luego comando deshacer, el deshacer comienza a funcionar nuevamente. Una irritación relativamente menor, pero todos los demás comandos de Emacs se pueden deshacer inmediatamente sin recurrir a un truco.
Teemu Leisti
1
Ésta es exactamente la forma correcta de hacerlo. La línea de transposición predeterminada en Emacs NO es la funcionalidad correcta ya que el cursor se mueve a la siguiente línea después de la transposición, lo que dificulta la repetición paso a paso.
mythicalcoder
51

Actualización: instale el move-textpaquete de Marmalade o MELPA para obtener el siguiente código.

Esto es lo que uso, que funciona tanto en regiones como en líneas individuales:

(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)
sanityinc
fuente
1
He agregado esto a EmacsWiki para usted, está aquí: emacswiki.org/emacs/MoveText
ocodo
¡Gracias! Creo que el código ya estaba allí ( emacswiki.org/emacs/basic-edit-toolkit.el ) pero esta nueva página será más fácil de encontrar para la gente.
sanityinc
Ahh, genial, no lo había visto antes, parece que el kit de herramientas de edición básico podría necesitar una limpieza.
ocodo
1
@mcb Sí, cualquier paquete ELPA se puede instalar a través de el-get. (Tenga en cuenta que incluso el autor de el-get se está moviendo hacia package.el en estos días). Para seleccionar líneas completas, sugeriría escribir una función separada que seleccione rápidamente la línea actual o extienda la región actual para incluir líneas completas. Luego, simplemente invoca esa función antes de las funciones de mover texto.
sanityinc
1
@sanityinc Gracias por esto. Acabo de tener una mini Emacs 'O' la primera vez que la usé. ¡Buen material!
JS.
31

¡Deberías intentarlo drag-stuff!

¡Funciona exactamente como eclipse Alt+ Up/ Downpara líneas individuales, así como para líneas de región seleccionadas!

Además de eso, te permite mover palabras con Alt+ Left/ ¡ Right
Esto es exactamente lo que estás buscando! ¡E incluso está disponible en los repositorios de ELPA !

Otras soluciones nunca funcionaron para mí. Algunos de ellos tenían errores (transponiendo líneas mientras cambiaban su orden, ¿wtf?) Y algunos de ellos se movían exactamente en la región seleccionada, dejando partes no seleccionadas de las líneas en sus posiciones. ¡Pero drag-stufffunciona exactamente como en eclipse!

¡Y aún más! Puede intentar seleccionar una región y usar Alt+ Left/ Right! Esto transpondrá la región seleccionada un carácter a la izquierda o la derecha. ¡Asombroso!

Para habilitarlo globalmente, simplemente ejecute esto:

(drag-stuff-global-mode)
Aleks-Daniel Jakimenko-A.
fuente
Realmente necesita tener movimiento a la izquierda y a la derecha opcional, ya que está anulando el mapa de teclas predeterminado de emacs.
ocodo
@Slomojo quizás! ¿Por qué no sugieres eso en emacswiki? :)
Aleks-Daniel Jakimenko-A.
1
Solía ​​usar esto, ahora estoy usando smart-shift. Me gusta que sea DWIM para movimiento horizontal.
jpkotta
1
Necesita (drag-stuff-define-keys)en el archivo init antes de que las combinaciones de teclas comiencen a funcionar. Esto se explica en este github: github.com/rejeep/drag-stuff.el
agent18
11

He escrito un par de funciones interactivas para mover líneas arriba / abajo:

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

Las combinaciones de teclas son del estilo IntelliJ IDEA, pero puede usar lo que quiera. Probablemente debería implementar algunas funciones que operan también en regiones.

Bozhidar Batsov
fuente
5

Aquí está mi fragmento para mover la línea actual o las líneas abarcadas por la región activa. Respeta la posición del cursor y la región resaltada. Y no romperá las líneas cuando la región no comience / termine en los bordes de la línea. (Está inspirado en el eclipse; encontré el eclipse mucho más conveniente que las 'líneas de transposición').

;; 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)))
Ji Han
fuente
1
Para mí, el paquete de texto de movimiento se rompió. Tu solución funciona perfectamente en mi sistema. ¡Gracias por eso!
Rune Kaagaard
1

No hay incorporado. Puede usar líneas de transposición (Cx Ct) pero no puede usarlo repetidamente. Mire las funciones en http://www.schuerig.de/michael/blog/index.php/2009/01/16/line-movement-for-emacs/ .

También debería ser fácil adaptar eso a las regiones.

Florian Thiel
fuente
1
Por cierto, si decide usar el fragmento vinculado, es posible que desee cambiar la declaración let a (let ((col (current-column)) (line-move-visual nil)), de lo contrario obtendrá un comportamiento divertido con líneas envueltas .
Leo Alekseyev-
1

La transpose-paragraphfunción podría ayudarte.

Es posible que también desee echar un vistazo a la sección de transposición en el manual de Emacs. Esencialmente:

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).
Roberto Aloi
fuente
0

Utilizo el paquete smart-shift (en Melpa) para esto. De forma predeterminada, se vuelve C-C <arrow>a enlazar para mover una línea o región. Se mueve horizontalmente en una cantidad específica del modo principal (por ejemplo, c-basic-offset o python-indent-offset). También funciona en regiones.

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