mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
update ctrlk
This commit is contained in:
parent
ff7830e704
commit
ef8bba00b9
5 changed files with 130 additions and 85 deletions
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ export type QuickEditPropsType = {
|
|||
diffareaid: number,
|
||||
onChangeHeight: (height: number) => void;
|
||||
onUserUpdateText: (text: string) => void;
|
||||
initText: string | null;
|
||||
}
|
||||
|
||||
export type QuickEdit = {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]" />
|
||||
|
|
|
|||
Loading…
Reference in a new issue