นำทางด้วยการเยื้อง


15

ฉันต้องการนำทางระหว่างบรรทัดของไฟล์ตามการเยื้อง ไฟล์ถูกจัดโครงสร้างโดยการเยื้อง: บรรทัดที่เยื้องกว่าบรรทัดก่อนหน้าคือชายด์ของบรรทัดก่อนหน้าบรรทัดที่มีการเยื้องเดียวกันกับบรรทัดก่อนหน้าคือพี่น้อง ฉันกำลังมองหาสามคำสั่งเป็นส่วนใหญ่:

  • ย้ายไปยังพี่น้องถัดไปเช่นบรรทัดถัดไปที่มีการเยื้องเดียวกันข้ามบรรทัดที่เยื้องมากกว่า แต่ไม่ข้ามบรรทัดที่เยื้องน้อยลง
  • ย้ายไปยังพี่น้องก่อนหน้านั่นคือสิ่งเดียวกันในทิศทางอื่น
  • ย้ายไปที่พาเรนต์เช่นไปยังบรรทัดก่อนหน้าโดยมีการย่อหน้าที่น้อยกว่า

ตำแหน่งคอลัมน์ของจุดไม่ควรเปลี่ยนแปลง

เหล่านี้เป็น analogs ข้อมูลเยื้องโครงสร้างการforward-sexp, backward-sexpและbackward-up-listข้อมูล sexp โครงสร้าง การเยื้องสอดคล้องกับโครงสร้างของโปรแกรมในภาษาเช่น Haskell และ Python ฟังก์ชั่นเหล่านี้มีประโยชน์อย่างยิ่งในบริบทนี้ แต่ฉันไม่ได้มองหาโหมดเฉพาะใด ๆ (กรณีการใช้งานหลักของฉันคือข้อมูลที่มีโครงสร้างความตั้งใจภายในรูปแบบไฟล์อื่น)

ระดับการเยื้องสีสามารถช่วยนำทางด้วยตนเองด้วยUp/ Downแต่ฉันต้องการบางสิ่งบางอย่างโดยอัตโนมัติ

คำถามผู้ใช้ระดับสูงนี้คล้ายกัน แต่มีข้อกำหนดน้อยกว่าและไม่มีคำตอบที่ตรงกับความต้องการของฉัน


ไม่set-selective-displayได้รับคุณใกล้ชิดกับสิ่งที่คุณต้องการ?
Kaushal Modi

1
@KaushalModi มันมีประโยชน์และฉันไม่รู้เกี่ยวกับสิ่งนี้ดังนั้นขอบคุณ แต่มันไม่ได้เป็นอย่างที่ฉันต้องการเสมอไป ตอนนี้ฉันต้องการที่จะย้ายไปและเห็นเด็ก ๆ ของเส้นที่ฉันกำลังย้ายไป
Gilles 'หยุดความชั่วร้าย'

ขอบคุณที่ถามคำถามนี้ ฉันกำลังจะถามคำถามเดียวกันโดยทั่วไปเท่านั้นไม่ดี สิ่งเพิ่มเติมที่ฉันต้องการคือ "ย้ายไปยังพี่น้องคนสุดท้าย" นั่นคือบรรทัดสุดท้ายที่มีการเยื้องเดียวกันไม่ใช่ข้ามเส้นที่เยื้องน้อย (เทียบเท่ากับการทำซ้ำ "ย้ายไปที่พี่น้องคนต่อไป" จนกว่าจะไม่มี)
ShreevatsaR

ฉันเพิ่งสังเกตเห็นแพคเกจindent-toolsใน melpa ( เยื้องเครื่องมือ ) ซึ่งอาจใช้งานได้เพื่อวัตถุประสงค์นี้ ความมุ่งมั่นครั้งแรกคือเมื่อวันที่ 2016 พฤษภาคม 16, ประมาณ 3 เดือนหลังจากถามคำถามนี้
ShreevatsaR

คำตอบ:


4

