インデントでナビゲートする


15

インデントに基づいてファイルの行間を移動したい。ファイルはインデントによって構成されます。前の行よりもインデントされている行は前の行の子であり、前の行と同じインデントを持つ行はその兄弟です。私は主に3つのコマンドを探しています:

  • 次の兄弟、つまり同じインデントを持つ次の行に移動します。インデントの多い行をスキップしますが、インデントの少ない行をスキップしません。
  • 前の兄弟、つまり他の方向に同じものに移動します。
  • 親、つまりインデントの少ない前の行に移動します。

ポイントの列位置は変更しないでください。

これらは、への字下げ構造化データの類似体であるforward-sexpbackward-sexpbackward-up-listS式構造化データのため。インデントは、HaskellやPythonなどの言語のプログラム構造に対応しています。これらの関数は、このコンテキストでは特に役立ちますが、モード固有のものは探していません(私の主な使用例は、別のファイル形式内の意図構造化データです)。

インデントレベルの色付けはUp/を使用して手動で移動するのに役立ちますが、Down自動的に何かが必要です。

このスーパーユーザーの質問は似ていますが、要件はより低く、現在、私の要件を満たす答えはありません。


set-selective-displayあなたが必要なものの近くにあなたを得ますか?
カウシャルモディ

1
@KaushalModiこれは便利だし、これについては知らなかったので、ありがとう。しかし、いつも必要なわけではない。ちょうど今、私は動き回り、私が動いていたラインの子供たちを見たかった。
ジル 'SO-悪であるのをやめる'

この質問をしてくれてありがとう。私は基本的に同じ質問をあまりよくしていませんでした。私が望む唯一の追加事項は、「最後の兄弟に移動する」、つまり、インデントが少ない行をスキップせずに、同じインデントを持つ最後の行です。(何もなくなるまで「次の兄弟に移動」を繰り返すのと同じです。)
ShreevatsaR

indent-toolsmelpa(indent-tools)のパッケージに気づいたところですが、これはおそらくこの目的で機能します。最初のコミットは2016-May-16で、この質問が尋ねられてから約3か月後に行われました。
シュ

回答:


4

現在利用可能な4つの回答(スーパーユーザーに2つ、この質問に2つ)を調べると、次の問題があります。

  • ステファンと鵬白によってスーパーユーザのものは、現在の列の位置を保持し、親にまで移動する実装されていません(現在のインデントを見て、ライン・バイ・ラインを動かします)
  • ダンによって答えは何の次の兄弟が存在しない場合、それはわからないので、兄弟ではないというものに移動することができます少ないインデントと行はスキップ(同じインデントで次の行を見つけるために、フォワード再検索を使用して)しかし、別の親の子…おそらく次の「いとこ」。
  • ジルによって回答(アウトラインモードを使用して)は、列位置を保持せず、ゼロくぼみ(「トップレベル」の線)と線で作業をしません。また、のコードを見ると、(ほとんど)すべての行がアウトラインの正規表現に一致し、「見出し」としてカウントされるため、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:編集してくれてありがとう!どうやら、ライブラリの一部として、Aquamacsのインストールに何らかの形で含まれている(current-line)もののように見えます。misc-fns.eloneonone.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-foutline-forward-same-level)次の兄弟に移動します。
  • C-C @ C-boutline-backward-same-level)前の兄弟に移動します。
  • C-C @ C-uoutline-up-heading)親に移動します。

1つのタブと1つのスペースは、同じ量のインデントに対してカウントされます。タブとスペースが混在している場合は、適切設定してtab-widthを呼び出しますuntabify

現在のメジャーモードにアウトライン設定がある場合、競合する可能性があります。この場合、多くの複数のメジャーモードソリューションのいずれかを使用できます。最も簡単な方法は、間接バッファを作成し、アウトラインメジャーモードに設定することです。アウトラインメジャーモードでは、デフォルトのキーボードショートカットをより簡単に入力できます:C-c C-fなど。


これは機能するはずですが、実際には何らかの理由で機能しません。M-x make-local-variable RET outline-regexp RETその変数を受け入れず、 `[No match]`のみを言います。まだもっと注意深く調べていません。
シュリーバツァー

@ShreevatsaRこれはEmacs 24.4の互換性のない変更outline-regexpです。もはやdefcustomではなく、対話的に簡単に設定することはできません。
ジル 'SO-悪であるのをやめる'

ありがとう、とても良いです。2つの小さな問題があります:(1)最上位(インデントのない行、outline-regexpに一致しないことを意味すると思います)にいる場合、前方にも後方にも機能せず、何らかの理由で2つ上がります行(2)次または前の兄弟に移動する場合、行の先頭(列0)に移動しますが、列を保持しておくと便利です。(質問で指定しているように。)両方ともアウトラインモード自体の制限かもしれません。
シュ

5

次の3つのコマンドは、最小限のテストで、インデントされた行による基本的なナビゲーションを可能にします。コードの繰り返しをおApびします。

(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)が、カーソルが移動しなくなります)が、正確には私の仕様を満たしていません:インデントレベルで移動すると、インデントされた行。
ジル 'SO-悪であるのをやめる'

これは機能しません。たとえばind-forward-sibling、同じインデントで次の行を探すだけなので、インデントの少ない行をスキップします(前の兄弟がない場合でも前に進みます)。
シュ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.