VSCodeにBookmark機能を付けてみた

>この記事は、東京大学電子情報工学科・電気電子工学科(EEIC)3年後期実験の一つ「大規模ソフトウェアを手探る」のレポートとして書かれたものです。

#はじめに
「[大規模ソフトウェアを手探る](https://doss.eidos.ic.i.u-tokyo.ac.jp/)」という実験は、学生が2-4人程度の班に分かれ、班ごとに好きなOSSを一つ選んで機能拡張を試みるというものです。私たちの班は、Visual Studio Code(VSCode)にBookmark機能を実装するという方針で課題に取り組みました。

#課題の設定まで
###Visual Studio Codeについて
Visual Studio Codeは Microsoft社が発表しているコードエディターであり、世界中で一番使われてているコードエディターです。GitHubなどと統合された機能と拡張機能を用いることで、開発、デバッグ、デプロイなどをVisual Studio Code一つで行うことができます。

https://azure.microsoft.com/ja-jp/products/visual-studio-code/

また、Visual Studio CodeはソースコードがGitHubに公開されており、今回はそれに一部変更を加えることで機能の追加を実現しました。

https://github.com/microsoft/vscode

VSCodeのソースコードは全体の90%以上がTypeScriptで書かれています。

###課題の設定
VSCodeで長いコードを編集する際、以前書いたコードを見失い長い時間かけてコードを遡って探すといった苦労をしたことはないでしょうか。また将来必ず見返さなければいけない部分だと感じてにメモ帳にそれがコードの何行目かを記録したりしたことはないでしょうか。
私たちはこのような不便点を解消するためにブックマーク機能を搭載しようと考えました。ブックマークを付けていれば、どれだけ昔に書いたコードでもすぐに飛ぶことができ、迷うこともなくなります。

#課題の前準備
###ビルドの方法
ビルドに必要なパッケージのインストールなどは以下のサイトを参考にしました。
https://github.com/Microsoft/vscode/wiki/How-to-Contribute#build-and-run-from-source

ビルドにおいては以下の記事を参考にしました。
https://eeic2020-doss1.hatenablog.com/entry/2020/11/02/220316


ビルドの方法は以下の通りです。

vscodeフォルダの中で
1. yarn
2. yarn watch
3. 別のターミナルで ./scripts/code.sh
をコマンドに打ち込みビルドします。

注意：yarn watchを打ち込むと「Finished compilation extensions with…」と表示されしばらく表示が動かなくなります。この時に./scripts/code.shを別のターミナルに打ち込んでもエラーになります。
しばらく待つと「Finished compilation with...」という表示が出るため、それから./scripts/code.shを打ち込んでください。
また、既存のVSCodeが立ち上がった状態でyarn watchを実行すると、「error Command failed with exit code 1」と表示されて中断されることがあります。この場合は一度VSCodeを閉じてからyarn watchを実行し、「Finished compilation with...」が表示されてからVSCodeを開き直してください。

yarn watchが動いている間はソースコードの修正を常に監視して自動で再ビルドをしてくれるので、ソースコードを編集した後は./scripts/code.shを再度行うだけで編集が反映されたVSCodeを立ち上げることができます。

###デバッグの方法
VSCodeやEmacsを用いて、GitHubから入手したVSCodeのソースコードをデバッグすることができます。私たちのチームではVSCodeをデバッガとして用いました。

VSCodeでのデバッグとして主に以下の二つの方法が考えられます。

####1. Developer Toolsを用いる
VSCode上でCtrl+Shift+Iを押すとDeveloper Toolsを開くことができます。これはGoogle Chromeで 右クリック後のメニューから「検証」を押すと使えるものと同じです。このDeveloper ToolsではEvent Listener Breakpointsでマウスを押したりクリックしたりする際に呼び出されるコードを一行ずつ追っていける機能があり、それを用いてコードを読み解いていくことができます。 
![image2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/fbc195f1-08f3-bd65-f768-07751910c0cb.png)
これを用いる時には、VSCode上で「Debugger for Chrome」という拡張機能をインストールする必要があります。
![image3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/46e071f8-86a1-b560-9498-f0c72160756a.png)

####2. VSCodeのファイル検索や文字検索機能を用いる
ファイル検索はCtrl＋Pで検索画面を出すことができ、文字検索機能を左のサイドバーから検索画面に遷移することができます。
特定の機能に関連するワードを検索することで、その機能を担っているファイルや関数を調べることができます。例えばデバッグ機能について知りたい場合「Debug」という名前が入った関数やファイルを検索することでデバッグ機能を実現するファイルや関数を特定することができます。
![image1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/d446489b-642e-c4d7-3f00-a7e1a72a686b.png)
私たちは二つ目の方法を主軸にしてVSCodeの機能を分析しました。

#実装方針
実現したい仕様は、大きく分けて以下の3つです。

- Bookmarkのある行を(何らかの方法で)目立たせる
- Bookmarkのあるファイル名・行数を一覧表示する
- スクロールバーにBookmarkの位置を表示する

これらの仕様がVSCodeに元々備わっているBreakpoint機能に類似していることから、Breakpoint機能をもとにしてBookmarkを構成することにしました。

#実装
###1. Bookmarkのある行を目立たせる
Breakpointをもとにするとはいえ、あれと同じようなものを1から作るのは難しそうだったので、ひとまずBreakpointを改造してBookmarkを作り、余裕があればBreakpointとBookmarkを分離しようと決めました(こっちは結果的に実現できませんでしたが)。ということで、Breakpointを設定するときのメニューに「Add Bookmark」の項目を追加し、表示される点の色もBookmarkっぽいものに変更していきます。
まずは、通常のVSCodeのデバッグ機能を使って、Breakpointを追加する際の挙動を追ってみます、が・・・
#####全然見つからない。
デバッグに慣れていないせいもあるのでしょうが、該当する処理を実行している部分をまったく見つけられません。このままでは埒が明かないので、アプローチを変えてVSCodeの検索機能を活用することにしました。検索機能は、VSCodeの画面左端にあるメニューで虫眼鏡のマークをクリックすると使うことができます。Githubから拾ってきた「vscode」ディレクトリをVSCodeから開いた状態で検索機能を使うと、「vscode」以下のディレクトリにあるファイル全てに対して一括で検索をかけてくれます。なんて便利！
Breakpointを追加する際のメニューには「Add Breakpoint」「Add Conditional Breakpoint」「Add Logpoint...」の3項目があるので、このメニューを管理している箇所には「Add Breakpoint」という文字列が登場するはず。そんな希望的観測をしつつ、「"Add Breakpoint"」で検索をかけてみます。
![search.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/d2829d14-acf7-9dfe-abac-00bf4ba165a1.png)

```typescript:breakpointEditorContribution.ts
actions.push(new Action(
	'addBreakpoint',
	nls.localize('addBreakpoint', "Add Breakpoint"),
	undefined,
	true,
	() => this.debugService.addBreakpoints(uri, [{ lineNumber, column }])
));
```

ありました。
vscode/src/vs/workbench/contrib/debug/browserの中にあるbreakpointEditorContribution.tsです。見たところ、actionsがメニューの項目を管理する配列であり、actions.pushによってこの中に一つずつ項目を追加していっているようです。というわけで、actionsに新しく「Add Bookmark」の項目をpushすればメニューの編集は完了です。
具体的には、以下のコードをbreakpointEditorContribution.tsに追加しました。

```typescript:breakpointEditorContribution.ts
actions.push(new Action(
	'addBookmark',
	nls.localize('addBookmark', "Add Bookmark"),
	undefined,
	true,
	() => this.debugService.addBreakpoints(uri, [{ lineNumber, column }])
));
```

######[実行結果]
![contextmenu.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/381a47e7-4431-fbc0-6633-64d9bb0a240e.png)
こんな感じで、メニューに「Add Bookmark」の項目を追加することができました。やったね！
ちなみに、「Add Bookmark」を選択したときの処理は、暫定的に「Add Breakpoint」と同じものにしています。今後、BookmarkとBreakpointを分離させる際は、this.debugService.addBreakpointsの部分をBookmark追加用の新しい関数にする必要があります。
さて、次にBreakpointの色をBookmarkっぽい色に変えていきます。先程のbreakpointEditorContribution.tsで、Breakpointを追加するときの挙動を管理していそうだということが分かったので、このファイルをなんとなく眺めていると・・・

```typescript:breakpointEditorContribution.ts
const debugIconBreakpointForeground = registerColor('debugIcon.breakpointForeground', { dark: '#E51400', light: '#E51400', hc: '#E51400' }, nls.localize('debugIcon.breakpointForeground', 'Icon color for breakpoints.'));
```

見つけました。どう見てもここでBreakpointの色を指定していますね。3箇所のカラーコードをすべて、Bookmarkっぽい色のものに変更します。今回は、#00C3E5を選択しました。
######[実行結果]
![bcolor.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/73934f83-bf8f-ea1f-522f-2de23aaead8c.png)
Bookmarkを表す点が、綺麗な水色になりました。
最後に、細かいことではありますが、Bookmarkにカーソルを置いた際に表示される文字列を「Breakpoint」から「Bookmark」に変更します。メニューのときと同じように、「"Breakpoint"」で検索してみます。

```typescript:breakpointsView.ts
const message = ('message' in breakpoint && breakpoint.message) ? breakpoint.message : breakpoint instanceof Breakpoint && labelService ? labelService.getUriLabel(breakpoint.uri) : localize('breakpoint', "Breakpoint");
```

breakpointsView.tsという、いかにもな名前のファイルがヒットしました。「Breakpoint」と書かれた文字列部分を「Bookmark」に変更すると・・・
######[実行結果]
![bookmark.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/59530ee9-76d5-59be-daf8-a09aa70d8934.png)
思惑通り、Bookmarkにカーソルを合わせたときの文字列が変更されました。
###2. Bookmarkのあるファイル名・行数を一覧表示する
Breakpointには元々、VSCodeの画面左端のメニューで虫の形のアイコンをクリックすると表示される「Run and Debug」ページ内の、「Breakpoints」というプルダウンメニューの中に場所の一覧を表示させる機能が備わっているので、これを改造してBookmark専用のページにしていきます。
![breakpoints.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/0f4a66f4-d3f9-7acb-f94c-e44b810b2815.png)
まず、プルダウンメニューの名前を「Breakpoints」から「Bookmarks」に変更します。Breakpointを追加するメニューを編集したときと同じ発想で、「Breakpoints」という文字列があるはずという期待を胸に検索をかけると・・・
![pulldown.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/0c1f249f-f2fc-41e2-a01d-e87cd0a995ec.png)

```typescript:debug.contribution.ts
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
```

ありました。ここで、プルダウンメニューを追加しているようです。上を見ても、「Variables」「Watch」「Call Stack」といった「Run and Debug」ページ内のプルダウンメニューにある文言が並んでいるので間違いありません。「Breakpoints」を「Bookmarks」に変えると同時に、他のプルダウンメニューはコメントアウトして消去します。
具体的には、debug.contribution.tsの383〜388行目を以下のように変更しました。

```typescript:debug.contribution.ts
// viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), containerIcon: icons.variablesViewIcon, ctorDescriptor: new SyncDescriptor(VariablesView), order: 10, weight: 40, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
// viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), containerIcon: icons.watchViewIcon, ctorDescriptor: new SyncDescriptor(WatchExpressionsView), order: 20, weight: 10, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
// viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), containerIcon: icons.callStackViewIcon, ctorDescriptor: new SyncDescriptor(CallStackView), order: 30, weight: 30, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' }, when: CONTEXT_DEBUG_UX.isEqualTo('default') }], viewContainer);
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), containerIcon: icons.breakpointsViewIcon, ctorDescriptor: new SyncDescriptor(BreakpointsView), order: 40, weight: 20, canToggleVisibility: true, canMoveView: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' }, when: ContextKeyExpr.or(CONTEXT_BREAKPOINTS_EXIST, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
// viewsRegistry.registerViews([{ id: WelcomeView.ID, name: WelcomeView.LABEL, containerIcon: icons.runViewIcon, ctorDescriptor: new SyncDescriptor(WelcomeView), order: 1, weight: 40, canToggleVisibility: true, when: CONTEXT_DEBUG_UX.isEqualTo('simple') }], viewContainer);
// viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), containerIcon: icons.loadedScriptsViewIcon, ctorDescriptor: new SyncDescriptor(LoadedScriptsView), order: 35, weight: 5, canToggleVisibility: true, canMoveView: true, collapsed: true, when: ContextKeyExpr.and(CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_DEBUG_UX.isEqualTo('default')) }], viewContainer);
```

######[実行結果]
![bookmarks.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/a42ec6e7-3e65-e965-c812-4dcc9974fca8.png)
このように、「Run and Debug」ページをBookmarkの一覧表示専用のページにすることができました。項目が1つだけだとプルダウンメニューとしては表示されないようですが、表記が「RUN AND DEBUG:BOOKMARKS」になっていることから、変更は反映されていることが分かります。
ついでに、「Run and Debug」ページの名前も「Bookmarks」に変えましょう。debug.contribution.tsの、先程の箇所より少し上にページの情報を管理している部分があります。

```typescript:debug.contribution.ts
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).registerViewContainer({
	id: VIEWLET_ID,
	title: nls.localize('run and debug', "Run and Debug"),
	openCommandActionDescriptor: {
		id: VIEWLET_ID,
		mnemonicTitle: nls.localize({ key: 'miViewRun', comment: ['&& denotes a mnemonic'] }, "&&Run"),
		keybindings: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D },
		order: 3
	},
	ctorDescriptor: new SyncDescriptor(DebugViewPaneContainer),
	icon: icons.runViewIcon,
	alwaysUseContainerInfo: true,
	order: 3,
}, ViewContainerLocation.Sidebar);
```

titleの部分を「Bookmarks」に変えると・・・
######[実行結果]
![bookmarks-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/97efcd12-0e54-182a-dbac-8cc3b5e64195.png)
見事、表記が「BOOKMARKS」に変わりました。加えて、虫の形のアイコンにカーソルを合わせたときに表示される文字列も「Bookmarks」になっています。
次に、「Bookmarks」のアイコンをBookmarkらしいものに変更します。まず、この虫の形のアイコンがどこにあるのか探してみると、vscode/extensions/simple-browser/node_modules/vscode-codicons/src/iconsにある「debug-alt.svg」だと分かりました。それと同時に、このディレクトリには「bookmark.svg」という、Bookmarkに最適なアイコンも入っていました。これは僥倖。
「debug-alt.svg」を参照している以上、どこかしらにこのファイル名を指定している箇所があるはず、と思い検索をかけてみますがヒットせず。ファイル名と拡張子を別々に処理しているのでしょうか。仕方なく、今度は「debug-alt」のみで検索してみると・・・
![codicon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/1f0aad62-973a-dfa7-110e-bd42c5a102c8.png)

```typescript:codicons.ts
export const debugAlt = new Codicon('debug-alt', { fontCharacter: '\\eb91' });
```

何箇所かヒットしましたが、どうもvscode/vs/base/commonにあるcodicons.tsのこの部分が一番怪しそうです。恐らく、ここでは「'debug-alt'」で指定される画像ファイルを使って新しくアイコンのオブジェクトを作っているのでしょう。というわけで、「'debug-alt'」の部分を「'bookmark'」に変更してみます。
######[実行結果]
![icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/71c1df7e-e193-ec8f-49b3-3e0cb48cecc4.png)
見るからにBookmarkなアイコンに変わりました。
あとは、Bookmarkに残っている「Breakpointとしての性質」が(後述のファイル形式の問題などに関して)悪さをしないように、このページからBookmarkにとって不要な機能を除去する必要があります。具体的には、以下の3箇所です。

- 一覧の中のBookmarkにカーソルを合わせると出現する「Edit Condition」(下図の鉛筆のアイコン)
- Breakpointを無効にする機能(下図のチェックボックス)
- VSCodeでディレクトリを開くと現れる「Start debugging」(下図最上部のプルダウンメニュー)

![debugging.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/e2d68137-bf5a-34ed-b8ab-3fb9c01c15b3.png)
まず「Edit Condition」について、極めて単純にそのまま検索をかけてみます。
![edic.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/c731bc54-5052-99ad-a26c-714beeb44460.png)

```typescript:breakpointsView.ts
registerAction2(class extends ViewAction<BreakpointsView> {
	constructor() {
		super({
			id: 'debug.editBreakpoint',
			viewId: BREAKPOINTS_VIEW_ID,
			title: localize('editCondition', "Edit Condition..."),
			icon: Codicon.edit,
			precondition: CONTEXT_BREAKPOINT_SUPPORTS_CONDITION,
			menu: [{
				id: MenuId.DebugBreakpointsContext,
				group: 'navigation',
				order: 10
			}, {
				id: MenuId.DebugBreakpointsContext,
				group: 'inline',
				order: 10
			}]
		});
	}

	async runInView(accessor: ServicesAccessor, view: BreakpointsView, breakpoint: ExceptionBreakpoint | Breakpoint | FunctionBreakpoint): Promise<void> {
		const debugService = accessor.get(IDebugService);
		const editorService = accessor.get(IEditorService);
		if (breakpoint instanceof Breakpoint) {
			const editor = await openBreakpointSource(breakpoint, false, false, true, debugService, editorService);
			if (editor) {
				const codeEditor = editor.getControl();
				if (isCodeEditor(codeEditor)) {
					codeEditor.getContribution<IBreakpointEditorContribution>(BREAKPOINT_EDITOR_CONTRIBUTION_ID).showBreakpointWidget(breakpoint.lineNumber, breakpoint.column);
				}
			}
		} else if (breakpoint instanceof FunctionBreakpoint) {
			const contextMenuService = accessor.get(IContextMenuService);
			const actions: IAction[] = [new Action('breakpoint.editCondition', localize('editCondition', "Edit Condition..."), undefined, true, async () => view.renderInputBox({ breakpoint, type: 'condition' })),
			new Action('breakpoint.editCondition', localize('editHitCount', "Edit Hit Count..."), undefined, true, async () => view.renderInputBox({ breakpoint, type: 'hitCount' }))];
			const domNode = breakpointIdToActionBarDomeNode.get(breakpoint.getId());

			if (domNode) {
				contextMenuService.showContextMenu({
					getActions: () => actions,
					getAnchor: () => domNode,
					onHide: () => dispose(actions)
				});
			}
		} else {
			view.renderInputBox({ breakpoint, type: 'condition' });
		}
	}
});
```

2件ヒットしましたが、両方ともbreakpointsView.tsのこの領域に入っています。ここで呼び出されている「registerAction2」という関数が「Edit Condition」の動作を定義しているようなので、関数の呼び出しを丸ごとコメントアウトしてみると・・・
######[実行結果]
![edit_c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/f776f225-b1af-2333-97ee-21c02507070d.png)
このように、一覧表示されたBookmarkにカーソルを合わせても「Edit Condition」が現れなくなりました。
続いて、Breakpointを無効にするためのチェックボックスを消します。breakpointsView.tsがBreakpointの表示を管理していそうだということが分かったので、このファイル内で「checkbox」を検索してみます。46件ほどヒットしますが、何箇所かに集中しているので、根気よく探していくと・・・

```typescript:breakpointsView.ts
renderTemplate(container: HTMLElement): IBreakpointTemplateData {
	const data: IBreakpointTemplateData = Object.create(null);
	data.breakpoint = dom.append(container, $('.breakpoint'));

	data.icon = $('.icon');
	data.checkbox = createCheckbox();
	data.toDispose = [];
	data.elementDisposable = [];
	data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
	this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
	}));

	dom.append(data.breakpoint, data.icon);
	dom.append(data.breakpoint, data.checkbox);

	data.name = dom.append(data.breakpoint, $('span.name'));

	data.filePath = dom.append(data.breakpoint, $('span.file-path'));
	data.actionBar = new ActionBar(data.breakpoint);
	data.toDispose.push(data.actionBar);
	const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
	data.lineNumber = dom.append(lineNumberContainer, $('span.line-number.monaco-count-badge'));

	return data;
}
```

「enableOrDisableBreakpoints」という、明らかに件のチェックボックスと関連していそうな文言を含む箇所を見つけました。そこで、この箇所の9〜11行目の、data.toDispose.pushで始まる部分をコメントアウトしてみますが、チェックボックスを外してもBreakpointが無効化されなくなるだけで、チェックボックス自体は残ってしまいます。どうやら、この部分はチェックボックスの動作を指定しているようです。
そこで、次は14行目の「dom.append(data.breakpoint, data.checkbox);」をコメントアウトしてみます。
######[実行結果]
![checkbox.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/ce5dd03d-60aa-5f0c-7040-e586a13e7a08.png)
今度こそ、ちゃんとチェックボックス自体が消えてくれました。
最後は、「Start Debugging」の消去です。「Run and Debug」の編集を通してdebug.contribution.tsがデバッグ機能に深く関わっているということが分かったので、ひとまずこのファイルを眺めてみます。すると、タイトルを指定する箇所とプルダウンメニューを構成している箇所の間に、「DebugViewPaneContainer」というクラスがありました。「Start Debugging」が「Run and Debug」ページの最上部にあることを考えても、このクラスが「Start Debugging」に関わっている可能性はありそうだ、ということで検索をかけてみます。
![scd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/d043a3f5-482c-c7b2-8ad3-bebf7bc91e83.png)
debug.contribution.tsの他に、debugViewlet.tsというファイルがヒットしました。しかし、DebugViewPaneContainerの定義をする部分が長く、どこが表示に直接関与しているのか分かりません。そのため、「まず広範囲をコメントアウトしてみて、Start Debuggingが消えたら範囲を狭めていく」という方針に切り替えました。

```typescript:debugViewlet.ts
MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, {
	when: ContextKeyExpr.and(ContextKeyExpr.equals('viewContainer', VIEWLET_ID), CONTEXT_DEBUG_UX.notEqualsTo('simple'), WorkbenchStateContext.notEqualsTo('empty'),
		ContextKeyExpr.or(CONTEXT_DEBUG_STATE.isEqualTo('inactive'), ContextKeyExpr.notEquals('config.debug.toolBarLocation', 'docked'))),
	order: 10,
	group: 'navigation',
	command: {
		precondition: CONTEXT_DEBUG_STATE.notEqualsTo(getStateLabel(State.Initializing)),
		id: DEBUG_START_COMMAND_ID,
		title: DEBUG_START_LABEL
	}
});
```

その結果、DebugViewPaneContainerの定義が終わったすぐ後の、appendMenuItemが関与していることが分かりました。この部分をコメントアウトして実行してみると・・・
######[実行結果]
![start_debugging.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/b11f2b11-b342-a889-56d9-f7e66e4f1112.png)
めでたく「Start Debugging」の表示が消えてくれました。これで、Bookmarkの一覧表示は完成です。
###3. スクロールバーにBookmarkの位置を表示する
今回の実装において、恐らく最も難儀すると思われるのが「スクロールバーに位置を表示する」機能です。なぜなら、Breakpointの位置をスクロールバーに表示する機能はVSCodeに備わっていないからです。つまり、似たような機能をどこかから探してきて、それを追加するということをしなければなりません。
と、思っていたのですが・・・

```typescript:debug.contribution.ts
'debug.showBreakpointsInOverviewRuler': {
	type: 'boolean',
	description: nls.localize({ comment: ['This is the description for a setting'], key: 'showBreakpointsInOverviewRuler' }, "Controls whether breakpoints should be shown in the overview ruler."),
	default: false
},
```

######ありました。
単に自分たちが知らなかったのと、デフォルトで非表示になっていたから気づかなかっただけで、元々そういう機能は存在していたようです。
文字列のファイル内検索を行った際、スクロールバー上に該当箇所が表示される機能が「OverviewRuler」と呼ばれていることは分かっていたのですが、「OverviewRuler」で検索をかけても該当箇所が多すぎるため別の方法を模索していたところ、先程のdebug.contribution.tsを眺める中で偶然この箇所を発見しました。
default:falseをdefault:trueにするだけで、Bookmarkの位置をスクロールバーに表示する機能は完成です。
######[実行結果]
![overviewruler.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/f3bf176b-3fe1-e704-b007-fd5cfb26ba1a.png)
めでたく、スクロールバー上に水色の点でBookmarkの位置が表示されるようになりました。
###4. 任意のファイル形式に対応できるようにする
これは、実装すべき内容の中にはありませんでしたが、Bookmark機能をBreakpointから作ったことによって考える必要が出てきたものです。Breakpointはjsやcppといったファイルには置くことができるのですが、cssなどのファイル形式には対応していません。こうした種類のファイルにも、Bookmarkを置くことができるようにします。
これに関しては何も手がかりがなく、関連していそうな語句を片っ端から検索していくより他にありません。その結果、「showBreakpoint」で検索をかけた際に「showBreakpointHintAtLineNumber」という変数を発見しました。

```typescript:breakpointEditorContribution.ts
let showBreakpointHintAtLineNumber = -1;
const model = this.editor.getModel();
if (model && e.target.position && (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN || e.target.type === MouseTargetType.GUTTER_LINE_NUMBERS) && this.debugService.canSetBreakpointsIn(model) &&
	this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
	const data = e.target.detail as IMarginData;
	if (!data.isAfterLines) {
	showBreakpointHintAtLineNumber = e.target.position.lineNumber;
	}
}
this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber);
```

この変数はデフォルトでは-1に設定されており、特定の条件を満たした場合にのみe.target.position.lineNumberという値が代入されています。この値がカーソルの置かれている行数を表していると考えると、e.target.position.lineNumberを代入する条件文の中に「Breakpointを置けるファイル形式か否か」という情報が含まれているはずです。
長々とした条件文を少し丁寧に見ていくと、最後の部分で「this.debugService.canSetBreakpointsIn」という関数が登場していることが分かります。関数名から推察するに、これがBreakpointを設置できるか否かを判定する関数で間違いなさそうです。ということで、次は「canSetBreakpointsIn」で検索をかけてみます。

```typescript:debugAdapterManager.ts
canSetBreakpointsIn(model: ITextModel): boolean {
	const modeId = model.getLanguageIdentifier().language;
	if (!modeId || modeId === 'jsonc' || modeId === 'log') {
		// do not allow breakpoints in our settings files and output
		return false;
	}
	if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
		return true;
	}

	return this.breakpointModeIdsSet.has(modeId);
}
```

ありました。vscode/src/vs/workbench/contrib/debug/browserにあるdebugAdapterManager.tsの中で、canSetBreakpointsInの内容が定義されています。色々と条件分岐がありますが、これらはデバッグを行う際に不都合が生じないようにするためのものであり、Bookmarkとして使う限りにおいては問題ないだろうということで、思い切ってこの関数が常にtrueを返すようにしてしまいます。

```typescript:debugAdapterManager.ts
canSetBreakpointsIn(model: ITextModel): boolean {
	// const modeId = model.getLanguageIdentifier().language;
	// if (!modeId || modeId === 'jsonc' || modeId === 'log') {
	// 	// do not allow breakpoints in our settings files and output
	// 	return false;
	// }
	// if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
		return true;
	// }

	// return this.breakpointModeIdsSet.has(modeId);
}
```

######[実行結果]
![txt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2255815/a0453290-e932-076f-4800-ecfaa0434172.png)
なんと、cssどころかテキストにまでBookmarkが置けるようになりました。これでどんな形式のファイルが来ても安心。

#感想
振り返ってみると、チームの皆さんの努力のおかげで時間内に目標にしていた機能を実装できました。前半ではデバッガを使って一行一行追って行こうとしましたが、チーム全員がTypescript初心者であるため進捗は芳しくありませんでした。しかし後半になってからデバッグ方針を変えて、検索機能を利用して該当箇所を探すやり方をとった結果、一気に進捗が良くなりました。また、今回はブックマーク機能を一から実装するのではなく、既存のBreakpoint機能に変更を加える方針をとったことが目標達成の鍵になったと感じました。最後にBreakpoint機能を諦めざるをえないのはとても残念でしたが、10日以内に課題を完成させるために取捨選択も必要だと実感しました。これからも引き続き改善して、Breakpoint機能を復活させるために頑張っていきたいと思います。
今回の実験で実際のソフトウェアに触れることを通じて、大規模ソフトウェアに対する恐怖を乗り越えることができました。そして、チームメンバーが一丸となって目標を達成でき、チームの力を改めて実感しました。今回の経験を大事にして、ぜひ今後の勉強に活かしたいと思います。