mirror of
https://github.com/voideditor/void
synced 2026-05-24 09:58:23 +00:00
multiple selections
This commit is contained in:
parent
19e3ebefda
commit
842d388669
4 changed files with 124 additions and 83 deletions
|
|
@ -22,6 +22,7 @@ import { OnError, ServiceSendLLMMessageParams } from '../../../../../../../platf
|
|||
import { getCmdKey } from '../../../getCmdKey.js'
|
||||
import { HistoryInputBox, InputBox } from '../../../../../../../base/browser/ui/inputbox/inputBox.js';
|
||||
import { VoidInputBox } from './inputs.js';
|
||||
import { ModelSelectionOfFeature } from './ModelSelectionSettings.js';
|
||||
|
||||
|
||||
const IconX = ({ size, className = '' }: { size: number, className?: string }) => {
|
||||
|
|
@ -111,7 +112,7 @@ export const SelectedFiles = (
|
|||
|
||||
return (
|
||||
!!selections && selections.length !== 0 && (
|
||||
<div className='flex flex-wrap'>
|
||||
<div className='flex flex-wrap gap-4'>
|
||||
{selections.map((selection, i) => (
|
||||
<Fragment key={i}>
|
||||
{/* selected file summary */}
|
||||
|
|
@ -131,16 +132,19 @@ export const SelectedFiles = (
|
|||
}}
|
||||
>
|
||||
|
||||
{/* file name */}
|
||||
<span className='truncate'>{getBasename(selection.fileURI.fsPath)}</span>
|
||||
<span className='truncate'>
|
||||
{/* file name */}
|
||||
{getBasename(selection.fileURI.fsPath)}
|
||||
{/* selection range */}
|
||||
{selection.selectionStr !== null ? ` (${selection.range.startLineNumber}-${selection.range.endLineNumber})` : ''}
|
||||
</span>
|
||||
|
||||
{/* type of selection */}
|
||||
<span className='truncate text-opacity-75'>{selection.selectionStr ? 'Selection' : 'File'}</span>
|
||||
|
||||
<span className='truncate text-opacity-75'>{selection.selectionStr !== null ? 'Selection' : 'File'}</span>
|
||||
|
||||
{/* X button */}
|
||||
{type === 'staging' && // hoveredIdx === i
|
||||
<span className='absolute right-0 top-0 translate-x-[50%] translate-y-[-50%] cursor-pointer bg-white rounded-full border border-vscode-widget-border'
|
||||
<span className='absolute right-0 top-0 translate-x-[50%] translate-y-[-50%] cursor-pointer bg-white rounded-full border border-vscode-input-border z-1'
|
||||
onClick={() => {
|
||||
if (type !== 'staging') return;
|
||||
setStaging([...selections.slice(0, i), ...selections.slice(i + 1)])
|
||||
|
|
@ -151,7 +155,7 @@ export const SelectedFiles = (
|
|||
}
|
||||
</div>
|
||||
{/* selection full text */}
|
||||
{type === 'staging' && selection.selectionStr && selectionIsOpened[i] &&
|
||||
{selection.selectionStr && selectionIsOpened[i] &&
|
||||
<BlockCode
|
||||
text={selection.selectionStr}
|
||||
// buttonsOnHover={(<button
|
||||
|
|
@ -245,7 +249,7 @@ export const SidebarChat = () => {
|
|||
// state of current message
|
||||
const [instructions, setInstructions] = useState('') // the user's instructions
|
||||
const onChangeText = useCallback((newStr: string) => { setInstructions(newStr) }, [setInstructions])
|
||||
const isDisabled = !instructions
|
||||
const isDisabled = !instructions.trim()
|
||||
const formRef = useRef<HTMLFormElement | null>(null)
|
||||
|
||||
|
||||
|
|
@ -352,10 +356,12 @@ export const SidebarChat = () => {
|
|||
|
||||
const selections = threadsState._currentStagingSelections
|
||||
|
||||
const previousMessages = currentThread?.messages ?? []
|
||||
|
||||
return <>
|
||||
<div className="overflow-x-hidden space-y-4">
|
||||
{/* previous messages */}
|
||||
{currentThread !== null && currentThread?.messages.map((message, i) =>
|
||||
{previousMessages.map((message, i) =>
|
||||
<ChatBubble key={i} chatMessage={message} />
|
||||
)}
|
||||
|
||||
|
|
@ -363,79 +369,95 @@ export const SidebarChat = () => {
|
|||
<ChatBubble chatMessage={{ role: 'assistant', content: messageStream, displayContent: messageStream || null }} />
|
||||
</div>
|
||||
|
||||
|
||||
{/* input box */}
|
||||
<form
|
||||
ref={formRef}
|
||||
className={`flex flex-col gap-2 p-2 relative input text-left shrink-0
|
||||
bg-vscode-input-bg
|
||||
border border-vscode-input-border rounded-md
|
||||
<div // this div is used to position the input box properly
|
||||
className={`right-0 left-0 m-2
|
||||
${previousMessages.length === 0 ? '' : 'absolute bottom-0'}
|
||||
`}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) onSubmit(e) }}
|
||||
|
||||
onSubmit={(e) => {
|
||||
console.log('submit!')
|
||||
onSubmit(e)
|
||||
}}
|
||||
>
|
||||
{/* top row */}
|
||||
<div className=''>
|
||||
{/* selections */}
|
||||
{(selections && selections.length !== 0) &&
|
||||
<SelectedFiles type='staging' selections={selections} setStaging={threadsStateService.setStaging.bind(threadsStateService)} />
|
||||
}
|
||||
<form
|
||||
ref={formRef}
|
||||
className={`flex flex-col gap-2 p-2 relative input text-left shrink-0
|
||||
transition-all duration-200
|
||||
rounded-md
|
||||
bg-vscode-input-bg
|
||||
border border-vscode-commandcenter-border hover:border-vscode-commandcenter-active-border
|
||||
`}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
onSubmit(e)
|
||||
}
|
||||
}}
|
||||
onSubmit={(e) => {
|
||||
console.log('submit!')
|
||||
onSubmit(e)
|
||||
}}
|
||||
>
|
||||
{/* top row */}
|
||||
<div className=''>
|
||||
{/* selections */}
|
||||
{(selections && selections.length !== 0) &&
|
||||
<SelectedFiles type='staging' selections={selections} setStaging={threadsStateService.setStaging.bind(threadsStateService)} />
|
||||
}
|
||||
|
||||
{/* error message */}
|
||||
{latestError === null ? null :
|
||||
<ErrorDisplay
|
||||
message={latestError.message}
|
||||
fullError={latestError.fullError}
|
||||
onDismiss={() => { setLatestError(null) }}
|
||||
showDismiss={true}
|
||||
{/* error message */}
|
||||
{latestError === null ? null :
|
||||
<ErrorDisplay
|
||||
message={latestError.message}
|
||||
fullError={latestError.fullError}
|
||||
onDismiss={() => { setLatestError(null) }}
|
||||
showDismiss={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
|
||||
{/* middle row */}
|
||||
<div className=''>
|
||||
{/* text input */}
|
||||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+L to select`}
|
||||
onChangeText={onChangeText}
|
||||
inputBoxRef={inputBoxRef}
|
||||
multiline={true}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* middle row */}
|
||||
<div className=''>
|
||||
{/* text input */}
|
||||
<VoidInputBox
|
||||
placeholder={`${getCmdKey()}+L to select`}
|
||||
onChangeText={onChangeText}
|
||||
inputBoxRef={inputBoxRef}
|
||||
multiline={true}
|
||||
/>
|
||||
</div>
|
||||
{/* bottom row */}
|
||||
<div className='flex flex-row justify-between items-end'>
|
||||
{/* submit options */}
|
||||
<div>
|
||||
<ModelSelectionOfFeature featureName='Ctrl+L' />
|
||||
</div>
|
||||
|
||||
{/* bottom row */}
|
||||
<div className=''>
|
||||
{/* submit / stop button */}
|
||||
{isLoading ?
|
||||
// stop button
|
||||
<button
|
||||
className="p-[5px] bg-white rounded-full cursor-pointer"
|
||||
onClick={onAbort}
|
||||
type='button'
|
||||
>
|
||||
<IconSquare size={24} className="stroke-[2]" />
|
||||
</button>
|
||||
:
|
||||
// submit button (up arrow)
|
||||
<button
|
||||
className={`${isDisabled ? 'prefix-bg-vscode-disabled-fg' : 'bg-white'}
|
||||
rounded-full cursor-pointer`}
|
||||
disabled={isDisabled}
|
||||
type='submit'
|
||||
>
|
||||
<IconArrowUp size={24} className="stroke-[2]" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
{/* submit / stop button */}
|
||||
{isLoading ?
|
||||
// stop button
|
||||
<button
|
||||
className="p-[5px] bg-white rounded-full cursor-pointer"
|
||||
onClick={onAbort}
|
||||
type='button'
|
||||
>
|
||||
<IconSquare size={24} className="stroke-[2]" />
|
||||
</button>
|
||||
:
|
||||
// submit button (up arrow)
|
||||
<button
|
||||
className={`${isDisabled ? 'bg-vscode-disabled-fg cursor-not-allowed' : 'bg-white cursor-pointer'}
|
||||
rounded-full
|
||||
shrink-0 grow-0
|
||||
`}
|
||||
disabled={isDisabled}
|
||||
type='submit'
|
||||
>
|
||||
<IconArrowUp size={24} className="stroke-[2]" />
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</>
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ module.exports = {
|
|||
extend: {
|
||||
colors: {
|
||||
vscode: {
|
||||
// see https://code.visualstudio.com/api/references/theme-color
|
||||
// see: https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content
|
||||
|
||||
// base colors
|
||||
"fg": "var(--vscode-foreground)",
|
||||
|
|
@ -53,6 +53,17 @@ module.exports = {
|
|||
"input-validation-warning-fg": "inputValidation-var(--vscode-warningForeground)",
|
||||
"input-validation-warning-border": "inputValidation-var(--vscode-warningBorder)",
|
||||
|
||||
// command center colors (the top bar)
|
||||
"commandcenter-fg": "commandCenter.foreground",
|
||||
"commandcenter-active-fg": "commandCenter.activeForeground",
|
||||
"commandcenter-bg": "commandCenter.background",
|
||||
"commandcenter-active-bg": "commandCenter.activeBackground",
|
||||
"commandcenter-border": "commandCenter.border",
|
||||
"commandcenter-inactive-fg": "commandCenter.inactiveForeground",
|
||||
"commandcenter-inactive-border": "commandCenter.inactiveBorder",
|
||||
"commandcenter-active-border": "commandCenter.activeBorder",
|
||||
"commandcenter-debugging-bg": "commandCenter.debuggingBackground",
|
||||
|
||||
// badge colors
|
||||
"badge-fg": "var(--vscode-badge-foreground)",
|
||||
"badge-bg": "var(--vscode-badge-background)",
|
||||
|
|
|
|||
|
|
@ -69,22 +69,27 @@ registerAction2(class extends Action2 {
|
|||
stateService.setState({ isHistoryOpen: false, currentTab: 'chat' })
|
||||
stateService.fireFocusChat()
|
||||
|
||||
const selectionRange = roundRangeToLines(
|
||||
accessor.get(IEditorService).activeTextEditorControl?.getSelection()
|
||||
)
|
||||
|
||||
// add selection
|
||||
const threadHistoryService = accessor.get(IThreadHistoryService)
|
||||
const currentStaging = threadHistoryService.state._currentStagingSelections
|
||||
const currentStagingEltIdx = currentStaging?.findIndex(s => s.fileURI.fsPath === model.uri.fsPath)
|
||||
|
||||
// if there exists a selection with this URI, replace it
|
||||
const selectionRange = roundRangeToLines(
|
||||
accessor.get(IEditorService).activeTextEditorControl?.getSelection()
|
||||
const currentStagingEltIdx = currentStaging?.findIndex(s =>
|
||||
s.fileURI.fsPath === model.uri.fsPath
|
||||
&& s.range?.startLineNumber === selectionRange?.startLineNumber
|
||||
&& s.range?.endLineNumber === selectionRange?.endLineNumber
|
||||
)
|
||||
|
||||
if (selectionRange) {
|
||||
const selection: CodeStagingSelection = {
|
||||
selectionStr: getContentInRange(model, selectionRange),
|
||||
fileURI: model.uri
|
||||
fileURI: model.uri,
|
||||
range: selectionRange,
|
||||
}
|
||||
|
||||
// overwrite selections that match with this one (compares by `fileURI` and line numbers in `range`)
|
||||
if (currentStagingEltIdx !== undefined && currentStagingEltIdx !== -1) {
|
||||
threadHistoryService.setStaging([
|
||||
...currentStaging!.slice(0, currentStagingEltIdx),
|
||||
|
|
|
|||
|
|
@ -11,17 +11,20 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
|||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { IAutocompleteService } from './registerAutocomplete.js';
|
||||
import { IRange } from '../../../../editor/common/core/range.js';
|
||||
|
||||
export type CodeSelection = {
|
||||
selectionStr: string | null;
|
||||
fileURI: URI;
|
||||
content: string; // TODO remove this
|
||||
selectionStr: string | null;
|
||||
content: string; // TODO remove this (replace `selectionStr` with `content`)
|
||||
range: IRange;
|
||||
}
|
||||
|
||||
// if selectionStr is null, it means to use the entire file at send time
|
||||
export type CodeStagingSelection = {
|
||||
selectionStr: string | null;
|
||||
fileURI: URI;
|
||||
selectionStr: string | null;
|
||||
range: IRange;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue