Nawiguj według wcięcia

15

Chcę nawigować między wierszami pliku na podstawie wcięcia. Plik ma strukturę wcięcia: linia, która jest bardziej wcięta niż poprzednia linia, jest potomkiem poprzedniej linii, która ma takie samo wcięcie jak poprzednia linia, jest jej rodzeństwem. Głównie szukam trzech poleceń:

  • Przejdź do następnego rodzeństwa, tj. Następnego wiersza z tym samym wcięciem, pomijając wiersze, które są bardziej wcięte, ale nie pomijaj linii mniej wciętej.
  • Przejdź do poprzedniego rodzeństwa, tj. Tego samego w przeciwnym kierunku.
  • Przejdź do elementu nadrzędnego, tj. Do poprzedniej linii z mniejszym wcięciem.

Pozycja kolumny w punkcie nie powinna się zmienić.

Są to analogi dla danych o strukturze do wcięć forward-sexp, backward-sexpa backward-up-listdla danych s-wyrażenie strukturze. Wcięcie odpowiada strukturze programu w językach takich jak Haskell i Python; funkcje te mogą być szczególnie przydatne w tym kontekście, ale nie szukam niczego specyficznego dla danego trybu (moim podstawowym przypadkiem użycia są dane o strukturze intendacyjnej w innym formacie pliku).

Poziomy wcięć kolorowania mogą pomóc w ręcznej nawigacji za pomocą Up/, Downale chcę coś automatycznego.

To pytanie superużytkownika jest podobne, ale ma słabsze wymagania i obecnie nie ma odpowiedzi, które spełniają moje wymagania.

Gilles „SO- przestań być zły”
źródło
Czy zbliżasz set-selective-displaysię do tego, czego potrzebujesz?
Kaushal Modi
1
@KaushalModi Jest to przydatne i nie wiedziałem o tym, więc dziękuję, ale nie zawsze jest to, czego potrzebuję. Właśnie teraz chciałem się poruszać i zobaczyć dzieci linii, nad którymi się poruszałem.
Gilles „SO- przestań być zły”
Dzięki, że zadałeś to pytanie; Miałem mniej więcej takie samo pytanie. Jedyną dodatkową rzeczą, którą chciałbym, to „przejść do ostatniego rodzeństwa”, tj. Ostatnia linia, która ma to samo wcięcie, a nie pomijanie linii, które są mniej wcięte. (Odpowiednik powtarzania „przejdź do następnego rodzeństwa”, dopóki go nie będzie.)
ShreevatsaR
Właśnie zauważyłem pakiet indent-toolsw melpa ( narzędzia wcięcia ), który prawdopodobnie działa w tym celu. Pierwsze zatwierdzenie nastąpiło 16 maja 2016 r., Około 3 miesiące po zadaniu tego pytania.
ShreevatsaR

Odpowiedzi:

4

Analizując cztery dostępne obecnie odpowiedzi ( dwie na Super User i dwie na to pytanie), widzę następujące problemy:

  • Te w SuperUser autorstwa Stefana i Peng Bai (poruszanie się linia po linii, patrząc na bieżące wcięcie) nie implementują zachowania aktualnej pozycji kolumny i przejścia do rodzica,
  • Odpowiedź Dan (stosując ponowne przeszukiwanie do przodu, aby znaleźć następną linię z tego samego wcięcia) pomija linie z mniejszym wcięciem: nie wie, kiedy nie ma obok rodzeństwo, a więc można przenieść do czegoś, co nie jest rodzeństwo ale dziecko innego rodzica… może kolejnego „kuzyna”.
  • Odpowiedź Gilles (za pomocą konturu-mode) nie zachowuje pozycję kolumny i to nie działa z linii z zerowym wcięcia ( „top-level” linie). Poza tym, patrząc na jego kod outline.el, i tak w zasadzie i tak przechodzi on linijka po linii ( outline-next-visible-headingw naszym przypadku), ponieważ (prawie) wszystkie linie pasują do wyrażenia regularnego konspektu i liczą się jako „nagłówek”.

Tak więc, łącząc kilka pomysłów każdego z nich, mam następujące: iść naprzód linia po linii, przeskakując puste i bardziej wcięte linie. Jeśli masz równe wcięcie, jest to następne rodzeństwo. Podstawowy pomysł wygląda następująco:

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