ตรวจสอบสี่คำตอบที่มีอยู่ในปัจจุบัน ( สองในผู้ใช้ Superและสองในคำถามนี้) ฉันเห็นปัญหาต่อไปนี้:

  • สิ่งที่อยู่ใน SuperUser โดย Stefan และ Peng Bai (การย้ายบรรทัดต่อบรรทัดดูที่การเยื้องปัจจุบัน) ไม่ได้ใช้การคงตำแหน่งคอลัมน์ปัจจุบันและเลื่อนขึ้นไปที่ผู้ปกครอง
  • คำตอบโดยแดน (ใช้ใหม่ค้นหาไปข้างหน้าเพื่อหาบรรทัดถัดไปกับการเยื้องเดียวกัน) ข้ามผ่านสายที่มีการเยื้องน้อย: มันไม่ได้รู้ว่าเมื่อไม่มีพี่น้องต่อไปและดังนั้นจึงสามารถย้ายไปยังบางสิ่งบางอย่างที่ไม่ได้เป็นพี่น้อง แต่ลูกของผู้ปกครองอีกคน ... อาจเป็น "ลูกพี่ลูกน้อง" คนต่อไป
  • คำตอบโดยกิลส์ (โดยใช้ร่างโหมด) ไม่รักษาตำแหน่งคอลัมน์และมันไม่ได้ทำงานกับเส้นที่มีศูนย์การเยื้อง ( "ระดับบน" บรรทัด) นอกจากนี้การดูรหัสในoutline.elมันก็เป็นไปตามบรรทัดต่อบรรทัด (ใช้outline-next-visible-heading) ในกรณีของเราเป็น (เกือบ) ทุกบรรทัดจะตรงกับ regexp เค้าร่างและนับเป็น "หัวเรื่อง"

ดังนั้นเมื่อรวบรวมแนวคิดบางอย่างของแต่ละข้อฉันมีสิ่งต่อไปนี้: เดินหน้าบรรทัดต่อบรรทัดข้ามบรรทัดว่างและเยื้องมากขึ้น หากคุณอยู่ที่การเยื้องเท่ากันมันก็เป็นพี่น้องคนต่อไป แนวคิดพื้นฐานมีลักษณะดังนี้:

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

generalized ที่เหมาะสม (ไปข้างหน้า / ข้างหลัง / ขึ้น / ลง) สิ่งที่ฉันใช้อยู่ดูเหมือนว่าต่อไปนี้:

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

ยังมีฟังก์ชั่นอื่น ๆ อีกมากมายที่ต้องการและการดูoutline.elและการนำบางส่วนมาใช้ใหม่อาจช่วยได้ แต่ตอนนี้ฉันมีความสุขกับสิ่งนี้เพื่อวัตถุประสงค์ของฉัน


@Gilles: ขอบคุณสำหรับการแก้ไข! ดูเหมือนว่า(current-line)เป็นอะไรบางอย่างmisc-fns.elที่ฉันมีในการติดตั้ง Aquamacs ของฉันซึ่งเป็นส่วนหนึ่งของoneonone.elห้องสมุดบางแห่ง
ShreevatsaR

6

