UNPKG

monaco-editor-core

Version:

A browser based code editor

1,062 lines (1,061 loc) • 178 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as arrays from '../../../base/common/arrays.js'; import * as objects from '../../../base/common/objects.js'; import * as platform from '../../../base/common/platform.js'; import { EDITOR_MODEL_DEFAULTS } from '../core/textModelDefaults.js'; import { USUAL_WORD_SEPARATORS } from '../core/wordHelper.js'; import * as nls from '../../../nls.js'; /** * @internal * The width of the minimap gutter, in pixels. */ export const MINIMAP_GUTTER_WIDTH = 8; //#endregion /** * An event describing that the configuration of the editor has changed. */ export class ConfigurationChangedEvent { /** * @internal */ constructor(values) { this._values = values; } hasChanged(id) { return this._values[id]; } } /** * @internal */ export class ComputeOptionsMemory { constructor() { this.stableMinimapLayoutInput = null; this.stableFitMaxMinimapScale = 0; this.stableFitRemainingWidth = 0; } } /** * @internal */ class BaseEditorOption { constructor(id, name, defaultValue, schema) { this.id = id; this.name = name; this.defaultValue = defaultValue; this.schema = schema; } applyUpdate(value, update) { return applyUpdate(value, update); } compute(env, options, value) { return value; } } export class ApplyUpdateResult { constructor(newValue, didChange) { this.newValue = newValue; this.didChange = didChange; } } function applyUpdate(value, update) { if (typeof value !== 'object' || typeof update !== 'object' || !value || !update) { return new ApplyUpdateResult(update, value !== update); } if (Array.isArray(value) || Array.isArray(update)) { const arrayEquals = Array.isArray(value) && Array.isArray(update) && arrays.equals(value, update); return new ApplyUpdateResult(update, !arrayEquals); } let didChange = false; for (const key in update) { if (update.hasOwnProperty(key)) { const result = applyUpdate(value[key], update[key]); if (result.didChange) { value[key] = result.newValue; didChange = true; } } } return new ApplyUpdateResult(value, didChange); } /** * @internal */ class ComputedEditorOption { constructor(id) { this.schema = undefined; this.id = id; this.name = '_never_'; this.defaultValue = undefined; } applyUpdate(value, update) { return applyUpdate(value, update); } validate(input) { return this.defaultValue; } } class SimpleEditorOption { constructor(id, name, defaultValue, schema) { this.id = id; this.name = name; this.defaultValue = defaultValue; this.schema = schema; } applyUpdate(value, update) { return applyUpdate(value, update); } validate(input) { if (typeof input === 'undefined') { return this.defaultValue; } return input; } compute(env, options, value) { return value; } } /** * @internal */ export function boolean(value, defaultValue) { if (typeof value === 'undefined') { return defaultValue; } if (value === 'false') { // treat the string 'false' as false return false; } return Boolean(value); } class EditorBooleanOption extends SimpleEditorOption { constructor(id, name, defaultValue, schema = undefined) { if (typeof schema !== 'undefined') { schema.type = 'boolean'; schema.default = defaultValue; } super(id, name, defaultValue, schema); } validate(input) { return boolean(input, this.defaultValue); } } /** * @internal */ export function clampedInt(value, defaultValue, minimum, maximum) { if (typeof value === 'undefined') { return defaultValue; } let r = parseInt(value, 10); if (isNaN(r)) { return defaultValue; } r = Math.max(minimum, r); r = Math.min(maximum, r); return r | 0; } class EditorIntOption extends SimpleEditorOption { static clampedInt(value, defaultValue, minimum, maximum) { return clampedInt(value, defaultValue, minimum, maximum); } constructor(id, name, defaultValue, minimum, maximum, schema = undefined) { if (typeof schema !== 'undefined') { schema.type = 'integer'; schema.default = defaultValue; schema.minimum = minimum; schema.maximum = maximum; } super(id, name, defaultValue, schema); this.minimum = minimum; this.maximum = maximum; } validate(input) { return EditorIntOption.clampedInt(input, this.defaultValue, this.minimum, this.maximum); } } /** * @internal */ export function clampedFloat(value, defaultValue, minimum, maximum) { if (typeof value === 'undefined') { return defaultValue; } const r = EditorFloatOption.float(value, defaultValue); return EditorFloatOption.clamp(r, minimum, maximum); } class EditorFloatOption extends SimpleEditorOption { static clamp(n, min, max) { if (n < min) { return min; } if (n > max) { return max; } return n; } static float(value, defaultValue) { if (typeof value === 'number') { return value; } if (typeof value === 'undefined') { return defaultValue; } const r = parseFloat(value); return (isNaN(r) ? defaultValue : r); } constructor(id, name, defaultValue, validationFn, schema) { if (typeof schema !== 'undefined') { schema.type = 'number'; schema.default = defaultValue; } super(id, name, defaultValue, schema); this.validationFn = validationFn; } validate(input) { return this.validationFn(EditorFloatOption.float(input, this.defaultValue)); } } class EditorStringOption extends SimpleEditorOption { static string(value, defaultValue) { if (typeof value !== 'string') { return defaultValue; } return value; } constructor(id, name, defaultValue, schema = undefined) { if (typeof schema !== 'undefined') { schema.type = 'string'; schema.default = defaultValue; } super(id, name, defaultValue, schema); } validate(input) { return EditorStringOption.string(input, this.defaultValue); } } /** * @internal */ export function stringSet(value, defaultValue, allowedValues, renamedValues) { if (typeof value !== 'string') { return defaultValue; } if (renamedValues && value in renamedValues) { return renamedValues[value]; } if (allowedValues.indexOf(value) === -1) { return defaultValue; } return value; } class EditorStringEnumOption extends SimpleEditorOption { constructor(id, name, defaultValue, allowedValues, schema = undefined) { if (typeof schema !== 'undefined') { schema.type = 'string'; schema.enum = allowedValues; schema.default = defaultValue; } super(id, name, defaultValue, schema); this._allowedValues = allowedValues; } validate(input) { return stringSet(input, this.defaultValue, this._allowedValues); } } class EditorEnumOption extends BaseEditorOption { constructor(id, name, defaultValue, defaultStringValue, allowedValues, convert, schema = undefined) { if (typeof schema !== 'undefined') { schema.type = 'string'; schema.enum = allowedValues; schema.default = defaultStringValue; } super(id, name, defaultValue, schema); this._allowedValues = allowedValues; this._convert = convert; } validate(input) { if (typeof input !== 'string') { return this.defaultValue; } if (this._allowedValues.indexOf(input) === -1) { return this.defaultValue; } return this._convert(input); } } //#endregion //#region autoIndent function _autoIndentFromString(autoIndent) { switch (autoIndent) { case 'none': return 0 /* EditorAutoIndentStrategy.None */; case 'keep': return 1 /* EditorAutoIndentStrategy.Keep */; case 'brackets': return 2 /* EditorAutoIndentStrategy.Brackets */; case 'advanced': return 3 /* EditorAutoIndentStrategy.Advanced */; case 'full': return 4 /* EditorAutoIndentStrategy.Full */; } } //#endregion //#region accessibilitySupport class EditorAccessibilitySupport extends BaseEditorOption { constructor() { super(2 /* EditorOption.accessibilitySupport */, 'accessibilitySupport', 0 /* AccessibilitySupport.Unknown */, { type: 'string', enum: ['auto', 'on', 'off'], enumDescriptions: [ nls.localize('accessibilitySupport.auto', "Use platform APIs to detect when a Screen Reader is attached."), nls.localize('accessibilitySupport.on', "Optimize for usage with a Screen Reader."), nls.localize('accessibilitySupport.off', "Assume a screen reader is not attached."), ], default: 'auto', tags: ['accessibility'], description: nls.localize('accessibilitySupport', "Controls if the UI should run in a mode where it is optimized for screen readers.") }); } validate(input) { switch (input) { case 'auto': return 0 /* AccessibilitySupport.Unknown */; case 'off': return 1 /* AccessibilitySupport.Disabled */; case 'on': return 2 /* AccessibilitySupport.Enabled */; } return this.defaultValue; } compute(env, options, value) { if (value === 0 /* AccessibilitySupport.Unknown */) { // The editor reads the `accessibilitySupport` from the environment return env.accessibilitySupport; } return value; } } class EditorComments extends BaseEditorOption { constructor() { const defaults = { insertSpace: true, ignoreEmptyLines: true, }; super(23 /* EditorOption.comments */, 'comments', defaults, { 'editor.comments.insertSpace': { type: 'boolean', default: defaults.insertSpace, description: nls.localize('comments.insertSpace', "Controls whether a space character is inserted when commenting.") }, 'editor.comments.ignoreEmptyLines': { type: 'boolean', default: defaults.ignoreEmptyLines, description: nls.localize('comments.ignoreEmptyLines', 'Controls if empty lines should be ignored with toggle, add or remove actions for line comments.') }, }); } validate(_input) { if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input; return { insertSpace: boolean(input.insertSpace, this.defaultValue.insertSpace), ignoreEmptyLines: boolean(input.ignoreEmptyLines, this.defaultValue.ignoreEmptyLines), }; } } function _cursorBlinkingStyleFromString(cursorBlinkingStyle) { switch (cursorBlinkingStyle) { case 'blink': return 1 /* TextEditorCursorBlinkingStyle.Blink */; case 'smooth': return 2 /* TextEditorCursorBlinkingStyle.Smooth */; case 'phase': return 3 /* TextEditorCursorBlinkingStyle.Phase */; case 'expand': return 4 /* TextEditorCursorBlinkingStyle.Expand */; case 'solid': return 5 /* TextEditorCursorBlinkingStyle.Solid */; } } //#endregion //#region cursorStyle /** * The style in which the editor's cursor should be rendered. */ export var TextEditorCursorStyle; (function (TextEditorCursorStyle) { /** * As a vertical line (sitting between two characters). */ TextEditorCursorStyle[TextEditorCursorStyle["Line"] = 1] = "Line"; /** * As a block (sitting on top of a character). */ TextEditorCursorStyle[TextEditorCursorStyle["Block"] = 2] = "Block"; /** * As a horizontal line (sitting under a character). */ TextEditorCursorStyle[TextEditorCursorStyle["Underline"] = 3] = "Underline"; /** * As a thin vertical line (sitting between two characters). */ TextEditorCursorStyle[TextEditorCursorStyle["LineThin"] = 4] = "LineThin"; /** * As an outlined block (sitting on top of a character). */ TextEditorCursorStyle[TextEditorCursorStyle["BlockOutline"] = 5] = "BlockOutline"; /** * As a thin horizontal line (sitting under a character). */ TextEditorCursorStyle[TextEditorCursorStyle["UnderlineThin"] = 6] = "UnderlineThin"; })(TextEditorCursorStyle || (TextEditorCursorStyle = {})); function _cursorStyleFromString(cursorStyle) { switch (cursorStyle) { case 'line': return TextEditorCursorStyle.Line; case 'block': return TextEditorCursorStyle.Block; case 'underline': return TextEditorCursorStyle.Underline; case 'line-thin': return TextEditorCursorStyle.LineThin; case 'block-outline': return TextEditorCursorStyle.BlockOutline; case 'underline-thin': return TextEditorCursorStyle.UnderlineThin; } } //#endregion //#region editorClassName class EditorClassName extends ComputedEditorOption { constructor() { super(143 /* EditorOption.editorClassName */); } compute(env, options, _) { const classNames = ['monaco-editor']; if (options.get(39 /* EditorOption.extraEditorClassName */)) { classNames.push(options.get(39 /* EditorOption.extraEditorClassName */)); } if (env.extraEditorClassName) { classNames.push(env.extraEditorClassName); } if (options.get(74 /* EditorOption.mouseStyle */) === 'default') { classNames.push('mouse-default'); } else if (options.get(74 /* EditorOption.mouseStyle */) === 'copy') { classNames.push('mouse-copy'); } if (options.get(112 /* EditorOption.showUnused */)) { classNames.push('showUnused'); } if (options.get(141 /* EditorOption.showDeprecated */)) { classNames.push('showDeprecated'); } return classNames.join(' '); } } //#endregion //#region emptySelectionClipboard class EditorEmptySelectionClipboard extends EditorBooleanOption { constructor() { super(37 /* EditorOption.emptySelectionClipboard */, 'emptySelectionClipboard', true, { description: nls.localize('emptySelectionClipboard', "Controls whether copying without a selection copies the current line.") }); } compute(env, options, value) { return value && env.emptySelectionClipboard; } } class EditorFind extends BaseEditorOption { constructor() { const defaults = { cursorMoveOnType: true, seedSearchStringFromSelection: 'always', autoFindInSelection: 'never', globalFindClipboard: false, addExtraSpaceOnTop: true, loop: true }; super(41 /* EditorOption.find */, 'find', defaults, { 'editor.find.cursorMoveOnType': { type: 'boolean', default: defaults.cursorMoveOnType, description: nls.localize('find.cursorMoveOnType', "Controls whether the cursor should jump to find matches while typing.") }, 'editor.find.seedSearchStringFromSelection': { type: 'string', enum: ['never', 'always', 'selection'], default: defaults.seedSearchStringFromSelection, enumDescriptions: [ nls.localize('editor.find.seedSearchStringFromSelection.never', 'Never seed search string from the editor selection.'), nls.localize('editor.find.seedSearchStringFromSelection.always', 'Always seed search string from the editor selection, including word at cursor position.'), nls.localize('editor.find.seedSearchStringFromSelection.selection', 'Only seed search string from the editor selection.') ], description: nls.localize('find.seedSearchStringFromSelection', "Controls whether the search string in the Find Widget is seeded from the editor selection.") }, 'editor.find.autoFindInSelection': { type: 'string', enum: ['never', 'always', 'multiline'], default: defaults.autoFindInSelection, enumDescriptions: [ nls.localize('editor.find.autoFindInSelection.never', 'Never turn on Find in Selection automatically (default).'), nls.localize('editor.find.autoFindInSelection.always', 'Always turn on Find in Selection automatically.'), nls.localize('editor.find.autoFindInSelection.multiline', 'Turn on Find in Selection automatically when multiple lines of content are selected.') ], description: nls.localize('find.autoFindInSelection', "Controls the condition for turning on Find in Selection automatically.") }, 'editor.find.globalFindClipboard': { type: 'boolean', default: defaults.globalFindClipboard, description: nls.localize('find.globalFindClipboard', "Controls whether the Find Widget should read or modify the shared find clipboard on macOS."), included: platform.isMacintosh }, 'editor.find.addExtraSpaceOnTop': { type: 'boolean', default: defaults.addExtraSpaceOnTop, description: nls.localize('find.addExtraSpaceOnTop', "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.") }, 'editor.find.loop': { type: 'boolean', default: defaults.loop, description: nls.localize('find.loop', "Controls whether the search automatically restarts from the beginning (or the end) when no further matches can be found.") }, }); } validate(_input) { if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input; return { cursorMoveOnType: boolean(input.cursorMoveOnType, this.defaultValue.cursorMoveOnType), seedSearchStringFromSelection: typeof _input.seedSearchStringFromSelection === 'boolean' ? (_input.seedSearchStringFromSelection ? 'always' : 'never') : stringSet(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection, ['never', 'always', 'selection']), autoFindInSelection: typeof _input.autoFindInSelection === 'boolean' ? (_input.autoFindInSelection ? 'always' : 'never') : stringSet(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']), globalFindClipboard: boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard), addExtraSpaceOnTop: boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop), loop: boolean(input.loop, this.defaultValue.loop), }; } } //#endregion //#region fontLigatures /** * @internal */ export class EditorFontLigatures extends BaseEditorOption { static { this.OFF = '"liga" off, "calt" off'; } static { this.ON = '"liga" on, "calt" on'; } constructor() { super(51 /* EditorOption.fontLigatures */, 'fontLigatures', EditorFontLigatures.OFF, { anyOf: [ { type: 'boolean', description: nls.localize('fontLigatures', "Enables/Disables font ligatures ('calt' and 'liga' font features). Change this to a string for fine-grained control of the 'font-feature-settings' CSS property."), }, { type: 'string', description: nls.localize('fontFeatureSettings', "Explicit 'font-feature-settings' CSS property. A boolean can be passed instead if one only needs to turn on/off ligatures.") } ], description: nls.localize('fontLigaturesGeneral', "Configures font ligatures or font features. Can be either a boolean to enable/disable ligatures or a string for the value of the CSS 'font-feature-settings' property."), default: false }); } validate(input) { if (typeof input === 'undefined') { return this.defaultValue; } if (typeof input === 'string') { if (input === 'false' || input.length === 0) { return EditorFontLigatures.OFF; } if (input === 'true') { return EditorFontLigatures.ON; } return input; } if (Boolean(input)) { return EditorFontLigatures.ON; } return EditorFontLigatures.OFF; } } //#endregion //#region fontVariations /** * @internal */ export class EditorFontVariations extends BaseEditorOption { // Text is laid out using default settings. static { this.OFF = 'normal'; } // Translate `fontWeight` config to the `font-variation-settings` CSS property. static { this.TRANSLATE = 'translate'; } constructor() { super(54 /* EditorOption.fontVariations */, 'fontVariations', EditorFontVariations.OFF, { anyOf: [ { type: 'boolean', description: nls.localize('fontVariations', "Enables/Disables the translation from font-weight to font-variation-settings. Change this to a string for fine-grained control of the 'font-variation-settings' CSS property."), }, { type: 'string', description: nls.localize('fontVariationSettings', "Explicit 'font-variation-settings' CSS property. A boolean can be passed instead if one only needs to translate font-weight to font-variation-settings.") } ], description: nls.localize('fontVariationsGeneral', "Configures font variations. Can be either a boolean to enable/disable the translation from font-weight to font-variation-settings or a string for the value of the CSS 'font-variation-settings' property."), default: false }); } validate(input) { if (typeof input === 'undefined') { return this.defaultValue; } if (typeof input === 'string') { if (input === 'false') { return EditorFontVariations.OFF; } if (input === 'true') { return EditorFontVariations.TRANSLATE; } return input; } if (Boolean(input)) { return EditorFontVariations.TRANSLATE; } return EditorFontVariations.OFF; } compute(env, options, value) { // The value is computed from the fontWeight if it is true. // So take the result from env.fontInfo return env.fontInfo.fontVariationSettings; } } //#endregion //#region fontInfo class EditorFontInfo extends ComputedEditorOption { constructor() { super(50 /* EditorOption.fontInfo */); } compute(env, options, _) { return env.fontInfo; } } //#endregion //#region fontSize class EditorFontSize extends SimpleEditorOption { constructor() { super(52 /* EditorOption.fontSize */, 'fontSize', EDITOR_FONT_DEFAULTS.fontSize, { type: 'number', minimum: 6, maximum: 100, default: EDITOR_FONT_DEFAULTS.fontSize, description: nls.localize('fontSize', "Controls the font size in pixels.") }); } validate(input) { const r = EditorFloatOption.float(input, this.defaultValue); if (r === 0) { return EDITOR_FONT_DEFAULTS.fontSize; } return EditorFloatOption.clamp(r, 6, 100); } compute(env, options, value) { // The final fontSize respects the editor zoom level. // So take the result from env.fontInfo return env.fontInfo.fontSize; } } //#endregion //#region fontWeight class EditorFontWeight extends BaseEditorOption { static { this.SUGGESTION_VALUES = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; } static { this.MINIMUM_VALUE = 1; } static { this.MAXIMUM_VALUE = 1000; } constructor() { super(53 /* EditorOption.fontWeight */, 'fontWeight', EDITOR_FONT_DEFAULTS.fontWeight, { anyOf: [ { type: 'number', minimum: EditorFontWeight.MINIMUM_VALUE, maximum: EditorFontWeight.MAXIMUM_VALUE, errorMessage: nls.localize('fontWeightErrorMessage', "Only \"normal\" and \"bold\" keywords or numbers between 1 and 1000 are allowed.") }, { type: 'string', pattern: '^(normal|bold|1000|[1-9][0-9]{0,2})$' }, { enum: EditorFontWeight.SUGGESTION_VALUES } ], default: EDITOR_FONT_DEFAULTS.fontWeight, description: nls.localize('fontWeight', "Controls the font weight. Accepts \"normal\" and \"bold\" keywords or numbers between 1 and 1000.") }); } validate(input) { if (input === 'normal' || input === 'bold') { return input; } return String(EditorIntOption.clampedInt(input, EDITOR_FONT_DEFAULTS.fontWeight, EditorFontWeight.MINIMUM_VALUE, EditorFontWeight.MAXIMUM_VALUE)); } } class EditorGoToLocation extends BaseEditorOption { constructor() { const defaults = { multiple: 'peek', multipleDefinitions: 'peek', multipleTypeDefinitions: 'peek', multipleDeclarations: 'peek', multipleImplementations: 'peek', multipleReferences: 'peek', multipleTests: 'peek', alternativeDefinitionCommand: 'editor.action.goToReferences', alternativeTypeDefinitionCommand: 'editor.action.goToReferences', alternativeDeclarationCommand: 'editor.action.goToReferences', alternativeImplementationCommand: '', alternativeReferenceCommand: '', alternativeTestsCommand: '', }; const jsonSubset = { type: 'string', enum: ['peek', 'gotoAndPeek', 'goto'], default: defaults.multiple, enumDescriptions: [ nls.localize('editor.gotoLocation.multiple.peek', 'Show Peek view of the results (default)'), nls.localize('editor.gotoLocation.multiple.gotoAndPeek', 'Go to the primary result and show a Peek view'), nls.localize('editor.gotoLocation.multiple.goto', 'Go to the primary result and enable Peek-less navigation to others') ] }; const alternativeCommandOptions = ['', 'editor.action.referenceSearch.trigger', 'editor.action.goToReferences', 'editor.action.peekImplementation', 'editor.action.goToImplementation', 'editor.action.peekTypeDefinition', 'editor.action.goToTypeDefinition', 'editor.action.peekDeclaration', 'editor.action.revealDeclaration', 'editor.action.peekDefinition', 'editor.action.revealDefinitionAside', 'editor.action.revealDefinition']; super(58 /* EditorOption.gotoLocation */, 'gotoLocation', defaults, { 'editor.gotoLocation.multiple': { deprecationMessage: nls.localize('editor.gotoLocation.multiple.deprecated', "This setting is deprecated, please use separate settings like 'editor.editor.gotoLocation.multipleDefinitions' or 'editor.editor.gotoLocation.multipleImplementations' instead."), }, 'editor.gotoLocation.multipleDefinitions': { description: nls.localize('editor.editor.gotoLocation.multipleDefinitions', "Controls the behavior the 'Go to Definition'-command when multiple target locations exist."), ...jsonSubset, }, 'editor.gotoLocation.multipleTypeDefinitions': { description: nls.localize('editor.editor.gotoLocation.multipleTypeDefinitions', "Controls the behavior the 'Go to Type Definition'-command when multiple target locations exist."), ...jsonSubset, }, 'editor.gotoLocation.multipleDeclarations': { description: nls.localize('editor.editor.gotoLocation.multipleDeclarations', "Controls the behavior the 'Go to Declaration'-command when multiple target locations exist."), ...jsonSubset, }, 'editor.gotoLocation.multipleImplementations': { description: nls.localize('editor.editor.gotoLocation.multipleImplemenattions', "Controls the behavior the 'Go to Implementations'-command when multiple target locations exist."), ...jsonSubset, }, 'editor.gotoLocation.multipleReferences': { description: nls.localize('editor.editor.gotoLocation.multipleReferences', "Controls the behavior the 'Go to References'-command when multiple target locations exist."), ...jsonSubset, }, 'editor.gotoLocation.alternativeDefinitionCommand': { type: 'string', default: defaults.alternativeDefinitionCommand, enum: alternativeCommandOptions, description: nls.localize('alternativeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Definition' is the current location.") }, 'editor.gotoLocation.alternativeTypeDefinitionCommand': { type: 'string', default: defaults.alternativeTypeDefinitionCommand, enum: alternativeCommandOptions, description: nls.localize('alternativeTypeDefinitionCommand', "Alternative command id that is being executed when the result of 'Go to Type Definition' is the current location.") }, 'editor.gotoLocation.alternativeDeclarationCommand': { type: 'string', default: defaults.alternativeDeclarationCommand, enum: alternativeCommandOptions, description: nls.localize('alternativeDeclarationCommand', "Alternative command id that is being executed when the result of 'Go to Declaration' is the current location.") }, 'editor.gotoLocation.alternativeImplementationCommand': { type: 'string', default: defaults.alternativeImplementationCommand, enum: alternativeCommandOptions, description: nls.localize('alternativeImplementationCommand', "Alternative command id that is being executed when the result of 'Go to Implementation' is the current location.") }, 'editor.gotoLocation.alternativeReferenceCommand': { type: 'string', default: defaults.alternativeReferenceCommand, enum: alternativeCommandOptions, description: nls.localize('alternativeReferenceCommand', "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.") }, }); } validate(_input) { if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input; return { multiple: stringSet(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto']), multipleDefinitions: input.multipleDefinitions ?? stringSet(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleTypeDefinitions: input.multipleTypeDefinitions ?? stringSet(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleDeclarations: input.multipleDeclarations ?? stringSet(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleImplementations: input.multipleImplementations ?? stringSet(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleReferences: input.multipleReferences ?? stringSet(input.multipleReferences, 'peek', ['peek', 'gotoAndPeek', 'goto']), multipleTests: input.multipleTests ?? stringSet(input.multipleTests, 'peek', ['peek', 'gotoAndPeek', 'goto']), alternativeDefinitionCommand: EditorStringOption.string(input.alternativeDefinitionCommand, this.defaultValue.alternativeDefinitionCommand), alternativeTypeDefinitionCommand: EditorStringOption.string(input.alternativeTypeDefinitionCommand, this.defaultValue.alternativeTypeDefinitionCommand), alternativeDeclarationCommand: EditorStringOption.string(input.alternativeDeclarationCommand, this.defaultValue.alternativeDeclarationCommand), alternativeImplementationCommand: EditorStringOption.string(input.alternativeImplementationCommand, this.defaultValue.alternativeImplementationCommand), alternativeReferenceCommand: EditorStringOption.string(input.alternativeReferenceCommand, this.defaultValue.alternativeReferenceCommand), alternativeTestsCommand: EditorStringOption.string(input.alternativeTestsCommand, this.defaultValue.alternativeTestsCommand), }; } } class EditorHover extends BaseEditorOption { constructor() { const defaults = { enabled: true, delay: 300, hidingDelay: 300, sticky: true, above: true, }; super(60 /* EditorOption.hover */, 'hover', defaults, { 'editor.hover.enabled': { type: 'boolean', default: defaults.enabled, description: nls.localize('hover.enabled', "Controls whether the hover is shown.") }, 'editor.hover.delay': { type: 'number', default: defaults.delay, minimum: 0, maximum: 10000, description: nls.localize('hover.delay', "Controls the delay in milliseconds after which the hover is shown.") }, 'editor.hover.sticky': { type: 'boolean', default: defaults.sticky, description: nls.localize('hover.sticky', "Controls whether the hover should remain visible when mouse is moved over it.") }, 'editor.hover.hidingDelay': { type: 'integer', minimum: 0, default: defaults.hidingDelay, description: nls.localize('hover.hidingDelay', "Controls the delay in milliseconds after which the hover is hidden. Requires `editor.hover.sticky` to be enabled.") }, 'editor.hover.above': { type: 'boolean', default: defaults.above, description: nls.localize('hover.above', "Prefer showing hovers above the line, if there's space.") }, }); } validate(_input) { if (!_input || typeof _input !== 'object') { return this.defaultValue; } const input = _input; return { enabled: boolean(input.enabled, this.defaultValue.enabled), delay: EditorIntOption.clampedInt(input.delay, this.defaultValue.delay, 0, 10000), sticky: boolean(input.sticky, this.defaultValue.sticky), hidingDelay: EditorIntOption.clampedInt(input.hidingDelay, this.defaultValue.hidingDelay, 0, 600000), above: boolean(input.above, this.defaultValue.above), }; } } /** * @internal */ export class EditorLayoutInfoComputer extends ComputedEditorOption { constructor() { super(146 /* EditorOption.layoutInfo */); } compute(env, options, _) { return EditorLayoutInfoComputer.computeLayout(options, { memory: env.memory, outerWidth: env.outerWidth, outerHeight: env.outerHeight, isDominatedByLongLines: env.isDominatedByLongLines, lineHeight: env.fontInfo.lineHeight, viewLineCount: env.viewLineCount, lineNumbersDigitCount: env.lineNumbersDigitCount, typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth, maxDigitWidth: env.fontInfo.maxDigitWidth, pixelRatio: env.pixelRatio, glyphMarginDecorationLaneCount: env.glyphMarginDecorationLaneCount }); } static computeContainedMinimapLineCount(input) { const typicalViewportLineCount = input.height / input.lineHeight; const extraLinesBeforeFirstLine = Math.floor(input.paddingTop / input.lineHeight); let extraLinesBeyondLastLine = Math.floor(input.paddingBottom / input.lineHeight); if (input.scrollBeyondLastLine) { extraLinesBeyondLastLine = Math.max(extraLinesBeyondLastLine, typicalViewportLineCount - 1); } const desiredRatio = (extraLinesBeforeFirstLine + input.viewLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height); const minimapLineCount = Math.floor(input.viewLineCount / desiredRatio); return { typicalViewportLineCount, extraLinesBeforeFirstLine, extraLinesBeyondLastLine, desiredRatio, minimapLineCount }; } static _computeMinimapLayout(input, memory) { const outerWidth = input.outerWidth; const outerHeight = input.outerHeight; const pixelRatio = input.pixelRatio; if (!input.minimap.enabled) { return { renderMinimap: 0 /* RenderMinimap.None */, minimapLeft: 0, minimapWidth: 0, minimapHeightIsEditorHeight: false, minimapIsSampling: false, minimapScale: 1, minimapLineHeight: 1, minimapCanvasInnerWidth: 0, minimapCanvasInnerHeight: Math.floor(pixelRatio * outerHeight), minimapCanvasOuterWidth: 0, minimapCanvasOuterHeight: outerHeight, }; } // Can use memory if only the `viewLineCount` and `remainingWidth` have changed const stableMinimapLayoutInput = memory.stableMinimapLayoutInput; const couldUseMemory = (stableMinimapLayoutInput // && input.outerWidth === lastMinimapLayoutInput.outerWidth !!! INTENTIONAL OMITTED && input.outerHeight === stableMinimapLayoutInput.outerHeight && input.lineHeight === stableMinimapLayoutInput.lineHeight && input.typicalHalfwidthCharacterWidth === stableMinimapLayoutInput.typicalHalfwidthCharacterWidth && input.pixelRatio === stableMinimapLayoutInput.pixelRatio && input.scrollBeyondLastLine === stableMinimapLayoutInput.scrollBeyondLastLine && input.paddingTop === stableMinimapLayoutInput.paddingTop && input.paddingBottom === stableMinimapLayoutInput.paddingBottom && input.minimap.enabled === stableMinimapLayoutInput.minimap.enabled && input.minimap.side === stableMinimapLayoutInput.minimap.side && input.minimap.size === stableMinimapLayoutInput.minimap.size && input.minimap.showSlider === stableMinimapLayoutInput.minimap.showSlider && input.minimap.renderCharacters === stableMinimapLayoutInput.minimap.renderCharacters && input.minimap.maxColumn === stableMinimapLayoutInput.minimap.maxColumn && input.minimap.scale === stableMinimapLayoutInput.minimap.scale && input.verticalScrollbarWidth === stableMinimapLayoutInput.verticalScrollbarWidth // && input.viewLineCount === lastMinimapLayoutInput.viewLineCount !!! INTENTIONAL OMITTED // && input.remainingWidth === lastMinimapLayoutInput.remainingWidth !!! INTENTIONAL OMITTED && input.isViewportWrapping === stableMinimapLayoutInput.isViewportWrapping); const lineHeight = input.lineHeight; const typicalHalfwidthCharacterWidth = input.typicalHalfwidthCharacterWidth; const scrollBeyondLastLine = input.scrollBeyondLastLine; const minimapRenderCharacters = input.minimap.renderCharacters; let minimapScale = (pixelRatio >= 2 ? Math.round(input.minimap.scale * 2) : input.minimap.scale); const minimapMaxColumn = input.minimap.maxColumn; const minimapSize = input.minimap.size; const minimapSide = input.minimap.side; const verticalScrollbarWidth = input.verticalScrollbarWidth; const viewLineCount = input.viewLineCount; const remainingWidth = input.remainingWidth; const isViewportWrapping = input.isViewportWrapping; const baseCharHeight = minimapRenderCharacters ? 2 : 3; let minimapCanvasInnerHeight = Math.floor(pixelRatio * outerHeight); const minimapCanvasOuterHeight = minimapCanvasInnerHeight / pixelRatio; let minimapHeightIsEditorHeight = false; let minimapIsSampling = false; let minimapLineHeight = baseCharHeight * minimapScale; let minimapCharWidth = minimapScale / pixelRatio; let minimapWidthMultiplier = 1; if (minimapSize === 'fill' || minimapSize === 'fit') { const { typicalViewportLineCount, extraLinesBeforeFirstLine, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ viewLineCount: viewLineCount, scrollBeyondLastLine: scrollBeyondLastLine, paddingTop: input.paddingTop, paddingBottom: input.paddingBottom, height: outerHeight, lineHeight: lineHeight, pixelRatio: pixelRatio }); // ratio is intentionally not part of the layout to avoid the layout changing all the time // when doing sampling const ratio = viewLineCount / minimapLineCount; if (ratio > 1) { minimapHeightIsEditorHeight = true; minimapIsSampling = true; minimapScale = 1; minimapLineHeight = 1; minimapCharWidth = minimapScale / pixelRatio; } else { let fitBecomesFill = false; let maxMinimapScale = minimapScale + 1; if (minimapSize === 'fit') { const effectiveMinimapHeight = Math.ceil((extraLinesBeforeFirstLine + viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight); if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) { // There is a loop when using `fit` and viewport wrapping: // - view line count impacts minimap layout // - minimap layout impacts viewport width // - viewport width impacts view line count // To break the loop, once we go to a smaller minimap scale, we try to stick with it. fitBecomesFill = true; maxMinimapScale = memory.stableFitMaxMinimapScale; } else { fitBecomesFill = (effectiveMinimapHeight > minimapCanvasInnerHeight); } } if (minimapSize === 'fill' || fitBecomesFill) { minimapHeightIsEditorHeight = true; const configuredMinimapScale = minimapScale; minimapLineHeight = Math.min(lineHeight * pixelRatio, Math.max(1, Math.floor(1 / desiredRatio))); if (isViewportWrapping && couldUseMemory && remainingWidth <= memory.stableFitRemainingWidth) { // There is a loop when using `fill` and viewport wrapping: // - view line count impacts minimap layout // - minimap layout impacts viewport width // - viewport width impacts view line count // To break the loop, once we go to a smaller minimap scale, we try to stick with it. maxMinimapScale = memory.stableFitMaxMinimapScale; } minimapScale = Math.min(maxMinimapScale, Math.max(1, Math.floor(minimapLineHeight / baseCharHeight))); if (minimapScale > configuredMinimapScale) { minimapWidthMultiplier = Math.min(2, minimapScale / configuredMinimapScale); } minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, extraLinesBeforeFirstLine + viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight); if (isViewportWrapping) { // remember for next time memory.stableMinimapLayoutInput = input; memory.stableFitRemainingWidth = remainingWidth; memory.stableFitMaxMinimapScale = minimapScale; } else { memory.stableMinimapLayoutInput = null; memory.stableFitRemainingWidth = 0; } } } } // Given: // (leaving 2px for the cursor to have space after the last character) // viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth // minimapWidth = viewportColumn * minimapCharWidth // contentWidth = remainingWidth - minimapWidth // What are good values for contentWidth and minimapWidth ? // minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth // typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth // typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth // (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth // minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth) const minimapMaxWidth = Math.floor(minimapMaxColumn * minimapCharWidth); const minimapWidth = Math.min(minimapMaxWidth, Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH); let minimapCanvasInnerWidth = Math.floor(pixelRatio * minimapWidth); const minimapCanvasOuterWidth = minimapCanvasInnerWidth / pixelRatio; minimapCanvasInnerWidth = Math.floor(minimapCanvasInnerWidth * minimapWidthMultiplier); const renderMinimap = (minimapRenderCharacters ? 1 /* RenderMinimap.Text */ : 2 /* RenderMinimap.Blocks */); const minimapLeft = (minimapSide === 'left' ? 0 : (outerWidth - minimapWidth - verticalScrollbarWidth)); return { renderMinimap, minimapLeft, minimapWidth, minimapHeightIsEditorHeight, minimapIsSampling, minimapScale, minimapLineHeight, minimapCanvasInnerWidth, minimapCanvasInnerHeight, minimapCanvasOuterWidth, minimapCanvasOuterHeight, }; } static computeLayout(options, env) { const outerWidth = env.outerWidth | 0; const outerHeight = env.outerHeight | 0; const lineHeight = env.lineHeight | 0; const lineNumbersDigitCount = env.lineNumbersDigitCount | 0; const typicalHalfwidthCharacterWidth = env.typicalHalfwidthCharacterWidth; const maxDigitWidth = env.maxDigitWidth; const pixelRatio = env.pixelRatio; const viewLineCount = env.viewLineCount; const wordWrapOverride2 = options.get(138 /* EditorOption.wordWrapOverride2 */); const wordWrapOverride1 = (wordWrapOverride2 === 'inherit' ? options.get(137 /* EditorOption.wordWrapOverride1 */) : wordWrapOverride2); const wordWrap = (wordWrapOverride1 === 'inherit' ? options.get(133 /* EditorOption.wordWrap */) : wordWrapOverride1); const wordWrapColumn = options.get(136 /* EditorOption.wordWrapColumn */); const isDominatedByLongLines = env.isDominatedByLongLines; const showGlyphMargin = options.get(57 /* EditorOption.glyphMargin */); const showLineNumbers = (options.get(68 /* EditorOption.lineNumbers */).renderType !== 0 /* RenderLineNumbersType.Off */);