Durch Einrückung navigieren

15

Ich möchte zwischen den Zeilen einer Datei basierend auf Einrückungen navigieren. Die Datei ist durch Einrückung strukturiert: Eine Zeile, die stärker eingerückt ist als die vorherige Zeile, ist ein untergeordnetes Element der vorherigen Zeile. Eine Zeile mit derselben Einrückung wie die vorherige Zeile ist das gleichrangige Element. Ich suche hauptsächlich drei Befehle:

  • Gehen Sie zum nächsten Geschwister, dh zur nächsten Zeile mit demselben Einzug, und überspringen Sie Zeilen, die stärker eingerückt sind, jedoch nicht hinter einer Zeile, die weniger eingerückt ist.
  • Gehe zum vorherigen Geschwister, dh dasselbe in die andere Richtung.
  • Gehen Sie zum übergeordneten Element, dh zur vorherigen Zeile mit weniger Einrückung.

Die Spaltenposition des Punktes sollte sich nicht ändern.

Diese sind Analoga für die Einrückung strukturierte Daten forward-sexp, backward-sexpund backward-up-listfür sexp strukturierten Daten. Einrückung entspricht Programmstruktur in Sprachen wie Haskell und Python; Diese Funktionen können in diesem Zusammenhang besonders nützlich sein, aber ich suche nichts Modusspezifisches (mein Hauptanwendungsfall sind Daten mit Absichtsstruktur in einem anderen Dateiformat).

Das Färben von Einrückungsstufen kann beim manuellen Navigieren mit Up/ hilfreich sein, Downaber ich möchte etwas Automatisches.

Diese Superuser-Frage ist ähnlich, hat jedoch geringere Anforderungen und enthält derzeit keine Antworten, die meinen Anforderungen entsprechen.

Gilles 'SO - hör auf böse zu sein'
quelle
Hat set-selective-displayerhalten Sie in der Nähe zu dem, was Sie brauchen?
Kaushal Modi
1
@KaushalModi Es ist nützlich, und ich wusste nichts darüber, also danke, aber es ist nicht immer das, was ich brauche. Gerade jetzt wollte ich mich bewegen und die Kinder der Linien sehen, über die ich mich bewegte.
Gilles 'SO - hör auf böse zu sein'
Vielen Dank, dass Sie diese Frage gestellt haben. Ich wollte im Grunde die gleiche Frage nur weniger gut stellen. Die einzige zusätzliche Sache, die ich möchte, ist "zum letzten Geschwister verschieben", dh die letzte Zeile, die den gleichen Einzug hat, und nicht das Überspringen von Zeilen, die weniger eingerückt sind. (Das Äquivalent zum Wiederholen von "Zum nächsten Geschwister wechseln", bis es keine mehr gibt.)
ShreevatsaR
Mir ist gerade das Paket indent-toolsin melpa (indent -tools ) aufgefallen , das wahrscheinlich für diesen Zweck funktioniert. Die erste Übergabe erfolgte am 16. Mai 2016, ungefähr 3 Monate nachdem diese Frage gestellt wurde.
ShreevatsaR

Antworten:

4

Wenn ich die vier derzeit verfügbaren Antworten ( zwei für Superuser und zwei für diese Frage) betrachte, sehe ich die folgenden Probleme:

  • Bei denen in SuperUser von Stefan und Peng Bai (zeilenweise verschieben, aktuelle Einrückung anzeigen ) wird die aktuelle Spaltenposition nicht beibehalten und zum übergeordneten Element verschoben.
  • Die Antwort von Dan (mit erneuter Suche nach vorne, um die nächste Zeile mit demselben Einzug zu finden) überspringt Zeilen mit weniger Einzug: Er weiß nicht, wann es kein nächstes Geschwister gibt, und kann daher zu etwas wechseln, das kein Geschwister ist aber ein Kind eines anderen Elternteils ... vielleicht ein nächster "Cousin".
  • Die Antwort von Gilles (im Umriss-Modus) behält die Spaltenposition nicht bei und funktioniert nicht mit Zeilen ohne Einrückung (Zeilen der obersten Ebene). Wenn man sich den Code darin ansieht outline.el, geht es outline-next-visible-headingin unserem Fall ohnehin auch grundsätzlich zeilenweise (mit ), da (fast) alle Zeilen dem regulären Ausdruck der Gliederung entsprechen und als "Überschrift" zählen würden.

Wenn ich also einige Ideen für jede zusammenstelle, habe ich Folgendes: Gehen Sie Zeile für Zeile vor, und überspringen Sie leere und stärker eingerückte Zeilen. Wenn Sie die gleiche Einrückung haben, ist es das nächste Geschwister. Die Grundidee sieht so aus:

