Adds MarkdownString support to SCM validations
This commit is contained in:
parent
8c4f6d75d9
commit
a4e75b4ec1
8 changed files with 92 additions and 21 deletions
4
src/vs/vscode.proposed.d.ts
vendored
4
src/vs/vscode.proposed.d.ts
vendored
|
@ -784,7 +784,7 @@ declare module 'vscode' {
|
|||
/**
|
||||
* The validation message to display.
|
||||
*/
|
||||
readonly message: string;
|
||||
readonly message: string | MarkdownString;
|
||||
|
||||
/**
|
||||
* The validation type.
|
||||
|
@ -800,7 +800,7 @@ declare module 'vscode' {
|
|||
/**
|
||||
* Shows a transient contextual message on the input.
|
||||
*/
|
||||
showValidationMessage(message: string, type: SourceControlInputBoxValidationType): void;
|
||||
showValidationMessage(message: string | MarkdownString, type: SourceControlInputBoxValidationType): void;
|
||||
|
||||
/**
|
||||
* A validation function for the input box. It's possible to change
|
||||
|
|
|
@ -14,6 +14,7 @@ import { ISplice, Sequence } from 'vs/base/common/sequence';
|
|||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { MarshalledId } from 'vs/base/common/marshalling';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
class MainThreadSCMResourceGroup implements ISCMResourceGroup {
|
||||
|
||||
|
@ -438,7 +439,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
|
|||
repository.input.setFocus();
|
||||
}
|
||||
|
||||
$showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType) {
|
||||
$showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType) {
|
||||
const repository = this._repositories.get(sourceControlHandle);
|
||||
if (!repository) {
|
||||
return;
|
||||
|
|
|
@ -1082,7 +1082,7 @@ export interface MainThreadSCMShape extends IDisposable {
|
|||
$setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void;
|
||||
$setInputBoxVisibility(sourceControlHandle: number, visible: boolean): void;
|
||||
$setInputBoxFocus(sourceControlHandle: number): void;
|
||||
$showValidationMessage(sourceControlHandle: number, message: string, type: InputValidationType): void;
|
||||
$showValidationMessage(sourceControlHandle: number, message: string | IMarkdownString, type: InputValidationType): void;
|
||||
$setValidationProviderIsEnabled(sourceControlHandle: number, enabled: boolean): void;
|
||||
}
|
||||
|
||||
|
@ -1757,7 +1757,7 @@ export interface ExtHostSCMShape {
|
|||
$provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise<UriComponents | null>;
|
||||
$onInputBoxValueChange(sourceControlHandle: number, value: string): void;
|
||||
$executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number, preserveFocus: boolean): Promise<void>;
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined>;
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined>;
|
||||
$setSelectedSourceControl(selectedSourceControlHandle: number | undefined): Promise<void>;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensio
|
|||
import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { MarshalledId } from 'vs/base/common/marshalling';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { MarkdownString } from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
|
||||
type ProviderHandle = number;
|
||||
type GroupHandle = number;
|
||||
|
@ -275,7 +277,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox {
|
|||
this._proxy.$setInputBoxFocus(this._sourceControlHandle);
|
||||
}
|
||||
|
||||
showValidationMessage(message: string, type: vscode.SourceControlInputBoxValidationType) {
|
||||
showValidationMessage(message: string | vscode.MarkdownString, type: vscode.SourceControlInputBoxValidationType) {
|
||||
checkProposedApiEnabled(this._extension);
|
||||
|
||||
this._proxy.$showValidationMessage(this._sourceControlHandle, message, type as any);
|
||||
|
@ -770,7 +772,7 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
|||
return group.$executeResourceCommand(handle, preserveFocus);
|
||||
}
|
||||
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined> {
|
||||
$validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string | IMarkdownString, number] | undefined> {
|
||||
this.logService.trace('ExtHostSCM#$validateInput', sourceControlHandle);
|
||||
|
||||
const sourceControl = this._sourceControls.get(sourceControlHandle);
|
||||
|
@ -788,7 +790,12 @@ export class ExtHostSCM implements ExtHostSCMShape {
|
|||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return Promise.resolve<[string, number]>([result.message, result.type]);
|
||||
const message = MarkdownString.fromStrict(result.message);
|
||||
if (!message) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return Promise.resolve<[string | IMarkdownString, number]>([message, result.type]);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -215,6 +215,16 @@
|
|||
border-top: none;
|
||||
}
|
||||
|
||||
.scm-editor-validation p {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.scm-editor-validation a {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.scm-view .scm-editor-placeholder {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Event, Emitter } from 'vs/base/common/event';
|
|||
import { basename, dirname } from 'vs/base/common/resources';
|
||||
import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { append, $, Dimension, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { append, $, Dimension, asCSSUrl, trackFocus } from 'vs/base/browser/dom';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
|
@ -22,7 +22,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
|||
import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IAction, ActionRunner } from 'vs/base/common/actions';
|
||||
import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IThemeService, registerThemingParticipant, IFileIconTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IThemeService, registerThemingParticipant, IFileIconTheme, ThemeIcon, IColorTheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
|
||||
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider } from './util';
|
||||
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
|
||||
import { WorkbenchCompressibleObjectTree, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
|
@ -56,7 +56,7 @@ import { SelectionClipboardContributionID } from 'vs/workbench/contrib/codeEdito
|
|||
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { compare, format } from 'vs/base/common/strings';
|
||||
import { inputPlaceholderForeground, inputValidationInfoBorder, inputValidationWarningBorder, inputValidationErrorBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBackground, inputValidationErrorForeground, inputBackground, inputForeground, inputBorder, focusBorder, registerColor, contrastBorder, editorSelectionBackground, selectionBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { inputPlaceholderForeground, inputValidationInfoBorder, inputValidationWarningBorder, inputValidationErrorBorder, inputValidationInfoBackground, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningForeground, inputValidationErrorBackground, inputValidationErrorForeground, inputBackground, inputForeground, inputBorder, focusBorder, registerColor, contrastBorder, editorSelectionBackground, selectionBackground, textLinkActiveForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
|
@ -81,6 +81,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
|||
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer';
|
||||
|
||||
type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
|
||||
|
||||
|
@ -1470,6 +1471,7 @@ class SCMInputWidget extends Disposable {
|
|||
|
||||
private validation: IInputValidation | undefined;
|
||||
private validationDisposable: IDisposable = Disposable.None;
|
||||
private validationHasFocus: boolean = false;
|
||||
private _validationTimer: any;
|
||||
|
||||
// This is due to "Setup height change listener on next tick" above
|
||||
|
@ -1488,7 +1490,7 @@ class SCMInputWidget extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
this.validationDisposable.dispose();
|
||||
this.clearValidation();
|
||||
this.editorContainer.classList.remove('synthetic-focus');
|
||||
|
||||
this.repositoryDisposables.dispose();
|
||||
|
@ -1556,6 +1558,7 @@ class SCMInputWidget extends Disposable {
|
|||
}));
|
||||
this.repositoryDisposables.add(input.onDidChangeFocus(() => this.focus()));
|
||||
this.repositoryDisposables.add(input.onDidChangeValidationMessage((e) => this.setValidation(e, { focus: true, timeout: true })));
|
||||
this.repositoryDisposables.add(input.onDidChangeValidateInput((e) => triggerValidation()));
|
||||
|
||||
// Keep API in sync with model, update placeholder visibility and validate
|
||||
const updatePlaceholderVisibility = () => this.placeholderTextContainer.classList.toggle('hidden', textModel.getValueLength() > 0);
|
||||
|
@ -1640,9 +1643,10 @@ class SCMInputWidget extends Disposable {
|
|||
@IModeService private modeService: IModeService,
|
||||
@IKeybindingService private keybindingService: IKeybindingService,
|
||||
@IConfigurationService private configurationService: IConfigurationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ISCMViewService private readonly scmViewService: ISCMViewService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IOpenerService private readonly openerService: IOpenerService,
|
||||
) {
|
||||
super();
|
||||
|
||||
|
@ -1705,7 +1709,12 @@ class SCMInputWidget extends Disposable {
|
|||
}));
|
||||
this._register(this.inputEditor.onDidBlurEditorText(() => {
|
||||
this.editorContainer.classList.remove('synthetic-focus');
|
||||
this.validationDisposable.dispose();
|
||||
|
||||
setTimeout(() => {
|
||||
if (!this.validation || !this.validationHasFocus) {
|
||||
this.clearValidation();
|
||||
}
|
||||
}, 0);
|
||||
}));
|
||||
|
||||
const firstLineKey = contextKeyService2.createKey('scmInputIsInFirstPosition', false);
|
||||
|
@ -1778,7 +1787,7 @@ class SCMInputWidget extends Disposable {
|
|||
}
|
||||
|
||||
private renderValidation(): void {
|
||||
this.validationDisposable.dispose();
|
||||
this.clearValidation();
|
||||
|
||||
this.editorContainer.classList.toggle('validation-info', this.validation?.type === InputValidationType.Information);
|
||||
this.editorContainer.classList.toggle('validation-warning', this.validation?.type === InputValidationType.Warning);
|
||||
|
@ -1788,6 +1797,8 @@ class SCMInputWidget extends Disposable {
|
|||
return;
|
||||
}
|
||||
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
this.validationDisposable = this.contextViewService.showContextView({
|
||||
getAnchor: () => this.editorContainer,
|
||||
render: container => {
|
||||
|
@ -1796,9 +1807,36 @@ class SCMInputWidget extends Disposable {
|
|||
element.classList.toggle('validation-warning', this.validation!.type === InputValidationType.Warning);
|
||||
element.classList.toggle('validation-error', this.validation!.type === InputValidationType.Error);
|
||||
element.style.width = `${this.editorContainer.clientWidth}px`;
|
||||
element.textContent = this.validation!.message;
|
||||
|
||||
const message = this.validation!.message;
|
||||
if (typeof message === 'string') {
|
||||
element.textContent = message;
|
||||
} else {
|
||||
const tracker = trackFocus(element);
|
||||
disposables.add(tracker);
|
||||
disposables.add(tracker.onDidFocus(() => (this.validationHasFocus = true)));
|
||||
disposables.add(tracker.onDidBlur(() => {
|
||||
this.validationHasFocus = false;
|
||||
this.contextViewService.hideContextView();
|
||||
}));
|
||||
|
||||
const { element: mdElement } = this.instantiationService.createInstance(MarkdownRenderer, {}).render(message, {
|
||||
actionHandler: {
|
||||
callback: (content) => {
|
||||
this.openerService.open(content, { allowCommands: typeof message !== 'string' && message.isTrusted });
|
||||
this.contextViewService.hideContextView();
|
||||
},
|
||||
disposeables: disposables
|
||||
},
|
||||
});
|
||||
element.appendChild(mdElement);
|
||||
}
|
||||
return Disposable.None;
|
||||
},
|
||||
onHide: () => {
|
||||
this.validationHasFocus = false;
|
||||
disposables.dispose();
|
||||
},
|
||||
anchorAlignment: AnchorAlignment.LEFT
|
||||
});
|
||||
}
|
||||
|
@ -1833,16 +1871,29 @@ class SCMInputWidget extends Disposable {
|
|||
|
||||
clearValidation(): void {
|
||||
this.validationDisposable.dispose();
|
||||
this.validationHasFocus = false;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
this.input = undefined;
|
||||
this.repositoryDisposables.dispose();
|
||||
this.validationDisposable.dispose();
|
||||
this.clearValidation();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
const link = theme.getColor(textLinkForeground);
|
||||
if (link) {
|
||||
collector.addRule(`.scm-editor-validation a { color: ${link}; }`);
|
||||
}
|
||||
|
||||
const activeLink = theme.getColor(textLinkActiveForeground);
|
||||
if (activeLink) {
|
||||
collector.addRule(`.scm-editor-validation a:active, .scm-editor-validation a:hover { color: ${activeLink}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
export class SCMViewPane extends ViewPane {
|
||||
|
||||
private _onDidLayout: Emitter<void>;
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ISequence } from 'vs/base/common/sequence';
|
|||
import { IAction } from 'vs/base/common/actions';
|
||||
import { IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.scm';
|
||||
export const VIEW_PANE_ID = 'workbench.scm';
|
||||
|
@ -77,7 +78,7 @@ export const enum InputValidationType {
|
|||
}
|
||||
|
||||
export interface IInputValidation {
|
||||
message: string;
|
||||
message: string | IMarkdownString;
|
||||
type: InputValidationType;
|
||||
}
|
||||
|
||||
|
@ -114,7 +115,7 @@ export interface ISCMInput {
|
|||
setFocus(): void;
|
||||
readonly onDidChangeFocus: Event<void>;
|
||||
|
||||
showValidationMessage(message: string, type: InputValidationType): void;
|
||||
showValidationMessage(message: string | IMarkdownString, type: InputValidationType): void;
|
||||
readonly onDidChangeValidationMessage: Event<IInputValidation>;
|
||||
|
||||
showNextHistoryValue(): void;
|
||||
|
|
|
@ -10,6 +10,7 @@ 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';
|
||||
import { HistoryNavigator2 } from 'vs/base/common/history';
|
||||
import { IMarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
class SCMInput implements ISCMInput {
|
||||
|
||||
|
@ -57,7 +58,7 @@ class SCMInput implements ISCMInput {
|
|||
private readonly _onDidChangeFocus = new Emitter<void>();
|
||||
readonly onDidChangeFocus: Event<void> = this._onDidChangeFocus.event;
|
||||
|
||||
showValidationMessage(message: string, type: InputValidationType): void {
|
||||
showValidationMessage(message: string | IMarkdownString, type: InputValidationType): void {
|
||||
this._onDidChangeValidationMessage.fire({ message: message, type: type });
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue