はじめに:テキスト編集における「範囲選択の不便さ」
VSCodeで長いログファイルやドキュメントを編集していて、「この行から特定の文字列まで一気にコピーしたい」と思ったことはありませんか?
通常のVSCodeでは、以下のような手順が必要です:
- 開始位置でクリック
- スクロールして目的の行を探す
- Shiftを押しながら終了位置をクリック
- 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操作手順:
STARTの行にカーソルを置くCtrl+Alt+Cを押す- ダイアログに
ENDと直接入力してEnter - → 一瞬で
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へのマクロ移行方法

簡易手順
- VSCode拡張機能プロジェクトを作成
- 上記TypeScriptコードを
src/macros/copyToSpecifiedLine.tsに配置 package.jsonにコマンドとキーバインディングを登録
{
"contributes": {
"commands": [
{
"command": "myMacros.copyToSpecifiedLine",
"title": "Copy to Specified Line"
}
],
"keybindings": [
{
"command": "myMacros.copyToSpecifiedLine",
"key": "ctrl+alt+c",
"when": "editorTextFocus"
}
]
}
}- ビルドして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: プリセット選択または任意の文字列まで選択・コピー
この機能は、以下のような作業で特に威力を発揮します:
- ログファイル解析: 特定のセクションだけを抽出
- ドキュメント編集: Markdownのセクション単位でコピー
- コードレビュー: 関数やクラス単位で抽出
- データ整形: 区切り文字で範囲指定して一括処理
「需要はないかもしれない」と思っていましたが、一度使うと手放せない便利機能です。特に大量のテキストを扱う作業では、作業効率が劇的に向上します。
同じような悩みを持つ方は、ぜひ試してみてください。TypeScriptでVSCodeマクロを書く楽しさも味わえるはずです。
参考リンク
- サクラエディタ版の元記事: [任意文字を含む行まで一括範囲選択コピー]

関連記事
- サクラエディタからVSCodeへのマクロ移行完全ガイド

- VSCodeでTypeScriptマクロ開発を始めるための完全ガイド

コメント