Naviga per rientro


15

Voglio navigare tra le righe di un file in base al rientro. Il file è strutturato per rientro: una riga che è più rientrata rispetto alla riga precedente è figlia della riga precedente, una riga che ha lo stesso rientro della riga precedente è suo fratello. Sto cercando principalmente tre comandi:

  • Passa al fratello successivo, ovvero alla riga successiva con la stessa rientranza, saltando le righe più rientrate, ma senza saltare una riga meno rientrata.
  • Passa al fratello precedente, ovvero la stessa cosa nell'altra direzione.
  • Passa al genitore, ovvero alla riga precedente con meno rientri.

La posizione della colonna del punto non dovrebbe cambiare.

Questi sono analoghi per i dati di indentazione-strutturata per forward-sexp, backward-sexpe backward-up-listper i dati sexp-strutturato. Il rientro corrisponde alla struttura del programma in linguaggi come Haskell e Python; queste funzioni possono essere particolarmente utili in questo contesto, ma non sto cercando nulla di specifico della modalità (il mio caso d'uso principale è costituito da dati strutturati con intenzioni all'interno di un altro formato di file).

I livelli di rientro da colorare possono aiutare a navigare manualmente con Up/ Downma voglio qualcosa di automatico.

Questa domanda da superutente è simile ma con requisiti più deboli e al momento non ha risposte che soddisfino i miei requisiti.


Ti set-selective-displayavvicina a ciò di cui hai bisogno?
Kaushal Modi

1
@KaushalModi È utile e non lo sapevo, quindi grazie, ma non è sempre quello di cui ho bisogno. Proprio ora, volevo muovermi e vedere i bambini delle linee su cui mi stavo muovendo.
Gilles 'SO- smetti di essere malvagio' il

Grazie per aver posto questa domanda; Stavo per fare sostanzialmente la stessa domanda solo meno bene. L'unica cosa aggiuntiva che mi piacerebbe è "passare all'ultimo fratello", ovvero l'ultima riga con lo stesso rientro, non saltare le righe che sono meno rientrate. (L'equivalente di ripetere "sposta al prossimo fratello" fino a quando non ce n'è nessuno.)
ShreevatsaR

Ho appena notato il pacchetto indent-toolsin melpa ( indent-tools ), che probabilmente funziona a questo scopo. Il primo impegno è stato il 16 maggio 2016, circa 3 mesi dopo la domanda.
ShreevatsaR,

Risposte:


4

Esaminando le quattro risposte attualmente disponibili ( due su Super User e due su questa domanda), vedo i seguenti problemi:

  • Gli quelli sulla SuperUser di Stefan e Peng Bai (spostando la linea per linea, guardando indentazione corrente) non implementare mantenendo la posizione della colonna corrente e in movimento fino al genitore,
  • La risposta di Dan (usando re-search-forward per trovare la riga successiva con lo stesso rientro) salta le righe con meno rientro: non sa quando non ci sono fratelli e può quindi passare a qualcosa che non è fratello ma un figlio di un altro genitore ... forse un prossimo "cugino".
  • La risposta di Gilles (utilizzando la modalità struttura) non mantiene la posizione della colonna e non funziona con le linee con rientro zero (linee "di livello superiore"). Inoltre, osservando il suo codice outline.el, anche sostanzialmente sta andando riga per riga (usando outline-next-visible-heading) nel nostro caso, poiché (quasi) tutte le linee corrisponderebbero al profilo regexp e contano come "titolo".

Quindi, mettendo insieme alcune idee per ciascuna, ho il seguente: andare avanti riga per riga, saltando su righe vuote e più rientrate. Se sei alla stessa indentazione, è il prossimo fratello. L'idea di base è simile a questa:

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

Opportunamente generalizzato (avanti / indietro / su / giù), quello che sto usando al momento è il seguente:

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

C'è ancora qualche funzionalità in più desiderabile, e guardare outline.ele reimplementarne una parte può aiutare, ma per ora sono contento di questo, per i miei scopi.


@Gilles: grazie per le modifiche! Sembra che (current-line)fosse qualcosa misc-fns.elche ho in qualche modo nella mia installazione di Aquamacs come parte di alcune oneonone.ellibrerie.
ShreevatsaR,

6

Questa funzione esiste in Emacs. La modalità struttura descrive un documento come contenente le linee di prua con un livello e dispone di funzionalità per spostarsi tra i livelli. Possiamo definire ogni linea come linea di prua con un livello che rifletta il suo rientro: impostato outline-regexpsul rientro. Più precisamente, il rientro più il primo carattere non-spazio (e l'inizio del file è il livello più alto): \`\|\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 puoi semplificare questo per:

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

Quindi è possibile utilizzare i comandi di movimento del contorno :

  • C-C @ C-f( outline-forward-same-level) per passare al prossimo fratello;
  • C-C @ C-b( outline-backward-same-level) per passare al fratello precedente;
  • C-C @ C-u( outline-up-heading) per passare al genitore.

Una scheda e uno spazio contano per la stessa quantità di rientro. Se hai una combinazione di schede e spazi, imposta in modo tab-widthappropriato e chiamauntabify .

Se la modalità principale corrente ha impostazioni di struttura, potrebbero essere in conflitto. In questo caso, è possibile utilizzare una delle molte soluzioni a più modalità principali , la più semplice è creare un buffer indiretto e impostarlo su Modalità principale struttura. In Modalità principale struttura, le scorciatoie da tastiera predefinite sono più semplici da digitare:, C-c C-fecc.


Sembra che dovrebbe funzionare, ma in realtà non funziona per me per qualche motivo. M-x make-local-variable RET outline-regexp RETnon accetta quella variabile e dice solo `[Nessuna corrispondenza]`. Devo ancora esaminarlo più attentamente.
ShreevatsaR,

@ShreevatsaR È un cambiamento incompatibile in Emacs 24.4: outline-regexpnon è più un defcustom e non può essere impostato in modo interattivo così facilmente.
Gilles 'SO- smetti di essere malvagio' il

Molto bello grazie. Ci sono due problemi minori: (1) Se si è al livello più alto (una linea senza solco, che immagino mezzi non può competere con il contorno-regexp) allora né avanti né indietro opere, e per qualche motivo si va fino a due righe (2) quando passa al fratello successivo o precedente, va all'inizio della riga (colonna 0) ma sarebbe bello mantenere la colonna. (Come specificato nella domanda.) Immagino che entrambi questi possano essere limiti della modalità struttura stessa.
ShreevatsaR,

5

I seguenti tre comandi, minimamente testati, dovrebbero consentire la navigazione di base con linee rientrate. Ci scusiamo per la ripetizione del codice.

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

Va bene (dopo la correzione - non capisco cosa stavi cercando di sottrarre 1 (current-column)ma fa sì che il cursore non si sposti), ma non soddisfa esattamente le mie specifiche: spostarsi a un livello di rientro si sposta di meno- linee rientrate.
Gilles 'SO-smetti di essere malvagio' il

Questo non funziona Ad esempio, ind-forward-siblingcerca semplicemente la riga successiva con la stessa rientranza, quindi salta le righe con una rientranza minore (va avanti anche quando non vi sono fratelli in avanti).
ShreevatsaR,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.