Навигация по отступу

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

Изучая четыре доступных ответа ( два на Супер пользователя и два на этот вопрос), я вижу следующие проблемы:

  • Те , что в SuperUser от Stefan и Peng Bai (перемещаясь построчно, глядя на текущий отступ), не реализуют сохранение текущей позиции столбца и перемещение вверх к родителю,
  • Ответ Дан ( с использованием повторного поиска вперед , чтобы найти следующую строку с тем же отступом) скачет по линиям с меньшим отступом: он не знает , когда нет рядом родной брата, и , следовательно , может двигаться к чему - то , что не является родственным но ребенок от другого родителя ... возможно, от "двоюродного брата".
  • Ответ на Жиль ( с использованием контура-режим) не сохраняет позицию столбца и она не работает со строками с нулевым отступом ( «верхнего уровня» линии). Кроме того, если посмотреть на его код 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может помочь рассмотрение и переопределение некоторых из них, но сейчас я доволен этим для моих целей.

ShreevatsaR
источник
@ Жиль: Спасибо за правки! Похоже, что- (current-line)то из того, misc-fns.elчто у меня есть в моей установке Aquamacs как часть какой-то oneonone.elбиблиотеки.
ShreevatsaR
6

Эта функция существует в Emacs. Режим Outline описывает документ как содержащий строки заголовка с уровнем и имеет средства для перемещения между уровнями. Мы можем определить каждую строку как строку заголовка с уровнем, который отражает ее отступ: установить 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 .

Если текущий основной режим имеет настройки контура, они могут конфликтовать. В этом случае вы можете использовать одно из множества решений для нескольких основных режимов , самое простое из которых - создать косвенный буфер и установить для него режим Outline Major Mode. В основном режиме Outline основные сочетания клавиш по умолчанию проще набирать: C-c C-fи т. Д.

Жиль "ТАК - перестань быть злым"
источник
Кажется, это должно сработать, но на самом деле у меня почему-то не работает. M-x make-local-variable RET outline-regexp RETне принимает эту переменную и говорит только `[Нет совпадений]`. Я еще, чтобы изучить это более тщательно.
ShreevatsaR
@ShreevatsaR Это несовместимое изменение в Emacs 24.4: outline-regexpоно больше не является пользовательским и не может быть легко установлено в интерактивном режиме.
Жиль "ТАК - перестань быть злым"
Очень хорошо, спасибо. Есть две незначительные проблемы: (1) Если вы находитесь на самом верхнем уровне (строка без отступа, что, я думаю, означает, что нет совпадения с outline-regexp), то ни вперед, ни назад не работает, и по какой-то причине она поднимается на два lines (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