LoginSignup
20
3

More than 1 year has passed since last update.

仕事をちょっぴり楽にするVSCode拡張機能を作ろう! メモファイル作成編

Last updated at Posted at 2022-12-11

フューチャー Advent Calendar 2022 12日目の記事です。
昨日は @2019Shun さんの「Excelファイルを読み込むPythonライブラリを比較してみた」でした。

はじめに

はじめまして。フューチャーの @920OJ と申します。アルバイトで2年弱勤務した後今年の7月に新卒入社し、新人研修を経て、最近PJに配属されたばかりの新人です。日々の業務に苦戦しつつも、刺激的な毎日を送っています。

今回は、「仕事をちょっぴり楽にするVSCode拡張機能を作ろう!」というテーマで、メモファイルを簡単に作成できるVisual Studio Code(以下VSCode)の拡張機能を作成する方法を紹介します。

環境

  • Windows 11 Home Edition
  • Visual Studio Code 1.74.0
  • Node.js v18.12.1
  • npm v8.19.2

完成したもの

まずはGIFをご覧ください。

Animation.gif

コマンドパレットでCreate Memo File を選択すると入力欄が表示されます。メモのタイトルを入力しEnterを押すと、「日付_タイトル.md」というMarkdownファイルが作成されます。また、このMarkdownファイルは先程入力したタイトルの見出し1が設定されています。

作った経緯

私はよく業務中のメモをMarkdown形式で、VSCodeを利用して取るようにしています。そんな中、毎回「日付_タイトル.md」というファイルを作成し、見出し1を記載し、というのはたかが10秒程度の作業ですが、同じタイトルを2回入力するため面倒に感じるときがあります。そこで、このファイルを用意する作業を拡張機能で半自動化すれば、少しは労力が軽減されるのでは、と考えました。(10秒が5秒に変わるだけでは、というツッコミは無いものとします)

後は、単純にVSCodeの拡張機能がどのように作られているかを知りたかったためです。では早速作っていきましょう!

環境構築

まずは、VSCodeの拡張機能を作成するために必要なものを導入します。モダンWebアプリ総合開発ツールであるYeomanのうちの一機能である雛形生成機能 yo と、VSCodeが公式に提供している拡張機能コードジェネレータである VS Code Extension Generator を、npmを利用してグローバルにインストールします。

npm install -g yo generator-code

次に、yo コマンドを利用して、VSCodeの拡張機能の雛形を生成します。ここでは、カレントディレクトリに memo-starter ディレクトリを作成した上で、その中に生成しています。

yo code memo-starter

前段で正常にインストールが完了していると、AAで表現されたおじさま(Yeoman)が表示されます。少し和みますね。

image.png

あとは質問に答えていきます。今回はTypeScriptで記述するのと、Gitレポジトリとして初期化することを選択しました。

? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? memo-starter
? What's the identifier of your extension? memo-starter
? What's the description of your extension?
? Initialize a git repository? Yes
? Bundle the source code with webpack? Yes
? Which package manager to use? npm

ここまで答えると、自動で必要なライブラリのインストール等が実施されます。最後に、自動でVSCodeを開くか聞いてくるので、 Open with 'Code' を選択しましょう。

Writing in C:\Users\920oj\Projects\Code\memo-starter...
   create memo-starter\.vscode\extensions.json
   create memo-starter\.vscode\launch.json
   create memo-starter\.vscode\settings.json
   create memo-starter\.vscode\tasks.json
   create memo-starter\package.json
   create memo-starter\tsconfig.json
   create memo-starter\.vscodeignore
   create memo-starter\webpack.config.js
   create memo-starter\vsc-extension-quickstart.md
   create memo-starter\.gitignore
   create memo-starter\README.md
   create memo-starter\CHANGELOG.md
   create memo-starter\src\extension.ts
   create memo-starter\src\test\runTest.ts
   create memo-starter\src\test\suite\extension.test.ts
   create memo-starter\src\test\suite\index.ts
   create memo-starter\.eslintrc.json

Changes to package.json were detected.

Running npm install for you to install the required dependencies.

added 303 packages, and audited 304 packages in 28s

58 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

Your extension memo-starter has been created!

To start editing with Visual Studio Code, use the following commands:

     code memo-starter

Open vsc-extension-quickstart.md inside the new extension for further instructions
on how to modify, test and publish your extension.

To run the extension you need to install the recommended extension 'amodio.tsl-problem-matcher'.

For more information, also visit http://code.visualstudio.com and follow us @code.


? Do you want to open the new folder with Visual Studio Code?
> Open with `code`
  Skip

このように、拡張機能を作成する前準備が整いました。

image.png

開発中の拡張機能の実行方法

この雛形には、拡張機能開発中にプレビューするためのデバッグ構成が含まれているため、すぐに拡張機能のデバッグを開始することができます。F5を押すと、新しいウィンドウが開くので、Ctrl + Shift + P を押下し、 Hello World を選択します。

image.png

右下に「Hello World from memo-starter!」と表示されていたら成功です。

image.png

ちなみに、これを実現しているコードは以下の通りです。package.jsonで指定しているコマンド memo-starter.helloWorld に対して、中身を定義していることがわかります。

package.json
  "contributes": {
    "commands": [
      {
        "command": "memo-starter.helloWorld",
        "title": "Hello World"
      }
    ]
  },
