mirror of
https://github.com/microsoft/vscode.git
synced 2026-02-15 07:28:05 +00:00
Reveal session when following up (#289694)
This commit is contained in:
@@ -12,6 +12,7 @@ import { DisposableStore, dispose, IDisposable, toDisposable } from '../../../..
|
||||
import { autorun, constObservable, derived, IObservable, observableFromEvent, observableValue } from '../../../../../base/common/observable.js';
|
||||
import { basename, isEqual } from '../../../../../base/common/resources.js';
|
||||
import { themeColorFromId } from '../../../../../base/common/themables.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IOverlayWidgetPositionCoordinates, IViewZone, MouseTargetType } from '../../../../../editor/browser/editorBrowser.js';
|
||||
import { observableCodeEditor } from '../../../../../editor/browser/observableCodeEditor.js';
|
||||
import { AccessibleDiffViewer, IAccessibleDiffViewerModel } from '../../../../../editor/browser/widget/diffEditor/components/accessibleDiffViewer.js';
|
||||
@@ -124,7 +125,10 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito
|
||||
this._explanationWidgetManager = this._store.add(new ChatEditingExplanationWidgetManager(this._editor, this._chatWidgetService, this._viewsService));
|
||||
// Pass the current diff info when creating lazily (untracked - diff changes handled separately)
|
||||
const diff = documentDiffInfo.read(undefined);
|
||||
this._explanationWidgetManager.update(diff, true);
|
||||
// Find the session that owns this entry to get the chat session resource
|
||||
const session = this._chatEditingService.editingSessionsObs.read(undefined)
|
||||
.find(candidate => candidate.getEntry(this._entry.modifiedURI));
|
||||
this._explanationWidgetManager.update(diff, true, session?.chatSessionResource);
|
||||
// Generate explanations asynchronously
|
||||
ChatEditingExplanationWidgetManager.generateExplanations(
|
||||
this._explanationWidgetManager.widgets,
|
||||
@@ -500,15 +504,17 @@ export class ChatEditingCodeEditorIntegration implements IModifiedFileEntryEdito
|
||||
}
|
||||
|
||||
// Update explanation widgets for chat changes
|
||||
// Get visibility from session that owns this entry
|
||||
// Get visibility and session resource from session that owns this entry
|
||||
let explanationVisible = false;
|
||||
let chatSessionResource: URI | undefined;
|
||||
for (const session of this._chatEditingService.editingSessionsObs.get()) {
|
||||
if (session.entries.get().some((e: IModifiedFileEntry) => e.entryId === this._entry.entryId)) {
|
||||
explanationVisible = session.explanationWidgetVisible.get();
|
||||
chatSessionResource = session.chatSessionResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
this._explanationWidgetManager?.update(diff, explanationVisible);
|
||||
this._explanationWidgetManager?.update(diff, explanationVisible, chatSessionResource);
|
||||
|
||||
const positionObs = observableFromEvent(this._editor.onDidChangeCursorPosition, _ => this._editor.getPosition());
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ import { IEditorGroupsService } from '../../../../services/editor/common/editorG
|
||||
import { ACTIVE_GROUP, IEditorService } from '../../../../services/editor/common/editorService.js';
|
||||
import { CTX_HOVER_MODE } from '../../../inlineChat/common/inlineChat.js';
|
||||
import { MultiDiffEditor } from '../../../multiDiffEditor/browser/multiDiffEditor.js';
|
||||
import { MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
|
||||
import { IDocumentDiffItemWithMultiDiffEditorItem, MultiDiffEditorInput } from '../../../multiDiffEditor/browser/multiDiffEditorInput.js';
|
||||
import { NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_EDITOR_FOCUSED } from '../../../notebook/common/notebookContextKeys.js';
|
||||
import { ChatContextKeys } from '../../common/actions/chatContextKeys.js';
|
||||
import { CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, IChatEditingService, IChatEditingSession, IModifiedFileEntry, IModifiedFileEntryChangeHunk, IModifiedFileEntryEditorIntegration, ModifiedFileEntryState, parseChatMultiDiffUri } from '../../common/editing/chatEditingService.js';
|
||||
@@ -35,6 +35,7 @@ import { DiffEditorViewModel } from '../../../../../editor/browser/widget/diffEd
|
||||
import { IChatWidgetService } from '../chat.js';
|
||||
import { IViewsService } from '../../../../services/views/common/viewsService.js';
|
||||
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
import { Event } from '../../../../../base/common/event.js';
|
||||
import { ChatConfiguration } from '../../common/constants.js';
|
||||
@@ -470,6 +471,8 @@ abstract class MultiDiffAcceptDiscardAction extends Action2 {
|
||||
}
|
||||
|
||||
|
||||
const explainMultiDiffSchemes = [CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME, 'copilotcli-worktree-changes', 'copilotcloud-pr-changes'];
|
||||
|
||||
class ExplainMultiDiffAction extends Action2 {
|
||||
|
||||
private readonly _widgetsByInput = new WeakMap<EditorInput, DisposableStore>();
|
||||
@@ -479,7 +482,7 @@ class ExplainMultiDiffAction extends Action2 {
|
||||
id: 'chatEditing.multidiff.explain',
|
||||
title: localize('explain', 'Explain'),
|
||||
menu: {
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.or(ContextKeyExpr.equals('resourceScheme', 'copilotcli-worktree-changes'), ContextKeyExpr.equals('resourceScheme', 'copilotcloud-pr-changes')), ContextKeyExpr.has(`config.${ChatConfiguration.ExplainChangesEnabled}`)),
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.or(...explainMultiDiffSchemes.map(scheme => ContextKeyExpr.equals('resourceScheme', scheme))), ContextKeyExpr.has(`config.${ChatConfiguration.ExplainChangesEnabled}`)),
|
||||
id: MenuId.MultiDiffEditorContent,
|
||||
order: 10,
|
||||
},
|
||||
@@ -491,6 +494,7 @@ class ExplainMultiDiffAction extends Action2 {
|
||||
const languageModelsService = accessor.get(ILanguageModelsService);
|
||||
const chatWidgetService = accessor.get(IChatWidgetService);
|
||||
const viewsService = accessor.get(IViewsService);
|
||||
const chatEditingService = accessor.get(IChatEditingService);
|
||||
|
||||
const activePane = editorService.activeEditorPane;
|
||||
if (!activePane) {
|
||||
@@ -521,6 +525,35 @@ class ExplainMultiDiffAction extends Action2 {
|
||||
const viewModel = activePane.viewModel;
|
||||
const items = viewModel.items.get();
|
||||
|
||||
// Try to extract chat session resource from the multi-diff editor URI or by scanning sessions
|
||||
let chatSessionResource: URI | undefined;
|
||||
if (input instanceof MultiDiffEditorInput && input.resource?.scheme === CHAT_EDITING_MULTI_DIFF_SOURCE_RESOLVER_SCHEME) {
|
||||
chatSessionResource = parseChatMultiDiffUri(input.resource).chatSessionResource;
|
||||
}
|
||||
if (!chatSessionResource) {
|
||||
// Scan sessions to find one that owns files in this multi-diff editor
|
||||
// Use goToFileUri if available, otherwise extract file path from the modified URI
|
||||
const fileUris = items.map(item => {
|
||||
const docDiffItem = item.documentDiffItem as IDocumentDiffItemWithMultiDiffEditorItem | undefined;
|
||||
const goToFileUri = docDiffItem?.multiDiffEditorItem?.goToFileUri;
|
||||
if (goToFileUri) {
|
||||
return goToFileUri;
|
||||
}
|
||||
// Fallback: extract file path from the modified URI (e.g., git: URIs have the path)
|
||||
const modifiedUri = docDiffItem?.multiDiffEditorItem?.modifiedUri ?? item.modifiedUri;
|
||||
if (modifiedUri?.path) {
|
||||
return URI.file(modifiedUri.path);
|
||||
}
|
||||
return undefined;
|
||||
}).filter((uri): uri is URI => !!uri);
|
||||
for (const session of chatEditingService.editingSessionsObs.get()) {
|
||||
if (fileUris.some(uri => session.getEntry(uri))) {
|
||||
chatSessionResource = session.chatSessionResource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First pass: collect all diffs grouped by file
|
||||
const diffsByFile = new Map<string, {
|
||||
editor: ICodeEditor;
|
||||
@@ -587,7 +620,7 @@ class ExplainMultiDiffAction extends Action2 {
|
||||
widgetsStore.add(manager);
|
||||
|
||||
// Update with diff info and show (but don't generate explanations yet)
|
||||
manager.update(diffInfo, true);
|
||||
manager.update(diffInfo, true, chatSessionResource);
|
||||
managersWithDiffInfo.push({ manager, diffInfo });
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,11 @@ import { overviewRulerRangeHighlight } from '../../../../../editor/common/core/e
|
||||
import { IEditorDecorationsCollection } from '../../../../../editor/common/editorCommon.js';
|
||||
import { ITextModel, OverviewRulerLane } from '../../../../../editor/common/model.js';
|
||||
import { themeColorFromId } from '../../../../../platform/theme/common/themeService.js';
|
||||
import { ChatViewId, IChatWidgetService } from '../chat.js';
|
||||
import { ChatViewId, ChatViewPaneTarget, IChatWidget, IChatWidgetService } from '../chat.js';
|
||||
import { IViewsService } from '../../../../services/views/common/viewsService.js';
|
||||
import * as nls from '../../../../../nls.js';
|
||||
import { basename } from '../../../../../base/common/resources.js';
|
||||
import { CancellationToken } from '../../../../../base/common/cancellation.js';
|
||||
|
||||
/**
|
||||
* Simple diff info interface for explanation widgets
|
||||
@@ -147,6 +148,7 @@ export class ChatEditingExplanationWidget extends Disposable implements IOverlay
|
||||
diffInfo: IExplanationDiffInfo,
|
||||
private readonly _chatWidgetService: IChatWidgetService,
|
||||
private readonly _viewsService: IViewsService,
|
||||
private readonly _chatSessionResource?: URI,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -361,14 +363,19 @@ export class ChatEditingExplanationWidget extends Disposable implements IOverlay
|
||||
// Reply button click handler
|
||||
this._eventStore.add(addDisposableListener(replyButton, 'click', async (e) => {
|
||||
e.stopPropagation();
|
||||
const chatWidget = this._chatWidgetService.lastFocusedWidget;
|
||||
const range = new Range(exp.startLineNumber, 1, exp.endLineNumber, 1);
|
||||
let chatWidget: IChatWidget | undefined;
|
||||
if (this._chatSessionResource) {
|
||||
chatWidget = await this._chatWidgetService.openSession(this._chatSessionResource, ChatViewPaneTarget);
|
||||
} else {
|
||||
await this._viewsService.openView(ChatViewId, true);
|
||||
chatWidget = this._chatWidgetService.lastFocusedWidget;
|
||||
}
|
||||
if (chatWidget) {
|
||||
const range = new Range(exp.startLineNumber, 1, exp.endLineNumber, 1);
|
||||
chatWidget.attachmentModel.addContext(
|
||||
chatWidget.attachmentModel.asFileVariableEntry(this._uri, range)
|
||||
);
|
||||
}
|
||||
await this._viewsService.openView(ChatViewId, true);
|
||||
}));
|
||||
|
||||
// Click on item to mark as read
|
||||
@@ -592,9 +599,10 @@ export class ChatEditingExplanationWidgetManager extends Disposable {
|
||||
/**
|
||||
* Updates the diff info and generates explanations.
|
||||
* Creates widgets immediately and starts LLM generation.
|
||||
* @param visible Whether widgets should be visible (default: false)
|
||||
* @param visible Whether widgets should be visible
|
||||
* @param chatSessionResource Chat session resource to open when following up, or undefined
|
||||
*/
|
||||
update(diffInfo: IExplanationDiffInfo, visible: boolean = false): void {
|
||||
update(diffInfo: IExplanationDiffInfo, visible: boolean, chatSessionResource: URI | undefined): void {
|
||||
this._modelUri = diffInfo.modifiedModel.uri;
|
||||
|
||||
// Clear existing widgets
|
||||
@@ -616,6 +624,7 @@ export class ChatEditingExplanationWidgetManager extends Disposable {
|
||||
diffInfo,
|
||||
this._chatWidgetService,
|
||||
this._viewsService,
|
||||
chatSessionResource,
|
||||
);
|
||||
this._widgets.push(widget);
|
||||
this._register(widget);
|
||||
@@ -650,7 +659,7 @@ export class ChatEditingExplanationWidgetManager extends Disposable {
|
||||
widgets: readonly ChatEditingExplanationWidget[],
|
||||
diffInfo: IExplanationDiffInfo,
|
||||
languageModelsService: ILanguageModelsService,
|
||||
cancellationToken: import('../../../../../base/common/cancellation.js').CancellationToken
|
||||
cancellationToken: CancellationToken
|
||||
): Promise<void> {
|
||||
if (diffInfo.changes.length === 0) {
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user