0
0

More than 1 year has passed since last update.

【VSCode】定義参照を簡単に!

Last updated at Posted at 2022-11-09

この記事は、東京大学工学部電子情報工学科/電気電子工学科の後期実験「大規模ソフトウェアを手探る」のレポートとして作成されました。

定義参照を楽にしたい

VSCodeでコードを書いているとき、おそらく皆さんのほとんどが関数やクラス、変数などを使うと思います。これらを使う際に、それぞれの定義を参照する機会も多いでしょう。

定義を参照したいとき、よく使われるのがGo to DefinitionGo to Type Definitionなどの定義元にコードジャンプする機能だと思います。しかし、例えば長いコードにおいてある関数について定義元にコードジャンプすると、参照した後に参照前の位置に戻るためにスクロールする必要があり、面倒です。また、同時に参照することができず、コード内を行ったり来たりしないといけなくなり、大変です。わかりにくいかもしれないので実際に見てみましょう。次のようなコードがあったとします。

def function1(x: str):
    print(f'Hello, {x}!')

def function2(x: str):
    # Long code begins...
    # ...(実際は数十行の空行がある)...
    # Long code ends
    print(x)

def function3(x: str):
    if x == '':
        print("string is empty")
    else:
        print("string is not empty")

def main():
    # The function whose definition you want to know
    # if you right-click on function1 below and select "Go to Definition"
    # you will jump to the beginning of this file in the same tab.
    function1("World")

if __name__ == "__main__":
    main()

このコードではmain()の中に、コードの冒頭で定義されたfunction1()という関数が呼び出されています。function1()の定義を参照したいとき、function1()を右クリックすると、context menuが出ます。

ここでGo to Definitionをクリックすると、次のように定義元のあるコードの冒頭に飛びます。

こうすると、main()内のfuntion1()を表示しながら定義元も同時に見ることができず、また、参照前の位置に戻るのも面倒です。

そこで、定義元を参照すると、別タブで画面分割して同時に表示できる機能を実装したいと思ったのですが、ソースコードを探っていくと、この動作を実行できるOpen Definition to Sideという機能が既に存在することがわかりました。しかしこの機能はコマンドパレットからしか実行できず不便なため、この機能をcontext menuからも呼び出せるようにしたら便利だと考えました。

今回はオープンソースソフトウェアであるVSCodeに、定義元を参照すると、別タブで画面分割して同時に表示できる機能を右クリックで実行できるよう実装しました。

ソースコードを探り、理解する

当初はOpen definition to Sideという機能の存在を知らなかったため、まずはGo to Definitionがどう実装されているかを知り、右クリックで展開されるContext menuに新しいアクションメニューを追加しようと考えました。Ctrl+FGo to Definitionと検索し、探ると次のようなコードが見つかりました。

registerAction2(class GoToDefinitionAction extends DefinitionAction {
	static readonly id = 'editor.action.revealDefinition';
	constructor() {
		super({
			openToSide: false,
			openInPeek: false,
			muteMessage: false
		}, {
			id: GoToDefinitionAction.id,
			title: {
				value: nls.localize('actions.goToDecl.label', "Go to Definition"),
				original: 'Go to Definition',
				mnemonicTitle: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition")
			},
			precondition: ContextKeyExpr.and(
				EditorContextKeys.hasDefinitionProvider,
				EditorContextKeys.isInWalkThroughSnippet.toNegated()),
			keybinding: {
				when: EditorContextKeys.editorTextFocus,
				primary: goToDefinitionKb,
				weight: KeybindingWeight.EditorContrib
			},
			menu: [{
				id: MenuId.EditorContext,
				group: 'navigation',
				order: 1.1
			}, {
				id: MenuId.MenubarGoMenu,
				group: '4_symbol_nav',
				order: 2,
			}]
		});
		CommandsRegistry.registerCommandAlias('editor.action.goToDeclaration', GoToDefinitionAction.id);
	}
});

