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
JavaScript
"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