はじめに:テキスト編集における「範囲選択の不便さ」
VSCodeで長いログファイルやドキュメントを編集していて、「この行から特定の文字列まで一気にコピーしたい」と思ったことはありませんか?
通常のVSCodeでは、以下のような手順が必要です:
- 開始位置でクリック
- スクロールして目的の行を探す
- Shiftを押しながら終了位置をクリック
- Ctrl+Cでコピー
数百行離れた箇所を選択する場合、この作業は非常に煩雑です。
私はサクラエディタから移行してきた際、この「任意行までの一括選択・コピー」機能がなくて困っていました。以前サクラエディタ用に作成したJavaScriptマクロ(任意文字を含む行まで一括範囲選択コピー)の機能をVSCodeでも実現したかったのです。
そこで、TypeScriptでVSCode拡張機能を自作し、キーボードショートカット一発で範囲選択・コピーできる機能を実装しました。
本記事では、実際に開発した4つの機能とそのTypeScriptコードを紹介します。同じような悩みを持つ方の参考になれば幸いです。
実装した4つの機能
今回実装したのは以下の4つの機能です:
1. Ctrl+Alt+C: 現在行から次の空行までコピー
現在のカーソル位置から次の空行(改行のみの行)までを一括でコピーします。ログファイルやMarkdownのセクション単位でのコピーに便利です。
2. Ctrl+Alt+S: 現在行から次の空行まで選択
コピーせずに選択だけを行います。選択後に別の操作(削除、移動など)を行いたい場合に使用します。
3. Ctrl+Shift+Alt+C: ダイアログで指定した行までコピー
これが最も強力な機能です。 ダイアログが開き、以下の指定方法で終了位置を決定できます:
- 文字列を入力: その文字列を含む最初の行まで
EOF: ファイル末尾まで- デフォルト値:
…(三点リーダー)
4. Ctrl+Shift+Alt+S: ダイアログで指定した行まで選択
上記3と同様ですが、コピーは行わず選択のみです。
具体的な使用場面とメリット
場面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+Shift+Alt+Cを押す- ダイアログに
ENDと入力してEnter - → 一瞬で
STARTから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コードを紹介します。copyToEmptyLine.tsファイルに以下の内容を実装しました。
import * as vscode from 'vscode';
/**
* 現在行から次の空行までを選択してコピー
*/
export async function copyToEmptyLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('アクティブなエディタがありません');
return;
}
const document = editor.document;
const currentLine = editor.selection.active.line;
// 次の空行を検索
let endLine = findNextEmptyLine(document, currentLine);
// 範囲選択
const startPos = new vscode.Position(currentLine, 0);
const endPos = 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');
// 次の行に移動
if (endLine + 1 < document.lineCount) {
const nextLinePos = new vscode.Position(endLine + 1, 0);
editor.selection = new vscode.Selection(nextLinePos, nextLinePos);
}
}
/**
* 現在行から次の空行までを選択(コピーなし)
*/
export async function selectToEmptyLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('アクティブなエディタがありません');
return;
}
const document = editor.document;
const currentLine = editor.selection.active.line;
// 次の空行を検索
let endLine = findNextEmptyLine(document, currentLine);
// 範囲選択
const startPos = new vscode.Position(currentLine, 0);
const endPos = 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);
}
/**
* 現在行から指定行までをコピー
*/
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 endLine = await promptForEndLine(document, currentLine);
if (endLine === null) {
return; // キャンセル
}
// 範囲選択
const startPos = new vscode.Position(currentLine, 0);
const endPos = 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');
// 次の行に移動
if (endLine + 1 < document.lineCount) {
const nextLinePos = new vscode.Position(endLine + 1, 0);
editor.selection = new vscode.Selection(nextLinePos, nextLinePos);
}
}
/**
* 現在行から指定行までを選択(コピーなし)
*/
export async function selectToSpecifiedLine() {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('アクティブなエディタがありません');
return;
}
const document = editor.document;
const currentLine = editor.selection.active.line;
// 対象行をダイアログで入力
const endLine = await promptForEndLine(document, currentLine);
if (endLine === null) {
return; // キャンセル
}
// 範囲選択
const startPos = new vscode.Position(currentLine, 0);
const endPos = 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);
}
/**
* 次の空行を検索
* @param document ドキュメント
* @param startLine 開始行
* @returns 空行の行番号(見つからない場合は最終行)
*/
function findNextEmptyLine(document: vscode.TextDocument, startLine: number): number {
for (let i = startLine + 1; i < document.lineCount; i++) {
const line = document.lineAt(i);
// 完全な空行(改行のみ)をチェック
if (line.isEmptyOrWhitespace) {
return i;
}
}
// 空行が見つからない場合は最終行
return document.lineCount - 1;
}
/**
* 終了行の入力プロンプト
* @param document ドキュメント
* @param startLine 開始行(0-based)
* @returns 終了行番号(0-based)、またはnull(キャンセル時)
*/
async function promptForEndLine(document: vscode.TextDocument, startLine: number): Promise {
const lastLine = document.lineCount - 1;
const input = await vscode.window.showInputBox({
prompt: '終了位置を指定してください',
placeHolder: '「…」で次の「…」まで / 「EOF」でファイル末尾まで / キャンセルで中止',
value: '…'
});
if (!input) {
return null; // キャンセル
}
// EOFの場合
if (input.toUpperCase() === 'EOF') {
return lastLine;
}
// 文字列検索
for (let i = startLine + 1; i <= lastLine; i++) {
const lineText = document.lineAt(i).text;
if (lineText.includes(input)) {
return i;
}
}
// 見つからない場合は最終行
vscode.window.showInformationMessage(`「${input}」が見つからないため、ファイル末尾まで選択します`);
return lastLine;
} コードのポイント解説
1. VSCode APIの活用
const editor = vscode.window.activeTextEditor;
const document = editor.document;VSCodeのエディタとドキュメントにアクセスすることで、現在開いているファイルの内容や選択範囲を操作できます。
2. 空行検出のロジック
if (line.isEmptyOrWhitespace) {
return i;
}isEmptyOrWhitespaceプロパティを使うことで、改行のみまたは空白のみの行を正確に判定できます。
3. ダイアログでの入力受付
const input = await vscode.window.showInputBox({
prompt: '終了位置を指定してください',
placeHolder: '「…」で次の「…」まで / 「EOF」でファイル末尾まで / キャンセルで中止',
value: '…'
});showInputBoxを使うことで、ユーザーに文字列を入力してもらえます。デフォルト値として…を設定しています。
4. 文字列検索
for (let i = startLine + 1; i <= lastLine; i++) {
const lineText = document.lineAt(i).text;
if (lineText.includes(input)) {
return i;
}
}シンプルなincludesメソッドで、指定文字列を含む行を検索します。正規表現ではなくリテラル検索なので、特殊文字のエスケープは不要です。
セットアップ方法
この機能を使うには、VSCode拡張機能として開発する必要があります。詳細な手順は以下の関連記事を参照してください:
- VSCodeのTypeScriptマクロ開発ガイド

