diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 4288785699b..28465f58328 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -727,11 +727,21 @@ declare module 'vscode' { */ export interface SourceControlInputBox { + /** + * Sets focus to the input. + */ + focus(): void; + + /** + * Shows a transient contextual message on the input. + */ + showValidationMessage(message: string, type: SourceControlInputBoxValidationType): void; + /** * A validation function for the input box. It's possible to change * the validation provider simply by setting this property to a different function. */ - validateInput?(value: string, cursorPosition: number): ProviderResult; + validateInput?(value: string, cursorPosition: number): ProviderResult; } //#endregion diff --git a/src/vs/workbench/api/browser/mainThreadSCM.ts b/src/vs/workbench/api/browser/mainThreadSCM.ts index 0c23ea4ad64..791f1d0c378 100644 --- a/src/vs/workbench/api/browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/browser/mainThreadSCM.ts @@ -6,7 +6,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, DisposableStore, combinedDisposable } from 'vs/base/common/lifecycle'; -import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; +import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation, ISCMViewService, InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../common/extHost.protocol'; import { Command } from 'vs/editor/common/modes'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; @@ -424,6 +424,24 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.visible = visible; } + $setInputBoxFocus(sourceControlHandle: number): void { + const repository = this._repositories.get(sourceControlHandle); + if (!repository) { + return; + } + + repository.input.setFocus(); + } + + $showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType) { + const repository = this._repositories.get(sourceControlHandle); + if (!repository) { + return; + } + + repository.input.showValidationMessage(message, type); + } + $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void { const repository = this._repositories.get(sourceControlHandle); diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index da43bd57865..83fd9e2e4b5 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -62,6 +62,7 @@ import { WorkspaceTrustRequestOptions, WorkspaceTrustStateChangeEvent } from 'vs import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { IShellLaunchConfig, IShellLaunchConfigDto, ITerminalDimensions, ITerminalEnvironment, ITerminalLaunchError } from 'vs/platform/terminal/common/terminal'; import { ITerminalProfile } from 'vs/workbench/contrib/terminal/common/terminal'; +import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -1037,6 +1038,8 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxValue(sourceControlHandle: number, value: string): void; $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; $setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void; + $setInputBoxFocus(sourceControlHandle: number): void; + $showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType): void; $setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void; } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index 058061633e8..2faa67c51f8 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -259,6 +259,18 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { // noop } + focus(): void { + if (!this._visible) { + this.visible = true; + } + + this._proxy.$setInputBoxFocus(this._sourceControlHandle); + } + + showValidationMessage(message: string, type: vscode.SourceControlInputBoxValidationType) { + this._proxy.$showValidationMessage(this._sourceControlHandle, message, type as any); + } + $onInputBoxValueChange(value: string): void { this.updateValue(value); } diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index 104d7d0f511..acb095e1d90 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -1438,6 +1438,11 @@ registerAction2(CollapseAllRepositoriesAction); registerAction2(ExpandAllRepositoriesAction); class SCMInputWidget extends Disposable { + private static readonly ValidationTimeouts: { [severity: number]: number } = { + [InputValidationType.Information]: 15000, + [InputValidationType.Warning]: 18000, + [InputValidationType.Error]: 20000 + }; private readonly defaultInputFontFamily = DEFAULT_FONT_FAMILY; @@ -1452,6 +1457,7 @@ class SCMInputWidget extends Disposable { private validation: IInputValidation | undefined; private validationDisposable: IDisposable = Disposable.None; + private _validationTimer: any; // This is due to "Setup height change listener on next tick" above // https://github.com/microsoft/vscode/issues/108067 @@ -1509,8 +1515,7 @@ class SCMInputWidget extends Disposable { const offset = position && textModel.getOffsetAt(position); const value = textModel.getValue(); - this.validation = await input.validateInput(value, offset || 0); - this.renderValidation(); + this.setValidation(await input.validateInput(value, offset || 0)); }; const triggerValidation = () => validationDelayer.trigger(validate); @@ -1536,6 +1541,8 @@ class SCMInputWidget extends Disposable { this.inputEditor.setPosition(position); this.inputEditor.revealPositionInCenterIfOutsideViewport(position); })); + this.repositoryDisposables.add(input.onDidChangeFocus(() => this.inputEditor.focus())); + this.repositoryDisposables.add(input.onDidChangeValidationMessage((e) => this.setValidation(e, { focus: true, timeout: true }))); // Keep API in sync with model, update placeholder visibility and validate const updatePlaceholderVisibility = () => this.placeholderTextContainer.classList.toggle('hidden', textModel.getValueLength() > 0); @@ -1594,6 +1601,23 @@ class SCMInputWidget extends Disposable { } } + private setValidation(validation: IInputValidation | undefined, options?: { focus?: boolean; timeout?: boolean }) { + if (this._validationTimer) { + clearTimeout(this._validationTimer); + this._validationTimer = 0; + } + + this.validation = validation; + if (options?.focus) { + this.inputEditor.focus(); + } + this.renderValidation(); + + if (validation && options?.timeout) { + this._validationTimer = setTimeout(() => this.setValidation(undefined), SCMInputWidget.ValidationTimeouts[validation.type]); + } + } + constructor( container: HTMLElement, overflowWidgetsDomNode: HTMLElement, diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index f9e77fdb961..66d80a359af 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -110,6 +110,12 @@ export interface ISCMInput { visible: boolean; readonly onDidChangeVisibility: Event; + setFocus(): void; + readonly onDidChangeFocus: Event; + + showValidationMessage(message: string, type: InputValidationType): void; + readonly onDidChangeValidationMessage: Event; + showNextHistoryValue(): void; showPreviousHistoryValue(): void; } diff --git a/src/vs/workbench/contrib/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts index 48af74fb989..a57d65a1aa4 100644 --- a/src/vs/workbench/contrib/scm/common/scmService.ts +++ b/src/vs/workbench/contrib/scm/common/scmService.ts @@ -5,7 +5,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason } from './scm'; +import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator, ISCMInputChangeEvent, SCMInputChangeReason, InputValidationType, IInputValidation } from './scm'; import { ILogService } from 'vs/platform/log/common/log'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; @@ -48,8 +48,22 @@ class SCMInput implements ISCMInput { } private readonly _onDidChangeVisibility = new Emitter(); - readonly onDidChangeVisibility: Event = this._onDidChangeVisibility - .event; + readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; + + setFocus(): void { + this._onDidChangeFocus.fire(); + } + + private readonly _onDidChangeFocus = new Emitter(); + readonly onDidChangeFocus: Event = this._onDidChangeFocus.event; + + showValidationMessage(message: string, type: InputValidationType): void { + this._onDidChangeValidationMessage.fire({ message: message, type: type }); + } + + private readonly _onDidChangeValidationMessage = new Emitter(); + readonly onDidChangeValidationMessage: Event = this._onDidChangeValidationMessage.event; + private _validateInput: IInputValidator = () => Promise.resolve(undefined);