คุณสมบัตินี้มีอยู่ใน Emacs โหมดเค้าร่างอธิบายเอกสารว่ามีบรรทัดหัวเรื่องที่มีระดับและมีสิ่งอำนวยความสะดวกในการเลื่อนไปมาระหว่างแต่ละด่าน เราสามารถกำหนดทุกบรรทัดเป็นบรรทัดหัวเรื่องที่มีระดับที่สะท้อนถึงการเยื้อง: ตั้งค่าoutline-regexpการเยื้อง อีกอย่างแม่นยำเยื้องบวกตัวอักษรตัวแรกที่ไม่ใช่ช่องว่าง \`\|\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

ใน Emacs 22.1–24.3 คุณสามารถทำให้สิ่งนี้ง่ายขึ้นเพื่อ:

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

จากนั้นคุณสามารถใช้คำสั่งการเคลื่อนที่เค้าร่าง :

  • C-C @ C-f( outline-forward-same-level) เพื่อย้ายไปยังพี่น้องคนถัดไป;
  • C-C @ C-b( outline-backward-same-level) เพื่อย้ายไปยังพี่น้องคนก่อนหน้า;
  • C-C @ C-u( outline-up-heading) เพื่อย้ายไปที่พาเรนต์

หนึ่งแท็บและหนึ่งช่องว่างนับสำหรับการเยื้องในจำนวนเดียวกัน หากคุณมีการผสมผสานของแท็บและพื้นที่ที่ตั้งtab-widthuntabifyเหมาะสมและโทร

หากโหมดหลักปัจจุบันมีการตั้งค่าเค้าร่างพวกเขาอาจขัดแย้งกัน ในกรณีนี้คุณสามารถใช้หนึ่งในหลายวิธีการแก้ปัญหาที่สำคัญโหมดที่ง่ายที่สุดในการสร้างบัฟเฟอร์ทางอ้อมและตั้งเป็นโหมดเค้าร่างหลัก ใน Outline Major Mode แป้นพิมพ์ลัดเริ่มต้นนั้นพิมพ์ได้ง่ายกว่า: C-c C-fฯลฯ


ดูเหมือนว่ามันจะใช้ได้ แต่ก็ไม่ได้ผลสำหรับฉันด้วยเหตุผลบางอย่าง M-x make-local-variable RET outline-regexp RETไม่ยอมรับตัวแปรนั้นและบอกเพียง `[ไม่มีการจับคู่] ' ยังไม่ได้ดูมันอย่างระมัดระวังมากขึ้น
ShreevatsaR

@ShreevatsaR มันเป็นการเปลี่ยนแปลงที่เข้ากันไม่ได้ใน Emacs 24.4: outline-regexpไม่เป็น defcustom อีกต่อไปและไม่สามารถตั้งค่าแบบโต้ตอบได้อย่างง่ายดาย
Gilles 'หยุดความชั่วร้าย'

ดีมากขอบคุณ มีสองประเด็นย่อย: (1) หากคุณอยู่ในระดับสูงสุด (บรรทัดที่ไม่มีการเยื้องซึ่งฉันเดาว่าไม่ตรงกับเค้าร่าง -regexp) จากนั้นไม่ไปข้างหน้าหรือข้างหลังและด้วยเหตุผลบางอย่างมันขึ้นสอง บรรทัด (2) เมื่อมันไปยังพี่น้องถัดไปหรือก่อนหน้ามันจะไปที่จุดเริ่มต้นของบรรทัด (คอลัมน์ 0) แต่จะเป็นการดีที่จะรักษาคอลัมน์ (ตามที่คุณระบุในคำถาม) ฉันเดาว่าทั้งสองอย่างนี้อาจเป็นข้อ จำกัด ของโหมดเค้าร่าง
ShreevatsaR

5

คำสั่งสามคำสั่งต่อไปนี้ซึ่งได้รับการทดสอบขั้นต่ำควรอนุญาตให้มีการนำทางพื้นฐานโดยบรรทัดที่เยื้อง ขออภัยในการทำซ้ำรหัส

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

นั่นเป็นสิ่งที่ดี (หลังจากการแก้ไข - ฉันไม่เข้าใจสิ่งที่คุณพยายามจะทำเมื่อลบ 1 ถึง(current-column)แต่ทำให้เคอร์เซอร์ไม่เคลื่อนที่) แต่ไม่ตรงตามข้อกำหนดของฉัน: การเคลื่อนที่ในระดับการเยื้องนั้นผ่านไปน้อยกว่า - เส้นที่เยื้อง
Gilles 'หยุดความชั่วร้าย'

มันใช้งานไม่ได้ เช่นind-forward-siblingเพียงมองหาบรรทัดถัดไปที่มีการเยื้องเดียวกันดังนั้นจึงข้ามบรรทัดที่มีการเยื้องน้อยกว่า (มันจะไปข้างหน้าแม้ในขณะที่ไม่มีพี่น้องอยู่ข้างหน้า)
ShreevatsaR
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.