Điều hướng bằng cách thụt lề


15

Tôi muốn điều hướng giữa các dòng của một tập tin dựa trên thụt lề. Tệp được cấu trúc bằng cách thụt lề: một dòng được thụt vào nhiều hơn dòng trước đó là con của dòng trước đó, một dòng có cùng độ lõm như dòng trước đó là anh chị em của nó. Tôi chủ yếu tìm kiếm ba lệnh:

  • Di chuyển đến anh chị em tiếp theo, tức là dòng tiếp theo có cùng vết lõm, bỏ qua các dòng được thụt vào nhiều hơn, nhưng không bỏ qua một dòng ít thụt vào.
  • Di chuyển đến anh chị em trước đó, tức là điều tương tự theo hướng khác.
  • Di chuyển đến cha mẹ, tức là đến dòng trước với ít thụt lề.

Vị trí cột của điểm không nên thay đổi.

Đây là những chất tương tự cho dữ liệu thụt đầu dòng có cấu trúc để forward-sexp, backward-sexpbackward-up-listcho dữ liệu sexp cấu trúc. Sự thụt lề tương ứng với cấu trúc chương trình trong các ngôn ngữ như Haskell và Python; các chức năng này có thể đặc biệt hữu ích trong ngữ cảnh này nhưng tôi không tìm kiếm bất kỳ chế độ cụ thể nào (trường hợp sử dụng chính của tôi là dữ liệu có cấu trúc dự định bên trong định dạng tệp khác).

Tô màu mức độ thụt lề có thể giúp điều hướng bằng tay với Up/ Downnhưng tôi muốn một cái gì đó tự động.

Câu hỏi siêu người dùng này tương tự nhưng với các yêu cầu yếu hơn và hiện không có câu trả lời đáp ứng yêu cầu của tôi.


set-selective-displaygiúp bạn gần với những gì bạn cần?
Kaushal Modi

1
@KaushalModi Nó hữu ích và tôi không biết về điều này, vì vậy cảm ơn bạn, nhưng nó không phải lúc nào cũng là thứ tôi cần. Vừa nãy, tôi muốn di chuyển và nhìn thấy những đứa trẻ của những dòng tôi đang di chuyển qua.
Gilles 'SO- ngừng trở nên xấu xa'

Cảm ơn đã hỏi câu hỏi này; Tôi đã định hỏi về cơ bản cùng một câu hỏi chỉ kém hơn. Điều bổ sung duy nhất tôi muốn là "chuyển đến anh chị em cuối cùng", tức là dòng cuối cùng có cùng vết lõm, không bỏ qua các dòng ít thụt vào. (Tương đương với việc lặp lại "chuyển sang anh chị em tiếp theo" cho đến khi không có gì.)
ShreevatsaR

Tôi chỉ nhận thấy gói indent-toolstrong melpa ( công cụ thụt lề ), có thể hoạt động cho mục đích này. Cam kết đầu tiên là vào năm 2016-ngày 16 tháng 5, khoảng 3 tháng sau khi câu hỏi này được hỏi.
ShreevatsaR

Câu trả lời:


4

Kiểm tra bốn câu trả lời hiện có ( hai trên Super User và hai cho câu hỏi này), tôi thấy các vấn đề sau:

  • Những người trên SuperUser của Stefan và Peng Bai (di chuyển từng dòng một, nhìn vào vết lõm hiện tại) không thực hiện giữ lại vị trí cột hiện tại và di chuyển lên tới cha mẹ,
  • Câu trả lời của Dan (sử dụng tìm kiếm lại chuyển tiếp để tìm dòng tiếp theo có cùng vết lõm) bỏ qua các dòng có ít vết lõm: không biết khi nào không có anh chị em tiếp theo và do đó có thể chuyển sang thứ không phải là anh chị em nhưng có thể là con của một phụ huynh khác, một người anh em họ tiếp theo có lẽ.
  • Câu trả lời của Gilles (sử dụng chế độ phác thảo) không giữ vị trí cột và nó không hoạt động với các dòng có độ thụt bằng 0 (dòng "cấp cao nhất"). Ngoài ra, nhìn vào mã của nó outline.el, về cơ bản, nó cũng đi theo từng dòng (sử dụng outline-next-visible-heading) trong trường hợp của chúng tôi, vì (gần như) tất cả các dòng sẽ khớp với biểu thức chính quy và được coi là "tiêu đề".

Vì vậy, kết hợp một số ý tưởng của từng ý tưởng, tôi có những điều sau đây: di chuyển về phía trước từng dòng, bỏ qua các dòng trống và thụt vào nhiều hơn. Nếu bạn ở vị trí thụt bằng nhau thì đó là anh chị em tiếp theo. Ý tưởng cơ bản trông như thế này:

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

