Bewegen Sie die Linie / Region in Emacs auf und ab

84

Was ist der einfachste Weg, um ausgewählte Regionen oder Linien (wenn keine Auswahl vorhanden ist) in Emacs nach oben oder unten zu verschieben? Ich suche nach der gleichen Funktionalität wie in Eclipse (begrenzt auf M-up, M-down).

fikovnik
quelle
1
Ich bin mir ziemlich sicher, dass die Redewendung darin besteht, nur das zu töten, was Sie verschieben möchten, die üblichen Navigationsfunktionen (einschließlich Suche usw.) zu verwenden, um den Punkt zu verschieben, an dem sich der Code befinden soll, und dann zu ziehen. Denken Sie daran, es gibt einen Stapel Kills, sodass Sie den Ort nicht einmal sofort finden müssen. Sie können andere Teile sammeln, um sie später zu ziehen. Persönlich kann ich mir als starker Emacs-Benutzer keine Situation vorstellen, in der ich lieber eine einzelne Zeile manuell nach oben oder unten verschieben möchte. Es wäre einfach nicht nützlich.
Jrockway
7
@ Rockway: Danke für deinen Kommentar. Ich denke, jeder hat seine eigene Art, Dinge zu tun. Ich arbeite viel mit Eclipse und bin es sehr gewohnt, Linien / Regionen zu bewegen. Eine der großartigen Eigenschaften von Emacs, die sehr erweiterbar sind, ist, dass diese Funktionalität einfach hinzugefügt werden kann.
Fikovnik
3
einverstanden. Ich möchte häufig Zeilen nach oben und unten verschieben, wie Sie beschreiben, unabhängig davon, ob ich mich in einem Editor befinde, der dies unterstützt. Oder ob ich an Code arbeite oder nicht. Word unterstützt dies (nach Absätzen) mit Alt + Umschalt + Auf und Ab, und ich ordne dies in meinen Editoren zu, wann immer ich kann.
Harpo
6
@Drew und Jrockway: Ich denke, sich mit weniger zufrieden zu geben, ist kaum der Emacs-Weg. Das Verschieben von Linien ist nur schneller, wenn Sie sie verschieben möchten als kill / yankk. Und der org-Modus hat es natürlich auch aus einem Grund implementiert: In der Tat gibt es viele Fälle, in denen Sie eine Zeile verschieben möchten. Das Verschieben einer falsch platzierten Anweisung in einen Blockbereich ist eine, ändern Sie die Anweisungsreihenfolge, ändern Sie die Dokumentreihenfolge (siehe org- Modus), .. Viele Leute verwenden es in Eclipse. Was Sie nicht verwenden, verpassen Sie oft nicht, aber die Nützlichkeit des Verschiebens von Linien ist eine Tatsache für eine ganze Reihe von Programmierern
Peter
2
Dies ist nützlich, wenn Sie Git-Rebase-Dateien bearbeiten, die Listen von Commits enthalten, die Sie möglicherweise neu anordnen möchten.
Ken Williams

Antworten:

47

Eine Linie kann mit an gebundenen Transponierlinien verschoben werden C-x C-t. Keine Ahnung von Regionen.

Ich habe dieses Elisp-Snippet gefunden, das macht, was Sie wollen, außer dass Sie die Bindungen ändern müssen.

