Add multiline text editors in settings editor (#127118)

Also adds an editPresentation prop with descriptions to the schema.
This commit is contained in:
Raymond Zhao 2021-07-07 14:35:16 -07:00 committed by GitHub
parent f35bf81919
commit be81d88a5e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 113 additions and 18 deletions

View file

@ -11,6 +11,11 @@ import * as types from 'vs/base/common/types';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IStringDictionary } from 'vs/base/common/collections';
export enum EditPresentationTypes {
Multiline = 'multilineText',
Singleline = 'singlelineText'
}
export const Extensions = {
Configuration: 'base.contributions.configuration'
};
@ -133,6 +138,12 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
disallowSyncIgnore?: boolean;
enumItemLabels?: string[];
/**
* When specified, controls the presentation format of string settings.
* Otherwise, the presentation format defaults to `singleline`.
*/
editPresentation?: EditPresentationTypes;
}
export interface IConfigurationExtensionInfo {

View file

@ -81,6 +81,16 @@ const configurationEntrySchema: IJSONSchema = {
markdownDeprecationMessage: {
type: 'string',
description: nls.localize('scope.markdownDeprecationMessage', 'If set, the property is marked as deprecated and the given message is shown as an explanation in the markdown format.')
},
editPresentation: {
type: 'string',
enum: ['singlelineText', 'multilineText'],
enumDescriptions: [
nls.localize('scope.singlelineText.description', 'The value will be shown in an inputbox.'),
nls.localize('scope.multilineText.description', 'The value will be shown in a textarea.')
],
default: 'singlelineText',
description: nls.localize('scope.editPresentation', 'When specified, controls the presentation format of the string setting.')
}
}
}

View file

