VSCodeでカーソル下のパスを開くマクロ【Office読み取り専用・関連付けアプリ・連続起動対応】

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

はじめに

VSCodeでコードやドキュメントを書いている時、テキスト内にパスが書かれていてそのファイルを開きたいこと、ありませんか?

作業メモ.txt
---
設計書: C:\work\documents\設計書_v1.0.docx
手順書: C:\work\manual\操作手順.xlsx
参考資料: \\server\share\資料\参考.pptx

こういった場面で、カーソル下のパスを一発で開けたら便利ですよね。

さらに、「参照するだけで編集はしたくない」というシーンも多いはず。特にOffice系ファイルは誤って編集して上書き保存してしまうリスクがあります。

また、複数ファイルを連続して開く作業では、いちいちフォーカスが奪われてストレスになることも。

今回、VSCodeのTypeScriptマクロでカーソル下のパスからファイル/フォルダを開く機能を実装しました。特に以下の点にこだわっています。

  • Office系ファイルを読み取り専用で開く
  • テキストファイルを拡張子関連付けアプリ(サクラエディタ等)で開く
  • 外部アプリ起動後もVSCodeのフォーカスを維持
  • 実行後に元のエディタへ自動復帰・次の行へ移動(連続起動対応)

本記事ではその実装方法と、なぜこの機能が実務で役立つのかを紹介します。

スポンサーリンク

なぜOffice系ファイルを読み取り専用で開く必要があるのか?

Windowsでは意外と面倒

実は、Windowsの標準機能ではOffice系ファイルを簡単に読み取り専用で開けません

標準的な方法:

  1. Excel/Word/PowerPointを起動
  2. ファイル → 開く
  3. ファイルを選択
  4. 「開く」ボタンの▼をクリック
  5. "読み取り専用で開く" を選択

面倒すぎる…!

読み取り専用で開きたいシーン(多い!)

実務でこんな経験ありませんか?

✅ 参照だけしたいファイル

  • マニュアル、手順書、テンプレート
  • 誤編集を絶対に避けたい重要ファイル

✅ 他人のファイルを確認

  • レビュー時に誤って保存してしまうのを防ぐ
  • 共有フォルダのファイルを安全に閲覧

✅ 古いバージョンを参照

  • バックアップファイルを見るだけ
  • 過去の仕様書を確認

✅ 複数のファイルを比較

  • 片方は読み取り専用で開いて参照
  • 編集版と元版を並べて確認

今回のマクロの価値

方法手順数手間
Office内から開く5手順以上★★★★★
ファイルプロパティ変更3手順+戻す作業★★★★☆
今回のマクロ1キー操作★☆☆☆☆
スポンサーリンク

実装した機能

1. パスオープン機能(通常モード)

ショートカット: Ctrl+F12

  • カーソル下のパスを通常モードで開く(編集可能)
  • フォルダ → Explorerで開く
  • テキストファイル → VSCodeで開く
  • Office系ファイル → COMで通常モード起動(フォーカス維持)

2. パスオープン機能(ReadOnly / 関連付けアプリ)

ショートカット: Ctrl+Shift+F12

  • テキスト系ファイル → 拡張子関連付けアプリで開く(サクラエディタ等)
  • Office系ファイル → 読み取り専用で開く(Excel/Word/PowerPoint)

3. 親フォルダを開く

ショートカット: Ctrl+Alt+F12

  • カーソル下のパスの親フォルダをExplorerで開く
  • ファイルの格納場所をすぐに確認したい時に便利

4. 新規Officeファイル作成

ショートカット: Ctrl+Shift+Alt+F12

  • カーソル下のパスにOffice系ファイルを新規作成して開く
  • Excel/Word/PowerPointの各フォーマットに対応
  • 親フォルダが存在しない場合は自動作成

対応ファイル形式

Office系(読み取り専用対応):

  • Excel: .xls, .xlsx, .xlsm, .xlsb
  • Word: .doc, .docx, .docm
  • PowerPoint: .ppt, .pptx, .pptm

テキスト系(Ctrl+F12はVSCodeで開く / Ctrl+Shift+F12は関連付けアプリで開く):

  • .txt, .log, .sql, .js, .ts, .java, .sh
  • .css, .html, .htm, .md, .json, .xml, .yml, .yaml, .csv
  • .c, .cpp, .h, .py, .rb, .go, .rs, .php, .jsx, .tsx
  • .cbl, .pco(COBOLソース)

