通过缩进导航


15

我想基于缩进在文件的各行之间导航。该文件由缩进构成:比前一行缩进更多的行是前一行的子级,与前一行缩进相同的行是其同级。我主要在寻找三个命令:

  • 移至下一个同级,即下一个具有相同缩进的行,跳过缩进较多的行,但不跳过缩进较少的行。
  • 移至上一个同级,即在另一个方向上移至同一事物。
  • 移至父级,即缩进较少的前一行。

该点的列位置不应更改。

这些是压痕结构化的数据,以类似物forward-sexpbackward-sexpbackward-up-list用于SEXP结构化数据。缩进对应于Haskell和Python等语言的程序结构;这些功能在这种情况下特别有用,但我并没有寻找任何特定于模式的内容(我的主要用例是另一种文件格式内的按意图构造的数据)。

着色缩进级别可以帮助使用Up/ 手动导航,Down但是我想要自动的东西。

这个超级用户问题与之类似,但要求较弱,并且目前没有满足我要求的答案。


是否set-selective-display使您接近所需的东西?
Kaushal Modi'3

1
@KaushalModi这很有用,我对此一无所知,所以谢谢,但这并不总是我所需要的。刚才,我想四处走走,看看我要走的线的孩子。
吉尔(Gilles)'所以

感谢您提出这个问题;我本来要问的基本上是相同的问题,只是不太好。我唯一想要的另一件事是“移至最后一个同级”,即具有相同缩进的最后一行,而不跳过缩进较少的行。(等同于重复“移至下一个兄弟姐妹”,直到没有兄弟姐妹为止。)
ShreevatsaR

我刚刚注意到indent-toolsmelpa(indent-tools)中的软件包,该软件包可能适用于此目的。第一次提交是在2016年5月16日,大约是提出此问题3个月后。
ShreevatsaR

Answers:


4

检查当前可用的四个答案(两个关于超级用户,两个关于这个问题),我发现以下问题:

  • 斯特凡和彭湃的那些超级用户(移动行由行,看着当前缩进)不执行保留当前的列位置和移动到父,
  • Dan答案(使用重新搜索找到具有相同缩进的下一行)跳过了缩进较少的行:它不知道何时没有下一个同级,因此可以移至非同级的行但是另一个父母的孩子……也许是下一个“表弟”。
  • Gilles答案(使用大纲模式)不会保留列的位置,并且不适用于缩进零的行(“顶层”行)。另外,在中查看其代码outline.eloutline-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和重新实现其中的一些功能可能会有所帮助,但是出于我的目的,我暂时对此感到满意。


@吉尔斯:感谢您的编辑!看起来(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-foutline-forward-same-level)移至下一个兄弟姐妹;
  • C-C @ C-boutline-backward-same-level)移至上一个兄弟姐妹;
  • C-C @ C-uoutline-up-heading)移至父母。

一个制表符和一个空格计入相同的缩进量。如果混合使用制表符和空格,请进行适当设置tab-width并致电untabify

如果当前的主模式具有大纲设置,则它们可能会发生冲突。在这种情况下,您可以使用多种主要模式解决方案之一,最简单的方法是创建一个间接缓冲区并将其设置为Outline Major Mode。在“大纲主要模式”下,默认的键盘快捷键更易于输入:C-c C-f,等等。


这似乎应该起作用,但由于某种原因实际上对我不起作用。M-x make-local-variable RET outline-regexp RET不接受该变量,只说“ [不匹配]​​”。还没有更仔细地研究它。
ShreevatsaR

@ShreevatsaR这是Emacs 24.4中的一个不兼容的更改:outline-regexp不再是defcustom,并且不能如此轻松地进行交互设置。
吉尔斯(Gillles)“所以-别再作恶了”

很好谢谢。有两个小问题:(1)如果您处于最高级别(没有缩进的行,我想这意味着与outline-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)但会导致光标不移动),但并不完全符合我的要求:以缩进级别移动的距离会减少-缩进线。
吉尔斯(Gillles)“所以-别再邪恶了”

这行不通。例如,ind-forward-sibling简单地寻找具有相同缩进的下一行,因此它跳过缩进较少的行(即使没有前向同级,它也会向前移动)。
ShreevatsaR
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.