はじめに
プロジェクト管理や作業記録で、こんな悩みありませんか?
作業メモ.txt
---
■ タスク
プロジェクトフォルダのファイル構成をドキュメント化
→ C:\work\my-project のファイル一覧を取得したい
■ 課題
共有フォルダの棚卸し
→ \\server\share\old-projects のファイル数とサイズを確認したい「フォルダ配下のファイル一覧が欲しい」
「サイズや更新日時も記録したい」
「再帰的に全サブフォルダも取得したい」
Windowsのエクスプローラーでは、ファイル一覧をテキストでコピーできません。コマンドプロンプトでdirコマンド…も面倒。
今回、カーソル行のフォルダパスから一覧を取得し、別ファイルに出力するTypeScriptマクロを実装しました。
シンプル版と詳細版の2モードで、実務でのファイル管理が劇的に効率化します。
なぜこのツールが必要なのか?
Windowsの標準機能では実現困難
エクスプローラーの限界:
- ファイル一覧をテキストでコピーできない
- ドラッグ&ドロップは画像のみ
- パスのリストは手動でコピペが必要
コマンドプロンプトの課題:
dir /s /b C:\work\my-project > list.txt- パス指定が面倒
- オプションを毎回調べる必要
- 出力フォーマットの調整が困難
PowerShellでも:
Get-ChildItem -Recurse | Out-File list.txt- コマンドが長い
- カスタマイズが複雑
- 毎回コマンドを打つのが手間
このマクロで実現できること
| 項目 | エクスプローラー | コマンドライン | このマクロ |
|---|---|---|---|
| ファイル一覧 | ❌ コピー不可 | ⚠️ コマンド必要 | ✅ 1キー |
| サイズ・日時 | ❌ 手動 | ⚠️ フォーマット調整 | ✅ 自動 |
| 再帰検索 | ❌ 不可 | ✅ 可能 | ✅ モード選択 |
| 安全装置 | - | ❌ なし | ✅ タイムアウト等 |
| 操作数 | 手動コピペ | コマンド入力 | 1キー操作 |
実装した機能
主な機能
ショートカット:
Ctrl+F11: シンプル版(パスのみ)Ctrl+Shift+F11: 詳細版(サイズ・日時付き)
✅ 2つの出力モード
- シンプル版:パスのみをリスト化
- 詳細版:ファイルサイズ+更新日時付き
✅ 4つの取得モード(ダイアログで選択)
- 直下のみ(フォルダ + ファイル)← 最も安全
- 直下のみ(ファイルのみ)
- 再帰的(フォルダ + ファイル)← 警告付き
- 再帰的(ファイルのみ)← 警告付き
✅ 充実の安全装置
- 再帰検索時の確認ダイアログ
- ネットワークドライブ検出&警告
- タイムアウト:60秒
- 最大件数:50,000件(超過時は切り詰め)
- プログレス表示+キャンセル可能
node_modules,.git,dist等を自動除外
✅ 柔軟なパス指定
- ファイルパス → 親フォルダを対象
- フォルダパス → そのフォルダを対象
- 絶対パス、相対パス、ネットワークパス対応
出力例
シンプル版(Ctrl+F11)
============================================================
フォルダ一覧取得結果
============================================================
実行日時: 2024-02-08 15:30:45
対象フォルダ: C:\work\my-project
モード: 直下のみ(フォルダ + ファイル)
取得件数: 25 件
============================================================
C:\work\my-project\src
C:\work\my-project\test
C:\work\my-project\package.json
C:\work\my-project\README.md
C:\work\my-project\tsconfig.json
...詳細版(Ctrl+Shift+F11)
============================================================
フォルダ一覧取得結果
============================================================
実行日時: 2024-02-08 15:30:45
対象フォルダ: C:\work\my-project
モード: 再帰的(ファイルのみ)
取得件数: 234 件
============================================================
[DIR ] C:\work\my-project\src
[FILE] C:\work\my-project\src\main.ts (12.5 KB) 2024-02-01
[FILE] C:\work\my-project\src\util.ts (8.2 KB) 2024-02-05
[DIR ] C:\work\my-project\test
[FILE] C:\work\my-project\test\test.ts (4.1 KB) 2024-02-03
[FILE] C:\work\my-project\package.json (1.2 KB) 2024-01-30
...実務での活用例
例1:プロジェクトのファイル構成をドキュメント化
シーン:READMEにファイル構成を記載
README.md作成タスク
---
プロジェクト: C:\work\api-server手順:
- カーソルをパスの行に置く
Ctrl+F11→ 直下のみ(全て)- 結果をREADMEにコピペ
成果:
## ファイル構成
src/
main.ts - エントリーポイント
routes/ - APIルート定義
models/ - データモデル
test/ - テストコード
package.json - 依存関係例2:共有フォルダの棚卸し
シーン:不要ファイルの洗い出し
棚卸しタスク
---
対象: \\server\share\old-projects手順:
Ctrl+Shift+F11→ 再帰的(ファイルのみ)- 警告確認 → 続行
- サイズと日時で古いファイルを特定
成果:
[FILE] \\server\share\old-projects\backup_2020.zip (500 MB) 2020-01-15
[FILE] \\server\share\old-projects\temp.dat (1.2 GB) 2019-08-22
→ 不要ファイルを削除して1.7GB削減例3:納品物のファイルリスト作成
シーン:納品前のファイルリスト作成
納品準備
---
成果物フォルダ: C:\delivery\client-system-v1.0手順:
Ctrl+F11→ 再帰的(全て)- ファイルリストを納品書に添付
成果:
【納品物一覧】
src/ - ソースコード(50ファイル)
docs/ - 設計書・マニュアル(12ファイル)
installer/ - インストーラー(3ファイル)
合計:65ファイル例4:バックアップ前の記録
シーン:バックアップ前のスナップショット
バックアップ計画
---
対象: C:\important-data手順:
Ctrl+Shift+F11→ 再帰的(詳細版)- ファイル一覧をバックアップと一緒に保存
成果:
- バックアップ時のファイル構成を記録
- 復旧時に何があったか確認可能
- サイズと日時で変更箇所を追跡
例5:プロジェクトの進捗報告
シーン:週次報告でのファイル数増加報告
先週: 150ファイル
今週: 234ファイル
→ 84ファイル増加(機能追加により)手順:
- 毎週同じフォルダで
Ctrl+F11実行 - ファイル数の推移を記録
- 報告書に添付
TypeScriptコード解説
メイン処理(listFolderContents.ts)
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
/**
* 出力モード
*/
enum OutputMode {
Simple, // シンプル(パスのみ)
Detailed // 詳細(サイズ・日時付き)
}
/**
* 一覧取得オプション
*/
interface ListOptions {
recursive: boolean; // 再帰的に取得するか
filesOnly: boolean; // ファイルのみか
}
/**
* フォルダ一覧取得(シンプル版)
*/
export async function listFolderContentsSimple() {
await listFolderContents(OutputMode.Simple);
}
/**
* フォルダ一覧取得(詳細版)
*/
export async function listFolderContentsDetailed() {
await listFolderContents(OutputMode.Detailed);
}
/**
* メイン処理
*/
async function listFolderContents(mode: OutputMode) {
const editor = vscode.window.activeTextEditor;
if (!editor) {
vscode.window.showWarningMessage('アクティブなエディタがありません');
return;
}
// カーソル行からパスを取得
const line = editor.document.lineAt(editor.selection.active.line);
let targetPath = line.text.trim();
// パス前処理
targetPath = preprocessPath(targetPath);
targetPath = resolveAbsolutePath(targetPath, editor.document.uri.fsPath);
// 存在チェック
if (!fs.existsSync(targetPath)) {
vscode.window.showErrorMessage(`パスが見つかりません: ${targetPath}`);
return;
}
// フォルダパスに変換(ファイルの場合は親フォルダ)
const folderPath = getFolderPath(targetPath);
// オプション選択
const options = await showOptionsDialog();
if (!options) return;
// 再帰的検索の場合は警告
if (options.recursive) {
const confirmed = await showWarningDialog(folderPath);
if (!confirmed) return;
}
// 実行
await executeWithProgress(folderPath, options, mode);
}オプション選択ダイアログ
/**
* オプション選択ダイアログ
*/
async function showOptionsDialog(): Promise<ListOptions | null> {
const items = [
{
label: '$(file-directory) 直下のみ(フォルダ + ファイル)',
description: '最も安全。1階層のみ取得',
recursive: false,
filesOnly: false
},
{
label: '$(file) 直下のみ(ファイルのみ)',
description: '最も安全。1階層のファイルのみ',
recursive: false,
filesOnly: true
},
{
label: '$(repo) 再帰的(フォルダ + ファイル)',
description: '⚠️ 時間がかかる可能性あり',
recursive: true,
filesOnly: false
},
{
label: '$(files) 再帰的(ファイルのみ)',
description: '⚠️ 時間がかかる可能性あり',
recursive: true,
filesOnly: true
}
];
const selected = await vscode.window.showQuickPick(items, {
placeHolder: '一覧取得のモードを選択してください',
ignoreFocusOut: true
});
if (!selected) return null;
return {
recursive: selected.recursive,
filesOnly: selected.filesOnly
};
}警告ダイアログ(再帰検索時)
/**
* 警告ダイアログ
*/
async function showWarningDialog(folderPath: string): Promise<boolean> {
let warningMessage = '⚠️ 再帰的検索を実行します。\n';
// ネットワークドライブ警告
if (isNetworkPath(folderPath)) {
warningMessage += '\n⚠️ ネットワークドライブが検出されました。\n' +
'処理に非常に時間がかかる可能性があります。\n';
}
warningMessage += '\n続行しますか?';
const answer = await vscode.window.showWarningMessage(
warningMessage,
{ modal: true },
'続行', 'キャンセル'
);
return answer === '続行';
}
/**
* ネットワークパスかどうか判定
*/
function isNetworkPath(p: string): boolean {
return p.startsWith('\\\\') || p.startsWith('//');
}プログレス表示とタイムアウト
/**
* プログレス表示付きで実行
*/
async function executeWithProgress(
folderPath: string,
options: ListOptions,
mode: OutputMode
) {
await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: "フォルダ一覧を取得中...",
cancellable: true
}, async (progress, token) => {
try {
let isCancelled = false;
// キャンセルトークン
token.onCancellationRequested(() => {
isCancelled = true;
});
// タイムアウトPromise(60秒)
const timeoutPromise = new Promise<never>((_, reject) => {
setTimeout(() => reject(new Error('タイムアウト(60秒)')), 60000);
});
// ファイル一覧取得Promise
const listPromise = listFiles(folderPath, options, mode, (count) => {
progress.report({
message: `${count}件取得済み...`,
increment: 1
});
return isCancelled;
});
// タイムアウトとレース
const result = await Promise.race([listPromise, timeoutPromise]);
if (isCancelled) {
vscode.window.showInformationMessage('処理をキャンセルしました');
return;
}
// 結果を出力
await createResultFile(folderPath, options, result.files, result.truncated, mode);
let message = `取得完了: ${result.files.length}件`;
if (result.truncated) {
message += '(上限50,000件に達したため切り詰め)';
}
vscode.window.showInformationMessage(message);
} catch (error: any) {
if (error.message.includes('タイムアウト')) {
vscode.window.showErrorMessage('処理がタイムアウトしました(60秒)');
} else {
vscode.window.showErrorMessage(`エラー: ${error.message}`);
}
}
});
}再帰的ファイル取得
/**
* 除外するフォルダ名
*/
const EXCLUDE_PATTERNS = [
'node_modules',
'.git',
'.vscode',
'target',
'dist',
'build',
'out',
'bin',
'.idea'
];
/**
* 最大件数
*/
const MAX_FILES = 50000;
/**
* 再帰的にファイル一覧を取得
*/
async function listFilesRecursive(
folderPath: string,
files: FileInfo[],
options: ListOptions,
mode: OutputMode,
onProgress: (count: number) => boolean
) {
// キャンセルチェック
if (onProgress(files.length)) return;
// 最大件数チェック
if (files.length >= MAX_FILES) return;
try {
const entries = fs.readdirSync(folderPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(folderPath, entry.name);
if (entry.isDirectory()) {
// 除外パターンチェック
if (EXCLUDE_PATTERNS.includes(entry.name)) {
continue;
}
if (!options.filesOnly) {
files.push(createFileInfo(fullPath, true, mode));
}
// 再帰
await listFilesRecursive(fullPath, files, options, mode, onProgress);
} else {
files.push(createFileInfo(fullPath, false, mode));
}
// 最大件数チェック
if (files.length >= MAX_FILES) return;
}
} catch (error: any) {
// アクセス権限エラーなどは無視
console.error(`Error accessing ${folderPath}:`, error.message);
}
}出力フォーマット生成
/**
* 出力内容を生成
*/
function generateOutput(
folderPath: string,
options: ListOptions,
files: FileInfo[],
truncated: boolean,
mode: OutputMode
): string {
const lines: string[] = [];
// ヘッダー
lines.push('='.repeat(60));
lines.push('フォルダ一覧取得結果');
lines.push('='.repeat(60));
lines.push(`実行日時: ${formatDateTime(new Date())}`);
lines.push(`対象フォルダ: ${folderPath}`);
lines.push(`モード: ${getModeText(options)}`);
lines.push(`取得件数: ${files.length.toLocaleString()} 件`);
if (truncated) {
lines.push('⚠️ 上限50,000件に達したため切り詰めました');
}
lines.push('='.repeat(60));
lines.push('');
// ファイル一覧
if (mode === OutputMode.Simple) {
// シンプル版:パスのみ
files.forEach(file => {
lines.push(file.path);
});
} else {
// 詳細版:サイズ・日時付き
files.forEach(file => {
const type = file.isDirectory ? '[DIR ]' : '[FILE]';
const sizeStr = file.size !== undefined ? formatSize(file.size) : '';
const dateStr = file.modifiedDate ? formatDate(file.modifiedDate) : '';
let line = `${type} ${file.path}`;
if (sizeStr) line += ` (${sizeStr})`;
if (dateStr) line += ` ${dateStr}`;
lines.push(line);
});
}
return lines.join('\n');
}
/**
* ファイルサイズをフォーマット
*/
function formatSize(bytes: number): string {
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
}使い方
1. 拡張機能のインストール
# GitHubからクローン
git clone https://github.com/xxxxx-sys/my-macros.git
cd my-macros
# 依存関係インストール
npm install --legacy-peer-deps
# パッケージ化
npm run compile
npx vsce package
# インストール
code --install-extension my-macros-0.0.6.vsix2. 基本的な使い方
作業メモ.txt
---
プロジェクト: C:\work\my-project
共有フォルダ: \\server\share\documentsシンプル版(パスのみ):
- パスの行にカーソルを置く
Ctrl+F11- モード選択(直下のみ or 再帰的)
- 結果が右側に表示
- 保存ダイアログで保存
詳細版(サイズ・日時付き):
- パスの行にカーソルを置く
Ctrl+Shift+F11- モード選択(直下のみ or 再帰的)
- 再帰の場合は警告確認
- 結果が右側に表示
- 保存ダイアログで保存
3. 実践例
シーン1: プロジェクトの構成確認
メモ.txt
---
C:\work\api-server手順:
- カーソルをパスに置く
Ctrl+F11→ 直下のみ(全て)- フォルダ構成を確認
シーン2: 大量ファイルの棚卸し
棚卸し.txt
---
\\server\share\old-projects手順:
Ctrl+Shift+F11→ 再帰的(ファイルのみ)- 警告確認 → 続行
- サイズと日時で古いファイルを特定
シーン3: 納品物リスト作成
納品準備.txt
---
C:\delivery\system-v1.0手順:
Ctrl+F11→ 再帰的(全て)- ファイルリストを納品書に添付
package.jsonの設定
{
"contributes": {
"commands": [
{
"command": "myMacros.listFolderContentsSimple",
"title": "List Folder Contents (Simple)",
"category": "My Macros"
},
{
"command": "myMacros.listFolderContentsDetailed",
"title": "List Folder Contents (Detailed)",
"category": "My Macros"
}
],
"keybindings": [
{
"command": "myMacros.listFolderContentsSimple",
"key": "ctrl+f11",
"when": "editorTextFocus"
},
{
"command": "myMacros.listFolderContentsDetailed",
"key": "ctrl+shift+f11",
"when": "editorTextFocus"
}
]
}
}メリット・デメリット
メリット
✅ 圧倒的な効率化
- 1キー操作でファイル一覧取得
- エクスプローラーでの手作業が不要
- コマンドを覚える必要なし
✅ 充実の安全装置
- タイムアウト(60秒)で暴走防止
- 最大50,000件で自動停止
- ネットワークドライブ警告
- キャンセル可能なプログレス表示
✅ 柔軟な出力モード
- シンプル版:パスのみ
- 詳細版:サイズ・日時付き
- 用途に応じて使い分け
✅ 自動除外機能
node_modules,.git等を自動スキップ- 不要なフォルダを除外して高速化
✅ 実行履歴の保存
- 実行日時を記録
- ファイル名に日時を自動付与
- 定期的な記録に最適
デメリット
❌ 巨大フォルダは時間がかかる
- 数万ファイルは数十秒待つ
- ネットワークドライブは特に遅い
- タイムアウト(60秒)で自動停止
❌ リアルタイム監視は非対応
- スナップショット取得のみ
- ファイル変更の自動検知なし
❌ アクセス権限エラー
- 権限のないフォルダはスキップ
- エラーは通知されない
トラブルシューティング
Q: パスが見つからない
A: パスの記述を確認
NG: プロジェクト: C:\work\project(余計な文字)
OK: C:\work\project
NG: "C:\work\project"(引用符付き)→ 自動除去されるが、余計な文字があると失敗
解決策: パスのみの行にするQ: 処理が途中で止まる
A: タイムアウトまたは最大件数に達した
タイムアウト: 60秒経過で自動停止
最大件数: 50,000件で自動切り詰め
解決策:
- 直下のみで試す
- 対象フォルダを絞る
- 除外パターンを活用Q: ネットワークドライブが遅い
A: 再帰検索を避ける
推奨: 直下のみで取得
非推奨: 再帰的(数分かかる可能性)
解決策:
- まず直下のみで確認
- サブフォルダを個別に処理Q: node_modulesが除外される
A: 除外パターンを変更
// 除外したくない場合はコードを修正
const EXCLUDE_PATTERNS = [
// 'node_modules', ← コメントアウト
'.git',
'.vscode'
];Q: ファイルが保存できない
A: パスに書き込み権限があるか確認
NG: C:\Program Files\...(権限不足)
OK: C:\Users\[ユーザー名]\Documents\...
解決策: 書き込み可能なフォルダに保存まとめ
VSCodeでカーソル行のフォルダパスから一覧を取得し、別ファイルに出力する機能を実装しました。
この機能が役立つ人:
- プロジェクトのファイル構成をドキュメント化したい
- 共有フォルダの棚卸しをしたい
- 納品物のファイルリストを作成したい
- バックアップ前の記録を残したい
- ファイル管理の効率を上げたい
特にエクスプローラーでは不可能なファイル一覧のテキスト化を1キーで実現できるため、実務での生産性向上に大きく貢献します。
プロジェクト管理、作業記録、納品準備、棚卸しなど、様々なシーンで活用できる実用的なツールです。
ぜひ試してみてください!
関連記事
サクラエディタからの移行経緯

TypeScriptマクロ開発環境の構築方法

正規表現マッチ抽出ツール

タグ: #VSCode #TypeScript #マクロ #ファイル管理 #生産性向上 #フォルダ一覧
コメント