その他の拡張子:

  • 「テキストで開く / そのまま開く / キャンセル」の警告ダイアログを表示

フォルダ:

  • Explorerで開く

パス記述の柔軟性

✅ 絶対パス
C:\work\documents\設計書.docx

✅ 相対パス(現在ファイルのディレクトリまたはワークスペース基準)
documents\設計書.docx
./documents/設計書.docx

✅ ネットワークパス
\\server\share\資料\手順書.xlsx

✅ 先頭の@を除去
@C:\work\test.txt

✅ ダブルクォーテーションを除去
"C:\work\test.txt"

✅ タブ区切り(先頭のみ対象)
C:\work\test.txt	更新日: 2025/02/06

✅ 選択テキスト優先
テキストを選択した状態で実行すると、選択内容をパスとして使用

連続起動対応

  • 実行後、元のエディタへフォーカスが戻り、1行下の行頭にカーソルが移動
  • 外部アプリ(Excel等)はバックグラウンドで開くため、続けて次のパスを起動可能
タスク一覧.txt
---
C:\work\設計書.docx   ← Ctrl+Shift+F12 で開く → 自動で次の行へ
C:\work\手順書.xlsx   ← そのままCtrl+Shift+F12 で続けて開ける
C:\work\参考.pptx     ← さらに連続して開ける

技術解説:PowerShell + COMでバージョン非依存を実現

なぜPowerShell + COMなのか?

問題点:Office実行ファイルのパスがバージョンで異なる

# Office 2016
C:\Program Files\Microsoft Office\Office16\EXCEL.EXE

# Office 2019
C:\Program Files\Microsoft Office\Office19\EXCEL.EXE

# Microsoft 365
C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE

解決策:COMオブジェクト経由でバージョン非依存に

# Officeのバージョンに関係なく動作
$excel = New-Object -ComObject Excel.Application
$excel.Workbooks.Open('C:\path\to\file.xlsx', 0, $true)
                                                   # ↑ ReadOnly = $true

GetActiveObjectで既存インスタンスを再利用

New-Object単独だと、Excelが既に起動中でも新しいインスタンスを作ってしまい、PERSONAL.XLSBのロック競合やセキュリティ通知が発生します。

GetActiveObjectで既存インスタンスを優先取得することで、この問題を回避します。

# 既存インスタンスを優先取得し、なければ新規作成
try {
    $excel = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application')
} catch {
    $excel = New-Object -ComObject Excel.Application
}
$excel.Visible = $true
$excel.Workbooks.Open('C:\path\to\file.xlsx', 0, $true)

フォーカスを奪わない起動方法

外部アプリを起動する際、通常の方法ではVSCodeのフォーカスが奪われます。これを回避するため、用途に応じて異なる方法を使います。

テキスト関連付けアプリ(サクラエディタ等): ShellExecuteのSW_SHOWNOACTIVATE

# SW_SHOWNOACTIVATE(4): ウィンドウを表示するがVSCodeのフォーカスを奪わない
(New-Object -ComObject Shell.Application).ShellExecute('C:\path\to\file.txt', '', '', 'open', 4)

Office系ファイル: COMで開く

COMで開く場合、ShellExecuteと異なりフォーカス権が付与されないため、VSCodeのフォーカスが維持されます。

COM APIの読み取り専用パラメータ

Excel:

$excel.Workbooks.Open(FileName, UpdateLinks, ReadOnly)
# 第3引数: ReadOnly = $true

Word:

$word.Documents.Open(FileName, ConfirmConversions, ReadOnly, AddToRecentFiles)
# 第3引数: ReadOnly = -1 (True)
# ※Wordは$trueではなく-1を指定

PowerPoint:

$ppt.Presentations.Open(FileName, ReadOnly, Untitled, WithWindow)
# 第2引数: ReadOnly = 1 (msoTrue)
# ※PowerPointはMsoTriState型で1を指定

PowerShellのパスエスケープ

シングルクォーテーション文字列内ではバックスラッシュはエスケープ不要です。シングルクォーテーションのみ''にエスケープします。