@ -40,7 +40,7 @@ import { IEditorMemento, IEditorOpenContext, IEditorPane } from 'vs/workbench/co
import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput';
import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets';
import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout';
import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, resolveExtensionsSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree';
import { AbstractSettingRenderer, HeightChangeParams, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveConfiguredUntrustedSettings, resolveExtensionsSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree';
import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels';
import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets';
import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree';
@ -195,7 +195,7 @@ export class SettingsEditor2 extends EditorPane {
@IEditorGroupsService protected editorGroupService: IEditorGroupsService,
@IUserDataSyncWorkbenchService private readonly userDataSyncWorkbenchService: IUserDataSyncWorkbenchService,
@IUserDataAutoSyncEnablementService private readonly userDataAutoSyncEnablementService: IUserDataAutoSyncEnablementService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService,
@IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService
) {
super(SettingsEditor2.ID, telemetryService, themeService, storageService);
this.delayedFilterLogging = new Delayer<void>(1000);
@ -741,6 +741,14 @@ export class SettingsEditor2 extends EditorPane {
this.searchWidget.setValue(element.targetKey);
}));
this._register(this.settingRenderers.onDidChangeSettingHeight((params: HeightChangeParams) => {
const { element, height } = params;
try {
this.settingsTree.updateElementHeight(element, height);
} catch (e) {
// the element was not found
}
}));
this.settingsTree = this._register(this.instantiationService.createInstance(SettingsTree,
this.settingsTreeContainer,

View file

@ -10,7 +10,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { alert as ariaAlert } from 'vs/base/browser/ui/aria/aria';
import { Button } from 'vs/base/browser/ui/button/button';
import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { CachedListVirtualDelegate } from 'vs/base/browser/ui/list/list';
import { DefaultStyleController, IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ISelectOptionItem, SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
@ -545,6 +545,7 @@ interface IGroupTitleTemplate extends IDisposableTemplate {
const SETTINGS_UNTRUSTED_TEMPLATE_ID = 'settings.untrusted.template';
const SETTINGS_TEXT_TEMPLATE_ID = 'settings.text.template';
const SETTINGS_MULTILINE_TEXT_TEMPLATE_ID = 'settings.multilineText.template';
const SETTINGS_NUMBER_TEMPLATE_ID = 'settings.number.template';
const SETTINGS_ENUM_TEMPLATE_ID = 'settings.enum.template';
const SETTINGS_BOOL_TEMPLATE_ID = 'settings.bool.template';
@ -600,6 +601,11 @@ function addChildrenToTabOrder(node: Element): void {
});
}
export interface HeightChangeParams {
element: SettingsTreeElement;
height: number;
}
export abstract class AbstractSettingRenderer extends Disposable implements ITreeRenderer<SettingsTreeElement, never, any> {
/** To override */
abstract get templateId(): string;
@ -633,6 +639,9 @@ export abstract class AbstractSettingRenderer extends Disposable implements ITre
private readonly _onDidChangeIgnoredSettings = this._register(new Emitter<void>());
readonly onDidChangeIgnoredSettings: Event<void> = this._onDidChangeIgnoredSettings.event;
protected readonly _onDidChangeSettingHeight = this._register(new Emitter<HeightChangeParams>());
readonly onDidChangeSettingHeight: Event<HeightChangeParams> = this._onDidChangeSettingHeight.event;
constructor(
private readonly settingActions: IAction[],
private readonly disposableActionFactory: (setting: ISetting) => IAction[],
@ -1417,14 +1426,19 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I
}
}
export class SettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
templateId = SETTINGS_TEXT_TEMPLATE_ID;
abstract class AbstractSettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
private readonly MULTILINE_MAX_HEIGHT = 150;
renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {
renderTemplate(_container: HTMLElement, useMultiline?: boolean): ISettingTextItemTemplate {
const common = this.renderCommonTemplate(null, _container, 'text');
const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message'));
const inputBox = new InputBox(common.controlElement, this._contextViewService);
const inputBoxOptions: IInputOptions = {
flexibleHeight: useMultiline,
flexibleWidth: false,
flexibleMaxHeight: this.MULTILINE_MAX_HEIGHT
};
const inputBox = new InputBox(common.controlElement, this._contextViewService, inputBoxOptions);
common.toDispose.add(inputBox);
common.toDispose.add(attachInputBoxStyler(inputBox, this._themeService, {
inputBackground: settingsTextInputBackground,
@ -1441,14 +1455,6 @@ export class SettingTextRenderer extends AbstractSettingRenderer implements ITre
inputBox.inputElement.classList.add(AbstractSettingRenderer.CONTROL_CLASS);
inputBox.inputElement.tabIndex = 0;
// TODO@9at8: listWidget filters out all key events from input boxes, so we need to come up with a better way
// Disable ArrowUp and ArrowDown behaviour in favor of list navigation
common.toDispose.add(DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, e => {
if (e.equals(KeyCode.UpArrow) || e.equals(KeyCode.DownArrow)) {
e.preventDefault();
}
}));
const template: ISettingTextItemTemplate = {
...common,
inputBox,
@ -1477,6 +1483,50 @@ export class SettingTextRenderer extends AbstractSettingRenderer implements ITre
}
}
export class SettingTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
templateId = SETTINGS_TEXT_TEMPLATE_ID;
override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {
const template = super.renderTemplate(_container, false);
// TODO@9at8: listWidget filters out all key events from input boxes, so we need to come up with a better way
// Disable ArrowUp and ArrowDown behaviour in favor of list navigation
template.toDispose.add(DOM.addStandardDisposableListener(template.inputBox.inputElement, DOM.EventType.KEY_DOWN, e => {
if (e.equals(KeyCode.UpArrow) || e.equals(KeyCode.DownArrow)) {
e.preventDefault();
}
}));
return template;
}
}
export class SettingMultilineTextRenderer extends AbstractSettingTextRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingTextItemTemplate> {
templateId = SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;
override renderTemplate(_container: HTMLElement): ISettingTextItemTemplate {
return super.renderTemplate(_container, true);
}
protected override renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void) {
super.renderValue(dataElement, template, onChange);
template.toDispose.add(
template.inputBox.onDidHeightChange(e => {
const height = template.containerElement.clientHeight;
// Don't fire event if height is reported as 0,
// which sometimes happens when clicking onto a new setting.
if (height) {
this._onDidChangeSettingHeight.fire({
element: dataElement,
height: template.containerElement.clientHeight
});
}
})
);
template.inputBox.layout();
}
}
export class SettingEnumRenderer extends AbstractSettingRenderer implements ITreeRenderer<SettingsTreeSettingElement, never, ISettingEnumItemTemplate> {
templateId = SETTINGS_ENUM_TEMPLATE_ID;
@ -1770,6 +1820,8 @@ export class SettingTreeRenderers {
readonly onDidFocusSetting: Event<SettingsTreeSettingElement>;
readonly onDidChangeSettingHeight: Event<HeightChangeParams>;
readonly allRenderers: ITreeRenderer<SettingsTreeElement, never, any>[];
private readonly settingActions: IAction[];
@ -1800,6 +1852,7 @@ export class SettingTreeRenderers {
this._instantiationService.createInstance(SettingArrayRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingComplexRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingTextRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingMultilineTextRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingExcludeRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingEnumRenderer, this.settingActions, actionFactory),
this._instantiationService.createInstance(SettingObjectRenderer, this.settingActions, actionFactory),
@ -1815,6 +1868,7 @@ export class SettingTreeRenderers {
this.onDidOpenSettings = Event.any(...settingRenderers.map(r => r.onDidOpenSettings));
this.onDidClickSettingLink = Event.any(...settingRenderers.map(r => r.onDidClickSettingLink));
this.onDidFocusSetting = Event.any(...settingRenderers.map(r => r.onDidFocusSetting));
this.onDidChangeSettingHeight = Event.any(...settingRenderers.map(r => r.onDidChangeSettingHeight));
this.allRenderers = [
...settingRenderers,
@ -2032,6 +2086,10 @@ class SettingsTreeDelegate extends CachedListVirtualDelegate<SettingsTreeGroupCh
return SETTINGS_NUMBER_TEMPLATE_ID;
}
if (element.valueType === SettingValueType.MultilineString) {
return SETTINGS_MULTILINE_TEXT_TEMPLATE_ID;
}
if (element.valueType === SettingValueType.String) {
return SETTINGS_TEXT_TEMPLATE_ID;
}

View file

@ -18,6 +18,7 @@ import { FOLDER_SCOPES, WORKSPACE_SCOPES, REMOTE_MACHINE_SCOPES, LOCAL_MACHINE_S
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
import { EditPresentationTypes } from 'vs/platform/configuration/common/configurationRegistry';
export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices';
@ -227,7 +228,11 @@ export class SettingsTreeSettingElement extends SettingsTreeElement {
if (this.setting.enum && (!this.setting.type || settingTypeEnumRenderable(this.setting.type))) {
this.valueType = SettingValueType.Enum;
} else if (this.setting.type === 'string') {
this.valueType = SettingValueType.String;
if (this.setting.editPresentation === EditPresentationTypes.Multiline) {
this.valueType = SettingValueType.MultilineString;
} else {
this.valueType = SettingValueType.String;
}
} else if (isExcludeSetting(this.setting)) {
this.valueType = SettingValueType.Exclude;
} else if (this.setting.type === 'integer') {

View file

@ -11,7 +11,7 @@ import { IJSONSchemaMap, IJSONSchema } from 'vs/base/common/jsonSchema';
import { ITextModel } from 'vs/editor/common/model';
import { localize } from 'vs/nls';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ConfigurationScope, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry';
import { ConfigurationScope, EditPresentationTypes, IConfigurationExtensionInfo } from 'vs/platform/configuration/common/configurationRegistry';
import { EditorResolution, IEditorOptions } from 'vs/platform/editor/common/editor';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
@ -26,6 +26,7 @@ export enum SettingValueType {
Null = 'null',
Enum = 'enum',
String = 'string',
MultilineString = 'multiline-string',
Integer = 'integer',
Number = 'number',
Boolean = 'boolean',
@ -84,6 +85,7 @@ export interface ISetting {
validator?: (value: any) => string | null;
enumItemLabels?: string[];
allKeysAreBoolean?: boolean;
editPresentation?: EditPresentationTypes;
}
export interface IExtensionSetting extends ISetting {

View file

@ -669,7 +669,8 @@ export class DefaultSettings extends Disposable {
deprecationMessageIsMarkdown: !!prop.markdownDeprecationMessage,
validator: createValidator(prop),
enumItemLabels: prop.enumItemLabels,
allKeysAreBoolean
allKeysAreBoolean,
editPresentation: prop.editPresentation
});
}
}