Tổng quát phù hợp (tiến / lùi / lên / xuống), những gì tôi đang sử dụng trông giống như sau:

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

Vẫn còn một số chức năng mong muốn, và xem xét outline.elvà thực hiện lại một số trong số đó có thể giúp ích, nhưng bây giờ tôi hài lòng với điều này, cho mục đích của tôi.


@Gilles: Cảm ơn bạn đã chỉnh sửa! Có vẻ như (current-line)là thứ gì đó misc-fns.elmà tôi có trong phần cài đặt Aquamacs của mình như là một phần của oneonone.elthư viện.
ShreevatsaR

5

Ba lệnh sau, được kiểm tra tối thiểu, sẽ cho phép điều hướng cơ bản bằng các đường thụt vào. Xin lỗi cho sự lặp lại mã.

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

Điều đó tốt (sau khi sửa lỗi - Tôi không hiểu bạn đang cố làm gì khi trừ đi 1 (current-column)nhưng nó khiến con trỏ không di chuyển), nhưng không đáp ứng chính xác thông số của tôi: di chuyển ở mức thụt lề di chuyển ít hơn- đường thụt vào.
Gilles 'SO- ngừng trở nên xấu xa'

Điều này không hoạt động. Ví dụ, ind-forward-siblingchỉ đơn giản là tìm kiếm dòng tiếp theo với cùng một vết lõm, vì vậy nó bỏ qua các dòng có ít vết lõm hơn (nó đi về phía trước ngay cả khi không có anh chị em phía trước).
ShreevatsaR

5

Tính năng này tồn tại trong Emacs. Chế độ phác thảo mô tả một tài liệu có chứa các dòng tiêu đề với một cấp độ và có các phương tiện để di chuyển giữa các cấp độ. Chúng ta có thể định nghĩa mỗi dòng là một dòng tiêu đề với một mức độ phản ánh thụt lề của nó: được đặt thành outline-regexpthụt lề. Chính xác hơn, thụt lề cộng với ký tự không phải khoảng trắng đầu tiên (và phần đầu của tệp là mức cao nhất) : \`\|\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

Trong Emacs 22.1 xông24.3, bạn có thể đơn giản hóa việc này thành:

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

Sau đó, bạn có thể sử dụng các lệnh chuyển động phác thảo :

  • C-C @ C-f( outline-forward-same-level) để chuyển đến anh chị em tiếp theo;
  • C-C @ C-b( outline-backward-same-level) để chuyển đến anh chị em trước đó;
  • C-C @ C-u( outline-up-heading) để chuyển đến cha mẹ.

Một tab và một không gian được tính cho cùng một lượng thụt. Nếu bạn có sự kết hợp giữa các tab và dấu cách, hãy đặttab-width một cách thích hợp và gọiuntabify .

Nếu chế độ chính hiện tại có cài đặt phác thảo, chúng có thể xung đột. Trong trường hợp này, bạn có thể sử dụng một trong nhiều giải pháp nhiều chế độ chính , đơn giản nhất là tạo bộ đệm gián tiếp và đặt nó thành Chế độ chính phác thảo. Trong Outline Major Mode, các phím tắt mặc định sẽ đơn giản hơn để nhập : C-c C-f, v.v.


Điều này có vẻ như nó sẽ hoạt động, nhưng không thực sự làm việc cho tôi vì một số lý do. M-x make-local-variable RET outline-regexp RETkhông chấp nhận biến đó và chỉ nói `[Không khớp]`. Vẫn chưa nhìn vào nó cẩn thận hơn.
ShreevatsaR

@ShreevatsaR Đó là một thay đổi không tương thích trong Emacs 24.4: outline-regexpkhông còn là một thói quen và không thể được thiết lập tương tác dễ dàng như vậy.
Gilles 'SO- ngừng trở nên xấu xa'

Rất tuyệt cảm ơn bạn. Có hai vấn đề nhỏ: (1) Nếu bạn ở cấp cao nhất (một dòng không có vết lõm, mà tôi đoán có nghĩa là không khớp với phác thảo-regrec) thì không chuyển tiếp cũng không hoạt động ngược và vì lý do nào đó, nó đi lên hai các dòng (2) khi đi đến anh chị em kế tiếp hoặc trước đó, nó đi đến đầu dòng (cột 0) nhưng sẽ rất tốt nếu giữ lại cột. (Như bạn chỉ định trong câu hỏi.) Tôi đoán cả hai điều này có thể là những hạn chế của chính chế độ phác thảo.
ShreevatsaR
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.