UNPKG

giga-code

Version:

A personal AI CLI assistant powered by Grok for local development.

386 lines 15.5 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 (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TextEditorTool = void 0; const fs = __importStar(require("fs-extra")); const path = __importStar(require("path")); const confirmation_service_1 = require("../utils/confirmation-service"); class TextEditorTool { constructor() { this.editHistory = []; this.confirmationService = confirmation_service_1.ConfirmationService.getInstance(); } async view(filePath, viewRange) { try { const resolvedPath = path.resolve(filePath); if (await fs.pathExists(resolvedPath)) { const stats = await fs.stat(resolvedPath); if (stats.isDirectory()) { const files = await fs.readdir(resolvedPath); return { success: true, output: `Directory contents of ${filePath}:\n${files.join("\n")}`, }; } const content = await fs.readFile(resolvedPath, "utf-8"); const lines = content.split("\n"); if (viewRange) { const [start, end] = viewRange; const selectedLines = lines.slice(start - 1, end); const numberedLines = selectedLines .map((line, idx) => `${start + idx}: ${line}`) .join("\n"); return { success: true, output: `Lines ${start}-${end} of ${filePath}:\n${numberedLines}`, }; } const totalLines = lines.length; const displayLines = totalLines > 10 ? lines.slice(0, 10) : lines; const numberedLines = displayLines .map((line, idx) => `${idx + 1}: ${line}`) .join("\n"); const additionalLinesMessage = totalLines > 10 ? `\n... +${totalLines - 10} lines` : ""; return { success: true, output: `Contents of ${filePath}:\n${numberedLines}${additionalLinesMessage}`, }; } else { return { success: false, error: `File or directory not found: ${filePath}`, }; } } catch (error) { return { success: false, error: `Error viewing ${filePath}: ${error.message}`, }; } } async strReplace(filePath, oldStr, newStr) { try { const resolvedPath = path.resolve(filePath); if (!(await fs.pathExists(resolvedPath))) { return { success: false, error: `File not found: ${filePath}`, }; } const content = await fs.readFile(resolvedPath, "utf-8"); if (!content.includes(oldStr)) { return { success: false, error: `String not found in file: "${oldStr}"`, }; } // Check if user has already accepted file operations for this session const sessionFlags = this.confirmationService.getSessionFlags(); if (!sessionFlags.fileOperations && !sessionFlags.allOperations) { // Create a proper diff preview showing the change const newContent = content.replace(oldStr, newStr); const oldLines = content.split("\n"); const newLines = newContent.split("\n"); const diffContent = this.generateDiff(oldLines, newLines, filePath); const confirmationResult = await this.confirmationService.requestConfirmation({ operation: "Edit file", filename: filePath, showVSCodeOpen: false, content: diffContent, }, "file"); if (!confirmationResult.confirmed) { return { success: false, error: confirmationResult.feedback || "File edit cancelled by user", }; } } const newContent = content.replace(oldStr, newStr); await fs.writeFile(resolvedPath, newContent, "utf-8"); this.editHistory.push({ command: "str_replace", path: filePath, old_str: oldStr, new_str: newStr, }); // Generate diff output const oldLines = content.split("\n"); const newLines = newContent.split("\n"); const diff = this.generateDiff(oldLines, newLines, filePath); return { success: true, output: diff, }; } catch (error) { return { success: false, error: `Error replacing text in ${filePath}: ${error.message}`, }; } } async create(filePath, content) { try { const resolvedPath = path.resolve(filePath); // Check if user has already accepted file operations for this session const sessionFlags = this.confirmationService.getSessionFlags(); if (!sessionFlags.fileOperations && !sessionFlags.allOperations) { // Create a diff-style preview for file creation const contentLines = content.split("\n"); const diffContent = [ `Created ${filePath}`, `--- /dev/null`, `+++ b/${filePath}`, `@@ -0,0 +1,${contentLines.length} @@`, ...contentLines.map((line) => `+${line}`), ].join("\n"); const confirmationResult = await this.confirmationService.requestConfirmation({ operation: "Write", filename: filePath, showVSCodeOpen: false, content: diffContent, }, "file"); if (!confirmationResult.confirmed) { return { success: false, error: confirmationResult.feedback || "File creation cancelled by user", }; } } const dir = path.dirname(resolvedPath); await fs.ensureDir(dir); await fs.writeFile(resolvedPath, content, "utf-8"); this.editHistory.push({ command: "create", path: filePath, content, }); // Generate diff output using the same method as str_replace const oldLines = []; // Empty for new files const newLines = content.split("\n"); const diff = this.generateDiff(oldLines, newLines, filePath); return { success: true, output: diff, }; } catch (error) { return { success: false, error: `Error creating ${filePath}: ${error.message}`, }; } } async insert(filePath, insertLine, content) { try { const resolvedPath = path.resolve(filePath); if (!(await fs.pathExists(resolvedPath))) { return { success: false, error: `File not found: ${filePath}`, }; } const fileContent = await fs.readFile(resolvedPath, "utf-8"); const lines = fileContent.split("\n"); lines.splice(insertLine - 1, 0, content); const newContent = lines.join("\n"); await fs.writeFile(resolvedPath, newContent, "utf-8"); this.editHistory.push({ command: "insert", path: filePath, insert_line: insertLine, content, }); return { success: true, output: `Successfully inserted content at line ${insertLine} in ${filePath}`, }; } catch (error) { return { success: false, error: `Error inserting content in ${filePath}: ${error.message}`, }; } } async undoEdit() { if (this.editHistory.length === 0) { return { success: false, error: "No edits to undo", }; } const lastEdit = this.editHistory.pop(); try { switch (lastEdit.command) { case "str_replace": if (lastEdit.path && lastEdit.old_str && lastEdit.new_str) { const content = await fs.readFile(lastEdit.path, "utf-8"); const revertedContent = content.replace(lastEdit.new_str, lastEdit.old_str); await fs.writeFile(lastEdit.path, revertedContent, "utf-8"); } break; case "create": if (lastEdit.path) { await fs.remove(lastEdit.path); } break; case "insert": if (lastEdit.path && lastEdit.insert_line) { const content = await fs.readFile(lastEdit.path, "utf-8"); const lines = content.split("\n"); lines.splice(lastEdit.insert_line - 1, 1); await fs.writeFile(lastEdit.path, lines.join("\n"), "utf-8"); } break; } return { success: true, output: `Successfully undid ${lastEdit.command} operation`, }; } catch (error) { return { success: false, error: `Error undoing edit: ${error.message}`, }; } } generateDiff(oldLines, newLines, filePath) { // Use Myers diff algorithm to find actual changes const lcs = this.findLCS(oldLines, newLines); const changes = this.getChanges(oldLines, newLines, lcs); // Count actual changes (not cancelling ones) let addedLines = 0; let removedLines = 0; changes.forEach(change => { if (change.type === 'add') addedLines++; else if (change.type === 'remove') removedLines++; }); // If no actual changes (all cancelled out), return empty if (addedLines === 0 && removedLines === 0) { return `No changes to ${filePath}`; } let summary = `Updated ${filePath}`; if (addedLines > 0 && removedLines > 0) { summary += ` with ${addedLines} addition${addedLines !== 1 ? "s" : ""} and ${removedLines} removal${removedLines !== 1 ? "s" : ""}`; } else if (addedLines > 0) { summary += ` with ${addedLines} addition${addedLines !== 1 ? "s" : ""}`; } else if (removedLines > 0) { summary += ` with ${removedLines} removal${removedLines !== 1 ? "s" : ""}`; } // Generate proper git-style unified diff const hunk = this.generateHunk(oldLines, newLines, changes); let diff = summary + "\n"; diff += `--- a/${filePath}\n`; diff += `+++ b/${filePath}\n`; diff += hunk; return diff.trim(); } findLCS(oldLines, newLines) { const m = oldLines.length; const n = newLines.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (oldLines[i - 1] === newLines[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } return dp; } getChanges(oldLines, newLines, lcs) { const changes = []; let i = oldLines.length; let j = newLines.length; while (i > 0 || j > 0) { if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) { i--; j--; } else if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) { changes.unshift({ type: 'add', line: newLines[j - 1], index: j - 1 }); j--; } else if (i > 0 && (j === 0 || lcs[i][j - 1] < lcs[i - 1][j])) { changes.unshift({ type: 'remove', line: oldLines[i - 1], index: i - 1 }); i--; } } return changes; } generateHunk(oldLines, newLines, changes) { if (changes.length === 0) return ""; const oldStart = 1; const newStart = 1; const oldCount = oldLines.length; const newCount = newLines.length; let hunk = `@@ -${oldStart},${oldCount} +${newStart},${newCount} @@\n`; // Build the actual diff const result = []; const lcs = this.findLCS(oldLines, newLines); let oldIndex = 0; let newIndex = 0; function backtrack(i, j) { if (i === 0 && j === 0) return; if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) { backtrack(i - 1, j - 1); result.push(` ${oldLines[i - 1]}`); oldIndex++; newIndex++; } else if (j > 0 && (i === 0 || lcs[i][j - 1] >= lcs[i - 1][j])) { backtrack(i, j - 1); result.push(`+${newLines[j - 1]}`); newIndex++; } else if (i > 0 && (j === 0 || lcs[i][j - 1] < lcs[i - 1][j])) { backtrack(i - 1, j); result.push(`-${oldLines[i - 1]}`); oldIndex++; } } backtrack(oldLines.length, newLines.length); hunk += result.join("\n"); return hunk; } getEditHistory() { return [...this.editHistory]; } } exports.TextEditorTool = TextEditorTool; //# sourceMappingURL=text-editor.js.map