Skip to content

Commit d08ac56

Browse files
authored
SCM - drag and drop change from graph to chat (microsoft#257091)
1 parent 6a8c325 commit d08ac56

File tree

5 files changed

+137
-4
lines changed

5 files changed

+137
-4
lines changed

src/vs/platform/dnd/browser/dnd.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const CodeDataTransfers = {
3434
SYMBOLS: 'application/vnd.code.symbols',
3535
MARKERS: 'application/vnd.code.diagnostics',
3636
NOTEBOOK_CELL_OUTPUT: 'notebook-cell-output',
37+
SCM_HISTORY_ITEM: 'scm-history-item',
3738
};
3839

3940
export interface IDraggedResourceEditorInput extends IBaseTextResourceEditorInput {

src/vs/workbench/contrib/chat/browser/chatAttachmentResolveService.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@ import { UntitledTextEditorInput } from '../../../services/untitled/common/untit
2525
import { createNotebookOutputVariableEntry, NOTEBOOK_CELL_OUTPUT_MIME_TYPE_LIST_FOR_CHAT_CONST } from '../../notebook/browser/contrib/chat/notebookChatUtils.js';
2626
import { getOutputViewModelFromId } from '../../notebook/browser/controller/cellOutputActions.js';
2727
import { getNotebookEditorFromEditorPane } from '../../notebook/browser/notebookBrowser.js';
28+
import { SCMHistoryItemTransferData } from '../../scm/browser/scmHistoryChatContext.js';
2829
import { CHAT_ATTACHABLE_IMAGE_MIME_TYPES, getAttachableImageExtension } from '../common/chatModel.js';
29-
import { IChatRequestVariableEntry, OmittedState, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry, toPromptFileVariableEntry, PromptFileVariableKind } from '../common/chatVariableEntries.js';
30+
import { IChatRequestVariableEntry, OmittedState, IDiagnosticVariableEntry, IDiagnosticVariableEntryFilterData, ISymbolVariableEntry, toPromptFileVariableEntry, PromptFileVariableKind, ISCMHistoryItemVariableEntry } from '../common/chatVariableEntries.js';
3031
import { getPromptsTypeForLanguageId, PromptsType } from '../common/promptSyntax/promptTypes.js';
3132
import { imageToHash } from './chatPasteProviders.js';
3233
import { resizeImage } from './imageUtils.js';
@@ -45,6 +46,7 @@ export interface IChatAttachmentResolveService {
4546
resolveMarkerAttachContext(markers: MarkerTransferData[]): IDiagnosticVariableEntry[];
4647
resolveSymbolsAttachContext(symbols: DocumentSymbolTransferData[]): ISymbolVariableEntry[];
4748
resolveNotebookOutputAttachContext(data: NotebookCellOutputTransferData): IChatRequestVariableEntry[];
49+
resolveSourceControlHistoryItemAttachContext(data: SCMHistoryItemTransferData[]): ISCMHistoryItemVariableEntry[];
4850
}
4951

5052
export class ChatAttachmentResolveService implements IChatAttachmentResolveService {
@@ -274,6 +276,21 @@ export class ChatAttachmentResolveService implements IChatAttachmentResolveServi
274276

275277
return [];
276278
}
279+
280+
// --- SOURCE CONTROL ---
281+
282+
public resolveSourceControlHistoryItemAttachContext(data: SCMHistoryItemTransferData[]): ISCMHistoryItemVariableEntry[] {
283+
return data.map(d => ({
284+
id: d.historyItem.id,
285+
name: d.name,
286+
value: URI.revive(d.resource),
287+
historyItem: {
288+
...d.historyItem,
289+
references: []
290+
},
291+
kind: 'scmHistoryItem'
292+
} satisfies ISCMHistoryItemVariableEntry));
293+
}
277294
}
278295

279296
function symbolId(resource: URI, range?: IRange): string {

src/vs/workbench/contrib/chat/browser/chatDragAndDrop.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { IChatAttachmentResolveService, ImageTransferData } from './chatAttachme
2525
import { ChatAttachmentModel } from './chatAttachmentModel.js';
2626
import { IChatInputStyles } from './chatInputPart.js';
2727
import { convertStringToUInt8Array } from './imageUtils.js';
28+
import { extractSCMHistoryItemDropData } from '../../scm/browser/scmHistoryChatContext.js';
2829

2930
enum ChatDragAndDropType {
3031
FILE_INTERNAL,
@@ -34,7 +35,8 @@ enum ChatDragAndDropType {
3435
SYMBOL,
3536
HTML,
3637
MARKER,
37-
NOTEBOOK_CELL_OUTPUT
38+
NOTEBOOK_CELL_OUTPUT,
39+
SCM_HISTORY_ITEM
3840
}
3941

4042
const IMAGE_DATA_REGEX = /^data:image\/[a-z]+;base64,/;
@@ -180,6 +182,8 @@ export class ChatDragAndDrop extends Themable {
180182
// This is an estimation based on the datatransfer types/items
181183
if (containsDragType(e, CodeDataTransfers.NOTEBOOK_CELL_OUTPUT)) {
182184
return ChatDragAndDropType.NOTEBOOK_CELL_OUTPUT;
185+
} else if (containsDragType(e, CodeDataTransfers.SCM_HISTORY_ITEM)) {
186+
return ChatDragAndDropType.SCM_HISTORY_ITEM;
183187
} else if (containsImageDragType(e)) {
184188
return this.extensionService.extensions.some(ext => isProposedApiEnabled(ext, 'chatReferenceBinaryData')) ? ChatDragAndDropType.IMAGE : undefined;
185189
} else if (containsDragType(e, 'text/html')) {
@@ -215,6 +219,7 @@ export class ChatDragAndDrop extends Themable {
215219
case ChatDragAndDropType.MARKER: return localize('problem', 'Problem');
216220
case ChatDragAndDropType.HTML: return localize('url', 'URL');
217221
case ChatDragAndDropType.NOTEBOOK_CELL_OUTPUT: return localize('notebookOutput', 'Output');
222+
case ChatDragAndDropType.SCM_HISTORY_ITEM: return localize('scmHistoryItem', 'Change');
218223
}
219224
}
220225

@@ -230,6 +235,13 @@ export class ChatDragAndDrop extends Themable {
230235
}
231236
}
232237

238+
if (containsDragType(e, CodeDataTransfers.SCM_HISTORY_ITEM)) {
239+
const scmHistoryItemData = extractSCMHistoryItemDropData(e);
240+
if (scmHistoryItemData) {
241+
return this.chatAttachmentResolveService.resolveSourceControlHistoryItemAttachContext(scmHistoryItemData);
242+
}
243+
}
244+
233245
const markerData = extractMarkerDropData(e);
234246
if (markerData) {
235247
return this.chatAttachmentResolveService.resolveMarkerAttachContext(markerData);

src/vs/workbench/contrib/scm/browser/scmHistoryChatContext.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,13 @@ import { Codicon } from '../../../../base/common/codicons.js';
1010
import { fromNow } from '../../../../base/common/date.js';
1111
import { Disposable } from '../../../../base/common/lifecycle.js';
1212
import { ThemeIcon } from '../../../../base/common/themables.js';
13-
import { URI } from '../../../../base/common/uri.js';
13+
import { URI, UriComponents } from '../../../../base/common/uri.js';
1414
import { ITextModel } from '../../../../editor/common/model.js';
1515
import { IModelService } from '../../../../editor/common/services/model.js';
1616
import { ITextModelContentProvider, ITextModelService } from '../../../../editor/common/services/resolverService.js';
1717
import { localize } from '../../../../nls.js';
1818
import { Action2, MenuId, registerAction2 } from '../../../../platform/actions/common/actions.js';
19+
import { CodeDataTransfers } from '../../../../platform/dnd/browser/dnd.js';
1920
import { IInstantiationService, ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
2021
import { IWorkbenchContribution } from '../../../common/contributions.js';
2122
import { IViewsService } from '../../../services/views/common/viewsService.js';
@@ -27,6 +28,25 @@ import { ScmHistoryItemResolver } from '../../multiDiffEditor/browser/scmMultiDi
2728
import { ISCMHistoryItem } from '../common/history.js';
2829
import { ISCMProvider, ISCMService, ISCMViewService } from '../common/scm.js';
2930

31+
export interface SCMHistoryItemTransferData {
32+
readonly name: string;
33+
readonly resource: UriComponents;
34+
readonly historyItem: ISCMHistoryItem;
35+
}
36+
37+
export function extractSCMHistoryItemDropData(e: DragEvent): SCMHistoryItemTransferData[] | undefined {
38+
if (!e.dataTransfer?.types.includes(CodeDataTransfers.SCM_HISTORY_ITEM)) {
39+
return undefined;
40+
}
41+
42+
const data = e.dataTransfer?.getData(CodeDataTransfers.SCM_HISTORY_ITEM);
43+
if (!data) {
44+
return undefined;
45+
}
46+
47+
return JSON.parse(data) as SCMHistoryItemTransferData[];
48+
}
49+
3050
export class SCMHistoryItemContextContribution extends Disposable implements IWorkbenchContribution {
3151

3252
static readonly ID = 'workbench.contrib.chat.scmHistoryItemContextContribution';

src/vs/workbench/contrib/scm/browser/scmHistoryViewPane.ts

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { IHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegate.
1010
import { IconLabel } from '../../../../base/browser/ui/iconLabel/iconLabel.js';
1111
import { IIdentityProvider, IListVirtualDelegate } from '../../../../base/browser/ui/list/list.js';
1212
import { LabelFuzzyScore } from '../../../../base/browser/ui/tree/abstractTree.js';
13-
import { IAsyncDataSource, ITreeContextMenuEvent, ITreeElementRenderDetails, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
13+
import { IAsyncDataSource, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeElementRenderDetails, ITreeNode } from '../../../../base/browser/ui/tree/tree.js';
1414
import { createMatches, FuzzyScore, IMatch } from '../../../../base/common/filters.js';
1515
import { combinedDisposable, Disposable, DisposableStore, IDisposable, MutableDisposable } from '../../../../base/common/lifecycle.js';
1616
import { autorun, autorunWithStore, derived, IObservable, observableValue, waitForState, constObservable, latestChangedValue, observableFromEvent, runOnChange, observableSignal, ISettableObservable } from '../../../../base/common/observable.js';
@@ -72,6 +72,10 @@ import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/async
7272
import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
7373
import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
7474
import { ILabelService } from '../../../../platform/label/common/label.js';
75+
import { IDragAndDropData } from '../../../../base/browser/dnd.js';
76+
import { ElementsDragAndDropData, ListViewTargetSector } from '../../../../base/browser/ui/list/listView.js';
77+
import { CodeDataTransfers } from '../../../../platform/dnd/browser/dnd.js';
78+
import { SCMHistoryItemTransferData } from './scmHistoryChatContext.js';
7579

7680
const PICK_REPOSITORY_ACTION_ID = 'workbench.scm.action.graph.pickRepository';
7781
const PICK_HISTORY_ITEM_REFS_ACTION_ID = 'workbench.scm.action.graph.pickHistoryItemRefs';
@@ -946,6 +950,84 @@ class SCMHistoryTreeDataSource extends Disposable implements IAsyncDataSource<SC
946950
}
947951
}
948952

953+
class SCMHistoryTreeDragAndDrop implements ITreeDragAndDrop<TreeElement> {
954+
getDragURI(element: TreeElement): string | null {
955+
const uri = this._getTreeElementUri(element);
956+
return uri ? uri.toString() : null;
957+
}
958+
959+
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
960+
if (!originalEvent.dataTransfer) {
961+
return;
962+
}
963+
964+
const historyItems = this._getDragAndDropData(data as ElementsDragAndDropData<TreeElement, TreeElement[]>);
965+
if (historyItems.length === 0) {
966+
return;
967+
}
968+
969+
originalEvent.dataTransfer.setData(CodeDataTransfers.SCM_HISTORY_ITEM, JSON.stringify(historyItems));
970+
}
971+
972+
getDragLabel(elements: TreeElement[], originalEvent: DragEvent): string | undefined {
973+
if (elements.length === 1) {
974+
const element = elements[0];
975+
return this._getTreeElementLabel(element);
976+
}
977+
978+
return String(elements.length);
979+
}
980+
981+
onDragOver(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean {
982+
return false;
983+
}
984+
985+
drop(data: IDragAndDropData, targetElement: TreeElement | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { }
986+
987+
private _getDragAndDropData(data: ElementsDragAndDropData<TreeElement, TreeElement[]>): SCMHistoryItemTransferData[] {
988+
const historyItems: SCMHistoryItemTransferData[] = [];
989+
for (const element of [...data.context ?? [], ...data.elements]) {
990+
if (!isSCMHistoryItemViewModelTreeElement(element)) {
991+
continue;
992+
}
993+
994+
const provider = element.repository.provider;
995+
const historyItem = element.historyItemViewModel.historyItem;
996+
const attachmentName = `$(${Codicon.repo.id})\u00A0${provider.name}\u00A0$(${Codicon.gitCommit.id})\u00A0${historyItem.displayId ?? historyItem.id}`;
997+
998+
historyItems.push({
999+
name: attachmentName,
1000+
resource: ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem),
1001+
historyItem: historyItem
1002+
});
1003+
}
1004+
1005+
return historyItems;
1006+
}
1007+
1008+
private _getTreeElementLabel(element: TreeElement): string | undefined {
1009+
if (isSCMHistoryItemViewModelTreeElement(element)) {
1010+
const historyItem = element.historyItemViewModel.historyItem;
1011+
return getHistoryItemEditorTitle(historyItem);
1012+
}
1013+
1014+
return undefined;
1015+
}
1016+
1017+
private _getTreeElementUri(element: TreeElement): URI | undefined {
1018+
if (isSCMHistoryItemViewModelTreeElement(element)) {
1019+
const provider = element.repository.provider;
1020+
const historyItem = element.historyItemViewModel.historyItem;
1021+
1022+
return ScmHistoryItemResolver.getMultiDiffSourceUri(provider, historyItem);
1023+
}
1024+
1025+
return undefined;
1026+
}
1027+
1028+
dispose(): void { }
1029+
}
1030+
9491031
type HistoryItemRefsFilter = 'all' | 'auto' | string[];
9501032

9511033
type RepositoryState = {
@@ -1793,6 +1875,7 @@ export class SCMHistoryViewPane extends ViewPane {
17931875
identityProvider: this._treeIdentityProvider,
17941876
collapseByDefault: (e: unknown) => !isSCMHistoryItemChangeNode(e),
17951877
compressionEnabled: compressionEnabled.get(),
1878+
dnd: new SCMHistoryTreeDragAndDrop(),
17961879
keyboardNavigationLabelProvider: new SCMHistoryTreeKeyboardNavigationLabelProvider(),
17971880
horizontalScrolling: false,
17981881
multipleSelectionSupport: false

0 commit comments

Comments
 (0)