(defun indentation-get-next-sibling-line ()
  "The line number of the next sibling, or nil if there isn't any."
  (let ((wanted-indentation (current-indentation)))
    (save-excursion
      (while (and (zerop (forward-line))  ; forward-line returns 0 on success
               (or (eolp)  ; Skip past blank lines and more-indented lines
                 (> (current-indentation) wanted-indentation))))
      ;; Now we can't go further. Which case is it?
      (if (and (not (eobp)) (= (current-indentation) wanted-indentation))
        (line-number-at-pos)
        nil))))

(defun indentation-forward-to-next-sibling ()
  (interactive)
  (let ((saved-column (current-column)))
    (forward-line (- (indentation-get-next-sibling-line) (line-number-at-pos)))
    (move-to-column saved-column)))

Entsprechend verallgemeinert (vorwärts / rückwärts / aufwärts / abwärts) sieht das, was ich verwende, derzeit folgendermaßen aus:

(defun indentation-get-next-good-line (direction skip good)
  "Moving in direction `direction', and skipping over blank lines and lines that
satisfy relation `skip' between their indentation and the original indentation,
finds the first line whose indentation satisfies predicate `good'."
  (let ((starting-indentation (current-indentation))
         (lines-moved direction))
    (save-excursion
      (while (and (zerop (forward-line direction))
               (or (eolp)  ; Skip past blank lines and other skip lines
                 (funcall skip (current-indentation) starting-indentation)))
        (setq lines-moved (+ lines-moved direction)))
      ;; Now we can't go further. Which case is it?
      (if (and
            (not (eobp))
            (not (bobp))
            (funcall good (current-indentation) starting-indentation))
        lines-moved
        nil))))

(defun indentation-get-next-sibling-line ()
  "The line number of the next sibling, if any."
  (indentation-get-next-good-line 1 '> '=))

(defun indentation-get-previous-sibling-line ()
  "The line number of the previous sibling, if any"
  (indentation-get-next-good-line -1 '> '=))

(defun indentation-get-parent-line ()
  "The line number of the parent, if any."
  (indentation-get-next-good-line -1 '>= '<))

(defun indentation-get-child-line ()
  "The line number of the first child, if any."
  (indentation-get-next-good-line +1 'ignore '>))


(defun indentation-move-to-line (func preserve-column name)
  "Move the number of lines given by func. If not possible, use `name' to say so."
  (let ((saved-column (current-column))
          (lines-to-move-by (funcall func)))
    (if lines-to-move-by
      (progn
        (forward-line lines-to-move-by)
        (move-to-column (if preserve-column
                          saved-column
                          (current-indentation))))
      (message "No %s to move to." name))))