// バックスラッシュは不要、シングルクォートのみエスケープ
const escapedPath = filePath.replace(/'/g, "''");

TypeScriptコード解説

メイン処理(openPath.ts)

import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { exec } from 'child_process';

/**
 * カーソル下のパスを開く(通常モード)
 */
export async function openPath() {
    await openFileOrFolder(false);
}

/**
 * カーソル下のパスを開く(ReadOnly / 関連付けアプリ)
 */
export async function openPathReadOnly() {
    await openFileOrFolder(true);
}

/**
 * ファイルまたはフォルダを開く
 */
async function openFileOrFolder(readOnly: boolean) {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('アクティブなエディタがありません');
        return;
    }

    // 元のドキュメントとカーソル行を保存(処理後のカーソル移動用)
    const originalDocument = editor.document;
    const originalLine = editor.selection.active.line;

    // パスの取得: 選択テキストがあれば優先、なければ現在行から取得
    let text: string;
    const selection = editor.selection;
    if (!selection.isEmpty) {
        const selectedText = editor.document.getText(selection).trim();
        if (selectedText) {
            text = preprocessPath(selectedText);
        } else {
            const line = editor.document.lineAt(selection.active.line);
            text = preprocessPath(line.text.trim());
        }
    } else {
        const line = editor.document.lineAt(selection.active.line);
        text = preprocessPath(line.text.trim());
    }

    // 絶対パス変換
    text = resolveAbsolutePath(text, editor.document.uri.fsPath);

    // 存在チェックとオープン
    if (!fs.existsSync(text)) {
        vscode.window.showErrorMessage(`パスが見つかりません: ${text}`);
        return;
    }

    const stat = fs.statSync(text);
    let opened = false;

    if (stat.isDirectory()) {
        // フォルダをExplorerで開く
        openInExplorer(text);
        opened = true;
    } else {
        // ファイルを開く
        opened = await openFile(text, readOnly);
    }

    // 処理後: 元のエディタをアクティブにして1行下に移動
    if (opened) {
        await moveCursorToNextLine(originalDocument, originalLine);
    }
}

パス前処理

/**
 * パスの前処理(@、引用符、タブ対応)
 */
function preprocessPath(text: string): string {
    // 先頭の@を除去
    text = text.replace(/^@/, '');

    // 先頭と末尾のダブルクォーテーション除去
    text = text.replace(/^"|"$/g, '');

    // タブ区切りの場合、先頭のみ対象
    if (text.includes('\t')) {
        text = text.split('\t')[0];
    }

    // 先頭の空白・全角スペースを除去
    text = text.replace(/^[\s\u3000]+/, '');

    return text;
}

ファイルオープン処理

/**
 * ファイルを開く
 * @returns 開く操作が実行されたかどうか(キャンセル時はfalse)
 */
async function openFile(filePath: string, readOnly: boolean): Promise<boolean> {
    const ext = path.extname(filePath).toLowerCase();

    // テキスト系ファイルの拡張子定義
    const textExtensions = [
        '.txt', '.log', '.sql', '.js', '.ts', '.java', '.sh',
        '.css', '.html', '.htm', '.md', '.json', '.xml',
        '.yml', '.yaml', '.csv', '.c', '.cpp', '.h', '.py',
        '.rb', '.go', '.rs', '.php', '.jsx', '.tsx',
        '.cbl', '.pco'  // COBOLソース
    ];

    if (textExtensions.includes(ext)) {
        if (readOnly) {
            // Ctrl+Shift+F12: 拡張子関連付けアプリで開く(サクラエディタ等)
            openWithAssociatedApp(filePath);
        } else {
            // Ctrl+F12: VSCodeで開く
            try {
                const document = await vscode.workspace.openTextDocument(filePath);
                await vscode.window.showTextDocument(document, {
                    preview: false,
                    viewColumn: vscode.ViewColumn.Active
                });
            } catch (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error}`);
            }
        }
        return true;
    } else if (isOfficeFile(ext)) {
        // Office系ファイル: ReadOnly対応
        openWithDefaultApp(filePath, ext, readOnly);
        return true;
    } else {
        // テキスト定義外の拡張子: 警告ダイアログ
        const answer = await vscode.window.showWarningMessage(
            `「${ext || '(拡張子なし)'}」はテキストとして定義されていない拡張子です。どのように開きますか?`,
            'テキストで開く',
            'そのまま開く',
            'キャンセル'
        );
        if (answer === 'テキストで開く') {
            try {
                const document = await vscode.workspace.openTextDocument(filePath);
                await vscode.window.showTextDocument(document, {
                    preview: false,
                    viewColumn: vscode.ViewColumn.Active
                });
            } catch (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error}`);
            }
            return true;
        } else if (answer === 'そのまま開く') {
            openWithAssociatedApp(filePath);
            return true;
        }
        return false;  // キャンセルまたは通知を閉じた
    }
}

