VS Codeで文字列のちょっとした変換ができるんです!

〜実装まで覗くクイックツアー〜

VS Codeで文字列のちょっとした変換ができるんです!

〜実装まで覗くクイックツアー〜

Event:

VS Code Conference JP 2024

Presented:

2024/04/20 nikkie

お前、誰よ(自己紹介)

  • nikkie / 毎日 ブログ 執筆、連続520日突破

  • ソフトウェアエンジニアリングで突破するデータサイエンティスト(We're hiring!

  • 仕事もプライベートも VS CodePython と戯れています

../_images/uzabase-white-logo.png

1年ぶりですね

VS Code Conference Japan 2022 - 2023で15分2本登壇しました

このトークでは(fortee

  • VS Codeで 文字列のちょっとした変換 をする方法を紹介

  • どのように実装されているか、少しだけ覗いてみよう(実装の話

本題:文字列のちょっとした変換

英語の文字列(例えば、Emily Stewart)を すべて小文字 にしたい

手に馴染む言語(Python)でやってました

>>> "Emily Stewart".lower()
'emily stewart'

選択肢は色々ある

$ python -c 'print("Emily Stewart".lower())'
emily stewart
$ echo 'Emily Stewart' | tr '[:upper:]' '[:lower:]'
emily stewart

VS Codeでも、できます!!

  1. テキストを選択

  2. コマンドパレット

  3. Transform で始まるコマンドを選択

コマンドパレットとは

押してみて!🫵

  • Linux または Windowsの方: Ctrl + Shift + P

  • macOSの方: ⇧⌘P (= Command + Shift + P

デモ

VS Codeを操作して、Emily Stewartを小文字にする

  • Version: 1.88.1 (Universal)

  • OS: Darwin arm64 21.6.0

Transform to (1/2)

VS Codeで文字列操作

Lowercase

emily stewart

Uppercase

EMILY STEWART

Title Case

Emily Stewart

Transform to (2/2)

VS Codeで文字列操作

Snake Case

emily_stewart

Camel Case

emilyStewart

Pascal Case

EmilyStewart

Kebab Case

emily-stewart

例:Snake ➡️ Kebab

  • Transform to Kebab Case

  • sphinx_new_tab_link ➡️ sphinx-new-tab-link

自作ライブラリ の開発中にちょっと助かりそう

参考資料 🏃‍♂️

🏃‍♂️は本編ではスキップします。興味ある方向けです

コマンドパレットまわりで寄り道

  1. Quick Open

  2. コマンド

寄り道1️⃣ Quick Openとは

押してみて!🫵

  • Linux または Windowsの方: Ctrl + P

  • macOSの方: ⇧P (= Command + P

> があるかないか

  • Quick Openで > と打ち込むと、コマンドパレット

  • コマンドパレットで > を消すと、Quick Open

時間に余裕があったら 行き来 するデモ

寄り道2️⃣ すべてはコマンド

  • コマンドパレットで Transform to コマンドを検索・実行 した

  • VS Code拡張開発でもコマンドを実装する

昨年の拡張開発の発表資料より

拙ブログ 🏃‍♂️

🌯まとめ:VS Codeで文字列のちょっとした変換ができるんです!

  1. テキストを選択

  2. コマンドパレット

  3. Transform to コマンド

文字列のちょっとした変換にVS Codeを使ってみてください!

VS Codeで文字列のちょっとした変換ができるんです!

実装まで覗くクイックツアー

これらのコマンドはどう実装されている?

  • VS Codeの実装(TypeScript)を見ていくパート

  • コマンドごとにクラスがある

registerEditorAction() 関数

registerEditorAction(LowerCaseAction);

https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1302-L1320

※コマンドパレットで提供される仕組みは、ここっぽいくらいにとどめます

Transform to コマンドに対応するクラス

  • UpperCaseAction

  • LowerCaseAction

  • TitleCaseAction

  • SnakeCaseAction

  • CamelCaseAction

  • PascalCaseAction

  • KebabCaseAction

共通のスーパークラス AbstractCaseAction

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1088
export class LowerCaseAction extends AbstractCaseAction {
    // 省略
}
  • Caseに関する 抽象 Action

  • サブクラスで具体のActionを実装する(例:小文字にする)

AbstractCaseAction までの継承関係 🏃‍♂️

../_images/InheritanceToAbstractCaseAction.png

メモ

AbstractCaseAction はGoFの Template Method パターン

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1032
export abstract class AbstractCaseAction extends EditorAction {
        public run(_accessor: ServicesAccessor, editor: ICodeEditor): void {
            // 処理の中で _modifyText が呼ばれる
        }

        protected abstract _modifyText(text: string, wordSeparators: string): string;
}

穴埋め問題 になっている

_modifyText() の実装を見る

  • LowerCaseAction

  • TitleCaseAction

1️⃣ LowerCaseAction

  • JavaScriptのメソッド を使う実装( UpperCaseAction も同様)

  • (私の理解が雑すぎるかもですが、)TypeScriptは型付きのJavaScript

テストコードより

'HELLO WORLD' ➡️ 'hello world'

https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts#L677

このメソッドで実装: toLocaleLowerCase()

呼び出した文字列の値を、ロケールに依存した対応付けに基づいて小文字に変換して返します。

MDN String.prototype.toLocaleLowerCase()

LowerCaseAction_modifyText()

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1098-L1100
    protected _modifyText(text: string, wordSeparators: string): string {
            return text.toLocaleLowerCase();
    }

2️⃣ TitleCaseAction

  • 正規表現 を使う実装

  • SnakeCaseAction などなども同様

テストコードより

入力

出力

'HELLO WORLD'

'Hello World'

'foO\'baR\'BaZ'

'Foo\'bar\'baz'

'\'physician\'s assistant\''

'\'Physician\'s Assistant\''

https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/test/browser/linesOperations.test.ts#L790

正規表現は、これだ!!

new RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu')

https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1135

正規表現リテラルの作成は 2通り

  • スラッシュで囲む /ab+c/g

  • new RegExp("ab+c", "g") (👈今回はこちら)

MDN 正規表現の作成

RegExp コンストラクタの引数

  • 第1引数がパターン

    • \\p\pエスケープ

  • 第2引数がフラグ

    • グローバル検索(g)・複数行の検索(m)・unicode(u)

独自クラス BackwardsCompatibleRegExp 🏃‍♂️

  • 正確には、 RegExp を返す BackwardsCompatibleRegExp を定義した実装

  • RegExp インスタンス作成時に例外を送出したら、握りつぶして null を返す(=後方互換)

正規表現を使った _modifyText()

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1146-L1155
protected _modifyText(text: string, wordSeparators: string): string {
    // 省略
    return text
            .toLocaleLowerCase()
            .replace(titleBoundary, (b) => b.toLocaleUpperCase());
}

正規表現を使った replace()

  • 置換文字列として関数を指定。シグネチャは b のみ

  • b正規表現に一致した文字列

  • キャプチャグループの () を(幾重も)含むが、このreplaceでは無視

MDN 置換文字列としての関数の指定

正規表現 '(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}' 🤯

new RegExp('(^|[^\\p{L}\\p{N}\']|((^|\\P{L})\'))\\p{L}', 'gmu');
// /(^|[^\p{L}\p{N}']|((^|\P{L})'))\p{L}/gmu
  • titleBoundary と呼ばれる

  • (再掲) \ をエスケープするために \\

\p{L}

  • Unicode文字クラスエスケープ

  • 一般カテゴリプロパティが L etter の Unicode文字の集合

  • 文字にマッチ する。記号や空白文字にはマッチしない

3つの「または」( |

  • ^

  • [^\\p{L}\\p{N}\']

  • ((^|\\P{L})\')

これらのあとに \\p{L} が続く

(1) ^ 入力の先頭

  • mフラグで「改行文字の直後にも一致

  • '(^)\\p{L}'先頭の文字 に一致( 'hello world''h'

MDN 境界型のアサーション

(2) 文字クラス [^\\p{L}\\p{N}\']

  • 否定の ^

  • 「UnicodeでLetterまたはNumber、またはシングルクォート」 でない

  • ([^\\p{L}\\p{N}\'])\\p{L}'hello world'' w' にマッチ

MDN 文字クラス 種類

(3) ((^|\\P{L})\')

  • \P{L} は、 \p{L} の否定(すなわち、 文字でない

    • (^|\\P{L}) :先頭、または、文字でない

((^|\\P{L})\')\\p{L} がマッチするもの

  • '\'physician\'s assistant\'''\'p' にマッチ(先頭の方がマッチ)

  • 'hello \'world'' \'w' にもマッチ(文字でない方がマッチ)

この正規表現の名は titleBoundary

  • タイトルケースにする上での 境界

  • 'hello world' であれば、境界に一致する部分文字列は 'h'' w'

🌯要点:タイトルケースの境界部分を大文字にする

  1. テキスト全体を 小文字 にする

  2. タイトルケースの 境界部分大文字 にする

// https://github.com/microsoft/vscode/blob/1.88.1/src/vs/editor/contrib/linesOperations/browser/linesOperations.ts#L1152-L1154
return text
        .toLocaleLowerCase()
        .replace(titleBoundary, (b) => b.toLocaleUpperCase());

拙ブログより 🏃‍♂️

🌯まとめ:VS Codeで文字列のちょっとした変換ができるんです!

  • コマンドパレットTransform to コマンドで文字列を変換できる

  • コマンドごとにActionクラス。 _modifyText() を実装する Template Method パターン

  • Unicode文字クラスエスケープをはじめ、正規表現を駆使

ご清聴ありがとうございました

コマンドパレットで Enjoy coding!✨

EOF