kawazu
Version:
kawazu CLI tool for real-time chat in your editor
311 lines (310 loc) • 11.7 kB
JavaScript
;
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;