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.