拡張子関連付けアプリで開く(フォーカス維持)

/**
 * OSの拡張子関連付けアプリでファイルを開く(VSCodeのフォーカスを維持)
 */
function openWithAssociatedApp(filePath: string) {
    const platform = process.platform;

    if (platform === 'win32') {
        // SW_SHOWNOACTIVATE(4): ウィンドウを表示するがVSCodeのフォーカスを奪わない
        const escapedPath = filePath.replace(/'/g, "''");
        exec(
            `powershell -NoProfile -ExecutionPolicy Bypass -Command "(New-Object -ComObject Shell.Application).ShellExecute('${escapedPath}', '', '', 'open', 4)"`,
            (error) => {
                if (error) {
                    vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
                }
            }
        );
    } else if (platform === 'darwin') {
        exec(`open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    } else {
        exec(`xdg-open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    }
}

Office系ファイルの読み取り専用オープン

/**
 * Office系ファイルかどうか判定
 */
function isOfficeFile(ext: string): boolean {
    const officeExtensions = [
        '.xls', '.xlsx', '.xlsm', '.xlsb',  // Excel
        '.doc', '.docx', '.docm',            // Word
        '.ppt', '.pptx', '.pptm'             // PowerPoint
    ];
    return officeExtensions.includes(ext);
}

/**
 * デフォルトアプリでファイルを開く(Office系COM対応)
 */
function openWithDefaultApp(filePath: string, ext: string, readOnly: boolean) {
    const platform = process.platform;

    if (platform === 'win32') {
        // Windows: Office系ファイルはCOMで開く(ShellExecuteと異なりフォーカス権が付与されない)
        if (readOnly) {
            openOfficeFileReadOnly(filePath, ext);
        } else {
            openOfficeFileNormal(filePath, ext);
        }
    } else if (platform === 'darwin') {
        exec(`open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    } else {
        exec(`xdg-open "${filePath}"`, (error) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
            }
        });
    }
}

/**
 * Office系ファイルを通常モードで開く(PowerShell + COM)
 * GetActiveObjectで既存インスタンスに接続することでPERSONAL.XLSBのロック競合とセキュリティ通知を回避する
 */
function openOfficeFileNormal(filePath: string, ext: string) {
    const escapedPath = filePath.replace(/'/g, "''");
    let psScript = '';

    if (['.xls', '.xlsx', '.xlsm', '.xlsb'].includes(ext)) {
        psScript = `try { $excel = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application') } catch { $excel = New-Object -ComObject Excel.Application }; $excel.Visible = $true; $excel.Workbooks.Open('${escapedPath}')`;
    } else if (['.doc', '.docx', '.docm'].includes(ext)) {
        psScript = `try { $word = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application') } catch { $word = New-Object -ComObject Word.Application }; $word.Visible = $true; $word.Documents.Open('${escapedPath}')`;
    } else if (['.ppt', '.pptx', '.pptm'].includes(ext)) {
        psScript = `try { $ppt = [System.Runtime.InteropServices.Marshal]::GetActiveObject('PowerPoint.Application') } catch { $ppt = New-Object -ComObject PowerPoint.Application }; $ppt.Visible = 1; $ppt.Presentations.Open('${escapedPath}')`;
    }

    if (psScript) {
        const command = `powershell -NoProfile -ExecutionPolicy Bypass -Command "${psScript}"`;
        exec(command, (error, _stdout, stderr) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
                console.error('Error:', error);
                console.error('stderr:', stderr);
            }
        });
    }
}

/**
 * Office系ファイルを読み取り専用で開く(PowerShell + COM)
 */
function openOfficeFileReadOnly(filePath: string, ext: string) {
    // パスのエスケープ処理(PowerShell用)
    // シングルクォーテーション文字列内ではバックスラッシュはエスケープ不要
    const escapedPath = filePath.replace(/'/g, "''");

    let psScript = '';

    if (['.xls', '.xlsx', '.xlsm', '.xlsb'].includes(ext)) {
        // Excel: Workbooks.Open(FileName, UpdateLinks, ReadOnly)
        psScript = `try { $excel = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Excel.Application') } catch { $excel = New-Object -ComObject Excel.Application }; $excel.Visible = $true; $excel.Workbooks.Open('${escapedPath}', 0, $true)`;
    } else if (['.doc', '.docx', '.docm'].includes(ext)) {
        // Word: Documents.Open(FileName, ConfirmConversions, ReadOnly, AddToRecentFiles)
        // $true/$falseではなく数値で指定(0=false, -1=true)
        psScript = `try { $word = [System.Runtime.InteropServices.Marshal]::GetActiveObject('Word.Application') } catch { $word = New-Object -ComObject Word.Application }; $word.Visible = $true; $word.Documents.Open('${escapedPath}', 0, -1, 0)`;
    } else if (['.ppt', '.pptx', '.pptm'].includes(ext)) {
        // PowerPoint: Presentations.Open(FileName, ReadOnly, Untitled, WithWindow)
        // MsoTriState: msoTrue=1, msoFalse=0
        psScript = `try { $ppt = [System.Runtime.InteropServices.Marshal]::GetActiveObject('PowerPoint.Application') } catch { $ppt = New-Object -ComObject PowerPoint.Application }; $ppt.Visible = 1; $ppt.Presentations.Open('${escapedPath}', 1, 0, 1)`;
    }

    if (psScript) {
        const command = `powershell -NoProfile -ExecutionPolicy Bypass -Command "${psScript}"`;
        exec(command, (error, _stdout, stderr) => {
            if (error) {
                vscode.window.showErrorMessage(`ファイルを開けませんでした: ${error.message}`);
                console.error('Error:', error);
                console.error('stderr:', stderr);
            }
        });
    }
}

連続起動対応:処理後のカーソル移動

/**
 * 元のエディタをアクティブにして1行下の行頭にカーソルを移動する
 */
async function moveCursorToNextLine(document: vscode.TextDocument, currentLine: number) {
    try {
        const editor = await vscode.window.showTextDocument(document, {
            preview: false,
            preserveFocus: false
        });
        const nextLine = Math.min(currentLine + 1, document.lineCount - 1);
        const newPosition = new vscode.Position(nextLine, 0);
        editor.selection = new vscode.Selection(newPosition, newPosition);
        editor.revealRange(new vscode.Range(newPosition, newPosition));
    } catch (_error) {
        // 元のエディタが既に閉じている等の場合は無視
    }
}

新機能:親フォルダを開く

/**
 * カーソル下のパスの親フォルダをExplorerで開く
 */
export async function openParentFolder() {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('アクティブなエディタがありません');
        return;
    }

    const line = editor.document.lineAt(editor.selection.active.line);
    let text = line.text.trim();
    text = preprocessPath(text);
    text = resolveAbsolutePath(text, editor.document.uri.fsPath);

    if (!fs.existsSync(text)) {
        vscode.window.showErrorMessage(`パスが見つかりません: ${text}`);
        return;
    }

    const parentFolder = path.dirname(text);
    openInExplorer(parentFolder);
    vscode.window.showInformationMessage(`親フォルダを開きました: ${parentFolder}`);
}

新機能:新規Officeファイル作成

/**
 * カーソル下のパスにOffice系ファイルを新規作成
 */
export async function createNewOfficeFile() {
    const editor = vscode.window.activeTextEditor;
    if (!editor) {
        vscode.window.showWarningMessage('アクティブなエディタがありません');
        return;
    }

    const line = editor.document.lineAt(editor.selection.active.line);
    let text = line.text.trim();
    text = preprocessPath(text);
    text = resolveAbsolutePath(text, editor.document.uri.fsPath);

    const ext = path.extname(text).toLowerCase();
    if (!isOfficeFile(ext)) {
        vscode.window.showErrorMessage('Office系ファイル(Excel/Word/PowerPoint)の拡張子を指定してください');
        return;
    }

    // ファイルが既に存在する場合は確認
    if (fs.existsSync(text)) {
        const answer = await vscode.window.showWarningMessage(
            `ファイルが既に存在します: ${path.basename(text)}\n上書きしますか?`,
            'はい',
            'いいえ'
        );
        if (answer !== 'はい') {
            return;
        }
    }

    // 親フォルダが存在しない場合は作成
    const parentFolder = path.dirname(text);
    if (!fs.existsSync(parentFolder)) {
        fs.mkdirSync(parentFolder, { recursive: true });
    }

    await createOfficeFile(text, ext);
}

使い方

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.11.vsix

2. 基本的な使い方

作業メモ.txt
---
設計書: C:\work\documents\設計書_v1.0.docx
手順書: C:\work\manual\操作手順.xlsx

通常モードで開く(テキスト→VSCode / Office→通常起動):

  1. パスの行にカーソルを置く
  2. Ctrl+F12

ReadOnly / 関連付けアプリで開く:

  1. パスの行にカーソルを置く
  2. Ctrl+Shift+F12

親フォルダをExplorerで開く:

  1. パスの行にカーソルを置く
  2. Ctrl+Alt+F12

選択テキストをパスとして使う:

  1. パス文字列を選択
  2. Ctrl+F12 または Ctrl+Shift+F12

3. 実践例

シーン1: 複数ファイルを参照しながら作業(連続起動)

タスク一覧.txt
---
■ 今日のタスク
1. 設計書レビュー: C:\work\設計書_v2.0.docx    ← Ctrl+Shift+F12(読み取り専用)→ 自動で次へ
2. データ分析: C:\work\分析結果.xlsx            ← Ctrl+Shift+F12(読み取り専用)→ 自動で次へ
3. 報告書作成: C:\work\報告書_draft.docx        ← Ctrl+F12(編集可能)

シーン2: 共有フォルダのファイルを安全に閲覧

共有資料.txt
---
■ 参考資料(編集禁止)
\\server\share\templates\提案書テンプレート.pptx ← Ctrl+Shift+F12(読み取り専用)
\\server\share\manual\操作手順_最新版.docx       ← Ctrl+Shift+F12(読み取り専用)

シーン3: テキストファイルをサクラエディタで開く

ソース一覧.txt
---
C:\work\src\main.cbl    ← Ctrl+Shift+F12(サクラエディタで開く)
C:\work\src\sub.pco     ← Ctrl+Shift+F12(サクラエディタで開く)

シーン4: バックアップファイルを確認

バージョン管理.txt
---
■ 過去バージョン
現行: C:\work\契約書_v3.0.docx    ← Ctrl+F12(編集可能)
前回: C:\backup\契約書_v2.0.docx  ← Ctrl+Shift+F12(読み取り専用)

package.jsonの設定

{
  "contributes": {
    "commands": [
      {
        "command": "myMacros.openPath",
        "title": "Open Path Under Cursor",
        "category": "My Macros"
      },
      {
        "command": "myMacros.openPathReadOnly",
        "title": "Open Path Under Cursor (ReadOnly / Associated App)",
        "category": "My Macros"
      },
      {
        "command": "myMacros.openParentFolder",
        "title": "Open Parent Folder",
        "category": "My Macros"
      },
      {
        "command": "myMacros.createNewOfficeFile",
        "title": "Create New Office File",
        "category": "My Macros"
      }
    ],
    "keybindings": [
      {
        "command": "myMacros.openPath",
        "key": "ctrl+f12",
        "when": "editorTextFocus"
      },
      {
        "command": "myMacros.openPathReadOnly",
        "key": "ctrl+shift+f12",
        "when": "editorTextFocus"
      },
      {
        "command": "myMacros.openParentFolder",
        "key": "ctrl+alt+f12",
        "when": "editorTextFocus"
      },
      {
        "command": "myMacros.createNewOfficeFile",
        "key": "ctrl+shift+alt+f12",
        "when": "editorTextFocus"
      }
    ]
  }
}

メリット・デメリット

メリット

✅ 圧倒的な効率化

  • 1キー操作でファイルを開ける
  • エクスプローラーでパスをコピペする手間が不要

✅ 誤編集の防止

  • 読み取り専用モードで安全に閲覧
  • 共有フォルダのファイルも安心

✅ サクラエディタ代替の素早い起動

  • COBOLソースやテキストを関連付けアプリで1キー起動
  • 起動中もVSCodeのフォーカスを維持

✅ バージョン非依存

  • COMオブジェクト経由でOfficeのバージョンに関係なく動作
  • Office 2016/2019/Microsoft 365すべて対応

✅ PERSONAL.XLSBのロック競合を回避

  • GetActiveObjectで既存Excelインスタンスを再利用
  • セキュリティ通知も発生しない

✅ 連続起動に最適

  • 外部アプリはバックグラウンド起動でVSCodeのフォーカスを維持
  • 実行後は自動的に次の行へ移動
  • リスト形式のパスを上から順番に一気に開ける

✅ 柔軟なパス記述

  • 絶対パス、相対パス、ネットワークパス対応
  • タブ区切り、引用符付きも自動処理
  • 選択テキストをパスとして使用可能

デメリット

❌ Windows専用(一部機能)

  • PowerShell + COM APIを使用しているためWindows限定
  • Mac/Linuxでは読み取り専用機能は動作しない(通常オープンは可能)

❌ Office系以外の読み取り専用は非対応

  • PDFはデフォルトアプリで開くのみ

トラブルシューティング

ファイルが開かない

原因1: パスが認識されない

解決策: パスの前後に余分な文字がないか確認
NG: 設計書: C:\work\設計書.docx(余計な文字あり)
OK: C:\work\設計書.docx

原因2: 相対パスの基準が異なる

解決策: 絶対パスで記述するか、ワークスペースを開く
優先順位: 1.現在ファイルのディレクトリ 2.ワークスペースフォルダ

原因3: Officeがインストールされていない

解決策: Office系ファイルを開くにはOfficeが必要

読み取り専用で開かない

原因: PowerShellの実行ポリシー

# 確認
Get-ExecutionPolicy

# 設定(管理者権限で実行)
Set-ExecutionPolicy RemoteSigned

外部アプリ起動時にVSCodeのフォーカスが奪われる

openWithAssociatedAppSW_SHOWNOACTIVATEが正しく動作していない可能性があります。PowerShellのバージョンや環境によっては挙動が異なる場合があります。

まとめ

VSCodeでカーソル下のパスからファイル/フォルダを一発で開く機能を実装しました。

主な機能:

ショートカット動作
Ctrl+F12テキスト→VSCodeで開く、Office→通常モード起動
Ctrl+Shift+F12テキスト→関連付けアプリ(サクラエディタ等)、Office→読み取り専用
Ctrl+Alt+F12親フォルダをExplorerで開く
Ctrl+Shift+Alt+F12新規Officeファイル作成

この機能が役立つ人:

  • VSCodeでドキュメント管理をしている
  • 作業メモにファイルパスを書いている
  • 共有フォルダのファイルを頻繁に参照する
  • 誤編集を防ぎたい
  • サクラエディタから移行したい
  • 複数ファイルを連続して開く作業が多い

ぜひ試してみてください!

関連記事

移行の経緯と環境構築

サクラエディタからVSCodeへマクロ移行!快適開発環境の構築記録
はじめに長年愛用してきたサクラエディタのマクロ機能。便利なJavaScript/VBSマクロを多数作成して日常業務で活用してきましたが、最近のAWS開発やブログ執筆でVSCodeを使う機会が増えてきました。「VSCodeでもサクラエディタの...

詳細なセットアップ手順

VSCode TypeScriptマクロ開発環境の完全ガイド【セットアップから運用まで】
はじめにVSCodeでカスタムマクロを作成したいけど、どうやって開発環境を構築すればいいか分からない。そんな悩みを持つ方に向けて、TypeScriptでVSCode拡張機能を開発する環境の構築から、実際にマクロを作成して使えるようにするまで...

タグ: #VSCode #TypeScript #マクロ #Excel #Word #PowerPoint #生産性向上 #Office #読み取り専用

コメント