Skip to content

Commit

Permalink
Add XML paste feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Thomas-McKanna-Test committed Oct 14, 2024
1 parent 75edf43 commit 777ae11
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 10 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ node_modules
.vscode-test/
*.vsix
foobar
.DS_Store
.DS_Store
docs/
prompt
1 change: 1 addition & 0 deletions .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ vsc-extension-quickstart.md
**/*.ts
**/.vscode-test.*
!node_modules/ignore/**
!node_modules/fast-xml-parser/**
demo.gif
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Copy file contents in XML format for LLM prompts effortlessly.
- **Configurable Shortcuts**: Quickly refresh the file tree or copy files using customizable keyboard shortcuts.
- **Git Ignore Support**: Automatically ignores files and directories specified in your .gitignore.
- **Selection History**: Quickly switch between sets of previously selected files.
- **Paste XML Response**: If XML is pasted into the editor in the same format as the copied output, the files will automatically be updated or created in your workspace. This allows for rapid iteration. See [recommended workflow](#recommended-workflow) for more information.

## Installation

Expand Down Expand Up @@ -59,13 +60,29 @@ Copy file contents in XML format for LLM prompts effortlessly.
- Windows/Linux: `Ctrl+Shift+C`
- macOS: `Cmd+Shift+C`

### Paste XML Response:

- To update or create files in your workspace from LLM-outputted XML content, use the following keyboard shortcut while the extension UI is active:
- Windows/Linux: `Ctrl+V`
- macOS: `Cmd+V`

It is recommended that if you use this feature you have a custom system message (see section below) to ensure that the XML content is correctly formatted. Here is an example of such a system message:

```
Always provide full code listings. You only need to include files that have changed. I repeat, ALWAYS provide full code listings. Your output should be in XML format (in a code block) that mirrors the input format (that is, `<files>` element with a list of `<file>` in the interior).
```

### Include System Message:

1. Go to Settings (`Ctrl+,` or `Cmd+,` on macOS).
2. Navigate to Extensions > Files2Prompt.
3. Enter your custom system message in the System Message field.
4. When you copy files, this message will be included at the top of the XML output.

See recommended system message above.



## Configuration

### Customizing Keyboard Shortcuts
Expand Down Expand Up @@ -106,3 +123,21 @@ This is a custom system message for LLM prompts.
</file>
</files>
```

## Recommended Workflow

The Files2Prompt extension allows for a rapid iterative workflow when working with an LLM. It is first recommended that you set the system message setting to that described [above](#include-system-message). This will ensure that the XML content is correctly formatted when pasted back into the editor.

Next, create a file called `prompt` in the root of your repository. You will want to add this to `.gitignore`. This file acts as a scratchpad for you latest prompt.

Optionally, create a directory called `docs` (also in `.gitignore`) where you can paste documentation into files for any technologies or code that is related to your project.

Then, a single iteration of the workflow might look like this:

1. Alter the `prompt` file with you desired next state.
2. Open the Files2Prompt view and select `prompt` and any other files that are relevant to your prompt. Also select any relevant documentation files from the `docs` directory.
3. Copy your XML prompt with the keyboard shortcut or button.
4. Paste the content into the LLM and submit.
5. Copy the LLM contents, which should be in XML format if you followed the system message instructions.
6. With the Files2Prompt view open, paste the LLM content with the keyboard shortcut. This will update the file contents or make brand new files.
7. Loop back to step 1.
31 changes: 29 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 14 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"displayName": "Files2Prompt",
"icon": "./files2prompt-icon.webp",
"description": "Copy file contents for LLM prompts",
"version": "1.3.0",
"version": "1.4.0",
"publisher": "thomas-mckanna",
"keywords": [
"files",
Expand Down Expand Up @@ -84,6 +84,10 @@
"light": "resources/light/forward.svg",
"dark": "resources/dark/forward.svg"
}
},
{
"command": "files2prompt.pasteXml",
"title": "Paste XML"
}
],
"menus": {
Expand Down Expand Up @@ -136,6 +140,13 @@
"mac": "cmd+shift+c",
"when": "files2PromptView.active && files2PromptView.visible && focusedView == 'files2PromptView'",
"title": "Files2Prompt: Clear All Checks"
},
{
"command": "files2prompt.pasteXml",
"key": "ctrl+v",
"mac": "cmd+v",
"when": "files2PromptView.active && files2PromptView.visible && focusedView == 'files2PromptView'",
"title": "Files2Prompt: Paste XML"
}
],
"configuration": {
Expand Down Expand Up @@ -163,6 +174,7 @@
"typescript": "^4.5.4"
},
"dependencies": {
"fast-xml-parser": "^4.5.0",
"ignore": "^6.0.2"
}
}
}
83 changes: 78 additions & 5 deletions src/extension.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
// src/extension.ts

import * as vscode from "vscode";
import * as fs from "fs";
import * as path from "path";
import { FileTreeProvider } from "./fileTreeProvider";
import { XMLParser } from 'fast-xml-parser';

export function activate(context: vscode.ExtensionContext) {
const workspaceFolders = vscode.workspace.workspaceFolders;
Expand Down Expand Up @@ -52,8 +51,10 @@ export function activate(context: vscode.ExtensionContext) {
let finalOutput = xmlOutput;

if (systemMessage && systemMessage.trim() !== "") {
// Escape any ']]>' sequences in systemMessage
const safeSystemMessage = systemMessage.replace(/]]>/g, ']]]]><![CDATA[>');
finalOutput =
`<systemMessage>\n<![CDATA[\n${systemMessage}\n]]>\n</systemMessage>\n` +
`<systemMessage>\n<![CDATA[\n${safeSystemMessage}\n]]>\n</systemMessage>\n` +
finalOutput;
}

Expand Down Expand Up @@ -95,6 +96,10 @@ export function activate(context: vscode.ExtensionContext) {
"No next selection to go forward to."
);
}
}),
vscode.commands.registerCommand("files2prompt.pasteXml", async () => {
const clipboardContent = await vscode.env.clipboard.readText();
await processXmlContent(clipboardContent);
})
);

Expand All @@ -120,20 +125,23 @@ export function activate(context: vscode.ExtensionContext) {
}
}

export function deactivate() {}
export function deactivate() { }

// Helper function to generate XML output
async function generateXmlOutput(filePaths: string[]): Promise<string> {
let xmlContent = "";

for (const filePath of filePaths) {
const content = fs.readFileSync(filePath, "utf-8");
// Escape any ']]>' sequences in file content
const safeContent = content.replace(/]]>/g, ']]]]><![CDATA[>');

const fileName = path.relative(
vscode.workspace.workspaceFolders![0].uri.fsPath,
filePath
);

xmlContent += `<file name="${fileName}">\n<![CDATA[\n${content}\n]]>\n</file>\n`;
xmlContent += `<file name="${fileName}">\n<![CDATA[\n${safeContent}\n]]>\n</file>\n`;
}

return `<files>\n${xmlContent}</files>`;
Expand All @@ -149,3 +157,68 @@ function arraysEqual(a: string[], b: string[]): boolean {
}
return true;
}

// Function to process XML content from clipboard
async function processXmlContent(xmlContent: string) {
// Extract only XML content
const xmlOnly = extractXmlContent(xmlContent);
if (!xmlOnly) {
vscode.window.showErrorMessage("No valid XML content found in clipboard.");
return;
}

const parser = new XMLParser({
ignoreAttributes: false,
allowBooleanAttributes: true,
parseTagValue: true,
parseAttributeValue: false,
trimValues: false,
cdataPropName: '__cdata',
tagValueProcessor: (val, tagName) => val, // Prevent default processing
});

let jsonObj;
try {
jsonObj = parser.parse(xmlOnly);
} catch (error) {
vscode.window.showErrorMessage("Error parsing XML content from clipboard.");
return;
}

if (!jsonObj || !jsonObj.files || !jsonObj.files.file) {
vscode.window.showErrorMessage("No <file> elements found in XML content.");
return;
}

const files = Array.isArray(jsonObj.files.file) ? jsonObj.files.file : [jsonObj.files.file];

for (const fileObj of files) {
const fileName = fileObj['@_name'];
let fileContent = '';

if (fileObj['__cdata']) {
fileContent = fileObj['__cdata'];
} else {
// If no CDATA, get text content
fileContent = fileObj['#text'] || '';
}

if (fileName) {
const filePath = path.join(vscode.workspace.workspaceFolders![0].uri.fsPath, fileName);
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
await fs.promises.writeFile(filePath, fileContent, 'utf8');
}
}

vscode.window.showInformationMessage("Files have been updated successfully.");
}

function extractXmlContent(text: string): string | null {
const xmlMatch = text.match(/<files>[\s\S]*<\/files>/);
if (xmlMatch) {
return xmlMatch[0];
}

// If no <files> tag found, return null
return null;
}

0 comments on commit 777ae11

Please sign in to comment.