UNPKG

kawazu

Version:

kawazu CLI tool for real-time chat in your editor

311 lines (310 loc) 11.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.EditorManager = void 0; const child_process_1 = require("child_process"); const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const os = __importStar(require("os")); class VSCodeController { constructor() { this.name = 'VSCode'; } async detect() { try { // VSCodeのプロセスが動いているかチェック const command = os.platform() === 'win32' ? 'tasklist /FI "IMAGENAME eq Code.exe"' : 'pgrep -f "Visual Studio Code"'; return new Promise((resolve) => { (0, child_process_1.exec)(command, (error, stdout) => { resolve(!error && stdout.includes('Code')); }); }); } catch { return false; } } async forceRefresh(filePath) { try { // VSCodeのコマンドライン経由でファイルリロードを指示 const commands = [ // 方法1: ワークスペースをリロード `code --reuse-window "${filePath}"`, // 方法2: ファイルの再オープン `code --goto "${filePath}:1:1"`, ]; for (const command of commands) { await this.executeCommand(command); await new Promise(resolve => setTimeout(resolve, 100)); } // 方法3: VSCodeの設定ファイルを一時的に変更してリロードを促す await this.triggerWorkspaceReload(filePath); return true; } catch (error) { console.error('VSCode制御エラー:', error); return false; } } async openFile(filePath) { try { await this.executeCommand(`code "${filePath}"`); return true; } catch { return false; } } async executeCommand(command) { return new Promise((resolve, reject) => { (0, child_process_1.exec)(command, (error) => { if (error) reject(error); else resolve(); }); }); } async triggerWorkspaceReload(filePath) { const workspaceDir = path.dirname(filePath); const vscodeDir = path.join(workspaceDir, '.vscode'); const settingsPath = path.join(vscodeDir, 'settings.json'); try { // .vscodeディレクトリを作成 await fs.ensureDir(vscodeDir); // 設定ファイルを読み込み let settings = {}; try { const content = await fs.readFile(settingsPath, 'utf8'); settings = JSON.parse(content); } catch { // ファイルが存在しない場合は新規作成 } // 一時的な設定を追加(ファイル監視を強制的に有効化) settings['files.watcherExclude'] = { ...settings['files.watcherExclude'], [`**/${path.basename(filePath)}`]: false }; settings['files.useExperimentalFileWatcher'] = true; // 設定ファイルを保存 await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2)); // 少し待ってから元に戻す setTimeout(async () => { try { delete settings['files.useExperimentalFileWatcher']; if (settings['files.watcherExclude']) { delete settings['files.watcherExclude'][`**/${path.basename(filePath)}`]; } await fs.writeFile(settingsPath, JSON.stringify(settings, null, 2)); } catch (error) { console.error('設定復元エラー:', error); } }, 1000); } catch (error) { console.error('VSCode設定操作エラー:', error); } } } class CursorController { constructor() { this.name = 'Cursor'; } async detect() { try { const command = os.platform() === 'win32' ? 'tasklist /FI "IMAGENAME eq Cursor.exe"' : 'pgrep -f "Cursor"'; return new Promise((resolve) => { (0, child_process_1.exec)(command, (error, stdout) => { resolve(!error && stdout.includes('Cursor')); }); }); } catch { return false; } } async forceRefresh(filePath) { try { // Cursorのコマンドライン経由での制御 const commands = [ `cursor --reuse-window "${filePath}"`, `cursor --goto "${filePath}:1:1"`, ]; for (const command of commands) { await this.executeCommand(command); await new Promise(resolve => setTimeout(resolve, 100)); } return true; } catch (error) { console.error('Cursor制御エラー:', error); return false; } } async openFile(filePath) { try { await this.executeCommand(`cursor "${filePath}"`); return true; } catch { return false; } } async executeCommand(command) { return new Promise((resolve, reject) => { (0, child_process_1.exec)(command, (error) => { if (error) reject(error); else resolve(); }); }); } } // ファイルシステムレベルでの強制更新 class FileSystemController { constructor() { this.name = 'FileSystem'; } async detect() { return true; // 常に利用可能 } async forceRefresh(filePath) { try { console.log('🔄 ファイルシステムレベルでの強制更新開始'); // 方法1: ファイルの修正時刻を段階的に更新 await this.touchFileMultipleTimes(filePath); // 方法2: 一時ファイル作成→削除でディレクトリ監視をトリガー await this.triggerDirectoryChange(filePath); // 方法3: ファイル内容の一時的変更 await this.temporaryContentChange(filePath); return true; } catch (error) { console.error('ファイルシステム制御エラー:', error); return false; } } async openFile(filePath) { // ファイルシステムレベルでは開くことはできない return false; } async touchFileMultipleTimes(filePath) { // 複数回にわたって修正時刻を更新 for (let i = 0; i < 5; i++) { const time = new Date(Date.now() + i * 200); await fs.utimes(filePath, time, time); await new Promise(resolve => setTimeout(resolve, 100)); } } async triggerDirectoryChange(filePath) { const dir = path.dirname(filePath); const tempFile = path.join(dir, `.kawazu-refresh-${Date.now()}.tmp`); try { // 一時ファイルを作成 await fs.writeFile(tempFile, ''); await new Promise(resolve => setTimeout(resolve, 50)); // 一時ファイルを削除 await fs.remove(tempFile); } catch (error) { // エラーは無視(一時ファイルの作成に失敗してもOK) } } async temporaryContentChange(filePath) { try { const content = await fs.readFile(filePath, 'utf8'); // 末尾に一時的な空白を追加 await fs.writeFile(filePath, content + ' ', 'utf8'); await new Promise(resolve => setTimeout(resolve, 50)); // 元の内容に戻す await fs.writeFile(filePath, content, 'utf8'); } catch (error) { console.error('一時的内容変更エラー:', error); } } } class EditorManager { constructor() { this.controllers = [ new VSCodeController(), new CursorController(), new FileSystemController() // フォールバック ]; } async forceRefreshFile(filePath) { console.log('🔄 エディター強制リフレッシュ開始:', path.basename(filePath)); // 利用可能なエディターコントローラーを検出 const availableControllers = []; for (const controller of this.controllers) { const isAvailable = await controller.detect(); if (isAvailable) { availableControllers.push(controller); console.log(`✅ ${controller.name} が検出されました`); } } if (availableControllers.length === 0) { console.log('❌ 利用可能なエディターが見つかりません'); return false; } // 複数のコントローラーで並列実行 const results = await Promise.allSettled(availableControllers.map(controller => controller.forceRefresh(filePath))); const successCount = results.filter(r => r.status === 'fulfilled' && r.value).length; console.log(`🔄 エディター強制リフレッシュ完了: ${successCount}/${results.length} 成功`); return successCount > 0; } async openFileInEditor(filePath) { for (const controller of this.controllers) { const isAvailable = await controller.detect(); if (isAvailable) { const success = await controller.openFile(filePath); if (success) { console.log(`📁 ${controller.name} でファイルを開きました:`, path.basename(filePath)); return true; } } } console.log('❌ ファイルを開けませんでした:', path.basename(filePath)); return false; } } exports.EditorManager = EditorManager;