- サクラエディタからVSCodeへのマクロ移行方法

簡易手順
- VSCode拡張機能プロジェクトを作成
- 上記TypeScriptコードを
src/macros/copyToEmptyLine.tsに配置 package.jsonにコマンドとキーバインディングを登録
{
"contributes": {
"commands": [
{
"command": "myMacros.copyToEmptyLine",
"title": "Copy to Empty Line"
},
{
"command": "myMacros.selectToEmptyLine",
"title": "Select to Empty Line"
},
{
"command": "myMacros.copyToSpecifiedLine",
"title": "Copy to Specified Line"
},
{
"command": "myMacros.selectToSpecifiedLine",
"title": "Select to Specified Line"
}
],
"keybindings": [
{
"command": "myMacros.copyToEmptyLine",
"key": "ctrl+alt+c",
"when": "editorTextFocus"
},
{
"command": "myMacros.selectToEmptyLine",
"key": "ctrl+alt+s",
"when": "editorTextFocus"
},
{
"command": "myMacros.copyToSpecifiedLine",
"key": "ctrl+shift+alt+c",
"when": "editorTextFocus"
},
{
"command": "myMacros.selectToSpecifiedLine",
"key": "ctrl+shift+alt+s",
"when": "editorTextFocus"
}
]
}
}- ビルドしてVSIXファイルをインストール
npm run compile
vsce package
code --install-extension my-macros-0.0.1.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 input = await vscode.window.showInputBox({
value: '…'
});
for (let i = startLine + 1; i <= lastLine; i++) {
const lineText = document.lineAt(i).text;
if (lineText.includes(input)) {
return i;
}
}TypeScriptの方が型安全で、VSCode APIも充実しているため、より堅牢な実装が可能です。
まとめ:キーボードだけで完結するテキスト編集
今回実装した4つの機能により、以下が実現できました:
- Ctrl+Alt+C / S: 空行まで自動選択・コピー
- Ctrl+Shift+Alt+C / S: 任意の文字列まで選択・コピー
これらの機能は、以下のような作業で特に威力を発揮します:
- ログファイル解析: 特定のセクションだけを抽出
- ドキュメント編集: Markdownのセクション単位でコピー
- コードレビュー: 関数やクラス単位で抽出
- データ整形: 区切り文字で範囲指定して一括処理
「需要はないかもしれない」と思っていましたが、一度使うと手放せない便利機能です。特に大量のテキストを扱う作業では、作業効率が劇的に向上します。
同じような悩みを持つ方は、ぜひ試してみてください。TypeScriptでVSCodeマクロを書く楽しさも味わえるはずです。
参考リンク
- サクラエディタ版の元記事: [任意文字を含む行まで一括範囲選択コピー]

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

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

コメント