VSCodeで「任意行までの一括選択・コピー」を実現するTypeScriptマクロ開発【サクラエディタ移行の集大成】

Development
スポンサーリンク
スポンサーリンク

はじめに:テキスト編集における「範囲選択の不便さ」

VSCodeで長いログファイルやドキュメントを編集していて、「この行から特定の文字列まで一気にコピーしたい」と思ったことはありませんか?

通常のVSCodeでは、以下のような手順が必要です:

  1. 開始位置でクリック
  2. スクロールして目的の行を探す
  3. Shiftを押しながら終了位置をクリック
  4. Ctrl+Cでコピー

数百行離れた箇所を選択する場合、この作業は非常に煩雑です。

私はサクラエディタから移行してきた際、この「任意行までの一括選択・コピー」機能がなくて困っていました。以前サクラエディタ用に作成したJavaScriptマクロ(任意文字を含む行まで一括範囲選択コピー)の機能をVSCodeでも実現したかったのです。

そこで、TypeScriptでVSCode拡張機能を自作し、キーボードショートカット一発で範囲選択・コピーできる機能を実装しました。

本記事では、実際に開発した機能とそのTypeScriptコードを紹介します。同じような悩みを持つ方の参考になれば幸いです。

スポンサーリンク

実装した機能

Ctrl+Alt+C: 現在行から指定位置までコピー

これが本マクロの核となる機能です。ダイアログが開き、以下の方法で終了位置を決定できます:

