Skip to content

Commit

Permalink
Merge pull request #238576 from microsoft/joh/inlineChatEdits
Browse files Browse the repository at this point in the history
Joh/inlineChatEdits
  • Loading branch information
jrieken authored Jan 24, 2025
2 parents 79d2a4c + d96ee37 commit c4c7c61
Show file tree
Hide file tree
Showing 28 changed files with 692 additions and 83 deletions.
10 changes: 7 additions & 3 deletions src/vs/base/common/observableInternal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,20 +202,24 @@ export namespace observableFromEvent {
}

export function observableSignalFromEvent(
debugName: string,
owner: DebugOwner | string,
event: Event<any>
): IObservable<void> {
return new FromEventObservableSignal(debugName, event);
return new FromEventObservableSignal(typeof owner === 'string' ? owner : new DebugNameData(owner, undefined, undefined), event);
}

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

public readonly debugName: string;
constructor(
public readonly debugName: string,
debugNameDataOrName: DebugNameData | string,
private readonly event: Event<any>,
) {
super();
this.debugName = typeof debugNameDataOrName === 'string'
? debugNameDataOrName
: debugNameDataOrName.getDebugName(this) ?? 'Observable Signal From Event';
}

protected override onFirstObserverAdded(): void {
Expand Down
31 changes: 30 additions & 1 deletion src/vs/base/common/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -749,7 +749,7 @@ export function isEmojiImprecise(x: number): boolean {
* happens at favorable positions - such as whitespace or punctuation characters.
* The return value can be longer than the given value of `n`. Leading whitespace is always trimmed.
*/
export function lcut(text: string, n: number, prefix = '') {
export function lcut(text: string, n: number, prefix = ''): string {
const trimmed = text.trimStart();

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

/**
* Given a string and a max length returns a shorted version. Shorting
* happens at favorable positions - such as whitespace or punctuation characters.
* The return value can be longer than the given value of `n`. Trailing whitespace is always trimmed.
*/
export function rcut(text: string, n: number, suffix = ''): string {
const trimmed = text.trimEnd();

if (trimmed.length < n) {
return trimmed;
}

const re = /\b/g;
let lastWordBreak = trimmed.length;

while (re.test(trimmed)) {
if (trimmed.length - re.lastIndex > n) {
lastWordBreak = re.lastIndex;
}
re.lastIndex += 1;
}

if (lastWordBreak === trimmed.length) {
return trimmed;
}

return (trimmed.substring(0, lastWordBreak) + suffix).trimEnd();
}

// Escape codes, compiled from https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Functions-using-CSI-_-ordered-by-the-final-character_s_
// Plus additional markers for custom `\x1b]...\x07` instructions.
const CSI_SEQUENCE = /(?:(?:\x1b\[|\x9B)[=?>!]?[\d;:]*["$#'* ]?[a-zA-Z@^`{}|~])|(:?\x1b\].*?\x07)/g;
Expand Down
2 changes: 1 addition & 1 deletion src/vs/editor/common/core/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export class Position {
* @param deltaColumn column delta
*/
delta(deltaLineNumber: number = 0, deltaColumn: number = 0): Position {
return this.with(this.lineNumber + deltaLineNumber, this.column + deltaColumn);
return this.with(Math.max(1, this.lineNumber + deltaLineNumber), Math.max(1, this.column + deltaColumn));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { ChatAgentLocation } from '../../common/chatAgents.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
import { hasAppliedChatEditsContextKey, hasUndecidedChatEditingResourceContextKey, IChatEditingService, WorkingSetEntryState } from '../../common/chatEditingService.js';
import { ChatViewId, EditsViewId, IChatWidgetService } from '../chat.js';
import { ctxIsGlobalEditingSession } from '../chatEditorController.js';
import { ChatEditorInput } from '../chatEditorInput.js';
import { ChatViewPane } from '../chatViewPane.js';
import { CHAT_CATEGORY } from './chatActions.js';
Expand Down Expand Up @@ -332,6 +333,7 @@ export function registerNewChatActions() {
order: 2
}, {
id: MenuId.ChatEditingEditorContent,
when: ctxIsGlobalEditingSession,
group: 'navigate',
order: 4,
}],
Expand Down
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ import { ChatRelatedFilesContribution } from './contrib/chatInputRelatedFilesCon
import { ChatQuotasService, ChatQuotasStatusBarEntry, IChatQuotasService } from './chatQuotasService.js';
import { BuiltinToolsContribution } from './tools/tools.js';
import { ChatSetupContribution } from './chatSetup.js';
import { ChatEditorOverlayController } from './chatEditorOverlay.js';
import '../common/promptSyntax/languageFeatures/promptLinkProvider.js';

// Register configuration
Expand Down Expand Up @@ -335,6 +336,7 @@ registerChatDeveloperActions();
registerChatEditorActions();

registerEditorFeature(ChatPasteProvidersFeature);
registerEditorContribution(ChatEditorOverlayController.ID, ChatEditorOverlayController, EditorContributionInstantiation.Lazy);
registerEditorContribution(ChatEditorController.ID, ChatEditorController, EditorContributionInstantiation.Eventually);

registerSingleton(IChatService, ChatService, InstantiationType.Delayed);
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/chat/browser/chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ export interface IChatWidgetViewOptions {
defaultElementHeight?: number;
editorOverflowWidgetsDomNode?: HTMLElement;
enableImplicitContext?: boolean;
enableWorkingSet?: 'explicit' | 'implicit';
}

export interface IChatViewViewContext {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/

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

// AUTO accept mode
if (!this.reviewMode.get()) {
if (!this.reviewMode.get() && !this._autoAcceptCtrl.get()) {

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

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

if (cts.token.isCancellationRequested) {
this._autoAcceptCtrl.set(undefined, undefined);
return;
}

const remain = Math.round((future - Date.now()) / 1000);
if (remain <= 0) {
this.accept(undefined);
} else {
this._autoAcceptCtrl.set(new AutoAcceptControl(remain, () => cts.cancel()), undefined);
setTimeout(update, 100);
const handle = setTimeout(update, 100);
this._autoAcceptCtrl.set(new AutoAcceptControl(remain, () => {
clearTimeout(handle);
this._autoAcceptCtrl.set(undefined, undefined);
}), undefined);
}
};
update();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ export class ChatEditingService extends Disposable implements IChatEditingServic

this._currentSessionDisposables.clear();

const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
const session = this._instantiationService.createInstance(ChatEditingSession, chatSessionId, true, this._editingSessionFileLimitPromise, this._lookupEntry.bind(this));
await session.init();

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

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

const list = this._adhocSessionsObs.get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,18 +155,14 @@ export class ChatEditingSession extends Disposable implements IChatEditingSessio
return this._onDidDispose.event;
}

get isVisible(): boolean {
this._assertNotDisposed();
return Boolean(this._editorPane && this._editorPane.isVisible());
}

private _isToolsAgentSession = false;
get isToolsAgentSession(): boolean {
return this._isToolsAgentSession;
}

constructor(
public readonly chatSessionId: string,
readonly chatSessionId: string,
readonly isGlobalEditingSession: boolean,
private editingSessionFileLimitPromise: Promise<number>,
private _lookupExternalEntry: (uri: URI) => ChatEditingModifiedFileEntry | undefined,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
Expand Down
17 changes: 11 additions & 6 deletions src/vs/workbench/contrib/chat/browser/chatEditorController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,9 @@ import { isDiffEditorForEntry } from './chatEditing/chatEditing.js';
import { basename, isEqual } from '../../../../base/common/resources.js';
import { ChatAgentLocation, IChatAgentService } from '../common/chatAgents.js';
import { EditorsOrder, IEditorIdentifier, isDiffEditorInput } from '../../../common/editor.js';
import { ChatEditorOverlayWidget } from './chatEditorOverlay.js';
import { ChatEditorOverlayController } from './chatEditorOverlay.js';

export const ctxIsGlobalEditingSession = new RawContextKey<boolean>('chat.isGlobalEditingSession', undefined, localize('chat.ctxEditSessionIsGlobal', "The current editor is part of the global edit session"));
export const ctxHasEditorModification = new RawContextKey<boolean>('chat.hasEditorModifications', undefined, localize('chat.hasEditorModifications', "The current editor contains chat modifications"));
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"));
export const ctxReviewModeEnabled = new RawContextKey<boolean>('chat.ctxReviewModeEnabled', true, localize('chat.ctxReviewModeEnabled', "Review mode for chat changes is enabled"));
Expand All @@ -54,8 +55,9 @@ export class ChatEditorController extends Disposable implements IEditorContribut

private _viewZones: string[] = [];

private readonly _overlayWidget: ChatEditorOverlayWidget;
private readonly _overlayCtrl: ChatEditorOverlayController;

private readonly _ctxIsGlobalEditsSession: IContextKey<boolean>;
private readonly _ctxHasEditorModification: IContextKey<boolean>;
private readonly _ctxRequestInProgress: IContextKey<boolean>;
private readonly _ctxReviewModelEnabled: IContextKey<boolean>;
Expand Down Expand Up @@ -83,7 +85,8 @@ export class ChatEditorController extends Disposable implements IEditorContribut
) {
super();

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

if (!currentEditorEntry) {
this._ctxIsGlobalEditsSession.reset();
this._clear();
didReval = false;
return;
Expand All @@ -141,16 +145,17 @@ export class ChatEditorController extends Disposable implements IEditorContribut

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

this._ctxIsGlobalEditsSession.set(session.isGlobalEditingSession);
this._ctxReviewModelEnabled.set(entry.reviewMode.read(r));

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

// overlay widget
if (entry.state.read(r) !== WorkingSetEntryState.Modified) {
this._overlayWidget.hide();
this._overlayCtrl.hide();
} else {
this._overlayWidget.show(session, entry, entries[(idx + 1) % entries.length]);
this._overlayCtrl.showEntry(session, entry, entries[(idx + 1) % entries.length]);
}

// scrolling logic
Expand Down Expand Up @@ -242,7 +247,7 @@ export class ChatEditorController extends Disposable implements IEditorContribut

private _clear() {
this._clearDiffRendering();
this._overlayWidget.hide();
this._overlayCtrl.hide();
this._diffLineDecorations.clear();
this._currentChangeIndex.set(undefined, undefined);
this._currentEntryIndex.set(undefined, undefined);
Expand Down
Loading

0 comments on commit c4c7c61

Please sign in to comment.