Compare commits

...

26 Commits

Author SHA1 Message Date
Andrew ab5245d2d9 Max tokens is optional + other config updates - Fixes #128 2024-10-28 15:50:05 -07:00
Andrew 70b6af34e1 Merge branch 'main' into multiple-webviews 2024-10-28 03:58:36 -07:00
Andrew 119462f663 fix imports?? 2024-10-28 03:58:09 -07:00
Andrew 8ee665cd57 fix imports 2024-10-28 03:53:40 -07:00
Andrew ace3f181ed Merge branch 'speculative-edits' into multiple-webviews 2024-10-28 03:53:02 -07:00
Andrew 6d12737fda add throttling 2024-10-28 03:52:19 -07:00
Andrew d3962acd30 merge - but appears broken 2024-10-28 03:44:13 -07:00
Andrew 18557c8f6e comment out ctrlk so it builds 2024-10-28 03:37:18 -07:00
Andrew 29b037726e Merge branch 'speculative-edits' into multiple-webviews 2024-10-28 03:36:39 -07:00
mp eae1af331f Create Ctrl + K 2024-10-28 03:36:07 -07:00
Andrew 2e92da500f lines sweep works! Need to polish it. 2024-10-28 03:19:40 -07:00
Andrew 59dc193f97 update contrib 2024-10-26 21:36:45 -07:00
Andrew Pareles bd778d259b fix typo 2024-10-26 21:33:12 -07:00
Andrew 605cf236a5 link issues 2024-10-26 19:17:53 -07:00
Andrew 8bc3a0d277 windows build works!! 2024-10-26 19:17:20 -07:00
Andrew d80c88bc38 move files to right places 2024-10-25 22:39:13 -07:00
Andrew dd34a5be57 fix ctrl L change 2024-10-25 22:35:13 -07:00
Andrew f4be80b724 Merge remote-tracking branch 'origin/ctrlk-diff-ui' into multiple-webviews 2024-10-25 22:31:44 -07:00
Andrew b40c58b78a Merge remote-tracking branch 'origin/speculative-edits' into multiple-webviews 2024-10-25 22:25:47 -07:00
Andrew 64468b6f4d Merge branch 'speculative-edits' into multiple-webviews 2024-10-25 22:14:11 -07:00
Andrew 8f83b063f1 add ctrlk webview provider 2024-10-25 22:10:22 -07:00
mp 9eb897b655 Better file reading function 2024-10-25 21:55:15 -07:00
mp a738ca0b2c Reafactor DiffProvider for ctrl+k 2024-10-25 21:47:00 -07:00
Andrew f56772a07a amend 2024-10-25 19:58:35 -07:00
Andrew 38662d2824 major refactor, allow 3 types of webview 2024-10-25 19:49:59 -07:00
Andrew a51873c06a move files to extension/ 2024-10-25 15:58:06 -07:00
41 changed files with 1685 additions and 1370 deletions
+67 -44
View File
@@ -1,15 +1,13 @@
# Contributing to Void
Welcome! 👋 This is a guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord!
Welcome! 👋 This is the official guide on how to contribute to Void. We want to make it as easy as possible to contribute, so if you have any questions or comments, reach out via email or discord!
There are two main ways to contribute:
There are a few ways to contribute:
- Suggest New Features ([discord](https://discord.gg/RSNjgaugJs))
- Build New Features ([project](https://github.com/orgs/voideditor/projects/2/views/3))
- Suggest New Features ([Discord](https://discord.gg/RSNjgaugJs))
- Build New Features ([Project](https://github.com/orgs/voideditor/projects/2/views/3))
- Submit Issues/Docs/Bugs ([Issues](https://github.com/voideditor/void/issues))
We use a [VSCode extension](https://code.visualstudio.com/api/get-started/your-first-extension) to implement most of Void's functionality. Scroll down to see 1. How to build/contribute to the Extension, or 2. How to build/contribute to the full IDE (for more native changes).
For some useful links we've compiled see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
## 1. Building the Extension
@@ -29,23 +27,51 @@ git clone https://github.com/voideditor/void
npm install
```
4. Compile the React by running `npm run build`. We created this build command to convert `sidebar/index.tsx` into `dist/`.
1. Compile the React files by running `npm run build`. This build command converts all the Tailwind/React entrypoint files into raw .css and .js files in `dist/`.
```
npm run build
```
5. Run the project by pressing <kbd>F5</kbd>.
5. Run the extension in a new window by pressing <kbd>F5</kbd>.
This will start a new instance of VSCode with the extension enabled. If this doesn't work, you can press <kbd>Ctrl+Shift+P</kbd>, select "Debug: Start Debugging", and select "VSCode Extension Development".
## 2. Building the full IDE
If you want to work on the full IDE, please follow the steps below. If you have any questions/issues, you can refer to VSCode's full [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page, which is where the steps below come from. Also feel free to submit an issue or get in touch with us with any build errors.
If you want to work on the full IDE, please follow the steps below. If you have any questions/issues, you can refer to VSCode's full [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute) page. Also feel free to submit an issue or get in touch with us with any build errors.
### a. Building on a Mac
<!-- TODO say whether you can build each distribution on any Operating System, or if you need to build Windows on Windows, etc -->
To build on a Mac, open `void/` in VSCode. Make sure you've built the extension by following the steps above (or just run `cd ./extensions/void && npm install && npm run build && npm run compile && cd ../..`). Also make sure you have Python and XCode installed on your system (you probably do by default).
### a. Build Prerequisites - Mac
If you're using a Mac, make sure you have Python and XCode installed (you probably do by default).
### b. Build Prerequisites - Windows
If you're using a Windows computer, first get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community) (recommended) or [VS Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) (not recommended). If you already have both, you might need to run the next few steps on both of them.
Find the box for Visual Studio 2022 (or VS Build Tools) and click Install/Modify.
Under Workloads, select "Desktop development with C++" and "Node.js build tools".
Under Individual components, select every item under:
- `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`,
- `C++ ATL for latest build tools with Spectre Mitigations`,
- `C++ MFC for latest build tools with Spectre Mitigations`.
Finally, click Install/Modify.
### c. Build Prerequisites - Linux
We haven't created prerequisite steps for building on Linux yet, but you can follow [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute).
### 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 ../..`).
First, open `void/` in VSCode. Then:
1. Install all dependencies.
@@ -53,9 +79,13 @@ To build on a Mac, open `void/` in VSCode. Make sure you've built the extension
npm install
```
2. Run `npm run watch`.
2. Press <kbd>Ctrl+Shift+B</kbd>, or if you prefer using the terminal run `npm run watch`.
This can take ~5 min. It's done when you see something like:
This can take ~5 min.
If you ran <kbd>Ctrl+Shift+B</kbd>, the build is done when you see two check marks.
If you ran `npm run watch`, the build is done when you see something like this:
```
[watch-extensions] [00:37:39] Finished compilation extensions with 0 errors after 19303 ms
@@ -66,48 +96,40 @@ This can take ~5 min. It's done when you see something like:
<!-- 3. Press <kbd>Ctrl+Shift+B</kbd> to start the build process. -->
3. In a new terminal, run `./scripts/code.sh`.
1. In a new terminal, run `./scripts/code.sh` (Mac/Linux) or `/.scripts/code.bat` (Windows). This should open up the built IDE!
This should open up the built IDE after loading for some time. To see new changes without restarting the build, use <kbd>Ctrl+Shift+P</kbd> and run "Reload Window".
To bundle the IDE, run `npm run gulp vscode-darwin-arm64`. Here are the full options: `vscode-{win32-ia32 | win32-x64 | darwin-x64 | darwin-arm64 | linux-ia32 | linux-x64 | linux-arm}(-min)`
To see new changes without restarting the build, press <kbd>Ctrl+Shift+P</kbd> and run "Reload Window" inside the new window.
Now that you're set up, feel free to check out our [Issues](https://github.com/voideditor/void/issues) page!
**Common Fixes:**
### Common Fixes
- Make sure you have the same NodeJS version as `.nvmrc`.
- If you see `X [ERROR] Cannot start service: Host version "0.23.1" does not match binary version "0.23.0"`, run `npm i -D esbuild@0.23.0`
### b. Building on Windows
To build on Windows, please refer to [How to Contribute](https://github.com/microsoft/vscode/wiki/How-to-Contribute). We recommend building on Mac; we're Windows users who switch to Mac to build right now.
<!-- Get [Visual Studio 2022](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community). Also find the boxes for "Desktop development with C++" and "Node.js development" and get those, too.
If you get a node-gyp error in the next few steps, you should also get [Visual Studio Build Tools](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools), find the Visual Studio Build Tools box, click Install (or Modify), then in Individual Components:
check every item under `MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)`, `C++ ATL for latest build tools with Spectre Mitigations`, and `C++ MFC for latest build tools with Spectre Mitigations`.
- If you see `[ERROR] Cannot start service: Host version "0.23.1" does not match binary version "0.23.0"`, run `npm i -D esbuild@0.23.0` or do a clean install of your npm dependencies.
```
npm config set msvs_version 2022
```
-->
## Bundling
To bundle the IDE into an executable, run `npm run gulp vscode-darwin-arm64`.
Here are the full options: `vscode-{win32-ia32 | win32-x64 | darwin-x64 | darwin-arm64 | linux-ia32 | linux-x64 | linux-arm}(-min)`
## Roadmap
Here are the most important topics on our Roadmap. More ⭐'s = more important.
These sometimes get outdated - please refer to our Issues page for the latest issues.
These sometimes get outdated - please refer to our [Issues](https://github.com/voideditor/void/issues) page for the latest issues.
## ⭐⭐⭐ Make History work well.
When the user submits a response or presses the apply/accept/reject button, we should add these events to the history, allowing the user to undo/redo them. Right now there is unexpected behavior if the user tries to undo or redo their changes.
## ⭐⭐⭐ Build Cursor-style quick edits (ctrl+k).
## ⭐⭐⭐ Build Cursor-style quick edits (Ctrl+K).
When the user presses ctrl+k, an input box should appear inline with the code that they were selecting. This is somewhat difficult to do because an extension alone cannot do this, and it requires creating a new component in the IDE. We think you can modify vscode's built-in "codelens" or "zone widget" components, but we are open to alternatives.
When the user presses Ctrl+K, an input box should appear inline with the code that they were selecting. This is somewhat difficult to do because an extension alone cannot do this, and it requires creating a new component in the IDE. We think you can modify vscode's built-in "codelens" or "zone widget" components, but we are open to alternatives.
## ⭐⭐⭐ Creative.
@@ -117,21 +139,17 @@ Eventually, we want to build a convenient API for creating AI tools. The API wil
## ⭐ One-stars.
⭐ Let the user accept / reject all Diffs in an entire file via the sidebar.
⭐ Let the user Accept / Reject all Diffs in an entire file via the sidebar.
# Guidelines
Please don't make big refactors without speaking with us first. We'd like to keep the codebase similar to vscode so we can periodically rebase, and if we have big changes that gets complicated.
We're always glad to talk about new ideas, help you get set up, and make sure your changes align with our vision for the project. Feel free to shoot us a message in the #general channel of the [Discord](https://discord.gg/RSNjgaugJs) for any reason. Please check in especially if you want to make a lot of changes or build a large new feature.
# Submitting a Pull Request
Please submit a pull request once you've made a change. Here are a few guidelines:
- A PR should be about one _single_ feature change. The fewer items you change, the more likely the PR is to be accepted.
## Submitting a Pull Request
- Your PR should contain a description that first explains at a high level what you did, and then describes the exact changes you made (and to which files). Please don't use vague statements like "refactored code" or "improved types" (instead, describe what code you refactored, or what types you changed).
- Try to avoid refactoring and making feature changes in the same PR.
Please submit a pull request once you've made a change. You don't need to submit an issue.
# Relevant files
@@ -156,3 +174,8 @@ We keep track of all the files we've changed with Void so it's easy to rebase:
- build/npm/dirs.js
- vscode.proposed.editorInsets.d.ts - not modified, but code copied
## References
For some useful links we've compiled on VSCode, see [`VOID_USEFUL_LINKS.md`](https://github.com/voideditor/void/blob/main/VOID_USEFUL_LINKS.md).
-19
View File
@@ -1,19 +0,0 @@
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const fs = require('fs')
const from = 'src/sidebar/styles.css'
const to = 'dist/sidebar/styles.css'
const original_css_contents = fs.readFileSync(from, 'utf8')
postcss([
tailwindcss, // this compiles tailwind of all the files specified in tailwind.config.json
autoprefixer,
])
.process(original_css_contents, { from, to })
.then(processed_css_contents => { fs.writeFileSync(to, processed_css_contents.css) })
.catch(error => {
console.error('Error in build-css:', error)
})
-13
View File
@@ -1,13 +0,0 @@
const esbuild = require('esbuild')
// Build JS
esbuild.build({
entryPoints: ['src/sidebar/index.tsx'],
bundle: true,
minify: true,
sourcemap: true,
outfile: 'dist/sidebar/index.js',
format: 'iife', // apparently iife is safe for browsers (safer than cjs)
platform: 'browser',
external: ['vscode'],
}).catch(() => process.exit(1));
+59
View File
@@ -0,0 +1,59 @@
const tailwindcss = require('tailwindcss')
const autoprefixer = require('autoprefixer')
const postcss = require('postcss')
const fs = require('fs')
const convertTailwindToCSS = ({ from, to }) => {
console.log('converting ', from, ' --> ', to)
const original_css_contents = fs.readFileSync(from, 'utf8')
return postcss([
tailwindcss, // this compiles tailwind of all the files specified in tailwind.config.json
autoprefixer,
])
.process(original_css_contents, { from, to })
.then(processed_css_contents => { fs.writeFileSync(to, processed_css_contents.css) })
.catch(error => {
console.error('Error in build-css:', error)
})
}
const esbuild = require('esbuild')
const convertTSXtoJS = async ({ from, to }) => {
console.log('converting ', from, ' --> ', to)
return esbuild.build({
entryPoints: [from],
bundle: true,
minify: true,
sourcemap: true,
outfile: to,
format: 'iife', // apparently iife is safe for browsers (safer than cjs)
platform: 'browser',
external: ['vscode'],
}).catch(() => process.exit(1));
}
(async () => {
// convert tsx to js
await convertTSXtoJS({
from: 'src/webviews/sidebar/index.tsx',
to: 'dist/webviews/sidebar/index.js',
})
await convertTSXtoJS({
from: 'src/webviews/ctrlk/index.tsx',
to: 'dist/webviews/ctrlk/index.js',
})
// convert tailwind to css
await convertTailwindToCSS({
from: 'src/webviews/styles.css',
to: 'dist/webviews/styles.css',
})
})()
-8
View File
@@ -6013,14 +6013,6 @@
"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",
+2 -2
View File
@@ -11,7 +11,7 @@
"Other"
],
"activationEvents": [],
"main": "./out/extension.js",
"main": "./out/extension/extension.js",
"contributes": {
"configuration": {
"title": "Void",
@@ -104,7 +104,7 @@
"vscode:prepublish": "npm run compile",
"compile": "tsc -p ./",
"watch": "tsc -watch -p ./",
"build": "rimraf dist && node build-tsx.js && node build-css.js",
"build": "rimraf dist && node build/build.js",
"pretest": "tsc -p ./ && eslint src --ext ts",
"test": "vscode-test"
},
-115
View File
@@ -1,115 +0,0 @@
import * as vscode from 'vscode';
import { SuggestedEdit } from './findDiffs';
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:...
})
export class DiffProvider {
originalCodeOfDocument: { [docUri: string]: string }
diffsOfDocument: {
[docUri: string]: {
startLine,
startCol,
endLine,
endCol,
originalText,
inset,
diffid,
}
}
// sweep
currentLine: { [docUri: string]: undefined | number }
weAreEditing: boolean = false
constructor() {
vscode.workspace.onDidChangeTextDocument((e) => {
// on user change, grow/shrink/merge/delete diff AREAS
// you dont have to do anything to the diffs here bc they all get recomputed in refresh()
// user changes only get highlighted if theyre in a diffarea
// go thru all diff areas and adjust line numbers based on the user's change
this.refreshStyles(e.document.uri.toString())
})
}
// refreshes styles on page
refreshStyles(docUriStr: string) {
if (this.weAreEditing) return
// recompute all diffs on the page
// run inset.dispose() on all diffs
// original and current code -> diffs
// originalCodeOfDocument[docUriStr]
// create new diffs
const inset = vscode.window.createWebviewTextEditorInset(editor, lineStart, height, {})
inset.webview.html = `
<html>
<body style="pointer-events:none;">Hello World!</body>
</html>
`;
}
// called on void.acceptDiff
public async acceptDiff({ diffid }: { diffid: number }) {
// update original based on the diff
// refresh()
}
// called on void.rejectDiff
public async rejectDiff({ diffid }: { diffid: number }) {
// get diffs[diffid]
// revert current file based on diff
// refresh()
}
// sweep
initializeSweep({ startLine }) {
// reject all diffs on the page
// store original code
// currentLine=start of sweep
}
onUpdateSweep(addedText) {
// update final
// refresh() ?
// currentLine += number of newlines in addedText
}
onAbortSweep() {
}
}
@@ -1,315 +0,0 @@
import * as vscode from 'vscode';
import { findDiffs } from './findDiffs';
import { Diff, BaseDiffArea, BaseDiff, DiffArea } from './common/shared_types';
// 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:...
})
// responsible for displaying diffs and showing accept/reject buttons
export class DisplayChangesProvider {
private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {}
private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {}
private _diffareaidPool = 0
private _diffidPool = 0
private _weAreEditing: boolean = false
private _onDidChangeDiffsEvent: vscode.EventEmitter<void> = new vscode.EventEmitter<void>(); // signals a UI refresh on .fire() events
// declared by us, registered with vscode.languages.registerCodeLensProvider()
constructor() {
console.log('Creating DisplayChangesProvider')
// update diffs whenever the event fires
this._onDidChangeDiffsEvent.event(() => {
const editor = vscode.window.activeTextEditor
if (!editor) return
let document = editor.document
const docUriStr = document.uri.toString()
return this._diffsOfDocument[docUriStr]?.flatMap(diff => diff.lenses) ?? []
})
// this acts as a useEffect. Every time text changes, run this
vscode.workspace.onDidChangeTextDocument((e) => {
const editor = vscode.window.activeTextEditor
if (!editor)
return
if (this._weAreEditing)
return
const docUri = editor.document.uri
const docUriStr = docUri.toString()
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
// loop through each change
for (const change of e.contentChanges) {
// here, `change.range` is the range of the original file that gets replaced with `change.text`
// compute net number of newlines lines that were added/removed
const numNewLines = (change.text.match(/\n/g) || []).length
const numLineDeletions = change.range.end.line - change.range.start.line
const deltaNewlines = numNewLines - numLineDeletions
// compute overlap with each diffArea and shrink/elongate the diffArea accordingly
for (const diffArea of diffAreas) {
// if the change is fully within the diffArea, elongate it by the delta amount of newlines
if (change.range.start.line >= diffArea.startLine && change.range.end.line <= diffArea.endLine) {
diffArea.endLine += deltaNewlines
}
// check if the `diffArea` was fully deleted and remove it if so
if (diffArea.startLine > diffArea.endLine) {
//remove it
const index = diffAreas.findIndex(da => da === diffArea)
diffAreas.splice(index, 1)
}
// TODO handle other cases where eg. the change overlaps many diffAreas
}
// if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines
for (const diffArea of diffAreas) {
if (diffArea.startLine > change.range.end.line) {
diffArea.startLine += deltaNewlines
diffArea.endLine += deltaNewlines
}
}
// TODO merge any diffAreas if they overlap with each other as a result from the shift
}
// refresh the diffAreas
this.refreshDiffAreas(docUri)
})
}
// used by us only
public addDiffArea(uri: vscode.Uri, diffArea: BaseDiffArea) {
const uriStr = uri.toString()
// make sure array is defined
if (!this._diffAreasOfDocument[uriStr])
this._diffAreasOfDocument[uriStr] = []
// remove all diffAreas that the new `diffArea` is overlapping with
this._diffAreasOfDocument[uriStr] = this._diffAreasOfDocument[uriStr].filter(da => {
const noOverlap = da.startLine > diffArea.endLine || da.endLine < diffArea.startLine
if (!noOverlap) return false
return true
})
// add `diffArea` to storage
this._diffAreasOfDocument[uriStr].push({
...diffArea,
diffareaid: this._diffareaidPool
})
this._diffareaidPool += 1
}
// used by us only
public refreshDiffAreas(docUri: vscode.Uri) {
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
if (!editor) {
console.log('Error: No active editor!')
return;
}
const docUriStr = docUri.toString()
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
// reset all diffs (we update them below)
this._diffsOfDocument[docUriStr] = []
// for each diffArea
for (const diffArea of diffAreas) {
// get code inside of diffArea
const currentCode = editor.document.getText(new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)).replace(/\r\n/g, '\n')
// compute the diffs
const diffs = findDiffs(diffArea.originalCode, currentCode)
// add the diffs to `this._diffsOfDocument[docUriStr]`
this.addDiffs(editor.document.uri, diffs, diffArea)
// // print diffs
console.log('!CodeBefore:', JSON.stringify(diffArea.originalCode))
console.log('!CodeAfter:', JSON.stringify(currentCode))
console.log('DiffRepr: ', diffs.map(diff => diff.code).join('\n'))
for (const diff of this._diffsOfDocument[docUriStr]) {
console.log('------------')
console.log('deletedCode:', JSON.stringify(diff.deletedCode))
console.log('insertedCode:', JSON.stringify(diff.insertedCode))
console.log('deletedRange:', diff.deletedRange.start.line, diff.deletedRange.end.line,)
console.log('insertedRange:', diff.insertedRange.start.line, diff.insertedRange.end.line,)
}
}
// update green highlighting
editor.setDecorations(
greenDecoration,
(this._diffsOfDocument[docUriStr]
.filter(diff => diff.insertedRange !== undefined)
.map(diff => diff.insertedRange)
)
);
// TODO update red highlighting
// this._diffsOfDocument[docUriStr].map(diff => diff.deletedCode)
// update code lenses
this._onDidChangeDiffsEvent.fire()
}
// used by us only
public addDiffs(docUri: vscode.Uri, diffs: BaseDiff[], diffArea: DiffArea) {
const docUriStr = docUri.toString()
// if no diffs, set diffs to []
if (!this._diffsOfDocument[docUriStr])
this._diffsOfDocument[docUriStr] = []
// add each diff and its codelens to the document
for (let i = diffs.length - 1; i > -1; i -= 1) {
let suggestedDiff = diffs[i]
this._diffsOfDocument[docUriStr].push({
...suggestedDiff,
diffid: this._diffidPool,
// originalCode: suggestedDiff.deletedText,
lenses: [
new vscode.CodeLens(suggestedDiff.insertedRange, { title: 'Accept', command: 'void.acceptDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] }),
new vscode.CodeLens(suggestedDiff.insertedRange, { title: 'Reject', command: 'void.rejectDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] })
]
});
this._diffidPool += 1
}
}
// called on void.acceptDiff
public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
// get document uri
const docUri = editor.document.uri
const docUriStr = docUri.toString()
// get relevant diff
// TODO speed up with hashmap
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
if (diffIdx === -1) {
console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
}
// get relevant diffArea
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
if (diffareaIdx === -1) {
console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
}
const diff = this._diffsOfDocument[docUriStr][diffIdx]
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
// replace `originalCode[diff.deletedRange]` with diff.insertedCode
// TODO add a history event to undo this change
const originalLines = diffArea.originalCode.split('\n');
const relativeStart = diff.deletedRange.start.line - diffArea.originalStartLine
const relativeEnd = diff.deletedRange.end.line - diffArea.originalStartLine
diffArea.originalCode = [
...originalLines.slice(0, relativeStart), // lines before the deleted range
...diff.insertedCode.split('\n'), // inserted lines
...originalLines.slice(relativeEnd + 1) // lines after the deleted range
].join('\n')
// if the diffArea has no changes, remove it
const currentDiffAreaCode = editor.document.getText()
.replace(/\r\n/g, '\n')
.split('\n')
.slice(diffArea.startLine, diffArea.endLine + 1)
.join('\n')
if (diffArea.originalCode === currentDiffAreaCode) { // if the currentDiffAreaCode === diffArea.originalCode, remove the diffArea
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
this._diffAreasOfDocument[docUriStr].splice(index, 1)
}
// refresh the diff area
this.refreshDiffAreas(docUri)
}
// called on void.rejectDiff
public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
// get document uri
const docUri = editor.document.uri
const docUriStr = docUri.toString()
// get relevant diff
// TODO speed up with hashmap
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
if (diffIdx === -1) {
console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
}
// get relevant diffArea
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
if (diffareaIdx === -1) {
console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return;
}
const diff = this._diffsOfDocument[docUriStr][diffIdx]
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
// replace `editorCode[diff.insertedRange]` with diff.deletedCode
const workspaceEdit = new vscode.WorkspaceEdit();
workspaceEdit.replace(docUri, diff.insertedRange, diff.deletedCode)
this._weAreEditing = true
await vscode.workspace.applyEdit(workspaceEdit)
this._weAreEditing = false
// if the diffArea has no changes, remove it
const currentDiffAreaCode = editor.document.getText()
.replace(/\r\n/g, '\n')
.split('\n')
.slice(diffArea.startLine, diffArea.endLine + 1)
.join('\n')
if (diffArea.originalCode === currentDiffAreaCode) { // if the currentDiffAreaCode === diffArea.originalCode, remove the diffArea
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
this._diffAreasOfDocument[docUriStr].splice(index, 1)
}
// refresh the diff area
this.refreshDiffAreas(docUri)
}
}
@@ -1,85 +0,0 @@
// renders the code from `src/sidebar`
import * as vscode from 'vscode';
function generateNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
public static readonly viewId = 'void.viewnumberone';
public webview: Promise<vscode.Webview> // used to send messages to the webview, resolved by _res in resolveWebviewView
private _res: (c: vscode.Webview) => void // used to resolve the webview
private readonly _extensionUri: vscode.Uri
// private _webviewView?: vscode.WebviewView;
private _webviewDeps: string[] = [];
constructor(context: vscode.ExtensionContext) {
// const extensionPath = context.extensionPath // the directory where the extension is installed, might be useful later... was included in webviewProvider code
this._extensionUri = context.extensionUri
let temp_res: typeof this._res | undefined = undefined
this.webview = new Promise((res, rej) => { temp_res = res })
if (!temp_res) throw new Error("Void sidebar provider: resolver was undefined")
this._res = temp_res
}
// called by us
updateWebviewHTML(webview: vscode.Webview) {
this._webviewDeps = []
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/index.js'));
const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri, 'dist/sidebar/styles.css'));
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(this._extensionUri));
const nonce = generateNonce();
const webviewHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom View</title>
<meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${rootUri}/">
<link href="${stylesUri}" rel="stylesheet">
</head>
<body>
<div id="root"></div>
<div id="ctrlkroot"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
webview.html = webviewHTML;
}
// called internally by vscode
resolveWebviewView(
webviewView: vscode.WebviewView,
context: vscode.WebviewViewResolveContext,
token: vscode.CancellationToken,
) {
const webview = webviewView.webview;
webview.options = {
enableScripts: true,
localResourceRoots: [this._extensionUri]
};
this.updateWebviewHTML(webview);
// resolve webview and _webviewView
this._res(webview);
// this._webviewView = webviewView;
}
}
+100
View File
@@ -0,0 +1,100 @@
import * as vscode from 'vscode';
import { AbortRef, OnFinalMessage, OnText, sendLLMMessage } from "./sendLLMMessage"
import { VoidConfig } from '../webviews/common/contextForConfig';
import { searchDiffChunkInstructions, writeFileWithDiffInstructions } from './systemPrompts';
import { throttle } from 'lodash';
import { readFileContentOfUri } from '../extension/extensionLib/readFileContentOfUri';
type Res<T> = ((value: T) => void)
const THRTOTLE_TIME = 100 // minimum time between edits
const LINES_PER_CHUNK = 20 // number of lines to search at a time
const applyCtrlLChangesToFile = throttle(
({ fileUri, newCurrentLine, oldCurrentLine, fullCompletedStr, oldFileStr, debug }: { fileUri: vscode.Uri, newCurrentLine: number, oldCurrentLine: number, fullCompletedStr: string, oldFileStr: string, debug?: string }) => {
// write the change to the file
const WRITE_TO_FILE = (
fullCompletedStr.split('\n').slice(0, newCurrentLine + 1).join('\n') // newFile[:newCurrentLine+1]
+ oldFileStr.split('\n').slice(oldCurrentLine + 1).join('\n') // oldFile[oldCurrentLine+1:]
)
const workspaceEdit = new vscode.WorkspaceEdit()
workspaceEdit.replace(fileUri, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), WRITE_TO_FILE)
vscode.workspace.applyEdit(workspaceEdit)
// highlight the `newCurrentLine` in white
// highlight the remaining part of the file in gray
},
THRTOTLE_TIME, { trailing: true }
)
const applyCtrlK = async ({ fileUri, startLine, endLine, instructions, voidConfig, abortRef }: { fileUri: vscode.Uri, startLine: number, endLine: number, instructions: string, voidConfig: VoidConfig, abortRef: AbortRef }) => {
const fileStr = await readFileContentOfUri(fileUri)
const fileLines = fileStr.split('\n')
const prefix = fileLines.slice(startLine).join('\n')
const suffix = fileLines.slice(endLine + 1).join('\n')
const selection = fileLines.slice(startLine, endLine + 1).join('\n')
const promptContent = `Here is the user's original selection:
\`\`\`
<MID>${selection}</MID>
\`\`\`
The user wants to apply the following instructions to the selection:
${instructions}
Please rewrite the selection following the user's instructions.
Instructions to follow:
1. Follow the user's instructions
2. You may ONLY CHANGE the selection, and nothing else in the file
3. Make sure all brackets in the new selection are balanced the same was as in the original selection
3. Be careful not to duplicate or remove variables, comments, or other syntax by mistake
Complete the following:
\`\`\`
<PRE>${prefix}</PRE>
<SUF>${suffix}</SUF>
<MID>`;
// TODO initialize stream
// update stream
sendLLMMessage({
messages: [{ role: 'user', content: promptContent, }],
onText: async (tokenStr, completionStr) => {
// TODO update stream
// apply the changes
const newCode = `${prefix}\n${completionStr}\n${suffix}`
const workspaceEdit = new vscode.WorkspaceEdit()
workspaceEdit.replace(fileUri, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), newCode)
vscode.workspace.applyEdit(workspaceEdit)
},
onFinalMessage: (completionStr) => {
// TODO end stream
// apply the changes
const newCode = `${prefix}\n${completionStr}\n${suffix}`
const workspaceEdit = new vscode.WorkspaceEdit()
workspaceEdit.replace(fileUri, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), newCode)
vscode.workspace.applyEdit(workspaceEdit)
},
onError: (e) => {
console.error('Error rewriting file with diff', e);
},
voidConfig,
abortRef,
})
}
export { applyCtrlK }
-261
View File
@@ -1,261 +0,0 @@
import * as vscode from 'vscode';
import { OnFinalMessage, OnText, sendLLMMessage, SetAbort } from "./sendLLMMessage"
import { VoidConfig } from '../sidebar/contextForConfig';
import { findDiffs } from '../findDiffs';
import { searchDiffChunkInstructions, writeFileWithDiffInstructions } from './systemPrompts';
import { throttle } from 'lodash';
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
}
type Res<T> = ((value: T) => void)
const THRTOTLE_TIME = 100 // minimum time between edits
const LINES_PER_CHUNK = 20 // number of lines to search at a time
const applyCtrlLChangesToFile = throttle(
({ fileUri, newCurrentLine, oldCurrentLine, fullCompletedStr, oldFileStr, debug }: { fileUri: vscode.Uri, newCurrentLine: number, oldCurrentLine: number, fullCompletedStr: string, oldFileStr: string, debug?: string }) => {
console.log('DEBUG: ', debug)
console.log('oldNext: ', oldCurrentLine)
console.log('newNext: ', newCurrentLine)
console.log('WRITE_TO_FILE1: ', fullCompletedStr.split('\n').slice(0, newCurrentLine + 1).join('\n'))
console.log('WRITE_TO_FILE2: ', oldFileStr.split('\n').slice(oldCurrentLine + 1).join('\n'))
// write the change to the file
const WRITE_TO_FILE = (
fullCompletedStr.split('\n').slice(0, newCurrentLine + 1).join('\n') // newFile[:newCurrentLine+1]
+ oldFileStr.split('\n').slice(oldCurrentLine + 1).join('\n') // oldFile[oldCurrentLine+1:]
)
const workspaceEdit = new vscode.WorkspaceEdit()
workspaceEdit.replace(fileUri, new vscode.Range(0, 0, Number.MAX_SAFE_INTEGER, 0), WRITE_TO_FILE)
vscode.workspace.applyEdit(workspaceEdit)
// highlight the `newCurrentLine` in white
// highlight the remaining part of the file in gray
},
THRTOTLE_TIME, { trailing: true }
)
// `next` is the line after the completed text
// `oldNext` is the same line but in the original file
type CompetedReturn = { isFinished: true, next?: undefined, oldNext?: undefined, } | { isFinished?: undefined, next: number, oldNext: number, }
const generateFileUsingDiffUntilMatchup = ({ fileUri, oldFileStr, completedStr, oldNext, next, diffStr, voidConfig, setAbort }: { fileUri: vscode.Uri, oldFileStr: string, completedStr: string, oldNext: number, next: number, diffStr: string, voidConfig: VoidConfig, setAbort: SetAbort }) => {
const NUM_MATCHUP_TOKENS = 20
const promptContent = `ORIGINAL_FILE
\`\`\`
${oldFileStr}
\`\`\`
DIFF
\`\`\`
${diffStr}
\`\`\`
INSTRUCTIONS
Please finish writing the new file \`NEW_FILE\`. Return ONLY the completion of the file, without any explanation.
NEW_FILE
\`\`\`
${completedStr}
\`\`\`
`
// create a promise that can be awaited
const promise = new Promise<CompetedReturn>((resolve, reject) => {
// get the abort method
let _abort = () => { }
let did_abort = false
sendLLMMessage({
messages: [{ role: 'system', content: writeFileWithDiffInstructions, }, { role: 'user', content: promptContent, }],
onText: (tokenStr, deltaStr) => {
if (did_abort) return;
const fullCompletedStr = completedStr + deltaStr
// diff `originalFileStr` and `newFileStr`
const diffs = findDiffs(oldFileStr, fullCompletedStr)
const lastDiff = diffs[diffs.length - 1]
const oldLineAfterLastDiff = lastDiff.originalEndLine + 1
const newLineAfterLastDiff = lastDiff.endLine + 1
// check if we've generated a diff
const didGenerateDiff = newLineAfterLastDiff > next
// get the line we are currently generating `newCurrentLine`; make sure it never goes past the last diff we've generated
// - if `deltaStr` contains a diff, then _next = newLineAfterLastDiff - 1
// - if it does not contain a diff, then _next = next + deltaStr.split('\n').length - 1
const newCurrentLine = didGenerateDiff ? newLineAfterLastDiff - 1 : next + deltaStr.split('\n').length - 1
const oldCurrentLine = didGenerateDiff ? oldLineAfterLastDiff - 1 : oldNext + (newCurrentLine - next)
// 1. Apply the changes and modify highlighting
applyCtrlLChangesToFile({ fileUri, newCurrentLine, oldCurrentLine, fullCompletedStr, oldFileStr })
// 2. Check for early stopping
// the conditions for early stopping are:
// - we have generated a diff
// - there is matchup with the original file after the diff
const isMatchupAfterDiff = fullCompletedStr.split('\n').slice(newLineAfterLastDiff).join('\n').length > NUM_MATCHUP_TOKENS
if (didGenerateDiff && isMatchupAfterDiff) {
// resolve the promise
resolve({ next: newCurrentLine + 1, oldNext: oldCurrentLine + 1, });
// abort the LLM call
_abort()
did_abort = true
} else {
}
},
onFinalMessage: (deltaStr) => {
const newCompletedStr = completedStr + deltaStr
applyCtrlLChangesToFile({ fileUri, newCurrentLine: Number.MAX_SAFE_INTEGER, oldCurrentLine: Number.MAX_SAFE_INTEGER, fullCompletedStr: newCompletedStr, oldFileStr, debug: 'FINAL' })
resolve({ isFinished: true });
},
onError: (e) => {
resolve({ isFinished: true });
console.error('Error rewriting file with diff', e);
},
voidConfig,
setAbort: (a) => { setAbort(a); _abort = a; },
})
})
return promise
}
const shouldApplyDiffFn = ({ diffStr, fileStr, speculationStr, voidConfig, setAbort }: { diffStr: string, fileStr: string, speculationStr: string, voidConfig: VoidConfig, setAbort: SetAbort }) => {
const promptContent = `DIFF
\`\`\`
${diffStr}
\`\`\`
FILES
\`\`\`
${fileStr}
\`\`\`
SELECTION
\`\`\`
${speculationStr}
\`\`\`
Return \`true\` if ANY part of the chunk should be modified, and \`false\` if it should not be modified. You should respond only with \`true\` or \`false\` and nothing else.
`
// create new promise
const promise = new Promise<boolean>((resolve, reject) => {
// send message to LLM
sendLLMMessage({
messages: [{ role: 'system', content: searchDiffChunkInstructions, }, { role: 'user', content: promptContent, }],
onFinalMessage: (finalMessage) => {
const containsTrue = finalMessage
.slice(-10) // check for `true` in last 10 characters
.toLowerCase()
.includes('true')
resolve(containsTrue)
},
onError: (e) => {
resolve(false);
console.error('Error in shouldApplyDiff: ', e)
},
onText: () => { },
voidConfig,
setAbort,
})
})
// return the promise
return promise
}
// 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 ({ fileUri, oldFileStr, diffStr, voidConfig, setAbort }: { fileUri: vscode.Uri, oldFileStr: string, diffStr: string, voidConfig: VoidConfig, setAbort: SetAbort }) => {
// stateful variables
let next = 0
let oldNext = 0
while (next < oldFileStr.split('\n').length) {
console.log('next line: ', next)
// get the chunk
const chunkStr = oldFileStr.split('\n').slice(next, next + LINES_PER_CHUNK).join('\n')
// ask LLM if we should apply the diff to the chunk
const __start = new Date().getTime()
let shouldApplyDiff = await shouldApplyDiffFn({ fileStr: oldFileStr, speculationStr: chunkStr, diffStr, voidConfig, setAbort })
const __end = new Date().getTime()
if (!shouldApplyDiff) { // should not change the chunk
console.log('KEEP CHUNK time: ', __end - __start)
next += LINES_PER_CHUNK
oldNext += LINES_PER_CHUNK
continue;
}
// ask LLM to rewrite file with diff (if there is significant matchup with the original file, we stop rewriting)
// make vscode read uri = 'asdasd'
const ___start = new Date().getTime()
const completedStr = (await readFileContentOfUri(fileUri)).split('\n').slice(0, next).join('\n');
const result = await generateFileUsingDiffUntilMatchup({ fileUri, oldFileStr, completedStr, oldNext, next, diffStr, voidConfig, setAbort, })
const ___end = new Date().getTime()
console.log('EDIT CHUNK time: ', ___end - ___start);
// if we are finished, stop the loop
if (result.isFinished) {
break;
}
next = result.next
oldNext = result.oldNext
}
}
export { applyDiffLazily }
+36 -33
View File
@@ -1,17 +1,14 @@
import Anthropic from '@anthropic-ai/sdk';
import OpenAI from 'openai';
import { Ollama } from 'ollama/browser'
import { VoidConfig } from '../sidebar/contextForConfig';
import { VoidConfig } from '../webviews/common/contextForConfig'
export type AbortRef = { current: (() => void) | null }
export type OnText = (newText: string, fullText: string) => void
export type OnFinalMessage = (input: string) => void
export type SetAbort = (abort: () => void) => void
export type LLMMessageAnthropic = {
role: 'user' | 'assistant',
content: string,
@@ -28,25 +25,30 @@ type SendLLMMessageFnTypeInternal = (params: {
onFinalMessage: OnFinalMessage,
onError: (error: string) => void,
voidConfig: VoidConfig,
setAbort: SetAbort,
abortRef: AbortRef,
}) => void
type SendLLMMessageFnTypeExternal = (params: {
messages: LLMMessage[],
onText: OnText,
onFinalMessage: (input: string) => void,
onFinalMessage: (fullText: string) => void,
onError: (error: string) => void,
voidConfig: VoidConfig | null,
setAbort: SetAbort,
})
=> void
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)
if (Number.isNaN(int))
return undefined
return int
}
// Anthropic
const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, setAbort }) => {
const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig }) => {
const anthropic = new Anthropic({ apiKey: voidConfig.anthropic.apikey, dangerouslyAllowBrowser: true }); // defaults to process.env["ANTHROPIC_API_KEY"]
@@ -97,30 +99,32 @@ const sendAnthropicMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFi
did_abort = true
stream.controller.abort() // TODO need to test this to make sure it works, it might throw an error
}
setAbort(abort)
return { abort }
};
// OpenAI, OpenRouter, OpenAICompatible
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, setAbort }) => {
const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
let didAbort = false
let fullText = ''
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
let abort: () => void = () => {
abortRef.current = () => {
didAbort = true;
};
let openai: OpenAI
let options: OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming
let maxTokens = parseMaxTokensStr(voidConfig.default.maxTokens)
if (voidConfig.default.whichApi === 'openAI') {
openai = new OpenAI({ apiKey: voidConfig.openAI.apikey, dangerouslyAllowBrowser: true });
options = { model: voidConfig.openAI.model, messages: messages, stream: true, max_completion_tokens: parseInt(voidConfig.default.maxTokens) }
options = { model: voidConfig.openAI.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else if (voidConfig.default.whichApi === 'openRouter') {
openai = new OpenAI({
@@ -130,11 +134,11 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
"X-Title": 'Void Editor', // Optional. Shows in rankings on openrouter.ai.
},
});
options = { model: voidConfig.openRouter.model, messages: messages, stream: true, max_completion_tokens: parseInt(voidConfig.default.maxTokens) }
options = { model: voidConfig.openRouter.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else if (voidConfig.default.whichApi === 'openAICompatible') {
openai = new OpenAI({ baseURL: voidConfig.openAICompatible.endpoint, apiKey: voidConfig.openAICompatible.apikey, dangerouslyAllowBrowser: true })
options = { model: voidConfig.openAICompatible.model, messages: messages, stream: true, max_completion_tokens: parseInt(voidConfig.default.maxTokens) }
options = { model: voidConfig.openAICompatible.model, messages: messages, stream: true, max_completion_tokens: maxTokens }
}
else {
console.error(`sendOpenAIMsg: invalid whichApi: ${voidConfig.default.whichApi}`)
@@ -144,7 +148,7 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
openai.chat.completions
.create(options)
.then(async response => {
abort = () => {
abortRef.current = () => {
// response.controller.abort()
didAbort = true;
}
@@ -172,18 +176,17 @@ const sendOpenAIMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinal
}
})
setAbort(abort)
};
// Ollama
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, setAbort }) => {
export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
let didAbort = false
let fullText = ""
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
let abort = () => {
abortRef.current = () => {
didAbort = true;
};
@@ -196,8 +199,8 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
options: { num_predict: parseInt(voidConfig.default.maxTokens) } // this is max_tokens
})
.then(async stream => {
abort = () => {
// ollama.abort()
abortRef.current = () => {
// stream.abort()
didAbort = true
}
// iterate through the stream
@@ -215,7 +218,6 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
onError(error)
})
setAbort(abort);
};
@@ -224,13 +226,15 @@ export const sendOllamaMsg: SendLLMMessageFnTypeInternal = ({ messages, onText,
// https://docs.greptile.com/api-reference/query
// https://docs.greptile.com/quickstart#sample-response-streamed
const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, setAbort }) => {
const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
let didAbort = false
let fullText = ''
// if abort is called, onFinalMessage is NOT called, and no later onTexts are called either
let abort: () => void = () => { didAbort = true }
abortRef.current = () => {
didAbort = true
}
fetch('https://api.greptile.com/v2/query', {
@@ -285,12 +289,11 @@ const sendGreptileMsg: SendLLMMessageFnTypeInternal = ({ messages, onText, onFin
onError(e)
});
setAbort(abort)
}
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, setAbort }) => {
export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText, onFinalMessage, onError, voidConfig, abortRef }) => {
if (!voidConfig) return;
// trim message content (Anthropic and other providers give an error if there is trailing whitespace)
@@ -298,15 +301,15 @@ export const sendLLMMessage: SendLLMMessageFnTypeExternal = ({ messages, onText,
switch (voidConfig.default.whichApi) {
case 'anthropic':
return sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, setAbort });
return sendAnthropicMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
case 'openAI':
case 'openRouter':
case 'openAICompatible':
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, setAbort });
return sendOpenAIMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
case 'ollama':
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, setAbort });
return sendOllamaMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
case 'greptile':
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, setAbort });
return sendGreptileMsg({ messages, onText, onFinalMessage, onError, voidConfig, abortRef });
default:
onError(`Error: whichApi was ${voidConfig.default.whichApi}, which is not recognized!`)
}
+15 -18
View File
@@ -1,6 +1,6 @@
import * as vscode from 'vscode';
import { PartialVoidConfig } from '../sidebar/contextForConfig';
import { PartialVoidConfig } from '../webviews/common/contextForConfig'
@@ -10,26 +10,23 @@ type CodeSelection = { selectionStr: string, selectionRange: vscode.Range, fileP
type File = { filepath: vscode.Uri, content: string }
// an area that is currently being diffed
type BaseDiffArea = {
// use `startLine` and `endLine` instead of `range` for mutibility
// bounds are relative to the file, inclusive
startLine: number;
endLine: number;
type DiffArea = {
diffareaid: number,
startLine: number,
endLine: number,
originalStartLine: number,
originalEndLine: number,
originalCode: string, // the original chunk of code (not necessarily the whole file)
// `newCode: string,` is not included because it is the code in the actual file, `document.text()[startline: endLine + 1]`
sweepIndex: number | null // null iff not sweeping
}
type DiffArea = BaseDiffArea & { diffareaid: number }
// the return type of diff creator
type BaseDiff = {
code: string; // representation of the diff in text
deletedRange: vscode.Range; // relative to the original file, inclusive
insertedRange: vscode.Range;
deletedCode: string; // relative to the new file, inclusive
insertedCode: string;
type: 'edit' | 'insertion' | 'deletion';
// repr: string; // representation of the diff in text
originalRange: vscode.Range;
originalCode: string;
range: vscode.Range;
code: string;
}
// each diff on the user's screen
@@ -53,7 +50,7 @@ type MessageToSidebar = (
// sidebar -> editor
type MessageFromSidebar = (
| { type: 'applyChanges', code: string } // user clicks "apply" in the sidebar
| { type: 'applyChanges', diffRepr: string } // user clicks "apply" in the sidebar
| { type: 'requestFiles', filepaths: vscode.Uri[] }
| { type: 'getPartialVoidConfig' }
| { type: 'persistPartialVoidConfig', partialVoidConfig: PartialVoidConfig }
@@ -92,8 +89,8 @@ type ChatMessage =
}
export {
BaseDiff, BaseDiffArea,
Diff, DiffArea,
BaseDiff, Diff,
DiffArea,
CodeSelection,
File,
MessageFromSidebar,
+37 -52
View File
@@ -1,12 +1,13 @@
import * as vscode from 'vscode';
import { ApprovalCodeLensProvider } from './ApprovalCodeLensProvider';
import { ChatThreads, MessageFromSidebar, MessageToSidebar } from './common/shared_types';
import { SidebarWebviewProvider } from './SidebarWebviewProvider';
import { DiffArea, ChatThreads, MessageFromSidebar, MessageToSidebar } from './common/shared_types';
import { v4 as uuidv4 } from 'uuid'
import { applyDiffLazily } from './common/ctrlL';
import { getVoidConfig } from './sidebar/contextForConfig';
import { DiffProvider } from './DiffProvider';
// import { getVoidConfig } from './sidebar/contextForConfig';
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' {
@@ -23,13 +24,6 @@ declare module 'vscode' {
}
}
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
}
const roundRangeToLines = (selection: vscode.Selection) => {
return new vscode.Range(selection.start.line, 0, selection.end.line, Number.MAX_SAFE_INTEGER)
}
@@ -50,6 +44,15 @@ export function activate(context: vscode.ExtensionContext) {
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
@@ -89,16 +92,16 @@ export function activate(context: vscode.ExtensionContext) {
})
);
// 3. Show an approve/reject
const displayChangesProvider = new DiffProvider();
// context.subscriptions.push(vscode.languages.registerCodeLensProvider('*', displayChangesProvider));
// 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) => {
displayChangesProvider.acceptDiff(params)
diffProvider.acceptDiff(params)
}));
context.subscriptions.push(vscode.commands.registerCommand('void.rejectDiff', async (params) => {
displayChangesProvider.rejectDiff(params)
diffProvider.rejectDiff(params)
}));
// 5. Receive messages from sidebar
@@ -119,6 +122,8 @@ export function activate(context: vscode.ExtensionContext) {
// 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
@@ -137,41 +142,21 @@ export function activate(context: vscode.ExtensionContext) {
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))
// // create an area to show diffs
// const diffArea = {
// startLine: 0, // in ctrl+L the start and end lines are the full document
// endLine: editor.document.lineCount,
// originalStartLine: 0,
// originalEndLine: editor.document.lineCount,
// originalCode: await readFileContentOfUri(editor.document.uri),
// }
// displayChangesProvider.addDiffArea(editor.document.uri, diffArea)
// write new code `m.code` to the document
// TODO update like this:
// this._weAreEditing = true
// await vscode.workspace.applyEdit(workspaceEdit)
// await vscode.workspace.save(docUri)
// this._weAreEditing = false
const fileUri = editor.document.uri
const fileStr = await readFileContentOfUri(fileUri)
const voidConfig = getVoidConfig(context.globalState.get('partialVoidConfig') ?? {})
let abort = () => { } // TODO this is unused
// apply the change
applyDiffLazily({ fileUri, oldFileStr: fileStr, diffStr: m.code, voidConfig, setAbort: (a) => { abort = a } })
// set the file equal to the change
// await editor.edit(editBuilder => {
// editBuilder.replace(new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER), m.code);
// });
// rediff the changes based on the diffAreas
displayChangesProvider.refreshLenses(editor, editor.document.uri.toString())
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') ?? {}
@@ -0,0 +1,560 @@
import * as vscode from 'vscode';
import { findDiffs } from './findDiffs';
import { throttle } from 'lodash';
import { DiffArea, BaseDiff, Diff } from '../common/shared_types';
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
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:...
})
const lightGrayDecoration = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(218 218 218 / .2)',
isWholeLine: true,
})
const darkGrayDecoration = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgb(148 148 148 / .2)',
isWholeLine: true,
})
// responsible for displaying diffs and showing accept/reject buttons
export class DiffProvider implements vscode.CodeLensProvider {
private _originalFileOfDocument: { [docUriStr: string]: string } = {}
private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {}
private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {}
private _diffareaidPool = 0
private _diffidPool = 0
// 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;
// used internally by vscode
public provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): vscode.ProviderResult<vscode.CodeLens[]> {
const docUriStr = document.uri.toString()
return this._diffsOfDocument[docUriStr]?.flatMap(diff => diff.lenses) ?? []
}
// declared by us, registered with vscode.languages.registerCodeLensProvider()
constructor() {
console.log('Creating DisplayChangesProvider')
// this acts as a useEffect every time text changes
vscode.workspace.onDidChangeTextDocument((e) => {
const editor = vscode.window.activeTextEditor
if (!editor) return
const docUriStr = editor.document.uri.toString()
const changes = e.contentChanges.map(c => ({ startLine: c.range.start.line, endLine: c.range.end.line, text: c.text, }))
// on user change, grow/shrink/merge/delete diff areas
this.refreshDiffAreasModel(docUriStr, changes, 'currentFile')
// refresh the diffAreas
this.refreshStylesAndDiffs(docUriStr)
})
}
// used by us only
public createDiffArea(uri: vscode.Uri, partialDiffArea: Omit<DiffArea, 'diffareaid'>, originalFile: string) {
const uriStr = uri.toString()
this._originalFileOfDocument[uriStr] = originalFile
// make sure array is defined
if (!this._diffAreasOfDocument[uriStr]) this._diffAreasOfDocument[uriStr] = []
// remove all diffAreas that the new `diffArea` is overlapping with
this._diffAreasOfDocument[uriStr] = this._diffAreasOfDocument[uriStr].filter(da => {
const noOverlap = da.startLine > partialDiffArea.endLine || da.endLine < partialDiffArea.startLine
if (!noOverlap) return false
return true
})
// add `diffArea` to storage
const diffArea = {
...partialDiffArea,
diffareaid: this._diffareaidPool
}
this._diffAreasOfDocument[uriStr].push(diffArea)
this._diffareaidPool += 1
return diffArea
}
// used by us only
// changes the start/line locations based on the changes that were recently made. does not change any of the diffs in the diff areas
// changes tells us how many lines were inserted/deleted so we can grow/shrink the diffAreas accordingly
public refreshDiffAreasModel(docUriStr: string, changes: { text: string, startLine: number, endLine: number }[], changesTo: 'originalFile' | 'currentFile') {
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
let endName
let startName
if (changesTo === 'originalFile') {
endName = 'originalEndLine' as const
startName = 'originalStartLine' as const
} else {
endName = 'endLine' as const
startName = 'startLine' as const
}
for (const change of changes) {
// here, `change.range` is the range of the original file that gets replaced with `change.text`
// compute net number of newlines lines that were added/removed
const numNewLines = (change.text.match(/\n/g) || []).length
const numLineDeletions = change.endLine - change.startLine
const deltaNewlines = numNewLines - numLineDeletions
// compute overlap with each diffArea and shrink/elongate the diffArea accordingly
for (const diffArea of diffAreas) {
// if the change is fully within the diffArea, elongate it by the delta amount of newlines
if (change.startLine >= diffArea[startName] && change.endLine <= diffArea[endName]) {
diffArea[endName] += deltaNewlines
}
// check if the `diffArea` was fully deleted and remove it if so
if (diffArea[startName] > diffArea[endName]) {
//remove it
const index = diffAreas.findIndex(da => da === diffArea)
diffAreas.splice(index, 1)
}
// TODO handle other cases where eg. the change overlaps many diffAreas
}
// if a diffArea is below the last character of the change, shift the diffArea up/down by the delta amount of newlines
for (const diffArea of diffAreas) {
if (diffArea[startName] > change.endLine) {
diffArea[startName] += deltaNewlines
diffArea[endName] += deltaNewlines
}
}
// TODO merge any diffAreas if they overlap with each other as a result from the shift
}
}
// used by us only
// refreshes all the diffs inside each diff area, and refreshes the styles
public refreshStylesAndDiffs(docUriStr: string) {
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
if (!editor) {
console.log('Error: No active editor!')
return;
}
const originalFile = this._originalFileOfDocument[docUriStr]
if (!originalFile) {
console.log('Error: No original file!')
return;
}
const diffAreas = this._diffAreasOfDocument[docUriStr] || []
// reset all diffs (we update them below)
this._diffsOfDocument[docUriStr] = []
// for each diffArea
for (const diffArea of diffAreas) {
// get code inside of diffArea
const originalCode = originalFile.split('\n').slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
const currentCode = editor.document.getText(new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)).replace(/\r\n/g, '\n')
// compute the diffs
const diffs = findDiffs(originalCode, currentCode)
// 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
editor.setDecorations(
greenDecoration,
(this._diffsOfDocument[docUriStr]
.filter(diff => diff.range !== undefined)
.map(diff => diff.range)
)
);
// 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(
lightGrayDecoration,
(this._diffAreasOfDocument[docUriStr]
.filter(diffArea => diffArea.sweepIndex !== null)
.map(diffArea => {
return new vscode.Range(diffArea.sweepIndex! + 1, 0, diffArea.endLine, 0)
})
)
)
// TODO update red highlighting
// this._diffsOfDocument[docUriStr].map(diff => diff.deletedCode)
// update code lenses
this._onDidChangeCodeLenses.fire()
}
// used by us only
public createDiffs(docUri: vscode.Uri, diffs: BaseDiff[], diffArea: DiffArea) {
const docUriStr = docUri.toString()
// if no diffs, set diffs to []
if (!this._diffsOfDocument[docUriStr])
this._diffsOfDocument[docUriStr] = []
// add each diff and its codelens to the document
for (let i = diffs.length - 1; i > -1; i -= 1) {
let suggestedDiff = diffs[i]
this._diffsOfDocument[docUriStr].push({
...suggestedDiff,
diffid: this._diffidPool,
// originalCode: suggestedDiff.deletedText,
lenses: [
new vscode.CodeLens(suggestedDiff.range, { title: 'Accept', command: 'void.acceptDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] }),
new vscode.CodeLens(suggestedDiff.range, { title: 'Reject', command: 'void.rejectDiff', arguments: [{ diffid: this._diffidPool, diffareaid: diffArea.diffareaid }] })
]
});
this._diffidPool += 1
}
}
// called on void.acceptDiff
public async acceptDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
const docUriStr = editor.document.uri.toString()
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
if (diffIdx === -1) { console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return; }
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
if (diffareaIdx === -1) { console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return; }
const diff = this._diffsOfDocument[docUriStr][diffIdx]
const originalFile = this._originalFileOfDocument[docUriStr]
const currentFile = await readFileContentOfUri(editor.document.uri)
// Fixed: Handle newlines properly by splitting into lines and joining with proper newlines
const originalLines = originalFile.split('\n');
const currentLines = currentFile.split('\n');
// Get the changed lines from current file
const changedLines = currentLines.slice(diff.range.start.line, diff.range.end.line + 1);
// Create new original file content by replacing the affected lines
const newOriginalLines = [
...originalLines.slice(0, diff.originalRange.start.line),
...changedLines,
...originalLines.slice(diff.originalRange.end.line + 1)
];
this._originalFileOfDocument[docUriStr] = newOriginalLines.join('\n');
// Update diff areas based on the change
this.refreshDiffAreasModel(docUriStr, [{
text: changedLines.join('\n'),
startLine: diff.originalRange.start.line,
endLine: diff.originalRange.end.line
}], 'originalFile')
// Check if diffArea should be removed
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
const originalArea = newOriginalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
console.log('ACCEPT change', changedLines.join('\n'), diff.originalRange.start.line, diff.originalRange.end.line)
console.log('ACCEPT area lines', diffArea.startLine, diffArea.endLine, diffArea.originalStartLine, diffArea.originalEndLine)
console.log('ACCEPT currentArea', currentArea)
console.log('ACCEPT originalArea', originalArea)
if (originalArea === currentArea) {
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
this._diffAreasOfDocument[docUriStr].splice(index, 1)
}
this.refreshStylesAndDiffs(docUriStr)
}
// called on void.rejectDiff
public async rejectDiff({ diffid, diffareaid }: { diffid: number, diffareaid: number }) {
const editor = vscode.window.activeTextEditor
if (!editor)
return
const docUriStr = editor.document.uri.toString()
const diffIdx = this._diffsOfDocument[docUriStr].findIndex(diff => diff.diffid === diffid);
if (diffIdx === -1) { console.error('Error: DiffID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return; }
const diffareaIdx = this._diffAreasOfDocument[docUriStr].findIndex(diff => diff.diffareaid === diffareaid);
if (diffareaIdx === -1) { console.error('Error: DiffAreaID could not be found: ', diffid, diffareaid, this._diffsOfDocument[docUriStr], this._diffAreasOfDocument[docUriStr]); return; }
const diff = this._diffsOfDocument[docUriStr][diffIdx]
// Apply the rejection by replacing with original code
// we don't have to edit the original or final file; just do a workspace edit so the code equals the original code
const workspaceEdit = new vscode.WorkspaceEdit();
workspaceEdit.replace(editor.document.uri, diff.range, diff.originalCode)
await vscode.workspace.applyEdit(workspaceEdit)
// Check if diffArea should be removed
const originalFile = this._originalFileOfDocument[docUriStr]
const currentFile = await readFileContentOfUri(editor.document.uri)
const diffArea = this._diffAreasOfDocument[docUriStr][diffareaIdx]
const currentLines = currentFile.split('\n');
const originalLines = originalFile.split('\n');
const currentArea = currentLines.slice(diffArea.startLine, diffArea.endLine + 1).join('\n')
const originalArea = originalLines.slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
console.log('REJECT diff lines', diff.originalRange.start.line, diff.originalRange.end.line)
console.log('REJECT area lines', diffArea.startLine, diffArea.endLine, diffArea.originalStartLine, diffArea.originalEndLine)
console.log('REJECT currentArea', currentArea)
console.log('REJECT originalArea', originalArea)
if (originalArea === currentArea) {
const index = this._diffAreasOfDocument[docUriStr].findIndex(da => da.diffareaid === diffArea.diffareaid)
this._diffAreasOfDocument[docUriStr].splice(index, 1)
}
this.refreshStylesAndDiffs(docUriStr)
}
// used by us only
public updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => {
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
if (!editor) {
console.log('Error: No active editor!')
return;
}
// original code all diffs are based on in the code
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
// these are two different coordinate systems - new and old line number
let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
let oldFileStartLine: number // get original[oldStartingPoint...]
if (!lastDiff) {
// if the writing is identical so far, display no changes
newFileEndLine = 0
oldFileStartLine = 0
}
else {
if (lastDiff.type === 'insertion') {
newFileEndLine = lastDiff.range.end.line
oldFileStartLine = lastDiff.originalRange.start.line
}
else if (lastDiff.type === 'deletion') {
newFileEndLine = lastDiff.range.start.line
oldFileStartLine = lastDiff.originalRange.start.line
}
else if (lastDiff.type === 'edit') {
newFileEndLine = lastDiff.range.end.line
oldFileStartLine = lastDiff.originalRange.start.line
}
else {
throw new Error(`updateStream: diff.type not recognized: ${lastDiff.type}`)
}
}
// display
const newFileTop = newDiffAreaCode.split('\n').slice(0, newFileEndLine + 1).join('\n')
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
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)
}
/*
import * as vscode from 'vscode';
import { SuggestedEdit } from './findDiffs';
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:...
})
export class DiffProvider {
originalCodeOfDocument: { [docUri: string]: string }
diffsOfDocument: {
[docUri: string]: {
startLine,
startCol,
endLine,
endCol,
originalText,
inset,
diffid,
}
}
// sweep
currentLine: { [docUri: string]: undefined | number }
weAreEditing: boolean = false
constructor() {
vscode.workspace.onDidChangeTextDocument((e) => {
// on user change, grow/shrink/merge/delete diff AREAS
// you dont have to do anything to the diffs here bc they all get recomputed in refresh()
// user changes only get highlighted if theyre in a diffarea
// go thru all diff areas and adjust line numbers based on the user's change
this.refreshStyles(e.document.uri.toString())
})
}
// refreshes styles on page
refreshStyles(docUriStr: string) {
if (this.weAreEditing) return
// recompute all diffs on the page
// run inset.dispose() on all diffs
// original and current code -> diffs
// originalCodeOfDocument[docUriStr]
// create new diffs
const inset = vscode.window.createWebviewTextEditorInset(editor, lineStart, height, {})
inset.webview.html = `
<html>
<body style="pointer-events:none;">Hello World!</body>
</html>
`;
}
// called on void.acceptDiff
public async acceptDiff({ diffid }: { diffid: number }) {
// update original based on the diff
// refresh()
}
// called on void.rejectDiff
public async rejectDiff({ diffid }: { diffid: number }) {
// get diffs[diffid]
// revert current file based on diff
// refresh()
}
// sweep
initializeSweep({ startLine }) {
// reject all diffs on the page
// store original code
// currentLine=start of sweep
}
onUpdateSweep(addedText) {
// update final
// refresh() ?
// currentLine += number of newlines in addedText
}
onAbortSweep() {
}
}
*/
+185
View File
@@ -0,0 +1,185 @@
import * as vscode from 'vscode';
import { AbortRef, sendLLMMessage } from '../common/sendLLMMessage';
import { DiffArea } from '../common/shared_types';
import { writeFileWithDiffInstructions, searchDiffChunkInstructions } from '../common/systemPrompts';
import { VoidConfig } from '../webviews/common/contextForConfig';
import { DiffProvider } from './DiffProvider';
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
const LINES_PER_CHUNK = 20 // number of lines to search at a time
type CompetedReturn = { isFinished: true, } | { isFinished?: undefined, }
const streamChunk = ({ diffProvider, docUri, oldFileStr, completedStr, diffRepr, diffArea, voidConfig, abortRef }: { diffProvider: DiffProvider, docUri: vscode.Uri, oldFileStr: string, completedStr: string, diffRepr: string, voidConfig: VoidConfig, diffArea: DiffArea, abortRef: AbortRef }) => {
const NUM_MATCHUP_TOKENS = 20
const promptContent = `ORIGINAL_FILE
\`\`\`
${oldFileStr}
\`\`\`
DIFF
\`\`\`
${diffRepr}
\`\`\`
INSTRUCTIONS
Please finish writing the new file \`NEW_FILE\`. Return ONLY the completion of the file, without any explanation.
NEW_FILE
\`\`\`
${completedStr}
\`\`\`
`
// create a promise that can be awaited
return new Promise<CompetedReturn>((resolve, reject) => {
let isAnyChangeSoFar = false
// make LLM complete the file to include the diff
sendLLMMessage({
messages: [{ role: 'system', content: writeFileWithDiffInstructions, }, { role: 'user', content: promptContent, }],
onText: (newText, fullText) => {
const fullCompletedStr = completedStr + fullText
diffProvider.updateStream(docUri.toString(), diffArea, fullCompletedStr)
// if there was any change from the original file
if (!oldFileStr.includes(fullCompletedStr)) {
isAnyChangeSoFar = true
}
const isRecentMatchup = false
// the final NUM_MATCHUP_TOKENS characters of fullCompletedStr are the same as the final NUM_MATCHUP_TOKENS characters of the last item in the diffs of oldFileStr that had 0 changes
if (isAnyChangeSoFar && isRecentMatchup) {
diffProvider.updateStream(docUri.toString(), diffArea, fullCompletedStr)
// TODO resolve the promise
// resolve({ speculativeIndex: newCurrentLine + 1 });
// abort the LLM call
abortRef.current?.()
}
},
onFinalMessage: (fullText) => {
const newCompletedStr = completedStr + fullText
diffProvider.updateStream(docUri.toString(), diffArea, newCompletedStr)
resolve({ isFinished: true });
},
onError: (e) => {
resolve({ isFinished: true });
console.error('Error rewriting file with diff', e);
},
voidConfig,
abortRef,
})
})
}
const shouldApplyDiff = ({ diffRepr, oldFileStr: fileStr, speculationStr, voidConfig, abortRef }: { diffRepr: string, oldFileStr: string, speculationStr: string, voidConfig: VoidConfig, abortRef: AbortRef }) => {
const promptContent = `DIFF
\`\`\`
${diffRepr}
\`\`\`
FILES
\`\`\`
${fileStr}
\`\`\`
SELECTION
\`\`\`
${speculationStr}
\`\`\`
Return \`true\` if ANY part of the chunk should be modified, and \`false\` if it should not be modified. You should respond only with \`true\` or \`false\` and nothing else.
`
// create new promise
return new Promise<boolean>((resolve, reject) => {
// send message to LLM
sendLLMMessage({
messages: [{ role: 'system', content: searchDiffChunkInstructions, }, { role: 'user', content: promptContent, }],
onFinalMessage: (finalMessage) => {
const containsTrue = finalMessage
.slice(-10) // check for `true` in last 10 characters
.toLowerCase()
.includes('true')
resolve(containsTrue)
},
onError: (e) => {
resolve(false);
console.error('Error in shouldApplyDiff: ', e)
},
onText: () => { },
voidConfig,
abortRef,
})
})
}
// 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 }) => {
// stateful variables
let speculativeIndex = 0
let writtenTextSoFar: string[] = []
while (speculativeIndex < oldFileStr.split('\n').length) {
const chunkStr = oldFileStr.split('\n').slice(speculativeIndex, speculativeIndex + LINES_PER_CHUNK).join('\n')
// ask LLM if we should apply the diff to the chunk
const START = new Date().getTime()
let shouldApplyDiff_ = await shouldApplyDiff({ oldFileStr, speculationStr: chunkStr, diffRepr, voidConfig, abortRef })
const END = new Date().getTime()
// if should not change the chunk
if (!shouldApplyDiff_) {
console.log('KEEP CHUNK time: ', END - START)
speculativeIndex += LINES_PER_CHUNK
writtenTextSoFar.push(chunkStr)
diffProvider.updateStream(docUri.toString(), diffArea, writtenTextSoFar.join('\n'))
continue;
}
// ask LLM to rewrite file with diff (if there is significant matchup with the original file, we stop rewriting)
const START2 = new Date().getTime()
const completedStr = (await readFileContentOfUri(docUri)).split('\n').slice(0, speculativeIndex).join('\n');
const result = await streamChunk({ diffProvider, docUri, oldFileStr, completedStr, diffRepr, voidConfig, diffArea, abortRef, })
const END2 = new Date().getTime()
console.log('EDIT CHUNK time: ', END2 - START2);
// if we are finished, stop the loop
if (result.isFinished) {
break;
}
// TODO
// speculativeIndex = result.speculativeIndex
}
}
export { applyDiffLazily }
+203
View File
@@ -0,0 +1,203 @@
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';
// 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 } })
);
// 1.5
const ctrlKWebviewProvider = new CtrlKWebviewProvider(context)
// 2. ctrl+l
context.subscriptions.push(
vscode.commands.registerCommand('void.ctrl+l', () => {
const editor = vscode.window.activeTextEditor
if (!editor) return
// 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)
ctrlKWebviewProvider.onPressCtrlK()
})
);
// 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
// })
// )
}
@@ -0,0 +1,14 @@
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
};
// 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
// }
@@ -0,0 +1,46 @@
import * as vscode from 'vscode'
function generateNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
// 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) => {
// 'dist/sidebar/index.js'
// 'dist/sidebar/styles.css'
const scriptUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, jsOutLocation));
const stylesUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri, cssOutLocation));
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri));
const nonce = generateNonce();
const webviewHTML = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Custom View</title>
<meta http-equiv="Content-Security-Policy" content="img-src vscode-resource: https:; script-src 'nonce-${nonce}'; style-src vscode-resource: 'unsafe-inline' http: https: data:;">
<base href="${rootUri}/">
<link href="${stylesUri}" rel="stylesheet">
</head>
<body>
<div id="root" ${props ? `data-void-props="${encodeURIComponent(JSON.stringify(props))}"` : ''}></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
webview.html = webviewHTML
webview.options = {
enableScripts: true,
localResourceRoots: [extensionUri]
};
}
+132
View File
@@ -0,0 +1,132 @@
import { Range } from 'vscode';
import { diffLines, Change } from 'diff';
import { BaseDiff } from '../common/shared_types';
// class Range {
// range: any;
// constructor(startLine, startCol, endLine, endCol) {
// const range = {
// startLine,
// startCol,
// endLine,
// endCol,
// };
// this.range = range;
// }
// }
// Andrew diff algo:
export type SuggestedEdit = {
// start/end of current file
newRange: Range;
// start/end of original file
originalRange: Range;
type: 'insertion' | 'deletion' | 'edit',
originalContent: string, // original content (originalfile[originalStart...originalEnd])
newContent: string,
}
export function findDiffs(oldStr: string, newStr: string) {
// an ordered list of every original line, line added to the new file, and line removed from the old file (order is unambiguous, think about it)
const lineByLineChanges: Change[] = diffLines(oldStr, newStr);
lineByLineChanges.push({ value: '' }) // add a dummy so we flush any streaks we haven't yet at the very end (!line.added && !line.removed)
let oldFileLineNum: number = 0;
let newFileLineNum: number = 0;
let streakStartInNewFile: number | undefined = undefined
let streakStartInOldFile: number | undefined = undefined
let oldStrLines = oldStr.split('\n')
let newStrLines = newStr.split('\n')
const replacements: BaseDiff[] = []
for (let line of lineByLineChanges) {
// no change on this line
if (!line.added && !line.removed) {
// do nothing
// if we were on a streak of +s and -s, end it
if (streakStartInNewFile !== undefined) {
let type: 'edit' | 'insertion' | 'deletion' = 'edit'
let startLine = streakStartInNewFile
let endLine = newFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
let startCol = 0
let endCol = Number.MAX_SAFE_INTEGER
let originalStartLine = streakStartInOldFile!
let originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
let originalStartCol = 0
let originalEndCol = Number.MAX_SAFE_INTEGER
let newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
let originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n')
// if the range is empty, mark it as a deletion / insertion (both won't be true at once)
// DELETION
if (endLine === startLine - 1) {
type = 'deletion'
endLine = startLine
startCol = 0
endCol = 0
newContent += '\n'
}
// INSERTION
else if (originalEndLine === originalStartLine - 1) {
type = 'insertion'
originalEndLine = originalStartLine
originalStartCol = 0
originalEndCol = 0
}
const replacement: BaseDiff = {
type,
range: new Range(startLine, startCol, endLine, endCol),
code: newContent,
originalRange: new Range(originalStartLine, originalStartCol, originalEndLine, originalEndCol),
originalCode: originalContent,
}
replacements.push(replacement)
streakStartInNewFile = undefined
streakStartInOldFile = undefined
}
oldFileLineNum += line.count ?? 0;
newFileLineNum += line.count ?? 0;
}
// line was removed from old file
else if (line.removed) {
// if we weren't on a streak, start one on this current line num
if (streakStartInNewFile === undefined) {
streakStartInNewFile = newFileLineNum
streakStartInOldFile = oldFileLineNum
}
oldFileLineNum += line.count ?? 0 // we processed the line so add 1
}
// line was added to new file
else if (line.added) {
// if we weren't on a streak, start one on this current line num
if (streakStartInNewFile === undefined) {
streakStartInNewFile = newFileLineNum
streakStartInOldFile = oldFileLineNum
}
newFileLineNum += line.count ?? 0; // we processed the line so add 1
}
} // end for
console.debug('Replacements', replacements)
return replacements
}
@@ -0,0 +1,57 @@
// renders the code from `src/sidebar`
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 {
private readonly _extensionUri: vscode.Uri
private _idPool = 0
constructor(context: vscode.ExtensionContext) {
this._extensionUri = context.extensionUri
}
onPressCtrlK() {
// // TODO if currently selecting a ctrl k element, just focus it and do nothing
// const inset = vscode.window.createWebviewTextEditorInset(editor, line, height);
// const newCtrlKId = this._idPool++
// updateWebviewHTML(inset.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/ctrlk/index.js', cssOutLocation: 'dist/webviews/styles.css' },
// { id: newCtrlKId }
// )
// ctrlKWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+k', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
}
onDisposeCtrlK() {
}
}
@@ -0,0 +1,30 @@
// renders the code from `src/sidebar`
import * as vscode from 'vscode';
import { updateWebviewHTML as _updateWebviewHTML } from '../extensionLib/updateWebviewHTML';
export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
public static readonly viewId = 'void.viewnumberone';
public webview: Promise<vscode.Webview> // used to send messages to the webview, resolved by _res in resolveWebviewView
private _res: (c: vscode.Webview) => void // used to resolve the webview
private readonly _extensionUri: vscode.Uri
constructor(context: vscode.ExtensionContext) {
// const extensionPath = context.extensionPath // the directory where the extension is installed, might be useful later... was included in webviewProvider code
this._extensionUri = context.extensionUri
let temp_res: typeof this._res | undefined = undefined
this.webview = new Promise((res, rej) => { temp_res = res })
if (!temp_res) throw new Error("Void sidebar provider: resolver was undefined")
this._res = temp_res
}
// called internally by vscode
resolveWebviewView(webviewView: vscode.WebviewView, context: vscode.WebviewViewResolveContext, token: vscode.CancellationToken,) {
const webview = webviewView.webview;
_updateWebviewHTML(webview, this._extensionUri, { jsOutLocation: 'dist/webviews/sidebar/index.js', cssOutLocation: 'dist/webviews/styles.css' })
this._res(webview); // resolve webview and _webviewView
}
}
-301
View File
@@ -1,301 +0,0 @@
// import { diffLines, Change } from 'diff';
import { diffLines, Change } from 'diff';
export type SuggestedEdit = {
// start/end of current file
startLine: number;
startCol: number;
endLine: number;
endCol: number;
// start/end of original file
originalStartLine: number,
originalStartCol: number,
originalEndLine: number,
originalEndCol: number,
type: 'insertion' | 'deletion' | 'edit',
originalContent: string, // original content (originalfile[originalStart...originalEnd])
newContent: string,
}
export function findDiffs(oldStr: string, newStr: string) {
// an ordered list of every original line, line added to the new file, and line removed from the old file (order is unambiguous, think about it)
const lineByLineChanges: Change[] = diffLines(oldStr, newStr);
lineByLineChanges.push({ value: '' }) // add a dummy so we flush any streaks we haven't yet at the very end (!line.added && !line.removed)
let oldFileLineNum: number = 0;
let newFileLineNum: number = 0;
let streakStartInNewFile: number | undefined = undefined
let streakStartInOldFile: number | undefined = undefined
let oldStrLines = oldStr.split('\n')
let newStrLines = newStr.split('\n')
const replacements: SuggestedEdit[] = []
for (let line of lineByLineChanges) {
// no change on this line
if (!line.added && !line.removed) {
// do nothing
// if we were on a streak of +s and -s, end it
if (streakStartInNewFile !== undefined) {
let type: 'edit' | 'insertion' | 'deletion' = 'edit'
let startLine = streakStartInNewFile
let endLine = newFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
let startCol = 0
let endCol = Number.MAX_SAFE_INTEGER
let originalStartLine = streakStartInOldFile!
let originalEndLine = oldFileLineNum - 1 // don't include current line, the edit was up to this line but not including it
let originalStartCol = 0
let originalEndCol = Number.MAX_SAFE_INTEGER
let newContent = newStrLines.slice(startLine, endLine + 1).join('\n')
let originalContent = oldStrLines.slice(originalStartLine, originalEndLine + 1).join('\n')
// if the range is empty, mark it as a deletion / insertion (both won't be true at once)
// DELETION
if (endLine === startLine - 1) {
type = 'deletion'
endLine = startLine
startCol = 0
endCol = 0
newContent += '\n'
}
// INSERTION
else if (originalEndLine === originalStartLine - 1) {
type = 'insertion'
originalEndLine = originalStartLine
originalStartCol = 0
originalEndCol = 0
}
const replacement: SuggestedEdit = {
type,
startLine, startCol, endLine, endCol, newContent,
originalStartLine, originalStartCol, originalEndLine, originalEndCol, originalContent
} as SuggestedEdit
replacements.push(replacement)
streakStartInNewFile = undefined
streakStartInOldFile = undefined
}
oldFileLineNum += line.count ?? 0;
newFileLineNum += line.count ?? 0;
}
// line was removed from old file
else if (line.removed) {
// if we weren't on a streak, start one on this current line num
if (streakStartInNewFile === undefined) {
streakStartInNewFile = newFileLineNum
streakStartInOldFile = oldFileLineNum
}
oldFileLineNum += line.count ?? 0 // we processed the line so add 1
}
// line was added to new file
else if (line.added) {
// if we weren't on a streak, start one on this current line num
if (streakStartInNewFile === undefined) {
streakStartInNewFile = newFileLineNum
streakStartInOldFile = oldFileLineNum
}
newFileLineNum += line.count ?? 0; // we processed the line so add 1
}
} // end for
console.debug('Replacements', replacements)
return replacements
}
// const diffLinesOld = (text1: string, text2: string) => {
// var dmp = new diff_match_patch();
// var a = dmp.diff_linesToChars_(text1, text2);
// var lineText1 = a.chars1;
// var lineText2 = a.chars2;
// var lineArray = a.lineArray;
// var diffs = dmp.diff_main(lineText1, lineText2, false);
// dmp.diff_charsToLines_(diffs, lineArray);
// // dmp.diff_cleanupSemantic(diffs);
// return diffs;
// }
// // TODO use a better diff algorithm
// export const findDiffsOld = (oldText: string, newText: string): BaseDiff[] => {
// const diffs = diffLinesOld(oldText, newText);
// const blocks: BaseDiff[] = [];
// let reprBlock: string[] = [];
// let deletedBlock: string[] = [];
// let insertedBlock: string[] = [];
// let insertedLine = 0;
// let deletedLine = 0;
// let insertedStart = 0;
// let deletedStart = 0;
// diffs.forEach(([operation, text]) => {
// const lines = text.split('\n');
// switch (operation) {
// // insertion
// case 1:
// if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
// if (insertedBlock.length === 0) insertedStart = insertedLine;
// insertedLine += lines.length - 1; // Update only the line count for new text
// insertedBlock.push(text);
// reprBlock.push(lines.map(line => `+ ${line}`).join('\n'));
// break;
// // deletion
// case -1:
// if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
// if (deletedBlock.length === 0) deletedStart = deletedLine;
// deletedLine += lines.length - 1; // Update only the line count for old text
// deletedBlock.push(text);
// reprBlock.push(lines.map(line => `- ${line}`).join('\n'));
// break;
// // no change
// case 0:
// // If we have a pending block, add it to the blocks array
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
// blocks.push({
// code: reprBlock.join(''),
// deletedCode: deletedBlock.join(''),
// insertedCode: insertedBlock.join(''),
// deletedRange: new vscode.Range(deletedStart, 0, deletedLine, Number.MAX_SAFE_INTEGER),
// insertedRange: new vscode.Range(insertedStart, 0, insertedLine, Number.MAX_SAFE_INTEGER),
// });
// }
// // Reset the block variables
// reprBlock = [];
// deletedBlock = [];
// insertedBlock = [];
// // Update line counts for unchanged text
// insertedLine += lines.length - 1;
// deletedLine += lines.length - 1;
// break;
// }
// });
// // Add any remaining blocks after the loop ends
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
// blocks.push({
// code: reprBlock.join(''),
// deletedCode: deletedBlock.join(''),
// insertedCode: insertedBlock.join(''),
// deletedRange: new vscode.Range(deletedStart, 0, deletedLine, Number.MAX_SAFE_INTEGER),
// insertedRange: new vscode.Range(insertedStart, 0, insertedLine, Number.MAX_SAFE_INTEGER),
// });
// }
// return blocks;
// };
// export const findDiffs = (oldText: string, newText: string): BaseDiff[] => {
// let diffs = diffLines(oldText, newText)
// .map(diff => {
// const operation = diff.added ? 1 : diff.removed ? -1 : 0;
// const text = diff.value;
// return [operation, text] as const;
// })
// const blocks: BaseDiff[] = [];
// let reprBlock: string[] = [];
// let deletedBlock: string[] = [];
// let insertedBlock: string[] = [];
// let newFileLine = 0;
// let oldFileLine = 0;
// let insertedStart = 0;
// let deletedStart = 0;
// diffs.forEach(([operation, text]) => {
// const lines = text.split('\n');
// switch (operation) {
// // insertion
// case 1:
// if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
// if (insertedBlock.length === 0) insertedStart = newFileLine;
// newFileLine += lines.length - 1; // update the line count for new text
// insertedBlock.push(text);
// reprBlock.push(lines.map(line => `+ ${line}`).join('\n'));
// break;
// // deletion
// case -1:
// if (reprBlock.length === 0) { reprBlock.push('@@@@'); }
// if (deletedBlock.length === 0) deletedStart = oldFileLine;
// oldFileLine += lines.length - 1; // update the line count for old text
// deletedBlock.push(text);
// reprBlock.push(lines.map(line => `- ${line}`).join('\n'));
// break;
// // no change
// case 0:
// // add pending block to the blocks array
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
// blocks.push({
// code: reprBlock.join(''),
// deletedCode: deletedBlock.join(''),
// insertedCode: insertedBlock.join(''),
// deletedRange: new vscode.Range(deletedStart, 0, oldFileLine, Number.MAX_SAFE_INTEGER),
// insertedRange: new vscode.Range(insertedStart, 0, newFileLine, Number.MAX_SAFE_INTEGER),
// });
// }
// // update variables
// reprBlock = [];
// deletedBlock = [];
// insertedBlock = [];
// deletedStart += lines.length - 1;
// insertedStart += lines.length - 1;
// newFileLine += lines.length - 1;
// oldFileLine += lines.length - 1;
// break;
// }
// });
// // Add any remaining blocks after the loop ends
// if (insertedBlock.length > 0 || deletedBlock.length > 0) {
// blocks.push({
// code: reprBlock.join(''),
// deletedCode: deletedBlock.join(''),
// insertedCode: insertedBlock.join(''),
// deletedRange: new vscode.Range(deletedStart, 0, oldFileLine, Number.MAX_SAFE_INTEGER),
// insertedRange: new vscode.Range(insertedStart, 0, newFileLine, Number.MAX_SAFE_INTEGER),
// });
// }
// return blocks;
// };
-66
View File
@@ -1,66 +0,0 @@
import * as React from "react"
import { useEffect } from "react"
import * as ReactDOM from "react-dom/client"
import Sidebar from "./Sidebar"
import { CtrlK } from "./CtrlK"
import { ThreadsProvider } from "./contextForThreads"
import { ConfigProvider } from "./contextForConfig"
import { MessageToSidebar } from "../common/shared_types"
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode } from "./getVscodeApi"
import { identifyUser, initPosthog } from "./metrics/posthog"
if (typeof document === "undefined") {
console.log("index.tsx error: document was undefined")
}
const CommonEffects = () => {
// initialize posthog
useEffect(() => {
initPosthog()
}, [])
// when we get the deviceid, identify the user
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getDeviceId' });
awaitVSCodeResponse('deviceId').then((m => {
identifyUser(m.deviceId)
}))
}, [])
// Receive messages from the VSCode extension
useEffect(() => {
const listener = (event: MessageEvent) => {
const m = event.data as MessageToSidebar;
onMessageFromVSCode(m)
}
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener)
}, [])
return null
}
(() => {
// mount the sidebar on the id="root" element
const rootElement = document.getElementById("root")!
console.log("Void root Element:", rootElement)
const sidebar = (<>
<CommonEffects />
<ThreadsProvider>
<ConfigProvider>
<Sidebar />
</ConfigProvider>
</ThreadsProvider>
<ConfigProvider>
<CtrlK />
</ConfigProvider>
</>)
const root = ReactDOM.createRoot(rootElement)
root.render(sidebar)
})();
@@ -46,15 +46,8 @@ const voidConfigInfo: Record<
configFields,
),
maxTokens: configEnum(
"Max number of tokens to output.",
'1024',
[
"1024",
"2048",
"4096",
"8192"
] as const,
maxTokens: configString(
"Max number of tokens to output. Must be a number.", ''
),
},
@@ -185,18 +178,18 @@ export type VoidConfig = {
export const getVoidConfig = (currentConfig: PartialVoidConfig): VoidConfig => {
export const getVoidConfigFromPartial = (partialVoidConfig: PartialVoidConfig): VoidConfig => {
const config = {} as PartialVoidConfig
for (let field of [...configFields, 'default'] as const) {
config[field] = {}
for (let prop in voidConfigInfo[field]) {
config[field][prop] = currentConfig[field]?.[prop] || voidConfigInfo[field][prop].defaultVal
config[field][prop] = partialVoidConfig[field]?.[prop]?.trim() || voidConfigInfo[field][prop].defaultVal
}
}
return config as VoidConfig
}
const defaultVoidConfig: VoidConfig = getVoidConfig({})
const defaultVoidConfig: VoidConfig = getVoidConfigFromPartial({})
// const [stateRef, setState] = useInstantState(initVal)
// setState instantly changes the value of stateRef instead of having to wait until the next render
@@ -234,7 +227,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
getVSCodeAPI().postMessage({ type: 'getPartialVoidConfig' })
awaitVSCodeResponse('partialVoidConfig').then((m) => {
setPartialVoidConfig(m.partialVoidConfig)
const newFullConfig = getVoidConfig(m.partialVoidConfig)
const newFullConfig = getVoidConfigFromPartial(m.partialVoidConfig)
setVoidConfig(newFullConfig)
})
}, [setPartialVoidConfig])
@@ -254,7 +247,7 @@ export function ConfigProvider({ children }: { children: ReactNode }) {
}
}
setPartialVoidConfig(newPartialConfig)
const newFullConfig = getVoidConfig(newPartialConfig)
const newFullConfig = getVoidConfigFromPartial(newPartialConfig)
setVoidConfig(newFullConfig)
getVSCodeAPI().postMessage({ type: 'persistPartialVoidConfig', partialVoidConfig: newPartialConfig })
}
@@ -0,0 +1,25 @@
import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"
type PropsType = { [s: string]: any } | null
type PropsValue = { props: PropsType }
const PropsContext = createContext<PropsValue>(undefined as unknown as PropsValue)
// provider for whatever came in data-void-props
export function PropsProvider({ children, props }: { children: ReactNode, props: PropsType }) {
return (
<PropsContext.Provider value={{ props }}>
{children}
</PropsContext.Provider>
)
}
export function useVoidProps(): PropsValue {
const context = useContext<PropsValue>(PropsContext)
if (context === undefined) {
throw new Error("useVoidProps missing Provider")
}
return context
}
@@ -1,5 +1,5 @@
import React, { ReactNode, createContext, useCallback, useContext, useEffect, useRef, useState, } from "react"
import { ChatMessage, ChatThreads } from "../common/shared_types"
import { ChatMessage, ChatThreads } from "../../common/shared_types"
import { awaitVSCodeResponse, getVSCodeAPI } from "./getVscodeApi"
@@ -1,5 +1,5 @@
import { useEffect } from "react";
import { MessageFromSidebar, MessageToSidebar, } from "../common/shared_types";
import { MessageFromSidebar, MessageToSidebar, } from "../../common/shared_types";
import { v4 as uuidv4 } from 'uuid';
@@ -0,0 +1,72 @@
import React, { useEffect } from "react";
import * as ReactDOM from "react-dom/client"
import { MessageToSidebar } from "../../common/shared_types";
import { getVSCodeAPI, awaitVSCodeResponse, onMessageFromVSCode } from "./getVscodeApi";
import { initPosthog, identifyUser } from "./posthog";
import { ThreadsProvider } from "./contextForThreads";
import { ConfigProvider } from "./contextForConfig";
import { PropsProvider } from "./contextForProps";
const ListenersAndTracking = () => {
// initialize posthog
useEffect(() => {
initPosthog()
}, [])
// when we get the deviceid, identify the user
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getDeviceId' });
awaitVSCodeResponse('deviceId').then((m => {
identifyUser(m.deviceId)
}))
}, [])
// Receive messages from the VSCode extension
useEffect(() => {
const listener = (event: MessageEvent) => {
const m = event.data as MessageToSidebar;
onMessageFromVSCode(m)
}
window.addEventListener('message', listener);
return () => window.removeEventListener('message', listener)
}, [])
return null
}
export const mount = (children: React.ReactNode) => {
if (typeof document === "undefined") {
console.error("index.tsx error: document was undefined")
return
}
// 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))
}
const content = (<>
<ListenersAndTracking />
<PropsProvider props={propsObj}>
<ThreadsProvider>
<ConfigProvider>
{children}
</ConfigProvider>
</ThreadsProvider>
</PropsProvider>
</>)
const root = ReactDOM.createRoot(rootElement)
root.render(content);
}
@@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { useOnVSCodeMessage } from './getVscodeApi';
import { useOnVSCodeMessage } from '../common/getVscodeApi';
export const CtrlK = () => {
@@ -0,0 +1,7 @@
import React from "react"
import { mount } from "../common/mount"
import { CtrlK } from "./CtrlK"
// this is the entry point that mounts ctrlk
mount(<CtrlK />)
@@ -1,11 +1,11 @@
import React, { useState, useEffect, useRef, useCallback, FormEvent } from "react"
import { CodeSelection, ChatMessage, MessageToSidebar } from "../common/shared_types"
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi"
import { CodeSelection, ChatMessage, MessageToSidebar } from "../../common/shared_types"
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "../common/getVscodeApi"
import { SidebarThreadSelector } from "./SidebarThreadSelector";
import { SidebarChat } from "./SidebarChat";
import { SidebarSettings } from './SidebarSettings';
import { identifyUser } from "./metrics/posthog";
import { SidebarSettings } from "./SidebarSettings";
import { identifyUser } from "../common/posthog";
const Sidebar = () => {
@@ -4,14 +4,14 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from "reac
import { marked } from 'marked';
import MarkdownRender from "./markdown/MarkdownRender";
import BlockCode from "./markdown/BlockCode";
import { File, ChatMessage, CodeSelection } from "../common/shared_types";
import { File, ChatMessage, CodeSelection } from "../../common/shared_types";
import * as vscode from 'vscode'
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "./getVscodeApi";
import { useThreads } from "./contextForThreads";
import { sendLLMMessage } from "../common/sendLLMMessage";
import { useVoidConfig } from "./contextForConfig";
import { generateDiffInstructions } from "../common/systemPrompts";
import { captureEvent } from "./metrics/posthog";
import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMessage } from "../common/getVscodeApi";
import { useThreads } from "../common/contextForThreads";
import { sendLLMMessage } from "../../common/sendLLMMessage";
import { useVoidConfig } from "../common/contextForConfig";
import { captureEvent } from "../common/posthog";
import { generateDiffInstructions } from "../../common/systemPrompts";
@@ -263,10 +263,8 @@ export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HT
setLatestError(error)
},
setAbort: (abort) => {
abortFnRef.current = abort
},
voidConfig,
abortRef: abortFnRef,
})
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { configFields, useVoidConfig, VoidConfigField } from "./contextForConfig";
import { configFields, useVoidConfig, VoidConfigField } from "../common/contextForConfig";
const SettingOfFieldAndParam = ({ field, param }: { field: VoidConfigField, param: string }) => {
@@ -1,5 +1,5 @@
import React from "react";
import { ThreadsProvider, useThreads } from "./contextForThreads";
import { ThreadsProvider, useThreads } from "../common/contextForThreads";
const truncate = (s: string) => {
@@ -0,0 +1,7 @@
import React from "react"
import Sidebar from "./Sidebar"
import { mount } from "../common/mount"
// this is the entry point that mounts the sidebar
mount(<Sidebar />)
@@ -1,7 +1,7 @@
import React, { JSX, useCallback, useEffect, useState } from "react"
import { marked, MarkedToken, Token, TokensList } from "marked"
import BlockCode from "./BlockCode"
import { getVSCodeAPI } from "../getVscodeApi"
import { getVSCodeAPI } from "../../common/getVscodeApi"
enum CopyButtonState {
@@ -12,7 +12,7 @@ enum CopyButtonState {
const COPY_FEEDBACK_TIMEOUT = 1000 // amount of time to say 'Copied!'
const CodeButtonsOnHover = ({ text }: { text: string }) => {
const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
const [copyButtonState, setCopyButtonState] = useState(CopyButtonState.Copy)
useEffect(() => {
@@ -44,7 +44,7 @@ const CodeButtonsOnHover = ({ text }: { text: string }) => {
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
getVSCodeAPI().postMessage({ type: "applyChanges", code: text })
getVSCodeAPI().postMessage({ type: "applyChanges", diffRepr: text })
}}
>
Apply
@@ -66,7 +66,7 @@ const RenderToken = ({ token, nested = false }: { token: Token | string, nested?
return <BlockCode
text={t.text}
language={t.lang}
buttonsOnHover={<CodeButtonsOnHover text={t.text} />}
buttonsOnHover={<CodeButtonsOnHover diffRepr={t.text} />}
/>
}
@@ -1,3 +1,5 @@
/* all the styles are shared right now between all webviews */
@tailwind base;
@tailwind components;
@tailwind utilities;
+1 -1
View File
@@ -2,7 +2,7 @@
// inject user's vscode theme colors: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
module.exports = {
content: ["./src/sidebar/**/*.{html,js,ts,jsx,tsx}"],
content: ["./src/webviews/**/*.{html,js,ts,jsx,tsx}"],
theme: {
extend: {
colors: {