add ctrlk, autofocus chatbar

This commit is contained in:
Andrew 2024-10-20 23:24:40 -07:00
parent febf492790
commit 18656372f8
10 changed files with 136 additions and 65 deletions

View file

@ -24,7 +24,7 @@
},
{
"command": "void.ctrl+k",
"title": "Show Selection Lens"
"title": "Make Inline Edit"
},
{
"command": "void.acceptDiff",

View file

@ -2,7 +2,7 @@
import * as vscode from 'vscode';
function getNonce() {
function generateNonce() {
let text = "";
const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
for (let i = 0; i < 32; i++) {
@ -39,7 +39,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
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 = getNonce();
const nonce = generateNonce();
const webviewHTML = `<!DOCTYPE html>
<html lang="en">
@ -53,6 +53,7 @@ export class SidebarWebviewProvider implements vscode.WebviewViewProvider {
</head>
<body>
<div id="root"></div>
<div id="ctrlkroot"></div>
<script nonce="${nonce}" src="${scriptUri}"></script>
</body>
</html>`;

View file

@ -41,6 +41,7 @@ type Diff = {
// editor -> sidebar
type MessageToSidebar = (
| { type: 'ctrl+l', selection: CodeSelection } // user presses ctrl+l in the editor
| { type: 'ctrl+k', selection: CodeSelection }
| { type: 'files', files: { filepath: vscode.Uri, content: string }[] }
| { type: 'partialVoidConfig', partialVoidConfig: PartialVoidConfig }
| { type: 'allThreads', threads: ChatThreads }

View file

@ -9,37 +9,60 @@ const readFileContentOfUri = async (uri: vscode.Uri) => {
.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)
}
export function activate(context: vscode.ExtensionContext) {
// 1. Mount the chat sidebar
const webviewProvider = new SidebarWebviewProvider(context);
const sidebarWebviewProvider = new SidebarWebviewProvider(context);
context.subscriptions.push(
vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, webviewProvider, { webviewOptions: { retainContextWhenHidden: true } })
vscode.window.registerWebviewViewProvider(SidebarWebviewProvider.viewId, sidebarWebviewProvider, { webviewOptions: { retainContextWhenHidden: true } })
);
// 2. Activate the sidebar on ctrl+l
// 2. ctrl+l
context.subscriptions.push(
vscode.commands.registerCommand('void.ctrl+l', () => {
const editor = vscode.window.activeTextEditor
if (!editor)
return
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 text the user is selecting
const selectionStr = editor.document.getText(editor.selection);
// get the range of the selection
const selectionRange = editor.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)
webviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+l', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
})
);
// 2.5: ctrl+k
context.subscriptions.push(
vscode.commands.registerCommand('void.ctrl+k', () => {
console.log('CTRLK PRESSED')
const editor = vscode.window.activeTextEditor
if (!editor) return
// get the range of the selection
const selectionRange = roundRangeToLines(editor.selection);
// get the text the user is selecting
const selectionStr = editor.document.getText(selectionRange);
// get the file the user is in
const filePath = editor.document.uri;
// send message to the webview (Sidebar.tsx)
sidebarWebviewProvider.webview.then(webview => webview.postMessage({ type: 'ctrl+k', selection: { selectionStr, selectionRange, filePath } } satisfies MessageToSidebar));
})
);
@ -56,7 +79,7 @@ export function activate(context: vscode.ExtensionContext) {
}));
// 5. Receive messages from sidebar
webviewProvider.webview.then(
sidebarWebviewProvider.webview.then(
webview => {
// top navigation bar commands

View file

@ -0,0 +1,20 @@
import React, { useState } from 'react';
import { useOnVSCodeMessage } from './getVscodeApi';
export const CtrlK = () => {
const [x, sx] = useState('abc')
useOnVSCodeMessage('ctrl+k', () => {
console.log('Ctrl+K pressed')
sx('Pressed ctrl+k')
})
return <>
<div>
{x}
</div>
</>
};

View file

@ -5,29 +5,26 @@ import { awaitVSCodeResponse, getVSCodeAPI, onMessageFromVSCode, useOnVSCodeMess
import { SidebarThreadSelector } from "./SidebarThreadSelector";
import { SidebarChat } from "./SidebarChat";
import { SidebarSettings } from './SidebarSettings';
import { identifyUser, useMetrics } from "./metrics/posthog";
import { identifyUser } from "./metrics/posthog";
const Sidebar = () => {
useMetrics()
// when we get the deviceid, identify the user
useEffect(() => {
getVSCodeAPI().postMessage({ type: 'getDeviceId' });
awaitVSCodeResponse('deviceId').then((m => {
identifyUser(m.deviceId)
}))
}, [])
const chatInputRef = useRef<HTMLTextAreaElement | null>(null)
const [tab, setTab] = useState<'threadSelector' | 'chat' | 'settings'>('chat')
// if they pressed the + to add a new chat
useOnVSCodeMessage('startNewThread', (m) => { setTab('chat') })
useOnVSCodeMessage('startNewThread', (m) => {
setTab('chat');
chatInputRef.current?.focus();
})
// ctrl+l should switch back to chat
useOnVSCodeMessage('ctrl+l', (m) => { setTab('chat') })
useOnVSCodeMessage('ctrl+l', (m) => {
setTab('chat');
chatInputRef.current?.focus();
})
// if they toggled thread selector
useOnVSCodeMessage('toggleThreadSelector', (m) => {
@ -45,18 +42,6 @@ const Sidebar = () => {
setTab('settings')
})
// 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 <>
<div className={`flex flex-col h-screen w-full`}>
@ -65,7 +50,7 @@ const Sidebar = () => {
</div>
<div className={`${tab !== 'chat' && tab !== 'threadSelector' ? 'hidden' : ''}`}>
<SidebarChat />
<SidebarChat chatInputRef={chatInputRef} />
</div>
<div className={`${tab !== 'settings' ? 'hidden' : ''}`}>

View file

@ -144,7 +144,7 @@ const ChatBubble = ({ chatMessage }: { chatMessage: ChatMessage }) => {
export const SidebarChat = () => {
export const SidebarChat = ({ chatInputRef }: { chatInputRef: React.RefObject<HTMLTextAreaElement> }) => {
// state of current message
@ -329,6 +329,7 @@ export const SidebarChat = () => {
{/* input */}
<textarea
ref={chatInputRef}
onChange={(e) => { setInstructions(e.target.value) }}
className="w-full p-2 leading-tight resize-none max-h-[50vh] overflow-hidden bg-transparent border-none !outline-none"
placeholder="Ctrl+L to select"

View file

@ -8,6 +8,7 @@ type Command = MessageToSidebar['type']
// messageType -> res[]
const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
"ctrl+l": [],
"ctrl+k": [],
"files": [],
"partialVoidConfig": [],
"startNewThread": [],
@ -20,6 +21,7 @@ const onetimeCallbacks: { [C in Command]: ((res: any) => void)[] } = {
// messageType -> id -> res
const callbacks: { [C in Command]: { [id: string]: ((res: any) => void) } } = {
"ctrl+l": {},
"ctrl+k": {},
"files": {},
"partialVoidConfig": {},
"startNewThread": {},

View file

@ -1,23 +1,66 @@
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"
// mount the sidebar on the id="root" element
if (typeof document === "undefined") {
console.log("index.tsx error: document was undefined")
}
const rootElement = document.getElementById("root")!
console.log("Void root Element:", rootElement)
const extension = (
<ThreadsProvider>
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>
<Sidebar />
<CtrlK />
</ConfigProvider>
</ThreadsProvider>
)
const root = ReactDOM.createRoot(rootElement)
root.render(extension)
</>)
const root = ReactDOM.createRoot(rootElement)
root.render(sidebar)
})();

View file

@ -1,5 +1,4 @@
import posthog from 'posthog-js'
import { useEffect } from 'react'
export const identifyUser = (id: string) => {
@ -10,16 +9,12 @@ export const captureEvent = (eventId: string, properties: object) => {
posthog.capture(eventId, properties)
}
export const useMetrics = () => {
export const initPosthog = () => {
// We send absolutely no code to the server. We only track usage metrics like button clicks, etc. This might change and we might eventually add an opt-in or opt-out.
useEffect(() => {
posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2',
{
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar
}
)
}, [])
}
posthog.init('phc_UanIdujHiLp55BkUTjB1AuBXcasVkdqRwgnwRlWESH2',
{
api_host: 'https://us.i.posthog.com',
person_profiles: 'identified_only' // we only track events from identified users. We identify them in Sidebar
}
)
}