(defun indentation-forward-to-next-sibling ()
  "Move to the next sibling if any, retaining column position."
  (interactive "@")
  (indentation-move-to-line 'indentation-get-next-sibling-line t "next sibling"))

(defun indentation-backward-to-previous-sibling ()
  "Move to the previous sibling if any, retaining column position."
  (interactive "@")
  (indentation-move-to-line 'indentation-get-previous-sibling-line t "previous sibling"))

(defun indentation-up-to-parent ()
  "Move to the parent line if any."
  (interactive "@")
  (indentation-move-to-line 'indentation-get-parent-line nil "parent"))

(defun indentation-down-to-child ()
  "Move to the first child line if any."
  (interactive "@")
  (indentation-move-to-line 'indentation-get-child-line nil "child"))

Es ist noch etwas mehr Funktionalität wünschenswert, und outline.eles kann hilfreich sein, einige davon zu betrachten und neu zu implementieren, aber ich bin für meine Zwecke im Moment damit zufrieden.

ShreevatsaR
quelle
@ Gilles: Danke für die Änderungen! Sieht (current-line)so aus misc-fns.el, als hätte ich etwas davon in meiner Aquamacs-Installation als Teil einer oneonone.elBibliothek.
ShreevatsaR
5

Die folgenden drei Befehle, die nur minimal getestet wurden, sollten eine einfache Navigation durch eingerückte Zeilen ermöglichen. Entschuldigung für die Code-Wiederholung.

(defun ind-forward-sibling ()
  "Move forward to the next sibling line with the same indentation."
  (interactive)
  (save-match-data
    (let ((col (current-column))
          (pad (progn
                 (back-to-indentation)
                 (current-column))))
      (end-of-line 1)
      (re-search-forward (concat "^\\s-\\{"
                                 (number-to-string pad)
                                 "\\}[^ ]") nil t)
      (move-to-column col))))

(defun ind-backward-sibling ()
  "Move backward to the next sibling line with the same indentation."
  (interactive)
  (save-match-data
    (let ((col (current-column))
          (pad (progn
                 (back-to-indentation)
                 (current-column))))
      (beginning-of-line 1)
      (re-search-backward (concat "^\\s-\\{"
                                 (number-to-string pad)
                                 "\\}[^ ]") nil t)
      (move-to-column col))))

(defun ind-up-parent ()
  "Move up to parent line with less indentation."
  (interactive)
  (save-match-data
    (let ((col (current-column))
          (pad (progn
                 (back-to-indentation)
                 (current-column))))
      (when (> pad 0)
        (beginning-of-line 1)
        (re-search-backward (concat "^\\s-\\{0,"
                                    (number-to-string (1- pad))
                                    "\\}[^ ]") nil t))
      (move-to-column col))))
Dan
quelle
Das ist gut (nach der Korrektur - ich verstehe nicht, was Sie mit dem Subtrahieren von 1 zu tun haben, (current-column)aber der Cursor bewegt sich nicht), aber entspricht nicht genau meiner Spezifikation: Bewegen auf einer Einrückungsstufe bewegt sich über weniger hinaus. eingerückte Zeilen.
Gilles 'SO - hör auf böse zu sein'
Das geht nicht. ZB ind-forward-siblingsucht einfach nach der nächsten Zeile mit dem gleichen Einzug, überspringt also Zeilen mit weniger Einzug (geht vorwärts, auch wenn es kein vorwärtsgeschwister gibt).
ShreevatsaR
5

Diese Funktion ist in Emacs vorhanden. Der Gliederungsmodus beschreibt ein Dokument so, dass es Überschriften mit einer Ebene enthält, und verfügt über Funktionen zum Verschieben zwischen Ebenen. Wir können jede Zeile als Überschriftenzeile mit einer Ebene definieren, die den Einzug widerspiegelt: Setzen Sie diese outline-regexpauf den Einzug. Mehr die Vertiefung und das erste Nicht-Leerzeichen genau (und der Anfang der Datei ist , die oberste Ebene) \`\|\s-+\S-.

M-x load-libray outline RET
M-: (make-local-variable 'outline-regexp) RET
M-: (setq outline-regexp "\\`\\|\\s-+\\S-") RET
M-x outline-minor-mode RET

In Emacs 22.1–24.3 können Sie dies vereinfachen, um:

M-x load-libray outline RET
M-1 M-x set-variable RET outline-regexp RET "\\`\\|\\s-+\\S-" RET
M-x outline-minor-mode RET

Dann können Sie Gliederungsbewegungsbefehle verwenden :

  • C-C @ C-f( outline-forward-same-level) zum nächsten Geschwister wechseln;
  • C-C @ C-b( outline-backward-same-level) zum vorherigen Geschwister wechseln;
  • C-C @ C-u( outline-up-heading), um zum übergeordneten Element zu wechseln.

Ein Tabulator und ein Leerzeichen zählen für das gleiche Maß an Einrückung. Wenn Sie eine Mischung aus Tabulatoren und Leerzeichen haben, legen Sie dies tab-widthentsprechend fest und rufen Sie anuntabify .

Wenn der aktuelle Hauptmodus Gliederungseinstellungen enthält, können Konflikte auftreten. In diesem Fall können Sie eine der vielen Hauptmodi verwenden. Am einfachsten ist es, einen indirekten Puffer zu erstellen und auf den Hauptmodus " Gliederung" zu setzen. Im Hauptmodus "Gliederung" sind die Standardtastenkürzel einfacher einzugeben: C-c C-fusw.

Gilles 'SO - hör auf böse zu sein'
quelle
Das scheint so, als ob es funktionieren sollte, funktioniert aber aus irgendeinem Grund nicht für mich. M-x make-local-variable RET outline-regexp RETakzeptiert diese Variable nicht und sagt nur "[No match]". Bin noch nicht genauer dran.
ShreevatsaR
@ShreevatsaR Es handelt sich um eine inkompatible Änderung in Emacs 24.4: Sie outline-regexpist keine Standardanpassung mehr und kann nicht so einfach interaktiv festgelegt werden.
Gilles 'SO- hör auf böse zu sein'
Sehr Schön. Danke. Es gibt zwei kleinere Probleme: (1) Wenn Sie sich auf der obersten Ebene befinden (eine Zeile ohne Einrückung, was meiner Meinung nach keine Übereinstimmung mit dem Outline-Regexp bedeutet), funktioniert weder vorwärts noch rückwärts, und aus irgendeinem Grund steigt sie um zwei Zeilen (2) Wenn es zum nächsten oder vorherigen Geschwister geht, geht es zum Anfang der Zeile (Spalte 0), aber es wäre schön, die Spalte beizubehalten. (Wie Sie in der Frage angeben.) Ich vermute, beide können Einschränkungen des Gliederungsmodus selbst sein.
ShreevatsaR