Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useSettingsState, useAccessor, useCtrlKZoneStreamingState } from '../util/services.js';
import { TextAreaFns, VoidInputBox2 } from '../util/inputs.js';
import { QuickEditPropsType } from '../../../quickEditActions.js';
import { ButtonStop, ButtonSubmit, IconX, VoidChatArea } from '../sidebar-tsx/SidebarChat.js';
import { VoidChatArea } from '../sidebar-tsx/composer/VoidChatArea.js';
import { CORTEXIDE_CTRL_K_ACTION_ID } from '../../../actionIDs.js';
import { useRefState } from '../util/helpers.js';
import { isFeatureNameDisabled } from '../../../../../../../workbench/contrib/cortexide/common/cortexideSettingsTypes.js';
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/

import React, { useEffect, useState } from 'react';

const scrollToBottom = (divRef: { current: HTMLElement | null }) => {
if (divRef.current) {
divRef.current.scrollTop = divRef.current.scrollHeight;
}
};

export const ScrollToBottomContainer = ({ children, className, style, scrollContainerRef }: { children: React.ReactNode, className?: string, style?: React.CSSProperties, scrollContainerRef: React.MutableRefObject<HTMLDivElement | null> }) => {
const [isAtBottom, setIsAtBottom] = useState(true);

const divRef = scrollContainerRef;

const onScroll = () => {
const div = divRef.current;
if (!div) return;

const isBottom = Math.abs(
div.scrollHeight - div.clientHeight - div.scrollTop
) < 4;

setIsAtBottom(isBottom);
};

useEffect(() => {
if (isAtBottom) {
scrollToBottom(divRef);
}
}, [children, isAtBottom, divRef]);

useEffect(() => {
scrollToBottom(divRef);
}, [divRef]);

return (
<div
ref={divRef}
onScroll={onScroll}
className={className}
style={style}
>
{children}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/*--------------------------------------------------------------------------------------
* Copyright 2025 Glass Devtools, Inc. All rights reserved.
* Licensed under the Apache License, Version 2.0. See LICENSE.txt for more information.
*--------------------------------------------------------------------------------------*/

import React, { useEffect, useState } from 'react';
import { File, Folder, Text } from 'lucide-react';
import { URI } from '../../../../../../../../base/common/uri.js';
import { useAccessor, useActiveURI } from '../../util/services.js';
import { StagingSelectionItem } from '../../../../../common/chatThreadServiceTypes.js';
import { IconX } from '../shared/icons.js';
import { getBasename, getRelative, voidOpenFileFn } from '../shared/pathUtils.js';

export const SelectedFiles = (
{ type, selections, setSelections, showProspectiveSelections, messageIdx, }:
| { type: 'past', selections: StagingSelectionItem[]; setSelections?: undefined, showProspectiveSelections?: undefined, messageIdx: number, }
| { type: 'staging', selections: StagingSelectionItem[]; setSelections: ((newSelections: StagingSelectionItem[]) => void), showProspectiveSelections?: boolean, messageIdx?: number }
) => {

const accessor = useAccessor()
const modelReferenceService = accessor.get('ICortexideModelService')

const { uri: currentURI } = useActiveURI()
const [recentUris, setRecentUris] = useState<URI[]>([])
const maxRecentUris = 10
const maxProspectiveFiles = 3
useEffect(() => {
if (!currentURI) return
setRecentUris(prev => {
const withoutCurrent = prev.filter(uri => uri.fsPath !== currentURI.fsPath)
const withCurrent = [currentURI, ...withoutCurrent]
return withCurrent.slice(0, maxRecentUris)
})
}, [currentURI])
const [prospectiveSelections, setProspectiveSelections] = useState<StagingSelectionItem[]>([])

useEffect(() => {
const computeRecents = async () => {
const prospectiveURIs = recentUris
.filter(uri => !selections.find(s => s.type === 'File' && s.uri.fsPath === uri.fsPath))
.slice(0, maxProspectiveFiles)

const answer: StagingSelectionItem[] = []
for (const uri of prospectiveURIs) {
answer.push({
type: 'File',
uri: uri,
language: (await modelReferenceService.getModelSafe(uri)).model?.getLanguageId() || 'plaintext',
state: { wasAddedAsCurrentFile: false },
})
}
return answer
}

if (type === 'staging' && showProspectiveSelections) {
computeRecents().then((a) => setProspectiveSelections(a))
}
else {
setProspectiveSelections([])
}
}, [recentUris, selections, type, showProspectiveSelections, modelReferenceService])


const allSelections = [...selections, ...prospectiveSelections]

if (allSelections.length === 0) {
return null
}

return (
<div className='flex items-center flex-wrap text-left relative gap-x-0.5 gap-y-1 pb-0.5'>

{allSelections.map((selection, i) => {

const isThisSelectionProspective = i > selections.length - 1

const thisKey = selection.type === 'CodeSelection' ? selection.type + selection.language + selection.range + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath
: selection.type === 'File' ? selection.type + selection.language + selection.state.wasAddedAsCurrentFile + selection.uri.fsPath
: selection.type === 'Folder' ? selection.type + selection.language + selection.state + selection.uri.fsPath
: i

const SelectionIcon = (
selection.type === 'File' ? File
: selection.type === 'Folder' ? Folder
: selection.type === 'CodeSelection' ? Text
: (undefined as never)
)

return <div
key={thisKey}
className={`flex flex-col space-y-[1px]`}
>
<span className="truncate overflow-hidden text-ellipsis"
data-tooltip-id='cortex-tooltip'
data-tooltip-content={getRelative(selection.uri, accessor)}
data-tooltip-place='top'
data-tooltip-delay-show={3000}
>
<div
className={`
flex items-center gap-1 relative
px-1
w-fit h-fit
select-none
text-xs text-nowrap
border rounded-sm
${isThisSelectionProspective ? 'bg-void-bg-1 text-void-fg-3 opacity-80' : 'bg-void-bg-1 hover:brightness-95 text-void-fg-1'}
${isThisSelectionProspective
? 'border-void-border-2'
: 'border-void-border-1'
}
hover:border-void-border-1
transition-all duration-150
`}
onClick={() => {
if (type !== 'staging') return;
if (isThisSelectionProspective) {
setSelections([...selections, selection])
}
else if (selection.type === 'File') {
voidOpenFileFn(selection.uri, accessor);

const wasAddedAsCurrentFile = selection.state.wasAddedAsCurrentFile
if (wasAddedAsCurrentFile) {
const newSelection: StagingSelectionItem = { ...selection, state: { ...selection.state, wasAddedAsCurrentFile: false } }
setSelections([
...selections.slice(0, i),
newSelection,
...selections.slice(i + 1)
])
}
}
else if (selection.type === 'CodeSelection') {
voidOpenFileFn(selection.uri, accessor, selection.range);
}
else if (selection.type === 'Folder') {
// TODO!!! reveal in tree
}
}}
>
{<SelectionIcon size={10} />}

{getBasename(selection.uri.fsPath)
+ (selection.type === 'CodeSelection' ? ` (${selection.range[0]}-${selection.range[1]})` : '')
}

{selection.type === 'File' && selection.state.wasAddedAsCurrentFile && messageIdx === undefined && currentURI?.fsPath === selection.uri.fsPath ?
<span className="text-[8px] void-opacity-60 text-void-fg-4">
{`(Current File)`}
</span>
: null
}

{type === 'staging' && !isThisSelectionProspective ?
<div
className='cursor-pointer z-1 self-stretch flex items-center justify-center'
onClick={(e) => {
e.stopPropagation();
if (type !== 'staging') return;
setSelections([...selections.slice(0, i), ...selections.slice(i + 1)])
}}
>
<IconX
className='stroke-[2]'
size={10}
/>
</div>
: <></>
}
</div>
</span>
</div>

})}


</div>

)
}
Loading
Loading