Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5c07136b36 | |||
| 580f9b6e02 | |||
| 68bc7ba620 | |||
| dd48db0ed3 | |||
| d7c00addd5 | |||
| b34119c4df | |||
| d098291c90 | |||
| ed9e126a4a | |||
| a8010eec15 | |||
| 8cd1ea305c | |||
| 762264b030 | |||
| 48d5369196 | |||
| aab840db2d | |||
| 3526005e1c | |||
| 0ef8c66d54 | |||
| cd9173c802 | |||
| 0c8a185df7 | |||
| 0f0719c596 | |||
| d9397d870f | |||
| 1704a7de7a | |||
| 616cd2e39b | |||
| 222f90bbea | |||
| a7a4488848 | |||
| 8e72c1392a |
+6
-7
@@ -69,9 +69,9 @@ We haven't created prerequisite steps for building on Linux yet, but you can fol
|
||||
|
||||
### Build instructions
|
||||
|
||||
Before building Void, please follow the prerequisite steps above for your operating system. Also make sure you've already built the Void extension (or just run `cd ./extensions/void && npm install && npm run build && npm run compile && cd ../..`).
|
||||
Before building Void, please follow the prerequisite steps above for your operating system. Also make sure you've already built and compiled the Void extension (or just run `cd ./extensions/void && npm install && npm run build && npm run compile && cd ../..`).
|
||||
|
||||
First, open `void/` in VSCode. Then:
|
||||
To build Void, first open `void/` in VSCode. Then:
|
||||
|
||||
1. Install all dependencies.
|
||||
|
||||
@@ -97,8 +97,7 @@ If you ran `npm run watch`, the build is done when you see something like this:
|
||||
<!-- 3. Press <kbd>Ctrl+Shift+B</kbd> to start the build process. -->
|
||||
|
||||
1. In a new terminal, run `./scripts/code.sh` (Mac/Linux) or `/.scripts/code.bat` (Windows). This should open up the built IDE!
|
||||
|
||||
To see new changes without restarting the build, press <kbd>Ctrl+Shift+P</kbd> and run "Reload Window" inside the new window.
|
||||
You can always press <kbd>Ctrl+Shift+P</kbd> and run "Reload Window" inside the new window to see changes without re-building.
|
||||
|
||||
Now that you're set up, feel free to check out our [Issues](https://github.com/voideditor/void/issues) page!
|
||||
|
||||
@@ -119,9 +118,7 @@ Here are the full options: `vscode-{win32-ia32 | win32-x64 | darwin-x64 | darwin
|
||||
|
||||
## Roadmap
|
||||
|
||||
Here are the most important topics on our Roadmap. More ⭐'s = more important.
|
||||
|
||||
These sometimes get outdated - please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues.
|
||||
Here are the most important topics on our Roadmap. More ⭐'s = more important. Please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues.
|
||||
|
||||
## ⭐⭐⭐ Make History work well.
|
||||
|
||||
@@ -151,6 +148,8 @@ We're always glad to talk about new ideas, help you get set up, and make sure yo
|
||||
|
||||
Please submit a pull request once you've made a change. You don't need to submit an issue.
|
||||
|
||||
Please don't use AI to write your PR 🙂.
|
||||
|
||||
# Relevant files
|
||||
|
||||
We keep track of all the files we've changed with Void so it's easy to rebase:
|
||||
|
||||
@@ -1,22 +1,16 @@
|
||||
# Welcome to Void.
|
||||
|
||||
Void is the open-source Cursor alternative. This repo contains the full sourcecode for Void. We have a [waitlist](https://voideditor.com/email) for downloading the official release, but you can build and develop Void right now.
|
||||
|
||||
Void is the open-source Cursor alternative.
|
||||
|
||||
If you're new, welcome! Feel free to check out our [Project board](https://github.com/orgs/voideditor/projects/2/views/3) for the most pressing Issues to work on, and see [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md) for instructions on building and running Void.
|
||||
|
||||
This repo contains the full sourcecode for Void. We have a [waitlist](https://voideditor.com/email) for downloading the official release, but you can build and develop Void right now.
|
||||
If you're new, welcome!
|
||||
|
||||
## Contributing
|
||||
|
||||
To build and run Void, see [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
|
||||
To build and run Void, follow the steps in [`CONTRIBUTING.md`](https://github.com/voideditor/void/blob/main/CONTRIBUTING.md).
|
||||
|
||||
## Reference
|
||||
|
||||
Void is a fork of the of [vscode](https://github.com/microsoft/vscode) repository.
|
||||
|
||||
For some useful links we've compiled see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
|
||||
|
||||
Void is a fork of the of [vscode](https://github.com/microsoft/vscode) repository. For some useful links on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
|
||||
|
||||
## Support
|
||||
Feel free to reach out in our [Discord](https://discord.gg/RSNjgaugJs) or contact us via email.
|
||||
|
||||
Vendored
+3
-2
@@ -10,7 +10,8 @@
|
||||
"type": "extensionHost",
|
||||
"request": "launch",
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
"--extensionDevelopmentPath=${workspaceFolder}",
|
||||
"--enable-proposed-api=void.void",
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceFolder}/out/**/*.js"
|
||||
@@ -18,4 +19,4 @@
|
||||
"preLaunchTask": "${defaultBuildTask}"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -49,6 +49,11 @@ const convertTSXtoJS = async ({ from, to }) => {
|
||||
to: 'dist/webviews/ctrlk/index.js',
|
||||
})
|
||||
|
||||
await convertTSXtoJS({
|
||||
from: 'src/webviews/diffline/index.tsx',
|
||||
to: 'dist/webviews/diffline/index.js',
|
||||
})
|
||||
|
||||
// convert tailwind to css
|
||||
await convertTailwindToCSS({
|
||||
from: 'src/webviews/styles.css',
|
||||
|
||||
Generated
+19
@@ -10,6 +10,7 @@
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.29.2",
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@rrweb/types": "^2.0.0-alpha.17",
|
||||
"@types/diff": "^5.2.2",
|
||||
@@ -737,6 +738,16 @@
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@google/generative-ai": {
|
||||
"version": "0.21.0",
|
||||
"resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz",
|
||||
"integrity": "sha512-7XhUbtnlkSEZK15kN3t+tzIMxsbKm/dSkKBFalj+20NvPKe1kBY7mR2P7vuijEn+f06z5+A8bVGKO0v39cr6Wg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
|
||||
@@ -6013,6 +6024,14 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/monaco-editor": {
|
||||
"version": "0.52.0",
|
||||
"resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.52.0.tgz",
|
||||
"integrity": "sha512-OeWhNpABLCeTqubfqLMXGsqf6OmPU6pHM85kF3dhy6kq5hnhuVS1p3VrEW/XhWHc71P2tHyS5JFySD8mgs1crw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"editorInsets"
|
||||
],
|
||||
"activationEvents": [],
|
||||
"main": "./out/extension/extension.js",
|
||||
"contributes": {
|
||||
@@ -111,6 +114,7 @@
|
||||
"devDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.29.2",
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@google/generative-ai": "^0.21.0",
|
||||
"@monaco-editor/react": "^4.6.0",
|
||||
"@rrweb/types": "^2.0.0-alpha.17",
|
||||
"@types/diff": "^5.2.2",
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
import OpenAI from 'openai';
|
||||
import { Ollama } from 'ollama/browser'
|
||||
import { Content, GoogleGenerativeAI, GoogleGenerativeAIError, GoogleGenerativeAIFetchError } from '@google/generative-ai';
|
||||
import { VoidConfig } from '../webviews/common/contextForConfig'
|
||||
|
||||
export type AbortRef = { current: (() => void) | null }
|
||||
@@ -37,8 +38,6 @@ type SendLLMMessageFnTypeExternal = (params: {
|
||||
abortRef: AbortRef,
|
||||
}) => void
|
||||
|
||||
|
||||
|
||||
const parseMaxTokensStr = (maxTokensStr: string) => {
|
||||
// parse the string but only if the full string is a valid number, eg parseInt('100abc') should return NaN
|
||||
let int = isNaN(Number(maxTokensStr)) ? undefined : parseInt(maxTokensStr)
|
||||
@@ -65,7 +64,7 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
|
||||
system: systemMessage,
|
||||
messages: anthropicMessages,
|
||||
model: voidConfig.anthropic.model,
|
||||
max_tokens: parseInt(voidConfig.default.maxTokens),
|
||||
max_tokens: parseMaxTokensStr(voidConfig.default.maxTokens)!, // this might be undefined, but it will just throw an error for the user
|
||||
});
|
||||
|
||||
let did_abort = false
|
||||
@@ -103,8 +102,62 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
|
||||
return { abort }
|
||||
};
|
||||
|
||||
// Gemini
|
||||
const sendGeminiMsg: SendLLMMessageFnTypeInternal = async ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||
|
||||
let didAbort = false
|
||||
let fullText = ''
|
||||
|
||||
abortRef.current = () => {
|
||||
didAbort = true
|
||||
}
|
||||
|
||||
const genAI = new GoogleGenerativeAI(voidConfig.gemini.apikey);
|
||||
const model = genAI.getGenerativeModel({ model: voidConfig.gemini.model });
|
||||
|
||||
// remove system messages that get sent to Gemini
|
||||
// str of all system messages
|
||||
let systemMessage = messages
|
||||
.filter(msg => msg.role === 'system')
|
||||
.map(msg => msg.content)
|
||||
.join('\n');
|
||||
|
||||
// Convert messages to Gemini format
|
||||
const geminiMessages: Content[] = messages
|
||||
.filter(msg => msg.role !== 'system')
|
||||
.map((msg, i) => ({
|
||||
parts: [{ text: msg.content }],
|
||||
role: msg.role === 'assistant' ? 'model' : 'user'
|
||||
}))
|
||||
|
||||
model.generateContentStream({ contents: geminiMessages, systemInstruction: systemMessage, })
|
||||
.then(async response => {
|
||||
abortRef.current = () => {
|
||||
// response.stream.return(fullText)
|
||||
didAbort = true;
|
||||
}
|
||||
for await (const chunk of response.stream) {
|
||||
if (didAbort) return;
|
||||
const newText = chunk.text();
|
||||
fullText += newText;
|
||||
onText(newText, fullText);
|
||||
}
|
||||
onFinalMessage(fullText);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error instanceof GoogleGenerativeAIFetchError) {
|
||||
if (error.status === 400) {
|
||||
onError('Invalid API key.');
|
||||
}
|
||||
else {
|
||||
onError(`${error.name}:\n${error.message}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
onError(error);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// OpenAI, OpenRouter, OpenAICompatible
|
||||
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||
@@ -168,7 +221,7 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||
onError('Invalid API key.');
|
||||
}
|
||||
else {
|
||||
onError(error.message);
|
||||
onError(`${error.name}:\n${error.message}`);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -178,7 +231,6 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
|
||||
|
||||
};
|
||||
|
||||
|
||||
// Ollama
|
||||
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||
|
||||
@@ -196,7 +248,7 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
|
||||
model: voidConfig.ollama.model,
|
||||
messages: messages,
|
||||
stream: true,
|
||||
options: { num_predict: parseInt(voidConfig.default.maxTokens) } // this is max_tokens
|
||||
options: { num_predict: parseMaxTokensStr(voidConfig.default.maxTokens) } // this is max_tokens
|
||||
})
|
||||
.then(async stream => {
|
||||
abortRef.current = () => {
|
||||
@@ -220,8 +272,6 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Greptile
|
||||
// https://docs.greptile.com/api-reference/query
|
||||
// https://docs.greptile.com/quickstart#sample-response-streamed
|
||||
@@ -236,7 +286,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||
didAbort = true
|
||||
}
|
||||
|
||||
|
||||
fetch('https://api.greptile.com/v2/query', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
@@ -291,8 +340,6 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
|
||||
if (!voidConfig) return;
|
||||
|
||||
@@ -306,6 +353,8 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText,
|
||||
case 'openRouter':
|
||||
case 'openAICompatible':
|
||||
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||
case 'gemini':
|
||||
return sendGeminiMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||
case 'ollama':
|
||||
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
|
||||
case 'greptile':
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { PartialVoidConfig } from '../webviews/common/contextForConfig'
|
||||
|
||||
|
||||
|
||||
// a selection is a frozen snapshot
|
||||
type CodeSelection = { selectionStr: string, selectionRange: vscode.Range, filePath: vscode.Uri }
|
||||
type CodeSelection = { selectionStr: string, filePath: vscode.Uri }
|
||||
|
||||
type File = { filepath: vscode.Uri, content: string }
|
||||
|
||||
@@ -35,9 +32,16 @@ type Diff = {
|
||||
lenses: vscode.CodeLens[],
|
||||
} & BaseDiff
|
||||
|
||||
type Inset = {
|
||||
text: string,
|
||||
height: number,
|
||||
line: number,
|
||||
insetObj: vscode.WebviewEditorInset,
|
||||
}
|
||||
|
||||
// editor -> sidebar
|
||||
type MessageToSidebar = (
|
||||
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
|
||||
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor. selection and path are frozen snapshots
|
||||
| { type: 'ctrl+k', selection: CodeSelection }
|
||||
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
|
||||
| { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig }
|
||||
@@ -91,6 +95,7 @@ type ChatMessage =
|
||||
export {
|
||||
BaseDiff, Diff,
|
||||
DiffArea,
|
||||
Inset,
|
||||
CodeSelection,
|
||||
File,
|
||||
MessageFromSidebar,
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import { DiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './common/shared_types';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { AbortRef } from './common/sendLLMMessage';
|
||||
import { DiffProvider } from './extension/DiffProvider';
|
||||
import { SidebarWebviewProvider } from './extension/providers/SidebarWebviewProvider';
|
||||
import { getVoidConfigFromPartial } from './webviews/common/contextForConfig';
|
||||
import { applyDiffLazily } from './extension/ctrlL';
|
||||
import { readFileContentOfUri } from './extension/extensionLib/readFileContentOfUri';
|
||||
|
||||
// this comes from vscode.proposed.editorInsets.d.ts
|
||||
declare module 'vscode' {
|
||||
export interface WebviewEditorInset {
|
||||
readonly editor: vscode.TextEditor;
|
||||
readonly line: number;
|
||||
readonly height: number;
|
||||
readonly webview: vscode.Webview;
|
||||
readonly onDidDispose: Event<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
export namespace window {
|
||||
export function createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): WebviewEditorInset;
|
||||
}
|
||||
}
|
||||
|
||||
const roundRangeToLines = (selection: vscode.Selection) => {
|
||||
return new vscode.Range(selection.start.line, 0, selection.end.line, Number.MAX_SAFE_INTEGER)
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
||||
// 1. Mount the chat sidebar
|
||||
const sidebarWebviewProvider = new SidebarWebviewProvider(context);
|
||||
context.subscriptions.push(
|
||||
vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, sidebarWebviewProvider, { webviewOptions: { retainContextWhenHidden: true } })
|
||||
);
|
||||
|
||||
|
||||
|
||||
// 2. ctrl+l
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('void.ctrl+l', () => {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) return
|
||||
|
||||
|
||||
// const inset = vscode.window.createWebviewTextEditorInset(editor, 10, 10, {})
|
||||
// inset.webview.html = `
|
||||
// <html>
|
||||
// <body style="pointer-events:none;">Hello World!</body>
|
||||
// </html>
|
||||
// `;
|
||||
|
||||
|
||||
// show the sidebar
|
||||
vscode.commands.executeCommand('workbench.view.extension.voidViewContainer');
|
||||
// vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar
|
||||
|
||||
// get the range of the selection
|
||||
const selectionRange = roundRangeToLines(editor.selection);
|
||||
|
||||
// get the text the user is selecting
|
||||
const selectionStr = editor.document.getText(selectionRange);
|
||||
|
||||
// get the file the user is in
|
||||
const filePath = editor.document.uri;
|
||||
|
||||
// send message to the webview (Sidebar.tsx)
|
||||
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
|
||||
})
|
||||
);
|
||||
|
||||
// 2.5: ctrl+k
|
||||
context.subscriptions.push(
|
||||
vscode.commands.registerCommand('void.ctrl+k', () => {
|
||||
console.log('CTRLK PRESSED')
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) return
|
||||
|
||||
// get the range of the selection
|
||||
const selectionRange = roundRangeToLines(editor.selection);
|
||||
|
||||
// get the text the user is selecting
|
||||
const selectionStr = editor.document.getText(selectionRange);
|
||||
|
||||
// get the file the user is in
|
||||
const filePath = editor.document.uri;
|
||||
|
||||
// send message to the webview (Sidebar.tsx)
|
||||
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+k', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Show an approve/reject codelens above each change
|
||||
const diffProvider = new DiffProvider();
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', diffProvider));
|
||||
|
||||
// 4. Add approve/reject commands
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.acceptDiff', async (params) => {
|
||||
diffProvider.acceptDiff(params)
|
||||
}));
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.rejectDiff', async (params) => {
|
||||
diffProvider.rejectDiff(params)
|
||||
}));
|
||||
|
||||
// 5. Receive messages from sidebar
|
||||
sidebarWebviewProvider.webview.then(
|
||||
webview => {
|
||||
|
||||
// top navigation bar commands
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.startNewThread', async () => {
|
||||
webview.postMessage({ type: 'startNewThread' } satisfies MessageToSidebar)
|
||||
}))
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.toggleThreadSelector', async () => {
|
||||
webview.postMessage({ type: 'toggleThreadSelector' } satisfies MessageToSidebar)
|
||||
}))
|
||||
context.subscriptions.push(vscode.commands.registerCommand('void.toggleSettings', async () => {
|
||||
webview.postMessage({ type: 'toggleSettings' } satisfies MessageToSidebar)
|
||||
}));
|
||||
|
||||
// Receive messages in the extension from the sidebar webview (messages are sent using `postMessage`)
|
||||
webview.onDidReceiveMessage(async (m: MessageFromSidebar) => {
|
||||
|
||||
const abortApplyRef: AbortRef = { current: null }
|
||||
|
||||
if (m.type === 'requestFiles') {
|
||||
|
||||
// get contents of all file paths
|
||||
const files = await Promise.all(
|
||||
m.filepaths.map(async (filepath) => ({ filepath, content: await readFileContentOfUri(filepath) }))
|
||||
)
|
||||
|
||||
// send contents to webview
|
||||
webview.postMessage({ type: 'files', files, } satisfies MessageToSidebar)
|
||||
|
||||
}
|
||||
else if (m.type === 'applyChanges') {
|
||||
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) {
|
||||
vscode.window.showInformationMessage('No active editor!')
|
||||
return
|
||||
}
|
||||
// create an area to show diffs
|
||||
const partialDiffArea: Omit<DiffArea, 'diffareaid'> = {
|
||||
startLine: 0, // in ctrl+L the start and end lines are the full document
|
||||
endLine: editor.document.lineCount,
|
||||
originalStartLine: 0,
|
||||
originalEndLine: editor.document.lineCount,
|
||||
sweepIndex: null,
|
||||
}
|
||||
const diffArea = diffProvider.createDiffArea(editor.document.uri, partialDiffArea, await readFileContentOfUri(editor.document.uri))
|
||||
|
||||
const docUri = editor.document.uri
|
||||
const fileStr = await readFileContentOfUri(docUri)
|
||||
const voidConfig = getVoidConfigFromPartial(context.globalState.get('partialVoidConfig') ?? {})
|
||||
|
||||
await applyDiffLazily({ docUri, oldFileStr: fileStr, diffRepr: m.diffRepr, voidConfig, diffProvider, diffArea, abortRef: abortApplyRef })
|
||||
}
|
||||
else if (m.type === 'getPartialVoidConfig') {
|
||||
const partialVoidConfig = context.globalState.get('partialVoidConfig') ?? {}
|
||||
webview.postMessage({ type: 'partialVoidConfig', partialVoidConfig } satisfies MessageToSidebar)
|
||||
}
|
||||
else if (m.type === 'persistPartialVoidConfig') {
|
||||
const partialVoidConfig = m.partialVoidConfig
|
||||
context.globalState.update('partialVoidConfig', partialVoidConfig)
|
||||
}
|
||||
else if (m.type === 'getAllThreads') {
|
||||
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
|
||||
webview.postMessage({ type: 'allThreads', threads } satisfies MessageToSidebar)
|
||||
}
|
||||
else if (m.type === 'persistThread') {
|
||||
const threads: ChatThreads = context.workspaceState.get('allThreads') ?? {}
|
||||
const updatedThreads: ChatThreads = { ...threads, [m.thread.id]: m.thread }
|
||||
context.workspaceState.update('allThreads', updatedThreads)
|
||||
}
|
||||
else if (m.type === 'getDeviceId') {
|
||||
let deviceId = context.globalState.get('void_deviceid')
|
||||
if (!deviceId || typeof deviceId !== 'string') {
|
||||
deviceId = uuidv4()
|
||||
context.globalState.update('void_deviceid', deviceId)
|
||||
}
|
||||
webview.postMessage({ type: 'deviceId', deviceId: deviceId as string } satisfies MessageToSidebar)
|
||||
}
|
||||
else {
|
||||
console.error('unrecognized command', m)
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
// Gets called when user presses ctrl + k (mounts ctrl+k-style codelens)
|
||||
// TODO need to build this
|
||||
// const ctrlKCodeLensProvider = new CtrlKCodeLensProvider();
|
||||
// context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', ctrlKCodeLensProvider));
|
||||
// context.subscriptions.push(
|
||||
// vscode.commands.registerCommand('void.ctrl+k', () => {
|
||||
// const editor = vscode.window.activeTextEditor;
|
||||
// if (!editor)
|
||||
// return
|
||||
// ctrlKCodeLensProvider.addNewCodeLens(editor.document, editor.selection);
|
||||
// // vscode.commands.executeCommand('editor.action.showHover'); // apparently this refreshes the codelenses by having the internals call provideCodeLenses
|
||||
// })
|
||||
// )
|
||||
|
||||
}
|
||||
@@ -1,23 +1,24 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { findDiffs } from './findDiffs';
|
||||
import { throttle } from 'lodash';
|
||||
import { DiffArea, BaseDiff, Diff } from '../common/shared_types';
|
||||
import { DiffArea, BaseDiff, Diff, Inset } from '../common/shared_types';
|
||||
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
|
||||
import { updateWebviewHTML } from './extensionLib/updateWebviewHTML';
|
||||
|
||||
|
||||
const THROTTLE_TIME = 100
|
||||
|
||||
// TODO in theory this should be disposed
|
||||
const greenDecoration = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: 'rgba(0 255 51 / 0.2)',
|
||||
isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
|
||||
backgroundColor: 'rgba(0, 255, 51, 0.2)',
|
||||
isWholeLine: true, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
|
||||
})
|
||||
const lightGrayDecoration = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: 'rgba(218 218 218 / .2)',
|
||||
backgroundColor: 'rgba(218, 218, 218, 0.2)',
|
||||
isWholeLine: true,
|
||||
})
|
||||
const darkGrayDecoration = vscode.window.createTextEditorDecorationType({
|
||||
backgroundColor: 'rgb(148 148 148 / .2)',
|
||||
backgroundColor: 'rgb(148, 148, 148, 0.2)',
|
||||
isWholeLine: true,
|
||||
})
|
||||
|
||||
@@ -27,10 +28,14 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
private _originalFileOfDocument: { [docUriStr: string]: string } = {}
|
||||
private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {}
|
||||
private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {}
|
||||
private _insetsOfDocument: { [docUriStr: string]: Inset[] } = {}
|
||||
|
||||
|
||||
private _diffareaidPool = 0
|
||||
private _diffidPool = 0
|
||||
|
||||
private _extensionUri: vscode.Uri
|
||||
|
||||
// used internally by vscode
|
||||
private _onDidChangeCodeLenses: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
|
||||
public readonly onDidChangeCodeLenses: vscode.Event<void> = this._onDidChangeCodeLenses.event;
|
||||
@@ -42,7 +47,8 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
}
|
||||
|
||||
// declared by us, registered with vscode.languages.registerCodeLensProvider()
|
||||
constructor() {
|
||||
constructor(context: vscode.ExtensionContext) {
|
||||
this._extensionUri = context.extensionUri
|
||||
|
||||
console.log('Creating DisplayChangesProvider')
|
||||
|
||||
@@ -182,23 +188,15 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
// compute the diffs
|
||||
const diffs = findDiffs(originalCode, currentCode)
|
||||
|
||||
// console.log('____________')
|
||||
// diffs.forEach(diff => {
|
||||
// console.log('__DELETED: ', diff.originalCode)
|
||||
// console.log('__INSERTED: ', diff.code)
|
||||
// })
|
||||
|
||||
// add the diffs to `this._diffsOfDocument[docUriStr]`
|
||||
this.createDiffs(editor.document.uri, diffs, diffArea)
|
||||
|
||||
|
||||
// // print diffs
|
||||
// console.log('!ORIGINAL FILE:', JSON.stringify(originalFile))
|
||||
// console.log('!NEW FILE :', JSON.stringify(editor.document.getText().replace(/\r\n/g, '\n')))
|
||||
// console.log('!AREA originalCode:', JSON.stringify(originalCode))
|
||||
// console.log('!AREA currentCode :', JSON.stringify(currentCode))
|
||||
// for (const diff of this._diffsOfDocument[docUriStr]) {
|
||||
// console.log('------------')
|
||||
// console.log('originalCode:', JSON.stringify(diff.originalCode))
|
||||
// console.log('currentCode:', JSON.stringify(diff.code))
|
||||
// console.log('originalRange:', diff.originalRange.start.line, diff.originalRange.end.line,)
|
||||
// console.log('currentRange:', diff.range.start.line, diff.range.end.line,)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
// update green highlighting
|
||||
@@ -210,18 +208,89 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
)
|
||||
);
|
||||
|
||||
// update red highlighting
|
||||
/* NAIVE APPROACH */
|
||||
if (!this._insetsOfDocument[docUriStr])
|
||||
this._insetsOfDocument[docUriStr] = []
|
||||
for (const inset of this._insetsOfDocument[docUriStr]) {
|
||||
inset.insetObj.dispose()
|
||||
}
|
||||
this._insetsOfDocument[docUriStr] = this._diffsOfDocument[docUriStr]
|
||||
.filter(diff => diff.originalCode !== '')
|
||||
.map(diff => {
|
||||
const text = diff.originalCode
|
||||
const height = diff.originalCode.split('\n').length
|
||||
const line = diff.range.start.line - 1
|
||||
const insetObj = vscode.window.createWebviewTextEditorInset(editor, line, height);
|
||||
updateWebviewHTML(insetObj.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/diffline/index.js', cssOutLocation: 'dist/webviews/styles.css', isCode: true },
|
||||
{ text }
|
||||
)
|
||||
return { text, line, height, insetObj }
|
||||
})
|
||||
/* CACHING APPROACH */
|
||||
// function getCompareString<T extends { [key: string]: any; }>(obj: T, compareKeys: string[]): string {
|
||||
// return compareKeys.map(key => `${key}:${JSON.stringify(obj?.[key])}`).join('|');
|
||||
// }
|
||||
|
||||
// for each diffArea, highlight its sweepIndex in dark gray
|
||||
editor.setDecorations(
|
||||
darkGrayDecoration,
|
||||
(this._diffAreasOfDocument[docUriStr]
|
||||
.filter(diffArea => diffArea.sweepIndex !== null)
|
||||
.map(diffArea => {
|
||||
let s = diffArea.sweepIndex!
|
||||
return new vscode.Range(s, 0, s, 0)
|
||||
})
|
||||
)
|
||||
)
|
||||
// function compareArrays<T extends { [key: string]: any }>(arr1: T[], arr2: T[], compareKeys: string[]) {
|
||||
|
||||
// const getKey = (obj: T) => getCompareString(obj, compareKeys);
|
||||
|
||||
// const set1 = new Set(arr1.map(getKey));
|
||||
// const set2 = new Set(arr2.map(getKey));
|
||||
|
||||
// const common = arr1.filter(obj => set2.has(getKey(obj)))
|
||||
// const uniqueToFirst = arr1.filter(obj => !set2.has(getKey(obj)))
|
||||
// const uniqueToSecond = arr2.filter(obj => !set1.has(getKey(obj)))
|
||||
// const uniqueToFirstKeys = new Set(uniqueToFirst.map(getKey))
|
||||
// const uniqueToSecondKeys = new Set(uniqueToSecond.map(getKey))
|
||||
|
||||
|
||||
// return {
|
||||
// common,
|
||||
// uniqueToFirst,
|
||||
// uniqueToSecond,
|
||||
// uniqueToFirstKeys,
|
||||
// uniqueToSecondKeys,
|
||||
// getKey,
|
||||
// };
|
||||
// }
|
||||
// compare the current and old insets and update the ones that have changed
|
||||
// if (!this._insetsOfDocument[docUriStr])
|
||||
// this._insetsOfDocument[docUriStr] = []
|
||||
// const oldInsets: Inset[] = this._insetsOfDocument[docUriStr]
|
||||
// const currInsets: Inset[] = this._diffsOfDocument[docUriStr]
|
||||
// .map(diff => ({ text: diff.originalCode, height: diff.originalCode.split('\n').length, line: diff.range.start.line - 1, insetObj: undefined! }))
|
||||
// const {
|
||||
// common: commonInsets,
|
||||
// uniqueToFirst: deletedInsets,
|
||||
// uniqueToSecond: addedInsets,
|
||||
// } = compareArrays(oldInsets, currInsets, ['text', 'height', 'line'])
|
||||
// console.log('DELETED INSETS', deletedInsets)
|
||||
// console.log('ADDED INSETS', addedInsets)
|
||||
// for (const deletedInset of deletedInsets) { // delete insets in the deleted set
|
||||
// deletedInset.insetObj.dispose()
|
||||
// }
|
||||
// for (const addedInset of addedInsets) { // add insets in the added set
|
||||
// const insetObj = vscode.window.createWebviewTextEditorInset(editor, addedInset.line, addedInset.height);
|
||||
// updateWebviewHTML(insetObj.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/diffline/index.js', cssOutLocation: 'dist/webviews/styles.css', isCode: true },
|
||||
// { text: addedInset.text }
|
||||
// )
|
||||
// addedInset.insetObj = insetObj
|
||||
// }
|
||||
// this._insetsOfDocument[docUriStr] = commonInsets.concat(addedInsets)
|
||||
|
||||
// // for each diffArea, highlight its sweepIndex in dark gray
|
||||
// editor.setDecorations(
|
||||
// darkGrayDecoration,
|
||||
// (this._diffAreasOfDocument[docUriStr]
|
||||
// .filter(diffArea => diffArea.sweepIndex !== null)
|
||||
// .map(diffArea => {
|
||||
// let s = diffArea.sweepIndex!
|
||||
// return new vscode.Range(s, 0, s, 0)
|
||||
// })
|
||||
// )
|
||||
// )
|
||||
|
||||
// for each diffArea, highlight sweepIndex+1...end in light gray
|
||||
editor.setDecorations(
|
||||
@@ -234,8 +303,6 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
)
|
||||
)
|
||||
|
||||
// TODO update red highlighting
|
||||
// this._diffsOfDocument[docUriStr].map(diff => diff.deletedCode)
|
||||
|
||||
// update code lenses
|
||||
this._onDidChangeCodeLenses.fire()
|
||||
@@ -379,7 +446,8 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
|
||||
|
||||
// used by us only
|
||||
public updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => {
|
||||
public updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string, options?: { endStream?: boolean }) => {
|
||||
const { endStream, } = options || {}
|
||||
|
||||
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
|
||||
if (!editor) {
|
||||
@@ -387,12 +455,12 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
return;
|
||||
}
|
||||
|
||||
// original code all diffs are based on in the code
|
||||
// original code all diffs are based on
|
||||
const originalDiffAreaCode = (this._originalFileOfDocument[docUriStr] || '').split('\n').slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
|
||||
|
||||
// figure out where to highlight based on where the AI is in the stream right now, use the last diff in findDiffs to figure that out
|
||||
const diffs = findDiffs(originalDiffAreaCode, newDiffAreaCode)
|
||||
const lastDiff = diffs[diffs.length - 1] ?? null
|
||||
const lastDiff = diffs?.[diffs.length - 1]
|
||||
|
||||
// these are two different coordinate systems - new and old line number
|
||||
let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
|
||||
@@ -426,14 +494,14 @@ export class DiffProvider implements vscode.CodeLensProvider {
|
||||
const oldFileBottom = originalDiffAreaCode.split('\n').slice(oldFileStartLine + 1, Infinity).join('\n')
|
||||
|
||||
let newCode = `${newFileTop}\n${oldFileBottom}`
|
||||
diffArea.sweepIndex = newFileEndLine
|
||||
// replace oldDACode with newDACode with a vscode edit
|
||||
diffArea.sweepIndex = endStream ? null : newFileEndLine
|
||||
|
||||
// replace oldCode with newCode using a vscode edit
|
||||
const workspaceEdit = new vscode.WorkspaceEdit();
|
||||
|
||||
const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)
|
||||
workspaceEdit.replace(editor.document.uri, diffareaRange, newCode)
|
||||
await vscode.workspace.applyEdit(workspaceEdit)
|
||||
|
||||
}, THROTTLE_TIME)
|
||||
|
||||
}
|
||||
|
||||
+1
-5
@@ -134,7 +134,7 @@ Return \`true\` if ANY part of the chunk should be modified, and \`false\` if it
|
||||
|
||||
// lazily applies the diff to the file
|
||||
// we chunk the text in the file, and ask an LLM whether it should edit each chunk
|
||||
const applyDiffLazily = async ({ docUri, oldFileStr, voidConfig, abortRef, diffRepr, diffProvider, diffArea }: { docUri: vscode.Uri, oldFileStr: string, diffRepr: string, voidConfig: VoidConfig, diffProvider: DiffProvider, diffArea: DiffArea, abortRef: AbortRef }) => {
|
||||
export const applyDiffLazily = async ({ docUri, oldFileStr, voidConfig, abortRef, diffRepr, diffProvider, diffArea }: { docUri: vscode.Uri, oldFileStr: string, diffRepr: string, voidConfig: VoidConfig, diffProvider: DiffProvider, diffArea: DiffArea, abortRef: AbortRef }) => {
|
||||
|
||||
|
||||
// stateful variables
|
||||
@@ -179,7 +179,3 @@ const applyDiffLazily = async ({ docUri, oldFileStr, voidConfig, abortRef, diffR
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export { applyDiffLazily }
|
||||
@@ -1,9 +1,9 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { AbortRef, OnFinalMessage, OnText, sendLLMMessage } from "./sendLLMMessage"
|
||||
import { AbortRef, OnFinalMessage, OnText, sendLLMMessage } from "../common/sendLLMMessage"
|
||||
import { VoidConfig } from '../webviews/common/contextForConfig';
|
||||
import { searchDiffChunkInstructions, writeFileWithDiffInstructions } from './systemPrompts';
|
||||
import { searchDiffChunkInstructions, writeFileWithDiffInstructions } from '../common/systemPrompts';
|
||||
import { throttle } from 'lodash';
|
||||
import { readFileContentOfUri } from '../extension/extensionLib/readFileContentOfUri';
|
||||
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
|
||||
|
||||
type Res<T> = ((value: T) => void)
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { applyDiffLazily } from './ctrlL';
|
||||
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
|
||||
import { MessageToSidebar, MessageFromSidebar, DiffArea, ChatThreads } from '../common/shared_types';
|
||||
import { DiffProvider } from './DiffProvider';
|
||||
import { getVoidConfigFromPartial } from '../webviews/common/contextForConfig';
|
||||
import { CtrlKWebviewProvider } from './providers/CtrlKWebviewProvider';
|
||||
import { SidebarWebviewProvider } from './providers/SidebarWebviewProvider';
|
||||
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { AbortRef } from '../common/sendLLMMessage';
|
||||
import { MessageToSidebar, MessageFromSidebar, DiffArea, ChatThreads } from '../common/shared_types';
|
||||
import { getVoidConfigFromPartial } from '../webviews/common/contextForConfig';
|
||||
import { applyDiffLazily } from './applyDiffLazily';
|
||||
import { DiffProvider } from './DiffProvider';
|
||||
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
|
||||
import { SidebarWebviewProvider } from './providers/SidebarWebviewProvider';
|
||||
import { CtrlKWebviewProvider } from './providers/CtrlKWebviewProvider';
|
||||
|
||||
// this comes from vscode.proposed.editorInsets.d.ts
|
||||
declare module 'vscode' {
|
||||
@@ -25,7 +26,16 @@ declare module 'vscode' {
|
||||
}
|
||||
|
||||
const roundRangeToLines = (selection: vscode.Selection) => {
|
||||
return new vscode.Range(selection.start.line, 0, selection.end.line, Number.MAX_SAFE_INTEGER)
|
||||
let endLine = selection.end.character === 0 ? selection.end.line - 1 : selection.end.line // e.g. if the user triple clicks, it selects column=0, line=line -> column=0, line=line+1
|
||||
return new vscode.Range(selection.start.line, 0, endLine, Number.MAX_SAFE_INTEGER)
|
||||
}
|
||||
|
||||
const getSelection = (editor: vscode.TextEditor) => {
|
||||
// get the range of the selection and the file the user is in
|
||||
const selectionRange = roundRangeToLines(editor.selection);
|
||||
const selectionStr = editor.document.getText(selectionRange).trim();
|
||||
const filePath = editor.document.uri;
|
||||
return { selectionStr, filePath }
|
||||
}
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
@@ -50,17 +60,10 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
vscode.commands.executeCommand('workbench.view.extension.voidViewContainer');
|
||||
// vscode.commands.executeCommand('vscode.moveViewToPanel', CustomViewProvider.viewId); // move to aux bar
|
||||
|
||||
// get the range of the selection
|
||||
const selectionRange = roundRangeToLines(editor.selection);
|
||||
|
||||
// get the text the user is selecting
|
||||
const selectionStr = editor.document.getText(selectionRange);
|
||||
|
||||
// get the file the user is in
|
||||
const filePath = editor.document.uri;
|
||||
const { selectionStr, filePath } = getSelection(editor)
|
||||
|
||||
// send message to the webview (Sidebar.tsx)
|
||||
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
|
||||
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, filePath } } satisfies MessageToSidebar));
|
||||
})
|
||||
);
|
||||
|
||||
@@ -71,22 +74,16 @@ export function activate(context: vscode.ExtensionContext) {
|
||||
const editor = vscode.window.activeTextEditor
|
||||
if (!editor) return
|
||||
|
||||
// get the range of the selection
|
||||
const selectionRange = roundRangeToLines(editor.selection);
|
||||
|
||||
// get the text the user is selecting
|
||||
const selectionStr = editor.document.getText(selectionRange);
|
||||
|
||||
// get the file the user is in
|
||||
const filePath = editor.document.uri;
|
||||
const { selectionStr, filePath } = getSelection(editor)
|
||||
|
||||
// send message to the webview (Sidebar.tsx)
|
||||
ctrlKWebviewProvider.onPressCtrlK()
|
||||
// ctrlKWebviewProvider.onPressCtrlK()
|
||||
// sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+k', selection: { selectionStr, filePath } } satisfies MessageToSidebar));
|
||||
})
|
||||
);
|
||||
|
||||
// 3. Show an approve/reject codelens above each change
|
||||
const diffProvider = new DiffProvider();
|
||||
const diffProvider = new DiffProvider(context);
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', diffProvider));
|
||||
|
||||
// 4. Add approve/reject commands
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
import * as vscode from 'vscode'
|
||||
|
||||
|
||||
export const readFileContentOfUri = async (uri: vscode.Uri): Promise<string> => {
|
||||
const document = await vscode.workspace.openTextDocument(uri);
|
||||
return document.getText().replace(/\r\n/g, '\n') ?? '' // Normalize line endings
|
||||
// export const readFileContentOfUri = async (uri: vscode.Uri): Promise<string> => {
|
||||
// const document = await vscode.workspace.openTextDocument(uri.fsPath);
|
||||
// return document.getText().replace(/\r\n/g, '\n') ?? '' // Normalize line endings
|
||||
// };
|
||||
|
||||
};
|
||||
|
||||
// this is the old version, which only reads the most recently saved version
|
||||
// export const readFileContentOfUri = async (uri: vscode.Uri) => {
|
||||
// return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
|
||||
// .replace(/\r\n/g, '\n') // replace windows \r\n with \n
|
||||
// }
|
||||
// TODO this only accesses the most recently saved version; make it instead access the most recent version in the vscode editor
|
||||
export const readFileContentOfUri = async (uri: vscode.Uri) => {
|
||||
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
|
||||
.replace(/\r\n/g, '\n') // replace windows \r\n with \n
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ function generateNonce() {
|
||||
|
||||
|
||||
// call this when you have access to the webview to set its html
|
||||
export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.Uri, { jsOutLocation, cssOutLocation }: { jsOutLocation: string, cssOutLocation: string }, props?: object) => {
|
||||
export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.Uri, { jsOutLocation, cssOutLocation, isCode }: { jsOutLocation: string, cssOutLocation: string, isCode?: boolean }, props?: object) => {
|
||||
|
||||
// 'dist/sidebar/index.js'
|
||||
// 'dist/sidebar/styles.css'
|
||||
@@ -21,6 +21,8 @@ export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.
|
||||
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri));
|
||||
const nonce = generateNonce();
|
||||
|
||||
const codeStyle = `style="padding-left:0; padding-right:0; font-family: Consolas, "Courier New", monospace; font-weight: normal; font-size: 14px; font-feature-settings: "liga" 0, "calt" 0; font-variation-settings: normal; line-height: 19px; letter-spacing: 0px;"`
|
||||
|
||||
const webviewHTML = `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
@@ -31,7 +33,7 @@ export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.
|
||||
<base href="${rootUri}/">
|
||||
<link href="${stylesUri}" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<body ${isCode ? codeStyle : ''}>
|
||||
<div id="root" ${props ? `data-void-props="${encodeURIComponent(JSON.stringify(props))}"` : ''}></div>
|
||||
<script nonce="${nonce}" src="${scriptUri}"></script>
|
||||
</body>
|
||||
|
||||
@@ -127,6 +127,6 @@ export function findDiffs(oldStr: string, newStr: string) {
|
||||
}
|
||||
} // end for
|
||||
|
||||
console.debug('Replacements', replacements)
|
||||
// console.debug('Replacements', replacements)
|
||||
return replacements
|
||||
}
|
||||
|
||||
@@ -3,22 +3,6 @@
|
||||
import * as vscode from 'vscode';
|
||||
import { updateWebviewHTML as _updateWebviewHTML, updateWebviewHTML } from '../extensionLib/updateWebviewHTML';
|
||||
|
||||
// this comes from vscode.proposed.editorInsets.d.ts
|
||||
declare module 'vscode' {
|
||||
export interface WebviewEditorInset {
|
||||
readonly editor: vscode.TextEditor;
|
||||
readonly line: number;
|
||||
readonly height: number;
|
||||
readonly webview: vscode.Webview;
|
||||
readonly onDidDispose: Event<void>;
|
||||
dispose(): void;
|
||||
}
|
||||
export namespace window {
|
||||
export function createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): WebviewEditorInset;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export class CtrlKWebviewProvider {
|
||||
|
||||
|
||||
@@ -21,11 +21,12 @@ const configString = (description: string, defaultVal: string) => {
|
||||
export const configFields = [
|
||||
'anthropic',
|
||||
'openAI',
|
||||
'gemini',
|
||||
'greptile',
|
||||
'ollama',
|
||||
'openRouter',
|
||||
'openAICompatible',
|
||||
'azure'
|
||||
'azure',
|
||||
] as const
|
||||
|
||||
|
||||
@@ -46,8 +47,16 @@ const voidConfigInfo: Record<
|
||||
configFields,
|
||||
),
|
||||
|
||||
maxTokens: configString(
|
||||
"Max number of tokens to output. Must be a number.", ''
|
||||
maxTokens: configEnum(
|
||||
"Max number of tokens to output.",
|
||||
'1024',
|
||||
[
|
||||
"default", // this will be parseInt'd into NaN and ignored by the API. Anything that's not a number has this behavior.
|
||||
"1024",
|
||||
"2048",
|
||||
"4096",
|
||||
"8192"
|
||||
] as const,
|
||||
),
|
||||
|
||||
},
|
||||
@@ -156,6 +165,19 @@ const voidConfigInfo: Record<
|
||||
// }
|
||||
// },
|
||||
},
|
||||
gemini: {
|
||||
apikey: configString('Google API key.', ''),
|
||||
model: configEnum(
|
||||
'Gemini model to use.',
|
||||
'gemini-1.5-flash',
|
||||
[
|
||||
"gemini-1.5-flash",
|
||||
"gemini-1.5-pro",
|
||||
"gemini-1.5-flash-8b",
|
||||
"gemini-1.0-pro"
|
||||
] as const
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -265,4 +287,3 @@ export function useVoidConfig(): ConfigValueType {
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
|
||||
@@ -1,25 +1,39 @@
|
||||
import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"
|
||||
|
||||
type PropsType = { [s: string]: any } | null
|
||||
const PropsContext = createContext<any>(undefined as unknown as any)
|
||||
|
||||
type PropsValue = { props: PropsType }
|
||||
|
||||
const PropsContext = createContext<PropsValue>(undefined as unknown as PropsValue)
|
||||
export const getPropsObj = (rootElement: HTMLElement) => {
|
||||
let props = rootElement.getAttribute("data-void-props")
|
||||
let propsObj: object | null = null
|
||||
if (props !== null) {
|
||||
propsObj = JSON.parse(decodeURIComponent(props))
|
||||
}
|
||||
return propsObj
|
||||
}
|
||||
|
||||
// provider for whatever came in data-void-props
|
||||
export function PropsProvider({ children, props }: { children: ReactNode, props: PropsType }) {
|
||||
export function PropsProvider({ children, props }: { children: ReactNode, props: object | null }) {
|
||||
return (
|
||||
<PropsContext.Provider value={{ props }}>
|
||||
<PropsContext.Provider value={props}>
|
||||
{children}
|
||||
</PropsContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useVoidProps(): PropsValue {
|
||||
const context = useContext<PropsValue>(PropsContext)
|
||||
export function useVoidProps<T extends {}>(): T {
|
||||
// context is the "value" from above
|
||||
const context: T | null | undefined = useContext<T>(PropsContext)
|
||||
// only undefined if has no provider
|
||||
if (context === undefined) {
|
||||
throw new Error("useVoidProps missing Provider")
|
||||
}
|
||||
if (context === null) {
|
||||
throw new Error("useVoidProps had null props")
|
||||
}
|
||||
if (!(context instanceof Object)) {
|
||||
throw new Error("useVoidProps props was not an object")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { getVSCodeAPI, awaitVSCodeResponse, onMessageFromVSCode } from "./getVsc
|
||||
import { initPosthog, identifyUser } from "./posthog";
|
||||
import { ThreadsProvider } from "./contextForThreads";
|
||||
import { ConfigProvider } from "./contextForConfig";
|
||||
import { PropsProvider } from "./contextForProps";
|
||||
import { getPropsObj, PropsProvider } from "./contextForProps";
|
||||
|
||||
const ListenersAndTracking = () => {
|
||||
// initialize posthog
|
||||
@@ -46,18 +46,12 @@ export const mount = (children: React.ReactNode) => {
|
||||
|
||||
// mount the sidebar on the id="root" element
|
||||
const rootElement = document.getElementById("root")!
|
||||
console.log("Void root Element:", rootElement)
|
||||
|
||||
let props = rootElement.getAttribute("data-void-props")
|
||||
let propsObj: object | null = null
|
||||
if (props !== null) {
|
||||
propsObj = JSON.parse(decodeURIComponent(props))
|
||||
}
|
||||
// console.log("Void root Element:", rootElement)
|
||||
|
||||
const content = (<>
|
||||
<ListenersAndTracking />
|
||||
|
||||
<PropsProvider props={propsObj}>
|
||||
<PropsProvider props={getPropsObj(rootElement)}>
|
||||
<ThreadsProvider>
|
||||
<ConfigProvider>
|
||||
{children}
|
||||
@@ -69,4 +63,4 @@ export const mount = (children: React.ReactNode) => {
|
||||
const root = ReactDOM.createRoot(rootElement)
|
||||
root.render(content);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,13 @@ export const CtrlK = () => {
|
||||
sx('Pressed ctrl+k')
|
||||
})
|
||||
|
||||
// const inset = vscode.window.createWebviewTextEditorInset(editor, 10, 10, {})
|
||||
// inset.webview.html = `
|
||||
// <html>
|
||||
// <body style="pointer-events:none;">Hello World!</body>
|
||||
// </html>
|
||||
// `;
|
||||
|
||||
return <>
|
||||
<div>
|
||||
{x}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useOnVSCodeMessage } from '../common/getVscodeApi';
|
||||
import { useVoidProps } from '../common/contextForProps';
|
||||
|
||||
|
||||
type props = {
|
||||
text: string
|
||||
}
|
||||
|
||||
export const DiffLine = () => {
|
||||
|
||||
const props = useVoidProps<props>()
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
let text = props?.text ?? ''
|
||||
|
||||
return <>
|
||||
<div className="view-line relative pointer-events-none">
|
||||
<div className={`absolute w-[100%] h-[100%] bg-[rgba(255,0,51,0.2)] z-10`}></div>
|
||||
<span>
|
||||
<span className='whitespace-pre text-[14px]'>
|
||||
{text}
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import React from "react"
|
||||
import { mount } from "../common/mount"
|
||||
import { DiffLine } from "./DiffLine"
|
||||
|
||||
// this is the entry point that mounts diffline
|
||||
mount(<DiffLine />)
|
||||
|
||||
@@ -43,4 +43,4 @@ html {
|
||||
|
||||
.dropdown {
|
||||
@apply bg-vscode-dropdown-bg text-vscode-dropdown-foreground border-vscode-dropdown-border focus:outline-vscode-focus-border;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user