modular code
This commit is contained in:
305
src/extension.ts
305
src/extension.ts
@@ -1,251 +1,22 @@
|
|||||||
// The module 'vscode' contains the VS Code extensibility API
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as fs from 'fs';
|
|
||||||
|
|
||||||
// Custom TreeItem for files and folders
|
// Import our modular components
|
||||||
class FileTreeItem extends vscode.TreeItem {
|
import { PrompterSettings, DEFAULT_SETTINGS } from './models/settings';
|
||||||
constructor(
|
import { FileTreeItem, SettingTreeItem } from './models/treeItems';
|
||||||
public readonly resourceUri: vscode.Uri,
|
import { PrompterTreeProvider } from './providers/prompterTreeProvider';
|
||||||
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
|
import { PromptGenerator } from './utils/promptGenerator';
|
||||||
public readonly isDirectory: boolean
|
|
||||||
) {
|
|
||||||
super(resourceUri, collapsibleState);
|
|
||||||
this.contextValue = isDirectory ? 'directory' : 'file';
|
|
||||||
this.tooltip = resourceUri.fsPath;
|
|
||||||
this.description = isDirectory ? 'folder' : path.extname(resourceUri.fsPath);
|
|
||||||
this.checkboxState = vscode.TreeItemCheckboxState.Unchecked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Settings interface
|
/**
|
||||||
interface PrompterSettings {
|
* This method is called when the extension is activated
|
||||||
xmlEditsEnabled: boolean;
|
*/
|
||||||
includeLineNumbers: boolean;
|
|
||||||
includeComments: boolean;
|
|
||||||
tokenCalculationEnabled: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Custom TreeItem for settings
|
|
||||||
class SettingTreeItem extends vscode.TreeItem {
|
|
||||||
constructor(
|
|
||||||
public readonly label: string,
|
|
||||||
public readonly settingKey: keyof PrompterSettings,
|
|
||||||
public readonly checked: boolean
|
|
||||||
) {
|
|
||||||
super(label, vscode.TreeItemCollapsibleState.None);
|
|
||||||
this.checkboxState = checked ? vscode.TreeItemCheckboxState.Checked : vscode.TreeItemCheckboxState.Unchecked;
|
|
||||||
this.contextValue = 'setting';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TreeView provider for the sidebar
|
|
||||||
class PrompterTreeProvider implements vscode.TreeDataProvider<FileTreeItem | SettingTreeItem> {
|
|
||||||
private _onDidChangeTreeData: vscode.EventEmitter<FileTreeItem | SettingTreeItem | undefined | null | void> = new vscode.EventEmitter<FileTreeItem | SettingTreeItem | undefined | null | void>();
|
|
||||||
readonly onDidChangeTreeData: vscode.Event<FileTreeItem | SettingTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;
|
|
||||||
private selectedFiles: Set<string> = new Set();
|
|
||||||
private settings: PrompterSettings = {
|
|
||||||
xmlEditsEnabled: false,
|
|
||||||
includeLineNumbers: false,
|
|
||||||
includeComments: true,
|
|
||||||
tokenCalculationEnabled: true
|
|
||||||
};
|
|
||||||
private showingSettings: boolean = false;
|
|
||||||
|
|
||||||
constructor(private workspaceRoot: string | undefined) {}
|
|
||||||
|
|
||||||
// Toggle between file view and settings view
|
|
||||||
toggleSettingsView(): void {
|
|
||||||
this.showingSettings = !this.showingSettings;
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTreeItem(element: FileTreeItem | SettingTreeItem): vscode.TreeItem {
|
|
||||||
// Return the element as is if it's a SettingTreeItem
|
|
||||||
if (element instanceof SettingTreeItem) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle FileTreeItem
|
|
||||||
const fileItem = element as FileTreeItem;
|
|
||||||
if (fileItem.resourceUri && this.selectedFiles.has(fileItem.resourceUri.fsPath)) {
|
|
||||||
fileItem.checkboxState = vscode.TreeItemCheckboxState.Checked;
|
|
||||||
} else {
|
|
||||||
fileItem.checkboxState = vscode.TreeItemCheckboxState.Unchecked;
|
|
||||||
}
|
|
||||||
return fileItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
async getChildren(element?: FileTreeItem | SettingTreeItem): Promise<(FileTreeItem | SettingTreeItem)[]> {
|
|
||||||
// If we're showing settings, return settings items
|
|
||||||
if (this.showingSettings && !element) {
|
|
||||||
return this.getSettingsItems();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise show files
|
|
||||||
if (!this.workspaceRoot) {
|
|
||||||
vscode.window.showInformationMessage('No workspace folder is opened');
|
|
||||||
return Promise.resolve([]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element) {
|
|
||||||
// Make sure we're dealing with a FileTreeItem that has a resourceUri
|
|
||||||
if (!(element instanceof SettingTreeItem) && element.resourceUri) {
|
|
||||||
return this.getFilesInDirectory(element.resourceUri.fsPath);
|
|
||||||
}
|
|
||||||
return Promise.resolve([]);
|
|
||||||
} else {
|
|
||||||
return this.getFilesInDirectory(this.workspaceRoot);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getFilesInDirectory(dirPath: string): Promise<FileTreeItem[]> {
|
|
||||||
try {
|
|
||||||
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
|
|
||||||
return Promise.all(files.map(async ([name, type]) => {
|
|
||||||
const filePath = path.join(dirPath, name);
|
|
||||||
const uri = vscode.Uri.file(filePath);
|
|
||||||
const isDirectory = (type & vscode.FileType.Directory) !== 0;
|
|
||||||
|
|
||||||
return new FileTreeItem(
|
|
||||||
uri,
|
|
||||||
isDirectory ? vscode.TreeItemCollapsibleState.Collapsed : vscode.TreeItemCollapsibleState.None,
|
|
||||||
isDirectory
|
|
||||||
);
|
|
||||||
}));
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error reading directory ${dirPath}:`, error);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleSelection(item: FileTreeItem): void {
|
|
||||||
const filePath = item.resourceUri.fsPath;
|
|
||||||
console.log('Toggle selection called for:', filePath);
|
|
||||||
if (this.selectedFiles.has(filePath)) {
|
|
||||||
this.removeFromSelection(item);
|
|
||||||
} else {
|
|
||||||
this.addToSelection(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addToSelection(item: FileTreeItem): void {
|
|
||||||
const filePath = item.resourceUri.fsPath;
|
|
||||||
this.selectedFiles.add(filePath);
|
|
||||||
console.log('Added file to selection, count:', this.selectedFiles.size);
|
|
||||||
this._onDidChangeTreeData.fire();
|
|
||||||
}
|
|
||||||
|
|
||||||
removeFromSelection(item: FileTreeItem): void {
|
|
||||||
const filePath = item.resourceUri.fsPath;
|
|
||||||
this.selectedFiles.delete(filePath);
|
|
||||||
console.log('Removed file from selection, count:', this.selectedFiles.size);
|
|
||||||
this._onDidChangeTreeData.fire();
|
|
||||||
}
|
|
||||||
|
|
||||||
getSelectedFiles(): Set<string> {
|
|
||||||
console.log('getSelectedFiles called, count:', this.selectedFiles.size);
|
|
||||||
return this.selectedFiles;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get settings items for the tree view
|
|
||||||
private getSettingsItems(): SettingTreeItem[] {
|
|
||||||
return [
|
|
||||||
new SettingTreeItem('XML Edits', 'xmlEditsEnabled', this.settings.xmlEditsEnabled),
|
|
||||||
new SettingTreeItem('Include Line Numbers', 'includeLineNumbers', this.settings.includeLineNumbers),
|
|
||||||
new SettingTreeItem('Include Comments', 'includeComments', this.settings.includeComments),
|
|
||||||
new SettingTreeItem('Token Calculation', 'tokenCalculationEnabled', this.settings.tokenCalculationEnabled)
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update a setting value
|
|
||||||
updateSetting(key: keyof PrompterSettings, value: boolean): void {
|
|
||||||
this.settings[key] = value;
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
isShowingSettings(): boolean {
|
|
||||||
return this.showingSettings;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleXmlEdits(): void {
|
|
||||||
this.settings.xmlEditsEnabled = !this.settings.xmlEditsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
isXmlEditsEnabled(): boolean {
|
|
||||||
return this.settings.xmlEditsEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
getSettings(): PrompterSettings {
|
|
||||||
return this.settings;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateSettings(newSettings: PrompterSettings): void {
|
|
||||||
this.settings = { ...newSettings };
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh(): void {
|
|
||||||
this._onDidChangeTreeData.fire();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Utility function to estimate tokens in text
|
|
||||||
function estimateTokens(text: string): number {
|
|
||||||
// Rough estimation: Split by whitespace and punctuation
|
|
||||||
// This is a simple approximation, actual token count may vary by model
|
|
||||||
const words = text.split(/[\s\p{P}]+/u).filter(Boolean);
|
|
||||||
return Math.ceil(words.length * 1.3); // Add 30% overhead for special tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to read file content
|
|
||||||
async function readFileContent(filePath: string): Promise<string> {
|
|
||||||
try {
|
|
||||||
const readData = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath));
|
|
||||||
return Buffer.from(readData).toString('utf8');
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Error reading file ${filePath}:`, error);
|
|
||||||
return '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Function to generate XML prompt
|
|
||||||
function generateXMLPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
|
|
||||||
const xmlParts = ['<?xml version="1.0" encoding="UTF-8"?>', '<prompt>'];
|
|
||||||
|
|
||||||
// Add files section
|
|
||||||
xmlParts.push(' <files>');
|
|
||||||
for (const [path, { content, tokens }] of files) {
|
|
||||||
xmlParts.push(' <file>');
|
|
||||||
xmlParts.push(` <path>${path}</path>`);
|
|
||||||
xmlParts.push(` <tokens>${tokens}</tokens>`);
|
|
||||||
xmlParts.push(` <content><![CDATA[${content}]]></content>`);
|
|
||||||
xmlParts.push(' </file>');
|
|
||||||
}
|
|
||||||
xmlParts.push(' </files>');
|
|
||||||
|
|
||||||
// Add options based on settings
|
|
||||||
xmlParts.push(' <options>');
|
|
||||||
if (settings.xmlEditsEnabled) {
|
|
||||||
xmlParts.push(' <xml_edits>true</xml_edits>');
|
|
||||||
}
|
|
||||||
if (settings.includeLineNumbers) {
|
|
||||||
xmlParts.push(' <include_line_numbers>true</include_line_numbers>');
|
|
||||||
}
|
|
||||||
if (!settings.includeComments) {
|
|
||||||
xmlParts.push(' <exclude_comments>true</exclude_comments>');
|
|
||||||
}
|
|
||||||
xmlParts.push(' </options>');
|
|
||||||
|
|
||||||
xmlParts.push('</prompt>');
|
|
||||||
return xmlParts.join('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is called when your extension is activated
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
console.log('Congratulations, your extension "prompter" is now active!');
|
console.log('Prompter extension is now active!');
|
||||||
|
|
||||||
|
// Get the workspace folder
|
||||||
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
|
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri?.fsPath;
|
||||||
|
|
||||||
|
// Create the tree data provider
|
||||||
const prompterTreeProvider = new PrompterTreeProvider(workspaceRoot);
|
const prompterTreeProvider = new PrompterTreeProvider(workspaceRoot);
|
||||||
|
|
||||||
// Register the TreeView with checkbox support
|
// Register the TreeView with checkbox support
|
||||||
@@ -293,14 +64,16 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
// Create copy button
|
// Create copy button
|
||||||
const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
const copyButton = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right);
|
||||||
copyButton.text = "$(clippy) Copy";
|
copyButton.text = "$(clippy) Copy";
|
||||||
copyButton.tooltip = "Generate and copy XML prompt";
|
copyButton.tooltip = "Generate and copy prompt";
|
||||||
copyButton.command = 'prompter.generatePrompt';
|
copyButton.command = 'prompter.generatePrompt';
|
||||||
copyButton.show();
|
copyButton.show();
|
||||||
|
|
||||||
// Register command to toggle file selection
|
// Register command to toggle file selection
|
||||||
let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => {
|
let toggleSelectionCommand = vscode.commands.registerCommand('prompter.toggleSelection', (item: FileTreeItem) => {
|
||||||
|
if (item.resourceUri) {
|
||||||
console.log('Toggle selection command triggered for:', item.resourceUri.fsPath);
|
console.log('Toggle selection command triggered for:', item.resourceUri.fsPath);
|
||||||
prompterTreeProvider.toggleSelection(item);
|
prompterTreeProvider.toggleSelection(item);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Register command to toggle XML edits
|
// Register command to toggle XML edits
|
||||||
@@ -317,9 +90,6 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
settingsButton.command = 'prompter.openSettings';
|
settingsButton.command = 'prompter.openSettings';
|
||||||
settingsButton.show();
|
settingsButton.show();
|
||||||
|
|
||||||
// Settings panel state
|
|
||||||
let settingsPanel: vscode.WebviewPanel | undefined = undefined;
|
|
||||||
|
|
||||||
// Register command to open settings
|
// Register command to open settings
|
||||||
let openSettingsCommand = vscode.commands.registerCommand('prompter.openSettings', () => {
|
let openSettingsCommand = vscode.commands.registerCommand('prompter.openSettings', () => {
|
||||||
prompterTreeProvider.toggleSettingsView();
|
prompterTreeProvider.toggleSettingsView();
|
||||||
@@ -335,40 +105,29 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => {
|
let generatePromptCommand = vscode.commands.registerCommand('prompter.generatePrompt', async () => {
|
||||||
const selectedFiles = prompterTreeProvider.getSelectedFiles();
|
const selectedFiles = prompterTreeProvider.getSelectedFiles();
|
||||||
console.log('Generate prompt command triggered, selected files:', [...selectedFiles]);
|
console.log('Generate prompt command triggered, selected files:', [...selectedFiles]);
|
||||||
|
|
||||||
if (selectedFiles.size === 0) {
|
if (selectedFiles.size === 0) {
|
||||||
vscode.window.showInformationMessage('Please select files first');
|
vscode.window.showInformationMessage('Please select files first');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let totalTokens = 0;
|
try {
|
||||||
const fileContents = new Map<string, { content: string; tokens: number }>();
|
// Generate the prompt using our utility class
|
||||||
|
const promptText = await PromptGenerator.generatePrompt(
|
||||||
// Process each selected file
|
selectedFiles,
|
||||||
for (const filePath of selectedFiles) {
|
prompterTreeProvider.getSettings()
|
||||||
const content = await readFileContent(filePath);
|
|
||||||
const tokens = estimateTokens(content);
|
|
||||||
totalTokens += tokens;
|
|
||||||
fileContents.set(filePath, { content, tokens });
|
|
||||||
|
|
||||||
// Show individual file token count
|
|
||||||
vscode.window.showInformationMessage(
|
|
||||||
`File: ${filePath}\nEstimated tokens: ${tokens}`
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Show total token count
|
|
||||||
vscode.window.showInformationMessage(
|
|
||||||
`Total estimated tokens for ${selectedFiles.size} files: ${totalTokens}`
|
|
||||||
);
|
|
||||||
|
|
||||||
// Generate XML prompt
|
|
||||||
const xmlPrompt = generateXMLPrompt(fileContents, prompterTreeProvider.getSettings());
|
|
||||||
|
|
||||||
// Copy to clipboard
|
// Copy to clipboard
|
||||||
await vscode.env.clipboard.writeText(xmlPrompt);
|
await vscode.env.clipboard.writeText(promptText);
|
||||||
vscode.window.showInformationMessage('XML prompt copied to clipboard!');
|
vscode.window.showInformationMessage('Prompt copied to clipboard!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error generating prompt:', error);
|
||||||
|
vscode.window.showErrorMessage('Error generating prompt');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add all disposables to context subscriptions
|
||||||
context.subscriptions.push(
|
context.subscriptions.push(
|
||||||
treeView,
|
treeView,
|
||||||
xmlEditsButton,
|
xmlEditsButton,
|
||||||
@@ -381,5 +140,9 @@ export function activate(context: vscode.ExtensionContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is called when your extension is deactivated
|
/**
|
||||||
export function deactivate() {}
|
* This method is called when the extension is deactivated
|
||||||
|
*/
|
||||||
|
export function deactivate() {
|
||||||
|
// Clean up resources when the extension is deactivated
|
||||||
|
}
|
||||||
15
src/models/settings.ts
Normal file
15
src/models/settings.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// Settings interface for the Prompter extension
|
||||||
|
export interface PrompterSettings {
|
||||||
|
xmlEditsEnabled: boolean;
|
||||||
|
includeLineNumbers: boolean;
|
||||||
|
includeComments: boolean;
|
||||||
|
tokenCalculationEnabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default settings values
|
||||||
|
export const DEFAULT_SETTINGS: PrompterSettings = {
|
||||||
|
xmlEditsEnabled: false,
|
||||||
|
includeLineNumbers: false,
|
||||||
|
includeComments: true,
|
||||||
|
tokenCalculationEnabled: true
|
||||||
|
};
|
||||||
32
src/models/treeItems.ts
Normal file
32
src/models/treeItems.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { PrompterSettings } from './settings';
|
||||||
|
|
||||||
|
// Tree item for files in the explorer
|
||||||
|
export class FileTreeItem extends vscode.TreeItem {
|
||||||
|
constructor(
|
||||||
|
public readonly resourceUri: vscode.Uri,
|
||||||
|
public readonly collapsibleState: vscode.TreeItemCollapsibleState
|
||||||
|
) {
|
||||||
|
super(resourceUri, collapsibleState);
|
||||||
|
this.tooltip = resourceUri.fsPath;
|
||||||
|
this.description = path.basename(resourceUri.fsPath);
|
||||||
|
this.contextValue = 'file';
|
||||||
|
|
||||||
|
// Set the checkbox state to unchecked by default
|
||||||
|
this.checkboxState = vscode.TreeItemCheckboxState.Unchecked;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tree item for settings in the settings view
|
||||||
|
export class SettingTreeItem extends vscode.TreeItem {
|
||||||
|
constructor(
|
||||||
|
public readonly label: string,
|
||||||
|
public readonly settingKey: keyof PrompterSettings,
|
||||||
|
public readonly checked: boolean
|
||||||
|
) {
|
||||||
|
super(label, vscode.TreeItemCollapsibleState.None);
|
||||||
|
this.checkboxState = checked ? vscode.TreeItemCheckboxState.Checked : vscode.TreeItemCheckboxState.Unchecked;
|
||||||
|
this.contextValue = 'setting';
|
||||||
|
}
|
||||||
|
}
|
||||||
171
src/providers/prompterTreeProvider.ts
Normal file
171
src/providers/prompterTreeProvider.ts
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { FileTreeItem, SettingTreeItem } from '../models/treeItems';
|
||||||
|
import { PrompterSettings, DEFAULT_SETTINGS } from '../models/settings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tree provider for the Prompter extension
|
||||||
|
* Handles both file browsing and settings views
|
||||||
|
*/
|
||||||
|
export class PrompterTreeProvider implements vscode.TreeDataProvider<FileTreeItem | SettingTreeItem> {
|
||||||
|
private _onDidChangeTreeData: vscode.EventEmitter<FileTreeItem | SettingTreeItem | undefined | null | void> =
|
||||||
|
new vscode.EventEmitter<FileTreeItem | SettingTreeItem | undefined | null | void>();
|
||||||
|
|
||||||
|
readonly onDidChangeTreeData: vscode.Event<FileTreeItem | SettingTreeItem | undefined | null | void> =
|
||||||
|
this._onDidChangeTreeData.event;
|
||||||
|
|
||||||
|
private selectedFiles: Set<string> = new Set();
|
||||||
|
private settings: PrompterSettings = { ...DEFAULT_SETTINGS };
|
||||||
|
private showingSettings: boolean = false;
|
||||||
|
|
||||||
|
constructor(private workspaceRoot: string | undefined) {}
|
||||||
|
|
||||||
|
// Toggle between file view and settings view
|
||||||
|
toggleSettingsView(): void {
|
||||||
|
this.showingSettings = !this.showingSettings;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(element: FileTreeItem | SettingTreeItem): vscode.TreeItem {
|
||||||
|
// Return the element as is if it's a SettingTreeItem
|
||||||
|
if (element instanceof SettingTreeItem) {
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle FileTreeItem
|
||||||
|
const fileItem = element as FileTreeItem;
|
||||||
|
if (fileItem.resourceUri && this.selectedFiles.has(fileItem.resourceUri.fsPath)) {
|
||||||
|
fileItem.checkboxState = vscode.TreeItemCheckboxState.Checked;
|
||||||
|
} else {
|
||||||
|
fileItem.checkboxState = vscode.TreeItemCheckboxState.Unchecked;
|
||||||
|
}
|
||||||
|
return fileItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildren(element?: FileTreeItem | SettingTreeItem): Promise<(FileTreeItem | SettingTreeItem)[]> {
|
||||||
|
// If we're showing settings, return settings items
|
||||||
|
if (this.showingSettings && !element) {
|
||||||
|
return this.getSettingsItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise show files
|
||||||
|
if (!this.workspaceRoot) {
|
||||||
|
vscode.window.showInformationMessage('No workspace folder is opened');
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
// Make sure we're dealing with a FileTreeItem that has a resourceUri
|
||||||
|
if (!(element instanceof SettingTreeItem) && element.resourceUri) {
|
||||||
|
return this.getFilesInDirectory(element.resourceUri.fsPath);
|
||||||
|
}
|
||||||
|
return Promise.resolve([]);
|
||||||
|
} else {
|
||||||
|
return this.getFilesInDirectory(this.workspaceRoot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getFilesInDirectory(dirPath: string): Promise<FileTreeItem[]> {
|
||||||
|
try {
|
||||||
|
const files = await vscode.workspace.fs.readDirectory(vscode.Uri.file(dirPath));
|
||||||
|
|
||||||
|
return files.map(([name, type]) => {
|
||||||
|
const filePath = path.join(dirPath, name);
|
||||||
|
const uri = vscode.Uri.file(filePath);
|
||||||
|
|
||||||
|
return new FileTreeItem(
|
||||||
|
uri,
|
||||||
|
type === vscode.FileType.Directory
|
||||||
|
? vscode.TreeItemCollapsibleState.Collapsed
|
||||||
|
: vscode.TreeItemCollapsibleState.None
|
||||||
|
);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reading directory: ${dirPath}`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh the tree view
|
||||||
|
refresh(): void {
|
||||||
|
this._onDidChangeTreeData.fire();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add a file to the selection
|
||||||
|
addToSelection(item: FileTreeItem): void {
|
||||||
|
if (item.resourceUri) {
|
||||||
|
this.selectedFiles.add(item.resourceUri.fsPath);
|
||||||
|
console.log(`Added ${item.resourceUri.fsPath} to selection`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove a file from the selection
|
||||||
|
removeFromSelection(item: FileTreeItem): void {
|
||||||
|
if (item.resourceUri) {
|
||||||
|
this.selectedFiles.delete(item.resourceUri.fsPath);
|
||||||
|
console.log(`Removed ${item.resourceUri.fsPath} from selection`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle a file's selection status
|
||||||
|
toggleSelection(item: FileTreeItem): void {
|
||||||
|
if (item.resourceUri) {
|
||||||
|
const filePath = item.resourceUri.fsPath;
|
||||||
|
if (this.selectedFiles.has(filePath)) {
|
||||||
|
this.removeFromSelection(item);
|
||||||
|
} else {
|
||||||
|
this.addToSelection(item);
|
||||||
|
}
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the selected files
|
||||||
|
getSelectedFiles(): Set<string> {
|
||||||
|
console.log('getSelectedFiles called, count:', this.selectedFiles.size);
|
||||||
|
return this.selectedFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get settings items for the tree view
|
||||||
|
private getSettingsItems(): SettingTreeItem[] {
|
||||||
|
return [
|
||||||
|
new SettingTreeItem('XML Edits', 'xmlEditsEnabled', this.settings.xmlEditsEnabled),
|
||||||
|
new SettingTreeItem('Include Line Numbers', 'includeLineNumbers', this.settings.includeLineNumbers),
|
||||||
|
new SettingTreeItem('Include Comments', 'includeComments', this.settings.includeComments),
|
||||||
|
new SettingTreeItem('Token Calculation', 'tokenCalculationEnabled', this.settings.tokenCalculationEnabled)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update a setting value
|
||||||
|
updateSetting(key: keyof PrompterSettings, value: boolean): void {
|
||||||
|
this.settings[key] = value;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if showing settings view
|
||||||
|
isShowingSettings(): boolean {
|
||||||
|
return this.showingSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toggle XML edits setting
|
||||||
|
toggleXmlEdits(): void {
|
||||||
|
this.settings.xmlEditsEnabled = !this.settings.xmlEditsEnabled;
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if XML edits are enabled
|
||||||
|
isXmlEditsEnabled(): boolean {
|
||||||
|
return this.settings.xmlEditsEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all settings
|
||||||
|
getSettings(): PrompterSettings {
|
||||||
|
return { ...this.settings };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all settings at once
|
||||||
|
updateSettings(newSettings: PrompterSettings): void {
|
||||||
|
this.settings = { ...newSettings };
|
||||||
|
this.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/utils/fileReader.ts
Normal file
21
src/utils/fileReader.ts
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility for reading file contents
|
||||||
|
*/
|
||||||
|
export class FileReader {
|
||||||
|
/**
|
||||||
|
* Read the content of a file
|
||||||
|
* @param filePath Path to the file to read
|
||||||
|
* @returns The file content as a string
|
||||||
|
*/
|
||||||
|
static async readFileContent(filePath: string): Promise<string> {
|
||||||
|
try {
|
||||||
|
const readData = await vscode.workspace.fs.readFile(vscode.Uri.file(filePath));
|
||||||
|
return Buffer.from(readData).toString('utf8');
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error reading file ${filePath}:`, error);
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
135
src/utils/promptGenerator.ts
Normal file
135
src/utils/promptGenerator.ts
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { PrompterSettings } from '../models/settings';
|
||||||
|
import { FileReader } from './fileReader';
|
||||||
|
import { TokenEstimator } from './tokenEstimator';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for generating prompts from selected files
|
||||||
|
*/
|
||||||
|
export class PromptGenerator {
|
||||||
|
/**
|
||||||
|
* Generate a prompt from the selected files
|
||||||
|
* @param selectedFiles Set of file paths to include in the prompt
|
||||||
|
* @param settings Settings to apply when generating the prompt
|
||||||
|
* @returns The generated prompt text
|
||||||
|
*/
|
||||||
|
static async generatePrompt(selectedFiles: Set<string>, settings: PrompterSettings): Promise<string> {
|
||||||
|
if (selectedFiles.size === 0) {
|
||||||
|
throw new Error('No files selected');
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalTokens = 0;
|
||||||
|
const fileContents = new Map<string, { content: string; tokens: number }>();
|
||||||
|
|
||||||
|
// Process each selected file
|
||||||
|
for (const filePath of selectedFiles) {
|
||||||
|
const content = await FileReader.readFileContent(filePath);
|
||||||
|
const tokens = TokenEstimator.estimateTokens(content);
|
||||||
|
totalTokens += tokens;
|
||||||
|
fileContents.set(filePath, { content, tokens });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate the prompt based on settings
|
||||||
|
if (settings.xmlEditsEnabled) {
|
||||||
|
return this.generateXMLPrompt(fileContents, settings);
|
||||||
|
} else {
|
||||||
|
return this.generatePlainPrompt(fileContents, settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a plain text prompt
|
||||||
|
* @param files Map of file paths to content and token counts
|
||||||
|
* @param settings Settings to apply
|
||||||
|
* @returns Plain text prompt
|
||||||
|
*/
|
||||||
|
private static generatePlainPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
|
||||||
|
let promptText = '';
|
||||||
|
let totalTokenCount = 0;
|
||||||
|
|
||||||
|
for (const [filePath, { content, tokens }] of files) {
|
||||||
|
const fileName = path.basename(filePath);
|
||||||
|
totalTokenCount += tokens;
|
||||||
|
|
||||||
|
// Add the file to the prompt
|
||||||
|
promptText += `File: ${fileName}\n`;
|
||||||
|
|
||||||
|
// Add line numbers if enabled
|
||||||
|
if (settings.includeLineNumbers) {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
promptText += `${i + 1}: ${lines[i]}\n`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
promptText += `${content}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
promptText += '\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add token count if enabled
|
||||||
|
if (settings.tokenCalculationEnabled) {
|
||||||
|
promptText += `\nEstimated token count: ${totalTokenCount}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return promptText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate an XML formatted prompt
|
||||||
|
* @param files Map of file paths to content and token counts
|
||||||
|
* @param settings Settings to apply
|
||||||
|
* @returns XML formatted prompt
|
||||||
|
*/
|
||||||
|
private static generateXMLPrompt(files: Map<string, { content: string; tokens: number }>, settings: PrompterSettings): string {
|
||||||
|
const xmlParts = ['<?xml version="1.0" encoding="UTF-8"?>', '<prompt>'];
|
||||||
|
|
||||||
|
// Add files section
|
||||||
|
xmlParts.push(' <files>');
|
||||||
|
for (const [filePath, { content, tokens }] of files) {
|
||||||
|
const extension = path.extname(filePath);
|
||||||
|
let language = extension.substring(1); // Remove the dot
|
||||||
|
|
||||||
|
// Handle special cases
|
||||||
|
if (extension === '.js' || extension === '.jsx') {
|
||||||
|
language = 'javascript';
|
||||||
|
} else if (extension === '.ts' || extension === '.tsx') {
|
||||||
|
language = 'typescript';
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlParts.push(' <file>');
|
||||||
|
xmlParts.push(` <path>${path.basename(filePath)}</path>`);
|
||||||
|
xmlParts.push(` <type>${language}</type>`);
|
||||||
|
xmlParts.push(` <tokens>${tokens}</tokens>`);
|
||||||
|
|
||||||
|
// Format content based on settings
|
||||||
|
let formattedContent = content;
|
||||||
|
if (settings.includeLineNumbers) {
|
||||||
|
const lines = content.split('\n');
|
||||||
|
formattedContent = lines.map((line, i) => `${i + 1}: ${line}`).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
xmlParts.push(` <content><![CDATA[${formattedContent}]]></content>`);
|
||||||
|
xmlParts.push(' </file>');
|
||||||
|
}
|
||||||
|
xmlParts.push(' </files>');
|
||||||
|
|
||||||
|
// Add options based on settings
|
||||||
|
xmlParts.push(' <options>');
|
||||||
|
if (settings.includeLineNumbers) {
|
||||||
|
xmlParts.push(' <include_line_numbers>true</include_line_numbers>');
|
||||||
|
}
|
||||||
|
if (!settings.includeComments) {
|
||||||
|
xmlParts.push(' <exclude_comments>true</exclude_comments>');
|
||||||
|
}
|
||||||
|
if (settings.tokenCalculationEnabled) {
|
||||||
|
xmlParts.push(` <total_tokens>${Array.from(files.values()).reduce((sum, { tokens }) => sum + tokens, 0)}</total_tokens>`);
|
||||||
|
}
|
||||||
|
xmlParts.push(' </options>');
|
||||||
|
|
||||||
|
xmlParts.push('</prompt>');
|
||||||
|
return xmlParts.join('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/utils/tokenEstimator.ts
Normal file
16
src/utils/tokenEstimator.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
/**
|
||||||
|
* Utility for estimating token counts in text
|
||||||
|
*/
|
||||||
|
export class TokenEstimator {
|
||||||
|
/**
|
||||||
|
* Estimate the number of tokens in a text string
|
||||||
|
* @param text The text to estimate tokens for
|
||||||
|
* @returns Estimated token count
|
||||||
|
*/
|
||||||
|
static estimateTokens(text: string): number {
|
||||||
|
// Rough estimation: Split by whitespace and punctuation
|
||||||
|
// This is a simple approximation, actual token count may vary by model
|
||||||
|
const words = text.split(/[\s\p{P}]+/u).filter(Boolean);
|
||||||
|
return Math.ceil(words.length * 1.3); // Add 30% overhead for special tokens
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user