Path: blob/main/src/vs/sessions/contrib/chat/browser/newChatInput.ts
13401 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import './media/chatInput.css';6import * as dom from '../../../../base/browser/dom.js';7import { Codicon } from '../../../../base/common/codicons.js';8import { Emitter } from '../../../../base/common/event.js';9import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js';10import { Disposable, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';11import { URI } from '../../../../base/common/uri.js';12import { Button } from '../../../../base/browser/ui/button/button.js';13import { CodeEditorWidget, ICodeEditorWidgetOptions } from '../../../../editor/browser/widget/codeEditor/codeEditorWidget.js';14import { EditorExtensionsRegistry } from '../../../../editor/browser/editorExtensions.js';15import { IEditorConstructionOptions } from '../../../../editor/browser/config/editorConfiguration.js';16import { IModelService } from '../../../../editor/common/services/model.js';17import { SuggestController } from '../../../../editor/contrib/suggest/browser/suggestController.js';18import { SnippetController2 } from '../../../../editor/contrib/snippet/browser/snippetController2.js';19import { PlaceholderTextContribution } from '../../../../editor/contrib/placeholderText/browser/placeholderTextContribution.js';20import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';21import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';22import { ServiceCollection } from '../../../../platform/instantiation/common/serviceCollection.js';23import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';24import { IKeybindingService } from '../../../../platform/keybinding/common/keybinding.js';25import { AccessibilityVerbositySettingId } from '../../../../workbench/contrib/accessibility/browser/accessibilityConfiguration.js';26import { AccessibilityCommandId } from '../../../../workbench/contrib/accessibility/common/accessibilityCommands.js';27import { ILogService } from '../../../../platform/log/common/log.js';28import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';29import { IHoverService } from '../../../../platform/hover/browser/hover.js';30import { getDefaultHoverDelegate } from '../../../../base/browser/ui/hover/hoverDelegateFactory.js';31import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';32import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js';33import { localize } from '../../../../nls.js';34import * as aria from '../../../../base/browser/ui/aria/aria.js';35import { ContextMenuController } from '../../../../editor/contrib/contextmenu/browser/contextmenu.js';36import { getSimpleEditorOptions } from '../../../../workbench/contrib/codeEditor/browser/simpleEditorOptions.js';37import { NewChatContextAttachments } from './newChatContextAttachments.js';38import { SessionTypePicker } from './sessionTypePicker.js';39import { Menus } from '../../../browser/menus.js';40import { HiddenItemStrategy, MenuWorkbenchToolBar } from '../../../../platform/actions/browser/toolbar.js';41import { SlashCommandHandler } from './slashCommands.js';42import { VariableCompletionHandler } from './variableCompletions.js';43import { IChatModelInputState } from '../../../../workbench/contrib/chat/common/model/chatModel.js';44import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js';45import { ChatAgentLocation, ChatModeKind } from '../../../../workbench/contrib/chat/common/constants.js';46import { ChatHistoryNavigator } from '../../../../workbench/contrib/chat/common/widget/chatWidgetHistoryService.js';47import { IHistoryNavigationWidget } from '../../../../base/browser/history.js';48import { registerAndCreateHistoryNavigationContext, IHistoryNavigationContext } from '../../../../platform/history/browser/contextScopedHistoryWidget.js';49import { autorun, IObservable } from '../../../../base/common/observable.js';50import { ChatInputNotificationWidget } from '../../../../workbench/contrib/chat/browser/widget/input/chatInputNotificationWidget.js';515253const STORAGE_KEY_DRAFT_STATE = 'sessions.draftState';54const MIN_EDITOR_HEIGHT = 50;55const MAX_EDITOR_HEIGHT = 200;5657interface IDraftState {58inputText: string;59attachments: readonly IChatRequestVariableEntry[];60}6162/**63* Randomized, friendly placeholders shown in the new-session chat input64* to add a bit of personality. One is picked per widget instance, avoiding65* an immediate repeat of the previous pick.66*/67const RANDOM_PLACEHOLDERS = [68localize('sessionsChatInput.placeholder.whatAreYouBuilding', "What are you building?"),69localize('sessionsChatInput.placeholder.whatWillYouShipToday', "What will you ship today?"),70localize('sessionsChatInput.placeholder.describeWhatYouWantToBuild', "Describe what you want to build"),71localize('sessionsChatInput.placeholder.whatsYourNextMilestone', "What's your next milestone?"),72localize('sessionsChatInput.placeholder.whatAreYouTryingToAchieve', "What are you trying to achieve?"),73localize('sessionsChatInput.placeholder.pitchYourIdea', "Pitch your idea"),74localize('sessionsChatInput.placeholder.whatsTheGoal', "What's the goal?"),75localize('sessionsChatInput.placeholder.whatWillYouCreate', "What will you create?"),76localize('sessionsChatInput.placeholder.whatFeatureAreYouDreamingUp', "What feature are you dreaming up?"),77localize('sessionsChatInput.placeholder.describeTheOutcome', "Describe the outcome you want"),78localize('sessionsChatInput.placeholder.whatProblemAreYouSolving', "What problem are you solving?"),79localize('sessionsChatInput.placeholder.whatsNextOnYourRoadmap', "What's next on your roadmap?"),80localize('sessionsChatInput.placeholder.whatWouldYouLikeToAutomate', "What would you like to automate?"),81localize('sessionsChatInput.placeholder.whatWillYouLaunch', "What will you launch?"),82localize('sessionsChatInput.placeholder.describeYourMission', "Describe your mission"),83];8485let lastPlaceholderIndex = -1;86function getRandomChatInputPlaceholder(): string {87let index = Math.floor(Math.random() * RANDOM_PLACEHOLDERS.length);88if (index === lastPlaceholderIndex) {89index = (index + 1) % RANDOM_PLACEHOLDERS.length;90}91lastPlaceholderIndex = index;92return RANDOM_PLACEHOLDERS[index];93}9495// #region --- New Chat Widget ---9697export class NewChatInputWidget extends Disposable implements IHistoryNavigationWidget {9899readonly sessionTypePicker: SessionTypePicker;100101// IHistoryNavigationWidget102private readonly _onDidFocus = this._register(new Emitter<void>());103readonly onDidFocus = this._onDidFocus.event;104private readonly _onDidBlur = this._register(new Emitter<void>());105readonly onDidBlur = this._onDidBlur.event;106get element(): HTMLElement { return this._editorContainer; }107108// Input109private _editor!: CodeEditorWidget;110private _editorContainer!: HTMLElement;111112// Send button113private _sendButton: Button | undefined;114private _sending = false;115116// Loading state117private _loadingSpinner: HTMLElement | undefined;118private readonly _loadingDelayDisposable = this._register(new MutableDisposable());119120// Attached context121private readonly _contextAttachments: NewChatContextAttachments;122123// Slash commands124private _slashCommandHandler: SlashCommandHandler | undefined;125126// Input state127private _draftState: IDraftState | undefined = {128inputText: '',129attachments: [],130};131132// Input history133private readonly _history: ChatHistoryNavigator;134private _historyNavigationBackwardsEnablement!: IHistoryNavigationContext['historyNavigationBackwardsEnablement'];135private _historyNavigationForwardsEnablement!: IHistoryNavigationContext['historyNavigationForwardsEnablement'];136137constructor(138private readonly options: {139getContextFolderUri: () => URI | undefined;140sendRequest: (query: string, attachments?: IChatRequestVariableEntry[]) => Promise<void>;141canSendRequest: IObservable<boolean>;142loading: IObservable<boolean>;143minEditorHeight?: number;144placeholder?: string;145},146@IInstantiationService private readonly instantiationService: IInstantiationService,147@IModelService private readonly modelService: IModelService,148@IConfigurationService private readonly configurationService: IConfigurationService,149@IContextKeyService private readonly contextKeyService: IContextKeyService,150@ILogService private readonly logService: ILogService,151@IHoverService private readonly hoverService: IHoverService,152@IStorageService private readonly storageService: IStorageService,153@IKeybindingService private readonly keybindingService: IKeybindingService,154) {155super();156this._history = this._register(this.instantiationService.createInstance(ChatHistoryNavigator, ChatAgentLocation.Chat));157this._contextAttachments = this._register(this.instantiationService.createInstance(NewChatContextAttachments));158this.sessionTypePicker = this._register(this.instantiationService.createInstance(SessionTypePicker));159this._register(this._contextAttachments.onDidChangeContext(() => {160this._updateDraftState();161this.focus();162}));163this._register(autorun(reader => {164this.options.canSendRequest.read(reader);165const isLoading = this.options.loading.read(reader);166this._loadingSpinner?.classList.toggle('visible', isLoading);167this._updateSendButtonState();168}));169}170171// --- Rendering ---172173render(parent: HTMLElement, root: HTMLElement): void {174// Input slot175const chatInputContainer = dom.append(parent, dom.$('.new-chat-input-container'));176177// Overflow widget DOM node at the top level so the suggest widget178// is not clipped by any overflow:hidden ancestor.179const editorOverflowWidgetsDomNode = dom.append(root, dom.$('.sessions-chat-editor-overflow.monaco-editor'));180this._register({ dispose: () => editorOverflowWidgetsDomNode.remove() });181182// Notification widget above the input area183const notificationContainer = dom.append(chatInputContainer, dom.$('.chat-input-notification-container'));184const notificationWidget = this._register(this.instantiationService.createInstance(ChatInputNotificationWidget));185notificationContainer.appendChild(notificationWidget.domNode);186187// Input area inside the input slot188const inputArea = dom.append(chatInputContainer, dom.$('.new-chat-input-area'));189190// Attachments row (pills only) inside input area, above editor191const attachRow = dom.append(inputArea, dom.$('.sessions-chat-attach-row'));192const attachedContextContainer = dom.append(attachRow, dom.$('.sessions-chat-attached-context'));193this._contextAttachments.renderAttachedContext(attachedContextContainer);194this._contextAttachments.registerDropTarget(root);195this._contextAttachments.registerPasteHandler(inputArea);196197this._createEditor(inputArea, editorOverflowWidgetsDomNode);198this._createInputToolbar(inputArea);199200const newChatBottomContainer = dom.append(parent, dom.$('.new-chat-bottom-container'));201const newChatControlsContainer = dom.append(newChatBottomContainer, dom.$('.new-chat-controls-container'));202this.sessionTypePicker.render(newChatControlsContainer);203this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, dom.append(newChatControlsContainer, dom.$('')), Menus.NewSessionControl, {204hiddenItemStrategy: HiddenItemStrategy.NoHide,205}));206207const repoConfigContainer = dom.append(newChatBottomContainer, dom.$('.new-chat-repo-config-container'));208this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, repoConfigContainer, Menus.NewSessionRepositoryConfig, {209hiddenItemStrategy: HiddenItemStrategy.NoHide,210}));211212// Restore draft input state from storage213this._restoreState();214215// Layout editor after the input slot fade-in animation completes216this._register(dom.addDisposableListener(chatInputContainer, 'animationend', () => {217this._editor?.layout();218}, { once: true }));219}220221private _updateInputLoadingState(): void {222const loading = this._sending;223if (loading) {224if (!this._loadingDelayDisposable.value) {225const timer = setTimeout(() => {226this._loadingDelayDisposable.clear();227if (this._sending) {228this._loadingSpinner?.classList.add('visible');229}230}, 500);231this._loadingDelayDisposable.value = toDisposable(() => clearTimeout(timer));232}233} else {234this._loadingDelayDisposable.clear();235this._loadingSpinner?.classList.remove('visible');236}237}238239// --- Editor ---240241private _getAriaLabel(): string {242const verbose = this.configurationService.getValue<boolean>(AccessibilityVerbositySettingId.SessionsChat);243if (verbose) {244const kbLabel = this.keybindingService.lookupKeybinding(AccessibilityCommandId.OpenAccessibilityHelp)?.getLabel();245return kbLabel246? localize('chatInput.accessibilityHelp', "Chat input. Press Enter to send out the request. Use {0} for Chat Accessibility Help.", kbLabel)247: localize('chatInput.accessibilityHelpNoKb', "Chat input. Press Enter to send out the request. Use the Chat Accessibility Help command for more information.");248}249return localize('chatInput', "Chat input");250}251252private _createEditor(container: HTMLElement, overflowWidgetsDomNode: HTMLElement): void {253const editorContainer = this._editorContainer = dom.append(container, dom.$('.sessions-chat-editor'));254const minHeight = this.options.minEditorHeight ?? MIN_EDITOR_HEIGHT;255editorContainer.style.height = `${minHeight}px`;256257// Create scoped context key service and register history navigation258// BEFORE creating the editor, so the editor's context key scope is a child259const inputScopedContextKeyService = this._register(this.contextKeyService.createScoped(container));260const { historyNavigationBackwardsEnablement, historyNavigationForwardsEnablement } = this._register(registerAndCreateHistoryNavigationContext(inputScopedContextKeyService, this));261this._historyNavigationBackwardsEnablement = historyNavigationBackwardsEnablement;262this._historyNavigationForwardsEnablement = historyNavigationForwardsEnablement;263264const scopedInstantiationService = this._register(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, inputScopedContextKeyService])));265266const uri = URI.from({ scheme: 'sessions-chat', path: `input-${Date.now()}` });267const textModel = this._register(this.modelService.createModel('', null, uri, true));268269const editorOptions: IEditorConstructionOptions = {270...getSimpleEditorOptions(this.configurationService),271readOnly: false,272ariaLabel: this._getAriaLabel(),273placeholder: this.options.placeholder ?? getRandomChatInputPlaceholder(),274fontFamily: 'system-ui, -apple-system, sans-serif',275fontSize: 13,276lineHeight: 20,277cursorWidth: 1,278padding: { top: 8, bottom: 2 },279wrappingStrategy: 'advanced',280stickyScroll: { enabled: false },281renderWhitespace: 'none',282overflowWidgetsDomNode,283suggest: {284showIcons: true,285showSnippets: false,286showWords: true,287showStatusBar: false,288insertMode: 'insert',289},290};291292const widgetOptions: ICodeEditorWidgetOptions = {293isSimpleWidget: true,294contributions: EditorExtensionsRegistry.getSomeEditorContributions([295ContextMenuController.ID,296SuggestController.ID,297SnippetController2.ID,298PlaceholderTextContribution.ID,299]),300};301302this._editor = this._register(scopedInstantiationService.createInstance(303CodeEditorWidget, editorContainer, editorOptions, widgetOptions,304));305this._editor.setModel(textModel);306307// Ensure suggest widget renders above the input (not clipped by container)308SuggestController.get(this._editor)?.forceRenderingAbove();309310// Update aria label when accessibility verbosity setting changes311this._register(this.configurationService.onDidChangeConfiguration(e => {312if (e.affectsConfiguration(AccessibilityVerbositySettingId.SessionsChat)) {313this._editor.updateOptions({ ariaLabel: this._getAriaLabel() });314}315}));316317this._register(this._editor.onDidFocusEditorWidget(() => this._onDidFocus.fire()));318this._register(this._editor.onDidBlurEditorWidget(() => this._onDidBlur.fire()));319320this._register(this._editor.onKeyDown(e => {321if (e.keyCode === KeyCode.Enter && !e.shiftKey && !e.ctrlKey && !e.altKey) {322// Don't send if the suggest widget is visible (let it accept the completion)323if (this._editor.contextKeyService.getContextKeyValue<boolean>('suggestWidgetVisible')) {324return;325}326e.preventDefault();327e.stopPropagation();328this._send();329}330if (e.keyCode === KeyCode.Enter && !e.shiftKey && !e.ctrlKey && e.altKey) {331e.preventDefault();332e.stopPropagation();333this._send();334}335// Cmd+/ / Ctrl+/ — open the context picker (same as the attach button)336if (e.equals(KeyMod.CtrlCmd | KeyCode.Slash)) {337e.preventDefault();338e.stopPropagation();339this._contextAttachments.showPicker(this.options.getContextFolderUri());340}341}));342343// Update history navigation enablement based on cursor position344const updateHistoryNavigationEnablement = () => {345const model = this._editor.getModel();346const position = this._editor.getPosition();347if (!model || !position) {348return;349}350this._historyNavigationBackwardsEnablement.set(position.lineNumber === 1 && position.column === 1);351this._historyNavigationForwardsEnablement.set(position.lineNumber === model.getLineCount() && position.column === model.getLineMaxColumn(position.lineNumber));352};353this._register(this._editor.onDidChangeCursorPosition(() => updateHistoryNavigationEnablement()));354updateHistoryNavigationEnablement();355356let previousHeight = -1;357this._register(this._editor.onDidContentSizeChange(e => {358if (!e.contentHeightChanged) {359return;360}361const contentHeight = this._editor.getContentHeight();362const clampedHeight = Math.min(MAX_EDITOR_HEIGHT, Math.max(this.options.minEditorHeight ?? MIN_EDITOR_HEIGHT, contentHeight));363if (clampedHeight === previousHeight) {364return;365}366previousHeight = clampedHeight;367this._editorContainer.style.height = `${clampedHeight}px`;368this._editor.layout();369}));370371// Slash commands372this._slashCommandHandler = this._register(this.instantiationService.createInstance(SlashCommandHandler, this._editor));373374// Variable completions (#file, #folder)375this._register(this.instantiationService.createInstance(376VariableCompletionHandler, this._editor, this._contextAttachments, () => this.options.getContextFolderUri(),377));378379this._register(this._editor.onDidChangeModelContent(() => {380this._updateDraftState();381this._updateSendButtonState();382}));383}384385private _createAttachButton(container: HTMLElement): void {386const attachButton = dom.append(container, dom.$('.sessions-chat-attach-button'));387const attachButtonLabel = localize('addContext', "Add Context...");388attachButton.tabIndex = 0;389attachButton.role = 'button';390attachButton.ariaLabel = attachButtonLabel;391this._register(this.hoverService.setupDelayedHover(attachButton, {392content: attachButtonLabel,393position: { hoverPosition: HoverPosition.BELOW },394appearance: { showPointer: true }395}));396dom.append(attachButton, renderIcon(Codicon.add));397this._register(dom.addDisposableListener(attachButton, dom.EventType.CLICK, () => {398this._contextAttachments.showPicker(this.options.getContextFolderUri());399}));400}401402private _createInputToolbar(container: HTMLElement): void {403const toolbar = dom.append(container, dom.$('.sessions-chat-toolbar'));404405this._createAttachButton(toolbar);406407// Session config pickers (mode, model) — rendered via MenuWorkbenchToolBar408// Visibility controlled by context keys (isActiveSessionBackgroundProvider, isNewChatSession)409const configContainer = dom.append(toolbar, dom.$('.sessions-chat-config-toolbar'));410this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, configContainer, Menus.NewSessionConfig, {411hiddenItemStrategy: HiddenItemStrategy.NoHide,412}));413414dom.append(toolbar, dom.$('.sessions-chat-toolbar-spacer'));415416this._loadingSpinner = dom.append(toolbar, dom.$('.sessions-chat-loading-spinner'));417this._register(this.hoverService.setupManagedHover(getDefaultHoverDelegate('mouse'), this._loadingSpinner, localize('loading', "Loading...")));418419const sendButtonContainer = dom.append(toolbar, dom.$('.sessions-chat-send-button'));420const sendButton = this._sendButton = this._register(new Button(sendButtonContainer, {421secondary: true,422title: localize('send', "Send"),423ariaLabel: localize('send', "Send"),424}));425sendButton.icon = Codicon.arrowUp;426this._register(sendButton.onDidClick(() => this._send()));427}428429// --- Input History (IHistoryNavigationWidget) ---430431showPreviousValue(): void {432if (this._history.isAtStart()) {433return;434}435if (this._draftState?.inputText || this._draftState?.attachments.length) {436this._history.overlay(this._toHistoryEntry(this._draftState));437}438this._navigateHistory(true);439}440441showNextValue(): void {442if (this._history.isAtEnd()) {443return;444}445if (this._draftState?.inputText || this._draftState?.attachments.length) {446this._history.overlay(this._toHistoryEntry(this._draftState));447}448this._navigateHistory(false);449}450451private _updateDraftState(): void {452this._draftState = {453inputText: this._editor?.getModel()?.getValue() ?? '',454attachments: [...this._contextAttachments.attachments],455};456}457458private _toHistoryEntry(draft: IDraftState): IChatModelInputState {459return {460...draft,461mode: { id: ChatModeKind.Agent, kind: ChatModeKind.Agent },462selectedModel: undefined,463selections: [],464contrib: {},465};466}467468private _navigateHistory(previous: boolean): void {469const entry = previous ? this._history.previous() : this._history.next();470const inputText = entry?.inputText ?? '';471if (entry) {472this._editor?.getModel()?.setValue(inputText);473this._contextAttachments.setAttachments(entry.attachments);474}475aria.status(inputText);476if (previous) {477this._editor.setPosition({ lineNumber: 1, column: 1 });478} else {479const model = this._editor.getModel();480if (model) {481const lastLine = model.getLineCount();482this._editor.setPosition({ lineNumber: lastLine, column: model.getLineMaxColumn(lastLine) });483}484}485}486487// --- Send ---488489490private async _send(): Promise<void> {491let query = this._editor.getModel()?.getValue().trim();492if (!query || this._sending) {493return;494}495496// Check for slash commands first497if (this._slashCommandHandler?.tryExecuteSlashCommand(query)) {498this._editor.getModel()?.setValue('');499return;500}501502// Expand prompt/skill slash commands into a CLI-friendly reference503const expanded = this._slashCommandHandler?.tryExpandPromptSlashCommand(query);504if (expanded) {505query = expanded;506}507508const attachedContext = this._contextAttachments.attachments.length > 0509? [...this._contextAttachments.attachments]510: undefined;511512if (this._draftState) {513this._history.append(this._toHistoryEntry(this._draftState));514}515this._clearDraftState();516517this._sending = true;518this._editor.updateOptions({ readOnly: true });519this._updateSendButtonState();520this._updateInputLoadingState();521522try {523await this.options.sendRequest(query, attachedContext);524this._contextAttachments.clear();525this._editor.getModel()?.setValue('');526} catch (e) {527this.logService.error('Failed to send request:', e);528}529530this._sending = false;531this._editor.updateOptions({ readOnly: false });532this._updateSendButtonState();533this._updateInputLoadingState();534}535536private _updateSendButtonState(): void {537if (!this._sendButton) {538return;539}540const hasText = !!this._editor?.getModel()?.getValue().trim();541this._sendButton.enabled = !this._sending && hasText && this.options.canSendRequest.get();542}543544private _restoreState(): void {545const draft = this._getDraftState();546if (draft) {547this._editor?.getModel()?.setValue(draft.inputText);548if (draft.attachments?.length) {549this._contextAttachments.setAttachments(draft.attachments.map(IChatRequestVariableEntry.fromExport));550}551}552}553554private _getDraftState(): IDraftState | undefined {555const raw = this.storageService.get(STORAGE_KEY_DRAFT_STATE, StorageScope.WORKSPACE);556if (!raw) {557return undefined;558}559try {560return JSON.parse(raw);561} catch {562return undefined;563}564}565566private _clearDraftState(): void {567this._draftState = { inputText: '', attachments: [] };568this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(this._draftState), StorageScope.WORKSPACE, StorageTarget.MACHINE);569}570571saveState(): void {572if (this._draftState) {573const state = {574...this._draftState,575attachments: this._draftState.attachments.map(IChatRequestVariableEntry.toExport),576};577this.storageService.store(STORAGE_KEY_DRAFT_STATE, JSON.stringify(state), StorageScope.WORKSPACE, StorageTarget.MACHINE);578}579}580581layout(_height: number, _width: number): void {582this._editor?.layout();583}584585focus(): void {586this._editor?.focus();587}588589prefillInput(text: string): void {590const editor = this._editor;591const model = editor?.getModel();592if (editor && model) {593model.setValue(text);594const lastLine = model.getLineCount();595const maxColumn = model.getLineMaxColumn(lastLine);596editor.setPosition({ lineNumber: lastLine, column: maxColumn });597editor.focus();598}599}600601sendQuery(text: string): void {602const model = this._editor?.getModel();603if (model) {604model.setValue(text);605this._send();606}607}608}609610// #endregion611612613