add basics

This commit is contained in:
meganrogge
2024-02-09 10:07:17 -06:00
parent 80ef615f0b
commit dad5b331ea
7 changed files with 273 additions and 0 deletions
@@ -121,6 +121,10 @@ export const enum TerminalSettingId {
StickyScrollEnabled = 'terminal.integrated.stickyScroll.enabled',
StickyScrollMaxLineCount = 'terminal.integrated.stickyScroll.maxLineCount',
MouseWheelZoom = 'terminal.integrated.mouseWheelZoom',
FocusChat = 'workbench.action.terminal.focusChat',
HideChat = 'workbench.action.terminal.hideChat',
SubmitChat = 'workbench.action.terminal.submitChat',
CancelChat = 'workbench.action.terminal.cancelChat',
// Debug settings that are hidden from user
@@ -565,3 +565,14 @@
.monaco-workbench .xterm.terminal.hide {
visibility: hidden;
}
.monaco-workbench .terminal-chat-widget {
z-index: 33 !important;
position: absolute;
bottom: 30px;
left: 10px;
}
.monaco-workbench .terminal-chat-widget.hide {
visibility: hidden;
}
@@ -500,6 +500,10 @@ export const enum TerminalCommandId {
FontZoomIn = 'workbench.action.terminal.fontZoomIn',
FontZoomOut = 'workbench.action.terminal.fontZoomOut',
FontZoomReset = 'workbench.action.terminal.fontZoomReset',
FocusChat = 'workbench.action.terminal.focusChat',
HideChat = 'workbench.action.terminal.hideChat',
SubmitChat = 'workbench.action.terminal.submitChat',
CancelChat = 'workbench.action.terminal.cancelChat',
// Developer commands
@@ -39,6 +39,10 @@ export const enum TerminalContextKeyStrings {
ShellType = 'terminalShellType',
InTerminalRunCommandPicker = 'inTerminalRunCommandPicker',
TerminalShellIntegrationEnabled = 'terminalShellIntegrationEnabled',
ChatFocus = 'terminalChatFocus',
ChatVisible = 'terminalChatVisible',
ChatSessionInProgress = 'terminalChatSessionInProgress',
ChatInputHasText = 'terminalChatInputHasText',
}
export namespace TerminalContextKeys {
@@ -158,4 +162,17 @@ export namespace TerminalContextKeys {
ContextKeyExpr.equals(`config.${TerminalSettingId.TabsShowActions}`, 'always')
)
);
/** Whether the chat widget is focused */
export const chatFocused = new RawContextKey<boolean>(TerminalContextKeyStrings.ChatFocus, false, localize('chatFocusedContextKey', "Whether the chat view is focused."));
/** Whether the chat widget is visible */
export const chatVisible = new RawContextKey<boolean>(TerminalContextKeyStrings.ChatVisible, false, localize('chatVisibleContextKey', "Whether the chat view is visible."));
/** Whether a chat session is in progress */
export const chatSessionInProgress = new RawContextKey<boolean>(TerminalContextKeyStrings.ChatSessionInProgress, false, localize('chatSessionInProgressContextKey', "Whether a chat session is in progress."));
/** Whether the chat input has text */
export const chatInputHasText = new RawContextKey<boolean>(TerminalContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text."));
}
@@ -17,6 +17,7 @@ import 'vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.acce
import 'vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution';
import 'vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution';
import 'vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution';
import 'vs/workbench/contrib/terminalContrib/chat/browser/terminal.chat.contribution';
import 'vs/workbench/contrib/terminalContrib/highlight/browser/terminal.highlight.contribution';
import 'vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution';
import 'vs/workbench/contrib/terminalContrib/zoom/browser/terminal.zoom.contribution';
@@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDimension } from 'vs/base/browser/dom';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
import { Disposable } from 'vs/base/common/lifecycle';
import { localize2 } from 'vs/nls';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions';
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
import type { Terminal as RawXtermTerminal } from '@xterm/xterm';
import { Codicon } from 'vs/base/common/codicons';
import { MenuId } from 'vs/platform/actions/common/actions';
import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget';
export class TerminalChatContribution extends Disposable implements ITerminalContribution {
static readonly ID = 'terminal.Chat';
/**
* Currently focused Chat widget. This is used to track action context since
* 'active terminals' are only tracked for non-detached terminal instanecs.
*/
static activeChatWidget?: TerminalChatContribution;
static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalChatContribution | null {
return instance.getContribution<TerminalChatContribution>(TerminalChatContribution.ID);
}
private _chatWidget: Lazy<TerminalChatWidget> | undefined;
private _lastLayoutDimensions: IDimension | undefined;
get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; }
constructor(
private readonly _instance: ITerminalInstance | IDetachedTerminalInstance,
processManager: ITerminalProcessManager | ITerminalProcessInfo,
widgetManager: TerminalWidgetManager,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ITerminalService private readonly _terminalService: ITerminalService
) {
super();
}
layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void {
this._lastLayoutDimensions = dimension;
this._chatWidget?.rawValue?.layout(dimension.width);
}
xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void {
this._chatWidget = new Lazy(() => {
const chatWidget = this._instantiationService.createInstance(TerminalChatWidget, this._instance.domElement!, this._instance);
// Track focus and set state so we can force the scroll bar to be visible
chatWidget.onDidFocus(() => {
TerminalChatContribution.activeChatWidget = this;
this._instance.forceScrollbarVisibility();
if (!isDetachedTerminalInstance(this._instance)) {
this._terminalService.setActiveInstance(this._instance);
}
});
// chatWidget.onDidBlur(() => {
// TerminalChatContribution.activeChatWidget = undefined;
// this._instance.resetScrollbarVisibility();
// });
if (!this._instance.domElement) {
throw new Error('FindWidget expected terminal DOM to be initialized');
}
// this._instance.domElement?.appendChild(chatWidget.getDomNode());
if (this._lastLayoutDimensions) {
chatWidget.layout(this._lastLayoutDimensions.width);
}
return chatWidget;
});
}
override dispose() {
if (TerminalChatContribution.activeChatWidget === this) {
TerminalChatContribution.activeChatWidget = undefined;
}
super.dispose();
this._chatWidget?.rawValue?.dispose();
}
}
registerTerminalContribution(TerminalChatContribution.ID, TerminalChatContribution, true);
registerActiveXtermAction({
id: TerminalCommandId.FocusChat,
title: localize2('workbench.action.terminal.focusChat', 'Terminal: Focus Chat'),
keybinding: {
primary: KeyMod.CtrlCmd | KeyCode.KeyI,
when: ContextKeyExpr.or(TerminalContextKeys.chatFocused, TerminalContextKeys.focusInAny),
weight: KeybindingWeight.WorkbenchContrib
},
f1: true,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
run: (_xterm, _accessor, activeInstance) => {
const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance);
contr?.chatWidget?.reveal();
}
});
registerActiveXtermAction({
id: TerminalCommandId.HideChat,
title: localize2('workbench.action.terminal.hideChat', 'Terminal: Hide Chat'),
keybinding: {
primary: KeyCode.Escape,
secondary: [KeyMod.Shift | KeyCode.Escape],
when: ContextKeyExpr.and(TerminalContextKeys.chatFocused, TerminalContextKeys.chatVisible),
weight: KeybindingWeight.WorkbenchContrib
},
f1: true,
precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
run: (_xterm, _accessor, activeInstance) => {
const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance);
contr?.chatWidget?.hide();
}
});
registerActiveXtermAction({
id: TerminalCommandId.SubmitChat,
title: localize2('workbench.action.terminal.submitChat', 'Terminal: Submit Chat'),
precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.chatInputHasText),
icon: Codicon.send,
menu: {
id: MenuId.ChatExecute,
when: TerminalContextKeys.chatSessionInProgress.negate(),
group: 'navigation',
},
run: (_xterm, _accessor, activeInstance) => {
const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance);
contr?.chatWidget?.acceptInput();
}
});
registerActiveXtermAction({
id: TerminalCommandId.CancelChat,
title: localize2('workbench.action.terminal.cancelChat', 'Cancel Chat'),
precondition: TerminalContextKeys.chatSessionInProgress,
icon: Codicon.debugStop,
menu: {
id: MenuId.ChatExecute,
group: 'navigation',
},
run: (_xterm, _accessor, activeInstance) => {
const contr = TerminalChatContribution.activeChatWidget || TerminalChatContribution.get(activeInstance);
contr?.chatWidget?.cancel();
}
});
@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { editorBackground, editorForeground, inputBackground } from 'vs/platform/theme/common/colorRegistry';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { IDetachedTerminalInstance, ITerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
export class TerminalChatWidget extends Disposable {
private _widget: ChatWidget | undefined;
private _scopedInstantiationService: IInstantiationService;
private readonly _onDidFocus = this._register(new Emitter<void>());
readonly onDidFocus = this._onDidFocus.event;
private _widgetContainer: HTMLElement | undefined;
private _chatWidgetFocused: IContextKey<boolean>;
private _chatWidgetVisible: IContextKey<boolean>;
constructor(
private readonly _container: HTMLElement,
private readonly _instance: ITerminalInstance | IDetachedTerminalInstance,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService) {
super();
const scopedContextKeyService = this._register(this._contextKeyService.createScoped(this._container));
this._scopedInstantiationService = this._instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService]));
this._chatWidgetFocused = TerminalContextKeys.chatFocused.bindTo(this._contextKeyService);
this._chatWidgetVisible = TerminalContextKeys.chatVisible.bindTo(this._contextKeyService);
}
reveal(): void {
this._widgetContainer = document.createElement('div');
this._widgetContainer.classList.add('terminal-chat-widget');
this._widget = this._register(this._scopedInstantiationService.createInstance(
ChatWidget,
{ viewId: 'terminal' },
{ supportsFileReferences: false, renderStyle: 'compact' },
{
listForeground: editorForeground,
listBackground: editorBackground,
inputEditorBackground: inputBackground,
resultEditorBackground: editorBackground
}));
this._widget.render(this._widgetContainer);
this._container.appendChild(this._widgetContainer);
this._register(this._widget.onDidFocus(() => {
this._onDidFocus.fire();
this._chatWidgetFocused.set(true);
}));
this._widget.setVisible(true);
this._chatWidgetVisible.set(true);
this._widget.setInput('@terminal');
this._widget.setInputPlaceholder('Request a terminal command');
this._widget.focusInput();
}
hide(): void {
if (this._widgetContainer) {
this._container.removeChild(this._widgetContainer);
}
this._chatWidgetVisible.set(false);
}
cancel(): void {
this._widget?.clear();
}
acceptInput(): void {
this._widget?.acceptInput();
}
layout(width: number): void {
this._widget?.layout(40, width);
}
}