mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-17 23:35:54 +01:00
Revert "chore - Refactor inline chat classes to use private class fields (#29…"
This reverts commit 81f2b5cd2f.
This commit is contained in:
@@ -91,11 +91,11 @@ export class StartSessionAction extends Action2 {
|
||||
logService.debug(`[EditorAction2] NOT running command because its precondition is FALSE`, this.desc.id, this.desc.precondition?.serialize());
|
||||
return;
|
||||
}
|
||||
return this.#runEditorCommand(editorAccessor, editor, ...args);
|
||||
return this._runEditorCommand(editorAccessor, editor, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
async #runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]) {
|
||||
private async _runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: unknown[]) {
|
||||
|
||||
const configServce = accessor.get(IConfigurationService);
|
||||
|
||||
@@ -262,15 +262,12 @@ export class FixDiagnosticsAction extends AbstractInlineChatAction {
|
||||
|
||||
class KeepOrUndoSessionAction extends AbstractInlineChatAction {
|
||||
|
||||
readonly #keep: boolean;
|
||||
|
||||
constructor(keep: boolean, desc: IAction2Options) {
|
||||
constructor(private readonly _keep: boolean, desc: IAction2Options) {
|
||||
super(desc);
|
||||
this.#keep = keep;
|
||||
}
|
||||
|
||||
override async runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController, editor: ICodeEditor, ..._args: unknown[]): Promise<void> {
|
||||
if (this.#keep) {
|
||||
if (this._keep) {
|
||||
await ctrl.acceptSession();
|
||||
} else {
|
||||
await ctrl.rejectSession();
|
||||
|
||||
@@ -112,93 +112,63 @@ export class InlineChatController implements IEditorContribution {
|
||||
* Stores the user's explicitly chosen model (qualified name) from a previous inline chat request in the same session.
|
||||
* When set, this takes priority over the inlineChat.defaultModel setting.
|
||||
*/
|
||||
static #userSelectedModel: string | undefined;
|
||||
private static _userSelectedModel: string | undefined;
|
||||
|
||||
readonly #store = new DisposableStore();
|
||||
readonly #isActiveController = observableValue(this, false);
|
||||
readonly #renderMode: IObservable<'zone' | 'hover'>;
|
||||
readonly #zone: Lazy<InlineChatZoneWidget>;
|
||||
private readonly _store = new DisposableStore();
|
||||
private readonly _isActiveController = observableValue(this, false);
|
||||
private readonly _renderMode: IObservable<'zone' | 'hover'>;
|
||||
private readonly _zone: Lazy<InlineChatZoneWidget>;
|
||||
readonly inputOverlayWidget: InlineChatAffordance;
|
||||
readonly #inputWidget: InlineChatInputWidget;
|
||||
private readonly _inputWidget: InlineChatInputWidget;
|
||||
|
||||
readonly #currentSession: IObservable<IInlineChatSession2 | undefined>;
|
||||
|
||||
readonly #editor: ICodeEditor;
|
||||
readonly #instaService: IInstantiationService;
|
||||
readonly #notebookEditorService: INotebookEditorService;
|
||||
readonly #inlineChatSessionService: IInlineChatSessionService;
|
||||
readonly #configurationService: IConfigurationService;
|
||||
readonly #webContentExtractorService: ISharedWebContentExtractorService;
|
||||
readonly #fileService: IFileService;
|
||||
readonly #chatAttachmentResolveService: IChatAttachmentResolveService;
|
||||
readonly #editorService: IEditorService;
|
||||
readonly #markerDecorationsService: IMarkerDecorationsService;
|
||||
readonly #languageModelService: ILanguageModelsService;
|
||||
readonly #logService: ILogService;
|
||||
readonly #chatEditingService: IChatEditingService;
|
||||
readonly #chatService: IChatService;
|
||||
private readonly _currentSession: IObservable<IInlineChatSession2 | undefined>;
|
||||
|
||||
get widget(): EditorBasedInlineChatWidget {
|
||||
return this.#zone.value.widget;
|
||||
return this._zone.value.widget;
|
||||
}
|
||||
|
||||
get isActive() {
|
||||
return Boolean(this.#currentSession.get());
|
||||
return Boolean(this._currentSession.get());
|
||||
}
|
||||
|
||||
get inputWidget(): InlineChatInputWidget {
|
||||
return this.#inputWidget;
|
||||
return this._inputWidget;
|
||||
}
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
@INotebookEditorService notebookEditorService: INotebookEditorService,
|
||||
@IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService,
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
|
||||
@IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ISharedWebContentExtractorService webContentExtractorService: ISharedWebContentExtractorService,
|
||||
@IFileService fileService: IFileService,
|
||||
@IChatAttachmentResolveService chatAttachmentResolveService: IChatAttachmentResolveService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IMarkerDecorationsService markerDecorationsService: IMarkerDecorationsService,
|
||||
@ILanguageModelsService languageModelService: ILanguageModelsService,
|
||||
@ILogService logService: ILogService,
|
||||
@IChatEditingService chatEditingService: IChatEditingService,
|
||||
@IChatService chatService: IChatService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@ISharedWebContentExtractorService private readonly _webContentExtractorService: ISharedWebContentExtractorService,
|
||||
@IFileService private readonly _fileService: IFileService,
|
||||
@IChatAttachmentResolveService private readonly _chatAttachmentResolveService: IChatAttachmentResolveService,
|
||||
@IEditorService private readonly _editorService: IEditorService,
|
||||
@IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService,
|
||||
@ILanguageModelsService private readonly _languageModelService: ILanguageModelsService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
@IChatEditingService private readonly _chatEditingService: IChatEditingService,
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
) {
|
||||
this.#editor = editor;
|
||||
this.#instaService = instaService;
|
||||
this.#notebookEditorService = notebookEditorService;
|
||||
this.#inlineChatSessionService = inlineChatSessionService;
|
||||
this.#configurationService = configurationService;
|
||||
this.#webContentExtractorService = webContentExtractorService;
|
||||
this.#fileService = fileService;
|
||||
this.#chatAttachmentResolveService = chatAttachmentResolveService;
|
||||
this.#editorService = editorService;
|
||||
this.#markerDecorationsService = markerDecorationsService;
|
||||
this.#languageModelService = languageModelService;
|
||||
this.#logService = logService;
|
||||
this.#chatEditingService = chatEditingService;
|
||||
this.#chatService = chatService;
|
||||
|
||||
const editorObs = observableCodeEditor(editor);
|
||||
const editorObs = observableCodeEditor(_editor);
|
||||
|
||||
const ctxInlineChatVisible = CTX_INLINE_CHAT_VISIBLE.bindTo(contextKeyService);
|
||||
const ctxFileBelongsToChat = CTX_INLINE_CHAT_FILE_BELONGS_TO_CHAT.bindTo(contextKeyService);
|
||||
const ctxPendingConfirmation = CTX_INLINE_CHAT_PENDING_CONFIRMATION.bindTo(contextKeyService);
|
||||
const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, this.#configurationService);
|
||||
this.#renderMode = observableConfigValue(InlineChatConfigKeys.RenderMode, 'zone', this.#configurationService);
|
||||
const notebookAgentConfig = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, this._configurationService);
|
||||
this._renderMode = observableConfigValue(InlineChatConfigKeys.RenderMode, 'zone', this._configurationService);
|
||||
|
||||
// Track whether the current editor's file is being edited by any chat editing session
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const model = editorObs.model.read(r);
|
||||
if (!model) {
|
||||
ctxFileBelongsToChat.set(false);
|
||||
return;
|
||||
}
|
||||
const sessions = this.#chatEditingService.editingSessionsObs.read(r);
|
||||
const sessions = this._chatEditingService.editingSessionsObs.read(r);
|
||||
let hasEdits = false;
|
||||
for (const session of sessions) {
|
||||
const entries = session.entries.read(r);
|
||||
@@ -215,25 +185,25 @@ export class InlineChatController implements IEditorContribution {
|
||||
ctxFileBelongsToChat.set(hasEdits);
|
||||
}));
|
||||
|
||||
const overlayWidget = this.#inputWidget = this.#store.add(this.#instaService.createInstance(InlineChatInputWidget, editorObs));
|
||||
const sessionOverlayWidget = this.#store.add(this.#instaService.createInstance(InlineChatSessionOverlayWidget, editorObs));
|
||||
this.inputOverlayWidget = this.#store.add(this.#instaService.createInstance(InlineChatAffordance, this.#editor, overlayWidget));
|
||||
const overlayWidget = this._inputWidget = this._store.add(this._instaService.createInstance(InlineChatInputWidget, editorObs));
|
||||
const sessionOverlayWidget = this._store.add(this._instaService.createInstance(InlineChatSessionOverlayWidget, editorObs));
|
||||
this.inputOverlayWidget = this._store.add(this._instaService.createInstance(InlineChatAffordance, this._editor, overlayWidget));
|
||||
|
||||
this.#zone = new Lazy<InlineChatZoneWidget>(() => {
|
||||
this._zone = new Lazy<InlineChatZoneWidget>(() => {
|
||||
|
||||
assertType(this.#editor.hasModel(), '[Illegal State] widget should only be created when the editor has a model');
|
||||
assertType(this._editor.hasModel(), '[Illegal State] widget should only be created when the editor has a model');
|
||||
|
||||
const location: IChatWidgetLocationOptions = {
|
||||
location: ChatAgentLocation.EditorInline,
|
||||
resolveData: () => {
|
||||
assertType(this.#editor.hasModel());
|
||||
const wholeRange = this.#editor.getSelection();
|
||||
const document = this.#editor.getModel().uri;
|
||||
assertType(this._editor.hasModel());
|
||||
const wholeRange = this._editor.getSelection();
|
||||
const document = this._editor.getModel().uri;
|
||||
|
||||
return {
|
||||
type: ChatAgentLocation.EditorInline,
|
||||
id: getEditorId(this.#editor, this.#editor.getModel()),
|
||||
selection: this.#editor.getSelection(),
|
||||
id: getEditorId(this._editor, this._editor.getModel()),
|
||||
selection: this._editor.getSelection(),
|
||||
document,
|
||||
wholeRange
|
||||
};
|
||||
@@ -243,22 +213,22 @@ export class InlineChatController implements IEditorContribution {
|
||||
// inline chat in notebooks
|
||||
// check if this editor is part of a notebook editor
|
||||
// if so, update the location and use the notebook specific widget
|
||||
const notebookEditor = this.#notebookEditorService.getNotebookForPossibleCell(this.#editor);
|
||||
const notebookEditor = this._notebookEditorService.getNotebookForPossibleCell(this._editor);
|
||||
if (!!notebookEditor) {
|
||||
location.location = ChatAgentLocation.Notebook;
|
||||
if (notebookAgentConfig.get()) {
|
||||
location.resolveData = () => {
|
||||
assertType(this.#editor.hasModel());
|
||||
assertType(this._editor.hasModel());
|
||||
|
||||
return {
|
||||
type: ChatAgentLocation.Notebook,
|
||||
sessionInputUri: this.#editor.getModel().uri,
|
||||
sessionInputUri: this._editor.getModel().uri,
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const result = this.#instaService.createInstance(InlineChatZoneWidget,
|
||||
const result = this._instaService.createInstance(InlineChatZoneWidget,
|
||||
location,
|
||||
{
|
||||
enableWorkingSet: 'implicit',
|
||||
@@ -278,33 +248,33 @@ export class InlineChatController implements IEditorContribution {
|
||||
},
|
||||
defaultMode: ChatMode.Ask
|
||||
},
|
||||
{ editor: this.#editor, notebookEditor },
|
||||
{ editor: this._editor, notebookEditor },
|
||||
() => Promise.resolve(),
|
||||
);
|
||||
|
||||
this.#store.add(result);
|
||||
this._store.add(result);
|
||||
|
||||
result.domNode.classList.add('inline-chat-2');
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
const sessionsSignal = observableSignalFromEvent(this, this.#inlineChatSessionService.onDidChangeSessions);
|
||||
const sessionsSignal = observableSignalFromEvent(this, _inlineChatSessionService.onDidChangeSessions);
|
||||
|
||||
this.#currentSession = derived(r => {
|
||||
this._currentSession = derived(r => {
|
||||
sessionsSignal.read(r);
|
||||
const model = editorObs.model.read(r);
|
||||
const session = model && this.#inlineChatSessionService.getSessionByTextModel(model.uri);
|
||||
const session = model && _inlineChatSessionService.getSessionByTextModel(model.uri);
|
||||
return session ?? undefined;
|
||||
});
|
||||
|
||||
|
||||
let lastSession: IInlineChatSession2 | undefined = undefined;
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
const session = this.#currentSession.read(r);
|
||||
this._store.add(autorun(r => {
|
||||
const session = this._currentSession.read(r);
|
||||
if (!session) {
|
||||
this.#isActiveController.set(false, undefined);
|
||||
this._isActiveController.set(false, undefined);
|
||||
|
||||
if (lastSession && !lastSession.chatModel.hasRequests) {
|
||||
const state = lastSession.chatModel.inputModel.state.read(undefined);
|
||||
@@ -320,24 +290,23 @@ export class InlineChatController implements IEditorContribution {
|
||||
|
||||
let foundOne = false;
|
||||
for (const editor of codeEditorService.listCodeEditors()) {
|
||||
const ctrl = InlineChatController.get(editor);
|
||||
if (ctrl && ctrl.#isActiveController.read(undefined)) {
|
||||
if (Boolean(InlineChatController.get(editor)?._isActiveController.read(undefined))) {
|
||||
foundOne = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!foundOne && editorObs.isFocused.read(r)) {
|
||||
this.#isActiveController.set(true, undefined);
|
||||
this._isActiveController.set(true, undefined);
|
||||
}
|
||||
}));
|
||||
|
||||
const visibleSessionObs = observableValue<IInlineChatSession2 | undefined>(this, undefined);
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
|
||||
const model = editorObs.model.read(r);
|
||||
const session = this.#currentSession.read(r);
|
||||
const isActive = this.#isActiveController.read(r);
|
||||
const session = this._currentSession.read(r);
|
||||
const isActive = this._isActiveController.read(r);
|
||||
|
||||
if (!session || !isActive || !model) {
|
||||
visibleSessionObs.set(undefined, undefined);
|
||||
@@ -353,38 +322,38 @@ export class InlineChatController implements IEditorContribution {
|
||||
});
|
||||
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
|
||||
// HIDE/SHOW
|
||||
const session = visibleSessionObs.read(r);
|
||||
const renderMode = this.#renderMode.read(r);
|
||||
const renderMode = this._renderMode.read(r);
|
||||
if (!session) {
|
||||
this.#zone.rawValue?.hide();
|
||||
this.#zone.rawValue?.widget.chatWidget.setModel(undefined);
|
||||
this.#editor.focus();
|
||||
this._zone.rawValue?.hide();
|
||||
this._zone.rawValue?.widget.chatWidget.setModel(undefined);
|
||||
_editor.focus();
|
||||
ctxInlineChatVisible.reset();
|
||||
} else if (renderMode === 'hover') {
|
||||
// hover mode: set model but don't show zone, keep focus in editor
|
||||
this.#zone.value.widget.chatWidget.setModel(session.chatModel);
|
||||
this.#zone.rawValue?.hide();
|
||||
this._zone.value.widget.chatWidget.setModel(session.chatModel);
|
||||
this._zone.rawValue?.hide();
|
||||
ctxInlineChatVisible.set(true);
|
||||
} else {
|
||||
ctxInlineChatVisible.set(true);
|
||||
this.#zone.value.widget.chatWidget.setModel(session.chatModel);
|
||||
if (!this.#zone.value.position) {
|
||||
this.#zone.value.widget.chatWidget.setInputPlaceholder(defaultPlaceholderObs.read(r));
|
||||
this.#zone.value.widget.chatWidget.input.renderAttachedContext(); // TODO - fights layout bug
|
||||
this.#zone.value.show(session.initialPosition);
|
||||
this._zone.value.widget.chatWidget.setModel(session.chatModel);
|
||||
if (!this._zone.value.position) {
|
||||
this._zone.value.widget.chatWidget.setInputPlaceholder(defaultPlaceholderObs.read(r));
|
||||
this._zone.value.widget.chatWidget.input.renderAttachedContext(); // TODO - fights layout bug
|
||||
this._zone.value.show(session.initialPosition);
|
||||
}
|
||||
this.#zone.value.reveal(this.#zone.value.position!);
|
||||
this.#zone.value.widget.focus();
|
||||
this._zone.value.reveal(this._zone.value.position!);
|
||||
this._zone.value.widget.focus();
|
||||
}
|
||||
}));
|
||||
|
||||
// Show progress overlay widget in hover mode when a request is in progress or edits are not yet settled
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const session = visibleSessionObs.read(r);
|
||||
const renderMode = this.#renderMode.read(r);
|
||||
const renderMode = this._renderMode.read(r);
|
||||
if (!session || renderMode !== 'hover') {
|
||||
ctxPendingConfirmation.set(false);
|
||||
sessionOverlayWidget.hide();
|
||||
@@ -406,7 +375,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
}));
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const session = visibleSessionObs.read(r);
|
||||
if (session) {
|
||||
const entries = session.editingSession.entries.read(r);
|
||||
@@ -424,7 +393,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
for (const entry of otherEntries) {
|
||||
// OPEN other modified files in side group. This is a workaround, temp-solution until we have no more backend
|
||||
// that modifies other files
|
||||
this.#editorService.openEditor({ resource: entry.modifiedURI }, SIDE_GROUP).catch(onUnexpectedError);
|
||||
this._editorService.openEditor({ resource: entry.modifiedURI }, SIDE_GROUP).catch(onUnexpectedError);
|
||||
}
|
||||
}
|
||||
}));
|
||||
@@ -445,36 +414,36 @@ export class InlineChatController implements IEditorContribution {
|
||||
});
|
||||
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const response = lastResponseObs.read(r);
|
||||
|
||||
this.#zone.rawValue?.widget.updateInfo('');
|
||||
this._zone.rawValue?.widget.updateInfo('');
|
||||
|
||||
if (!response?.isInProgress.read(r)) {
|
||||
|
||||
if (response?.result?.errorDetails) {
|
||||
// ERROR case
|
||||
this.#zone.rawValue?.widget.updateInfo(`$(error) ${response.result.errorDetails.message}`);
|
||||
this._zone.rawValue?.widget.updateInfo(`$(error) ${response.result.errorDetails.message}`);
|
||||
alert(response.result.errorDetails.message);
|
||||
}
|
||||
|
||||
// no response or not in progress
|
||||
this.#zone.rawValue?.widget.domNode.classList.toggle('request-in-progress', false);
|
||||
this.#zone.rawValue?.widget.chatWidget.setInputPlaceholder(defaultPlaceholderObs.read(r));
|
||||
this._zone.rawValue?.widget.domNode.classList.toggle('request-in-progress', false);
|
||||
this._zone.rawValue?.widget.chatWidget.setInputPlaceholder(defaultPlaceholderObs.read(r));
|
||||
|
||||
} else {
|
||||
this.#zone.rawValue?.widget.domNode.classList.toggle('request-in-progress', true);
|
||||
this._zone.rawValue?.widget.domNode.classList.toggle('request-in-progress', true);
|
||||
let placeholder = response.request?.message.text;
|
||||
const lastProgress = lastResponseProgressObs.read(r);
|
||||
if (lastProgress) {
|
||||
placeholder = renderAsPlaintext(lastProgress.content);
|
||||
}
|
||||
this.#zone.rawValue?.widget.chatWidget.setInputPlaceholder(placeholder || localize('loading', "Working..."));
|
||||
this._zone.rawValue?.widget.chatWidget.setInputPlaceholder(placeholder || localize('loading', "Working..."));
|
||||
}
|
||||
|
||||
}));
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const session = visibleSessionObs.read(r);
|
||||
if (!session) {
|
||||
return;
|
||||
@@ -487,25 +456,25 @@ export class InlineChatController implements IEditorContribution {
|
||||
}));
|
||||
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
|
||||
const session = visibleSessionObs.read(r);
|
||||
const entry = session?.editingSession.readEntry(session.uri, r);
|
||||
|
||||
// make sure there is an editor integration
|
||||
const pane = this.#editorService.visibleEditorPanes.find(candidate => candidate.getControl() === this.#editor || isNotebookWithCellEditor(candidate, this.#editor));
|
||||
const pane = this._editorService.visibleEditorPanes.find(candidate => candidate.getControl() === this._editor || isNotebookWithCellEditor(candidate, this._editor));
|
||||
if (pane && entry) {
|
||||
entry?.getEditorIntegration(pane);
|
||||
}
|
||||
|
||||
// make sure the ZONE isn't inbetween a diff and move above if so
|
||||
if (entry?.diffInfo && this.#zone.value.position) {
|
||||
const { position } = this.#zone.value;
|
||||
if (entry?.diffInfo && this._zone.value.position) {
|
||||
const { position } = this._zone.value;
|
||||
const diff = entry.diffInfo.read(r);
|
||||
|
||||
for (const change of diff.changes) {
|
||||
if (change.modified.contains(position.lineNumber)) {
|
||||
this.#zone.value.updatePositionAndHeight(new Position(change.modified.startLineNumber - 1, 1));
|
||||
this._zone.value.updatePositionAndHeight(new Position(change.modified.startLineNumber - 1, 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -514,90 +483,90 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.#store.dispose();
|
||||
this._store.dispose();
|
||||
}
|
||||
|
||||
getWidgetPosition(): Position | undefined {
|
||||
return this.#zone.rawValue?.position;
|
||||
return this._zone.rawValue?.position;
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.#zone.rawValue?.widget.focus();
|
||||
this._zone.rawValue?.widget.focus();
|
||||
}
|
||||
|
||||
async run(arg?: InlineChatRunOptions): Promise<boolean> {
|
||||
assertType(this.#editor.hasModel());
|
||||
const uri = this.#editor.getModel().uri;
|
||||
assertType(this._editor.hasModel());
|
||||
const uri = this._editor.getModel().uri;
|
||||
|
||||
const existingSession = this.#inlineChatSessionService.getSessionByTextModel(uri);
|
||||
const existingSession = this._inlineChatSessionService.getSessionByTextModel(uri);
|
||||
if (existingSession) {
|
||||
await existingSession.editingSession.accept();
|
||||
existingSession.dispose();
|
||||
}
|
||||
|
||||
this.#isActiveController.set(true, undefined);
|
||||
this._isActiveController.set(true, undefined);
|
||||
|
||||
const session = this.#inlineChatSessionService.createSession(this.#editor);
|
||||
const session = this._inlineChatSessionService.createSession(this._editor);
|
||||
|
||||
// Store for tracking model changes during this session
|
||||
const sessionStore = new DisposableStore();
|
||||
|
||||
try {
|
||||
await this.#applyModelDefaults(session, sessionStore);
|
||||
await this._applyModelDefaults(session, sessionStore);
|
||||
|
||||
if (arg) {
|
||||
arg.attachDiagnostics ??= this.#configurationService.getValue(InlineChatConfigKeys.RenderMode) === 'zone';
|
||||
arg.attachDiagnostics ??= this._configurationService.getValue(InlineChatConfigKeys.RenderMode) === 'zone';
|
||||
}
|
||||
|
||||
// ADD diagnostics (only when explicitly requested)
|
||||
if (arg?.attachDiagnostics) {
|
||||
const entries: IChatRequestVariableEntry[] = [];
|
||||
for (const [range, marker] of this.#markerDecorationsService.getLiveMarkers(uri)) {
|
||||
if (range.intersectRanges(this.#editor.getSelection())) {
|
||||
for (const [range, marker] of this._markerDecorationsService.getLiveMarkers(uri)) {
|
||||
if (range.intersectRanges(this._editor.getSelection())) {
|
||||
const filter = IDiagnosticVariableEntryFilterData.fromMarker(marker);
|
||||
entries.push(IDiagnosticVariableEntryFilterData.toEntry(filter));
|
||||
}
|
||||
}
|
||||
if (entries.length > 0) {
|
||||
this.#zone.value.widget.chatWidget.attachmentModel.addContext(...entries);
|
||||
this._zone.value.widget.chatWidget.attachmentModel.addContext(...entries);
|
||||
const msg = entries.length > 1
|
||||
? localize('fixN', "Fix the attached problems")
|
||||
: localize('fix1', "Fix the attached problem");
|
||||
this.#zone.value.widget.chatWidget.input.setValue(msg, true);
|
||||
this._zone.value.widget.chatWidget.input.setValue(msg, true);
|
||||
arg.message = msg;
|
||||
this.#zone.value.widget.chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
|
||||
this._zone.value.widget.chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
|
||||
}
|
||||
}
|
||||
|
||||
// Check args
|
||||
if (arg && InlineChatRunOptions.isInlineChatRunOptions(arg)) {
|
||||
if (arg.initialRange) {
|
||||
this.#editor.revealRange(arg.initialRange);
|
||||
this._editor.revealRange(arg.initialRange);
|
||||
}
|
||||
if (arg.initialSelection) {
|
||||
this.#editor.setSelection(arg.initialSelection);
|
||||
this._editor.setSelection(arg.initialSelection);
|
||||
}
|
||||
if (arg.attachments) {
|
||||
await Promise.all(arg.attachments.map(async attachment => {
|
||||
await this.#zone.value.widget.chatWidget.attachmentModel.addFile(attachment);
|
||||
await this._zone.value.widget.chatWidget.attachmentModel.addFile(attachment);
|
||||
}));
|
||||
delete arg.attachments;
|
||||
}
|
||||
if (arg.modelSelector) {
|
||||
const id = (await this.#languageModelService.selectLanguageModels(arg.modelSelector)).sort().at(0);
|
||||
const id = (await this._languageModelService.selectLanguageModels(arg.modelSelector)).sort().at(0);
|
||||
if (!id) {
|
||||
throw new Error(`No language models found matching selector: ${JSON.stringify(arg.modelSelector)}.`);
|
||||
}
|
||||
const model = this.#languageModelService.lookupLanguageModel(id);
|
||||
const model = this._languageModelService.lookupLanguageModel(id);
|
||||
if (!model) {
|
||||
throw new Error(`Language model not loaded: ${id}.`);
|
||||
}
|
||||
this.#zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: model, identifier: id });
|
||||
this._zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: model, identifier: id });
|
||||
}
|
||||
if (arg.message) {
|
||||
this.#zone.value.widget.chatWidget.setInput(arg.message);
|
||||
this._zone.value.widget.chatWidget.setInput(arg.message);
|
||||
if (arg.autoSend) {
|
||||
await this.#zone.value.widget.chatWidget.acceptInput();
|
||||
await this._zone.value.widget.chatWidget.acceptInput();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -623,7 +592,7 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
|
||||
async acceptSession() {
|
||||
const session = this.#currentSession.get();
|
||||
const session = this._currentSession.get();
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
@@ -632,23 +601,23 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
|
||||
async rejectSession() {
|
||||
const session = this.#currentSession.get();
|
||||
const session = this._currentSession.get();
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
this.#chatService.cancelCurrentRequestForSession(session.chatModel.sessionResource, 'inlineChatReject');
|
||||
this._chatService.cancelCurrentRequestForSession(session.chatModel.sessionResource, 'inlineChatReject');
|
||||
await session.editingSession.reject();
|
||||
session.dispose();
|
||||
}
|
||||
|
||||
async #selectVendorDefaultModel(session: IInlineChatSession2): Promise<void> {
|
||||
const model = this.#zone.value.widget.chatWidget.input.selectedLanguageModel.get();
|
||||
private async _selectVendorDefaultModel(session: IInlineChatSession2): Promise<void> {
|
||||
const model = this._zone.value.widget.chatWidget.input.selectedLanguageModel.get();
|
||||
if (model && !model.metadata.isDefaultForLocation[session.chatModel.initialLocation]) {
|
||||
const ids = await this.#languageModelService.selectLanguageModels({ vendor: model.metadata.vendor });
|
||||
const ids = await this._languageModelService.selectLanguageModels({ vendor: model.metadata.vendor });
|
||||
for (const identifier of ids) {
|
||||
const candidate = this.#languageModelService.lookupLanguageModel(identifier);
|
||||
const candidate = this._languageModelService.lookupLanguageModel(identifier);
|
||||
if (candidate?.isDefaultForLocation[session.chatModel.initialLocation]) {
|
||||
this.#zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: candidate, identifier });
|
||||
this._zone.value.widget.chatWidget.input.setCurrentLanguageModel({ metadata: candidate, identifier });
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -659,39 +628,39 @@ export class InlineChatController implements IEditorContribution {
|
||||
* Applies model defaults based on settings and tracks user model changes.
|
||||
* Prioritization: user session choice > inlineChat.defaultModel setting > vendor default
|
||||
*/
|
||||
async #applyModelDefaults(session: IInlineChatSession2, sessionStore: DisposableStore): Promise<void> {
|
||||
const userSelectedModel = InlineChatController.#userSelectedModel;
|
||||
const defaultModelSetting = this.#configurationService.getValue<string>(InlineChatConfigKeys.DefaultModel);
|
||||
private async _applyModelDefaults(session: IInlineChatSession2, sessionStore: DisposableStore): Promise<void> {
|
||||
const userSelectedModel = InlineChatController._userSelectedModel;
|
||||
const defaultModelSetting = this._configurationService.getValue<string>(InlineChatConfigKeys.DefaultModel);
|
||||
|
||||
let modelApplied = false;
|
||||
|
||||
// 1. Try user's explicitly chosen model from a previous inline chat in the same session
|
||||
if (userSelectedModel) {
|
||||
modelApplied = this.#zone.value.widget.chatWidget.input.switchModelByQualifiedName([userSelectedModel]);
|
||||
modelApplied = this._zone.value.widget.chatWidget.input.switchModelByQualifiedName([userSelectedModel]);
|
||||
if (!modelApplied) {
|
||||
// User's previously selected model is no longer available, clear it
|
||||
InlineChatController.#userSelectedModel = undefined;
|
||||
InlineChatController._userSelectedModel = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Try inlineChat.defaultModel setting
|
||||
if (!modelApplied && defaultModelSetting) {
|
||||
modelApplied = this.#zone.value.widget.chatWidget.input.switchModelByQualifiedName([defaultModelSetting]);
|
||||
modelApplied = this._zone.value.widget.chatWidget.input.switchModelByQualifiedName([defaultModelSetting]);
|
||||
if (!modelApplied) {
|
||||
this.#logService.warn(`inlineChat.defaultModel setting value '${defaultModelSetting}' did not match any available model. Falling back to vendor default.`);
|
||||
this._logService.warn(`inlineChat.defaultModel setting value '${defaultModelSetting}' did not match any available model. Falling back to vendor default.`);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Fall back to vendor default
|
||||
if (!modelApplied) {
|
||||
await this.#selectVendorDefaultModel(session);
|
||||
await this._selectVendorDefaultModel(session);
|
||||
}
|
||||
|
||||
// Track model changes - store user's explicit choice in the given sessions.
|
||||
// NOTE: This currently detects any model change, not just user-initiated ones.
|
||||
let initialModelId: string | undefined;
|
||||
sessionStore.add(autorun(r => {
|
||||
const newModel = this.#zone.value.widget.chatWidget.input.selectedLanguageModel.read(r);
|
||||
const newModel = this._zone.value.widget.chatWidget.input.selectedLanguageModel.read(r);
|
||||
if (!newModel) {
|
||||
return;
|
||||
}
|
||||
@@ -701,25 +670,25 @@ export class InlineChatController implements IEditorContribution {
|
||||
}
|
||||
if (initialModelId !== newModel.identifier) {
|
||||
// User explicitly changed model, store their choice as qualified name
|
||||
InlineChatController.#userSelectedModel = ILanguageModelChatMetadata.asQualifiedName(newModel.metadata);
|
||||
InlineChatController._userSelectedModel = ILanguageModelChatMetadata.asQualifiedName(newModel.metadata);
|
||||
initialModelId = newModel.identifier;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
async createImageAttachment(attachment: URI): Promise<IChatRequestVariableEntry | undefined> {
|
||||
const value = this.#currentSession.get();
|
||||
const value = this._currentSession.get();
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
if (attachment.scheme === Schemas.file) {
|
||||
if (await this.#fileService.canHandleResource(attachment)) {
|
||||
return await this.#chatAttachmentResolveService.resolveImageEditorAttachContext(attachment);
|
||||
if (await this._fileService.canHandleResource(attachment)) {
|
||||
return await this._chatAttachmentResolveService.resolveImageEditorAttachContext(attachment);
|
||||
}
|
||||
} else if (attachment.scheme === Schemas.http || attachment.scheme === Schemas.https) {
|
||||
const extractedImages = await this.#webContentExtractorService.readImage(attachment, CancellationToken.None);
|
||||
const extractedImages = await this._webContentExtractorService.readImage(attachment, CancellationToken.None);
|
||||
if (extractedImages) {
|
||||
return await this.#chatAttachmentResolveService.resolveImageEditorAttachContext(attachment, extractedImages);
|
||||
return await this._chatAttachmentResolveService.resolveImageEditorAttachContext(attachment, extractedImages);
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
|
||||
@@ -33,13 +33,12 @@ import { ICommandService } from '../../../../platform/commands/common/commands.j
|
||||
|
||||
class QuickFixActionViewItem extends MenuEntryActionViewItem {
|
||||
|
||||
readonly #lightBulbStore = this._store.add(new MutableDisposable<DisposableStore>());
|
||||
readonly #editor: ICodeEditor;
|
||||
#currentTitle: string | undefined;
|
||||
private readonly _lightBulbStore = this._store.add(new MutableDisposable<DisposableStore>());
|
||||
private _currentTitle: string | undefined;
|
||||
|
||||
constructor(
|
||||
action: MenuItemAction,
|
||||
editor: ICodeEditor,
|
||||
private readonly _editor: ICodeEditor,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@@ -56,7 +55,7 @@ class QuickFixActionViewItem extends MenuEntryActionViewItem {
|
||||
elementGetter: () => HTMLElement | undefined = () => undefined;
|
||||
|
||||
override async run(...args: unknown[]): Promise<void> {
|
||||
const controller = CodeActionController.get(editor);
|
||||
const controller = CodeActionController.get(_editor);
|
||||
const info = controller?.lightBulbState.get();
|
||||
const element = this.elementGetter();
|
||||
if (controller && info && element) {
|
||||
@@ -68,27 +67,26 @@ class QuickFixActionViewItem extends MenuEntryActionViewItem {
|
||||
|
||||
super(wrappedAction, { draggable: false }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService, accessibilityService);
|
||||
|
||||
this.#editor = editor;
|
||||
wrappedAction.elementGetter = () => this.element;
|
||||
}
|
||||
|
||||
override render(container: HTMLElement): void {
|
||||
super.render(container);
|
||||
this.#updateFromLightBulb();
|
||||
this._updateFromLightBulb();
|
||||
}
|
||||
|
||||
protected override getTooltip(): string {
|
||||
return this.#currentTitle ?? super.getTooltip();
|
||||
return this._currentTitle ?? super.getTooltip();
|
||||
}
|
||||
|
||||
#updateFromLightBulb(): void {
|
||||
const controller = CodeActionController.get(this.#editor);
|
||||
private _updateFromLightBulb(): void {
|
||||
const controller = CodeActionController.get(this._editor);
|
||||
if (!controller) {
|
||||
return;
|
||||
}
|
||||
|
||||
const store = new DisposableStore();
|
||||
this.#lightBulbStore.value = store;
|
||||
this._lightBulbStore.value = store;
|
||||
|
||||
store.add(autorun(reader => {
|
||||
const info = controller.lightBulbState.read(reader);
|
||||
@@ -101,7 +99,7 @@ class QuickFixActionViewItem extends MenuEntryActionViewItem {
|
||||
}
|
||||
|
||||
// Update tooltip
|
||||
this.#currentTitle = info?.title;
|
||||
this._currentTitle = info?.title;
|
||||
this.updateTooltip();
|
||||
}));
|
||||
}
|
||||
@@ -109,7 +107,7 @@ class QuickFixActionViewItem extends MenuEntryActionViewItem {
|
||||
|
||||
class LabelWithKeybindingActionViewItem extends MenuEntryActionViewItem {
|
||||
|
||||
readonly #kbLabel: string | undefined;
|
||||
private readonly _kbLabel: string | undefined;
|
||||
|
||||
constructor(
|
||||
action: MenuItemAction,
|
||||
@@ -123,14 +121,14 @@ class LabelWithKeybindingActionViewItem extends MenuEntryActionViewItem {
|
||||
super(action, { draggable: false }, keybindingService, notificationService, contextKeyService, themeService, contextMenuService, accessibilityService);
|
||||
this.options.label = true;
|
||||
this.options.icon = false;
|
||||
this.#kbLabel = keybindingService.lookupKeybinding(action.id)?.getLabel() ?? undefined;
|
||||
this._kbLabel = keybindingService.lookupKeybinding(action.id)?.getLabel() ?? undefined;
|
||||
}
|
||||
|
||||
protected override updateLabel(): void {
|
||||
if (this.label) {
|
||||
dom.reset(this.label,
|
||||
this.action.label,
|
||||
...(this.#kbLabel ? [dom.$('span.inline-chat-keybinding', undefined, this.#kbLabel)] : [])
|
||||
...(this._kbLabel ? [dom.$('span.inline-chat-keybinding', undefined, this._kbLabel)] : [])
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -142,42 +140,38 @@ class LabelWithKeybindingActionViewItem extends MenuEntryActionViewItem {
|
||||
*/
|
||||
export class InlineChatEditorAffordance extends Disposable implements IContentWidget {
|
||||
|
||||
static #idPool = 0;
|
||||
private static _idPool = 0;
|
||||
|
||||
readonly #id = `inline-chat-content-widget-${InlineChatEditorAffordance.#idPool++}`;
|
||||
readonly #domNode: HTMLElement;
|
||||
#position: IContentWidgetPosition | null = null;
|
||||
#isVisible = false;
|
||||
private readonly _id = `inline-chat-content-widget-${InlineChatEditorAffordance._idPool++}`;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private _position: IContentWidgetPosition | null = null;
|
||||
private _isVisible = false;
|
||||
|
||||
readonly #onDidRunAction = this._store.add(new Emitter<string>());
|
||||
readonly onDidRunAction: Event<string> = this.#onDidRunAction.event;
|
||||
private readonly _onDidRunAction = this._store.add(new Emitter<string>());
|
||||
readonly onDidRunAction: Event<string> = this._onDidRunAction.event;
|
||||
|
||||
readonly allowEditorOverflow = true;
|
||||
readonly suppressMouseDown = false;
|
||||
|
||||
readonly #editor: ICodeEditor;
|
||||
|
||||
constructor(
|
||||
editor: ICodeEditor,
|
||||
private readonly _editor: ICodeEditor,
|
||||
selection: IObservable<Selection | undefined>,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#editor = editor;
|
||||
|
||||
// Create the widget DOM
|
||||
this.#domNode = dom.$('.inline-chat-content-widget');
|
||||
this._domNode = dom.$('.inline-chat-content-widget');
|
||||
|
||||
// Create toolbar with the inline chat start action
|
||||
const toolbar = this._store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this.#domNode, MenuId.InlineChatEditorAffordance, {
|
||||
const toolbar = this._store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this._domNode, MenuId.InlineChatEditorAffordance, {
|
||||
telemetrySource: 'inlineChatEditorAffordance',
|
||||
hiddenItemStrategy: HiddenItemStrategy.Ignore,
|
||||
menuOptions: { renderShortTitle: true },
|
||||
toolbarOptions: { primaryGroup: () => true, useSeparatorsInPrimaryActions: true },
|
||||
actionViewItemProvider: (action: IAction) => {
|
||||
if (action instanceof MenuItemAction && action.id === quickFixCommandId) {
|
||||
return instantiationService.createInstance(QuickFixActionViewItem, action, this.#editor);
|
||||
return instantiationService.createInstance(QuickFixActionViewItem, action, this._editor);
|
||||
}
|
||||
if (action instanceof MenuItemAction && (action.id === ACTION_START || action.id === ACTION_ASK_IN_CHAT || action.id === 'inlineChat.fixDiagnostics')) {
|
||||
return instantiationService.createInstance(LabelWithKeybindingActionViewItem, action);
|
||||
@@ -186,37 +180,37 @@ export class InlineChatEditorAffordance extends Disposable implements IContentWi
|
||||
}
|
||||
}));
|
||||
this._store.add(toolbar.actionRunner.onDidRun((e) => {
|
||||
this.#onDidRunAction.fire(e.action.id);
|
||||
this.#hide();
|
||||
this._onDidRunAction.fire(e.action.id);
|
||||
this._hide();
|
||||
}));
|
||||
|
||||
this._store.add(autorun(r => {
|
||||
const sel = selection.read(r);
|
||||
if (sel) {
|
||||
this.#show(sel);
|
||||
this._show(sel);
|
||||
} else {
|
||||
this.#hide();
|
||||
this._hide();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#show(selection: Selection): void {
|
||||
private _show(selection: Selection): void {
|
||||
|
||||
if (selection.isEmpty()) {
|
||||
this.#showAtLineStart(selection.getPosition().lineNumber);
|
||||
this._showAtLineStart(selection.getPosition().lineNumber);
|
||||
} else {
|
||||
this.#showAtSelection(selection);
|
||||
this._showAtSelection(selection);
|
||||
}
|
||||
|
||||
if (this.#isVisible) {
|
||||
this.#editor.layoutContentWidget(this);
|
||||
if (this._isVisible) {
|
||||
this._editor.layoutContentWidget(this);
|
||||
} else {
|
||||
this.#editor.addContentWidget(this);
|
||||
this.#isVisible = true;
|
||||
this._editor.addContentWidget(this);
|
||||
this._isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
#showAtSelection(selection: Selection): void {
|
||||
private _showAtSelection(selection: Selection): void {
|
||||
const cursorPosition = selection.getPosition();
|
||||
const direction = selection.getDirection();
|
||||
|
||||
@@ -224,20 +218,20 @@ export class InlineChatEditorAffordance extends Disposable implements IContentWi
|
||||
? ContentWidgetPositionPreference.ABOVE
|
||||
: ContentWidgetPositionPreference.BELOW;
|
||||
|
||||
this.#position = {
|
||||
this._position = {
|
||||
position: cursorPosition,
|
||||
preference: [preference],
|
||||
};
|
||||
}
|
||||
|
||||
#showAtLineStart(lineNumber: number): void {
|
||||
const model = this.#editor.getModel();
|
||||
private _showAtLineStart(lineNumber: number): void {
|
||||
const model = this._editor.getModel();
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabSize = model.getOptions().tabSize;
|
||||
const fontInfo = this.#editor.getOptions().get(EditorOption.fontInfo);
|
||||
const fontInfo = this._editor.getOptions().get(EditorOption.fontInfo);
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
const indent = computeIndentLevel(lineContent, tabSize);
|
||||
const lineHasSpace = indent < 0 ? true : fontInfo.spaceWidth * indent > 22;
|
||||
@@ -260,43 +254,43 @@ export class InlineChatEditorAffordance extends Disposable implements IContentWi
|
||||
|
||||
const effectiveColumnNumber = /^\S\s*$/.test(model.getLineContent(effectiveLineNumber)) ? 2 : 1;
|
||||
|
||||
this.#position = {
|
||||
this._position = {
|
||||
position: { lineNumber: effectiveLineNumber, column: effectiveColumnNumber },
|
||||
preference: [ContentWidgetPositionPreference.EXACT],
|
||||
};
|
||||
}
|
||||
|
||||
#hide(): void {
|
||||
if (this.#isVisible) {
|
||||
this.#isVisible = false;
|
||||
this.#editor.removeContentWidget(this);
|
||||
private _hide(): void {
|
||||
if (this._isVisible) {
|
||||
this._isVisible = false;
|
||||
this._editor.removeContentWidget(this);
|
||||
}
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.#id;
|
||||
return this._id;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.#domNode;
|
||||
return this._domNode;
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
return this.#position;
|
||||
return this._position;
|
||||
}
|
||||
|
||||
beforeRender(): IDimension | null {
|
||||
const position = this.#editor.getPosition();
|
||||
const lineHeight = position ? this.#editor.getLineHeightForPosition(position) : this.#editor.getOption(EditorOption.lineHeight);
|
||||
const position = this._editor.getPosition();
|
||||
const lineHeight = position ? this._editor.getLineHeightForPosition(position) : this._editor.getOption(EditorOption.lineHeight);
|
||||
|
||||
this.#domNode.style.setProperty('--vscode-inline-chat-affordance-height', `${lineHeight}px`);
|
||||
this._domNode.style.setProperty('--vscode-inline-chat-affordance-height', `${lineHeight}px`);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
if (this.#isVisible) {
|
||||
this.#editor.removeContentWidget(this);
|
||||
if (this._isVisible) {
|
||||
this._editor.removeContentWidget(this);
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@@ -26,8 +26,8 @@ import { IUserInteractionService } from '../../../../platform/userInteraction/br
|
||||
|
||||
export class InlineChatGutterAffordance extends InlineEditsGutterIndicator {
|
||||
|
||||
readonly #onDidRunAction = this._store.add(new Emitter<string>());
|
||||
readonly onDidRunAction: Event<string> = this.#onDidRunAction.event;
|
||||
private readonly _onDidRunAction = this._store.add(new Emitter<string>());
|
||||
readonly onDidRunAction: Event<string> = this._onDidRunAction.event;
|
||||
|
||||
constructor(
|
||||
myEditorObs: ObservableCodeEditor,
|
||||
@@ -108,6 +108,6 @@ export class InlineChatGutterAffordance extends InlineEditsGutterIndicator {
|
||||
this._store.add(menu);
|
||||
|
||||
|
||||
this._store.add(this.onDidCloseWithCommand(commandId => this.#onDidRunAction.fire(commandId)));
|
||||
this._store.add(this.onDidCloseWithCommand(commandId => this._onDidRunAction.fire(commandId)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IEditorService } from '../../../services/editor/common/editorService.js
|
||||
|
||||
export class InlineChatNotebookContribution {
|
||||
|
||||
readonly #store = new DisposableStore();
|
||||
private readonly _store = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@IInlineChatSessionService sessionService: IInlineChatSessionService,
|
||||
@@ -22,7 +22,7 @@ export class InlineChatNotebookContribution {
|
||||
@INotebookEditorService notebookEditorService: INotebookEditorService,
|
||||
) {
|
||||
|
||||
this.#store.add(sessionService.onWillStartSession(newSessionEditor => {
|
||||
this._store.add(sessionService.onWillStartSession(newSessionEditor => {
|
||||
const candidate = CellUri.parse(newSessionEditor.getModel().uri);
|
||||
if (!candidate) {
|
||||
return;
|
||||
@@ -51,6 +51,6 @@ export class InlineChatNotebookContribution {
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.#store.dispose();
|
||||
this._store.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,58 +44,51 @@ import { assertType } from '../../../../base/common/types.js';
|
||||
*/
|
||||
export class InlineChatInputWidget extends Disposable {
|
||||
|
||||
readonly #domNode: HTMLElement;
|
||||
readonly #container: HTMLElement;
|
||||
readonly #inputContainer: HTMLElement;
|
||||
readonly #toolbarContainer: HTMLElement;
|
||||
readonly #input: IActiveCodeEditor;
|
||||
readonly #position = observableValue<IOverlayWidgetPosition | null>(this, null);
|
||||
readonly position: IObservable<IOverlayWidgetPosition | null> = this.#position;
|
||||
private readonly _domNode: HTMLElement;
|
||||
private readonly _container: HTMLElement;
|
||||
private readonly _inputContainer: HTMLElement;
|
||||
private readonly _toolbarContainer: HTMLElement;
|
||||
private readonly _input: IActiveCodeEditor;
|
||||
private readonly _position = observableValue<IOverlayWidgetPosition | null>(this, null);
|
||||
readonly position: IObservable<IOverlayWidgetPosition | null> = this._position;
|
||||
|
||||
readonly #showStore = this._store.add(new DisposableStore());
|
||||
readonly #stickyScrollHeight: IObservable<number>;
|
||||
readonly #layoutData: IObservable<{ totalWidth: number; toolbarWidth: number; height: number; editorPad: number }>;
|
||||
#anchorLineNumber: number = 0;
|
||||
#anchorLeft: number = 0;
|
||||
#anchorAbove: boolean = false;
|
||||
private readonly _showStore = this._store.add(new DisposableStore());
|
||||
private readonly _stickyScrollHeight: IObservable<number>;
|
||||
private readonly _layoutData: IObservable<{ totalWidth: number; toolbarWidth: number; height: number; editorPad: number }>;
|
||||
private _anchorLineNumber: number = 0;
|
||||
private _anchorLeft: number = 0;
|
||||
private _anchorAbove: boolean = false;
|
||||
|
||||
readonly #editorObs: ObservableCodeEditor;
|
||||
readonly #contextKeyService: IContextKeyService;
|
||||
readonly #menuService: IMenuService;
|
||||
|
||||
constructor(
|
||||
editorObs: ObservableCodeEditor,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
private readonly _editorObs: ObservableCodeEditor,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IMenuService private readonly _menuService: IMenuService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IModelService modelService: IModelService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#editorObs = editorObs;
|
||||
this.#contextKeyService = contextKeyService;
|
||||
this.#menuService = menuService;
|
||||
|
||||
// Create container
|
||||
this.#domNode = dom.$('.inline-chat-gutter-menu');
|
||||
this._domNode = dom.$('.inline-chat-gutter-menu');
|
||||
|
||||
// Create inner container (background + focus border)
|
||||
this.#container = dom.append(this.#domNode, dom.$('.inline-chat-gutter-container'));
|
||||
this._container = dom.append(this._domNode, dom.$('.inline-chat-gutter-container'));
|
||||
|
||||
// Create input editor container
|
||||
this.#inputContainer = dom.append(this.#container, dom.$('.input'));
|
||||
this._inputContainer = dom.append(this._container, dom.$('.input'));
|
||||
|
||||
// Create toolbar container
|
||||
this.#toolbarContainer = dom.append(this.#container, dom.$('.toolbar'));
|
||||
this._toolbarContainer = dom.append(this._container, dom.$('.toolbar'));
|
||||
|
||||
// Create vertical actions bar below the input container
|
||||
const actionsContainer = dom.append(this.#domNode, dom.$('.inline-chat-gutter-actions'));
|
||||
const actionsContainer = dom.append(this._domNode, dom.$('.inline-chat-gutter-actions'));
|
||||
const actionBar = this._store.add(new ActionBar(actionsContainer, {
|
||||
orientation: ActionsOrientation.VERTICAL,
|
||||
preventLoopNavigation: true,
|
||||
}));
|
||||
const actionsMenu = this._store.add(this.#menuService.createMenu(MenuId.ChatEditorInlineMenu, this.#contextKeyService));
|
||||
const actionsMenu = this._store.add(this._menuService.createMenu(MenuId.ChatEditorInlineMenu, this._contextKeyService));
|
||||
const updateActions = () => {
|
||||
const actions = getFlatActionBarActions(actionsMenu.getActions({ shouldForwardArgs: true }));
|
||||
actionBar.clear();
|
||||
@@ -130,13 +123,13 @@ export class InlineChatInputWidget extends Disposable {
|
||||
])
|
||||
};
|
||||
|
||||
this.#input = this._store.add(instantiationService.createInstance(CodeEditorWidget, this.#inputContainer, options, codeEditorWidgetOptions)) as IActiveCodeEditor;
|
||||
this._input = this._store.add(instantiationService.createInstance(CodeEditorWidget, this._inputContainer, options, codeEditorWidgetOptions)) as IActiveCodeEditor;
|
||||
|
||||
const model = this._store.add(modelService.createModel('', null, URI.parse(`gutter-input:${Date.now()}`), true));
|
||||
this.#input.setModel(model);
|
||||
this._input.setModel(model);
|
||||
|
||||
// Create toolbar
|
||||
const toolbar = this._store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this.#toolbarContainer, MenuId.InlineChatInput, {
|
||||
const toolbar = this._store.add(instantiationService.createInstance(MenuWorkbenchToolBar, this._toolbarContainer, MenuId.InlineChatInput, {
|
||||
telemetrySource: 'inlineChatInput.toolbar',
|
||||
hiddenItemStrategy: HiddenItemStrategy.NoHide,
|
||||
toolbarOptions: {
|
||||
@@ -146,8 +139,8 @@ export class InlineChatInputWidget extends Disposable {
|
||||
}));
|
||||
|
||||
// Initialize sticky scroll height observable
|
||||
const stickyScrollController = StickyScrollController.get(this.#editorObs.editor);
|
||||
this.#stickyScrollHeight = stickyScrollController ? observableFromEvent(stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight) : constObservable(0);
|
||||
const stickyScrollController = StickyScrollController.get(this._editorObs.editor);
|
||||
this._stickyScrollHeight = stickyScrollController ? observableFromEvent(stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight) : constObservable(0);
|
||||
|
||||
// Track toolbar width changes
|
||||
const toolbarWidth = observableValue<number>(this, 0);
|
||||
@@ -157,24 +150,24 @@ export class InlineChatInputWidget extends Disposable {
|
||||
this._store.add(resizeObserver);
|
||||
this._store.add(resizeObserver.observe(toolbar.getElement()));
|
||||
|
||||
const contentWidth = observableFromEvent(this, this.#input.onDidChangeModelContent, () => this.#input.getContentWidth());
|
||||
const contentHeight = observableFromEvent(this, this.#input.onDidContentSizeChange, () => this.#input.getContentHeight());
|
||||
const contentWidth = observableFromEvent(this, this._input.onDidChangeModelContent, () => this._input.getContentWidth());
|
||||
const contentHeight = observableFromEvent(this, this._input.onDidContentSizeChange, () => this._input.getContentHeight());
|
||||
|
||||
this.#layoutData = derived(r => {
|
||||
this._layoutData = derived(r => {
|
||||
const editorPad = 6;
|
||||
const totalWidth = contentWidth.read(r) + editorPad + toolbarWidth.read(r);
|
||||
const minWidth = 220;
|
||||
const maxWidth = 600;
|
||||
const clampedWidth = this.#input.getOption(EditorOption.wordWrap) === 'on'
|
||||
const clampedWidth = this._input.getOption(EditorOption.wordWrap) === 'on'
|
||||
? maxWidth
|
||||
: Math.max(minWidth, Math.min(totalWidth, maxWidth));
|
||||
|
||||
const lineHeight = this.#input.getOption(EditorOption.lineHeight);
|
||||
const lineHeight = this._input.getOption(EditorOption.lineHeight);
|
||||
const clampedHeight = Math.min(contentHeight.read(r), (3 * lineHeight));
|
||||
|
||||
if (totalWidth > clampedWidth) {
|
||||
// enable word wrap
|
||||
this.#input.updateOptions({ wordWrap: 'on', });
|
||||
this._input.updateOptions({ wordWrap: 'on', });
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -187,42 +180,42 @@ export class InlineChatInputWidget extends Disposable {
|
||||
|
||||
// Update container width and editor layout when width changes
|
||||
this._store.add(autorun(r => {
|
||||
const { editorPad, toolbarWidth, totalWidth, height } = this.#layoutData.read(r);
|
||||
const { editorPad, toolbarWidth, totalWidth, height } = this._layoutData.read(r);
|
||||
|
||||
const inputWidth = totalWidth - toolbarWidth - editorPad;
|
||||
this.#container.style.width = `${totalWidth}px`;
|
||||
this.#inputContainer.style.width = `${inputWidth}px`;
|
||||
this.#input.layout({ width: inputWidth, height });
|
||||
this._container.style.width = `${totalWidth}px`;
|
||||
this._inputContainer.style.width = `${inputWidth}px`;
|
||||
this._input.layout({ width: inputWidth, height });
|
||||
}));
|
||||
|
||||
// Toggle focus class on the container
|
||||
this._store.add(this.#input.onDidFocusEditorText(() => this.#container.classList.add('focused')));
|
||||
this._store.add(this.#input.onDidBlurEditorText(() => this.#container.classList.remove('focused')));
|
||||
this._store.add(this._input.onDidFocusEditorText(() => this._container.classList.add('focused')));
|
||||
this._store.add(this._input.onDidBlurEditorText(() => this._container.classList.remove('focused')));
|
||||
|
||||
// Toggle scroll decoration on the toolbar
|
||||
this._store.add(this.#input.onDidScrollChange(e => {
|
||||
this.#toolbarContainer.classList.toggle('fake-scroll-decoration', e.scrollTop > 0);
|
||||
this._store.add(this._input.onDidScrollChange(e => {
|
||||
this._toolbarContainer.classList.toggle('fake-scroll-decoration', e.scrollTop > 0);
|
||||
}));
|
||||
|
||||
|
||||
// Track input text for context key and adjust width based on content
|
||||
const inputHasText = CTX_INLINE_CHAT_INPUT_HAS_TEXT.bindTo(this.#contextKeyService);
|
||||
this._store.add(this.#input.onDidChangeModelContent(() => {
|
||||
inputHasText.set(this.#input.getModel().getValue().trim().length > 0);
|
||||
const inputHasText = CTX_INLINE_CHAT_INPUT_HAS_TEXT.bindTo(this._contextKeyService);
|
||||
this._store.add(this._input.onDidChangeModelContent(() => {
|
||||
inputHasText.set(this._input.getModel().getValue().trim().length > 0);
|
||||
}));
|
||||
this._store.add(toDisposable(() => inputHasText.reset()));
|
||||
|
||||
// Track focus state
|
||||
const inputWidgetFocused = CTX_INLINE_CHAT_INPUT_WIDGET_FOCUSED.bindTo(this.#contextKeyService);
|
||||
this._store.add(this.#input.onDidFocusEditorText(() => inputWidgetFocused.set(true)));
|
||||
this._store.add(this.#input.onDidBlurEditorText(() => inputWidgetFocused.set(false)));
|
||||
const inputWidgetFocused = CTX_INLINE_CHAT_INPUT_WIDGET_FOCUSED.bindTo(this._contextKeyService);
|
||||
this._store.add(this._input.onDidFocusEditorText(() => inputWidgetFocused.set(true)));
|
||||
this._store.add(this._input.onDidBlurEditorText(() => inputWidgetFocused.set(false)));
|
||||
this._store.add(toDisposable(() => inputWidgetFocused.reset()));
|
||||
|
||||
// Handle key events: ArrowDown to move to actions
|
||||
this._store.add(this.#input.onKeyDown(e => {
|
||||
this._store.add(this._input.onKeyDown(e => {
|
||||
if (e.keyCode === KeyCode.DownArrow && !actionBar.isEmpty()) {
|
||||
const model = this.#input.getModel();
|
||||
const position = this.#input.getPosition();
|
||||
const model = this._input.getModel();
|
||||
const position = this._input.getPosition();
|
||||
if (position && position.lineNumber === model.getLineCount()) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
@@ -244,18 +237,18 @@ export class InlineChatInputWidget extends Disposable {
|
||||
if (firstItem?.element && dom.isAncestorOfActiveElement(firstItem.element)) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
this.#input.focus();
|
||||
this._input.focus();
|
||||
}
|
||||
}
|
||||
}, true));
|
||||
|
||||
// Track focus - hide when focus leaves
|
||||
const focusTracker = this._store.add(dom.trackFocus(this.#domNode));
|
||||
const focusTracker = this._store.add(dom.trackFocus(this._domNode));
|
||||
this._store.add(focusTracker.onDidBlur(() => this.hide()));
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.#input.getModel().getValue().trim();
|
||||
return this._input.getModel().getValue().trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -265,77 +258,77 @@ export class InlineChatInputWidget extends Disposable {
|
||||
* @param anchorAbove Whether to anchor above the position (widget grows upward)
|
||||
*/
|
||||
show(lineNumber: number, left: number, anchorAbove: boolean, placeholder: string): void {
|
||||
this.#showStore.clear();
|
||||
this._showStore.clear();
|
||||
|
||||
// Clear input state
|
||||
this.#input.updateOptions({ wordWrap: 'off', placeholder });
|
||||
this.#input.getModel().setValue('');
|
||||
this._input.updateOptions({ wordWrap: 'off', placeholder });
|
||||
this._input.getModel().setValue('');
|
||||
|
||||
// Store anchor info for scroll updates
|
||||
this.#anchorLineNumber = lineNumber;
|
||||
this.#anchorLeft = left;
|
||||
this.#anchorAbove = anchorAbove;
|
||||
this._anchorLineNumber = lineNumber;
|
||||
this._anchorLeft = left;
|
||||
this._anchorAbove = anchorAbove;
|
||||
|
||||
// Set initial position
|
||||
this.#updatePosition();
|
||||
this._updatePosition();
|
||||
|
||||
// Create overlay widget via observable pattern
|
||||
this.#showStore.add(this.#editorObs.createOverlayWidget({
|
||||
domNode: this.#domNode,
|
||||
position: this.#position,
|
||||
this._showStore.add(this._editorObs.createOverlayWidget({
|
||||
domNode: this._domNode,
|
||||
position: this._position,
|
||||
minContentWidthInPx: constObservable(0),
|
||||
allowEditorOverflow: true,
|
||||
}));
|
||||
|
||||
// If anchoring above, adjust position after render to account for widget height
|
||||
if (anchorAbove) {
|
||||
this.#updatePosition();
|
||||
this._updatePosition();
|
||||
}
|
||||
|
||||
// Update position on scroll, hide if anchor line is out of view (only when input is empty)
|
||||
this.#showStore.add(this.#editorObs.editor.onDidScrollChange(() => {
|
||||
const visibleRanges = this.#editorObs.editor.getVisibleRanges();
|
||||
this._showStore.add(this._editorObs.editor.onDidScrollChange(() => {
|
||||
const visibleRanges = this._editorObs.editor.getVisibleRanges();
|
||||
const isLineVisible = visibleRanges.some(range =>
|
||||
this.#anchorLineNumber >= range.startLineNumber && this.#anchorLineNumber <= range.endLineNumber
|
||||
this._anchorLineNumber >= range.startLineNumber && this._anchorLineNumber <= range.endLineNumber
|
||||
);
|
||||
const hasContent = !!this.#input.getModel().getValue();
|
||||
const hasContent = !!this._input.getModel().getValue();
|
||||
if (!isLineVisible && !hasContent) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.#updatePosition();
|
||||
this._updatePosition();
|
||||
}
|
||||
}));
|
||||
|
||||
// Focus the input editor
|
||||
setTimeout(() => this.#input.focus(), 0);
|
||||
setTimeout(() => this._input.focus(), 0);
|
||||
}
|
||||
|
||||
#updatePosition(): void {
|
||||
const editor = this.#editorObs.editor;
|
||||
private _updatePosition(): void {
|
||||
const editor = this._editorObs.editor;
|
||||
const lineHeight = editor.getOption(EditorOption.lineHeight);
|
||||
const top = editor.getTopForLineNumber(this.#anchorLineNumber) - editor.getScrollTop();
|
||||
const top = editor.getTopForLineNumber(this._anchorLineNumber) - editor.getScrollTop();
|
||||
let adjustedTop = top;
|
||||
|
||||
if (this.#anchorAbove) {
|
||||
const widgetHeight = this.#domNode.offsetHeight;
|
||||
if (this._anchorAbove) {
|
||||
const widgetHeight = this._domNode.offsetHeight;
|
||||
adjustedTop = top - widgetHeight;
|
||||
} else {
|
||||
adjustedTop = top + lineHeight;
|
||||
}
|
||||
|
||||
// Clamp to viewport bounds when anchor line is out of view
|
||||
const stickyScrollHeight = this.#stickyScrollHeight.get();
|
||||
const stickyScrollHeight = this._stickyScrollHeight.get();
|
||||
const layoutInfo = editor.getLayoutInfo();
|
||||
const widgetHeight = this.#domNode.offsetHeight;
|
||||
const widgetHeight = this._domNode.offsetHeight;
|
||||
const minTop = stickyScrollHeight;
|
||||
const maxTop = layoutInfo.height - widgetHeight;
|
||||
|
||||
const clampedTop = Math.max(minTop, Math.min(adjustedTop, maxTop));
|
||||
const isClamped = clampedTop !== adjustedTop;
|
||||
this.#domNode.classList.toggle('clamped', isClamped);
|
||||
this._domNode.classList.toggle('clamped', isClamped);
|
||||
|
||||
this.#position.set({
|
||||
preference: { top: clampedTop, left: this.#anchorLeft },
|
||||
this._position.set({
|
||||
preference: { top: clampedTop, left: this._anchorLeft },
|
||||
stackOrdinal: 10000,
|
||||
}, undefined);
|
||||
}
|
||||
@@ -345,13 +338,13 @@ export class InlineChatInputWidget extends Disposable {
|
||||
*/
|
||||
hide(): void {
|
||||
// Focus editor if focus is still within the editor's DOM
|
||||
const editorDomNode = this.#editorObs.editor.getDomNode();
|
||||
const editorDomNode = this._editorObs.editor.getDomNode();
|
||||
if (editorDomNode && dom.isAncestorOfActiveElement(editorDomNode)) {
|
||||
this.#editorObs.editor.focus();
|
||||
this._editorObs.editor.focus();
|
||||
}
|
||||
this.#position.set(null, undefined);
|
||||
this.#input.getModel().setValue('');
|
||||
this.#showStore.clear();
|
||||
this._position.set(null, undefined);
|
||||
this._input.getModel().setValue('');
|
||||
this._showStore.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -360,62 +353,52 @@ export class InlineChatInputWidget extends Disposable {
|
||||
*/
|
||||
export class InlineChatSessionOverlayWidget extends Disposable {
|
||||
|
||||
readonly #domNode: HTMLElement = document.createElement('div');
|
||||
readonly #container: HTMLElement;
|
||||
readonly #statusNode: HTMLElement;
|
||||
readonly #icon: HTMLElement;
|
||||
readonly #message: HTMLElement;
|
||||
readonly #toolbarNode: HTMLElement;
|
||||
private readonly _domNode: HTMLElement = document.createElement('div');
|
||||
private readonly _container: HTMLElement;
|
||||
private readonly _statusNode: HTMLElement;
|
||||
private readonly _icon: HTMLElement;
|
||||
private readonly _message: HTMLElement;
|
||||
private readonly _toolbarNode: HTMLElement;
|
||||
|
||||
readonly #showStore = this._store.add(new DisposableStore());
|
||||
readonly #position = observableValue<IOverlayWidgetPosition | null>(this, null);
|
||||
readonly #minContentWidthInPx = constObservable(0);
|
||||
private readonly _showStore = this._store.add(new DisposableStore());
|
||||
private readonly _position = observableValue<IOverlayWidgetPosition | null>(this, null);
|
||||
private readonly _minContentWidthInPx = constObservable(0);
|
||||
|
||||
readonly #stickyScrollHeight: IObservable<number>;
|
||||
|
||||
readonly #editorObs: ObservableCodeEditor;
|
||||
readonly #instaService: IInstantiationService;
|
||||
readonly #keybindingService: IKeybindingService;
|
||||
readonly #logService: ILogService;
|
||||
private readonly _stickyScrollHeight: IObservable<number>;
|
||||
|
||||
constructor(
|
||||
editorObs: ObservableCodeEditor,
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@ILogService logService: ILogService,
|
||||
private readonly _editorObs: ObservableCodeEditor,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@ILogService private readonly _logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.#editorObs = editorObs;
|
||||
this.#instaService = instaService;
|
||||
this.#keybindingService = keybindingService;
|
||||
this.#logService = logService;
|
||||
this._domNode.classList.add('inline-chat-session-overlay-widget');
|
||||
|
||||
this.#domNode.classList.add('inline-chat-session-overlay-widget');
|
||||
|
||||
this.#container = document.createElement('div');
|
||||
this.#domNode.appendChild(this.#container);
|
||||
this.#container.classList.add('inline-chat-session-overlay-container');
|
||||
this._container = document.createElement('div');
|
||||
this._domNode.appendChild(this._container);
|
||||
this._container.classList.add('inline-chat-session-overlay-container');
|
||||
|
||||
// Create status node with icon and message
|
||||
this.#statusNode = document.createElement('div');
|
||||
this.#statusNode.classList.add('status');
|
||||
this.#icon = dom.append(this.#statusNode, dom.$('span'));
|
||||
this.#message = dom.append(this.#statusNode, dom.$('span.message'));
|
||||
this.#container.appendChild(this.#statusNode);
|
||||
this._statusNode = document.createElement('div');
|
||||
this._statusNode.classList.add('status');
|
||||
this._icon = dom.append(this._statusNode, dom.$('span'));
|
||||
this._message = dom.append(this._statusNode, dom.$('span.message'));
|
||||
this._container.appendChild(this._statusNode);
|
||||
|
||||
// Create toolbar node
|
||||
this.#toolbarNode = document.createElement('div');
|
||||
this.#toolbarNode.classList.add('toolbar');
|
||||
this._toolbarNode = document.createElement('div');
|
||||
this._toolbarNode.classList.add('toolbar');
|
||||
|
||||
// Initialize sticky scroll height observable
|
||||
const stickyScrollController = StickyScrollController.get(this.#editorObs.editor);
|
||||
this.#stickyScrollHeight = stickyScrollController ? observableFromEvent(stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight) : constObservable(0);
|
||||
const stickyScrollController = StickyScrollController.get(this._editorObs.editor);
|
||||
this._stickyScrollHeight = stickyScrollController ? observableFromEvent(stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight) : constObservable(0);
|
||||
}
|
||||
|
||||
show(session: IInlineChatSession2): void {
|
||||
assertType(this.#editorObs.editor.hasModel());
|
||||
this.#showStore.clear();
|
||||
assertType(this._editorObs.editor.hasModel());
|
||||
this._showStore.clear();
|
||||
|
||||
// Derived entry observable for this session
|
||||
const entry = derived(r => session.editingSession.readEntry(session.uri, r));
|
||||
@@ -475,34 +458,34 @@ export class InlineChatSessionOverlayWidget extends Disposable {
|
||||
}
|
||||
});
|
||||
|
||||
this.#showStore.add(autorun(r => {
|
||||
this._showStore.add(autorun(r => {
|
||||
const value = requestMessage.read(r);
|
||||
if (value) {
|
||||
this.#message.innerText = renderAsPlaintext(value.message);
|
||||
this.#icon.className = '';
|
||||
this.#icon.classList.add(...ThemeIcon.asClassNameArray(value.icon));
|
||||
this._message.innerText = renderAsPlaintext(value.message);
|
||||
this._icon.className = '';
|
||||
this._icon.classList.add(...ThemeIcon.asClassNameArray(value.icon));
|
||||
} else {
|
||||
this.#message.innerText = '';
|
||||
this.#icon.className = '';
|
||||
this._message.innerText = '';
|
||||
this._icon.className = '';
|
||||
}
|
||||
}));
|
||||
|
||||
// Log when pending confirmation changes
|
||||
this.#showStore.add(autorun(r => {
|
||||
this._showStore.add(autorun(r => {
|
||||
const response = session.chatModel.lastRequestObs.read(r)?.response;
|
||||
const pending = response?.isPendingConfirmation.read(r);
|
||||
if (pending) {
|
||||
this.#logService.info(`[InlineChat] UNEXPECTED approval needed: ${pending.detail ?? 'unknown'}`);
|
||||
this._logService.info(`[InlineChat] UNEXPECTED approval needed: ${pending.detail ?? 'unknown'}`);
|
||||
}
|
||||
}));
|
||||
|
||||
// Add toolbar
|
||||
this.#container.appendChild(this.#toolbarNode);
|
||||
this.#showStore.add(toDisposable(() => this.#toolbarNode.remove()));
|
||||
this._container.appendChild(this._toolbarNode);
|
||||
this._showStore.add(toDisposable(() => this._toolbarNode.remove()));
|
||||
|
||||
const that = this;
|
||||
|
||||
this.#showStore.add(this.#instaService.createInstance(MenuWorkbenchToolBar, this.#toolbarNode, MenuId.ChatEditorInlineExecute, {
|
||||
this._showStore.add(this._instaService.createInstance(MenuWorkbenchToolBar, this._toolbarNode, MenuId.ChatEditorInlineExecute, {
|
||||
telemetrySource: 'inlineChatProgress.overlayToolbar',
|
||||
hiddenItemStrategy: HiddenItemStrategy.Ignore,
|
||||
toolbarOptions: {
|
||||
@@ -518,52 +501,52 @@ export class InlineChatSessionOverlayWidget extends Disposable {
|
||||
return undefined; // use default action view item with label
|
||||
}
|
||||
|
||||
return new ChatEditingAcceptRejectActionViewItem(action, { ...options, keybinding: undefined }, entry, undefined, that.#keybindingService, primaryActions);
|
||||
return new ChatEditingAcceptRejectActionViewItem(action, { ...options, keybinding: undefined }, entry, undefined, that._keybindingService, primaryActions);
|
||||
}
|
||||
}));
|
||||
|
||||
// Position in top right of editor, below sticky scroll
|
||||
const lineHeight = this.#editorObs.getOption(EditorOption.lineHeight);
|
||||
const lineHeight = this._editorObs.getOption(EditorOption.lineHeight);
|
||||
|
||||
// Track widget width changes
|
||||
const widgetWidth = observableValue<number>(this, 0);
|
||||
const resizeObserver = new dom.DisposableResizeObserver(() => {
|
||||
widgetWidth.set(this.#domNode.offsetWidth, undefined);
|
||||
widgetWidth.set(this._domNode.offsetWidth, undefined);
|
||||
});
|
||||
this.#showStore.add(resizeObserver);
|
||||
this.#showStore.add(resizeObserver.observe(this.#domNode));
|
||||
this._showStore.add(resizeObserver);
|
||||
this._showStore.add(resizeObserver.observe(this._domNode));
|
||||
|
||||
this.#showStore.add(autorun(r => {
|
||||
const layoutInfo = this.#editorObs.layoutInfo.read(r);
|
||||
const stickyScrollHeight = this.#stickyScrollHeight.read(r);
|
||||
this._showStore.add(autorun(r => {
|
||||
const layoutInfo = this._editorObs.layoutInfo.read(r);
|
||||
const stickyScrollHeight = this._stickyScrollHeight.read(r);
|
||||
const width = widgetWidth.read(r);
|
||||
const padding = Math.round(lineHeight.read(r) * 2 / 3);
|
||||
|
||||
// Cap max-width to the editor viewport (content area)
|
||||
const maxWidth = layoutInfo.contentWidth - 2 * padding;
|
||||
this.#domNode.style.maxWidth = `${maxWidth}px`;
|
||||
this._domNode.style.maxWidth = `${maxWidth}px`;
|
||||
|
||||
// Position: top right, below sticky scroll with padding, left of minimap and scrollbar
|
||||
const top = stickyScrollHeight + padding;
|
||||
const left = layoutInfo.width - width - layoutInfo.verticalScrollbarWidth - layoutInfo.minimap.minimapWidth - padding;
|
||||
|
||||
this.#position.set({
|
||||
this._position.set({
|
||||
preference: { top, left },
|
||||
stackOrdinal: 10000,
|
||||
}, undefined);
|
||||
}));
|
||||
|
||||
// Create overlay widget
|
||||
this.#showStore.add(this.#editorObs.createOverlayWidget({
|
||||
domNode: this.#domNode,
|
||||
position: this.#position,
|
||||
minContentWidthInPx: this.#minContentWidthInPx,
|
||||
this._showStore.add(this._editorObs.createOverlayWidget({
|
||||
domNode: this._domNode,
|
||||
position: this._position,
|
||||
minContentWidthInPx: this._minContentWidthInPx,
|
||||
allowEditorOverflow: false,
|
||||
}));
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
this.#position.set(null, undefined);
|
||||
this.#showStore.clear();
|
||||
this._position.set(null, undefined);
|
||||
this._showStore.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,58 +41,55 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
|
||||
declare _serviceBrand: undefined;
|
||||
|
||||
readonly #store = new DisposableStore();
|
||||
readonly #sessions = new ResourceMap<IInlineChatSession2>();
|
||||
private readonly _store = new DisposableStore();
|
||||
private readonly _sessions = new ResourceMap<IInlineChatSession2>();
|
||||
|
||||
readonly #onWillStartSession = this.#store.add(new Emitter<IActiveCodeEditor>());
|
||||
readonly onWillStartSession: Event<IActiveCodeEditor> = this.#onWillStartSession.event;
|
||||
private readonly _onWillStartSession = this._store.add(new Emitter<IActiveCodeEditor>());
|
||||
readonly onWillStartSession: Event<IActiveCodeEditor> = this._onWillStartSession.event;
|
||||
|
||||
readonly #onDidChangeSessions = this.#store.add(new Emitter<this>());
|
||||
readonly onDidChangeSessions: Event<this> = this.#onDidChangeSessions.event;
|
||||
|
||||
readonly #chatService: IChatService;
|
||||
private readonly _onDidChangeSessions = this._store.add(new Emitter<this>());
|
||||
readonly onDidChangeSessions: Event<this> = this._onDidChangeSessions.event;
|
||||
|
||||
constructor(
|
||||
@IChatService chatService: IChatService,
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
@IChatAgentService chatAgentService: IChatAgentService,
|
||||
) {
|
||||
this.#chatService = chatService;
|
||||
// Listen for agent changes and dispose all sessions when there is no agent
|
||||
const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline));
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const agent = agentObs.read(r);
|
||||
if (!agent) {
|
||||
// No agent available, dispose all sessions
|
||||
dispose(this.#sessions.values());
|
||||
this.#sessions.clear();
|
||||
dispose(this._sessions.values());
|
||||
this._sessions.clear();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#store.dispose();
|
||||
this._store.dispose();
|
||||
}
|
||||
|
||||
|
||||
createSession(editor: IActiveCodeEditor): IInlineChatSession2 {
|
||||
const uri = editor.getModel().uri;
|
||||
|
||||
if (this.#sessions.has(uri)) {
|
||||
if (this._sessions.has(uri)) {
|
||||
throw new Error('Session already exists');
|
||||
}
|
||||
|
||||
this.#onWillStartSession.fire(editor);
|
||||
this._onWillStartSession.fire(editor);
|
||||
|
||||
const chatModelRef = this.#chatService.startNewLocalSession(ChatAgentLocation.EditorInline, { canUseTools: false /* SEE https://github.com/microsoft/vscode/issues/279946 */ });
|
||||
const chatModelRef = this._chatService.startNewLocalSession(ChatAgentLocation.EditorInline, { canUseTools: false /* SEE https://github.com/microsoft/vscode/issues/279946 */ });
|
||||
const chatModel = chatModelRef.object;
|
||||
chatModel.startEditingSession(false);
|
||||
|
||||
const store = new DisposableStore();
|
||||
store.add(toDisposable(() => {
|
||||
this.#chatService.cancelCurrentRequestForSession(chatModel.sessionResource, 'inlineChatSession');
|
||||
this._chatService.cancelCurrentRequestForSession(chatModel.sessionResource, 'inlineChatSession');
|
||||
chatModel.editingSession?.reject();
|
||||
this.#sessions.delete(uri);
|
||||
this.#onDidChangeSessions.fire(this);
|
||||
this._sessions.delete(uri);
|
||||
this._onDidChangeSessions.fire(this);
|
||||
}));
|
||||
store.add(chatModelRef);
|
||||
|
||||
@@ -107,7 +104,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
if (state === ModifiedFileEntryState.Accepted || state === ModifiedFileEntryState.Rejected) {
|
||||
const response = chatModel.getRequests().at(-1)?.response;
|
||||
if (response) {
|
||||
this.#chatService.notifyUserAction({
|
||||
this._chatService.notifyUserAction({
|
||||
sessionResource: response.session.sessionResource,
|
||||
requestId: response.requestId,
|
||||
agentId: response.agent?.id,
|
||||
@@ -141,16 +138,16 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
editingSession: chatModel.editingSession!,
|
||||
dispose: store.dispose.bind(store)
|
||||
};
|
||||
this.#sessions.set(uri, result);
|
||||
this.#onDidChangeSessions.fire(this);
|
||||
this._sessions.set(uri, result);
|
||||
this._onDidChangeSessions.fire(this);
|
||||
return result;
|
||||
}
|
||||
|
||||
getSessionByTextModel(uri: URI): IInlineChatSession2 | undefined {
|
||||
let result = this.#sessions.get(uri);
|
||||
let result = this._sessions.get(uri);
|
||||
if (!result) {
|
||||
// no direct session, try to find an editing session which has a file entry for the uri
|
||||
for (const [_, candidate] of this.#sessions) {
|
||||
for (const [_, candidate] of this._sessions) {
|
||||
const entry = candidate.editingSession.getEntry(uri);
|
||||
if (entry) {
|
||||
result = candidate;
|
||||
@@ -162,7 +159,7 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
}
|
||||
|
||||
getSessionBySessionUri(sessionResource: URI): IInlineChatSession2 | undefined {
|
||||
for (const session of this.#sessions.values()) {
|
||||
for (const session of this._sessions.values()) {
|
||||
if (isEqual(session.chatModel.sessionResource, sessionResource)) {
|
||||
return session;
|
||||
}
|
||||
@@ -175,11 +172,11 @@ export class InlineChatEnabler {
|
||||
|
||||
static Id = 'inlineChat.enabler';
|
||||
|
||||
readonly #ctxHasProvider2: IContextKey<boolean>;
|
||||
readonly #ctxHasNotebookProvider: IContextKey<boolean>;
|
||||
readonly #ctxPossible: IContextKey<boolean>;
|
||||
private readonly _ctxHasProvider2: IContextKey<boolean>;
|
||||
private readonly _ctxHasNotebookProvider: IContextKey<boolean>;
|
||||
private readonly _ctxPossible: IContextKey<boolean>;
|
||||
|
||||
readonly #store = new DisposableStore();
|
||||
private readonly _store = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@@ -187,41 +184,41 @@ export class InlineChatEnabler {
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IConfigurationService configService: IConfigurationService,
|
||||
) {
|
||||
this.#ctxHasProvider2 = CTX_INLINE_CHAT_HAS_AGENT2.bindTo(contextKeyService);
|
||||
this.#ctxHasNotebookProvider = CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT.bindTo(contextKeyService);
|
||||
this.#ctxPossible = CTX_INLINE_CHAT_POSSIBLE.bindTo(contextKeyService);
|
||||
this._ctxHasProvider2 = CTX_INLINE_CHAT_HAS_AGENT2.bindTo(contextKeyService);
|
||||
this._ctxHasNotebookProvider = CTX_INLINE_CHAT_HAS_NOTEBOOK_AGENT.bindTo(contextKeyService);
|
||||
this._ctxPossible = CTX_INLINE_CHAT_POSSIBLE.bindTo(contextKeyService);
|
||||
|
||||
const agentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.EditorInline));
|
||||
const notebookAgentObs = observableFromEvent(this, chatAgentService.onDidChangeAgents, () => chatAgentService.getDefaultAgent(ChatAgentLocation.Notebook));
|
||||
const notebookAgentConfigObs = observableConfigValue(InlineChatConfigKeys.notebookAgent, false, configService);
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this._store.add(autorun(r => {
|
||||
const agent = agentObs.read(r);
|
||||
if (!agent) {
|
||||
this.#ctxHasProvider2.reset();
|
||||
this._ctxHasProvider2.reset();
|
||||
} else {
|
||||
this.#ctxHasProvider2.set(true);
|
||||
this._ctxHasProvider2.set(true);
|
||||
}
|
||||
}));
|
||||
|
||||
this.#store.add(autorun(r => {
|
||||
this.#ctxHasNotebookProvider.set(notebookAgentConfigObs.read(r) && !!notebookAgentObs.read(r));
|
||||
this._store.add(autorun(r => {
|
||||
this._ctxHasNotebookProvider.set(notebookAgentConfigObs.read(r) && !!notebookAgentObs.read(r));
|
||||
}));
|
||||
|
||||
const updateEditor = () => {
|
||||
const ctrl = editorService.activeEditorPane?.getControl();
|
||||
const isCodeEditorLike = isCodeEditor(ctrl) || isDiffEditor(ctrl) || isCompositeEditor(ctrl);
|
||||
this.#ctxPossible.set(isCodeEditorLike);
|
||||
this._ctxPossible.set(isCodeEditorLike);
|
||||
};
|
||||
|
||||
this.#store.add(editorService.onDidActiveEditorChange(updateEditor));
|
||||
this._store.add(editorService.onDidActiveEditorChange(updateEditor));
|
||||
updateEditor();
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.#ctxPossible.reset();
|
||||
this.#ctxHasProvider2.reset();
|
||||
this.#store.dispose();
|
||||
this._ctxPossible.reset();
|
||||
this._ctxHasProvider2.reset();
|
||||
this._store.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,7 +229,7 @@ export class InlineChatEscapeToolContribution extends Disposable {
|
||||
|
||||
static readonly DONT_ASK_AGAIN_KEY = 'inlineChat.dontAskMoveToPanelChat';
|
||||
|
||||
static readonly #data: IToolData = {
|
||||
private static readonly _data: IToolData = {
|
||||
id: 'inline_chat_exit',
|
||||
source: ToolDataSource.Internal,
|
||||
canBeReferencedInPrompt: false,
|
||||
@@ -254,7 +251,7 @@ export class InlineChatEscapeToolContribution extends Disposable {
|
||||
|
||||
super();
|
||||
|
||||
this._store.add(lmTools.registerTool(InlineChatEscapeToolContribution.#data, {
|
||||
this._store.add(lmTools.registerTool(InlineChatEscapeToolContribution._data, {
|
||||
invoke: async (invocation, _tokenCountFn, _progress, _token) => {
|
||||
|
||||
const sessionResource = invocation.context?.sessionResource;
|
||||
|
||||
@@ -95,59 +95,37 @@ export class InlineChatWidget {
|
||||
|
||||
protected readonly _store = new DisposableStore();
|
||||
|
||||
readonly #ctxInputEditorFocused: IContextKey<boolean>;
|
||||
readonly #ctxResponseFocused: IContextKey<boolean>;
|
||||
private readonly _ctxInputEditorFocused: IContextKey<boolean>;
|
||||
private readonly _ctxResponseFocused: IContextKey<boolean>;
|
||||
|
||||
readonly #chatWidget: ChatWidget;
|
||||
private readonly _chatWidget: ChatWidget;
|
||||
|
||||
protected readonly _onDidChangeHeight = this._store.add(new Emitter<void>());
|
||||
readonly onDidChangeHeight: Event<void> = Event.filter(this._onDidChangeHeight.event, _ => !this.#isLayouting);
|
||||
readonly onDidChangeHeight: Event<void> = Event.filter(this._onDidChangeHeight.event, _ => !this._isLayouting);
|
||||
|
||||
readonly #requestInProgress = observableValue(this, false);
|
||||
readonly requestInProgress: IObservable<boolean> = this.#requestInProgress;
|
||||
private readonly _requestInProgress = observableValue(this, false);
|
||||
readonly requestInProgress: IObservable<boolean> = this._requestInProgress;
|
||||
|
||||
#isLayouting: boolean = false;
|
||||
private _isLayouting: boolean = false;
|
||||
|
||||
readonly scopedContextKeyService: IContextKeyService;
|
||||
|
||||
readonly #options: IInlineChatWidgetConstructionOptions;
|
||||
readonly #contextKeyService: IContextKeyService;
|
||||
readonly #keybindingService: IKeybindingService;
|
||||
readonly #accessibilityService: IAccessibilityService;
|
||||
readonly #configurationService: IConfigurationService;
|
||||
readonly #accessibleViewService: IAccessibleViewService;
|
||||
readonly #chatService: IChatService;
|
||||
readonly #hoverService: IHoverService;
|
||||
readonly #chatEntitlementService: IChatEntitlementService;
|
||||
readonly #markdownRendererService: IMarkdownRendererService;
|
||||
|
||||
constructor(
|
||||
location: IChatWidgetLocationOptions,
|
||||
options: IInlineChatWidgetConstructionOptions,
|
||||
private readonly _options: IInlineChatWidgetConstructionOptions,
|
||||
@IInstantiationService protected readonly _instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IAccessibilityService accessibilityService: IAccessibilityService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IAccessibleViewService accessibleViewService: IAccessibleViewService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IKeybindingService private readonly _keybindingService: IKeybindingService,
|
||||
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
|
||||
@IConfigurationService private readonly _configurationService: IConfigurationService,
|
||||
@IAccessibleViewService private readonly _accessibleViewService: IAccessibleViewService,
|
||||
@ITextModelService protected readonly _textModelResolverService: ITextModelService,
|
||||
@IChatService chatService: IChatService,
|
||||
@IHoverService hoverService: IHoverService,
|
||||
@IChatEntitlementService chatEntitlementService: IChatEntitlementService,
|
||||
@IMarkdownRendererService markdownRendererService: IMarkdownRendererService,
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
@IHoverService private readonly _hoverService: IHoverService,
|
||||
@IChatEntitlementService private readonly _chatEntitlementService: IChatEntitlementService,
|
||||
@IMarkdownRendererService private readonly _markdownRendererService: IMarkdownRendererService,
|
||||
) {
|
||||
this.#options = options;
|
||||
this.#contextKeyService = contextKeyService;
|
||||
this.#keybindingService = keybindingService;
|
||||
this.#accessibilityService = accessibilityService;
|
||||
this.#configurationService = configurationService;
|
||||
this.#accessibleViewService = accessibleViewService;
|
||||
this.#chatService = chatService;
|
||||
this.#hoverService = hoverService;
|
||||
this.#chatEntitlementService = chatEntitlementService;
|
||||
this.#markdownRendererService = markdownRendererService;
|
||||
|
||||
this.scopedContextKeyService = this._store.add(contextKeyService.createScoped(this._elements.chatWidget));
|
||||
this.scopedContextKeyService = this._store.add(_contextKeyService.createScoped(this._elements.chatWidget));
|
||||
const scopedInstaService = _instantiationService.createChild(
|
||||
new ServiceCollection([
|
||||
IContextKeyService,
|
||||
@@ -156,7 +134,7 @@ export class InlineChatWidget {
|
||||
this._store
|
||||
);
|
||||
|
||||
this.#chatWidget = scopedInstaService.createInstance(
|
||||
this._chatWidget = scopedInstaService.createInstance(
|
||||
ChatWidget,
|
||||
location,
|
||||
{ isInlineChat: true },
|
||||
@@ -176,14 +154,14 @@ export class InlineChatWidget {
|
||||
if (emptyResponse) {
|
||||
return false;
|
||||
}
|
||||
if (item.response.value.every(item => item.kind === 'textEditGroup' && this.#options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) {
|
||||
if (item.response.value.every(item => item.kind === 'textEditGroup' && _options.chatWidgetViewOptions?.rendererOptions?.renderTextEditsAsSummary?.(item.uri))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
dndContainer: this._elements.root,
|
||||
defaultMode: ChatMode.Ask,
|
||||
...this.#options.chatWidgetViewOptions
|
||||
..._options.chatWidgetViewOptions
|
||||
},
|
||||
{
|
||||
listForeground: inlineChatForeground,
|
||||
@@ -193,11 +171,11 @@ export class InlineChatWidget {
|
||||
resultEditorBackground: editorBackground
|
||||
}
|
||||
);
|
||||
this._elements.root.classList.toggle('in-zone-widget', !!this.#options.inZoneWidget);
|
||||
this.#chatWidget.render(this._elements.chatWidget);
|
||||
this._elements.root.classList.toggle('in-zone-widget', !!_options.inZoneWidget);
|
||||
this._chatWidget.render(this._elements.chatWidget);
|
||||
this._elements.chatWidget.style.setProperty(asCssVariableName(chatRequestBackground), asCssVariable(inlineChatBackground));
|
||||
this.#chatWidget.setVisible(true);
|
||||
this._store.add(this.#chatWidget);
|
||||
this._chatWidget.setVisible(true);
|
||||
this._store.add(this._chatWidget);
|
||||
|
||||
const ctxResponse = ChatContextKeys.isResponse.bindTo(this.scopedContextKeyService);
|
||||
const ctxResponseVote = ChatContextKeys.responseVote.bindTo(this.scopedContextKeyService);
|
||||
@@ -206,10 +184,10 @@ export class InlineChatWidget {
|
||||
const ctxResponseErrorFiltered = ChatContextKeys.responseIsFiltered.bindTo(this.scopedContextKeyService);
|
||||
|
||||
const viewModelStore = this._store.add(new DisposableStore());
|
||||
this._store.add(this.#chatWidget.onDidChangeViewModel(() => {
|
||||
this._store.add(this._chatWidget.onDidChangeViewModel(() => {
|
||||
viewModelStore.clear();
|
||||
|
||||
const viewModel = this.#chatWidget.viewModel;
|
||||
const viewModel = this._chatWidget.viewModel;
|
||||
if (!viewModel) {
|
||||
return;
|
||||
}
|
||||
@@ -225,7 +203,7 @@ export class InlineChatWidget {
|
||||
|
||||
viewModelStore.add(viewModel.onDidChange(() => {
|
||||
|
||||
this.#requestInProgress.set(viewModel.model.requestInProgress.get(), undefined);
|
||||
this._requestInProgress.set(viewModel.model.requestInProgress.get(), undefined);
|
||||
|
||||
const last = viewModel.getItems().at(-1);
|
||||
toolbar2.context = last;
|
||||
@@ -246,22 +224,22 @@ export class InlineChatWidget {
|
||||
}));
|
||||
|
||||
// context keys
|
||||
this.#ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this.#contextKeyService);
|
||||
this._ctxResponseFocused = CTX_INLINE_CHAT_RESPONSE_FOCUSED.bindTo(this._contextKeyService);
|
||||
const tracker = this._store.add(trackFocus(this.domNode));
|
||||
this._store.add(tracker.onDidBlur(() => this.#ctxResponseFocused.set(false)));
|
||||
this._store.add(tracker.onDidFocus(() => this.#ctxResponseFocused.set(true)));
|
||||
this._store.add(tracker.onDidBlur(() => this._ctxResponseFocused.set(false)));
|
||||
this._store.add(tracker.onDidFocus(() => this._ctxResponseFocused.set(true)));
|
||||
|
||||
this.#ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(this.#contextKeyService);
|
||||
this._store.add(this.#chatWidget.inputEditor.onDidFocusEditorWidget(() => this.#ctxInputEditorFocused.set(true)));
|
||||
this._store.add(this.#chatWidget.inputEditor.onDidBlurEditorWidget(() => this.#ctxInputEditorFocused.set(false)));
|
||||
this._ctxInputEditorFocused = CTX_INLINE_CHAT_FOCUSED.bindTo(_contextKeyService);
|
||||
this._store.add(this._chatWidget.inputEditor.onDidFocusEditorWidget(() => this._ctxInputEditorFocused.set(true)));
|
||||
this._store.add(this._chatWidget.inputEditor.onDidBlurEditorWidget(() => this._ctxInputEditorFocused.set(false)));
|
||||
|
||||
const statusMenuId = this.#options.statusMenuId instanceof MenuId ? this.#options.statusMenuId : this.#options.statusMenuId.menu;
|
||||
const statusMenuId = _options.statusMenuId instanceof MenuId ? _options.statusMenuId : _options.statusMenuId.menu;
|
||||
|
||||
// BUTTON bar
|
||||
const statusMenuOptions = this.#options.statusMenuId instanceof MenuId ? undefined : this.#options.statusMenuId.options;
|
||||
const statusMenuOptions = _options.statusMenuId instanceof MenuId ? undefined : _options.statusMenuId.options;
|
||||
const statusButtonBar = scopedInstaService.createInstance(MenuWorkbenchButtonBar, this._elements.toolbar1, statusMenuId, {
|
||||
toolbarOptions: { primaryGroup: '0_main' },
|
||||
telemetrySource: this.#options.chatWidgetViewOptions?.menus?.telemetrySource,
|
||||
telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,
|
||||
menuOptions: { renderShortTitle: true },
|
||||
...statusMenuOptions,
|
||||
});
|
||||
@@ -269,8 +247,8 @@ export class InlineChatWidget {
|
||||
this._store.add(statusButtonBar);
|
||||
|
||||
// secondary toolbar
|
||||
const toolbar2 = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar2, this.#options.secondaryMenuId ?? MenuId.for(''), {
|
||||
telemetrySource: this.#options.chatWidgetViewOptions?.menus?.telemetrySource,
|
||||
const toolbar2 = scopedInstaService.createInstance(MenuWorkbenchToolBar, this._elements.toolbar2, _options.secondaryMenuId ?? MenuId.for(''), {
|
||||
telemetrySource: _options.chatWidgetViewOptions?.menus?.telemetrySource,
|
||||
menuOptions: { renderShortTitle: true, shouldForwardArgs: true },
|
||||
actionViewItemProvider: (action: IAction, options: IActionViewItemOptions) => {
|
||||
if (action instanceof MenuItemAction && action.item.id === MarkUnhelpfulActionId) {
|
||||
@@ -283,60 +261,60 @@ export class InlineChatWidget {
|
||||
this._store.add(toolbar2);
|
||||
|
||||
|
||||
this._store.add(this.#configurationService.onDidChangeConfiguration(e => {
|
||||
this._store.add(this._configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(AccessibilityVerbositySettingId.InlineChat)) {
|
||||
this.#updateAriaLabel();
|
||||
this._updateAriaLabel();
|
||||
}
|
||||
}));
|
||||
|
||||
this._elements.root.tabIndex = 0;
|
||||
this._elements.statusLabel.tabIndex = 0;
|
||||
this.#updateAriaLabel();
|
||||
this.#setupDisclaimer();
|
||||
this._updateAriaLabel();
|
||||
this._setupDisclaimer();
|
||||
|
||||
this._store.add(this.#hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => {
|
||||
this._store.add(this._hoverService.setupManagedHover(getDefaultHoverDelegate('element'), this._elements.statusLabel, () => {
|
||||
return this._elements.statusLabel.dataset['title'];
|
||||
}));
|
||||
|
||||
this._store.add(this.#chatService.onDidPerformUserAction(e => {
|
||||
if (isEqual(e.sessionResource, this.#chatWidget.viewModel?.model.sessionResource) && e.action.kind === 'vote') {
|
||||
this._store.add(this._chatService.onDidPerformUserAction(e => {
|
||||
if (isEqual(e.sessionResource, this._chatWidget.viewModel?.model.sessionResource) && e.action.kind === 'vote') {
|
||||
this.updateStatus(localize('feedbackThanks', "Thank you for your feedback!"), { resetAfter: 1250 });
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
#updateAriaLabel(): void {
|
||||
private _updateAriaLabel(): void {
|
||||
|
||||
this._elements.root.ariaLabel = this.#accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat);
|
||||
this._elements.root.ariaLabel = this._accessibleViewService.getOpenAriaHint(AccessibilityVerbositySettingId.InlineChat);
|
||||
|
||||
if (this.#accessibilityService.isScreenReaderOptimized()) {
|
||||
if (this._accessibilityService.isScreenReaderOptimized()) {
|
||||
let label = defaultAriaLabel;
|
||||
if (this.#configurationService.getValue<boolean>(AccessibilityVerbositySettingId.InlineChat)) {
|
||||
const kbLabel = this.#keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();
|
||||
if (this._configurationService.getValue<boolean>(AccessibilityVerbositySettingId.InlineChat)) {
|
||||
const kbLabel = this._keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();
|
||||
label = kbLabel
|
||||
? localize('inlineChat.accessibilityHelp', "Inline Chat Input, Use {0} for Inline Chat Accessibility Help.", kbLabel)
|
||||
: localize('inlineChat.accessibilityHelpNoKb', "Inline Chat Input, Run the Inline Chat Accessibility Help command for more information.");
|
||||
}
|
||||
this.#chatWidget.inputEditor.updateOptions({ ariaLabel: label });
|
||||
this._chatWidget.inputEditor.updateOptions({ ariaLabel: label });
|
||||
}
|
||||
}
|
||||
|
||||
#setupDisclaimer(): void {
|
||||
private _setupDisclaimer(): void {
|
||||
const disposables = this._store.add(new DisposableStore());
|
||||
|
||||
this._store.add(autorun(reader => {
|
||||
disposables.clear();
|
||||
reset(this._elements.disclaimerLabel);
|
||||
|
||||
const sentiment = this.#chatEntitlementService.sentimentObs.read(reader);
|
||||
const anonymous = this.#chatEntitlementService.anonymousObs.read(reader);
|
||||
const requestInProgress = this.#chatService.requestInProgressObs.read(reader);
|
||||
const sentiment = this._chatEntitlementService.sentimentObs.read(reader);
|
||||
const anonymous = this._chatEntitlementService.anonymousObs.read(reader);
|
||||
const requestInProgress = this._chatService.requestInProgressObs.read(reader);
|
||||
|
||||
const showDisclaimer = !sentiment.installed && anonymous && !requestInProgress;
|
||||
this._elements.disclaimerLabel.classList.toggle('hidden', !showDisclaimer);
|
||||
|
||||
if (showDisclaimer) {
|
||||
const renderedMarkdown = disposables.add(this.#markdownRendererService.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true })));
|
||||
const renderedMarkdown = disposables.add(this._markdownRendererService.render(new MarkdownString(localize({ key: 'termsDisclaimer', comment: ['{Locked="]({2})"}', '{Locked="]({3})"}'] }, "By continuing with {0} Copilot, you agree to {1}'s [Terms]({2}) and [Privacy Statement]({3})", product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.provider?.default?.name ?? '', product.defaultChatAgent?.termsStatementUrl ?? '', product.defaultChatAgent?.privacyStatementUrl ?? ''), { isTrusted: true })));
|
||||
this._elements.disclaimerLabel.appendChild(renderedMarkdown.element);
|
||||
}
|
||||
|
||||
@@ -353,20 +331,20 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
get chatWidget(): ChatWidget {
|
||||
return this.#chatWidget;
|
||||
return this._chatWidget;
|
||||
}
|
||||
|
||||
saveState() {
|
||||
this.#chatWidget.saveState();
|
||||
this._chatWidget.saveState();
|
||||
}
|
||||
|
||||
layout(widgetDim: Dimension) {
|
||||
const contentHeight = this.contentHeight;
|
||||
this.#isLayouting = true;
|
||||
this._isLayouting = true;
|
||||
try {
|
||||
this._doLayout(widgetDim);
|
||||
} finally {
|
||||
this.#isLayouting = false;
|
||||
this._isLayouting = false;
|
||||
|
||||
if (this.contentHeight !== contentHeight) {
|
||||
this._onDidChangeHeight.fire();
|
||||
@@ -383,7 +361,7 @@ export class InlineChatWidget {
|
||||
this._elements.root.style.height = `${dimension.height - extraHeight}px`;
|
||||
this._elements.root.style.width = `${dimension.width}px`;
|
||||
|
||||
this.#chatWidget.layout(
|
||||
this._chatWidget.layout(
|
||||
dimension.height - statusHeight - extraHeight,
|
||||
dimension.width
|
||||
);
|
||||
@@ -394,7 +372,7 @@ export class InlineChatWidget {
|
||||
*/
|
||||
get contentHeight(): number {
|
||||
const data = {
|
||||
chatWidgetContentHeight: this.#chatWidget.contentHeight,
|
||||
chatWidgetContentHeight: this._chatWidget.contentHeight,
|
||||
statusHeight: getTotalHeight(this._elements.status),
|
||||
extraHeight: this._getExtraHeight()
|
||||
};
|
||||
@@ -407,7 +385,7 @@ export class InlineChatWidget {
|
||||
// at least "maxWidgetHeight" high and at most the content height.
|
||||
|
||||
let maxWidgetOutputHeight = 100;
|
||||
for (const item of this.#chatWidget.viewModel?.getItems() ?? []) {
|
||||
for (const item of this._chatWidget.viewModel?.getItems() ?? []) {
|
||||
if (isResponseVM(item) && item.response.value.some(r => r.kind === 'textEditGroup' && !r.state?.applied)) {
|
||||
maxWidgetOutputHeight = 270;
|
||||
break;
|
||||
@@ -415,29 +393,29 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
let value = this.contentHeight;
|
||||
value -= this.#chatWidget.contentHeight;
|
||||
value += Math.min(this.#chatWidget.input.height.get() + maxWidgetOutputHeight, this.#chatWidget.contentHeight);
|
||||
value -= this._chatWidget.contentHeight;
|
||||
value += Math.min(this._chatWidget.input.height.get() + maxWidgetOutputHeight, this._chatWidget.contentHeight);
|
||||
return value;
|
||||
}
|
||||
|
||||
protected _getExtraHeight(): number {
|
||||
return this.#options.inZoneWidget ? 1 : (2 /*border*/ + 4 /*shadow*/);
|
||||
return this._options.inZoneWidget ? 1 : (2 /*border*/ + 4 /*shadow*/);
|
||||
}
|
||||
|
||||
get value(): string {
|
||||
return this.#chatWidget.getInput();
|
||||
return this._chatWidget.getInput();
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
this.#chatWidget.setInput(value);
|
||||
this._chatWidget.setInput(value);
|
||||
}
|
||||
|
||||
selectAll() {
|
||||
this.#chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
|
||||
this._chatWidget.inputEditor.setSelection(new Selection(1, 1, Number.MAX_SAFE_INTEGER, 1));
|
||||
}
|
||||
|
||||
set placeholder(value: string) {
|
||||
this.#chatWidget.setInputPlaceholder(value);
|
||||
this._chatWidget.setInputPlaceholder(value);
|
||||
}
|
||||
|
||||
toggleStatus(show: boolean) {
|
||||
@@ -458,7 +436,7 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
async getCodeBlockInfo(codeBlockIndex: number): Promise<ITextModel | undefined> {
|
||||
const { viewModel } = this.#chatWidget;
|
||||
const { viewModel } = this._chatWidget;
|
||||
if (!viewModel) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -505,18 +483,18 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
get responseContent(): string | undefined {
|
||||
const requests = this.#chatWidget.viewModel?.model.getRequests();
|
||||
const requests = this._chatWidget.viewModel?.model.getRequests();
|
||||
return requests?.at(-1)?.response?.response.toString();
|
||||
}
|
||||
|
||||
|
||||
getChatModel(): IChatModel | undefined {
|
||||
return this.#chatWidget.viewModel?.model;
|
||||
return this._chatWidget.viewModel?.model;
|
||||
}
|
||||
|
||||
setChatModel(chatModel: IChatModel) {
|
||||
chatModel.inputModel.setState({ inputText: '', selections: [] });
|
||||
this.#chatWidget.setModel(chatModel);
|
||||
this._chatWidget.setModel(chatModel);
|
||||
}
|
||||
|
||||
updateInfo(message: string): void {
|
||||
@@ -555,8 +533,8 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.#chatWidget.attachmentModel.clear(true);
|
||||
this.#chatWidget.saveState();
|
||||
this._chatWidget.attachmentModel.clear(true);
|
||||
this._chatWidget.saveState();
|
||||
|
||||
reset(this._elements.statusLabel);
|
||||
this._elements.statusLabel.classList.toggle('hidden', true);
|
||||
@@ -569,7 +547,7 @@ export class InlineChatWidget {
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.#chatWidget.focusInput();
|
||||
this._chatWidget.focusInput();
|
||||
}
|
||||
|
||||
hasFocus() {
|
||||
|
||||
@@ -28,7 +28,7 @@ import { EditorBasedInlineChatWidget } from './inlineChatWidget.js';
|
||||
|
||||
export class InlineChatZoneWidget extends ZoneWidget {
|
||||
|
||||
static readonly #options: IOptions = {
|
||||
private static readonly _options: IOptions = {
|
||||
showFrame: true,
|
||||
frameWidth: 1,
|
||||
// frameColor: 'var(--vscode-inlineChat-border)',
|
||||
@@ -43,12 +43,9 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
|
||||
readonly widget: EditorBasedInlineChatWidget;
|
||||
|
||||
readonly #ctxCursorPosition: IContextKey<'above' | 'below' | ''>;
|
||||
#dimension?: Dimension;
|
||||
#notebookEditor?: INotebookEditor;
|
||||
|
||||
readonly #instaService: IInstantiationService;
|
||||
#logService: ILogService;
|
||||
private readonly _ctxCursorPosition: IContextKey<'above' | 'below' | ''>;
|
||||
private _dimension?: Dimension;
|
||||
private notebookEditor?: INotebookEditor;
|
||||
|
||||
constructor(
|
||||
location: IChatWidgetLocationOptions,
|
||||
@@ -56,22 +53,20 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
editors: { editor: ICodeEditor; notebookEditor?: INotebookEditor },
|
||||
/** @deprecated should go away with inline2 */
|
||||
clearDelegate: () => Promise<void>,
|
||||
@IInstantiationService instaService: IInstantiationService,
|
||||
@ILogService logService: ILogService,
|
||||
@IInstantiationService private readonly _instaService: IInstantiationService,
|
||||
@ILogService private _logService: ILogService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super(editors.editor, InlineChatZoneWidget.#options);
|
||||
this.#instaService = instaService;
|
||||
this.#logService = logService;
|
||||
this.#notebookEditor = editors.notebookEditor;
|
||||
super(editors.editor, InlineChatZoneWidget._options);
|
||||
this.notebookEditor = editors.notebookEditor;
|
||||
|
||||
this.#ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService);
|
||||
this._ctxCursorPosition = CTX_INLINE_CHAT_OUTER_CURSOR_POSITION.bindTo(contextKeyService);
|
||||
|
||||
this._disposables.add(toDisposable(() => {
|
||||
this.#ctxCursorPosition.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
}));
|
||||
|
||||
this.widget = this.#instaService.createInstance(EditorBasedInlineChatWidget, location, this.editor, {
|
||||
this.widget = this._instaService.createInstance(EditorBasedInlineChatWidget, location, this.editor, {
|
||||
statusMenuId: {
|
||||
menu: MENU_INLINE_CHAT_WIDGET_STATUS,
|
||||
options: {
|
||||
@@ -110,14 +105,14 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
let revealFn: (() => void) | undefined;
|
||||
this._disposables.add(this.widget.chatWidget.onWillMaybeChangeHeight(() => {
|
||||
if (this.position) {
|
||||
revealFn = this.#createZoneAndScrollRestoreFn(this.position);
|
||||
revealFn = this._createZoneAndScrollRestoreFn(this.position);
|
||||
}
|
||||
}));
|
||||
this._disposables.add(this.widget.onDidChangeHeight(() => {
|
||||
if (this.position && !this._usesResizeHeight) {
|
||||
// only relayout when visible
|
||||
revealFn ??= this.#createZoneAndScrollRestoreFn(this.position);
|
||||
const height = this.#computeHeight();
|
||||
revealFn ??= this._createZoneAndScrollRestoreFn(this.position);
|
||||
const height = this._computeHeight();
|
||||
this._relayout(height.linesValue);
|
||||
revealFn?.();
|
||||
revealFn = undefined;
|
||||
@@ -141,13 +136,13 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
// todo@jrieken listen ONLY when showing
|
||||
const updateCursorIsAboveContextKey = () => {
|
||||
if (!this.position || !this.editor.hasModel()) {
|
||||
this.#ctxCursorPosition.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
} else if (this.position.lineNumber === this.editor.getPosition().lineNumber) {
|
||||
this.#ctxCursorPosition.set('above');
|
||||
this._ctxCursorPosition.set('above');
|
||||
} else if (this.position.lineNumber + 1 === this.editor.getPosition().lineNumber) {
|
||||
this.#ctxCursorPosition.set('below');
|
||||
this._ctxCursorPosition.set('below');
|
||||
} else {
|
||||
this.#ctxCursorPosition.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
}
|
||||
};
|
||||
this._disposables.add(this.editor.onDidChangeCursorPosition(e => updateCursorIsAboveContextKey()));
|
||||
@@ -164,19 +159,19 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
|
||||
protected override _doLayout(heightInPixel: number): void {
|
||||
|
||||
this.#updatePadding();
|
||||
this._updatePadding();
|
||||
|
||||
const info = this.editor.getLayoutInfo();
|
||||
const width = info.contentWidth - info.verticalScrollbarWidth;
|
||||
// width = Math.min(850, width);
|
||||
|
||||
this.#dimension = new Dimension(width, heightInPixel);
|
||||
this.widget.layout(this.#dimension);
|
||||
this._dimension = new Dimension(width, heightInPixel);
|
||||
this.widget.layout(this._dimension);
|
||||
}
|
||||
|
||||
#computeHeight(): { linesValue: number; pixelsValue: number } {
|
||||
private _computeHeight(): { linesValue: number; pixelsValue: number } {
|
||||
const chatContentHeight = this.widget.contentHeight;
|
||||
const editorHeight = this.#notebookEditor?.getLayoutInfo().height ?? this.editor.getLayoutInfo().height;
|
||||
const editorHeight = this.notebookEditor?.getLayoutInfo().height ?? this.editor.getLayoutInfo().height;
|
||||
|
||||
const contentHeight = this._decoratingElementsHeight() + Math.min(chatContentHeight, Math.max(this.widget.minHeight, editorHeight * 0.42));
|
||||
const heightInLines = contentHeight / this.editor.getOption(EditorOption.lineHeight);
|
||||
@@ -197,25 +192,25 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
}
|
||||
|
||||
protected override _onWidth(_widthInPixel: number): void {
|
||||
if (this.#dimension) {
|
||||
this._doLayout(this.#dimension.height);
|
||||
if (this._dimension) {
|
||||
this._doLayout(this._dimension.height);
|
||||
}
|
||||
}
|
||||
|
||||
override show(position: Position): void {
|
||||
assertType(this.container);
|
||||
|
||||
this.#updatePadding();
|
||||
this._updatePadding();
|
||||
|
||||
const revealZone = this.#createZoneAndScrollRestoreFn(position);
|
||||
super.show(position, this.#computeHeight().linesValue);
|
||||
const revealZone = this._createZoneAndScrollRestoreFn(position);
|
||||
super.show(position, this._computeHeight().linesValue);
|
||||
this.widget.chatWidget.setVisible(true);
|
||||
this.widget.focus();
|
||||
|
||||
revealZone();
|
||||
}
|
||||
|
||||
#updatePadding() {
|
||||
private _updatePadding() {
|
||||
assertType(this.container);
|
||||
|
||||
const info = this.editor.getLayoutInfo();
|
||||
@@ -231,12 +226,12 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
}
|
||||
|
||||
override updatePositionAndHeight(position: Position): void {
|
||||
const revealZone = this.#createZoneAndScrollRestoreFn(position);
|
||||
super.updatePositionAndHeight(position, !this._usesResizeHeight ? this.#computeHeight().linesValue : undefined);
|
||||
const revealZone = this._createZoneAndScrollRestoreFn(position);
|
||||
super.updatePositionAndHeight(position, !this._usesResizeHeight ? this._computeHeight().linesValue : undefined);
|
||||
revealZone();
|
||||
}
|
||||
|
||||
#createZoneAndScrollRestoreFn(position: Position): () => void {
|
||||
private _createZoneAndScrollRestoreFn(position: Position): () => void {
|
||||
|
||||
const scrollState = StableEditorBottomScrollState.capture(this.editor);
|
||||
|
||||
@@ -247,7 +242,7 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
|
||||
const scrollTop = this.editor.getScrollTop();
|
||||
const lineTop = this.editor.getTopForLineNumber(lineNumber);
|
||||
const zoneTop = lineTop - this.#computeHeight().pixelsValue;
|
||||
const zoneTop = lineTop - this._computeHeight().pixelsValue;
|
||||
const editorHeight = this.editor.getLayoutInfo().height;
|
||||
const lineBottom = this.editor.getBottomForLineNumber(lineNumber);
|
||||
|
||||
@@ -262,7 +257,7 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
}
|
||||
|
||||
if (newScrollTop < scrollTop || forceScrollTop) {
|
||||
this.#logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop });
|
||||
this._logService.trace('[IE] REVEAL zone', { zoneTop, lineTop, lineBottom, scrollTop, newScrollTop, forceScrollTop });
|
||||
this.editor.setScrollTop(newScrollTop, ScrollType.Immediate);
|
||||
}
|
||||
};
|
||||
@@ -274,7 +269,7 @@ export class InlineChatZoneWidget extends ZoneWidget {
|
||||
|
||||
override hide(): void {
|
||||
const scrollState = StableEditorBottomScrollState.capture(this.editor);
|
||||
this.#ctxCursorPosition.reset();
|
||||
this._ctxCursorPosition.reset();
|
||||
this.widget.chatWidget.setVisible(false);
|
||||
super.hide();
|
||||
aria.status(localize('inlineChatClosed', 'Closed inline chat widget'));
|
||||
|
||||
@@ -20,49 +20,47 @@ import { DisposableStore, IDisposable } from '../../../../../base/common/lifecyc
|
||||
|
||||
export class TestWorkerService extends mock<IEditorWorkerService>() implements IDisposable {
|
||||
|
||||
readonly #store = new DisposableStore();
|
||||
readonly #worker = this.#store.add(new EditorWorker());
|
||||
readonly #modelService: IModelService;
|
||||
private readonly _store = new DisposableStore();
|
||||
private readonly _worker = this._store.add(new EditorWorker());
|
||||
|
||||
constructor(@IModelService modelService: IModelService) {
|
||||
constructor(@IModelService private readonly _modelService: IModelService) {
|
||||
super();
|
||||
this.#modelService = modelService;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.#store.dispose();
|
||||
this._store.dispose();
|
||||
}
|
||||
override async computeMoreMinimalEdits(resource: URI, edits: TextEdit[] | null | undefined, pretty?: boolean | undefined): Promise<TextEdit[] | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
override async computeDiff(original: URI, modified: URI, options: IDocumentDiffProviderOptions, algorithm: DiffAlgorithmName): Promise<IDocumentDiff | null> {
|
||||
await new Promise<void>(resolve => disposableTimeout(() => resolve(), 0, this.#store));
|
||||
if (this.#store.isDisposed) {
|
||||
await new Promise<void>(resolve => disposableTimeout(() => resolve(), 0, this._store));
|
||||
if (this._store.isDisposed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const originalModel = this.#modelService.getModel(original);
|
||||
const modifiedModel = this.#modelService.getModel(modified);
|
||||
const originalModel = this._modelService.getModel(original);
|
||||
const modifiedModel = this._modelService.getModel(modified);
|
||||
|
||||
assertType(originalModel);
|
||||
assertType(modifiedModel);
|
||||
|
||||
this.#worker.$acceptNewModel({
|
||||
this._worker.$acceptNewModel({
|
||||
url: originalModel.uri.toString(),
|
||||
versionId: originalModel.getVersionId(),
|
||||
lines: originalModel.getLinesContent(),
|
||||
EOL: originalModel.getEOL(),
|
||||
});
|
||||
|
||||
this.#worker.$acceptNewModel({
|
||||
this._worker.$acceptNewModel({
|
||||
url: modifiedModel.uri.toString(),
|
||||
versionId: modifiedModel.getVersionId(),
|
||||
lines: modifiedModel.getLinesContent(),
|
||||
EOL: modifiedModel.getEOL(),
|
||||
});
|
||||
|
||||
const result = await this.#worker.$computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm);
|
||||
const result = await this._worker.$computeDiff(originalModel.uri.toString(), modifiedModel.uri.toString(), options, algorithm);
|
||||
if (!result) {
|
||||
return result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user