extensions.ts
	// The command has been defined in the package.json file
	// Now provide the implementation of the command with registerCommand
	// The commandId parameter must match the command field in package.json
	let disposable = vscode.commands.registerCommand('memo-starter.helloWorld', () => {
		// The code you place here will be executed every time your command is executed
		// Display a message box to the user
		vscode.window.showInformationMessage('Hello World from memo-starter!');
	});

	context.subscriptions.push(disposable);

コードを書いていく

今回実現したい要件を書き出します。ざっとこんな感じです。

  1. コマンドパレットを開き、「create memo file」を選択する
  2. メモのタイトルとなる文字列を入力する
  3. VSCodeで開いているディレクトリ上に、「YYYYMMDD_2で入力した文字列.md」ファイルが出来上がる
    • 中身の1行目に2で入力した文字列が見出し1として入力されている

package.json にコマンドを登録する

先述の通り、package.json

pakcage.json
  "contributes": {
    "commands": [
      {
        "command": "memo-starter.createMemoFile",
        "title": "Create Memo File"
      }
    ]
  },

extensions.ts を記入する

以下に activate() 関数を記載します。

export function activate(context: vscode.ExtensionContext) {
	// createMemoFileCommand メモファイル生成用
	const createMemoFileCommand = vscode.commands.registerCommand('memo-starter.createMemoFile', async () => {
		// VSCodeで開いているディレクトリを取得
		// 開いていない場合はエラーを出して終了する
		const folders = vscode.workspace.workspaceFolders;
		if (folders === undefined) {
			vscode.window.showErrorMessage('Error: Open the folder before executing this command.');
			return;
		}
		const folderPath = folders[0].uri;

		// ファイル名入力
		const inputName = await vscode.window.showInputBox();

		// ファイル名構築
		// フォーマットは YYYYMMDD_入力した文字列.md とする
		const date = new Date();
		const fileName = `${date.getFullYear()}${("00" + String(date.getMonth()+1)).slice(-2)}${("00" + String(date.getDate())).slice(-2)}_${inputName}.md`;

		// 書き込むファイルのフルパスを生成
		const fullUri = vscode.Uri.joinPath(folderPath, fileName);
		
		// 書き込むファイルの内容を準備
		// Uint8Arrayに変換する
		const content = `# ${inputName}`;
		const blob: Uint8Array = Buffer.from(content);

		// ファイル書き込み
		await vscode.workspace.fs.writeFile(fullUri, blob);
	});

	context.subscriptions.push(createMemoFileCommand);
}

いくつかフォーカスして説明します。

現在VSCodeで開いているディレクトリのパスを取得する

今回は、VSCodeで開いているディレクトリにファイルを新規作成します。今回初めて知ったのですが、VSCodeには「ワークスペース」という概念があり、「フォルダを開く」と、ワークスペースで1つのフォルダを読み込むという動作が取られているようです。ワークスペースは複数のディレクトリを読み込ませることができるそうですが、ここでは1ワークスペースに1ディレクトリという構成のみを考えることとします。

以下のように、 vscode.workspace.workspaceFolders を利用することで、ワークスペースで開いているディレクトリの情報を取得します。ワークスペースでディレクトリが開かれていれば配列を、開かれていない場合はundefinedになるため、開かれていない場合はエラーメッセージを表示します。

		// VSCodeで開いているディレクトリを取得
		// 開いていない場合はエラーを出して終了する
		const folders = vscode.workspace.workspaceFolders;
		if (folders === undefined) {
			vscode.window.showErrorMessage('Error: Open the folder before executing this command.');
			return;
		}
		const folderPath = folders[0].uri;

ワークスペースでディレクトリが開かれていない場合は、以下のようにエラーメッセージが表示されます。

image.png

ファイルを書き込む

ファイルを書き込むためには、Node.jsで提供されている fs ではなく、VSCodeのAPIとして用意されている vscode.workspace.fs を利用します。

まずは、書き込むファイルのフルパスを生成します。書き込む際に利用する vscode.workspace.fs.writeFile では、第一引数に vscode.Uri 型を指定する必要があります。そこで、先程取得したワークスペースで開かれているディレクトリのUri情報(vscode.Uri型)と、ファイル名(String型)を結合するために、vscode.Uri.joinPath() メソッドを利用します。

        const folderPath = editor[0].uri; // vscode.Uri型のオブジェクト
        
        /** (中略)**/

        const fileName = `${date.getFullYear()}${("00" + String(date.getMonth()+1)).slice(-2)}${("00" + String(date.getDate())).slice(-2)}_${inputName}.md`; // String型

        // 書き込むファイルのフルパスを生成
		const fullUri = vscode.Uri.joinPath(folderPath, fileName); // vscode.Uri型とString型を結合し、vscode.Uri型を返す

さらに、書き込むファイルの内容は Uint8Array (バイト列)にする必要があります。 Buffer.from() を利用して変換します。最後に、 vscode.workspace.fs.writeFile() を呼び出して書き込み完了です。

		// 書き込むファイルの内容を準備
		// Uint8Arrayに変換する
		const content = `# ${inputName}`;
		const blob: Uint8Array = Buffer.from(content);

		// ファイル書き込み
		await vscode.workspace.fs.writeFile(fullUri, blob);

まとめ

意外と簡単に作ることができました。APIも豊富に用意されているため、様々なことを拡張機能で実現できそうな感触でした。

今回は時間が無かったので簡単なものになってしまいましたが、今度はテンプレートファイルを登録する機能を追加したり、テストコードを書いたり(大事)に、挑戦してみたいと思います。

参考文献


明日は @kazupc さんの「Random Network Distillation 徹底解説」です。

20
3
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
20
3