update ctrlk

This commit is contained in:
Andrew Pareles 2024-12-29 19:16:16 -08:00
parent ff7830e704
commit ef8bba00b9
5 changed files with 130 additions and 85 deletions

View file

@ -56,10 +56,10 @@ registerColor('void.sweepIdxBG', configOfBG(sweepIdxBG), '', true);
// similar to ServiceLLM
export type StartStreamingOpts = {
export type StartApplyingOpts = {
featureName: 'Ctrl+K';
diffareaid: number; // id of the CtrlK area
userMessage?: undefined;
diffareaid: number; // id of the CtrlK area (contains text selection)
userMessage: string; // user message
} | {
featureName: 'Ctrl+L';
userMessage: string;
@ -103,6 +103,7 @@ type CtrlKZone = {
type: 'CtrlKZone';
originalCode?: undefined;
userText: string | null;
_alreadyMounted: boolean;
} & CommonZoneProps
@ -154,7 +155,7 @@ type HistorySnapshot = {
export interface IInlineDiffsService {
readonly _serviceBrand: undefined;
startStreaming(opts: StartStreamingOpts, userMessage: string): number | undefined;
startApplying(opts: StartApplyingOpts): number | undefined;
interruptStreaming(diffareaid: number): void;
addCtrlKZone(opts: AddCtrlKOpts): number;
}
@ -199,7 +200,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
for (const change of e.changes) { this._realignAllDiffAreasLines(uri, change.text, change.range) }
this._refreshStylesAndDiffsInURI(uri)
// // diffArea should be removed if we just discovered it has no more diffs in it
// // TODO diffArea should be removed if we just discovered it has no more diffs in it
// for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) {
// const diffArea = this.diffAreaOfId[diffareaid]
// if (Object.keys(diffArea._diffOfId).length === 0 && !diffArea._sweepState.isStreaming) {
@ -281,6 +282,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
}
// highlight the ctrlK zone
if (diffArea.type === 'CtrlKZone') {
if (diffArea._alreadyMounted) continue
diffArea._alreadyMounted = true
const consistentZoneId = this._zoneStyleService.addConsistentItemToURI({
uri,
fn: (editor) => {
@ -311,6 +315,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
editor.changeViewZones(accessor => { if (zoneId) { accessor.layoutZone(zoneId) } })
},
onUserUpdateText(text) { diffArea.userText = text; },
initText: diffArea.userText,
}
mountCtrlK(domNode, accessor, props)
})
@ -476,7 +481,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const { snapshottedDiffAreaOfId, entireFileCode: entireModelCode } = structuredClone(snapshot) // don't want to destroy the snapshot
// delete all current decorations (diffs, sweep styles) so we don't have any unwanted leftover decorations
this._clearAllEffects(uri)
this._clearAllDiffZoneEffects(uri)
// for each diffarea in this uri, stop streaming if currently streaming
for (const diffareaid in this.diffAreaOfId) {
@ -510,6 +515,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
...snapshottedDiffArea as DiffAreaSnapshot<CtrlKZone>,
_URI: uri,
_removeStylesFns: new Set(),
_alreadyMounted: false,
}
}
this.diffAreasOfURI[uri.fsPath].add(diffareaid)
@ -561,27 +567,30 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
// clear diffZone effects (diffs)
if (diffArea.type === 'DiffZone')
this._deleteDiffs(diffArea)
else if (diffArea.type === 'CtrlKZone') {
}
diffArea._removeStylesFns.forEach(removeStyles => removeStyles())
}
private _clearAllDiffAreaEffects(diffArea: DiffArea) {
this._deleteEffects(diffArea)
diffArea._removeStylesFns.clear()
}
// clears all Diffs (and their styles) and all styles of DiffAreas
private _clearAllEffects(uri: URI) {
private _clearAllDiffZoneEffects(uri: URI) {
for (let diffareaid of this.diffAreasOfURI[uri.fsPath]) {
const diffArea = this.diffAreaOfId[diffareaid]
this._deleteEffects(diffArea)
diffArea._removeStylesFns.clear()
if (diffArea.type !== 'DiffZone') continue
this._clearAllDiffAreaEffects(diffArea)
}
}
// delete all diffs, update diffAreaOfId, update diffAreasOfModelId
private _deleteDiffArea(diffArea: DiffArea) {
// we had clear all diffs, but not really needed
this._clearAllDiffAreaEffects(diffArea)
delete this.diffAreaOfId[diffArea.diffareaid]
this.diffAreasOfURI[diffArea._URI.fsPath].delete(diffArea.diffareaid.toString())
}
@ -692,10 +701,10 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
const content = this._readURI(uri)
if (content === null) return
// 1. clear Diffs and styles
this._clearAllEffects(uri)
// 1. clear Diffs and DiffZone styles
this._clearAllDiffZoneEffects(uri)
// 2. recompute all diffs on each editor with this URI
// 2. recompute all Diffs on each editor with this URI
this._addDiffAreaStylesToURI(uri)
@ -768,7 +777,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
private _initializeStream(opts: StartStreamingOpts): DiffZone | undefined {
private _initializeApplyStream(opts: StartApplyingOpts): DiffZone | undefined {
const { featureName } = opts
@ -807,6 +816,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
userMessage = opts.userMessage
}
else if (featureName === 'Ctrl+K') {
console.log('INIT APPLY STREAM CTRLK')
const { diffareaid } = opts
@ -818,8 +828,9 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
endLine = endLine_
// check if there's overlap with any other ctrlKZones and if so, focus them
for (const diffareaid of this.diffAreasOfURI[uri.fsPath]) {
const da2 = this.diffAreaOfId[diffareaid]
for (const diffareaid2 of this.diffAreasOfURI[uri.fsPath]) {
if (diffareaid.toString() === diffareaid2) continue
const da2 = this.diffAreaOfId[diffareaid2]
if (!da2) continue
const noOverlap = da2.startLine > endLine || da2.endLine < startLine
if (!noOverlap) {
@ -827,6 +838,7 @@ class InlineDiffsService extends Disposable implements IInlineDiffsService {
return
}
}
console.log('userTEXT', userText)
if (!userText) return
userMessage = userText
@ -948,6 +960,7 @@ ${userMessage}
type: 'CtrlKZone',
startLine: startLine,
endLine: endLine,
_alreadyMounted: false,
_URI: uri,
userText: null,
_removeStylesFns: new Set(),
@ -962,8 +975,8 @@ ${userMessage}
public startStreaming(opts: StartStreamingOpts) {
const addedDiffZone = this._initializeStream(opts)
public startApplying(opts: StartApplyingOpts) {
const addedDiffZone = this._initializeApplyStream(opts)
return addedDiffZone?.diffareaid
}

View file

@ -11,6 +11,7 @@ export type QuickEditPropsType = {
diffareaid: number,
onChangeHeight: (height: number) => void;
onUserUpdateText: (text: string) => void;
initText: string | null;
}
export type QuickEdit = {

View file

@ -6,25 +6,26 @@ import { InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox
import { getCmdKey } from '../../../helpers/getCmdKey.js';
import { VoidInputBox } from '../util/inputs.js';
import { QuickEditPropsType } from '../../../quickEditActions.js';
import { ButtonStop, ButtonSubmit } from '../sidebar-tsx/SidebarChat.js';
export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight }: QuickEditPropsType) => {
export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight, initText }: QuickEditPropsType) => {
const accessor = useAccessor()
const inlineDiffsService = accessor.get('IInlineDiffsService')
const formRef = useRef<HTMLFormElement | null>(null)
const sizerRef = useRef<HTMLDivElement | null>(null)
const inputBoxRef: React.MutableRefObject<InputBox | null> = useRef(null);
useEffect(() => {
console.log('mounting resize observer')
const inputContainer = formRef.current
const inputContainer = sizerRef.current
if (!inputContainer) return;
// only observing 1 element
const resizeObserver = new ResizeObserver((entries) => {
const height = entries[0].contentRect.height
const height = entries[0].borderBoxSize[0].blockSize
console.log('NEW HEIGHT', height)
onChangeHeight(height)
});
@ -32,83 +33,114 @@ export const CtrlKChat = ({ diffareaid, onUserUpdateText, onChangeHeight }: Quic
return () => { resizeObserver.disconnect(); };
}, [onChangeHeight]);
// state of current message
const [instructions, setInstructions] = useState('') // the user's instructions
const [instructions, setInstructions] = useState(initText ?? '') // the user's instructions
const onChangeText = useCallback((newStr: string) => {
setInstructions(newStr)
onUserUpdateText(newStr)
}, [setInstructions])
const isDisabled = !instructions.trim()
const currentlyStreamingRef = useRef<number | undefined>(undefined)
const currentlyStreamingIdRef = useRef<number | undefined>(undefined)
const [isStreaming, setIsStreaming] = useState(false)
const onSubmit = useCallback((e: FormEvent) => {
currentlyStreamingRef.current = inlineDiffsService.startStreaming({
if (currentlyStreamingIdRef.current !== undefined) return
currentlyStreamingIdRef.current = inlineDiffsService.startApplying({
featureName: 'Ctrl+K',
diffareaid: diffareaid,
}, instructions)
userMessage: instructions,
})
setIsStreaming(true)
}, [inlineDiffsService, diffareaid, instructions])
const onInterrupt = useCallback(() => {
if (currentlyStreamingRef.current !== undefined)
inlineDiffsService.interruptStreaming(currentlyStreamingRef.current)
}, [])
if (currentlyStreamingIdRef.current !== undefined)
inlineDiffsService.interruptStreaming(currentlyStreamingIdRef.current)
setIsStreaming(false)
}, [inlineDiffsService])
return <form
ref={formRef}
className={
// copied from SidebarChat.tsx
`flex flex-col gap-2 p-1 relative input text-left shrink-0
transition-all duration-200
rounded-md
bg-vscode-input-bg
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border
`
}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
return
}
}}
onSubmit={(e) => {
if (isDisabled) {
// __TODO__ show disabled
return
}
console.log('submit!')
onSubmit(e)
}}
onClick={(e) => {
if (e.currentTarget === e.target) {
inputBoxRef.current?.focus()
}
}}
>
<div
// 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])
return <div className='py-2' ref={sizerRef}>
<form
className={
// copied from SidebarChat.tsx
`@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_div.monaco-inputbox]:!void- @@[&_div.monaco-inputbox]:!void-outline-none`
`flex flex-col gap-2 p-1 relative input text-left shrink-0
transition-all duration-200
rounded-md
bg-vscode-input-bg
border border-vscode-commandcenter-inactive-border focus-within:border-vscode-commandcenter-active-border hover:border-vscode-commandcenter-active-border
`
}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
onSubmit(e)
return
}
}}
onSubmit={(e) => {
if (isDisabled) {
// __TODO__ show disabled
return
}
console.log('submit!')
onSubmit(e)
}}
onClick={(e) => {
if (e.currentTarget === e.target) {
inputBoxRef.current?.focus()
}
}}
>
{/* text input */}
<VoidInputBox
placeholder={`${getCmdKey()}+K to select`}
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
multiline={true}
/>
<button type='button' onClick={() => { onInterrupt() }}>
Stop
</button>
</div>
<div // this div is used to position the input box properly
className={`right-0 left-0 m-2 z-[999]`}
>
<div className='flex flex-row justify-between items-end gap-1'>
{/* left (input) */}
<div // copied from SidebarChat.tsx
className={`w-full
@@[&_textarea]:!void-bg-transparent @@[&_textarea]:!void-outline-none @@[&_textarea]:!void-text-vscode-input-fg @@[&_div.monaco-inputbox]:!void-outline-none`}>
{/* text input */}
<VoidInputBox
placeholder={`${getCmdKey()}+K to select`}
onChangeText={onChangeText}
inputBoxRef={inputBoxRef}
multiline={true}
/>
</div>
{/* right (button) */}
<div className='flex flex-row items-end'>
{/* submit / stop button */}
{isStreaming ?
// stop button
<ButtonStop
onClick={onInterrupt}
/>
:
// submit button (up arrow)
<ButtonSubmit
disabled={isDisabled}
/>
}
</div>
</div>
</div>
</form>
</form>
</div>
}

View file

@ -47,9 +47,8 @@ const CodeButtonsOnHover = ({ diffRepr: text }: { diffRepr: string }) => {
</button>
<button
className="btn btn-secondary btn-sm border border-vscode-input-border rounded"
onClick={async () => {
inlineDiffService.startStreaming({ featureName: 'Ctrl+L' }, text)
onClick={() => {
inlineDiffService.startApplying({ featureName: 'Ctrl+L', userMessage: text })
}}
>
Apply

View file

@ -112,11 +112,11 @@ export const IconWarning = ({ size, className = '' }: { size: number, className?
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement>
export const ButtonSubmit = ({ className, disabled, ...props }: ButtonProps & Required<Pick<ButtonProps, 'disabled'>>) => {
return <button
type='submit'
className={`size-[20px] rounded-full shrink-0 grow-0 cursor-pointer
${disabled ? 'bg-vscode-disabled-fg' : 'bg-white'}
${className}
`}
type='submit'
{...props}
>
<IconArrowUp size={20} className="stroke-[2]" />