Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/attachments/chatInputRelatedFilesContrib.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../../base/common/cancellation.js';
7
import { Emitter, Event } from '../../../../../base/common/event.js';
8
import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';
9
import { ResourceMap, ResourceSet } from '../../../../../base/common/map.js';
10
import { autorun } from '../../../../../base/common/observable.js';
11
import { isEqual } from '../../../../../base/common/resources.js';
12
import { URI } from '../../../../../base/common/uri.js';
13
import { localize } from '../../../../../nls.js';
14
import { IWorkbenchContribution } from '../../../../common/contributions.js';
15
import { IChatEditingService, IChatEditingSession } from '../../common/editing/chatEditingService.js';
16
import { IChatWidget, IChatWidgetService } from '../chat.js';
17
18
export class ChatRelatedFilesContribution extends Disposable implements IWorkbenchContribution {
19
static readonly ID = 'chat.relatedFilesWorkingSet';
20
21
private readonly chatEditingSessionDisposables = new ResourceMap<DisposableStore>();
22
private _currentRelatedFilesRetrievalOperation: Promise<void> | undefined;
23
24
constructor(
25
@IChatEditingService private readonly chatEditingService: IChatEditingService,
26
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
27
) {
28
super();
29
30
this._register(autorun((reader) => {
31
const sessions = this.chatEditingService.editingSessionsObs.read(reader);
32
sessions.forEach(session => {
33
const widget = this.chatWidgetService.getWidgetBySessionResource(session.chatSessionResource);
34
if (widget && !this.chatEditingSessionDisposables.has(session.chatSessionResource)) {
35
this._handleNewEditingSession(session, widget);
36
}
37
});
38
}));
39
}
40
41
private _updateRelatedFileSuggestions(currentEditingSession: IChatEditingSession, widget: IChatWidget) {
42
if (this._currentRelatedFilesRetrievalOperation) {
43
return;
44
}
45
46
const workingSetEntries = currentEditingSession.entries.get();
47
if (workingSetEntries.length > 0 || widget.attachmentModel.fileAttachments.length === 0) {
48
// Do this only for the initial working set state
49
return;
50
}
51
52
this._currentRelatedFilesRetrievalOperation = this.chatEditingService.getRelatedFiles(currentEditingSession.chatSessionResource, widget.getInput(), widget.attachmentModel.fileAttachments, CancellationToken.None)
53
.then((files) => {
54
if (!files?.length || !widget.viewModel || !widget.input.relatedFiles) {
55
return;
56
}
57
58
const currentEditingSession = this.chatEditingService.getEditingSession(widget.viewModel.sessionResource);
59
if (!currentEditingSession || currentEditingSession.entries.get().length) {
60
return; // Might have disposed while we were calculating
61
}
62
63
const existingFiles = new ResourceSet([...widget.attachmentModel.fileAttachments, ...widget.input.relatedFiles.removedFiles]);
64
if (!existingFiles.size) {
65
return;
66
}
67
68
// Pick up to 2 related files
69
const newSuggestions = new ResourceMap<string>();
70
for (const group of files) {
71
for (const file of group.files) {
72
if (newSuggestions.size >= 2) {
73
break;
74
}
75
if (existingFiles.has(file.uri)) {
76
continue;
77
}
78
newSuggestions.set(file.uri, localize('relatedFile', "{0} (Suggested)", file.description));
79
existingFiles.add(file.uri);
80
}
81
}
82
83
widget.input.relatedFiles.value = [...newSuggestions.entries()].map(([uri, description]) => ({ uri, description }));
84
})
85
.finally(() => {
86
this._currentRelatedFilesRetrievalOperation = undefined;
87
});
88
89
}
90
91
private _handleNewEditingSession(currentEditingSession: IChatEditingSession, widget: IChatWidget) {
92
const disposableStore = new DisposableStore();
93
disposableStore.add(currentEditingSession.onDidDispose(() => {
94
disposableStore.clear();
95
}));
96
this._updateRelatedFileSuggestions(currentEditingSession, widget);
97
const onDebouncedType = Event.debounce(widget.inputEditor.onDidChangeModelContent, () => null, 3000);
98
disposableStore.add(onDebouncedType(() => {
99
this._updateRelatedFileSuggestions(currentEditingSession, widget);
100
}));
101
disposableStore.add(widget.attachmentModel.onDidChange(() => {
102
this._updateRelatedFileSuggestions(currentEditingSession, widget);
103
}));
104
disposableStore.add(currentEditingSession.onDidDispose(() => {
105
disposableStore.dispose();
106
}));
107
disposableStore.add(widget.onDidAcceptInput(() => {
108
widget.input.relatedFiles?.clear();
109
this._updateRelatedFileSuggestions(currentEditingSession, widget);
110
}));
111
this.chatEditingSessionDisposables.set(currentEditingSession.chatSessionResource, disposableStore);
112
}
113
114
override dispose() {
115
for (const store of this.chatEditingSessionDisposables.values()) {
116
store.dispose();
117
}
118
super.dispose();
119
}
120
}
121
122
export interface IChatRelatedFile {
123
uri: URI;
124
description: string;
125
}
126
export class ChatRelatedFiles extends Disposable {
127
128
private readonly _onDidChange = this._register(new Emitter<void>());
129
readonly onDidChange: Event<void> = this._onDidChange.event;
130
131
private _removedFiles = new ResourceSet();
132
get removedFiles() {
133
return this._removedFiles;
134
}
135
136
private _value: IChatRelatedFile[] = [];
137
get value() {
138
return this._value;
139
}
140
141
set value(value: IChatRelatedFile[]) {
142
this._value = value;
143
this._onDidChange.fire();
144
}
145
146
remove(uri: URI) {
147
this._value = this._value.filter(file => !isEqual(file.uri, uri));
148
this._removedFiles.add(uri);
149
this._onDidChange.fire();
150
}
151
152
clearRemovedFiles() {
153
this._removedFiles.clear();
154
}
155
156
clear() {
157
this._value = [];
158
this._removedFiles.clear();
159
this._onDidChange.fire();
160
}
161
}
162
163