この関数を探しているときに、この下に`Open Definition to Side`を定義する関数がありました。以下に示すのがその部分のコードです。
registerAction2(class OpenDefinitionToSideAction extends DefinitionAction {
	static readonly id = 'editor.action.revealDefinitionAside';
	constructor() {
		super({
			openToSide: true,
			openInPeek: false,
			muteMessage: false
		}, {
			id: OpenDefinitionToSideAction.id,
			title: {
				value: nls.localize('actions.goToDeclToSide.label', "Open Definition to the Side"),
				original: 'Open Definition to the Side'
			},
			precondition: ContextKeyExpr.and(
				EditorContextKeys.hasDefinitionProvider,
				EditorContextKeys.isInWalkThroughSnippet.toNegated()),
			keybinding: {
				when: EditorContextKeys.editorTextFocus,
				primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, goToDefinitionKb),
				weight: KeybindingWeight.EditorContrib
			}
		});
		CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id);
	}
});

この部分に注目すると、class OpenDefinitionToSideActionというクラスを定義して、そのクラスをregisterAction2という関数に引数として渡していることがわかります。

このregisterAction2という関数は vs/platform/actions/common/actions で定義されているのをimportしている上に、関数の名前などからこれはActionを表すクラスからショートカットキーや右クリックメニューにアクションを追加するという処理を行っていると考えられます。

すなわち、Actionという共通の動作を表すクラスが存在し、右クリックやコマンドパレットからの実行、ショートカットキーの実行は全てこのクラスを参照し、このクラス内で指定している関数が実行されるようになっているであろうということです。

いざ実装!

今回はOpen Definition to Sideというアクションが、コマンドパレットからは実行できるにも関わらず右クリックのメニューに表示されないことが問題でしたから、Go to DefinitionOpen Definition to Sideを見比べて、適切な宣言や処理を追加すれば良いのではという考えのもと、コードを改変しました。

registerAction2(class OpenDefinitionToSideAction extends DefinitionAction {

	static readonly id = 'editor.action.revealDefinitionAside';

	constructor() {
		super({
			openToSide: true,
			openInPeek: false,
			muteMessage: false
		}, {
			id: OpenDefinitionToSideAction.id,
			title: {
				value: nls.localize('actions.goToDeclToSide.label', "Open Definition to Side"),
				original: 'Open Definition to Side',
+				mnemonicTitle: nls.localize({ key: 'miOpenDefinitionToSideAction', comment: ['&& denotes a mnemonic'] }, "Open &&Definition to Side")
			},
			precondition: ContextKeyExpr.and(
				EditorContextKeys.hasDefinitionProvider,
				EditorContextKeys.isInWalkThroughSnippet.toNegated()),
			keybinding: {
				when: EditorContextKeys.editorTextFocus,
-               primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KeyK, goToDefinitionKb),
+				primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyK,
				weight: KeybindingWeight.EditorContrib
			},
+			menu: [{
+				id: MenuId.EditorContext,
+				group: 'navigation',
+				order: 1.1
+			}, {
+				id: MenuId.MenubarGoMenu,
+				group: '4_symbol_nav',
+				order: 2,
+			}]
		});
		CommandsRegistry.registerCommandAlias('editor.action.openDeclarationToTheSide', OpenDefinitionToSideAction.id);
	}
});

上の実装では、DefinitionActionのコンストラクタに渡すオブジェクトにmenuというメンバを追加し、そこでidやどのグループに表示するか、表示順はどうするかなどの情報を追加しています。
こうすることで、右クリックした時のコンテキストメニューにOpen Definition to Sideが追加されます。

デモンストレーション

では、実際に動かしてみましょう。

右クリックすると、context menuOpen Definition to Sideというアクションメニューが追加されており、これをクリックすると画面分割して現在の位置と定義元の位置が同時に表示できていますね!これなら参照しながらわかりやすくコードを書くことができると思います。

ビルド方法について

ビルドのやり方についてはこの記事で説明しています。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0