From 1d590510ae3a7d0b01a6deff3f7425306cd12585 Mon Sep 17 00:00:00 2001 From: Andrew Pareles Date: Mon, 13 Jan 2025 03:55:18 -0800 Subject: [PATCH] mostly works!! tracethroughs would be insanely helpful... --- .../void/browser/inlineDiffsService.ts | 40 ++++---- .../src/quick-edit-tsx/QuickEditChat.tsx | 98 +++++++++---------- .../void/browser/react/src/util/inputs.tsx | 10 +- 3 files changed, 75 insertions(+), 73 deletions(-) diff --git a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts index 90a026a4..82fb5ee8 100644 --- a/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts +++ b/src/vs/workbench/contrib/void/browser/inlineDiffsService.ts @@ -32,7 +32,6 @@ import { ILLMMessageService } from '../../../../platform/void/common/llmMessageS import { mountCtrlK } from '../browser/react/out/quick-edit-tsx/index.js' import { QuickEditPropsType } from './quickEditActions.js'; -import { InputBox } from '../../../../base/browser/ui/inputbox/inputBox.js'; import { errorDetails, LLMMessage } from '../../../../platform/void/common/llmMessageTypes.js'; import { IModelContentChangedEvent } from '../../../../editor/common/textModelEvents.js'; import { extractCodeFromFIM, extractCodeFromRegular } from './helpers/extractCodeFromResult.js'; @@ -49,7 +48,7 @@ import { isMacintosh } from '../../../../base/common/platform.js'; const configOfBG = (color: Color) => { return { dark: color, light: color, hcDark: color, hcLight: color, } } -// gets converted to --vscode-void-greenBG, see void.css +// gets converted to --vscode-void-greenBG, see void.css, asCssVariable const greenBG = new Color(new RGBA(155, 185, 85, .3)); // default is RGBA(155, 185, 85, .2) registerColor('void.greenBG', configOfBG(greenBG), '', true); @@ -126,7 +125,7 @@ type CtrlKZone = { editorId: string; // the editor the input lives on _mountInfo: null | { - inputBoxRef: { current: InputBox | null }; // the input box that lives in the zone + textAreaRef: { current: HTMLTextAreaElement | null } dispose: () => void; refresh: () => void; } @@ -342,15 +341,16 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { let zoneId: string | null = null let viewZone_: IViewZone | null = null - const inputBoxRef: { current: InputBox | null } = { current: null } + const textAreaRef: { current: HTMLTextAreaElement | null } = { current: null } const itemId = this._consistentEditorItemService.addToEditor(editor, () => { const domNode = document.createElement('div'); domNode.style.zIndex = '1' + domNode.style.height = 'auto' const viewZone: IViewZone = { afterLineNumber: ctrlKZone.startLine - 1, domNode: domNode, - heightInPx: 52, + // heightInPx: 80, suppressMouseDown: false, showInHiddenAreas: true, }; @@ -361,30 +361,32 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { zoneId = accessor.addZone(viewZone) }) + // mount react this._instantiationService.invokeFunction(accessor => { mountCtrlK(domNode, accessor, { + diffareaid: ctrlKZone.diffareaid, - onGetInputBox: (inputBox) => { - inputBoxRef.current = inputBox - // if it's mounting for the first time, focus it + + textAreaRef: (r) => { + textAreaRef.current = r + if (!textAreaRef.current) return + if (!(ctrlKZone.diffareaid in this.mostRecentTextOfCtrlKZoneId)) { // detect first mount this way (a hack) this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = undefined - setTimeout(() => inputBox.focus(), 0) + setTimeout(() => textAreaRef.current?.focus(), 100) } }, onChangeHeight(height) { - if (height === undefined) return - if (height === 0) return // if hidden, height is set to 0 creating a jumpy scroll. ignore viewZone.heightInPx = height // re-render with this new height editor.changeViewZones(accessor => { - if (zoneId) { - accessor.layoutZone(zoneId) - } + if (zoneId) accessor.layoutZone(zoneId) }) }, - onUserUpdateText: (text) => { this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = text; }, + onChangeText: (text) => { + this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] = text; + }, initText: this.mostRecentTextOfCtrlKZoneId[ctrlKZone.diffareaid] ?? null, } satisfies QuickEditPropsType) @@ -397,7 +399,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { }) return { - inputBoxRef, + textAreaRef, refresh: () => editor.changeViewZones(accessor => { if (zoneId && viewZone_) { viewZone_.afterLineNumber = ctrlKZone.startLine - 1 @@ -956,7 +958,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { // check if there's overlap with any other ctrlKZone and if so, focus it const overlappingCtrlKZone = this._findOverlappingDiffArea({ startLine, endLine, uri, filter: (diffArea) => diffArea.type === 'CtrlKZone' }) if (overlappingCtrlKZone) { - setTimeout(() => (overlappingCtrlKZone as CtrlKZone)._mountInfo?.inputBoxRef.current?.focus(), 0) + (overlappingCtrlKZone as CtrlKZone)._mountInfo?.textAreaRef.current?.focus() return } @@ -1057,8 +1059,8 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService { startLine = startLine_ endLine = endLine_ - if (!_mountInfo?.inputBoxRef.current) return - userMessage = _mountInfo.inputBoxRef.current?.value + if (!_mountInfo?.textAreaRef.current) return + userMessage = _mountInfo.textAreaRef.current?.value } else { throw new Error(`Void: diff.type not recognized on: ${featureName}`) diff --git a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx index 40ba88f2..22976f2a 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/quick-edit-tsx/QuickEditChat.tsx @@ -5,27 +5,29 @@ import React, { FormEvent, useCallback, useEffect, useRef, useState } from 'react'; import { useSettingsState, useSidebarState, useThreadsState, useQuickEditState, useAccessor } from '../util/services.js'; -import { OnError } from '../../../../../../../platform/void/common/llmMessageTypes.js'; -import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js'; -import { VoidInputBox } from '../util/inputs.js'; +import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js'; import { QuickEditPropsType } from '../../../quickEditActions.js'; import { ButtonStop, ButtonSubmit, IconX } from '../sidebar-tsx/SidebarChat.js'; import { ModelDropdown } from '../void-settings-tsx/ModelDropdown.js'; -import { X } from 'lucide-react'; import { VOID_CTRL_K_ACTION_ID } from '../../../actionIDs.js'; -export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => { +export const QuickEditChat = ({ + diffareaid, + onChangeHeight, + onChangeText: onChangeText_, + textAreaRef: textAreaRef_, + initText +}: QuickEditPropsType) => { const accessor = useAccessor() const inlineDiffsService = accessor.get('IInlineDiffsService') const sizerRef = useRef(null) - const inputBoxRef: React.MutableRefObject = useRef(null); - + const textAreaRef = useRef(null) + const textAreaFnsRef = useRef(null) useEffect(() => { const inputContainer = sizerRef.current if (!inputContainer) return; - // only observing 1 element let resizeObserver: ResizeObserver | undefined resizeObserver = new ResizeObserver((entries) => { @@ -33,37 +35,35 @@ export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onC onChangeHeight(height) }) resizeObserver.observe(inputContainer); - return () => { resizeObserver?.disconnect(); }; }, [onChangeHeight]); + // state of current message - const [instructions, setInstructions] = useState(initText ?? '') // the user's instructions - const onChangeText = useCallback((newStr: string) => { - setInstructions(newStr) - onUserUpdateText(newStr) - }, [setInstructions]) - const isDisabled = !instructions.trim() + const [instructionsAreEmpty, setInstructionsAreEmpty] = useState(!(initText ?? '')) // the user's instructions + const isDisabled = instructionsAreEmpty const currentlyStreamingIdRef = useRef(undefined) const [isStreaming, setIsStreaming] = useState(false) const onSubmit = useCallback((e: FormEvent) => { + if (isDisabled) return if (currentlyStreamingIdRef.current !== undefined) return - inputBoxRef.current?.disable() + textAreaFnsRef.current?.disable() + const instructions = textAreaRef.current?.value ?? '' currentlyStreamingIdRef.current = inlineDiffsService.startApplying({ featureName: 'Ctrl+K', diffareaid: diffareaid, userMessage: instructions, }) setIsStreaming(true) - }, [inlineDiffsService, diffareaid, instructions]) + }, [isDisabled, inlineDiffsService, diffareaid]) const onInterrupt = useCallback(() => { if (currentlyStreamingIdRef.current !== undefined) inlineDiffsService.interruptStreaming(currentlyStreamingIdRef.current) - inputBoxRef.current?.enable() + textAreaFnsRef.current?.enable() setIsStreaming(false) }, [inlineDiffsService]) @@ -73,19 +73,8 @@ export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onC }, [inlineDiffsService, diffareaid]) - // sync init value - const alreadySetRef = useRef(false) - useEffect(() => { - if (!inputBoxRef.current) return - if (alreadySetRef.current) return - alreadySetRef.current = true - inputBoxRef.current.value = instructions - }, [initText, instructions]) - const keybindingString = accessor.get('IKeybindingService').lookupKeybinding(VOID_CTRL_K_ACTION_ID)?.getLabel() - - return
{ - if (e.key === 'Enter' && !e.shiftKey) { - onSubmit(e) - return - } - }} - onSubmit={(e) => { - if (isDisabled) - return - console.log('submit!') - onSubmit(e) - }} onClick={(e) => { - inputBoxRef.current?.focus() + textAreaRef.current?.focus() }} > @@ -134,20 +111,38 @@ export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onC `} > {/* text input */} - { - inputBoxRef.current = instance; + { + textAreaRef.current = r + textAreaRef_(r) + + // sync init value + textAreaFnsRef.current?.setValue(initText ?? '') // if presses the esc key, X - instance.element.addEventListener('keydown', (e) => { + r?.addEventListener('keydown', (e) => { if (e.key === 'Escape') onX() }) - onGetInputBox(instance); - instance.focus() - }, [onGetInputBox])} + + }, [textAreaRef_, initText, onX])} + + fnsRef={textAreaFnsRef} + + placeholder={`${keybindingString} to select`} + + onChangeText={useCallback((newStr: string) => { + setInstructionsAreEmpty(!newStr) + onChangeText_(newStr) + }, [onChangeText_])} + + onKeyDown={(e) => { + if (e.key === 'Enter' && !e.shiftKey) { + onSubmit(e) + return + } + }} + multiline={true} />
@@ -184,6 +179,7 @@ export const QuickEditChat = ({ diffareaid, onGetInputBox, onUserUpdateText, onC : // submit button (up arrow) } diff --git a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx index 1733d6a0..2df87e1c 100644 --- a/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx +++ b/src/vs/workbench/contrib/void/browser/react/src/util/inputs.tsx @@ -53,6 +53,7 @@ type InputBox2Props = { fnsRef?: { current: null | TextAreaFns }; onChangeText?: (value: string) => void; onKeyDown?: (e: React.KeyboardEvent) => void; + onChangeHeight?: (newHeight: number) => void; } export const VoidInputBox2 = forwardRef(function X({ placeholder, multiline, fnsRef, onKeyDown, onChangeText }, ref) { @@ -62,9 +63,12 @@ export const VoidInputBox2 = forwardRef(fun const adjustHeight = useCallback(() => { const r = textAreaRef.current if (!r) return - r.style.height = 'auto'; - const newHeight = Math.min(r.scrollHeight + 1, 500); - r.style.height = `${newHeight}px`; + + r.style.height = 'auto' // set to auto to reset height, then set to new height + if (r.scrollHeight === 0) return + const h = r.scrollHeight + const newHeight = Math.min(h, 500) + r.style.height = `${newHeight}px` }, []);