UNPKG

lookatni-file-markers

Version:

Advanced file organization system with unique markers for code sharing and project management. Extract files from marked content, generate markers, and validate project integrity.

1,428 lines (1,397 loc) 122 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/extension.ts var extension_exports = {}; __export(extension_exports, { activate: () => activate, deactivate: () => deactivate }); module.exports = __toCommonJS(extension_exports); var vscode17 = __toESM(require("vscode")); // src/commands/extractFiles.ts var vscode = __toESM(require("vscode")); var fs2 = __toESM(require("fs")); // src/utils/markerParser.ts var fs = __toESM(require("fs")); var path = __toESM(require("path")); var MarkerParser = class { constructor(logger) { this.logger = logger; } // ASCII 28 (File Separator) character for invisible markers FS_CHAR = String.fromCharCode(28); markerRegex = new RegExp(`^\\/\\/${this.FS_CHAR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/ (.+?) \\/${this.FS_CHAR.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\/\\/$`); parseMarkedFile(filePath) { const results = { totalMarkers: 0, totalFiles: 0, totalBytes: 0, errors: [], markers: [] }; try { const content = fs.readFileSync(filePath, "utf-8"); const lines = content.split("\n"); let currentMarker = null; let currentContent = []; for (let i = 0; i < lines.length; i++) { const line = lines[i]; const lineNumber = i + 1; const markerMatch = line.match(this.markerRegex); if (markerMatch) { if (currentMarker) { this.finalizeMarker(currentMarker, currentContent, results, i); } const filename = markerMatch[1].trim(); if (!filename) { results.errors.push({ line: lineNumber, message: "Empty filename in marker" }); continue; } currentMarker = { filename, startLine: lineNumber }; currentContent = []; results.totalMarkers++; } else if (currentMarker) { currentContent.push(line); } } if (currentMarker) { this.finalizeMarker(currentMarker, currentContent, results, lines.length); } this.logger.info(`Parsed ${results.totalMarkers} markers from ${filePath}`); } catch (error) { this.logger.error(`Error parsing marked file ${filePath}:`, error); results.errors.push({ line: 0, message: `File read error: ${error}` }); } return results; } finalizeMarker(marker, content, results, endLine) { if (!marker.filename || !marker.startLine) { return; } while (content.length > 0 && content[content.length - 1].trim() === "") { content.pop(); } const finalContent = content.join("\n"); const completedMarker = { filename: marker.filename, content: finalContent, startLine: marker.startLine, endLine }; results.markers.push(completedMarker); results.totalFiles++; results.totalBytes += Buffer.byteLength(finalContent, "utf-8"); } extractFiles(markedFilePath, outputDir, options = {}) { const result = { success: true, extractedFiles: [], errors: [] }; const parseResults = this.parseMarkedFile(markedFilePath); if (parseResults.errors.length > 0) { result.errors.push(...parseResults.errors.map((e) => `Line ${e.line}: ${e.message}`)); } for (const marker of parseResults.markers) { try { const outputPath = path.join(outputDir, marker.filename); if (!options.overwrite && fs.existsSync(outputPath)) { const choice = options.dryRun ? "skip" : "skip"; if (choice === "skip") { this.logger.warn(`Skipping existing file: ${outputPath}`); continue; } } if (options.dryRun) { this.logger.info(`[DRY RUN] Would extract: ${outputPath}`); result.extractedFiles.push(outputPath); continue; } if (options.createDirs) { const dir = path.dirname(outputPath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); this.logger.debug(`Created directory: ${dir}`); } } fs.writeFileSync(outputPath, marker.content, "utf-8"); result.extractedFiles.push(outputPath); this.logger.debug(`Extracted: ${outputPath} (${marker.content.length} chars)`); } catch (error) { const errorMsg = `Error extracting ${marker.filename}: ${error}`; this.logger.error(errorMsg); result.errors.push(errorMsg); result.success = false; } } return result; } validateMarkers(filePath) { const result = { isValid: true, errors: [], statistics: { totalMarkers: 0, duplicateFilenames: [], invalidFilenames: [], emptyMarkers: 0 } }; const parseResults = this.parseMarkedFile(filePath); result.statistics.totalMarkers = parseResults.totalMarkers; result.errors.push(...parseResults.errors.map((e) => ({ line: e.line, message: e.message, severity: "error" }))); const seenFilenames = /* @__PURE__ */ new Set(); for (const marker of parseResults.markers) { if (seenFilenames.has(marker.filename)) { if (!result.statistics.duplicateFilenames.includes(marker.filename)) { result.statistics.duplicateFilenames.push(marker.filename); result.errors.push({ line: marker.startLine, message: `Duplicate filename: ${marker.filename}`, severity: "warning" }); } } seenFilenames.add(marker.filename); if (this.isInvalidFilename(marker.filename)) { result.statistics.invalidFilenames.push(marker.filename); result.errors.push({ line: marker.startLine, message: `Invalid filename: ${marker.filename}`, severity: "error" }); result.isValid = false; } if (marker.content.trim() === "") { result.statistics.emptyMarkers++; result.errors.push({ line: marker.startLine, message: `Empty content for file: ${marker.filename}`, severity: "warning" }); } } if (result.errors.some((e) => e.severity === "error")) { result.isValid = false; } return result; } isInvalidFilename(filename) { const invalidChars = /[<>:"|?*\x00-\x1f]/; if (invalidChars.test(filename)) { return true; } const reservedNames = /^(CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])$/i; const baseName = path.basename(filename, path.extname(filename)); if (reservedNames.test(baseName)) { return true; } if (!filename.trim()) { return true; } return false; } }; // src/commands/extractFiles.ts var ExtractFilesCommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.extractFiles"; async execute(uri) { this.logger.info("\u{1F504} Starting file extraction..."); try { const markedFile = await this.getMarkedFile(uri); if (!markedFile) { return; } const destFolder = await this.getDestinationFolder(); if (!destFolder) { return; } const options = await this.getExtractionOptions(); if (!options) { return; } const parser = new MarkerParser(this.logger); const results = await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: "Extracting files...", cancellable: false }, async () => { return parser.extractFiles(markedFile, destFolder, options); }); this.showResults(results, destFolder); const openFolder = await vscode.window.showInformationMessage( `\u2705 Extracted ${results.extractedFiles.length} files`, "Open Folder" ); if (openFolder === "Open Folder") { vscode.commands.executeCommand("revealFileInOS", vscode.Uri.file(destFolder)); } } catch (error) { this.logger.error("Error during extraction:", error); vscode.window.showErrorMessage(`Extraction failed: ${error}`); } } async getMarkedFile(uri) { if (uri && uri.fsPath && fs2.existsSync(uri.fsPath)) { return uri.fsPath; } const fileUri = await vscode.window.showOpenDialog({ canSelectMany: false, canSelectFiles: true, canSelectFolders: false, openLabel: "Select marked file to extract", filters: { "LookAtni Files": ["txt", "md"], "All Files": ["*"] } }); return fileUri?.[0]?.fsPath; } async getDestinationFolder() { const folderUri = await vscode.window.showOpenDialog({ canSelectMany: false, canSelectFiles: false, canSelectFolders: true, openLabel: "Select destination folder" }); return folderUri?.[0]?.fsPath; } async getExtractionOptions() { return { overwrite: true, createDirs: true }; } showResults(results, destFolder) { this.outputChannel.appendLine("\n=== EXTRACTION RESULTS ==="); this.outputChannel.appendLine(`\u{1F4C1} Destination: ${destFolder}`); this.outputChannel.appendLine(`\u2705 Files extracted: ${results.extractedFiles.length}`); this.outputChannel.show(); } }; // src/commands/generateMarkers.ts var vscode2 = __toESM(require("vscode")); var fs4 = __toESM(require("fs")); var path3 = __toESM(require("path")); // src/utils/markerGenerator.ts var fs3 = __toESM(require("fs")); var path2 = __toESM(require("path")); var MarkerGenerator = class { constructor(logger) { this.logger = logger; } // ASCII 28 (File Separator) character for invisible markers FS_CHAR = String.fromCharCode(28); async generateMarkers(sourceFolder, outputFile, options, progressCallback) { const results = { sourceFolder, totalFiles: 0, totalBytes: 0, skippedFiles: [], fileTypes: {} }; const allFiles = this.getAllFiles(sourceFolder, options.excludePatterns); this.logger.info(`Found ${allFiles.length} files to process`); const filesToProcess = allFiles.filter((filePath) => { try { const stats = fs3.statSync(filePath); const sizeKB = stats.size / 1024; if (options.maxFileSize !== -1 && sizeKB > options.maxFileSize) { results.skippedFiles.push({ path: path2.relative(sourceFolder, filePath), reason: `File too large (${sizeKB.toFixed(1)} KB > ${options.maxFileSize} KB)` }); return false; } if (this.isBinaryFile(filePath)) { results.skippedFiles.push({ path: path2.relative(sourceFolder, filePath), reason: "Binary file" }); return false; } return true; } catch (error) { results.skippedFiles.push({ path: path2.relative(sourceFolder, filePath), reason: `Error reading file: ${error}` }); return false; } }); this.logger.info(`Processing ${filesToProcess.length} files after filtering`); let output = `//${this.FS_CHAR}/ PROJECT_INFO /${this.FS_CHAR}// `; output += `Project: ${path2.basename(sourceFolder)} `; output += `Generated: ${(/* @__PURE__ */ new Date()).toISOString()} `; output += `Total Files: ${filesToProcess.length} `; output += `Source: ${sourceFolder} `; for (let i = 0; i < filesToProcess.length; i++) { const filePath = filesToProcess[i]; const relativePath = path2.relative(sourceFolder, filePath); if (progressCallback) { progressCallback(i + 1, filesToProcess.length); } try { const content = fs3.readFileSync(filePath, "utf-8"); const stats = fs3.statSync(filePath); results.totalFiles++; results.totalBytes += stats.size; const ext = path2.extname(filePath).toLowerCase() || "no-extension"; results.fileTypes[ext] = (results.fileTypes[ext] || 0) + 1; output += `//${this.FS_CHAR}/ ${relativePath} /${this.FS_CHAR}// `; output += content; if (!content.endsWith("\n")) { output += "\n"; } output += "\n"; this.logger.debug(`Processed: ${relativePath} (${stats.size} bytes)`); } catch (error) { this.logger.error(`Error processing ${relativePath}:`, error); results.skippedFiles.push({ path: relativePath, reason: `Read error: ${error}` }); } } fs3.writeFileSync(outputFile, output, "utf-8"); this.logger.info(`Generated markers file: ${outputFile}`); return results; } getAllFiles(dir, excludePatterns) { const files = []; const traverse = (currentDir) => { try { const entries = fs3.readdirSync(currentDir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path2.join(currentDir, entry.name); const relativePath = path2.relative(dir, fullPath); if (this.shouldExclude(entry.name, relativePath, excludePatterns)) { continue; } if (entry.isDirectory()) { traverse(fullPath); } else if (entry.isFile()) { files.push(fullPath); } } } catch (error) { this.logger.warn(`Cannot read directory ${currentDir}:`, error); } }; traverse(dir); return files; } shouldExclude(name, relativePath, patterns) { for (const pattern of patterns) { if (pattern.includes("*")) { const regex = new RegExp(pattern.replace(/\*/g, ".*")); if (regex.test(name) || regex.test(relativePath)) { return true; } } else if (name === pattern || relativePath.includes(pattern)) { return true; } } return false; } isBinaryFile(filePath) { const binaryExtensions = [ ".exe", ".dll", ".so", ".dylib", ".bin", ".obj", ".jpg", ".jpeg", ".png", ".gif", ".bmp", ".ico", ".webp", ".mp3", ".mp4", ".avi", ".mov", ".wav", ".ogg", ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".zip", ".rar", ".7z", ".tar", ".gz", ".bz2", ".class", ".jar", ".war", ".ear", ".pyc", ".pyo", ".pyd", ".woff", ".woff2", ".ttf", ".otf", ".eot" ]; const ext = path2.extname(filePath).toLowerCase(); if (binaryExtensions.includes(ext)) { return true; } try { const buffer = fs3.readFileSync(filePath, { encoding: null, flag: "r" }); if (buffer.length === 0) { return false; } const sampleSize = Math.min(1024, buffer.length); for (let i = 0; i < sampleSize; i++) { if (buffer[i] === 0) { return true; } } return false; } catch { return true; } } }; // src/commands/generateMarkers.ts var GenerateMarkersCommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.generateMarkers"; async execute(uri) { this.logger.info("\u{1F504} Starting marker generation..."); try { const sourceFolder = await this.getSourceFolder(uri); if (!sourceFolder) { return; } const outputFile = await this.getOutputFile(sourceFolder); if (!outputFile) { return; } const options = await this.getGenerationOptions(); if (!options) { return; } const generator = new MarkerGenerator(this.logger); const results = await vscode2.window.withProgress({ location: vscode2.ProgressLocation.Notification, title: "Generating markers...", cancellable: false }, async (progress) => { return generator.generateMarkers(sourceFolder, outputFile, options, (current, total) => { progress.report({ increment: 100 / total, message: `Processing ${current}/${total} files...` }); }); }); this.showResults(results, outputFile); const openFile = await vscode2.window.showInformationMessage( `\u2705 Generated markers for ${results.totalFiles} files`, "Open File", "Copy Path" ); if (openFile === "Open File") { const document = await vscode2.workspace.openTextDocument(outputFile); vscode2.window.showTextDocument(document); } else if (openFile === "Copy Path") { vscode2.env.clipboard.writeText(outputFile); vscode2.window.showInformationMessage("Path copied to clipboard!"); } } catch (error) { this.logger.error("Error during generation:", error); vscode2.window.showErrorMessage(`Generation failed: ${error}`); } } async getSourceFolder(uri) { if (uri && uri.fsPath) { const stat = fs4.statSync(uri.fsPath); if (stat.isDirectory()) { return uri.fsPath; } } if (vscode2.workspace.workspaceFolders && vscode2.workspace.workspaceFolders.length > 0) { const workspaceRoot = vscode2.workspace.workspaceFolders[0].uri.fsPath; const choice = await vscode2.window.showQuickPick([ { label: "\u{1F4C1} Entire Workspace", description: "Generate from workspace root", path: workspaceRoot }, { label: "\u{1F4C2} Choose Subfolder...", description: "Select specific folder", path: "custom" } ], { placeHolder: "Select source folder" }); if (!choice) { return void 0; } if (choice.path === "custom") { const folderUri2 = await vscode2.window.showOpenDialog({ canSelectMany: false, canSelectFiles: false, canSelectFolders: true, openLabel: "Select source folder" }); return folderUri2?.[0]?.fsPath; } return choice.path; } const folderUri = await vscode2.window.showOpenDialog({ canSelectMany: false, canSelectFiles: false, canSelectFolders: true, openLabel: "Select source folder" }); return folderUri?.[0]?.fsPath; } async getOutputFile(sourceFolder) { const baseName = path3.basename(sourceFolder); const defaultName = `${baseName}-lookatni.txt`; const result = await vscode2.window.showInputBox({ prompt: "Enter output file name", value: defaultName, validateInput: (value) => { if (!value.trim()) { return "File name cannot be empty"; } if (!/\.(txt|md)$/i.test(value)) { return "File should have .txt or .md extension"; } return null; } }); if (!result) { return void 0; } if (vscode2.workspace.workspaceFolders && vscode2.workspace.workspaceFolders.length > 0) { const workspaceRoot = vscode2.workspace.workspaceFolders[0].uri.fsPath; return path3.join(workspaceRoot, result); } return path3.join(path3.dirname(sourceFolder), result); } async getGenerationOptions() { const options = {}; const maxSize = await vscode2.window.showQuickPick([ { label: "500 KB", description: "Small files only", value: 500 }, { label: "1 MB", description: "Medium files (recommended)", value: 1e3 }, { label: "5 MB", description: "Large files", value: 5e3 }, { label: "No limit", description: "Include all files", value: -1 } ], { placeHolder: "Select maximum file size to include" }); if (!maxSize) { return void 0; } options.maxFileSize = maxSize.value; const excludeChoice = await vscode2.window.showQuickPick([ { label: "Standard exclusions", description: "node_modules, .git, build folders", value: "standard" }, { label: "Minimal exclusions", description: "Only hidden files", value: "minimal" }, { label: "Custom exclusions", description: "Specify patterns", value: "custom" } ], { placeHolder: "Select exclusion strategy" }); if (!excludeChoice) { return void 0; } switch (excludeChoice.value) { case "standard": options.excludePatterns = [ "node_modules", ".git", ".svn", ".hg", "build", "dist", "out", "target", "__pycache__", ".pytest_cache", ".vscode", "*.log", "*.tmp", "*.cache" ]; break; case "minimal": options.excludePatterns = [".*"]; break; case "custom": const patterns = await vscode2.window.showInputBox({ prompt: "Enter exclusion patterns (comma-separated)", placeHolder: "e.g., node_modules, *.log, build", value: "node_modules, .git, build" }); if (patterns) { options.excludePatterns = patterns.split(",").map((p) => p.trim()); } else { options.excludePatterns = []; } break; } return options; } showResults(results, outputFile) { const { totalFiles, totalBytes, skippedFiles } = results; this.outputChannel.appendLine("\n=== GENERATION RESULTS ==="); this.outputChannel.appendLine(`\u{1F4C1} Source: ${results.sourceFolder}`); this.outputChannel.appendLine(`\u{1F4C4} Output: ${outputFile}`); this.outputChannel.appendLine(`\u2705 Files processed: ${totalFiles}`); this.outputChannel.appendLine(`\u{1F4CA} Total size: ${this.formatBytes(totalBytes)}`); this.outputChannel.appendLine(`\u23ED\uFE0F Files skipped: ${skippedFiles.length}`); if (skippedFiles.length > 0) { this.outputChannel.appendLine("\n=== SKIPPED FILES ==="); skippedFiles.forEach((file) => { this.outputChannel.appendLine(`\u23ED\uFE0F ${file.path}: ${file.reason}`); }); } const config = vscode2.workspace.getConfiguration("lookatni"); if (config.get("showStatistics", true)) { vscode2.commands.executeCommand("lookatni-file-markers.showStatistics", results); } } formatBytes(bytes) { if (bytes === 0) { return "0 B"; } const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; } }; // src/commands/validateMarkers.ts var vscode3 = __toESM(require("vscode")); var fs5 = __toESM(require("fs")); var ValidateMarkersCommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.validateMarkers"; async execute(uri) { this.logger.info("\u{1F50D} Starting marker validation..."); try { const markedFile = await this.getMarkedFile(uri); if (!markedFile) { return; } const parser = new MarkerParser(this.logger); const validation = await vscode3.window.withProgress({ location: vscode3.ProgressLocation.Notification, title: "Validating markers...", cancellable: false }, async () => { return parser.validateMarkers(markedFile); }); this.showValidationResults(validation, markedFile); await this.createDiagnostics(markedFile, validation); } catch (error) { this.logger.error("Error during validation:", error); vscode3.window.showErrorMessage(`Validation failed: ${error}`); } } async getMarkedFile(uri) { if (uri && uri.fsPath && fs5.existsSync(uri.fsPath)) { return uri.fsPath; } const activeEditor = vscode3.window.activeTextEditor; if (activeEditor && activeEditor.document.fileName.endsWith(".txt")) { return activeEditor.document.fileName; } const fileUri = await vscode3.window.showOpenDialog({ canSelectMany: false, canSelectFiles: true, canSelectFolders: false, openLabel: "Select marked file to validate", filters: { "LookAtni Files": ["txt", "md"], "All Files": ["*"] } }); return fileUri?.[0]?.fsPath; } showValidationResults(validation, filePath) { const { isValid, errors, statistics } = validation; this.outputChannel.clear(); this.outputChannel.appendLine("=== MARKER VALIDATION RESULTS ==="); this.outputChannel.appendLine(`\u{1F4C4} File: ${filePath}`); this.outputChannel.appendLine(`\u2705 Valid: ${isValid ? "YES" : "NO"}`); this.outputChannel.appendLine(`\u{1F4CA} Total markers: ${statistics.totalMarkers}`); this.outputChannel.appendLine(`\u{1F50D} Errors: ${errors.filter((e) => e.severity === "error").length}`); this.outputChannel.appendLine(`\u26A0\uFE0F Warnings: ${errors.filter((e) => e.severity === "warning").length}`); this.outputChannel.appendLine(""); if (statistics.duplicateFilenames.length > 0) { this.outputChannel.appendLine("=== DUPLICATE FILENAMES ==="); statistics.duplicateFilenames.forEach((filename) => { this.outputChannel.appendLine(`\u{1F504} ${filename}`); }); this.outputChannel.appendLine(""); } if (statistics.invalidFilenames.length > 0) { this.outputChannel.appendLine("=== INVALID FILENAMES ==="); statistics.invalidFilenames.forEach((filename) => { this.outputChannel.appendLine(`\u274C ${filename}`); }); this.outputChannel.appendLine(""); } if (statistics.emptyMarkers > 0) { this.outputChannel.appendLine(`=== EMPTY MARKERS: ${statistics.emptyMarkers} ===`); this.outputChannel.appendLine(""); } if (errors.length > 0) { this.outputChannel.appendLine("=== DETAILED ERRORS ==="); errors.forEach((error) => { const icon = error.severity === "error" ? "\u274C" : "\u26A0\uFE0F"; this.outputChannel.appendLine(`${icon} Line ${error.line}: ${error.message}`); }); } this.outputChannel.show(); const errorCount = errors.filter((e) => e.severity === "error").length; const warningCount = errors.filter((e) => e.severity === "warning").length; if (isValid) { const message = warningCount > 0 ? `\u2705 File is valid with ${warningCount} warnings` : "\u2705 File is completely valid!"; vscode3.window.showInformationMessage(message); } else { vscode3.window.showErrorMessage( `\u274C File is invalid: ${errorCount} errors, ${warningCount} warnings` ); } } async createDiagnostics(filePath, validation) { try { const document = await vscode3.workspace.openTextDocument(filePath); const diagnostics = []; for (const error of validation.errors) { if (error.line > 0 && error.line <= document.lineCount) { const line = document.lineAt(error.line - 1); const range = new vscode3.Range( error.line - 1, 0, error.line - 1, line.text.length ); const severity = error.severity === "error" ? vscode3.DiagnosticSeverity.Error : vscode3.DiagnosticSeverity.Warning; const diagnostic = new vscode3.Diagnostic( range, error.message, severity ); diagnostic.source = "LookAtni"; diagnostics.push(diagnostic); } } const collection = vscode3.languages.createDiagnosticCollection("lookatni"); collection.set(document.uri, diagnostics); this.context.subscriptions.push(collection); } catch (error) { this.logger.warn("Could not create diagnostics:", error); } } }; // src/commands/quickDemo.ts var vscode4 = __toESM(require("vscode")); var fs6 = __toESM(require("fs")); var path4 = __toESM(require("path")); var QuickDemoCommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.quickDemo"; // ASCII 28 (File Separator) character for invisible markers FS_CHAR = String.fromCharCode(28); async execute() { this.logger.info("\u{1F680} Starting LookAtni Quick Demo..."); try { const proceed = await this.showDemoIntroduction(); if (!proceed) { return; } const demoPath = await this.createDemoWorkspace(); if (!demoPath) { return; } const markedFile = await this.generateDemoMarkers(demoPath); await this.extractDemoFiles(markedFile, demoPath); this.showDemoCompletion(demoPath, markedFile); } catch (error) { this.logger.error("Error during demo:", error); vscode4.window.showErrorMessage(`Demo failed: ${error}`); } } async showDemoIntroduction() { const message = `\u{1F3AF} Welcome to LookAtni File Markers Quick Demo! This demo will: \u2022 Create sample files with different extensions \u2022 Generate a marked file using invisible Unicode markers \u2022 Extract files back from the marked content \u2022 Show validation and statistics The demo creates a temporary folder and won't affect your current workspace. Ready to see LookAtni in action?`; const choice = await vscode4.window.showInformationMessage( message, { modal: true }, "Start Demo", "Cancel" ); return choice === "Start Demo"; } async createDemoWorkspace() { const workspaceRoot = vscode4.workspace.workspaceFolders?.[0]?.uri.fsPath; const demoParent = workspaceRoot || require("os").homedir(); const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-"); const demoPath = path4.join(demoParent, `lookatni-demo-${timestamp}`); try { fs6.mkdirSync(demoPath, { recursive: true }); await this.createSampleFiles(demoPath); this.logger.success(`Created demo workspace: ${demoPath}`); return demoPath; } catch (error) { this.logger.error("Failed to create demo workspace:", error); vscode4.window.showErrorMessage("Failed to create demo workspace"); return void 0; } } async createSampleFiles(demoPath) { const samples = { "README.md": `# LookAtni Demo Project This is a sample project to demonstrate the LookAtni File Markers system. ## Features - File marker system with invisible Unicode characters - CLI tools for extraction and generation - VS Code extension for seamless integration Generated on: ${(/* @__PURE__ */ new Date()).toISOString()} `, "package.json": JSON.stringify({ name: "lookatni-demo", version: "1.0.0", description: "Demo project for LookAtni File Markers", main: "index.js", scripts: { start: "node index.js", test: 'echo "No tests yet"' }, keywords: ["lookatni", "demo", "markers"], author: "LookAtni File Markers", license: "MIT" }, null, 2), "src/index.js": `// LookAtni Demo - Main File console.log('\u{1F680} Welcome to LookAtni File Markers!'); function demonstrateMarkers() { console.log('This file will be marked with invisible Unicode characters'); console.log('Each file gets unique markers for easy extraction'); const features = [ 'Unique file markers', 'CLI tools', 'VS Code extension', 'Validation system' ]; features.forEach((feature, index) => { console.log(\`\${index + 1}. \${feature}\`); }); } demonstrateMarkers(); `, "src/utils.js": `// Utility functions for LookAtni Demo export function formatBytes(bytes) { const sizes = ['B', 'KB', 'MB', 'GB']; if (bytes === 0) return '0 B'; const i = Math.floor(Math.log(bytes) / Math.log(1024)); return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i]; } export function validateFilename(filename) { const invalidChars = /[<>:"|?*]/; return !invalidChars.test(filename); } export function createMarker(filename) { const FS_CHAR = String.fromCharCode(28); return \`//\${FS_CHAR}/ \${filename} /\${FS_CHAR}//\`; } `, "config/settings.json": JSON.stringify({ demo: true, version: "1.0.0", features: { markers: true, validation: true, extraction: true }, created: (/* @__PURE__ */ new Date()).toISOString() }, null, 2), "docs/guide.md": `# LookAtni Usage Guide ## What are markers? Markers use invisible Unicode characters (ASCII 28 File Separator). They are completely invisible but allow for perfect file organization. ## Example: \`\`\` // Markers are invisible - shown as \u241C for demonstration only //\u241C/ src/example.js /\u241C// console.log('This content belongs to src/example.js'); const demo = true; //\u241C/ README.md /\u241C// # Another File This content belongs to README.md \`\`\` ## Benefits: - Easy file identification - Preserves directory structure - Supports validation - Works with any text format ` }; for (const [filePath, content] of Object.entries(samples)) { const fullPath = path4.join(demoPath, filePath); const dir = path4.dirname(fullPath); if (!fs6.existsSync(dir)) { fs6.mkdirSync(dir, { recursive: true }); } fs6.writeFileSync(fullPath, content, "utf-8"); } } async generateDemoMarkers(demoPath) { const generator = new MarkerGenerator(this.logger); const outputFile = path4.join(demoPath, "demo-marked.txt"); const options = { maxFileSize: 1e3, // 1MB limit excludePatterns: ["node_modules", ".git", "*.log"] }; this.outputChannel.appendLine("\n=== GENERATING MARKERS ==="); const results = await vscode4.window.withProgress({ location: vscode4.ProgressLocation.Notification, title: "Generating demo markers...", cancellable: false }, async (progress) => { return generator.generateMarkers(demoPath, outputFile, options, (current, total) => { progress.report({ increment: 100 / total, message: `Processing ${current}/${total} files...` }); }); }); this.outputChannel.appendLine(`\u2705 Generated markers for ${results.totalFiles} files`); this.outputChannel.appendLine(`\u{1F4CA} Total size: ${this.formatBytes(results.totalBytes)}`); this.outputChannel.appendLine(`\u{1F4C4} Output: ${outputFile}`); return outputFile; } async extractDemoFiles(markedFile, demoPath) { const parser = new MarkerParser(this.logger); const extractPath = path4.join(demoPath, "extracted"); this.outputChannel.appendLine("\n=== EXTRACTING FILES ==="); const results = await vscode4.window.withProgress({ location: vscode4.ProgressLocation.Notification, title: "Extracting demo files...", cancellable: false }, async () => { return parser.extractFiles(markedFile, extractPath, { overwrite: true, createDirs: true, dryRun: false }); }); this.outputChannel.appendLine(`\u2705 Extracted ${results.extractedFiles.length} files`); this.outputChannel.appendLine(`\u{1F4C1} Output directory: ${extractPath}`); if (results.errors.length > 0) { this.outputChannel.appendLine("\u26A0\uFE0F Errors:"); results.errors.forEach((error) => { this.outputChannel.appendLine(` \u2022 ${error}`); }); } } async showDemoCompletion(demoPath, markedFile) { this.outputChannel.appendLine("\n=== DEMO COMPLETED ==="); this.outputChannel.appendLine(`\u{1F4C1} Demo workspace: ${demoPath}`); this.outputChannel.appendLine(`\u{1F4C4} Marked file: ${markedFile}`); this.outputChannel.appendLine(`\u{1F4C2} Extracted files: ${path4.join(demoPath, "extracted")}`); this.outputChannel.appendLine(""); this.outputChannel.appendLine("\u{1F3AF} Demo completed successfully!"); this.outputChannel.appendLine("You can now explore the generated files and see how LookAtni works."); this.outputChannel.show(); const choice = await vscode4.window.showInformationMessage( "\u{1F389} Demo completed! What would you like to do?", "Open Demo Folder", "View Marked File", "Open Extracted Files", "Close" ); switch (choice) { case "Open Demo Folder": vscode4.commands.executeCommand("revealFileInOS", vscode4.Uri.file(demoPath)); break; case "View Marked File": const document = await vscode4.workspace.openTextDocument(markedFile); vscode4.window.showTextDocument(document); break; case "Open Extracted Files": const extractedPath = path4.join(demoPath, "extracted"); vscode4.commands.executeCommand("revealFileInOS", vscode4.Uri.file(extractedPath)); break; } } formatBytes(bytes) { if (bytes === 0) { return "0 B"; } const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; } }; // src/commands/showStatistics.ts var vscode5 = __toESM(require("vscode")); var fs7 = __toESM(require("fs")); var path5 = __toESM(require("path")); var ShowStatisticsCommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.showStatistics"; async execute(uri, data) { this.logger.info("\u{1F4CA} Showing LookAtni statistics..."); try { if (data) { this.showPassedStatistics(data); } else { const markedFile = await this.getMarkedFile(uri); if (!markedFile) { return; } await this.analyzeMarkedFile(markedFile); } } catch (error) { this.logger.error("Error showing statistics:", error); vscode5.window.showErrorMessage(`Statistics failed: ${error}`); } } async getMarkedFile(uri) { if (uri && uri.fsPath && fs7.existsSync(uri.fsPath)) { return uri.fsPath; } const activeEditor = vscode5.window.activeTextEditor; if (activeEditor && activeEditor.document.fileName.endsWith(".txt")) { return activeEditor.document.fileName; } const fileUri = await vscode5.window.showOpenDialog({ canSelectMany: false, canSelectFiles: true, canSelectFolders: false, openLabel: "Select marked file for statistics", filters: { "LookAtni Files": ["txt", "md"], "All Files": ["*"] } }); return fileUri?.[0]?.fsPath; } async analyzeMarkedFile(filePath) { const parser = new MarkerParser(this.logger); const results = await vscode5.window.withProgress({ location: vscode5.ProgressLocation.Notification, title: "Analyzing marked file...", cancellable: false }, async () => { return parser.parseMarkedFile(filePath); }); const fileStats = fs7.statSync(filePath); const statistics = { filePath, fileSize: fileStats.size, created: fileStats.birthtime, modified: fileStats.mtime, ...results }; this.showDetailedStatistics(statistics); } showPassedStatistics(data) { this.outputChannel.clear(); this.outputChannel.appendLine("=== LOOKATNI STATISTICS (FROM OPERATION) ==="); if (data.sourceFolder) { this.outputChannel.appendLine(`\u{1F4C1} Source: ${data.sourceFolder}`); } if (data.totalFiles !== void 0) { this.outputChannel.appendLine(`\u{1F4C4} Files processed: ${data.totalFiles}`); } if (data.totalBytes !== void 0) { this.outputChannel.appendLine(`\u{1F4CA} Total size: ${this.formatBytes(data.totalBytes)}`); } if (data.skippedFiles && data.skippedFiles.length > 0) { this.outputChannel.appendLine(`\u23ED\uFE0F Files skipped: ${data.skippedFiles.length}`); } if (data.fileTypes) { this.outputChannel.appendLine("\n=== FILE TYPES ==="); const sortedTypes = Object.entries(data.fileTypes).sort(([, a], [, b]) => b - a); sortedTypes.forEach(([ext, count]) => { const percentage = (count / data.totalFiles * 100).toFixed(1); this.outputChannel.appendLine(`${ext || "no-ext"}: ${count} files (${percentage}%)`); }); } this.outputChannel.show(); } showDetailedStatistics(stats) { this.outputChannel.clear(); this.outputChannel.appendLine("=== DETAILED LOOKATNI STATISTICS ==="); this.outputChannel.appendLine(`\u{1F4C4} File: ${path5.basename(stats.filePath)}`); this.outputChannel.appendLine(`\u{1F4C1} Path: ${stats.filePath}`); this.outputChannel.appendLine(`\u{1F4CA} File size: ${this.formatBytes(stats.fileSize)}`); this.outputChannel.appendLine(`\u{1F550} Created: ${stats.created.toLocaleString()}`); this.outputChannel.appendLine(`\u{1F551} Modified: ${stats.modified.toLocaleString()}`); this.outputChannel.appendLine(""); this.outputChannel.appendLine("=== MARKER ANALYSIS ==="); this.outputChannel.appendLine(`\u{1F3F7}\uFE0F Total markers: ${stats.totalMarkers}`); this.outputChannel.appendLine(`\u{1F4C1} Unique files: ${stats.totalFiles}`); this.outputChannel.appendLine(`\u{1F4CA} Content size: ${this.formatBytes(stats.totalBytes)}`); if (stats.totalFiles > 0) { const avgSize = stats.totalBytes / stats.totalFiles; this.outputChannel.appendLine(`\u{1F4CF} Average file size: ${this.formatBytes(avgSize)}`); } if (stats.errors && stats.errors.length > 0) { this.outputChannel.appendLine(""); this.outputChannel.appendLine("=== ERRORS FOUND ==="); stats.errors.forEach((error) => { this.outputChannel.appendLine(`\u274C Line ${error.line}: ${error.message}`); }); } if (stats.markers && stats.markers.length > 0) { const fileTypes = {}; const fileSizes = []; stats.markers.forEach((marker) => { const ext = path5.extname(marker.filename).toLowerCase() || "no-extension"; fileTypes[ext] = (fileTypes[ext] || 0) + 1; fileSizes.push({ name: marker.filename, size: Buffer.byteLength(marker.content, "utf-8") }); }); this.outputChannel.appendLine(""); this.outputChannel.appendLine("=== FILE TYPES DISTRIBUTION ==="); const sortedTypes = Object.entries(fileTypes).sort(([, a], [, b]) => b - a); sortedTypes.forEach(([ext, count]) => { const percentage = (count / stats.totalFiles * 100).toFixed(1); this.outputChannel.appendLine(`${ext}: ${count} files (${percentage}%)`); }); this.outputChannel.appendLine(""); this.outputChannel.appendLine("=== LARGEST FILES ==="); const largestFiles = fileSizes.sort((a, b) => b.size - a.size).slice(0, 10); largestFiles.forEach((file) => { this.outputChannel.appendLine(`\u{1F4C4} ${file.name}: ${this.formatBytes(file.size)}`); }); } this.outputChannel.appendLine(""); this.outputChannel.appendLine("=== EFFICIENCY METRICS ==="); if (stats.totalMarkers > 0) { const markerOverhead = stats.totalMarkers * 20; const efficiency = (stats.totalBytes / (stats.totalBytes + markerOverhead) * 100).toFixed(1); this.outputChannel.appendLine(`\u26A1 Content efficiency: ${efficiency}%`); } const compressionRatio = stats.fileSize > 0 ? (stats.totalBytes / stats.fileSize * 100).toFixed(1) : "0"; this.outputChannel.appendLine(`\u{1F5DC}\uFE0F Content ratio: ${compressionRatio}%`); this.outputChannel.show(); vscode5.window.showInformationMessage( `\u{1F4CA} Statistics: ${stats.totalMarkers} markers, ${stats.totalFiles} files, ${this.formatBytes(stats.totalBytes)}` ); } formatBytes(bytes) { if (bytes === 0) { return "0 B"; } const k = 1024; const sizes = ["B", "KB", "MB", "GB"]; const i = Math.floor(Math.log(bytes) / Math.log(k)); return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + " " + sizes[i]; } }; // src/commands/openCLI.ts var vscode6 = __toESM(require("vscode")); var fs8 = __toESM(require("fs")); var path6 = __toESM(require("path")); var OpenCLICommand = class { constructor(context, logger, outputChannel) { this.context = context; this.logger = logger; this.outputChannel = outputChannel; } commandId = "lookatni-file-markers.openCLI"; // ASCII 28 (File Separator) character for invisible markers FS_CHAR = String.fromCharCode(28); async execute() { this.logger.info("\u{1F527} Opening LookAtni CLI tools..."); try { const cliPath = await this.findCLITools(); if (cliPath) { await this.showCLIOptions(cliPath); } else { await this.offerCLIInstallation(); } } catch (error) { this.logger.error("Error opening CLI:", error); vscode6.window.showErrorMessage(`CLI access failed: ${error}`); } } async findCLITools() { const possiblePaths = [ // Check extension resources path6.join(this.context.extensionPath, "cli"), // Check workspace vscode6.workspace.workspaceFolders?.[0]?.uri.fsPath + "/cli", // Check parent directories vscode6.workspace.workspaceFolders?.[0]?.uri.fsPath + "/../kortex", // Check common locations path6.join(require("os").homedir(), ".lookatni"), "/usr/local/bin/lookatni", "C:\\Program Files\\LookAtni" ]; for (const possiblePath of possiblePaths) { if (possiblePath && fs8.existsSync(possiblePath)) { const packageJsonPath = path6.join(possiblePath, "package.json"); if (fs8.existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8")); if (packageJson.scripts && packageJson.scripts["lookatni:extract"] && packageJson.scripts["lookatni:generate"]) { this.logger.info(`Found LookAtni npm scripts at: ${possiblePath}`); return possiblePath; } } catch (error) { } } } } return void 0; } async showCLIOptions(cliPath) { const choice = await vscode6.window.showQuickPick([ { label: "\u{1F4C2} Open CLI Folder", description: "Open the CLI tools folder in file explorer", action: "folder" }, { label: "\u26A1 Extract Files", description: "Run npm script: lookatni:extract", action: "extract" }, { label: "\u{1F3F7}\uFE0F Generate Markers", description: "Run npm script: lookatni:generate", action: "generate" }, { label: "\u{1F9EA} Run Tests", description: "Run npm script: lookatni:test", action: "test" }, { label: "\u{1F3AF} Quick Demo", description: "Run npm script: lookatni:demo", action: "demo" }, { label: "\u{1F4D6} Show Help", description: "Display CLI usage information", action: "help" } ], { placeHolder: "Choose CLI action" }); if (!choice) { return; } switch (choice.action) { case "folder": vscode6.commands.executeCommand("revealFileInOS", vscode6.Uri.file(cliPath)); break; case "extract": await this.runNpmScript("lookatni:extract"); break; case "generate": await this.runNpmScript("lookatni:generate"); break; case "test": await this.runNpmScript("lookatni:test"); break; case "demo": await this.runNpmScript("lookatni:demo"); break; case "help": this.showCLIHelp(cliPath); break; } } async runNpmScript(scriptName) { const workspaceRoot = vscode6.workspace.workspaceFolders?.[0]?.uri.fsPath; if (!workspaceRoot) { vscode6.window.showErrorMessage("No workspace folder found. Please open a folder first."); return; } const packageJsonPath = path6.join(workspaceRoot, "package.json"); if (!fs8.existsSync(packageJsonPath)) { vscode6.window.showErrorMessage("No package.json found in workspace root."); return; } let args = ""; if (scriptName === "lookatni:extract" || scriptName === "lookatni:generate") { const inputArgs = await vscode6.window.showInputBox({ prompt: `Enter arguments for ${scriptName} (or leave empty for interactive mode