はじめに
- ちょっとした VSCode の拡張機能のアイデアを思いついたので、VSCode 拡張機能に入門(?)してみました。
- 今回使いたい機能は、主に文字の装飾
setDecorations()
がメインだったので、調べてみたことをアウトプットしてみます。 - ついでに、今回作った拡張機能も紹介させてください。
VSCode 開発環境の構築
基本的にいろいろなサイトや記事があると思うので、詳細は折りたたんでおきます。自分用のメモです。
環境構築とプロジェクトフォルダ作成
当然、node.js のインストールが必要です。またバージョンが古い場合にはアップグレードが必要です。方法はいろいろあると思うので、各自の環境や好みに合わせればよいと思います。
私の場合は、以前 n
コマンドで node.js をインストールしていたみたいなので、必要であればそれでアップグレードをすることになります。
また、 VSCode も最新にしておいた方が良いだろうと思います。
次に、必要なツール・ライブラリをインストールします。
sudo npm install -g yo generator-code vsce
あるいは既にインストール済みでも、久しぶりならバージョンアップする必要があると思うので、やはり上記のコマンドを実行します。
最後に、適当なディレクトリにプロジェクトを作成します(実行するとプロジェクト用のディレクトリが新しくできるので、それを考慮して決定)。
yo code
いろいろ聞かれますがとりあえず New Extension (TypeScript)
を選択して、プロジェクト名(拡張機能名)を適当に決めて、後はお好みで進めて、しばらく待つと、カレントディレクトリにプロジェクト名で新しいディレクトリができます。
setDecorations() について
setDecorations()
は、vscode.TextEditor
のメソッドで、テキストエディタ上の文字を装飾する機能です。
例えば予約語の前景色を変えたり、インデントや末尾の空白の背景色を変えたりする拡張機能があると思いますが、そのようなことができます。
しかし、調べてみるともう少しいろいろなことができることが分かったので、自分のためにもメモを残しておきます。
参考情報
オフィシャルな API ドキュメントについては以下のページを参考にしてください。
API 以外にも、Extension Manifest (package.json
の記載方法)や、Contribution Points、その他の項目もあります。
また、VSCode の拡張機能には、いろいろな公式サンプルコードがあり、setDecorations()
に関しては以下のサンプルコードが非常に参考になると思います。
前景色(color)と背景色(backgroundColor)など
サンプルコードを参考に以下のような拡張機能を作ってみました。
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext) {
console.log("activate");
const regex = /\bTODO\b/g;
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
});
function updateDecorations(editor: vscode.TextEditor) {
console.log("updateDecorations");
const text = editor.document.getText();
const options: vscode.DecorationOptions[] = Array.from(
text.matchAll(regex),
match => ({
range: new vscode.Range(
editor.document.positionAt(match.index!),
editor.document.positionAt(match.index! + match[0].length))
}));
editor.setDecorations(decorationType, options);
}
const updateDecorationsIfPossible = (): void => {
const editor = vscode.window.activeTextEditor;
if (editor) {
updateDecorations(editor);
}
};
let timeout: NodeJS.Timer | undefined = undefined;
function triggerUpdateDecorations(throttle = false) {
if (timeout) {
clearTimeout(timeout);
timeout = undefined;
}
if (throttle) {
timeout = setTimeout(updateDecorationsIfPossible, 500);
} else {
updateDecorationsIfPossible();
}
}
triggerUpdateDecorations();
vscode.window.onDidChangeActiveTextEditor(_editor => {
console.log("onDidChangeActiveTextEditor");
triggerUpdateDecorations();
}, null, context.subscriptions);
vscode.workspace.onDidChangeTextDocument(event => {
console.log("onDidChangeTextDocument");
if (event.document === vscode.window.activeTextEditor?.document) {
triggerUpdateDecorations(true);
}
}, null, context.subscriptions);
}
イベントや細かい動作については割愛しますが、簡単に概要だけ説明すると……。
-
TextEditorDecorationType
は事前に(activate()
が呼び出された時に)作っておきます。 - ドキュメントが最初に表示された時や、書き換わった場合に、文字の装飾を変更します。
- ただし、ドキュメントが書き換わった場合には、連続してイベントが発生する可能性(文字を連続して入力するなど)があるため、ディレイタイム(ここでは 500ms)を設けておいて、それ以内に再度イベントが発生した場合にはディレイ中の処理をキャンセルして、再度処理を
setTimeout()
で登録します。ディレイタイムを小さくした方が色の切り替わりは早くなりますが、装飾更新の処理が何度も走るので処理コストが増えます。500ms はちょっとのんびり目かな? いくつかの拡張機能だと 100ms ぐらいがデフォルトになっているようでした。10ms だと小さすぎる感じ。
ソースのポイントは以下です。
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
});
color
が前景色、backgroundColor
が背景色を指定する箇所です。
また package.json
には以下の修正をします。
"activationEvents": [
"onLanguage:plaintext"
],
"main": "./dist/extension.js",
"contributes": {
},
今回はテスト用にプレーンテキストの場合に有効にしています。
実際にこの拡張機能を動かすと以下のようになります。
プレーンテキストのファイルに テスト TODO
という文字を入れたところです。
TODO
の部分の前景色・背景色が変わっているのがわかると思います。
CSS と同じような感じなので、理解しやすいと思います。
細かい説明は省略しますが、DecorationRenderOptions
の以下の項目も CSS と同様なので理解しやすいかと思います。詳細は VSCode の API 仕様や、CSS の仕様をご確認ください。
-
cursor
: マウスカーソルの種類 -
fontStyle
: フォントのスタイル。イタリックとか -
fontWeight
: フォントの線の太さ -
letterSpacing
: 文字の間隔 -
opacity
: 透明度 -
textDecoration
: 下線とか上線とか。波線などにもできる
ちなみに CSS での対応する名前はキャメルケースではなく、ケバブケース(例えば font-style
)になります。
範囲の自動拡張制御(rangeBehavior)
上記の拡張機能を実際に動かして、いろいろと試していると気づくかもしれませんが、色が変わっている TODO
の直前直後に文字を追加すると、一瞬追加した文字も色が変わって(TODO
と同じ色になって)いて、その後(ディレイタイム終了後)に色が消えていると思います。
これは、DecorationRenderOptions
に設定する項目 rangeBehavior
のデフォルト値が OpenOpen
になっているためです。
この値は、装飾している文字範囲の直前と直後に文字を追加された場合、範囲を拡張するか(Open)、拡張しないか(Closed)かを指定するもので、前半が直前、後半が直後の指定です。
個人的には、ほとんどのケースでは範囲を拡張する必要はないと考えるので、基本は ClosedClosed
を指定したほうが良いのではないかと思っています。
この rangeBehavior
は意外と重要な項目ではないかと思います。特に後述の before
や after
を指定している場合におかしな値を定義していると、かなり違和感を感じると思います。
前述の拡張機能を以下のように修正すると、直前直後に文字を追加しても範囲が拡張されないようになります。
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
});
ガーターアイコン(gutterIconPath)
ガーターアイコンを指定すると、文字装飾のある行の先頭(行番号などよりもさらに前)に、アイコンを表示することもできます。
例えば、以下のように修正してみます。
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
gutterIconPath: context.asAbsolutePath("images/todo.png"),
});
また、プロジェクトフォルダ内に images/todo.png
を追加して、拡張機能を動かしてみると以下のようになります。
指定した行の先頭に、アイコン(赤地に白い「!」みたいなもの)が表示されていると思います。
サイズは 16x16 が推奨されているようです。
サイズが違う場合等には、gutterIconSize
を指定して調整できるようです。詳細は API 仕様を確認してください。
範囲の前後にコンテンツの追加(before と after)
before
と after
を指定すると、指定した文字装飾の範囲の前後に、コンテンツを追加できます。
イメージとしては、CSS の ::before
疑似要素や ::after
疑似要素のような感じです。
追加できるのは文字列か、アイコンのどちらか一方になるようです。
例えば、前にアイコン、後ろにテキスト追加してみます。
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
before: {
contentIconPath: context.asAbsolutePath("images/todo.png"),
},
after: {
contentText: "[!]",
color: "#0000ff",
backgroundColor: "#000000",
},
});
before
や after
の中にも fontStyle
や fontWeight
等いくつかの項目が指定できます。詳細は API 仕様を参照ください。
文字の重ね合わせ表示
上記の before
で、contentText
と width
を上手く使うことで、文字の重ね合わせの表示が可能です。
何を言っているのかわかりにくいと思うので、実際の例を見てください。
例えば、全角スペースを入力すると、薄い線で四角が表示されるようにしてみます。
const regex = /\u3000/g;
const decorationType = vscode.window.createTextEditorDecorationType({
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
before: {
contentText: "\u2395",
width: "0",
color: "rgb(127,127,127)",
},
});
「おはよう」と「ございます」の間にある全角スペース(薄い四角)がわかると思います。
ちなみに、重ね合わせで表示している "⎕" (U+2395) は、Unicode 的には「APL Functional Symbol Quad」という文字で、APL 言語の関数用の記号だそうです。
本当は、JIS にも登録されている "□" (U+25A1)が使いたかったのですが、この文字は日本語などのフォントでは幅広にデザインされていますが、英語などのフォントでは幅が狭く(正方形なので小さく)デザインされています。
Unicode の規格の 東アジアの文字幅 (East Asian Width) で、この文字は A (Ambiguous; 曖昧) という扱いになっており、表示される文脈で幅広かそうでないかが変わってくる文字なので、設定によっては上手く表示できないかもしれないということで、上記の U+2395 を使ってみました。Unicode 3.0 で追加された文字なので、大抵の環境では既に表示可能だろうと思っています。
もし、APL 記号が上手く表示されない環境があるようでした、違う文字に書き換えてください。
ボーダー(border 等)
表示テキストに枠線を引くことができます。
CSS と同じように border
でまとめて指定することも、borderColor
, borderStyle
, borderWidth
でそれぞれ指定することもできます。
試しに TODO
に白い枠線をつけてみます。
const regex = /\bTODO\b/g;
const decorationType = vscode.window.createTextEditorDecorationType({
border: "1px solid #ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
});
ただ、いくつかよくわかっていない動作をしているんですよね。
- 同じボーダーを設定した範囲指定が隣り合っていると、それぞれの範囲に対してはボーダーが付かずに、それらの範囲全体に対してボーダーが表示されるみたい。
-
borderSpacing
も効いていないみたい?
詳しい人がいたら教えてほしい。
ちなみに、borderRadius
は、CSS 同様に角丸のボーダーになります。
また、border
とよく似たものに outline
があります。CSS の outline
と同じという説明になっています(ボーダーのさらに外側に、ボーダーのようなものを描画するはず)。
その他の機能
dark と light
dark
と light
という定義があります。
これは、各種定義を(主に色やアイコンだと思いますが)、ダークモードとライトモードで切り替えられるようにする定義です。
先ほどダークモードを前提に白い背景や枠線の定義にしていましたが、dark
と light
を使えばダークモードでは明るい色で表示して、ライトモードでは暗い色で表示する、といった定義ができます。
dark
や light
の中には color
や backgroundColor
、その他さまざまなものが指定可能です。詳細は API 仕様を参照してください。
isWholeLine
isWholeLine
を true
にすると、範囲指定した行の全体(行頭から、行末を超えて画面右端まで)を装飾するようです。
const regex = /\bTODO\b/g;
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
isWholeLine: true,
});
TODO
だけを範囲指定していますが、行全体が装飾されているのがわかると思います(変な矢印は改行の位置です)。
overviewRulerLane と overviewRulerColor
overviewRulerLane
を指定すると、スクロールバーあたりに、マーカーといったらいいのか、「色」が表示できます。
検索したときに、検索項目と一致した箇所に色がつくような感じですね。
const regex = /\bTODO\b/g;
const decorationType = vscode.window.createTextEditorDecorationType({
color: "#ee0000",
backgroundColor: "#ffffff",
rangeBehavior: vscode.DecorationRangeBehavior.ClosedClosed,
overviewRulerLane: vscode.OverviewRulerLane.Right,
overviewRulerColor: "#ffff00",
});
実際の表示イメージは以下のような感じですが、ちょっとわかりにくいですかね。
画像は、スクロールバー内(右側にうっすら線が見えますよね)の右の方に黄色が表示されているところです。
右側(Right
)以外にも、Left
, Center
, Full
があるようです。
ホバーメッセージ
文字装飾箇所に対して、マウスカーソルを合わせるとホバーメッセージを出すことができます。
これは DecorationRenderOptions
ではなくて、setDecorations()
するときに、指定した範囲と合わせて DecorationOptions
に登録します。
const options: vscode.DecorationOptions[] = Array.from(
text.matchAll(regex),
match => ({
range: new vscode.Range(
editor.document.positionAt(match.index!),
editor.document.positionAt(match.index! + match[0].length)),
hoverMessage: new vscode.MarkdownString("後で **絶対** やること"),
}));
editor.setDecorations(decorationType, options);
この文字列には、マークダウン風の装飾ができるようです。
(直接文字列を指定する方法も使えるのですが、MarkedString
のドキュメントを見ると deprecated のようです)
作った拡張機能について
というわけで、これらの機能を使った拡張機能を作ってみました。
markdown-table-rainbow
作った拡張機能は単純で、マークダウンを書いているときに、テーブルの各カラムに色付けたらわかりやすくないだろうか、という思いがきっかけです。いわゆるレインボー系の拡張機能ですね(indent-rainbow とか Rainbow CSV みたいな)。
実際に作った拡張機能は以下です。
ソースも GitHub にあるので(上記の拡張機能のページから「Repository」でたどれると思います)、もしソースが見たい場合にはどうぞ。
その時に思ったことは、別の記事に記載しました。
特にどこでも宣伝はしていないので、誰も使っていないかと思ったら、現在すでに 10 回以上のダウンロードがされているようです(内 1 回分は私自身ですが)。
visible-whitespace
上記の拡張機能を作って、さらに setDecorations()
をお勉強していたら、もう一つ拡張機能を作りたくなりました。
機能的には、空白文字を見えるようにするというもので、既に似たようなものはたくさんあったので、本当に自分のお勉強用ですが。
その時に思ったことは、別の記事に記載しました。
拡張機能の公開方法
拡張機能をマーケットプレイスで公開する方法も、各種記事があると思うので閉じておきます。主に将来の自分のためのメモです。
拡張機能の公開方法
大きく以下の手順を踏む必要があります。
- Azure DevOps のアカウントを作成し、アクセストークンを生成
- マーケットプレイスに publisher の登録
- マーケットプレイスに公開
https://code.visualstudio.com/api/working-with-extensions/publishing-extension#publishing-extensions
Azure DevOps のアカウントを作成
オフィシャルドキュメントを参考に。
- Azure DevOps に行って、サインイン(なんかフォワードかリダイレクトされそうな URL だけどこれが正式なのかな?)
- 組織がまだなければ作る。左側の "New organization" から(詳細は、組織の作成を参照)。作成済なら不要
- パーソナルアクセストークンを作る。右上の自分のアイコンの横の「人」のアイコン→「Personal Access Tokens」→「New Token」
- Name はわかりやすければなんでもよいが、今回のトークンは VSCode の拡張機能の公開用なので、例えば「vscode-extensions」とか。あるいは、複数マシンで別トークンを使うのであればマシン名を入れる、など。
- Scopes は「Custom defined」にして、「Show all scope」をクリックしてから、下の方にスクロースして「Marketplace」の「Manage」だけチェック
- Create ボタンで作成
- 生成されたパーソナルアクセストークンはこのタイミングでしか確認できないので、忘れずにコピー。もし忘れたりコピーに失敗したら、古いトークンは削除して再度新しいトークンを作成してメモしなおす。
- パーソナルアクセストークンは有効期限があるので、有効期限が切れた場合にも同様の手順で新しいトークンを生成する必要がある。
マーケットプレイスに publisher の登録
マーケットプレイスの管理画面で publisher を作成。
ここで作成した publisher 名はあとで使うのでメモ。私の場合にはいつものアカウント名にあわせて yoshi389111
にした。
マーケットプレイスに公開
まずは、vsce
コマンドでログインする(グローバルでなくローカルにインストールしていれば npx vsce ~
で)。
たぶん、この操作はトークンの有効期限が切れた場合や、別のマシンで操作するときにも必要になるはず。
vsce login <publisher 名>
パーソナルアクセストークンを聞かれるので、先ほどメモしたトークンを入力する。
package.json
にも publisher
を登録する。
他にも、icon
とか、repository
とか license
とかも入れておいた方がよいかも。
必要なら keywords
もかな。
{
// ...
"icon": "assets/icon.png",
"publisher": "yoshi389111",
"repository": {
"type": "git",
"url": "https://github.com/yoshi389111/XXXXXXXXXXXXXXXXX"
},
"license": "MIT",
// ...
}
詳細は、Extension Manifest を参照のこと。
https://code.visualstudio.com/api/references/extension-manifest
他にも README.md
や CHANGELOG.md
を修正する。バージョンアップ時には package.json
のバージョン番号も修正が必要。
package.json
と package-lock.json
は以下のような感じでも更新できる(CHANGELOG.md
と合わせてコミットしたいので、わざとコミットはしないようにしている。あとで手動でコミットが必要)。
npm version 0.0.2 --no-git-tag-version
バージョンを直接指定しないで、パッチバージョンを自動カウントアップさせたり
npm version patch --no-git-tag-version
マイナーバージョンを自動カウントアップさせたり
npm version minor --no-git-tag-version
したほうがミスが無くて良いかも。
マーケットプレイスに公開する際には、プロジェクトルート(package.json
のあるところ)で、以下のコマンドを実行(ログインしている必要があるはず)。
バージョンアップ時も同じコマンドで OK。
vsce publish
メッセージがいろいろ出るけど、その中に拡張機能の公開 URL (Extension URL
と書いてあるもの)と、各種情報が見える管理ページ(Hub URL:
と書いてあるもの)が出力される(この管理ページは、前述のマーケットプレイスの管理画面からも行ける)。
実際に公開されるまでには数分かかるみたい。
その前に公開用 URL 等にアクセスしても 404 Not Found
になるので、のんびり待ってください。