Skip to content

Commit c4c7c61

Browse files
authored
Merge pull request #238576 from microsoft/joh/inlineChatEdits
Joh/inlineChatEdits
2 parents 79d2a4c + d96ee37 commit c4c7c61

28 files changed

+692
-83
lines changed

src/vs/base/common/observableInternal/utils.ts

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -202,20 +202,24 @@ export namespace observableFromEvent {
202202
}
203203

204204
export function observableSignalFromEvent(
205-
debugName: string,
205+
owner: DebugOwner | string,
206206
event: Event<any>
207207
): IObservable<void> {
208-
return new FromEventObservableSignal(debugName, event);
208+
return new FromEventObservableSignal(typeof owner === 'string' ? owner : new DebugNameData(owner, undefined, undefined), event);
209209
}
210210

211211
class FromEventObservableSignal extends BaseObservable<void> {
212212
private subscription: IDisposable | undefined;
213213

214+
public readonly debugName: string;
214215
constructor(
215-
public readonly debugName: string,
216+
debugNameDataOrName: DebugNameData | string,
216217
private readonly event: Event<any>,
217218
) {
218219
super();
220+
this.debugName = typeof debugNameDataOrName === 'string'
221+
? debugNameDataOrName
222+
: debugNameDataOrName.getDebugName(this) ?? 'Observable Signal From Event';
219223
}
220224

221225
protected override onFirstObserverAdded(): void {

src/vs/base/common/strings.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,7 @@ export function isEmojiImprecise(x: number): boolean {
749749
* happens at favorable positions - such as whitespace or punctuation characters.
750750
* The return value can be longer than the given value of `n`. Leading whitespace is always trimmed.
751751
*/
752-
export function lcut(text: string, n: number, prefix = '') {
752+
export function lcut(text: string, n: number, prefix = ''): string {
753753
const trimmed = text.trimStart();
754754

755755
if (trimmed.length < n) {
@@ -774,6 +774,35 @@ export function lcut(text: string, n: number, prefix = '') {
774774
return prefix + trimmed.substring(i).trimStart();
775775
}
776776

777+
/**
778+
* Given a string and a max length returns a shorted version. Shorting
779+
* happens at favorable positions - such as whitespace or punctuation characters.
780+
* The return value can be longer than the given value of `n`. Trailing whitespace is always trimmed.
781+
*/
782+
export function rcut(text: string, n: number, suffix = ''): string {
783+
const trimmed = text.trimEnd();
784+
785+
if (trimmed.length < n) {
786+
return trimmed;
787+
}
788+
789+
const re = /\b/g;
790+
let lastWordBreak = trimmed.length;
791+
792+
while (re.test(trimmed)) {
793+
if (trimmed.length - re.lastIndex > n) {
794+
lastWordBreak = re.lastIndex;
795+
}
796+
re.lastIndex += 1;
797+
}
798+
799+
if (lastWordBreak === trimmed.length) {
800+
return trimmed;
801+
}
802+
803+
return (trimmed.substring(0, lastWordBreak) + suffix).trimEnd();
804+
}
805+
777806
// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
778807
// Plus additional markers for custom `\x1b]...\x07` instructions.
779808
const CSI_SEQUENCE = /(?:(?:\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~])|(:?\x1b\].*?\x07)/g;

src/vs/editor/common/core/position.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export class Position {
5656
* @param deltaColumn column delta
5757
*/
5858
delta(deltaLineNumber: number = 0, deltaColumn: number = 0): Position {
59-
return this.with(this.lineNumber + deltaLineNumber, this.column + deltaColumn);
59+
return this.with(Math.max(1, this.lineNumber + deltaLineNumber), Math.max(1, this.column + deltaColumn));
6060
}
6161

6262
/**

src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { ChatAgentLocation } from '../../common/chatAgents.js';
1919
import { ChatContextKeys } from '../../common/chatContextKeys.js';
2020
import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js';
2121
import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js';
22+
import { ctxIsGlobalEditingSession } from '../chatEditorController.js';
2223
import { ChatEditorInput } from '../chatEditorInput.js';
2324
import { ChatViewPane } from '../chatViewPane.js';
2425
import { CHAT_CATEGORY } from './chatActions.js';
@@ -332,6 +333,7 @@ export function registerNewChatActions() {
332333
order: 2
333334
}, {
334335
id: MenuId.ChatEditingEditorContent,
336+
when: ctxIsGlobalEditingSession,
335337
group: 'navigate',
336338
order: 4,
337339
}],

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesCon
8181
import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js';
8282
import { BuiltinToolsContribution } from './tools/tools.js';
8383
import { ChatSetupContribution } from './chatSetup.js';
84+
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
8485
import '../common/promptSyntax/languageFeatures/promptLinkProvider.js';
8586

8687
// Register configuration
@@ -335,6 +336,7 @@ registerChatDeveloperActions();
335336
registerChatEditorActions();
336337

337338
registerEditorFeature(ChatPasteProvidersFeature);
339+
registerEditorContribution(ChatEditorOverlayController.ID, ChatEditorOverlayController, EditorContributionInstantiation.Lazy);
338340
registerEditorContribution(ChatEditorController.ID, ChatEditorController, EditorContributionInstantiation.Eventually);
339341

340342
registerSingleton(IChatService, ChatService, InstantiationType.Delayed);

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,7 @@ export interface IChatWidgetViewOptions {
187187
defaultElementHeight?: number;
188188
editorOverflowWidgetsDomNode?: HTMLElement;
189189
enableImplicitContext?: boolean;
190+
enableWorkingSet?: 'explicit' | 'implicit';
190191
}
191192

192193
export interface IChatViewViewContext {

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingModifiedFileEntry.ts

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { RunOnceScheduler } from '../../../../../base/common/async.js';
7-
import { CancellationTokenSource } from '../../../../../base/common/cancellation.js';
87
import { Emitter } from '../../../../../base/common/event.js';
98
import { Disposable, IReference, toDisposable } from '../../../../../base/common/lifecycle.js';
109
import { Schemas } from '../../../../../base/common/network.js';
@@ -305,10 +304,9 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
305304
this._clearCurrentEditLineDecoration();
306305

307306
// AUTO accept mode
308-
if (!this.reviewMode.get()) {
307+
if (!this.reviewMode.get() && !this._autoAcceptCtrl.get()) {
309308

310309
const future = Date.now() + (this._autoAcceptTimeout.get() * 1000);
311-
const cts = new CancellationTokenSource();
312310
const update = () => {
313311

314312
const reviewMode = this.reviewMode.get();
@@ -318,17 +316,15 @@ export class ChatEditingModifiedFileEntry extends Disposable implements IModifie
318316
return;
319317
}
320318

321-
if (cts.token.isCancellationRequested) {
322-
this._autoAcceptCtrl.set(undefined, undefined);
323-
return;
324-
}
325-
326319
const remain = Math.round((future - Date.now()) / 1000);
327320
if (remain <= 0) {
328321
this.accept(undefined);
329322
} else {
330-
this._autoAcceptCtrl.set(new AutoAcceptControl(remain, () => cts.cancel()), undefined);
331-
setTimeout(update, 100);
323+
const handle = setTimeout(update, 100);
324+
this._autoAcceptCtrl.set(new AutoAcceptControl(remain, () => {
325+
clearTimeout(handle);
326+
this._autoAcceptCtrl.set(undefined, undefined);
327+
}), undefined);
332328
}
333329
};
334330
update();

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingService.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
242242

243243
this._currentSessionDisposables.clear();
244244

245-
const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
245+
const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, true, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
246246
await session.init();
247247

248248
// listen for completed responses, run the code mapper and apply the edits to this edit session
@@ -258,7 +258,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic
258258
}
259259

260260
async createAdhocEditingSession(chatSessionId: string): Promise<IChatEditingSession & IDisposable> {
261-
const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
261+
const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, false, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
262262
await session.init();
263263

264264
const list = this._adhocSessionsObs.get();

src/vs/workbench/contrib/chat/browser/chatEditing/chatEditingSession.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -155,18 +155,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
155155
return this._onDidDispose.event;
156156
}
157157

158-
get isVisible(): boolean {
159-
this._assertNotDisposed();
160-
return Boolean(this._editorPane && this._editorPane.isVisible());
161-
}
162-
163158
private _isToolsAgentSession = false;
164159
get isToolsAgentSession(): boolean {
165160
return this._isToolsAgentSession;
166161
}
167162

168163
constructor(
169-
public readonly chatSessionId: string,
164+
readonly chatSessionId: string,
165+
readonly isGlobalEditingSession: boolean,
170166
private editingSessionFileLimitPromise: Promise<number>,
171167
private _lookupExternalEntry: (uri: URI) => ChatEditingModifiedFileEntry | undefined,
172168
@IInstantiationService private readonly _instantiationService: IInstantiationService,

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

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import { isDiffEditorForEntry } from './chatEditing/chatEditing.js';
3535
import { basename, isEqual } from '../../../../base/common/resources.js';
3636
import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js';
3737
import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../common/editor.js';
38-
import { ChatEditorOverlayWidget } from './chatEditorOverlay.js';
38+
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
3939

40+
export const ctxIsGlobalEditingSession = new RawContextKey<boolean>('chat.isGlobalEditingSession', undefined, localize('chat.ctxEditSessionIsGlobal', "The current editor is part of the global edit session"));
4041
export const ctxHasEditorModification = new RawContextKey<boolean>('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications"));
4142
export const ctxHasRequestInProgress = new RawContextKey<boolean>('chat.ctxHasRequestInProgress', false, localize('chat.ctxHasRequestInProgress', "The current editor shows a file from an edit session which is still in progress"));
4243
export const ctxReviewModeEnabled = new RawContextKey<boolean>('chat.ctxReviewModeEnabled', true, localize('chat.ctxReviewModeEnabled', "Review mode for chat changes is enabled"));
@@ -54,8 +55,9 @@ export class ChatEditorController extends Disposable implements IEditorContribut
5455

5556
private _viewZones: string[] = [];
5657

57-
private readonly _overlayWidget: ChatEditorOverlayWidget;
58+
private readonly _overlayCtrl: ChatEditorOverlayController;
5859

60+
private readonly _ctxIsGlobalEditsSession: IContextKey<boolean>;
5961
private readonly _ctxHasEditorModification: IContextKey<boolean>;
6062
private readonly _ctxRequestInProgress: IContextKey<boolean>;
6163
private readonly _ctxReviewModelEnabled: IContextKey<boolean>;
@@ -83,7 +85,8 @@ export class ChatEditorController extends Disposable implements IEditorContribut
8385
) {
8486
super();
8587

86-
this._overlayWidget = _instantiationService.createInstance(ChatEditorOverlayWidget, _editor);
88+
this._overlayCtrl = ChatEditorOverlayController.get(_editor)!;
89+
this._ctxIsGlobalEditsSession = ctxIsGlobalEditingSession.bindTo(contextKeyService);
8790
this._ctxHasEditorModification = ctxHasEditorModification.bindTo(contextKeyService);
8891
this._ctxRequestInProgress = ctxHasRequestInProgress.bindTo(contextKeyService);
8992
this._ctxReviewModelEnabled = ctxReviewModeEnabled.bindTo(contextKeyService);
@@ -129,6 +132,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut
129132
const currentEditorEntry = entryForEditor.read(r);
130133

131134
if (!currentEditorEntry) {
135+
this._ctxIsGlobalEditsSession.reset();
132136
this._clear();
133137
didReval = false;
134138
return;
@@ -141,16 +145,17 @@ export class ChatEditorController extends Disposable implements IEditorContribut
141145

142146
const { session, entries, idx, entry } = currentEditorEntry;
143147

148+
this._ctxIsGlobalEditsSession.set(session.isGlobalEditingSession);
144149
this._ctxReviewModelEnabled.set(entry.reviewMode.read(r));
145150

146151
// context
147152
this._currentEntryIndex.set(idx, undefined);
148153

149154
// overlay widget
150155
if (entry.state.read(r) !== WorkingSetEntryState.Modified) {
151-
this._overlayWidget.hide();
156+
this._overlayCtrl.hide();
152157
} else {
153-
this._overlayWidget.show(session, entry, entries[(idx + 1) % entries.length]);
158+
this._overlayCtrl.showEntry(session, entry, entries[(idx + 1) % entries.length]);
154159
}
155160

156161
// scrolling logic
@@ -242,7 +247,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut
242247

243248
private _clear() {
244249
this._clearDiffRendering();
245-
this._overlayWidget.hide();
250+
this._overlayCtrl.hide();
246251
this._diffLineDecorations.clear();
247252
this._currentChangeIndex.set(undefined, undefined);
248253
this._currentEntryIndex.set(undefined, undefined);

0 commit comments

Comments
 (0)