Переміщення за допомогою відступу


15

Я хочу переходити між рядками файлу на основі відступу. Файл структурований за допомогою відступу: рядок, який є більш відрізним, ніж попередній рядок, є дочірньою частиною попереднього рядка; рядок, який має такий самий відступ, як і попередній рядок, є його побратимом. Я в основному шукаю три команди:

  • Перейдіть до наступного рядка, тобто до наступного рядка з тим же відступом, пропускаючи лінії, які є більш відступними, але не пропускаючи повз рядки з меншим відступом.
  • Перехід до попереднього брата, тобто те саме в іншому напрямку.
  • Перехід до батьківського, тобто до попереднього рядка з меншим відступом.

Положення стовпця точки не повинно змінюватися.

Це аналоги як для даних, структурованих відступом forward-sexp, так backward-sexpі backward-up-listдля даних, структурованих по сексам. Відступ відповідає структурі програми на таких мовах, як Haskell та Python; ці функції можуть бути особливо корисними в цьому контексті, але я не шукаю нічого, що залежить від режиму (мій основний випадок використання - це структурировані за наміром дані в іншому форматі файлу).

Рівні відступів фарбування можуть допомогти вручну переміщатися за допомогою Up/, Downале я хочу щось автоматичне.

Це питання щодо суперкористувача схожий, але із слабшими вимогами, і наразі немає відповідей, які відповідають моїм вимогам.


Чи set-selective-displayнаближаєтесь ви до того, що вам потрібно?
Каушал Моді

1
@KaushalModi Це корисно, і я про це не знав, тому дякую, але це не завжди те, що мені потрібно. Якраз зараз я хотів рухатися і бачити дітей ліній, по яких я рухався.
Жил "ТАК - перестань бути злим"

Дякуємо, що задали це запитання; Я збирався поставити одне і те ж питання лише менш добре. Єдине додаткове, що я хотів би - це "перейти до останнього брата", тобто останній рядок, який має однаковий відступ, не пропускаючи рядки з меншим відступом. (Еквівалент повторення "перехід до наступного брата", поки не буде.)
ShreevatsaR

Я щойно помітив пакет indent-toolsу melpa ( indent-tools ), який, ймовірно, працює для цієї мети. Перший вчинок був у 2016-му-16 травня, приблизно через 3 місяці після того, як було задано це питання.
ShreevatsaR

Відповіді:


4

Розглядаючи наявні чотири відповіді ( два на Super User та два на це питання), я бачу такі питання:

  • Ті , що в SuperUser від Стефана та Пен Бая (рухаються по рядку, дивлячись на поточний відступ), не реалізують збереження поточного положення стовпця та переміщення до батьківського,
  • Відповідь Дан ( з використанням повторного пошуку вперед , щоб знайти наступний рядок з тим же відступом) скаче по лініях з меншим відступом: він не знає , коли немає поруч рідний брата, і , отже , може рухатися до чого - то , що не є спорідненим але дитина іншого батька ... можливо, наступний "двоюрідний брат".
  • Відповідь на Жиль ( з використанням контуру-режим) не зберігається позицію стовпця і вона не працює з рядками з нульовим відступом ( «верхнього рівня» лінії). Крім того, дивлячись на його код outline.el, він також в основному йде по outline-next-visible-headingодному рядку в будь-якому випадку (використовуючи ) в нашому випадку, оскільки (майже) всі рядки будуть відповідати контуру регулярним виразом і вважатись "заголовком".

Отже, складаючи деякі ідеї кожного, у мене є наступне: рухатися вперед по черзі, пропускаючи порожні та більш відрізні рядки. Якщо ви з рівним відступом, то це наступний брат. Основна ідея виглядає так:

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

Відповідно узагальнений (вперед / назад / вгору / вниз), що я зараз використовую, виглядає наступним чином:

(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бібліотеки.
ШреєвацаР

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-widthвідповідне місце та зателефонуйтеuntabify .

Якщо в поточному головному режимі встановлені контури, вони можуть конфліктувати. У цьому випадку ви можете використовувати одне з безлічі рішень декількох основних режимів , найпростішим - створити непрямий буфер і встановити його в режим «Основний режим». У режимі основного контуру швидкі комбінації клавіатур за замовчуванням простіші:C-c C-f тощо.


Здається, це має працювати, але насправді чомусь не працює для мене. M-x make-local-variable RET outline-regexp RETне приймає цю змінну і каже лише `[No match]`. Я ще повинен уважніше розглянути його.
ShreevatsaR

@ShreevatsaR Це несумісна зміна в Emacs 24.4: outline-regexpце вже не дефект, і його не можна легко налаштувати інтерактивно.
Жил "ТАК - перестань бути злим"

Дуже приємно, дякую. Є дві незначні проблеми: (1) Якщо ви перебуваєте на найвищому рівні (рядок без відступів, який, мабуть, означає, що немає відповідності для контуру-регулярного виразів), то не працює ні вперед, ні вперед, і чомусь це піднімається на два рядки (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)але це призводить до того, що курсор не рухається), але не відповідає моїй специфікації: переміщення на рівні відступу рухається повз менше- відступні лінії.
Жил "ТАК - перестань бути злим"

Це не працює. Напр., ind-forward-siblingПросто шукає наступний рядок з тим же відступом, тому він пропускає рядки з меншим відступом (він йде вперед навіть тоді, коли немає перемоги вперед).
ShreevatsaR
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.