Skip to content

Commit

Permalink
Merge pull request #210688 from microsoft/tyriar/210663
Browse files Browse the repository at this point in the history
Adopt PromptInputModel in TextAreaSyncAddon
  • Loading branch information
Tyriar committed Apr 18, 2024
2 parents 3be6cd6 + a062004 commit a9d5a65
Showing 1 changed file with 32 additions and 82 deletions.
Expand Up @@ -3,31 +3,22 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import type { ITerminalAddon, Terminal } from '@xterm/xterm';
import { debounce } from 'vs/base/common/decorators';
import { Event } from 'vs/base/common/event';
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities';
import { ITerminalLogService, TerminalSettingId } from 'vs/platform/terminal/common/terminal';
import type { Terminal, ITerminalAddon } from '@xterm/xterm';
import { debounce } from 'vs/base/common/decorators';
import { addDisposableListener } from 'vs/base/browser/dom';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';

export interface ITextAreaData {
content: string;
cursorX: number;
}

export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
private _terminal: Terminal | undefined;
private readonly _listeners = this._register(new MutableDisposable<DisposableStore>());
private _currentCommand: string | undefined;
private _cursorX: number | undefined;
private readonly _listeners = this._register(new MutableDisposable());

activate(terminal: Terminal): void {
this._terminal = terminal;
if (this._shouldBeActive()) {
this._registerSyncListeners();
}
this._refreshListeners();
}

constructor(
Expand All @@ -37,22 +28,27 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
@ITerminalLogService private readonly _logService: ITerminalLogService
) {
super();
this._register(this._accessibilityService.onDidChangeScreenReaderOptimized(() => {
if (this._shouldBeActive()) {
this._syncTextArea();
this._registerSyncListeners();
} else {
this._listeners.clear();
}

this._register(Event.runAndSubscribe(Event.any(
this._capabilities.onDidAddCapability,
this._capabilities.onDidRemoveCapability,
this._accessibilityService.onDidChangeScreenReaderOptimized,
), () => {
this._refreshListeners();
}));
}

private _registerSyncListeners(): void {
if (this._shouldBeActive() && this._terminal?.textarea) {
this._listeners.value = new DisposableStore();
this._listeners.value.add(this._terminal.onCursorMove(() => this._syncTextArea()));
this._listeners.value.add(this._terminal.onData(() => this._syncTextArea()));
this._listeners.value.add(addDisposableListener(this._terminal.textarea, 'focus', () => this._syncTextArea()));
private _refreshListeners(): void {
const commandDetection = this._capabilities.get(TerminalCapability.CommandDetection);
if (this._shouldBeActive() && commandDetection) {
if (!this._listeners.value) {
const textarea = this._terminal?.textarea;
if (textarea) {
this._listeners.value = Event.runAndSubscribe(commandDetection.promptInputModel.onDidChangeInput, () => this._sync(textarea));
}
}
} else {
this._listeners.clear();
}
}

Expand All @@ -61,62 +57,16 @@ export class TextAreaSyncAddon extends Disposable implements ITerminalAddon {
}

@debounce(50)
private _syncTextArea(): void {
this._logService.debug('TextAreaSyncAddon#syncTextArea');
const textArea = this._terminal?.textarea;
if (!textArea) {
this._logService.debug(`TextAreaSyncAddon#syncTextArea: no textarea`);
private _sync(textArea: HTMLTextAreaElement): void {
const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection);
if (!commandCapability) {
return;
}

this._updateCommandAndCursor();

if (this._currentCommand !== textArea.value) {
textArea.value = this._currentCommand || '';
this._logService.debug(`TextAreaSyncAddon#syncTextArea: text changed to "${this._currentCommand}"`);
} else if (!this._currentCommand) {
textArea.value = '';
this._logService.debug(`TextAreaSyncAddon#syncTextArea: text cleared`);
}

if (this._cursorX !== textArea.selectionStart) {
const selection = !this._cursorX || this._cursorX < 0 ? 0 : this._cursorX;
textArea.selectionStart = selection;
textArea.selectionEnd = selection;
this._logService.debug(`TextAreaSyncAddon#syncTextArea: selection start/end changed to ${selection}`);
}
}
textArea.value = commandCapability.promptInputModel.value;
textArea.selectionStart = commandCapability.promptInputModel.cursorIndex;
textArea.selectionEnd = commandCapability.promptInputModel.cursorIndex;

private _updateCommandAndCursor(): void {
if (!this._terminal) {
return;
}
const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection);
const currentCommand = commandCapability?.currentCommand;
if (!currentCommand) {
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no current command`);
return;
}
const buffer = this._terminal.buffer.active;
const lineNumber = currentCommand.commandStartMarker?.line;
this._logService.debug('line number?', lineNumber);
if (!lineNumber) {
return;
}
const commandLine = buffer.getLine(lineNumber)?.translateToString(true);
if (!commandLine) {
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no line`);
return;
}
// TODO: Support multi-line prompts
if (currentCommand.commandStartX !== undefined) {
this._currentCommand = commandLine.substring(currentCommand.commandStartX);
const cursorPosition = buffer.cursorX - currentCommand.commandStartX;
this._cursorX = cursorPosition >= 0 ? cursorPosition : 0;
} else {
this._currentCommand = undefined;
this._cursorX = undefined;
this._logService.debug(`TextAreaSyncAddon#updateCommandAndCursor: no commandStartX`);
}
this._logService.debug(`TextAreaSyncAddon#sync: text changed to "${textArea.value}"`);
}
}

0 comments on commit a9d5a65

Please sign in to comment.