Compare commits

...

4 Commits

Author SHA1 Message Date
mp 5c07136b36 Minor edit 2024-11-01 16:30:36 -07:00
mp 580f9b6e02 Implement basic Inset functionality 2024-11-01 16:18:34 -07:00
Andrew 68bc7ba620 update contrib 2024-10-31 21:58:02 -07:00
mp dd48db0ed3 minor changes 2024-10-31 19:23:17 -07:00
11 changed files with 152 additions and 106 deletions
+2 -2
View File
@@ -69,7 +69,7 @@ We haven't created prerequisite steps for building on Linux yet, but you can fol
### Build instructions
Before building Void, please follow the prerequisite steps above for your operating system. Also make sure you've already built the Void extension (or just run `cd ./extensions/void && npm install && npm run build && npm run compile && cd ../..`).
Before building Void, please follow the prerequisite steps above for your operating system. Also make sure you've already built and compiled the Void extension (or just run `cd ./extensions/void && npm install && npm run build && npm run compile && cd ../..`).
To build Void, first open `void/` in VSCode. Then:
@@ -146,7 +146,7 @@ We're always glad to talk about new ideas, help you get set up, and make sure yo
## Submitting a Pull Request
Please submit a pull request once you've made a change. You don't need to submit an issue.
Please submit a pull request once you've made a change. You don't need to submit an issue.
Please don't use AI to write your PR 🙂.
@@ -32,6 +32,13 @@ type Diff = {
lenses: vscode.CodeLens[],
} & BaseDiff
type Inset = {
text: string,
height: number,
line: number,
insetObj: vscode.WebviewEditorInset,
}
// editor -> sidebar
type MessageToSidebar = (
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor. selection and path are frozen snapshots
@@ -88,6 +95,7 @@ type ChatMessage =
export {
BaseDiff, Diff,
DiffArea,
Inset,
CodeSelection,
File,
MessageFromSidebar,
+98 -47
View File
@@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { findDiffs } from './findDiffs';
import { throttle } from 'lodash';
import { DiffArea, BaseDiff, Diff } from '../common/shared_types';
import { DiffArea, BaseDiff, Diff, Inset } from '../common/shared_types';
import { readFileContentOfUri } from './extensionLib/readFileContentOfUri';
import { updateWebviewHTML } from './extensionLib/updateWebviewHTML';
@@ -10,15 +10,15 @@ const THROTTLE_TIME = 100
// TODO in theory this should be disposed
const greenDecoration = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(0 255 51 / 0.2)',
isWholeLine: false, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
backgroundColor: 'rgba(0, 255, 51, 0.2)',
isWholeLine: true, // after: { contentText: ' [original]', color: 'rgba(0 255 60 / 0.5)' } // hoverMessage: originalText // this applies to hovering over after:...
})
const lightGrayDecoration = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgba(218 218 218 / .2)',
backgroundColor: 'rgba(218, 218, 218, 0.2)',
isWholeLine: true,
})
const darkGrayDecoration = vscode.window.createTextEditorDecorationType({
backgroundColor: 'rgb(148 148 148 / .2)',
backgroundColor: 'rgb(148, 148, 148, 0.2)',
isWholeLine: true,
})
@@ -28,6 +28,8 @@ export class DiffProvider implements vscode.CodeLensProvider {
private _originalFileOfDocument: { [docUriStr: string]: string } = {}
private _diffAreasOfDocument: { [docUriStr: string]: DiffArea[] } = {}
private _diffsOfDocument: { [docUriStr: string]: Diff[] } = {}
private _insetsOfDocument: { [docUriStr: string]: Inset[] } = {}
private _diffareaidPool = 0
private _diffidPool = 0
@@ -186,23 +188,15 @@ export class DiffProvider implements vscode.CodeLensProvider {
// compute the diffs
const diffs = findDiffs(originalCode, currentCode)
// console.log('____________')
// diffs.forEach(diff => {
// console.log('__DELETED: ', diff.originalCode)
// console.log('__INSERTED: ', diff.code)
// })
// add the diffs to `this._diffsOfDocument[docUriStr]`
this.createDiffs(editor.document.uri, diffs, diffArea)
// // print diffs
// console.log('!ORIGINAL FILE:', JSON.stringify(originalFile))
// console.log('!NEW FILE :', JSON.stringify(editor.document.getText().replace(/\r\n/g, '\n')))
// console.log('!AREA originalCode:', JSON.stringify(originalCode))
// console.log('!AREA currentCode :', JSON.stringify(currentCode))
// for (const diff of this._diffsOfDocument[docUriStr]) {
// console.log('------------')
// console.log('originalCode:', JSON.stringify(diff.originalCode))
// console.log('currentCode:', JSON.stringify(diff.code))
// console.log('originalRange:', diff.originalRange.start.line, diff.originalRange.end.line,)
// console.log('currentRange:', diff.range.start.line, diff.range.end.line,)
// }
}
// update green highlighting
@@ -215,32 +209,88 @@ export class DiffProvider implements vscode.CodeLensProvider {
);
// update red highlighting
// this._diffsOfDocument[docUriStr]
// .filter(diff => diff.originalCode !== '')
// .forEach(diff => {
// const text = originalFile.split('\n').slice(diff.originalRange.start.line, diff.originalRange.start.line + 1).join('\n')
// const height = text.split('\n').length
// const line = diff.range.start.line - 1
/* NAIVE APPROACH */
if (!this._insetsOfDocument[docUriStr])
this._insetsOfDocument[docUriStr] = []
for (const inset of this._insetsOfDocument[docUriStr]) {
inset.insetObj.dispose()
}
this._insetsOfDocument[docUriStr] = this._diffsOfDocument[docUriStr]
.filter(diff => diff.originalCode !== '')
.map(diff => {
const text = diff.originalCode
const height = diff.originalCode.split('\n').length
const line = diff.range.start.line - 1
const insetObj = vscode.window.createWebviewTextEditorInset(editor, line, height);
updateWebviewHTML(insetObj.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/diffline/index.js', cssOutLocation: 'dist/webviews/styles.css', isCode: true },
{ text }
)
return { text, line, height, insetObj }
})
/* CACHING APPROACH */
// function getCompareString<T extends { [key: string]: any; }>(obj: T, compareKeys: string[]): string {
// return compareKeys.map(key => `${key}:${JSON.stringify(obj?.[key])}`).join('|');
// }
// const inset = vscode.window.createWebviewTextEditorInset(editor, line, height);
// updateWebviewHTML(inset.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/diffline/index.js', cssOutLocation: 'dist/webviews/styles.css' },
// { text }
// )
// function compareArrays<T extends { [key: string]: any }>(arr1: T[], arr2: T[], compareKeys: string[]) {
// const getKey = (obj: T) => getCompareString(obj, compareKeys);
// const set1 = new Set(arr1.map(getKey));
// const set2 = new Set(arr2.map(getKey));
// const common = arr1.filter(obj => set2.has(getKey(obj)))
// const uniqueToFirst = arr1.filter(obj => !set2.has(getKey(obj)))
// const uniqueToSecond = arr2.filter(obj => !set1.has(getKey(obj)))
// const uniqueToFirstKeys = new Set(uniqueToFirst.map(getKey))
// const uniqueToSecondKeys = new Set(uniqueToSecond.map(getKey))
// })
// return {
// common,
// uniqueToFirst,
// uniqueToSecond,
// uniqueToFirstKeys,
// uniqueToSecondKeys,
// getKey,
// };
// }
// compare the current and old insets and update the ones that have changed
// if (!this._insetsOfDocument[docUriStr])
// this._insetsOfDocument[docUriStr] = []
// const oldInsets: Inset[] = this._insetsOfDocument[docUriStr]
// const currInsets: Inset[] = this._diffsOfDocument[docUriStr]
// .map(diff => ({ text: diff.originalCode, height: diff.originalCode.split('\n').length, line: diff.range.start.line - 1, insetObj: undefined! }))
// const {
// common: commonInsets,
// uniqueToFirst: deletedInsets,
// uniqueToSecond: addedInsets,
// } = compareArrays(oldInsets, currInsets, ['text', 'height', 'line'])
// console.log('DELETED INSETS', deletedInsets)
// console.log('ADDED INSETS', addedInsets)
// for (const deletedInset of deletedInsets) { // delete insets in the deleted set
// deletedInset.insetObj.dispose()
// }
// for (const addedInset of addedInsets) { // add insets in the added set
// const insetObj = vscode.window.createWebviewTextEditorInset(editor, addedInset.line, addedInset.height);
// updateWebviewHTML(insetObj.webview, this._extensionUri, { jsOutLocation: 'dist/webviews/diffline/index.js', cssOutLocation: 'dist/webviews/styles.css', isCode: true },
// { text: addedInset.text }
// )
// addedInset.insetObj = insetObj
// }
// this._insetsOfDocument[docUriStr] = commonInsets.concat(addedInsets)
// for each diffArea, highlight its sweepIndex in dark gray
editor.setDecorations(
darkGrayDecoration,
(this._diffAreasOfDocument[docUriStr]
.filter(diffArea => diffArea.sweepIndex !== null)
.map(diffArea => {
let s = diffArea.sweepIndex!
return new vscode.Range(s, 0, s, 0)
})
)
)
// // for each diffArea, highlight 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(
@@ -396,7 +446,8 @@ export class DiffProvider implements vscode.CodeLensProvider {
// used by us only
public updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string) => {
public updateStream = throttle(async (docUriStr: string, diffArea: DiffArea, newDiffAreaCode: string, options?: { endStream?: boolean }) => {
const { endStream, } = options || {}
const editor = vscode.window.activeTextEditor // TODO the editor should be that of `docUri` and not necessarily the current editor
if (!editor) {
@@ -404,12 +455,12 @@ export class DiffProvider implements vscode.CodeLensProvider {
return;
}
// original code all diffs are based on in the code
// original code all diffs are based on
const originalDiffAreaCode = (this._originalFileOfDocument[docUriStr] || '').split('\n').slice(diffArea.originalStartLine, diffArea.originalEndLine + 1).join('\n')
// figure out where to highlight based on where the AI is in the stream right now, use the last diff in findDiffs to figure that out
const diffs = findDiffs(originalDiffAreaCode, newDiffAreaCode)
const lastDiff = diffs?.[diffs.length - 1] ?? null
const lastDiff = diffs?.[diffs.length - 1]
// these are two different coordinate systems - new and old line number
let newFileEndLine: number // get new[0...newStoppingPoint] with line=newStoppingPoint highlighted
@@ -443,14 +494,14 @@ export class DiffProvider implements vscode.CodeLensProvider {
const oldFileBottom = originalDiffAreaCode.split('\n').slice(oldFileStartLine + 1, Infinity).join('\n')
let newCode = `${newFileTop}\n${oldFileBottom}`
diffArea.sweepIndex = newFileEndLine
// replace oldDACode with newDACode with a vscode edit
diffArea.sweepIndex = endStream ? null : newFileEndLine
// replace oldCode with newCode using a vscode edit
const workspaceEdit = new vscode.WorkspaceEdit();
const diffareaRange = new vscode.Range(diffArea.startLine, 0, diffArea.endLine, Number.MAX_SAFE_INTEGER)
workspaceEdit.replace(editor.document.uri, diffareaRange, newCode)
await vscode.workspace.applyEdit(workspaceEdit)
}, THROTTLE_TIME)
}
@@ -1,14 +1,13 @@
import * as vscode from 'vscode'
export const readFileContentOfUri = async (uri: vscode.Uri): Promise<string> => {
const document = await vscode.workspace.openTextDocument(uri);
return document.getText().replace(/\r\n/g, '\n') ?? '' // Normalize line endings
// export const readFileContentOfUri = async (uri: vscode.Uri): Promise<string> => {
// const document = await vscode.workspace.openTextDocument(uri.fsPath);
// return document.getText().replace(/\r\n/g, '\n') ?? '' // Normalize line endings
// };
};
// this is the old version, which only reads the most recently saved version
// export const readFileContentOfUri = async (uri: vscode.Uri) => {
// return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
// .replace(/\r\n/g, '\n') // replace windows \r\n with \n
// }
// TODO this only accesses the most recently saved version; make it instead access the most recent version in the vscode editor
export const readFileContentOfUri = async (uri: vscode.Uri) => {
return Buffer.from(await vscode.workspace.fs.readFile(uri)).toString('utf8')
.replace(/\r\n/g, '\n') // replace windows \r\n with \n
}
@@ -11,7 +11,7 @@ function generateNonce() {
// call this when you have access to the webview to set its html
export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.Uri, { jsOutLocation, cssOutLocation }: { jsOutLocation: string, cssOutLocation: string }, props?: object) => {
export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.Uri, { jsOutLocation, cssOutLocation, isCode }: { jsOutLocation: string, cssOutLocation: string, isCode?: boolean }, props?: object) => {
// 'dist/sidebar/index.js'
// 'dist/sidebar/styles.css'
@@ -21,6 +21,8 @@ export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.
const rootUri = webview.asWebviewUri(vscode.Uri.joinPath(extensionUri));
const nonce = generateNonce();
const codeStyle = `style="padding-left:0; padding-right:0; font-family: Consolas, &quot;Courier New&quot;, monospace; font-weight: normal; font-size: 14px; font-feature-settings: &quot;liga&quot; 0, &quot;calt&quot; 0; font-variation-settings: normal; line-height: 19px; letter-spacing: 0px;"`
const webviewHTML = `<!DOCTYPE html>
<html lang="en">
<head>
@@ -31,8 +33,8 @@ export const updateWebviewHTML = (webview: vscode.Webview, extensionUri: vscode.
<base href="${rootUri}/">
<link href="${stylesUri}" rel="stylesheet">
</head>
<body>
<div id="root"${props ? ` data-void-props="${encodeURIComponent(JSON.stringify(props))}"` : ''}></div>
<body ${isCode ? codeStyle : ''}>
<div id="root" ${props ? `data-void-props="${encodeURIComponent(JSON.stringify(props))}"` : ''}></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;
+1 -1
View File
@@ -127,6 +127,6 @@ export function findDiffs(oldStr: string, newStr: string) {
}
} // end for
console.debug('Replacements', replacements)
// console.debug('Replacements', replacements)
return replacements
}
@@ -3,22 +3,6 @@
import * as vscode from 'vscode';
import { updateWebviewHTML as _updateWebviewHTML, updateWebviewHTML } from '../extensionLib/updateWebviewHTML';
// this comes from vscode.proposed.editorInsets.d.ts
declare module 'vscode' {
export interface WebviewEditorInset {
readonly editor: vscode.TextEditor;
readonly line: number;
readonly height: number;
readonly webview: vscode.Webview;
readonly onDidDispose: Event<void>;
dispose(): void;
}
export namespace window {
export function createWebviewTextEditorInset(editor: vscode.TextEditor, line: number, height: number, options?: vscode.WebviewOptions): WebviewEditorInset;
}
}
export class CtrlKWebviewProvider {
@@ -2,21 +2,17 @@ import React, { ReactNode, createContext, useCallback, useContext, useEffect, us
const PropsContext = createContext<any>(undefined as unknown as any)
export const getPropsObj = (rootElement: HTMLElement) => {
let props = rootElement.getAttribute("data-void-props")
let propsObj: object | null = null
if (props !== null) {
propsObj = JSON.parse(decodeURIComponent(props))
}
return propsObj
}
// provider for whatever came in data-void-props
export function PropsProvider({ children, rootElement }: { children: ReactNode, rootElement: HTMLElement }) {
const [props, setProps] = useState<object | null>(null)
// update props when rootElement changes
useEffect(() => {
let props = rootElement.getAttribute("data-void-props")
let propsObj: object | null = null
if (props !== null) {
propsObj = JSON.parse(decodeURIComponent(props))
}
setProps(propsObj)
}, [rootElement])
export function PropsProvider({ children, props }: { children: ReactNode, props: object | null }) {
return (
<PropsContext.Provider value={props}>
{children}
@@ -24,13 +20,20 @@ export function PropsProvider({ children, rootElement }: { children: ReactNode,
)
}
export function useVoidProps<T extends {}>(): T | null {
export function useVoidProps<T extends {}>(): T {
// context is the "value" from above
const context: T | null | undefined = useContext<T>(PropsContext)
// only undefined if has no provider
if (context === undefined) {
throw new Error("useVoidProps missing Provider")
}
if (context === null) {
throw new Error("useVoidProps had null props")
}
if (!(context instanceof Object)) {
throw new Error("useVoidProps props was not an object")
}
return context
}
@@ -5,7 +5,7 @@ import { getVSCodeAPI, awaitVSCodeResponse, onMessageFromVSCode } from "./getVsc
import { initPosthog, identifyUser } from "./posthog";
import { ThreadsProvider } from "./contextForThreads";
import { ConfigProvider } from "./contextForConfig";
import { PropsProvider } from "./contextForProps";
import { getPropsObj, PropsProvider } from "./contextForProps";
const ListenersAndTracking = () => {
// initialize posthog
@@ -51,7 +51,7 @@ export const mount = (children: React.ReactNode) => {
const content = (<>
<ListenersAndTracking />
<PropsProvider rootElement={rootElement}>
<PropsProvider props={getPropsObj(rootElement)}>
<ThreadsProvider>
<ConfigProvider>
{children}
@@ -11,18 +11,17 @@ export const DiffLine = () => {
const props = useVoidProps<props>()
console.log('props!', props)
if (!props) {
return null
}
// eslint-disable-next-line react/prop-types
const text = props.text
let text = props?.text ?? ''
return <>
<div>
{text}
<div className="view-line relative pointer-events-none">
<div className={`absolute w-[100%] h-[100%] bg-[rgba(255,0,51,0.2)] z-10`}></div>
<span>
<span className='whitespace-pre text-[14px]'>
{text}
</span>
</span>
</div>
</>
};
+1 -1
View File
@@ -43,4 +43,4 @@ html {
.dropdown {
@apply bg-vscode-dropdown-bg text-vscode-dropdown-foreground border-vscode-dropdown-border focus:outline-vscode-focus-border;
}
}