(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
quelle
Danke für den Ausschnitt. Genau das habe ich gesucht!
Fikovnik
Sowohl bei dieser als auch bei der von @sanityinc veröffentlichten fast identischen Lösung gibt es ein kleines Problem: Wenn ich die definierte Verknüpfung zum Verschieben einer Region nach oben und / oder unten verwende, verwenden Sie die Verknüpfung sofort zum Rückgängigmachen (standardmäßig C-_). Die Region verschwindet einfach und die Meldung "In Region rückgängig machen!" wird im Echo-Bereich angezeigt. Ein weiterer Rückgängig-Befehl gibt die Meldung "Keine weiteren Rückgängig-Informationen für Region" aus. Wenn ich jedoch den Cursor bewege und dann den Befehl "Rückgängig" befehle, funktioniert das Rückgängigmachen wieder. Eine relativ kleine Irritation, aber alle anderen Emacs-Befehle können sofort rückgängig gemacht werden, ohne auf einen Trick zurückzugreifen.
Teemu Leisti
1
Dies ist genau der richtige Weg. Die Standardtransponierungszeile in Emacs ist NICHT korrekt, da der Cursor nach der Transponierung zur nächsten Zeile wechselt, was es schwierig macht, sie Schritt für Schritt zu wiederholen.
Mythicalcoder
51

Update: Installieren Sie das move-textPaket von Marmalade oder MELPA , um den folgenden Code zu erhalten.

Folgendes verwende ich, das sowohl für Regionen als auch für einzelne Linien funktioniert:

(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
quelle
1
Ich habe dies zu EmacsWiki für Sie hinzugefügt, es ist hier: emacswiki.org/emacs/MoveText
ocodo
Vielen Dank! Ich denke, dass der Code bereits vorhanden war ( emacswiki.org/emacs/basic-edit-toolkit.el ), aber diese neue Seite wird für die Leute leichter zu finden sein.
Sanityinc
Ahh, cool, das habe ich noch nie gesehen. Es sieht so aus, als könnte das Basic-Edit-Toolkit eine Bereinigung gebrauchen.
ocodo
1
@mcb Ja, jedes ELPA-Paket kann über el-get installiert werden. (Beachten Sie, dass sich heutzutage sogar der el-get-Autor in Richtung package.el bewegt.) Um vollständige Zeilen auszuwählen, würde ich vorschlagen, eine separate Funktion zu schreiben, die schnell entweder die aktuelle Zeile auswählt oder den aktuellen Bereich um vollständige Zeilen erweitert. Rufen Sie dann einfach diese Funktion auf, bevor Sie Text verschieben.
Sanityinc
1
@sanityinc Danke dafür. Ich hatte gerade ein Mini-Emacs 'O', als ich es zum ersten Mal benutzte. Gutes Zeug!
JS.
31

Du solltest es versuchen drag-stuff !

Es funktioniert genau wie Eclipse Alt+ Up/ Downfür einzelne Linien sowie für ausgewählte Regionslinien!

Darüber hinaus können Sie Wörter mit Alt+ Left/ verschieben. Right
Dies ist genau das, wonach Sie suchen! Und es ist sogar in den ELPA-Repos erhältlich !

Andere Lösungen haben bei mir nie funktioniert. Einige von ihnen waren fehlerhaft (Transponieren von Linien beim Ändern ihrer Reihenfolge, wtf?) Und einige von ihnen bewegten genau ausgewählte Regionen und ließen nicht ausgewählte Teile der Linien an ihren Positionen. Funktioniert aber drag-stuffgenau wie bei Eclipse!

Und noch mehr! Sie können versuchen, eine Region auszuwählen und Alt+ Left/ Right! Dadurch wird der ausgewählte Bereich um ein Zeichen nach links oder rechts transponiert. Tolle!

Um es global zu aktivieren, führen Sie einfach Folgendes aus:

(drag-stuff-global-mode)
Aleks-Daniel Jakimenko-A.
quelle
Die Bewegung nach links und rechts muss wirklich optional sein, da sie die Standard-Emacs-Keymap überschreibt.
ocodo
@ Slomojo vielleicht! Warum schlägst du das nicht auf emacswiki vor? :)
Aleks-Daniel Jakimenko-A.
1
Früher habe ich das benutzt, jetzt benutze ich Smart-Shift. Ich mag es, dass es DWIM für horizontale Bewegung.
Jpkotta
1
Wird (drag-stuff-define-keys)in der Init-Datei benötigt, bevor die Tastenkombinationen funktionieren. Dies wird in diesem Github erklärt: github.com/rejeep/drag-stuff.el
Thej Kiran
11

Ich habe einige interaktive Funktionen zum Verschieben von Linien nach oben / unten geschrieben:

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

Die Tastenkombinationen sind im IntelliJ IDEA-Stil, Sie können jedoch alles verwenden, was Sie möchten. Ich sollte wahrscheinlich einige Funktionen implementieren, die auch für Regionen funktionieren.

Bozhidar Batsov
quelle
5

Hier ist mein Snippet, um die aktuelle Linie oder die von der aktiven Region überspannten Linien zu verschieben. Es berücksichtigt die Cursorposition und den hervorgehobenen Bereich. Und es werden keine Linien unterbrochen, wenn die Region nicht an den Liniengrenzen beginnt / endet. (Es ist von der Sonnenfinsternis inspiriert; ich fand die Sonnenfinsternis bequemer als 'Transponierungslinien'.)

;; 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
quelle
1
Für mich war das Move-Text-Paket kaputt. Ihre Lösung funktioniert perfekt auf meinem System. Dank dafür!
Rune Kaagaard
1

Es ist kein eingebauter. Sie können Transponierungslinien (Cx Ct) verwenden, aber Sie können sie nicht wiederholt verwenden. Schauen Sie sich die Funktionen auf http://www.schuerig.de/michael/blog/index.php/2009/01/16/line-movement-for-emacs/ an. .

Es sollte leicht sein, dies auch an Regionen anzupassen.

Florian Thiel
quelle
1
Übrigens, wenn Sie sich für die Verwendung des verknüpften Snippets entscheiden, möchten Sie möglicherweise die let-Anweisung in (let ((col (aktuelle Spalte)) (line-move-visual nil)) ändern, da Sie sonst mit umbrochenen Zeilen ein lustiges Verhalten erhalten .
Leo Alekseyev
1

Die transpose-paragraphFunktion könnte Ihnen helfen.

Vielleicht möchten Sie auch einen Blick auf den Transponierungsabschnitt im Emacs-Handbuch werfen . Im Wesentlichen:

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
quelle
0

Ich benutze dafür das Smart-Shift-Paket (in Melpa). Standardmäßig wird erneut gebunden C-C <arrow>, um eine Linie oder Region zu verschieben. Es bewegt sich horizontal um einen für den Hauptmodus spezifischen Betrag (z. B. c-basic-offset oder python-indent-offset). Funktioniert auch in Regionen.

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