Navigasi dengan indentasi

15

Saya ingin menavigasi di antara baris file berdasarkan indentasi. File terstruktur berdasarkan indentasi: baris yang lebih indentasi daripada baris sebelumnya adalah anak dari baris sebelumnya, baris yang memiliki lekukan yang sama dengan baris sebelumnya adalah saudara kandungnya. Saya kebanyakan mencari tiga perintah:

  • Pindah ke saudara berikutnya, yaitu baris berikutnya dengan lekukan yang sama, melompati garis yang lebih menjorok, tetapi tidak melewati garis yang kurang menjorok.
  • Pindah ke saudara sebelumnya, yaitu hal yang sama di arah lain.
  • Pindah ke induk, yaitu ke baris sebelumnya dengan sedikit lekukan.

Posisi kolom titik tidak boleh berubah.

Ini adalah analog untuk data terstruktur indentasi forward-sexp, backward-sexpdan backward-up-listuntuk data terstruktur jenis kelamin. Lekukan sesuai dengan struktur program dalam bahasa seperti Haskell dan Python; fungsi-fungsi ini dapat sangat berguna dalam konteks ini, tetapi saya tidak mencari apa pun untuk mode khusus (kasus penggunaan utama saya adalah data terstruktur dengan maksud di dalam format file lain).

Tingkat indentasi warna dapat membantu menavigasi secara manual dengan Up/ Downtetapi saya menginginkan sesuatu yang otomatis.

Pertanyaan pengguna super ini serupa tetapi dengan persyaratan yang lebih lemah dan saat ini tidak memiliki jawaban yang memenuhi persyaratan saya.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Apakah set-selective-displayAnda dekat dengan yang Anda butuhkan?
Kaushal Modi
1
@ KaushalModi Bermanfaat, dan saya tidak tahu tentang ini, jadi terima kasih, tapi itu tidak selalu yang saya butuhkan. Baru saja, saya ingin bergerak dan melihat anak-anak dari garis yang saya pindah.
Gilles 'SANGAT berhenti menjadi jahat'
Terima kasih telah mengajukan pertanyaan ini; Pada dasarnya saya akan mengajukan pertanyaan yang sama dengan kurang baik. Satu-satunya hal tambahan yang saya suka adalah "pindah ke saudara terakhir" yaitu baris terakhir yang memiliki lekukan yang sama, bukan melompati garis yang kurang menjorok. (Setara dengan pengulangan "pindah ke saudara berikutnya" sampai tidak ada.)
ShreevatsaR
Saya hanya memperhatikan paket indent-toolsdalam melpa ( indent-tools ), yang mungkin berfungsi untuk tujuan ini. Komit pertama adalah pada 2016-Mei-16, sekitar 3 bulan setelah pertanyaan ini diajukan.
ShreevatsaR

Jawaban:

4

Meneliti empat jawaban yang tersedia saat ini ( dua di Super User dan dua di pertanyaan ini), saya melihat masalah berikut:

  • Yang di SuperUser oleh Stefan dan Peng Bai (bergerak baris demi baris, melihat lekukan saat ini) tidak menerapkan mempertahankan posisi kolom saat ini dan naik ke orangtua,
  • The jawaban oleh Dan (menggunakan kembali pencarian maju untuk menemukan baris berikutnya dengan lekukan yang sama) melompati garis dengan kurang lekukan: tidak tahu kapan ada saudara berikutnya, dan karena itu dapat pindah ke sesuatu yang tidak saudara kandung tapi anak dari orang tua lain ... "sepupu" selanjutnya mungkin.
  • The jawaban dengan Gilles (menggunakan garis-mode) tidak mempertahankan posisi kolom dan itu tidak bekerja dengan garis-garis dengan nol lekukan ( "top-level" garis). Juga, dengan melihat kodenya outline.el, pada dasarnya juga akan berjalan baris demi baris (menggunakan outline-next-visible-heading) dalam kasus kami, karena (hampir) semua baris akan cocok dengan regexp garis besar dan dihitung sebagai "heading".

Jadi, mengumpulkan beberapa ide dari masing-masing, saya memiliki yang berikut: maju selangkah demi selangkah, melompati garis-garis kosong dan lebih indentasi. Jika Anda berada pada indentasi yang sama maka itu adalah saudara kandung berikutnya. Ide dasarnya terlihat seperti ini:

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