Odpowiednio uogólnione (do przodu / do tyłu / w górę / w dół) to, czego używam, wygląda następująco:

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

Pożądana jest jeszcze większa funkcjonalność, a przeglądanie outline.eli ponowne wdrażanie niektórych z nich może pomóc, ale na razie jestem zadowolony z tego, dla moich celów.

ShreevatsaR
źródło
@Gilles: Dzięki za zmiany! Wygląda na (current-line)to, że coś z misc-fns.eltego mam w instalacji Aquamacs jako część oneonone.elbiblioteki.
ShreevatsaR
6

Ta funkcja istnieje w Emacsie. Tryb konspektu opisuje dokument jako zawierający linie nagłówka z poziomem i umożliwia przechodzenie między poziomami. Możemy zdefiniować każdą linię jako linię nagłówka z poziomem, który odzwierciedla jej wcięcie: ustaw outline-regexpna wcięcie. Dokładniej, wcięcie plus pierwszy znak zakaz spacje (i początek pliku to poziom najwyższy) \`\|\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

W Emacsie 22.1–24.3 możesz to uprościć, aby:

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

Następnie możesz użyć poleceń ruchu konspektu :

  • C-C @ C-f( outline-forward-same-level), aby przejść do następnego rodzeństwa;
  • C-C @ C-b( outline-backward-same-level), aby przejść do poprzedniego rodzeństwa;
  • C-C @ C-u( outline-up-heading), aby przejść do rodzica.

Jedna zakładka i jedna spacja liczą się dla tej samej wielkości wcięcia. Jeśli masz kombinację tabulatorów i spacji, ustaw tab-widthodpowiednio i zadzwońuntabify .

Jeśli bieżący tryb główny ma ustawienia konturu, mogą powodować konflikty. W takim przypadku możesz użyć jednego z wielu rozwiązań dla wielu głównych trybów , najprostszym z nich jest utworzenie bufora pośredniego i ustawienie go na Zarys trybu głównego. W trybie Zarys główny domyślne skróty klawiaturowe są łatwiejsze do wpisania: C-c C-fitd.

Gilles „SO- przestań być zły”
źródło
Wygląda na to, że powinno działać, ale z jakiegoś powodu tak naprawdę nie działa. M-x make-local-variable RET outline-regexp RETnie akceptuje tej zmiennej i mówi tylko „[Brak dopasowania]`. Mam jeszcze przyjrzeć się temu dokładniej.
ShreevatsaR
@ShreevatsaR Jest to niezgodna zmiana w Emacsie 24.4: outline-regexpnie jest już defcustom i nie można jej tak łatwo ustawić interaktywnie.
Gilles „SO- przestań być zły”
Bardzo ładnie, dziękuje. Istnieją dwa drobne problemy: (1) Jeśli jesteś na najwyższym poziomie (linia bez wcięcia, co, jak sądzę, oznacza brak dopasowania do wyrażenia regularnego konspektu), to nie działa ani do przodu ani do tyłu, a z jakiegoś powodu idzie o dwa wiersze (2), gdy przechodzi do następnego lub poprzedniego rodzeństwa, przechodzi na początek wiersza (kolumna 0), ale dobrze byłoby zachować kolumnę. (Jak określasz w pytaniu.) Myślę, że oba mogą być ograniczeniami samego trybu konspektu.
ShreevatsaR
5

Poniższe trzy polecenia, minimalnie przetestowane, powinny umożliwić podstawową nawigację według wciętych linii. Przepraszamy za powtórzenie kodu.

(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
źródło
To dobrze (po poprawce - nie rozumiem, co próbujesz zrobić, odejmując 1, (current-column)ale powoduje to, że kursor się nie porusza), ale nie do końca odpowiada mojej specyfikacji: poruszanie się na poziomie wcięcia przesuwa się mniej niż wcięte linie.
Gilles „SO- przestań być zły”
To nie działa Np. Po ind-forward-siblingprostu szuka następnej linii z tym samym wcięciem, więc przeskakuje przez linie z mniejszą wcięciem (idzie do przodu, nawet jeśli nie ma rodzeństwa do przodu).
ShreevatsaR