プリセット選択(↑↓キーで選択)

  • 空行: 次の空行の1つ前まで
  • EOF: ファイル末尾まで
  • : 「▲」を含む行の1つ前まで
  • ▲▲▲: 「▲▲▲」を含む行の1つ前まで
  • ```: コードブロック終端の1つ前まで
  • ---: 区切り線の1つ前まで

直接入力も可能

プリセットを選ばずに、そのまま文字列を入力してEnterでもOK。例えば「TODO」と入力すれば、TODOを含む行の1つ前までコピーされます。

スポンサーリンク

具体的な使用場面とメリット

場面1:ログファイルの特定区間を抽出

大量のログファイルから、STARTからENDまでのセクションだけを抽出したい場合:

2026-02-08 10:00:00 INFO: Application started
2026-02-08 10:00:01 DEBUG: Loading configuration
START
2026-02-08 10:00:05 ERROR: Connection timeout
2026-02-08 10:00:06 WARN: Retrying connection
... (数百行のログ)
END
2026-02-08 11:00:00 INFO: Process completed

操作手順:

  1. STARTの行にカーソルを置く
  2. Ctrl+Alt+Cを押す
  3. ダイアログにENDと直接入力してEnter
  4. → 一瞬でSTARTからENDの1行前までがコピーされる(ENDは含まない)

従来の方法だと、スクロールしながらShiftクリックで選択する必要があり、数百行離れていると非常に面倒でした。

場面2:Markdownドキュメントのセクション単位でコピー

Markdownファイルで特定のセクションだけをコピーしたい場合:

## はじめに

本記事では...

## インストール手順

以下の手順で...
... (長い説明)

## 設定方法

設定ファイルに...

見出しの行でCtrl+Alt+Cを押し、プリセットから「空行」を選択すれば、次の空行(セクションの区切り)までが自動コピーされます。

場面3:コード内の関数やクラスを抽出

JavaScriptやPythonのコードで、特定の関数だけを別ファイルにコピーしたい場合:

export async function processData() {
    const editor = vscode.window.activeTextEditor;
    // ... 処理内容 (50行)
}

関数の先頭行でCtrl+Alt+Cを押し、「空行」を選択すれば、次の空行(関数の終わり)まで一括コピーできます。

メリットまとめ

  • スクロール不要: 目的の文字列を入力するだけ
  • 正確: クリック位置のミスがない
  • 高速: キーボードだけで完結
  • 汎用性: 任意の文字列を指定可能
  • 柔軟性: プリセット選択と直接入力の両対応

実装コード

実際のTypeScriptコードを紹介します。copyToSpecifiedLine.tsファイルに以下の内容を実装しました。

import * as vscode from 'vscode';

/**
 * プリセット検索パターン
 */
const PRESET_PATTERNS = [
    { label: '空行', value: 'EMPTY_LINE', description: '次の空行まで' },
    { label: 'EOF', value: 'EOF', description: 'ファイル末尾まで' },
    { label: '▲', value: '▲', description: '▲が含まれる行の前まで' },
    { label: '▲▲▲', value: '▲▲▲', description: '▲▲▲が含まれる行の前まで' },
    { label: '```', value: '```', description: 'コードブロック終端の前まで' },
    { label: '---', value: '---', description: '区切り線の前まで' }
];

/**
 * 現在行から指定行までをコピー
 */
export async function copyToSpecifiedLine() {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('アクティブなエディタがありません');
        return;
    }

    const document = editor.document;
    const currentLine = editor.selection.active.line;
    
    // パターン選択ダイアログ
    const pattern = await showPatternDialog();
    if (pattern === null) {
        return; // キャンセル
    }
    
    // 終了行を検索
    const endLine = await findEndLine(document, currentLine, pattern);
    if (endLine === null) {
        return; // 見つからない/エラー
    }
    
    // 範囲選択(改行を含む)
    const startPos = new vscode.Position(currentLine, 0);
    // 改行を含めるため、次の行の先頭を終了位置にする(最終行の場合は行末)
    const endPos = endLine < document.lineCount - 1
        ? new vscode.Position(endLine + 1, 0)
        : new vscode.Position(endLine, document.lineAt(endLine).text.length);
    const range = new vscode.Range(startPos, endPos);
    
    // 選択
    editor.selection = new vscode.Selection(range.start, range.end);
    
    // コピー
    await vscode.commands.executeCommand('editor.action.clipboardCopyAction');
    
    // 選択状態を維持(移動しない)
}

/**
 * パターン選択ダイアログを表示(直接入力も可能)
 * @returns 選択されたパターン文字列、またはnull(キャンセル時)
 */
async function showPatternDialog(): Promise {
    const quickPick = vscode.window.createQuickPick();
    quickPick.items = PRESET_PATTERNS;
    quickPick.placeholder = '終了位置を選択または直接入力してください';
    quickPick.ignoreFocusOut = true;
    
    return new Promise((resolve) => {
        quickPick.onDidAccept(() => {
            const selected = quickPick.selectedItems[0];
            const inputValue = quickPick.value.trim();
            
            // 選択肢を選んだ場合
            if (selected) {
                const pattern = PRESET_PATTERNS.find(p => p.label === selected.label);
                resolve(pattern?.value || null);
                quickPick.hide();
            }
            // 直接入力した場合(選択肢を選ばずにEnter)
            else if (inputValue) {
                resolve(inputValue);
                quickPick.hide();
            } else {
                // 何も入力せずにEnter(キャンセル扱い)
                resolve(null);
                quickPick.hide();
            }
        });
        
        quickPick.onDidHide(() => {
            resolve(null);
            quickPick.dispose();
        });
        
        quickPick.show();
    });
}

/**
 * 終了行を検索
 * @param document ドキュメント
 * @param startLine 開始行(0-based)
 * @param pattern 検索パターン
 * @returns 終了行番号(0-based)、またはnull(見つからない/エラー)
 */
async function findEndLine(
    document: vscode.TextDocument,
    startLine: number,
    pattern: string
): Promise {
    const lastLine = document.lineCount - 1;
    
    // EOF の場合
    if (pattern === 'EOF') {
        return lastLine;
    }
    
    // 空行の場合
    if (pattern === 'EMPTY_LINE') {
        for (let i = startLine + 1; i <= lastLine; i++) {
            const line = document.lineAt(i);
            if (line.isEmptyOrWhitespace) {
                // 空行の1つ前の行を返す
                return i - 1;
            }
        }
        
        // 空行が見つからない場合は最終行
        vscode.window.showInformationMessage('空行が見つからないため、ファイル末尾まで選択します');
        return lastLine;
    }
    
    // 文字列検索(指定文字列がある行の1つ前まで)
    for (let i = startLine + 1; i <= lastLine; i++) {
        const lineText = document.lineAt(i).text;
        if (lineText.includes(pattern)) {
            // 指定文字列がある行の1つ前を返す
            if (i - 1 <= startLine) {
                vscode.window.showWarningMessage('指定文字列が次の行にあるため、選択できません');
                return null;
            }
            return i - 1;
        }
    }
    
    // 見つからない場合は最終行
    vscode.window.showInformationMessage(`「${pattern}」が見つからないため、ファイル末尾まで選択します`);
    return lastLine;
}

コードのポイント解説

1. QuickPickによる柔軟な入力

const quickPick = vscode.window.createQuickPick();
quickPick.items = PRESET_PATTERNS;
quickPick.placeholder = '終了位置を選択または直接入力してください';

createQuickPickを使うことで、プリセットから選択することも、直接文字列を入力することも可能になります。

2. プリセットと直接入力の両対応

quickPick.onDidAccept(() => {
    const selected = quickPick.selectedItems[0];
    const inputValue = quickPick.value.trim();
    
    if (selected) {
        // プリセット選択
        resolve(pattern?.value || null);
    } else if (inputValue) {
        // 直接入力
        resolve(inputValue);
    }
});

ユーザーの操作に応じて、適切な値を返します。

3. 空行検出のロジック

if (line.isEmptyOrWhitespace) {
    return i - 1;
}

isEmptyOrWhitespaceプロパティを使うことで、改行のみまたは空白のみの行を正確に判定できます。指定文字列自体は含まないため、1つ前の行を返します。

4. 改行を含む選択

const endPos = endLine < document.lineCount - 1
    ? new vscode.Position(endLine + 1, 0)  // 次の行の先頭(改行含む)
    : new vscode.Position(endLine, document.lineAt(endLine).text.length);

選択範囲に改行を含めることで、貼り付け時に自動的に改行が入ります。

セットアップ方法

この機能を使うには、VSCode拡張機能として開発する必要があります。詳細な手順は以下の関連記事を参照してください:

  • VSCodeのTypeScriptマクロ開発ガイド
VSCode TypeScriptマクロ開発環境の完全ガイド【セットアップから運用まで】
はじめにVSCodeでカスタムマクロを作成したいけど、どうやって開発環境を構築すればいいか分からない。そんな悩みを持つ方に向けて、TypeScriptでVSCode拡張機能を開発する環境の構築から、実際にマクロを作成して使えるようにするまで...
  • サクラエディタからVSCodeへのマクロ移行方法
サクラエディタからVSCodeへマクロ移行!快適開発環境の構築記録
はじめに長年愛用してきたサクラエディタのマクロ機能。便利なJavaScript/VBSマクロを多数作成して日常業務で活用してきましたが、最近のAWS開発やブログ執筆でVSCodeを使う機会が増えてきました。「VSCodeでもサクラエディタの...

簡易手順

  1. VSCode拡張機能プロジェクトを作成
  2. 上記TypeScriptコードをsrc/macros/copyToSpecifiedLine.tsに配置
  3. package.jsonにコマンドとキーバインディングを登録
{
  "contributes": {
    "commands": [
      {
        "command": "myMacros.copyToSpecifiedLine",
        "title": "Copy to Specified Line"
      }
    ],
    "keybindings": [
      {
        "command": "myMacros.copyToSpecifiedLine",
        "key": "ctrl+alt+c",
        "when": "editorTextFocus"
      }
    ]
  }
}
  1. ビルドしてVSIXファイルをインストール
npm run compile
vsce package
code --install-extension my-macros-0.0.10.vsix

サクラエディタからの移行という視点

かつてサクラエディタで実装していた機能(任意文字を含む行まで一括範囲選択コピー)を、VSCodeでも実現できました。

サクラエディタのJavaScriptマクロでは以下のようなコードでした:

// サクラエディタ版(抜粋)
var userInput = Editor.InputBox("終了位置を指定してください", "…");
for (var y = start; y <= last; y++) {
    var s = getLineBySelect(y);
    if (s.indexOf(marker) >= 0) return y;
}

VSCodeでは以下のように書き直せます:

// VSCode版
const quickPick = vscode.window.createQuickPick();
quickPick.items = PRESET_PATTERNS;
// プリセット選択 or 直接入力

for (let i = startLine + 1; i <= lastLine; i++) {
    const lineText = document.lineAt(i).text;
    if (lineText.includes(pattern)) {
        return i - 1;  // 指定行の1つ前
    }
}

TypeScriptの方が型安全で、VSCode APIも充実しているため、より堅牢な実装が可能です。

まとめ:キーボードだけで完結するテキスト編集

今回実装した機能により、以下が実現できました:

  • Ctrl+Alt+C: プリセット選択または任意の文字列まで選択・コピー

この機能は、以下のような作業で特に威力を発揮します:

  1. ログファイル解析: 特定のセクションだけを抽出
  2. ドキュメント編集: Markdownのセクション単位でコピー
  3. コードレビュー: 関数やクラス単位で抽出
  4. データ整形: 区切り文字で範囲指定して一括処理

「需要はないかもしれない」と思っていましたが、一度使うと手放せない便利機能です。特に大量のテキストを扱う作業では、作業効率が劇的に向上します。

同じような悩みを持つ方は、ぜひ試してみてください。TypeScriptでVSCodeマクロを書く楽しさも味わえるはずです。

参考リンク

  • サクラエディタ版の元記事: [任意文字を含む行まで一括範囲選択コピー]
【サクラエディタ】任意文字を含む行まで一括範囲選択コピー【操作画像あり】
サクラエディタで複数行の範囲選択テキストを一括で超簡単にコピーするマクロをJavaScriptで作ってみました。わざわざマウスを使ってある程度まとまった行を選択する操作、めんどくさくないですか?頻繁にコピー作業する人にはきっと役に立つと思い...

関連記事

  • サクラエディタからVSCodeへのマクロ移行完全ガイド
サクラエディタからVSCodeへマクロ移行!快適開発環境の構築記録
はじめに長年愛用してきたサクラエディタのマクロ機能。便利なJavaScript/VBSマクロを多数作成して日常業務で活用してきましたが、最近のAWS開発やブログ執筆でVSCodeを使う機会が増えてきました。「VSCodeでもサクラエディタの...
  • VSCodeでTypeScriptマクロ開発を始めるための完全ガイド
VSCode TypeScriptマクロ開発環境の完全ガイド【セットアップから運用まで】
はじめにVSCodeでカスタムマクロを作成したいけど、どうやって開発環境を構築すればいいか分からない。そんな悩みを持つ方に向けて、TypeScriptでVSCode拡張機能を開発する環境の構築から、実際にマクロを作成して使えるようにするまで...

コメント