Secara umum sesuai (maju / mundur / naik / turun), apa yang saya gunakan tampak seperti berikut ini saat ini:

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

Masih ada beberapa fungsi yang diinginkan, dan melihat outline.eldan mengimplementasikan kembali beberapa yang dapat membantu, tapi saya senang dengan ini untuk saat ini, untuk tujuan saya.

ShreevatsaR
sumber
@Gilles: Terima kasih atas suntingannya! Sepertinya (current-line)ada sesuatu dari misc-fns.elyang entah bagaimana saya miliki di instalasi Aquamacs saya sebagai bagian dari beberapa oneonone.elperpustakaan.
ShreevatsaR
6

Fitur ini ada di Emacs. Mode Outline menggambarkan dokumen sebagai berisi garis judul dengan level, dan memiliki fasilitas untuk berpindah antar level. Kita dapat mendefinisikan setiap baris sebagai baris judul dengan level yang mencerminkan lekukannya: atur outline-regexpke lekukan tersebut. Lebih tepatnya, lekukan ditambah karakter non-spasi pertama (dan awal file adalah tingkat paling atas): \`\|\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

Di Emacs 22.1–24.3 Anda dapat menyederhanakan ini untuk:

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

Kemudian Anda dapat menggunakan perintah gerakan garis besar :

  • C-C @ C-f( outline-forward-same-level) untuk pindah ke saudara kandung berikutnya;
  • C-C @ C-b( outline-backward-same-level) untuk pindah ke saudara sebelumnya;
  • C-C @ C-u( outline-up-heading) untuk pindah ke induk.

Satu tab dan satu spasi menghitung jumlah indentasi yang sama. Jika Anda memiliki campuran tab dan spasi, atur dengan tab-widthtepat dan panggiluntabify .

Jika mode utama saat ini memiliki pengaturan garis besar, mereka mungkin bertentangan. Dalam hal ini, Anda dapat menggunakan salah satu dari banyak solusi mode utama , yang paling sederhana adalah membuat buffer tidak langsung dan mengaturnya ke Mode Mode Garis Besar. Di Outline Major Mode, pintasan keyboard default lebih mudah untuk diketik:, C-c C-fdll.

Gilles 'SANGAT berhenti menjadi jahat'
sumber
Sepertinya ini seharusnya berhasil, tetapi sebenarnya tidak bekerja untuk saya untuk beberapa alasan. M-x make-local-variable RET outline-regexp RETtidak menerima variabel itu, dan hanya mengatakan `[Tidak cocok]`. Saya belum melihat ke dalamnya lebih hati-hati.
ShreevatsaR
@ShreevatsaR Ini adalah perubahan yang tidak kompatibel dalam Emacs 24.4: outline-regexptidak lagi merupakan defcustom dan tidak dapat diatur secara interaktif dengan mudah.
Gilles 'SO- stop being evil'
Sangat baik terima kasih. Ada dua masalah kecil: (1) Jika Anda berada di level teratas (garis tanpa lekukan, yang saya kira berarti tidak ada kecocokan untuk outline-regexp) maka maju atau mundur tidak berfungsi, dan untuk beberapa alasan naik dua baris (2) ketika menuju ke saudara kandung berikutnya atau sebelumnya, ia pergi ke awal baris (kolom 0) tetapi akan lebih baik untuk mempertahankan kolom. (Seperti yang Anda tentukan dalam pertanyaan.) Saya kira keduanya mungkin merupakan batasan mode garis besar itu sendiri.
ShreevatsaR
5

Tiga perintah berikut, minimal diuji, harus memungkinkan untuk navigasi dasar dengan jalur indentasi. Permintaan maaf untuk pengulangan kode.

(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
sumber
Itu bagus (setelah perbaikan - saya tidak mengerti apa yang Anda coba lakukan dengan mengurangi 1 menjadi (current-column)tetapi itu menyebabkan kursor tidak bergerak), tetapi tidak benar-benar memenuhi spesifikasi saya: bergerak pada tingkat indentasi bergerak melewati kurang- garis indentasi.
Gilles 'SANGAT berhenti menjadi jahat'
Ini tidak berhasil. Misalnya ind-forward-siblinghanya mencari baris berikutnya dengan lekukan yang sama, sehingga melompati baris dengan lekukan yang lebih sedikit (berjalan maju bahkan ketika tidak ada saudara kandung maju).
ShreevatsaR