mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-09 17:52:34 -05:00
Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c (#8525)
* Merge from vscode a5cf1da01d5db3d2557132be8d30f89c38019f6c * remove files we don't want * fix hygiene * update distro * update distro * fix hygiene * fix strict nulls * distro * distro * fix tests * fix tests * add another edit * fix viewlet icon * fix azure dialog * fix some padding * fix more padding issues
This commit is contained in:
@@ -11,6 +11,7 @@ import { Selection, SelectionDirection } from 'vs/editor/common/core/selection';
|
||||
import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export interface IShiftCommandOpts {
|
||||
isUnshift: boolean;
|
||||
@@ -18,6 +19,7 @@ export interface IShiftCommandOpts {
|
||||
indentSize: number;
|
||||
insertSpaces: boolean;
|
||||
useTabStops: boolean;
|
||||
autoIndent: EditorAutoIndentStrategy;
|
||||
}
|
||||
|
||||
const repeatCache: { [str: string]: string[]; } = Object.create(null);
|
||||
@@ -137,7 +139,7 @@ export class ShiftCommand implements ICommand {
|
||||
// The current line is "miss-aligned", so let's see if this is expected...
|
||||
// This can only happen when it has trailing commas in the indent
|
||||
if (model.isCheapToTokenize(lineNumber - 1)) {
|
||||
let enterAction = LanguageConfigurationRegistry.getRawEnterActionAtPosition(model, lineNumber - 1, model.getLineMaxColumn(lineNumber - 1));
|
||||
let enterAction = LanguageConfigurationRegistry.getEnterAction(this._opts.autoIndent, model, new Range(lineNumber - 1, model.getLineMaxColumn(lineNumber - 1), lineNumber - 1, model.getLineMaxColumn(lineNumber - 1)));
|
||||
if (enterAction) {
|
||||
extraSpaces = previousLineExtraSpaces;
|
||||
if (enterAction.appendText) {
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { forEach } from 'vs/base/common/collections';
|
||||
|
||||
/**
|
||||
* Control what pressing Tab does.
|
||||
@@ -197,6 +198,43 @@ function migrateOptions(options: IEditorOptions): void {
|
||||
options.tabCompletion = 'onlySnippets';
|
||||
}
|
||||
|
||||
const suggest = options.suggest;
|
||||
if (suggest && typeof (<any>suggest).filteredTypes === 'object' && (<any>suggest).filteredTypes) {
|
||||
const mapping: Record<string, string> = {};
|
||||
mapping['method'] = 'showMethods';
|
||||
mapping['function'] = 'showFunctions';
|
||||
mapping['constructor'] = 'showConstructors';
|
||||
mapping['field'] = 'showFields';
|
||||
mapping['variable'] = 'showVariables';
|
||||
mapping['class'] = 'showClasses';
|
||||
mapping['struct'] = 'showStructs';
|
||||
mapping['interface'] = 'showInterfaces';
|
||||
mapping['module'] = 'showModules';
|
||||
mapping['property'] = 'showProperties';
|
||||
mapping['event'] = 'showEvents';
|
||||
mapping['operator'] = 'showOperators';
|
||||
mapping['unit'] = 'showUnits';
|
||||
mapping['value'] = 'showValues';
|
||||
mapping['constant'] = 'showConstants';
|
||||
mapping['enum'] = 'showEnums';
|
||||
mapping['enumMember'] = 'showEnumMembers';
|
||||
mapping['keyword'] = 'showKeywords';
|
||||
mapping['text'] = 'showWords';
|
||||
mapping['color'] = 'showColors';
|
||||
mapping['file'] = 'showFiles';
|
||||
mapping['reference'] = 'showReferences';
|
||||
mapping['folder'] = 'showFolders';
|
||||
mapping['typeParameter'] = 'showTypeParameters';
|
||||
mapping['snippet'] = 'showSnippets';
|
||||
forEach(mapping, entry => {
|
||||
const value = (<any>suggest).filteredTypes[entry.key];
|
||||
if (value === false) {
|
||||
(<any>suggest)[entry.value] = value;
|
||||
}
|
||||
});
|
||||
// delete (<any>suggest).filteredTypes;
|
||||
}
|
||||
|
||||
const hover = options.hover;
|
||||
if (<any>hover === true) {
|
||||
options.hover = {
|
||||
@@ -218,6 +256,13 @@ function migrateOptions(options: IEditorOptions): void {
|
||||
enabled: false
|
||||
};
|
||||
}
|
||||
|
||||
const autoIndent = options.autoIndent;
|
||||
if (<any>autoIndent === true) {
|
||||
options.autoIndent = 'full';
|
||||
} else if (<any>autoIndent === false) {
|
||||
options.autoIndent = 'advanced';
|
||||
}
|
||||
}
|
||||
|
||||
function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions {
|
||||
@@ -304,18 +349,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
|
||||
return EditorConfiguration2.computeOptions(this._validatedOptions, env);
|
||||
}
|
||||
|
||||
private static _primitiveArrayEquals(a: any[], b: any[]): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static _subsetEquals(base: { [key: string]: any }, subset: { [key: string]: any }): boolean {
|
||||
for (const key in subset) {
|
||||
if (hasOwnProperty.call(subset, key)) {
|
||||
@@ -326,7 +359,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
|
||||
continue;
|
||||
}
|
||||
if (Array.isArray(baseValue) && Array.isArray(subsetValue)) {
|
||||
if (!this._primitiveArrayEquals(baseValue, subsetValue)) {
|
||||
if (!arrays.equals(baseValue, subsetValue)) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
@@ -387,14 +420,18 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
|
||||
|
||||
}
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
const editorConfiguration: IConfigurationNode = {
|
||||
export const editorConfigurationBaseNode = Object.freeze<IConfigurationNode>({
|
||||
id: 'editor',
|
||||
order: 5,
|
||||
type: 'object',
|
||||
title: nls.localize('editorConfigurationTitle', "Editor"),
|
||||
overridable: true,
|
||||
scope: ConfigurationScope.RESOURCE,
|
||||
});
|
||||
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
const editorConfiguration: IConfigurationNode = {
|
||||
...editorConfigurationBaseNode,
|
||||
properties: {
|
||||
'editor.tabSize': {
|
||||
type: 'number',
|
||||
@@ -451,29 +488,6 @@ const editorConfiguration: IConfigurationNode = {
|
||||
default: 20_000,
|
||||
description: nls.localize('maxTokenizationLineLength', "Lines above this length will not be tokenized for performance reasons")
|
||||
},
|
||||
'editor.codeActionsOnSave': {
|
||||
type: 'object',
|
||||
properties: {
|
||||
'source.organizeImports': {
|
||||
type: 'boolean',
|
||||
description: nls.localize('codeActionsOnSave.organizeImports', "Controls whether organize imports action should be run on file save.")
|
||||
},
|
||||
'source.fixAll': {
|
||||
type: 'boolean',
|
||||
description: nls.localize('codeActionsOnSave.fixAll', "Controls whether auto fix action should be run on file save.")
|
||||
}
|
||||
},
|
||||
'additionalProperties': {
|
||||
type: 'boolean'
|
||||
},
|
||||
default: {},
|
||||
description: nls.localize('codeActionsOnSave', "Code action kinds to be run on save.")
|
||||
},
|
||||
'editor.codeActionsOnSaveTimeout': {
|
||||
type: 'number',
|
||||
default: 750,
|
||||
description: nls.localize('codeActionsOnSaveTimeout', "Timeout in milliseconds after which the code actions that are run on save are cancelled.")
|
||||
},
|
||||
'diffEditor.maxComputationTime': {
|
||||
type: 'number',
|
||||
default: 5000,
|
||||
|
||||
@@ -10,9 +10,9 @@ import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { Constants } from 'vs/base/common/uint';
|
||||
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
|
||||
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
|
||||
import { isObject } from 'vs/base/common/types';
|
||||
import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IDimension } from 'vs/editor/common/editorCommon';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
|
||||
//#region typed options
|
||||
|
||||
@@ -31,6 +31,18 @@ export type EditorAutoSurroundStrategy = 'languageDefined' | 'quotes' | 'bracket
|
||||
*/
|
||||
export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never';
|
||||
|
||||
/**
|
||||
* Configuration options for auto indentation in the editor
|
||||
* @internal
|
||||
*/
|
||||
export const enum EditorAutoIndentStrategy {
|
||||
None = 0,
|
||||
Keep = 1,
|
||||
Brackets = 2,
|
||||
Advanced = 3,
|
||||
Full = 4
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for the editor.
|
||||
*/
|
||||
@@ -71,6 +83,12 @@ export interface IEditorOptions {
|
||||
* Defaults to 0.
|
||||
*/
|
||||
cursorSurroundingLines?: number;
|
||||
/**
|
||||
* Controls when `cursorSurroundingLines` should be enforced
|
||||
* Defaults to `default`, `cursorSurroundingLines` is not enforced when cursor position is changed
|
||||
* by mouse.
|
||||
*/
|
||||
cursorSurroundingLinesStyle?: 'default' | 'all';
|
||||
/**
|
||||
* Render last line number when the file ends with a newline.
|
||||
* Defaults to true.
|
||||
@@ -137,7 +155,7 @@ export interface IEditorOptions {
|
||||
fixedOverflowWidgets?: boolean;
|
||||
/**
|
||||
* The number of vertical lanes the overview ruler should render.
|
||||
* Defaults to 2.
|
||||
* Defaults to 3.
|
||||
*/
|
||||
overviewRulerLanes?: number;
|
||||
/**
|
||||
@@ -180,8 +198,8 @@ export interface IEditorOptions {
|
||||
*/
|
||||
fontLigatures?: boolean | string;
|
||||
/**
|
||||
* Disable the use of `will-change` for the editor margin and lines layers.
|
||||
* The usage of `will-change` acts as a hint for browsers to create an extra layer.
|
||||
* Disable the use of `transform: translate3d(0px, 0px, 0px)` for the editor margin and lines layers.
|
||||
* The usage of `transform: translate3d(0px, 0px, 0px)` acts as a hint for browsers to create an extra layer.
|
||||
* Defaults to false.
|
||||
*/
|
||||
disableLayerHinting?: boolean;
|
||||
@@ -313,6 +331,10 @@ export interface IEditorOptions {
|
||||
* Defaults to 'auto'. It is best to leave this to 'auto'.
|
||||
*/
|
||||
accessibilitySupport?: 'auto' | 'off' | 'on';
|
||||
/**
|
||||
* Controls the number of lines in the editor that can be read out by a screen reader
|
||||
*/
|
||||
accessibilityPageSize?: number;
|
||||
/**
|
||||
* Suggest options.
|
||||
*/
|
||||
@@ -358,7 +380,7 @@ export interface IEditorOptions {
|
||||
* Enable auto indentation adjustment.
|
||||
* Defaults to false.
|
||||
*/
|
||||
autoIndent?: boolean;
|
||||
autoIndent?: 'none' | 'keep' | 'brackets' | 'advanced' | 'full';
|
||||
/**
|
||||
* Enable format on type.
|
||||
* Defaults to false.
|
||||
@@ -524,6 +546,12 @@ export interface IEditorConstructionOptions extends IEditorOptions {
|
||||
dimension?: IDimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* The width of the minimap gutter, in pixels.
|
||||
*/
|
||||
export const MINIMAP_GUTTER_WIDTH = 8;
|
||||
|
||||
/**
|
||||
* Configuration options for the diff editor.
|
||||
*/
|
||||
@@ -909,6 +937,20 @@ class EditorEnumOption<K1 extends EditorOption, T extends string, V> extends Bas
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region autoIndent
|
||||
|
||||
function _autoIndentFromString(autoIndent: 'none' | 'keep' | 'brackets' | 'advanced' | 'full'): EditorAutoIndentStrategy {
|
||||
switch (autoIndent) {
|
||||
case 'none': return EditorAutoIndentStrategy.None;
|
||||
case 'keep': return EditorAutoIndentStrategy.Keep;
|
||||
case 'brackets': return EditorAutoIndentStrategy.Brackets;
|
||||
case 'advanced': return EditorAutoIndentStrategy.Advanced;
|
||||
case 'full': return EditorAutoIndentStrategy.Full;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region accessibilitySupport
|
||||
|
||||
class EditorAccessibilitySupport extends BaseEditorOption<EditorOption.accessibilitySupport, AccessibilitySupport> {
|
||||
@@ -1109,9 +1151,9 @@ export interface IEditorFindOptions {
|
||||
*/
|
||||
seedSearchStringFromSelection?: boolean;
|
||||
/**
|
||||
* Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor.
|
||||
* Controls if Find in Selection flag is turned on in the editor.
|
||||
*/
|
||||
autoFindInSelection?: boolean;
|
||||
autoFindInSelection?: 'never' | 'always' | 'multiline';
|
||||
/*
|
||||
* Controls whether the Find Widget should add extra lines on top of the editor.
|
||||
*/
|
||||
@@ -1130,7 +1172,7 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
constructor() {
|
||||
const defaults: EditorFindOptions = {
|
||||
seedSearchStringFromSelection: true,
|
||||
autoFindInSelection: false,
|
||||
autoFindInSelection: 'never',
|
||||
globalFindClipboard: false,
|
||||
addExtraSpaceOnTop: true
|
||||
};
|
||||
@@ -1143,8 +1185,14 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
description: nls.localize('find.seedSearchStringFromSelection', "Controls whether the search string in the Find Widget is seeded from the editor selection.")
|
||||
},
|
||||
'editor.find.autoFindInSelection': {
|
||||
type: 'boolean',
|
||||
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 whether the find operation is carried out on selected text or the entire file in the editor.")
|
||||
},
|
||||
'editor.find.globalFindClipboard': {
|
||||
@@ -1169,7 +1217,9 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
|
||||
const input = _input as IEditorFindOptions;
|
||||
return {
|
||||
seedSearchStringFromSelection: EditorBooleanOption.boolean(input.seedSearchStringFromSelection, this.defaultValue.seedSearchStringFromSelection),
|
||||
autoFindInSelection: EditorBooleanOption.boolean(input.autoFindInSelection, this.defaultValue.autoFindInSelection),
|
||||
autoFindInSelection: typeof _input.autoFindInSelection === 'boolean'
|
||||
? (_input.autoFindInSelection ? 'always' : 'never')
|
||||
: EditorStringEnumOption.stringSet<'never' | 'always' | 'multiline'>(input.autoFindInSelection, this.defaultValue.autoFindInSelection, ['never', 'always', 'multiline']),
|
||||
globalFindClipboard: EditorBooleanOption.boolean(input.globalFindClipboard, this.defaultValue.globalFindClipboard),
|
||||
addExtraSpaceOnTop: EditorBooleanOption.boolean(input.addExtraSpaceOnTop, this.defaultValue.addExtraSpaceOnTop)
|
||||
};
|
||||
@@ -1202,6 +1252,7 @@ export class EditorFontLigatures extends BaseEditorOption<EditorOption.fontLigat
|
||||
description: nls.localize('fontFeatureSettings', "Explicit font-feature-settings.")
|
||||
}
|
||||
],
|
||||
description: nls.localize('fontLigaturesGeneral', "Configures font ligatures."),
|
||||
default: false
|
||||
}
|
||||
);
|
||||
@@ -1253,6 +1304,8 @@ class EditorFontSize extends SimpleEditorOption<EditorOption.fontSize, number> {
|
||||
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.")
|
||||
}
|
||||
@@ -1264,7 +1317,7 @@ class EditorFontSize extends SimpleEditorOption<EditorOption.fontSize, number> {
|
||||
if (r === 0) {
|
||||
return EDITOR_FONT_DEFAULTS.fontSize;
|
||||
}
|
||||
return EditorFloatOption.clamp(r, 8, 100);
|
||||
return EditorFloatOption.clamp(r, 6, 100);
|
||||
}
|
||||
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, value: number): number {
|
||||
// The final fontSize respects the editor zoom level.
|
||||
@@ -1277,14 +1330,26 @@ class EditorFontSize extends SimpleEditorOption<EditorOption.fontSize, number> {
|
||||
|
||||
//#region gotoLocation
|
||||
|
||||
export type GoToLocationValues = 'peek' | 'gotoAndPeek' | 'goto';
|
||||
|
||||
/**
|
||||
* Configuration options for go to location
|
||||
*/
|
||||
export interface IGotoLocationOptions {
|
||||
/**
|
||||
* Control how goto-command work when having multiple results.
|
||||
*/
|
||||
multiple?: 'peek' | 'gotoAndPeek' | 'goto';
|
||||
|
||||
multiple?: GoToLocationValues;
|
||||
|
||||
multipleDefinitions?: GoToLocationValues;
|
||||
multipleTypeDefinitions?: GoToLocationValues;
|
||||
multipleDeclarations?: GoToLocationValues;
|
||||
multipleImplementations?: GoToLocationValues;
|
||||
multipleReferences?: GoToLocationValues;
|
||||
|
||||
alternativeDefinitionCommand?: string;
|
||||
alternativeTypeDefinitionCommand?: string;
|
||||
alternativeDeclarationCommand?: string;
|
||||
alternativeImplementationCommand?: string;
|
||||
alternativeReferenceCommand?: string;
|
||||
}
|
||||
|
||||
export type GoToLocationOptions = Readonly<Required<IGotoLocationOptions>>;
|
||||
@@ -1292,20 +1357,79 @@ export type GoToLocationOptions = Readonly<Required<IGotoLocationOptions>>;
|
||||
class EditorGoToLocation extends BaseEditorOption<EditorOption.gotoLocation, GoToLocationOptions> {
|
||||
|
||||
constructor() {
|
||||
const defaults: GoToLocationOptions = { multiple: 'peek' };
|
||||
const defaults: GoToLocationOptions = {
|
||||
multiple: 'peek',
|
||||
multipleDefinitions: 'peek',
|
||||
multipleTypeDefinitions: 'peek',
|
||||
multipleDeclarations: 'peek',
|
||||
multipleImplementations: 'peek',
|
||||
multipleReferences: 'peek',
|
||||
alternativeDefinitionCommand: 'editor.action.goToReferences',
|
||||
alternativeTypeDefinitionCommand: 'editor.action.goToReferences',
|
||||
alternativeDeclarationCommand: 'editor.action.goToReferences',
|
||||
alternativeImplementationCommand: '',
|
||||
alternativeReferenceCommand: '',
|
||||
};
|
||||
const jsonSubset: IJSONSchema = {
|
||||
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')
|
||||
]
|
||||
};
|
||||
super(
|
||||
EditorOption.gotoLocation, 'gotoLocation', defaults,
|
||||
{
|
||||
'editor.gotoLocation.multiple': {
|
||||
description: nls.localize('editor.gotoLocation.multiple', "Controls the behavior of 'Go To' commands, like Go To Definition, when multiple target locations exist."),
|
||||
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',
|
||||
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')
|
||||
]
|
||||
default: defaults.alternativeDefinitionCommand,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
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,
|
||||
description: nls.localize('alternativeReferenceCommand', "Alternative command id that is being executed when the result of 'Go to Reference' is the current location.")
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -1317,7 +1441,17 @@ class EditorGoToLocation extends BaseEditorOption<EditorOption.gotoLocation, GoT
|
||||
}
|
||||
const input = _input as IGotoLocationOptions;
|
||||
return {
|
||||
multiple: EditorStringEnumOption.stringSet<'peek' | 'gotoAndPeek' | 'goto'>(input.multiple, this.defaultValue.multiple, ['peek', 'gotoAndPeek', 'goto'])
|
||||
multiple: EditorStringEnumOption.stringSet<GoToLocationValues>(input.multiple, this.defaultValue.multiple!, ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDefinitions: input.multipleDefinitions ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleTypeDefinitions: input.multipleTypeDefinitions ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleTypeDefinitions, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleDeclarations: input.multipleDeclarations ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleDeclarations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleImplementations: input.multipleImplementations ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleImplementations, 'peek', ['peek', 'gotoAndPeek', 'goto']),
|
||||
multipleReferences: input.multipleReferences ?? EditorStringEnumOption.stringSet<GoToLocationValues>(input.multipleReferences, '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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1646,7 +1780,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
|
||||
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
|
||||
|
||||
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
|
||||
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth))) + MINIMAP_GUTTER_WIDTH;
|
||||
let minimapColumns = minimapWidth / minimapCharWidth;
|
||||
if (minimapColumns > minimapMaxColumn) {
|
||||
minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
|
||||
@@ -2295,7 +2429,11 @@ export interface ISuggestOptions {
|
||||
/**
|
||||
* Overwrite word ends on accept. Default to false.
|
||||
*/
|
||||
overwriteOnAccept?: boolean;
|
||||
insertMode?: 'insert' | 'replace';
|
||||
/**
|
||||
* Show a highlight when suggestion replaces or keep text after the cursor. Defaults to false.
|
||||
*/
|
||||
insertHighlight?: boolean;
|
||||
/**
|
||||
* Enable graceful matching. Defaults to true.
|
||||
*/
|
||||
@@ -2321,9 +2459,105 @@ export interface ISuggestOptions {
|
||||
*/
|
||||
maxVisibleSuggestions?: number;
|
||||
/**
|
||||
* Names of suggestion types to filter.
|
||||
* Show method-suggestions.
|
||||
*/
|
||||
filteredTypes?: Record<string, boolean>;
|
||||
showMethods?: boolean;
|
||||
/**
|
||||
* Show function-suggestions.
|
||||
*/
|
||||
showFunctions?: boolean;
|
||||
/**
|
||||
* Show constructor-suggestions.
|
||||
*/
|
||||
showConstructors?: boolean;
|
||||
/**
|
||||
* Show field-suggestions.
|
||||
*/
|
||||
showFields?: boolean;
|
||||
/**
|
||||
* Show variable-suggestions.
|
||||
*/
|
||||
showVariables?: boolean;
|
||||
/**
|
||||
* Show class-suggestions.
|
||||
*/
|
||||
showClasses?: boolean;
|
||||
/**
|
||||
* Show struct-suggestions.
|
||||
*/
|
||||
showStructs?: boolean;
|
||||
/**
|
||||
* Show interface-suggestions.
|
||||
*/
|
||||
showInterfaces?: boolean;
|
||||
/**
|
||||
* Show module-suggestions.
|
||||
*/
|
||||
showModules?: boolean;
|
||||
/**
|
||||
* Show property-suggestions.
|
||||
*/
|
||||
showProperties?: boolean;
|
||||
/**
|
||||
* Show event-suggestions.
|
||||
*/
|
||||
showEvents?: boolean;
|
||||
/**
|
||||
* Show operator-suggestions.
|
||||
*/
|
||||
showOperators?: boolean;
|
||||
/**
|
||||
* Show unit-suggestions.
|
||||
*/
|
||||
showUnits?: boolean;
|
||||
/**
|
||||
* Show value-suggestions.
|
||||
*/
|
||||
showValues?: boolean;
|
||||
/**
|
||||
* Show constant-suggestions.
|
||||
*/
|
||||
showConstants?: boolean;
|
||||
/**
|
||||
* Show enum-suggestions.
|
||||
*/
|
||||
showEnums?: boolean;
|
||||
/**
|
||||
* Show enumMember-suggestions.
|
||||
*/
|
||||
showEnumMembers?: boolean;
|
||||
/**
|
||||
* Show keyword-suggestions.
|
||||
*/
|
||||
showKeywords?: boolean;
|
||||
/**
|
||||
* Show text-suggestions.
|
||||
*/
|
||||
showWords?: boolean;
|
||||
/**
|
||||
* Show color-suggestions.
|
||||
*/
|
||||
showColors?: boolean;
|
||||
/**
|
||||
* Show file-suggestions.
|
||||
*/
|
||||
showFiles?: boolean;
|
||||
/**
|
||||
* Show reference-suggestions.
|
||||
*/
|
||||
showReferences?: boolean;
|
||||
/**
|
||||
* Show folder-suggestions.
|
||||
*/
|
||||
showFolders?: boolean;
|
||||
/**
|
||||
* Show typeParameter-suggestions.
|
||||
*/
|
||||
showTypeParameters?: boolean;
|
||||
/**
|
||||
* Show snippet-suggestions.
|
||||
*/
|
||||
showSnippets?: boolean;
|
||||
}
|
||||
|
||||
export type InternalSuggestOptions = Readonly<Required<ISuggestOptions>>;
|
||||
@@ -2332,22 +2566,57 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
|
||||
constructor() {
|
||||
const defaults: InternalSuggestOptions = {
|
||||
overwriteOnAccept: false,
|
||||
insertMode: 'insert',
|
||||
insertHighlight: false,
|
||||
filterGraceful: true,
|
||||
snippetsPreventQuickSuggestions: true,
|
||||
localityBonus: false,
|
||||
shareSuggestSelections: false,
|
||||
showIcons: true,
|
||||
maxVisibleSuggestions: 12,
|
||||
filteredTypes: Object.create(null)
|
||||
showMethods: true,
|
||||
showFunctions: true,
|
||||
showConstructors: true,
|
||||
showFields: true,
|
||||
showVariables: true,
|
||||
showClasses: true,
|
||||
showStructs: true,
|
||||
showInterfaces: true,
|
||||
showModules: true,
|
||||
showProperties: true,
|
||||
showEvents: true,
|
||||
showOperators: true,
|
||||
showUnits: true,
|
||||
showValues: true,
|
||||
showConstants: true,
|
||||
showEnums: true,
|
||||
showEnumMembers: true,
|
||||
showKeywords: true,
|
||||
showWords: true,
|
||||
showColors: true,
|
||||
showFiles: true,
|
||||
showReferences: true,
|
||||
showFolders: true,
|
||||
showTypeParameters: true,
|
||||
showSnippets: true,
|
||||
};
|
||||
super(
|
||||
EditorOption.suggest, 'suggest', defaults,
|
||||
{
|
||||
'editor.suggest.overwriteOnAccept': {
|
||||
'editor.suggest.insertMode': {
|
||||
type: 'string',
|
||||
enum: ['insert', 'replace'],
|
||||
enumDescriptions: [
|
||||
nls.localize('suggest.insertMode.insert', "Insert suggestion without overwriting text right of the cursor."),
|
||||
nls.localize('suggest.insertMode.replace', "Insert suggestion and overwrite text right of the cursor."),
|
||||
],
|
||||
default: defaults.insertMode,
|
||||
description: nls.localize('suggest.insertMode', "Controls whether words are overwritten when accepting completions. Note that this depends on extensions opting into this feature.")
|
||||
},
|
||||
'editor.suggest.insertHighlight': {
|
||||
type: 'boolean',
|
||||
default: defaults.overwriteOnAccept,
|
||||
description: nls.localize('suggest.overwriteOnAccept', "Controls whether words are overwritten when accepting completions.")
|
||||
default: defaults.insertHighlight,
|
||||
description: nls.localize('suggest.insertHighlight', "Controls whether unexpected text modifications while accepting completions should be highlighted, e.g `insertMode` is `replace` but the completion only supports `insert`.")
|
||||
},
|
||||
'editor.suggest.filterGraceful': {
|
||||
type: 'boolean',
|
||||
@@ -2381,135 +2650,139 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
maximum: 15,
|
||||
description: nls.localize('suggest.maxVisibleSuggestions', "Controls how many suggestions IntelliSense will show before showing a scrollbar (maximum 15).")
|
||||
},
|
||||
'editor.suggest.filteredTypes.method': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.method', "When set to `false` IntelliSense never shows `method` suggestions.")
|
||||
'editor.suggest.filteredTypes': {
|
||||
type: 'object',
|
||||
deprecationMessage: nls.localize('deprecated', "This setting is deprecated, please use separate settings like 'editor.suggest.showKeywords' or 'editor.suggest.showSnippets' instead.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.function': {
|
||||
'editor.suggest.showMethods': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.function', "When set to `false` IntelliSense never shows `function` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showMethods', "When enabled IntelliSense shows `method`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.constructor': {
|
||||
'editor.suggest.showFunctions': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.constructor', "When set to `false` IntelliSense never shows `constructor` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showFunctions', "When enabled IntelliSense shows `function`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.field': {
|
||||
'editor.suggest.showConstructors': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.field', "When set to `false` IntelliSense never shows `field` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showConstructors', "When enabled IntelliSense shows `constructor`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.variable': {
|
||||
'editor.suggest.showFields': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.variable', "When set to `false` IntelliSense never shows `variable` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showFields', "When enabled IntelliSense shows `field`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.class': {
|
||||
'editor.suggest.showVariables': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.class', "When set to `false` IntelliSense never shows `class` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showVariables', "When enabled IntelliSense shows `variable`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.struct': {
|
||||
'editor.suggest.showClasses': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.struct', "When set to `false` IntelliSense never shows `struct` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showClasss', "When enabled IntelliSense shows `class`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.interface': {
|
||||
'editor.suggest.showStructs': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.interface', "When set to `false` IntelliSense never shows `interface` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showStructs', "When enabled IntelliSense shows `struct`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.module': {
|
||||
'editor.suggest.showInterfaces': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.module', "When set to `false` IntelliSense never shows `module` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showInterfaces', "When enabled IntelliSense shows `interface`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.property': {
|
||||
'editor.suggest.showModules': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.property', "When set to `false` IntelliSense never shows `property` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showModules', "When enabled IntelliSense shows `module`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.event': {
|
||||
'editor.suggest.showProperties': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.event', "When set to `false` IntelliSense never shows `event` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showPropertys', "When enabled IntelliSense shows `property`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.operator': {
|
||||
'editor.suggest.showEvents': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.operator', "When set to `false` IntelliSense never shows `operator` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showEvents', "When enabled IntelliSense shows `event`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.unit': {
|
||||
'editor.suggest.showOperators': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.unit', "When set to `false` IntelliSense never shows `unit` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showOperators', "When enabled IntelliSense shows `operator`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.value': {
|
||||
'editor.suggest.showUnits': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.value', "When set to `false` IntelliSense never shows `value` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showUnits', "When enabled IntelliSense shows `unit`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.constant': {
|
||||
'editor.suggest.showValues': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.constant', "When set to `false` IntelliSense never shows `constant` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showValues', "When enabled IntelliSense shows `value`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.enum': {
|
||||
'editor.suggest.showConstants': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.enum', "When set to `false` IntelliSense never shows `enum` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showConstants', "When enabled IntelliSense shows `constant`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.enumMember': {
|
||||
'editor.suggest.showEnums': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.enumMember', "When set to `false` IntelliSense never shows `enumMember` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showEnums', "When enabled IntelliSense shows `enum`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.keyword': {
|
||||
'editor.suggest.showEnumMembers': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.keyword', "When set to `false` IntelliSense never shows `keyword` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showEnumMembers', "When enabled IntelliSense shows `enumMember`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.text': {
|
||||
'editor.suggest.showKeywords': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.text', "When set to `false` IntelliSense never shows `text` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showKeywords', "When enabled IntelliSense shows `keyword`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.color': {
|
||||
'editor.suggest.showWords': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.color', "When set to `false` IntelliSense never shows `color` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showTexts', "When enabled IntelliSense shows `text`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.file': {
|
||||
'editor.suggest.showColors': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.file', "When set to `false` IntelliSense never shows `file` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showColors', "When enabled IntelliSense shows `color`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.reference': {
|
||||
'editor.suggest.showFiles': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.reference', "When set to `false` IntelliSense never shows `reference` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showFiles', "When enabled IntelliSense shows `file`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.customcolor': {
|
||||
'editor.suggest.showReferences': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.customcolor', "When set to `false` IntelliSense never shows `customcolor` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showReferences', "When enabled IntelliSense shows `reference`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.folder': {
|
||||
'editor.suggest.showCustomcolors': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.folder', "When set to `false` IntelliSense never shows `folder` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showCustomcolors', "When enabled IntelliSense shows `customcolor`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.typeParameter': {
|
||||
'editor.suggest.showFolders': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.typeParameter', "When set to `false` IntelliSense never shows `typeParameter` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showFolders', "When enabled IntelliSense shows `folder`-suggestions.")
|
||||
},
|
||||
'editor.suggest.filteredTypes.snippet': {
|
||||
'editor.suggest.showTypeParameters': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('suggest.filtered.snippet', "When set to `false` IntelliSense never shows `snippet` suggestions.")
|
||||
markdownDescription: nls.localize('editor.suggest.showTypeParameters', "When enabled IntelliSense shows `typeParameter`-suggestions.")
|
||||
},
|
||||
'editor.suggest.showSnippets': {
|
||||
type: 'boolean',
|
||||
default: true,
|
||||
markdownDescription: nls.localize('editor.suggest.showSnippets', "When enabled IntelliSense shows `snippet`-suggestions.")
|
||||
},
|
||||
}
|
||||
);
|
||||
@@ -2521,14 +2794,39 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
|
||||
}
|
||||
const input = _input as ISuggestOptions;
|
||||
return {
|
||||
overwriteOnAccept: EditorBooleanOption.boolean(input.overwriteOnAccept, this.defaultValue.overwriteOnAccept),
|
||||
insertMode: EditorStringEnumOption.stringSet(input.insertMode, this.defaultValue.insertMode, ['insert', 'replace']),
|
||||
insertHighlight: EditorBooleanOption.boolean(input.insertHighlight, this.defaultValue.insertHighlight),
|
||||
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
|
||||
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
|
||||
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
|
||||
shareSuggestSelections: EditorBooleanOption.boolean(input.shareSuggestSelections, this.defaultValue.shareSuggestSelections),
|
||||
showIcons: EditorBooleanOption.boolean(input.showIcons, this.defaultValue.showIcons),
|
||||
maxVisibleSuggestions: EditorIntOption.clampedInt(input.maxVisibleSuggestions, this.defaultValue.maxVisibleSuggestions, 1, 15),
|
||||
filteredTypes: isObject(input.filteredTypes) ? input.filteredTypes : Object.create(null)
|
||||
showMethods: EditorBooleanOption.boolean(input.showMethods, this.defaultValue.showMethods),
|
||||
showFunctions: EditorBooleanOption.boolean(input.showFunctions, this.defaultValue.showFunctions),
|
||||
showConstructors: EditorBooleanOption.boolean(input.showConstructors, this.defaultValue.showConstructors),
|
||||
showFields: EditorBooleanOption.boolean(input.showFields, this.defaultValue.showFields),
|
||||
showVariables: EditorBooleanOption.boolean(input.showVariables, this.defaultValue.showVariables),
|
||||
showClasses: EditorBooleanOption.boolean(input.showClasses, this.defaultValue.showClasses),
|
||||
showStructs: EditorBooleanOption.boolean(input.showStructs, this.defaultValue.showStructs),
|
||||
showInterfaces: EditorBooleanOption.boolean(input.showInterfaces, this.defaultValue.showInterfaces),
|
||||
showModules: EditorBooleanOption.boolean(input.showModules, this.defaultValue.showModules),
|
||||
showProperties: EditorBooleanOption.boolean(input.showProperties, this.defaultValue.showProperties),
|
||||
showEvents: EditorBooleanOption.boolean(input.showEvents, this.defaultValue.showEvents),
|
||||
showOperators: EditorBooleanOption.boolean(input.showOperators, this.defaultValue.showOperators),
|
||||
showUnits: EditorBooleanOption.boolean(input.showUnits, this.defaultValue.showUnits),
|
||||
showValues: EditorBooleanOption.boolean(input.showValues, this.defaultValue.showValues),
|
||||
showConstants: EditorBooleanOption.boolean(input.showConstants, this.defaultValue.showConstants),
|
||||
showEnums: EditorBooleanOption.boolean(input.showEnums, this.defaultValue.showEnums),
|
||||
showEnumMembers: EditorBooleanOption.boolean(input.showEnumMembers, this.defaultValue.showEnumMembers),
|
||||
showKeywords: EditorBooleanOption.boolean(input.showKeywords, this.defaultValue.showKeywords),
|
||||
showWords: EditorBooleanOption.boolean(input.showWords, this.defaultValue.showWords),
|
||||
showColors: EditorBooleanOption.boolean(input.showColors, this.defaultValue.showColors),
|
||||
showFiles: EditorBooleanOption.boolean(input.showFiles, this.defaultValue.showFiles),
|
||||
showReferences: EditorBooleanOption.boolean(input.showReferences, this.defaultValue.showReferences),
|
||||
showFolders: EditorBooleanOption.boolean(input.showFolders, this.defaultValue.showFolders),
|
||||
showTypeParameters: EditorBooleanOption.boolean(input.showTypeParameters, this.defaultValue.showTypeParameters),
|
||||
showSnippets: EditorBooleanOption.boolean(input.showSnippets, this.defaultValue.showSnippets),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -2715,6 +3013,7 @@ export const enum EditorOption {
|
||||
acceptSuggestionOnCommitCharacter,
|
||||
acceptSuggestionOnEnter,
|
||||
accessibilitySupport,
|
||||
accessibilityPageSize,
|
||||
ariaLabel,
|
||||
autoClosingBrackets,
|
||||
autoClosingOvertype,
|
||||
@@ -2730,6 +3029,7 @@ export const enum EditorOption {
|
||||
cursorSmoothCaretAnimation,
|
||||
cursorStyle,
|
||||
cursorSurroundingLines,
|
||||
cursorSurroundingLinesStyle,
|
||||
cursorWidth,
|
||||
disableLayerHinting,
|
||||
disableMonospaceOptimizations,
|
||||
@@ -2841,6 +3141,8 @@ export const EditorOptions = {
|
||||
}
|
||||
)),
|
||||
accessibilitySupport: register(new EditorAccessibilitySupport()),
|
||||
accessibilityPageSize: register(new EditorIntOption(EditorOption.accessibilityPageSize, 'accessibilityPageSize', 10, 1, Constants.MAX_SAFE_SMALL_INTEGER,
|
||||
{ description: nls.localize('accessibilityPageSize', "Controls the number of lines in the editor that can be read out by a screen reader. Warning: this has a performance implication for numbers larger than the default.") })),
|
||||
ariaLabel: register(new EditorStringOption(
|
||||
EditorOption.ariaLabel, 'ariaLabel', nls.localize('editorViewAccessibleLabel', "Editor content")
|
||||
)),
|
||||
@@ -2885,9 +3187,21 @@ export const EditorOptions = {
|
||||
description: nls.localize('autoClosingQuotes', "Controls whether the editor should automatically close quotes after the user adds an opening quote.")
|
||||
}
|
||||
)),
|
||||
autoIndent: register(new EditorBooleanOption(
|
||||
EditorOption.autoIndent, 'autoIndent', true,
|
||||
{ description: nls.localize('autoIndent', "Controls whether the editor should automatically adjust the indentation when users type, paste or move lines. Extensions with indentation rules of the language must be available.") }
|
||||
autoIndent: register(new EditorEnumOption(
|
||||
EditorOption.autoIndent, 'autoIndent',
|
||||
EditorAutoIndentStrategy.Full, 'full',
|
||||
['none', 'keep', 'brackets', 'advanced', 'full'],
|
||||
_autoIndentFromString,
|
||||
{
|
||||
enumDescriptions: [
|
||||
nls.localize('editor.autoIndent.none', "The editor will not insert indentation automatically."),
|
||||
nls.localize('editor.autoIndent.keep', "The editor will keep the current line's indentation."),
|
||||
nls.localize('editor.autoIndent.brackets', "The editor will keep the current line's indentation and honor language defined brackets."),
|
||||
nls.localize('editor.autoIndent.advanced', "The editor will keep the current line's indentation, honor language defined brackets and invoke special onEnterRules defined by languages."),
|
||||
nls.localize('editor.autoIndent.full', "The editor will keep the current line's indentation, honor language defined brackets, invoke special onEnterRules defined by languages, and honor indentationRules defined by languages."),
|
||||
],
|
||||
description: nls.localize('autoIndent', "Controls whether the editor should automatically adjust the indentation when users type, paste, move or indent lines.")
|
||||
}
|
||||
)),
|
||||
automaticLayout: register(new EditorBooleanOption(
|
||||
EditorOption.automaticLayout, 'automaticLayout', false,
|
||||
@@ -2944,6 +3258,18 @@ export const EditorOptions = {
|
||||
0, 0, Constants.MAX_SAFE_SMALL_INTEGER,
|
||||
{ description: nls.localize('cursorSurroundingLines', "Controls the minimal number of visible leading and trailing lines surrounding the cursor. Known as 'scrollOff' or `scrollOffset` in some other editors.") }
|
||||
)),
|
||||
cursorSurroundingLinesStyle: register(new EditorStringEnumOption(
|
||||
EditorOption.cursorSurroundingLinesStyle, 'cursorSurroundingLinesStyle',
|
||||
'default' as 'default' | 'all',
|
||||
['default', 'all'] as const,
|
||||
{
|
||||
enumDescriptions: [
|
||||
nls.localize('cursorSurroundingLinesStyle.default', "`cursorSurroundingLines` is enforced only when triggered via the keyboard or API."),
|
||||
nls.localize('cursorSurroundingLinesStyle.all', "`cursorSurroundingLines` is enforced always.")
|
||||
],
|
||||
description: nls.localize('cursorSurroundingLinesStyle', "Controls when `cursorSurroundingLines` should be enforced.")
|
||||
}
|
||||
)),
|
||||
cursorWidth: register(new EditorIntOption(
|
||||
EditorOption.cursorWidth, 'cursorWidth',
|
||||
0, 0, Constants.MAX_SAFE_SMALL_INTEGER,
|
||||
@@ -3102,8 +3428,7 @@ export const EditorOptions = {
|
||||
)),
|
||||
overviewRulerLanes: register(new EditorIntOption(
|
||||
EditorOption.overviewRulerLanes, 'overviewRulerLanes',
|
||||
3, 0, 3,
|
||||
{ description: nls.localize('overviewRulerLanes', "Controls the number of decorations that can show up at the same position in the overview ruler.") }
|
||||
3, 0, 3
|
||||
)),
|
||||
parameterHints: register(new EditorParameterHints()),
|
||||
quickSuggestions: register(new EditorQuickSuggestions()),
|
||||
|
||||
@@ -37,6 +37,18 @@ export class CursorStateChangedEvent {
|
||||
* The primary selection is always at index 0.
|
||||
*/
|
||||
readonly selections: Selection[];
|
||||
/**
|
||||
* The new model version id that `selections` apply to.
|
||||
*/
|
||||
readonly modelVersionId: number;
|
||||
/**
|
||||
* The old selections.
|
||||
*/
|
||||
readonly oldSelections: Selection[] | null;
|
||||
/**
|
||||
* The model version id the that `oldSelections` apply to.
|
||||
*/
|
||||
readonly oldModelVersionId: number;
|
||||
/**
|
||||
* Source of the call that caused the event.
|
||||
*/
|
||||
@@ -46,8 +58,11 @@ export class CursorStateChangedEvent {
|
||||
*/
|
||||
readonly reason: CursorChangeReason;
|
||||
|
||||
constructor(selections: Selection[], source: string, reason: CursorChangeReason) {
|
||||
constructor(selections: Selection[], modelVersionId: number, oldSelections: Selection[] | null, oldModelVersionId: number, source: string, reason: CursorChangeReason) {
|
||||
this.selections = selections;
|
||||
this.modelVersionId = modelVersionId;
|
||||
this.oldSelections = oldSelections;
|
||||
this.oldModelVersionId = oldModelVersionId;
|
||||
this.source = source;
|
||||
this.reason = reason;
|
||||
}
|
||||
@@ -540,7 +555,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|| oldState.cursorState.length !== newState.cursorState.length
|
||||
|| newState.cursorState.some((newCursorState, i) => !newCursorState.modelState.equals(oldState.cursorState[i].modelState))
|
||||
) {
|
||||
this._onDidChange.fire(new CursorStateChangedEvent(selections, source || 'keyboard', reason));
|
||||
const oldSelections = oldState ? oldState.cursorState.map(s => s.modelState.selection) : null;
|
||||
const oldModelVersionId = oldState ? oldState.modelVersionId : 0;
|
||||
this._onDidChange.fire(new CursorStateChangedEvent(selections, newState.modelVersionId, oldSelections, oldModelVersionId, source || 'keyboard', reason));
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditorAutoClosingStrategy, EditorAutoSurroundStrategy, ConfigurationChangedEvent, EditorAutoClosingOvertypeStrategy, EditorOption, EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -103,7 +103,7 @@ export class CursorConfiguration {
|
||||
public readonly autoClosingQuotes: EditorAutoClosingStrategy;
|
||||
public readonly autoClosingOvertype: EditorAutoClosingOvertypeStrategy;
|
||||
public readonly autoSurround: EditorAutoSurroundStrategy;
|
||||
public readonly autoIndent: boolean;
|
||||
public readonly autoIndent: EditorAutoIndentStrategy;
|
||||
public readonly autoClosingPairsOpen2: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
public readonly autoClosingPairsClose2: Map<string, StandardAutoClosingPairConditional[]>;
|
||||
public readonly surroundingPairs: CharacterMap;
|
||||
@@ -523,12 +523,15 @@ export class CursorColumns {
|
||||
if (codePoint === CharCode.Tab) {
|
||||
result = CursorColumns.nextRenderTabStop(result, tabSize);
|
||||
} else {
|
||||
let graphemeBreakType = strings.getGraphemeBreakType(codePoint);
|
||||
while (i < endOffset) {
|
||||
const nextCodePoint = strings.getNextCodePoint(lineContent, endOffset, i);
|
||||
if (!strings.isUnicodeMark(nextCodePoint)) {
|
||||
const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint);
|
||||
if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
|
||||
break;
|
||||
}
|
||||
i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
||||
graphemeBreakType = nextGraphemeBreakType;
|
||||
}
|
||||
if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) {
|
||||
result = result + 2;
|
||||
@@ -582,12 +585,15 @@ export class CursorColumns {
|
||||
if (codePoint === CharCode.Tab) {
|
||||
afterVisibleColumn = CursorColumns.nextRenderTabStop(beforeVisibleColumn, tabSize);
|
||||
} else {
|
||||
let graphemeBreakType = strings.getGraphemeBreakType(codePoint);
|
||||
while (i < lineLength) {
|
||||
const nextCodePoint = strings.getNextCodePoint(lineContent, lineLength, i);
|
||||
if (!strings.isUnicodeMark(nextCodePoint)) {
|
||||
const nextGraphemeBreakType = strings.getGraphemeBreakType(nextCodePoint);
|
||||
if (strings.breakBetweenGraphemeBreakType(graphemeBreakType, nextGraphemeBreakType)) {
|
||||
break;
|
||||
}
|
||||
i += (nextCodePoint >= Constants.UNICODE_SUPPLEMENTARY_PLANE_BEGIN ? 2 : 1);
|
||||
graphemeBreakType = nextGraphemeBreakType;
|
||||
}
|
||||
if (strings.isFullWidthCharacter(codePoint) || strings.isEmojiImprecise(codePoint)) {
|
||||
afterVisibleColumn = beforeVisibleColumn + 2;
|
||||
|
||||
@@ -72,6 +72,18 @@ export interface ICursorSelectionChangedEvent {
|
||||
* The secondary selections.
|
||||
*/
|
||||
readonly secondarySelections: Selection[];
|
||||
/**
|
||||
* The model version id.
|
||||
*/
|
||||
readonly modelVersionId: number;
|
||||
/**
|
||||
* The old selections.
|
||||
*/
|
||||
readonly oldSelections: Selection[] | null;
|
||||
/**
|
||||
* The model version id the that `oldSelections` refer to.
|
||||
*/
|
||||
readonly oldModelVersionId: number;
|
||||
/**
|
||||
* Source of the call that caused the event.
|
||||
*/
|
||||
|
||||
@@ -19,6 +19,7 @@ import { ITextModel } from 'vs/editor/common/model';
|
||||
import { EnterAction, IndentAction, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export class TypeOperations {
|
||||
|
||||
@@ -34,7 +35,8 @@ export class TypeOperations {
|
||||
tabSize: config.tabSize,
|
||||
indentSize: config.indentSize,
|
||||
insertSpaces: config.insertSpaces,
|
||||
useTabStops: config.useTabStops
|
||||
useTabStops: config.useTabStops,
|
||||
autoIndent: config.autoIndent
|
||||
});
|
||||
}
|
||||
return commands;
|
||||
@@ -48,7 +50,8 @@ export class TypeOperations {
|
||||
tabSize: config.tabSize,
|
||||
indentSize: config.indentSize,
|
||||
insertSpaces: config.insertSpaces,
|
||||
useTabStops: config.useTabStops
|
||||
useTabStops: config.useTabStops,
|
||||
autoIndent: config.autoIndent
|
||||
});
|
||||
}
|
||||
return commands;
|
||||
@@ -149,15 +152,15 @@ export class TypeOperations {
|
||||
let action: IndentAction | EnterAction | null = null;
|
||||
let indentation: string = '';
|
||||
|
||||
let expectedIndentAction = config.autoIndent ? LanguageConfigurationRegistry.getInheritIndentForLine(model, lineNumber, false) : null;
|
||||
const expectedIndentAction = LanguageConfigurationRegistry.getInheritIndentForLine(config.autoIndent, model, lineNumber, false);
|
||||
if (expectedIndentAction) {
|
||||
action = expectedIndentAction.action;
|
||||
indentation = expectedIndentAction.indentation;
|
||||
} else if (lineNumber > 1) {
|
||||
let lastLineNumber: number;
|
||||
for (lastLineNumber = lineNumber - 1; lastLineNumber >= 1; lastLineNumber--) {
|
||||
let lineText = model.getLineContent(lastLineNumber);
|
||||
let nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText);
|
||||
const lineText = model.getLineContent(lastLineNumber);
|
||||
const nonWhitespaceIdx = strings.lastNonWhitespaceIndex(lineText);
|
||||
if (nonWhitespaceIdx >= 0) {
|
||||
break;
|
||||
}
|
||||
@@ -168,14 +171,10 @@ export class TypeOperations {
|
||||
return null;
|
||||
}
|
||||
|
||||
let maxColumn = model.getLineMaxColumn(lastLineNumber);
|
||||
let expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn));
|
||||
const maxColumn = model.getLineMaxColumn(lastLineNumber);
|
||||
const expectedEnterAction = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, new Range(lastLineNumber, maxColumn, lastLineNumber, maxColumn));
|
||||
if (expectedEnterAction) {
|
||||
indentation = expectedEnterAction.indentation;
|
||||
action = expectedEnterAction.enterAction;
|
||||
if (action) {
|
||||
indentation += action.appendText;
|
||||
}
|
||||
indentation = expectedEnterAction.indentation + expectedEnterAction.appendText;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +250,8 @@ export class TypeOperations {
|
||||
tabSize: config.tabSize,
|
||||
indentSize: config.indentSize,
|
||||
insertSpaces: config.insertSpaces,
|
||||
useTabStops: config.useTabStops
|
||||
useTabStops: config.useTabStops,
|
||||
autoIndent: config.autoIndent
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -289,104 +289,97 @@ export class TypeOperations {
|
||||
}
|
||||
|
||||
private static _enter(config: CursorConfiguration, model: ITextModel, keepPosition: boolean, range: Range): ICommand {
|
||||
if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) {
|
||||
if (config.autoIndent === EditorAutoIndentStrategy.None) {
|
||||
return TypeOperations._typeCommand(range, '\n', keepPosition);
|
||||
}
|
||||
if (!model.isCheapToTokenize(range.getStartPosition().lineNumber) || config.autoIndent === EditorAutoIndentStrategy.Keep) {
|
||||
let lineText = model.getLineContent(range.startLineNumber);
|
||||
let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
|
||||
}
|
||||
|
||||
let r = LanguageConfigurationRegistry.getEnterAction(model, range);
|
||||
const r = LanguageConfigurationRegistry.getEnterAction(config.autoIndent, model, range);
|
||||
if (r) {
|
||||
let enterAction = r.enterAction;
|
||||
let indentation = r.indentation;
|
||||
|
||||
if (enterAction.indentAction === IndentAction.None) {
|
||||
if (r.indentAction === IndentAction.None) {
|
||||
// Nothing special
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition);
|
||||
|
||||
} else if (enterAction.indentAction === IndentAction.Indent) {
|
||||
} else if (r.indentAction === IndentAction.Indent) {
|
||||
// Indent once
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation + enterAction.appendText), keepPosition);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(r.indentation + r.appendText), keepPosition);
|
||||
|
||||
} else if (enterAction.indentAction === IndentAction.IndentOutdent) {
|
||||
} else if (r.indentAction === IndentAction.IndentOutdent) {
|
||||
// Ultra special
|
||||
let normalIndent = config.normalizeIndentation(indentation);
|
||||
let increasedIndent = config.normalizeIndentation(indentation + enterAction.appendText);
|
||||
const normalIndent = config.normalizeIndentation(r.indentation);
|
||||
const increasedIndent = config.normalizeIndentation(r.indentation + r.appendText);
|
||||
|
||||
let typeText = '\n' + increasedIndent + '\n' + normalIndent;
|
||||
const typeText = '\n' + increasedIndent + '\n' + normalIndent;
|
||||
|
||||
if (keepPosition) {
|
||||
return new ReplaceCommandWithoutChangingPosition(range, typeText, true);
|
||||
} else {
|
||||
return new ReplaceCommandWithOffsetCursorState(range, typeText, -1, increasedIndent.length - normalIndent.length, true);
|
||||
}
|
||||
} else if (enterAction.indentAction === IndentAction.Outdent) {
|
||||
let actualIndentation = TypeOperations.unshiftIndent(config, indentation);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + enterAction.appendText), keepPosition);
|
||||
} else if (r.indentAction === IndentAction.Outdent) {
|
||||
const actualIndentation = TypeOperations.unshiftIndent(config, r.indentation);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(actualIndentation + r.appendText), keepPosition);
|
||||
}
|
||||
}
|
||||
|
||||
// no enter rules applied, we should check indentation rules then.
|
||||
if (!config.autoIndent) {
|
||||
// Nothing special
|
||||
let lineText = model.getLineContent(range.startLineNumber);
|
||||
let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
|
||||
}
|
||||
const lineText = model.getLineContent(range.startLineNumber);
|
||||
const indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
|
||||
|
||||
let ir = LanguageConfigurationRegistry.getIndentForEnter(model, range, {
|
||||
unshiftIndent: (indent) => {
|
||||
return TypeOperations.unshiftIndent(config, indent);
|
||||
},
|
||||
shiftIndent: (indent) => {
|
||||
return TypeOperations.shiftIndent(config, indent);
|
||||
},
|
||||
normalizeIndentation: (indent) => {
|
||||
return config.normalizeIndentation(indent);
|
||||
}
|
||||
}, config.autoIndent);
|
||||
|
||||
let lineText = model.getLineContent(range.startLineNumber);
|
||||
let indentation = strings.getLeadingWhitespace(lineText).substring(0, range.startColumn - 1);
|
||||
|
||||
if (ir) {
|
||||
let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition());
|
||||
let oldEndColumn = range.endColumn;
|
||||
|
||||
let beforeText = '\n';
|
||||
if (indentation !== config.normalizeIndentation(ir.beforeEnter)) {
|
||||
beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n';
|
||||
range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn);
|
||||
}
|
||||
|
||||
let newLineContent = model.getLineContent(range.endLineNumber);
|
||||
let firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent);
|
||||
if (firstNonWhitespace >= 0) {
|
||||
range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1));
|
||||
} else {
|
||||
range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
|
||||
}
|
||||
|
||||
if (keepPosition) {
|
||||
return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true);
|
||||
} else {
|
||||
let offset = 0;
|
||||
if (oldEndColumn <= firstNonWhitespace + 1) {
|
||||
if (!config.insertSpaces) {
|
||||
oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize);
|
||||
}
|
||||
offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0);
|
||||
if (config.autoIndent >= EditorAutoIndentStrategy.Full) {
|
||||
const ir = LanguageConfigurationRegistry.getIndentForEnter(config.autoIndent, model, range, {
|
||||
unshiftIndent: (indent) => {
|
||||
return TypeOperations.unshiftIndent(config, indent);
|
||||
},
|
||||
shiftIndent: (indent) => {
|
||||
return TypeOperations.shiftIndent(config, indent);
|
||||
},
|
||||
normalizeIndentation: (indent) => {
|
||||
return config.normalizeIndentation(indent);
|
||||
}
|
||||
return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true);
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
|
||||
if (ir) {
|
||||
let oldEndViewColumn = CursorColumns.visibleColumnFromColumn2(config, model, range.getEndPosition());
|
||||
const oldEndColumn = range.endColumn;
|
||||
|
||||
let beforeText = '\n';
|
||||
if (indentation !== config.normalizeIndentation(ir.beforeEnter)) {
|
||||
beforeText = config.normalizeIndentation(ir.beforeEnter) + lineText.substring(indentation.length, range.startColumn - 1) + '\n';
|
||||
range = new Range(range.startLineNumber, 1, range.endLineNumber, range.endColumn);
|
||||
}
|
||||
|
||||
const newLineContent = model.getLineContent(range.endLineNumber);
|
||||
const firstNonWhitespace = strings.firstNonWhitespaceIndex(newLineContent);
|
||||
if (firstNonWhitespace >= 0) {
|
||||
range = range.setEndPosition(range.endLineNumber, Math.max(range.endColumn, firstNonWhitespace + 1));
|
||||
} else {
|
||||
range = range.setEndPosition(range.endLineNumber, model.getLineMaxColumn(range.endLineNumber));
|
||||
}
|
||||
|
||||
if (keepPosition) {
|
||||
return new ReplaceCommandWithoutChangingPosition(range, beforeText + config.normalizeIndentation(ir.afterEnter), true);
|
||||
} else {
|
||||
let offset = 0;
|
||||
if (oldEndColumn <= firstNonWhitespace + 1) {
|
||||
if (!config.insertSpaces) {
|
||||
oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize);
|
||||
}
|
||||
offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0);
|
||||
}
|
||||
return new ReplaceCommandWithOffsetCursorState(range, beforeText + config.normalizeIndentation(ir.afterEnter), 0, offset, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TypeOperations._typeCommand(range, '\n' + config.normalizeIndentation(indentation), keepPosition);
|
||||
}
|
||||
|
||||
private static _isAutoIndentType(config: CursorConfiguration, model: ITextModel, selections: Selection[]): boolean {
|
||||
if (!config.autoIndent) {
|
||||
if (config.autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -400,8 +393,8 @@ export class TypeOperations {
|
||||
}
|
||||
|
||||
private static _runAutoIndentType(config: CursorConfiguration, model: ITextModel, range: Range, ch: string): ICommand | null {
|
||||
let currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
|
||||
let actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(model, range, ch, {
|
||||
const currentIndentation = LanguageConfigurationRegistry.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
|
||||
const actualIndentation = LanguageConfigurationRegistry.getIndentActionForType(config.autoIndent, model, range, ch, {
|
||||
shiftIndent: (indentation) => {
|
||||
return TypeOperations.shiftIndent(config, indentation);
|
||||
},
|
||||
@@ -415,7 +408,7 @@ export class TypeOperations {
|
||||
}
|
||||
|
||||
if (actualIndentation !== config.normalizeIndentation(currentIndentation)) {
|
||||
let firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber);
|
||||
const firstNonWhitespace = model.getLineFirstNonWhitespaceColumn(range.startLineNumber);
|
||||
if (firstNonWhitespace === 0) {
|
||||
return TypeOperations._typeCommand(
|
||||
new Range(range.startLineNumber, 0, range.endLineNumber, range.endColumn),
|
||||
@@ -459,9 +452,10 @@ export class TypeOperations {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not over-type after a backslash
|
||||
// Do not over-type quotes after a backslash
|
||||
const chIsQuote = isQuote(ch);
|
||||
const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null;
|
||||
if (beforeCharacter === CharCode.Backslash) {
|
||||
if (beforeCharacter === CharCode.Backslash && chIsQuote) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -289,17 +289,26 @@ export class WordOperations {
|
||||
column = model.getLineMaxColumn(lineNumber);
|
||||
}
|
||||
} else if (wordNavigationType === WordNavigationType.WordAccessibility) {
|
||||
if (movedDown) {
|
||||
// If we move to the next line, pretend that the cursor is right before the first character.
|
||||
// This is needed when the first word starts right at the first character - and in order not to miss it,
|
||||
// we need to start before.
|
||||
column = 0;
|
||||
}
|
||||
|
||||
while (
|
||||
nextWordOnLine
|
||||
&& nextWordOnLine.wordType === WordType.Separator
|
||||
&& (nextWordOnLine.wordType === WordType.Separator
|
||||
|| nextWordOnLine.start + 1 <= column
|
||||
)
|
||||
) {
|
||||
// Skip over a word made up of one single separator
|
||||
// Also skip over word if it begins before current cursor position to ascertain we're moving forward at least 1 character.
|
||||
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1));
|
||||
}
|
||||
|
||||
if (nextWordOnLine) {
|
||||
column = nextWordOnLine.end + 1;
|
||||
column = nextWordOnLine.start + 1;
|
||||
} else {
|
||||
column = model.getLineMaxColumn(lineNumber);
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ export class LineTokens implements IViewLineTokens {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public getMetadata(tokenIndex: number): number {
|
||||
const metadata = this._tokens[(tokenIndex << 1) + 1];
|
||||
return metadata;
|
||||
}
|
||||
|
||||
public getLanguageId(tokenIndex: number): LanguageId {
|
||||
const metadata = this._tokens[(tokenIndex << 1) + 1];
|
||||
return TokenMetadata.getLanguageId(metadata);
|
||||
@@ -132,8 +137,8 @@ export class LineTokens implements IViewLineTokens {
|
||||
|
||||
while (low < high) {
|
||||
|
||||
let mid = low + Math.floor((high - low) / 2);
|
||||
let endOffset = tokens[(mid << 1)];
|
||||
const mid = low + Math.floor((high - low) / 2);
|
||||
const endOffset = tokens[(mid << 1)];
|
||||
|
||||
if (endOffset === desiredIndex) {
|
||||
return mid + 1;
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChange
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { MultilineTokens } from 'vs/editor/common/model/tokensStore';
|
||||
import { MultilineTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
|
||||
|
||||
/**
|
||||
* Vertical Lane in the overview ruler of the editor.
|
||||
@@ -30,7 +30,8 @@ export enum OverviewRulerLane {
|
||||
* Position in the minimap to render the decoration.
|
||||
*/
|
||||
export enum MinimapPosition {
|
||||
Inline = 1
|
||||
Inline = 1,
|
||||
Gutter = 2
|
||||
}
|
||||
|
||||
export interface IDecorationOptions {
|
||||
@@ -791,6 +792,11 @@ export interface ITextModel {
|
||||
*/
|
||||
setTokens(tokens: MultilineTokens[]): void;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setSemanticTokens(tokens: MultilineTokens2[] | null): void;
|
||||
|
||||
/**
|
||||
* Flush all tokenization state.
|
||||
* @internal
|
||||
@@ -1192,6 +1198,13 @@ export interface ITextBufferFactory {
|
||||
getFirstLineText(lengthLimit: number): string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const enum ModelConstants {
|
||||
FIRST_LINE_DETECTION_LENGTH_LIMIT = 1000
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -577,23 +577,34 @@ export class PieceTreeBase {
|
||||
|
||||
let m: RegExpExecArray | null;
|
||||
// Reset regex to search from the beginning
|
||||
searcher.reset(start);
|
||||
let ret: BufferCursor = { line: 0, column: 0 };
|
||||
let searchText: string;
|
||||
let offsetInBuffer: (offset: number) => number;
|
||||
|
||||
if (searcher._wordSeparators) {
|
||||
searchText = buffer.buffer.substring(start, end);
|
||||
offsetInBuffer = (offset: number) => offset + start;
|
||||
searcher.reset(-1);
|
||||
} else {
|
||||
searchText = buffer.buffer;
|
||||
offsetInBuffer = (offset: number) => offset;
|
||||
searcher.reset(start);
|
||||
}
|
||||
|
||||
do {
|
||||
m = searcher.next(buffer.buffer);
|
||||
m = searcher.next(searchText);
|
||||
|
||||
if (m) {
|
||||
if (m.index >= end) {
|
||||
if (offsetInBuffer(m.index) >= end) {
|
||||
return resultLen;
|
||||
}
|
||||
this.positionInBuffer(node, m.index - startOffsetInBuffer, ret);
|
||||
this.positionInBuffer(node, offsetInBuffer(m.index) - startOffsetInBuffer, ret);
|
||||
let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret);
|
||||
let retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1;
|
||||
let retEndColumn = retStartColumn + m[0].length;
|
||||
result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches);
|
||||
|
||||
if (m.index + m[0].length >= end) {
|
||||
if (offsetInBuffer(m.index) + m[0].length >= end) {
|
||||
return resultLen;
|
||||
}
|
||||
if (resultLen >= limitResultCount) {
|
||||
|
||||
@@ -57,7 +57,7 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory {
|
||||
}
|
||||
|
||||
public getFirstLineText(lengthLimit: number): string {
|
||||
return this._chunks[0].buffer.substr(0, 100).split(/\r\n|\r|\n/)[0];
|
||||
return this._chunks[0].buffer.substr(0, lengthLimit).split(/\r\n|\r|\n/)[0];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ import { BracketsUtils, RichEditBracket, RichEditBrackets } from 'vs/editor/comm
|
||||
import { ITheme, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { VSBufferReadableStream, VSBuffer } from 'vs/base/common/buffer';
|
||||
import { TokensStore, MultilineTokens, countEOL } from 'vs/editor/common/model/tokensStore';
|
||||
import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
function createTextBufferBuilder() {
|
||||
@@ -276,6 +276,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
private _languageIdentifier: LanguageIdentifier;
|
||||
private readonly _languageRegistryListener: IDisposable;
|
||||
private readonly _tokens: TokensStore;
|
||||
private readonly _tokens2: TokensStore2;
|
||||
private readonly _tokenization: TextModelTokenization;
|
||||
//#endregion
|
||||
|
||||
@@ -339,6 +340,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this._trimAutoWhitespaceLines = null;
|
||||
|
||||
this._tokens = new TokensStore();
|
||||
this._tokens2 = new TokensStore2();
|
||||
this._tokenization = new TextModelTokenization(this);
|
||||
}
|
||||
|
||||
@@ -414,6 +416,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
// Flush all tokens
|
||||
this._tokens.flush();
|
||||
this._tokens2.flush();
|
||||
|
||||
// Destroy all my decorations
|
||||
this._decorations = Object.create(null);
|
||||
@@ -1262,8 +1265,9 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
let lineCount = oldLineCount;
|
||||
for (let i = 0, len = contentChanges.length; i < len; i++) {
|
||||
const change = contentChanges[i];
|
||||
const [eolCount, firstLineLength] = countEOL(change.text);
|
||||
const [eolCount, firstLineLength, lastLineLength] = countEOL(change.text);
|
||||
this._tokens.acceptEdit(change.range, eolCount, firstLineLength);
|
||||
this._tokens2.acceptEdit(change.range, eolCount, firstLineLength, lastLineLength, change.text.length > 0 ? change.text.charCodeAt(0) : CharCode.Null);
|
||||
this._onDidChangeDecorations.fire();
|
||||
this._decorationsTree.acceptReplace(change.rangeOffset, change.rangeLength, change.text.length, change.forceMoveMarkers);
|
||||
|
||||
@@ -1717,6 +1721,15 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
});
|
||||
}
|
||||
|
||||
public setSemanticTokens(tokens: MultilineTokens2[] | null): void {
|
||||
this._tokens2.set(tokens);
|
||||
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: false,
|
||||
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
|
||||
});
|
||||
}
|
||||
|
||||
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
|
||||
startLineNumber = Math.max(1, startLineNumber);
|
||||
endLineNumber = Math.min(this._buffer.getLineCount(), endLineNumber);
|
||||
@@ -1734,6 +1747,15 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
});
|
||||
}
|
||||
|
||||
public clearSemanticTokens(): void {
|
||||
this._tokens2.flush();
|
||||
|
||||
this._emitModelTokensChangedEvent({
|
||||
tokenizationSupportChanged: false,
|
||||
ranges: [{ fromLineNumber: 1, toLineNumber: this.getLineCount() }]
|
||||
});
|
||||
}
|
||||
|
||||
private _emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void {
|
||||
if (!this._isDisposing) {
|
||||
this._onDidChangeTokens.fire(e);
|
||||
@@ -1772,7 +1794,8 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
private _getLineTokens(lineNumber: number): LineTokens {
|
||||
const lineText = this.getLineContent(lineNumber);
|
||||
return this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText);
|
||||
const syntacticTokens = this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText);
|
||||
return this._tokens2.addSemanticTokens(lineNumber, syntacticTokens);
|
||||
}
|
||||
|
||||
public getLanguageIdentifier(): LanguageIdentifier {
|
||||
|
||||
@@ -510,7 +510,7 @@ export function isValidMatch(wordSeparators: WordCharacterClassifier, text: stri
|
||||
}
|
||||
|
||||
export class Searcher {
|
||||
private readonly _wordSeparators: WordCharacterClassifier | null;
|
||||
public readonly _wordSeparators: WordCharacterClassifier | null;
|
||||
private readonly _searchRegex: RegExp;
|
||||
private _prevMatchStartIndex: number;
|
||||
private _prevMatchLength: number;
|
||||
|
||||
@@ -11,9 +11,10 @@ import { ColorId, FontStyle, LanguageId, MetadataConsts, StandardTokenType, Toke
|
||||
import { writeUInt32BE, readUInt32BE } from 'vs/base/common/buffer';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export function countEOL(text: string): [number, number] {
|
||||
export function countEOL(text: string): [number, number, number] {
|
||||
let eolCount = 0;
|
||||
let firstLineLength = 0;
|
||||
let lastLineStart = 0;
|
||||
for (let i = 0, len = text.length; i < len; i++) {
|
||||
const chr = text.charCodeAt(i);
|
||||
|
||||
@@ -28,17 +29,19 @@ export function countEOL(text: string): [number, number] {
|
||||
} else {
|
||||
// \r... case
|
||||
}
|
||||
lastLineStart = i + 1;
|
||||
} else if (chr === CharCode.LineFeed) {
|
||||
if (eolCount === 0) {
|
||||
firstLineLength = i;
|
||||
}
|
||||
eolCount++;
|
||||
lastLineStart = i + 1;
|
||||
}
|
||||
}
|
||||
if (eolCount === 0) {
|
||||
firstLineLength = text.length;
|
||||
}
|
||||
return [eolCount, firstLineLength];
|
||||
return [eolCount, firstLineLength, text.length - lastLineStart];
|
||||
}
|
||||
|
||||
function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
|
||||
@@ -109,6 +112,453 @@ export class MultilineTokensBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
export interface IEncodedTokens {
|
||||
getTokenCount(): number;
|
||||
getDeltaLine(tokenIndex: number): number;
|
||||
getMaxDeltaLine(): number;
|
||||
getStartCharacter(tokenIndex: number): number;
|
||||
getEndCharacter(tokenIndex: number): number;
|
||||
getMetadata(tokenIndex: number): number;
|
||||
|
||||
clear(): void;
|
||||
acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void;
|
||||
acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void;
|
||||
}
|
||||
|
||||
export class SparseEncodedTokens implements IEncodedTokens {
|
||||
/**
|
||||
* The encoding of tokens is:
|
||||
* 4*i deltaLine (from `startLineNumber`)
|
||||
* 4*i+1 startCharacter (from the line start)
|
||||
* 4*i+2 endCharacter (from the line start)
|
||||
* 4*i+3 metadata
|
||||
*/
|
||||
private _tokens: Uint32Array;
|
||||
private _tokenCount: number;
|
||||
|
||||
constructor(tokens: Uint32Array) {
|
||||
this._tokens = tokens;
|
||||
this._tokenCount = tokens.length / 4;
|
||||
}
|
||||
|
||||
public getMaxDeltaLine(): number {
|
||||
const tokenCount = this.getTokenCount();
|
||||
if (tokenCount === 0) {
|
||||
return -1;
|
||||
}
|
||||
return this.getDeltaLine(tokenCount - 1);
|
||||
}
|
||||
|
||||
public getTokenCount(): number {
|
||||
return this._tokenCount;
|
||||
}
|
||||
|
||||
public getDeltaLine(tokenIndex: number): number {
|
||||
return this._tokens[4 * tokenIndex];
|
||||
}
|
||||
|
||||
public getStartCharacter(tokenIndex: number): number {
|
||||
return this._tokens[4 * tokenIndex + 1];
|
||||
}
|
||||
|
||||
public getEndCharacter(tokenIndex: number): number {
|
||||
return this._tokens[4 * tokenIndex + 2];
|
||||
}
|
||||
|
||||
public getMetadata(tokenIndex: number): number {
|
||||
return this._tokens[4 * tokenIndex + 3];
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._tokenCount = 0;
|
||||
}
|
||||
|
||||
public acceptDeleteRange(startDeltaLine: number, startCharacter: number, endDeltaLine: number, endCharacter: number): void {
|
||||
// This is a bit complex, here are the cases I used to think about this:
|
||||
//
|
||||
// 1. The token starts before the deletion range
|
||||
// 1a. The token is completely before the deletion range
|
||||
// -----------
|
||||
// xxxxxxxxxxx
|
||||
// 1b. The token starts before, the deletion range ends after the token
|
||||
// -----------
|
||||
// xxxxxxxxxxx
|
||||
// 1c. The token starts before, the deletion range ends precisely with the token
|
||||
// ---------------
|
||||
// xxxxxxxx
|
||||
// 1d. The token starts before, the deletion range is inside the token
|
||||
// ---------------
|
||||
// xxxxx
|
||||
//
|
||||
// 2. The token starts at the same position with the deletion range
|
||||
// 2a. The token starts at the same position, and ends inside the deletion range
|
||||
// -------
|
||||
// xxxxxxxxxxx
|
||||
// 2b. The token starts at the same position, and ends at the same position as the deletion range
|
||||
// ----------
|
||||
// xxxxxxxxxx
|
||||
// 2c. The token starts at the same position, and ends after the deletion range
|
||||
// -------------
|
||||
// xxxxxxx
|
||||
//
|
||||
// 3. The token starts inside the deletion range
|
||||
// 3a. The token is inside the deletion range
|
||||
// -------
|
||||
// xxxxxxxxxxxxx
|
||||
// 3b. The token starts inside the deletion range, and ends at the same position as the deletion range
|
||||
// ----------
|
||||
// xxxxxxxxxxxxx
|
||||
// 3c. The token starts inside the deletion range, and ends after the deletion range
|
||||
// ------------
|
||||
// xxxxxxxxxxx
|
||||
//
|
||||
// 4. The token starts after the deletion range
|
||||
// -----------
|
||||
// xxxxxxxx
|
||||
//
|
||||
const tokens = this._tokens;
|
||||
const tokenCount = this._tokenCount;
|
||||
const deletedLineCount = (endDeltaLine - startDeltaLine);
|
||||
let newTokenCount = 0;
|
||||
let hasDeletedTokens = false;
|
||||
for (let i = 0; i < tokenCount; i++) {
|
||||
const srcOffset = 4 * i;
|
||||
let tokenDeltaLine = tokens[srcOffset];
|
||||
let tokenStartCharacter = tokens[srcOffset + 1];
|
||||
let tokenEndCharacter = tokens[srcOffset + 2];
|
||||
const tokenMetadata = tokens[srcOffset + 3];
|
||||
|
||||
if (tokenDeltaLine < startDeltaLine || (tokenDeltaLine === startDeltaLine && tokenEndCharacter <= startCharacter)) {
|
||||
// 1a. The token is completely before the deletion range
|
||||
// => nothing to do
|
||||
newTokenCount++;
|
||||
continue;
|
||||
} else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter < startCharacter) {
|
||||
// 1b, 1c, 1d
|
||||
// => the token survives, but it needs to shrink
|
||||
if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
|
||||
// 1d. The token starts before, the deletion range is inside the token
|
||||
// => the token shrinks by the deletion character count
|
||||
tokenEndCharacter -= (endCharacter - startCharacter);
|
||||
} else {
|
||||
// 1b. The token starts before, the deletion range ends after the token
|
||||
// 1c. The token starts before, the deletion range ends precisely with the token
|
||||
// => the token shrinks its ending to the deletion start
|
||||
tokenEndCharacter = startCharacter;
|
||||
}
|
||||
} else if (tokenDeltaLine === startDeltaLine && tokenStartCharacter === startCharacter) {
|
||||
// 2a, 2b, 2c
|
||||
if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
|
||||
// 2c. The token starts at the same position, and ends after the deletion range
|
||||
// => the token shrinks by the deletion character count
|
||||
tokenEndCharacter -= (endCharacter - startCharacter);
|
||||
} else {
|
||||
// 2a. The token starts at the same position, and ends inside the deletion range
|
||||
// 2b. The token starts at the same position, and ends at the same position as the deletion range
|
||||
// => the token is deleted
|
||||
hasDeletedTokens = true;
|
||||
continue;
|
||||
}
|
||||
} else if (tokenDeltaLine < endDeltaLine || (tokenDeltaLine === endDeltaLine && tokenStartCharacter < endCharacter)) {
|
||||
// 3a, 3b, 3c
|
||||
if (tokenDeltaLine === endDeltaLine && tokenEndCharacter > endCharacter) {
|
||||
// 3c. The token starts inside the deletion range, and ends after the deletion range
|
||||
// => the token moves left and shrinks
|
||||
if (tokenDeltaLine === startDeltaLine) {
|
||||
// the deletion started on the same line as the token
|
||||
// => the token moves left and shrinks
|
||||
tokenStartCharacter = startCharacter;
|
||||
tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter);
|
||||
} else {
|
||||
// the deletion started on a line above the token
|
||||
// => the token moves to the beginning of the line
|
||||
tokenStartCharacter = 0;
|
||||
tokenEndCharacter = tokenStartCharacter + (tokenEndCharacter - endCharacter);
|
||||
}
|
||||
} else {
|
||||
// 3a. The token is inside the deletion range
|
||||
// 3b. The token starts inside the deletion range, and ends at the same position as the deletion range
|
||||
// => the token is deleted
|
||||
hasDeletedTokens = true;
|
||||
continue;
|
||||
}
|
||||
} else if (tokenDeltaLine > endDeltaLine) {
|
||||
// 4. (partial) The token starts after the deletion range, on a line below...
|
||||
if (deletedLineCount === 0 && !hasDeletedTokens) {
|
||||
// early stop, there is no need to walk all the tokens and do nothing...
|
||||
newTokenCount = tokenCount;
|
||||
break;
|
||||
}
|
||||
tokenDeltaLine -= deletedLineCount;
|
||||
} else if (tokenDeltaLine === endDeltaLine && tokenStartCharacter >= endCharacter) {
|
||||
// 4. (continued) The token starts after the deletion range, on the last line where a deletion occurs
|
||||
tokenDeltaLine -= deletedLineCount;
|
||||
tokenStartCharacter -= endCharacter;
|
||||
tokenEndCharacter -= endCharacter;
|
||||
} else {
|
||||
throw new Error(`Not possible!`);
|
||||
}
|
||||
|
||||
const destOffset = 4 * newTokenCount;
|
||||
tokens[destOffset] = tokenDeltaLine;
|
||||
tokens[destOffset + 1] = tokenStartCharacter;
|
||||
tokens[destOffset + 2] = tokenEndCharacter;
|
||||
tokens[destOffset + 3] = tokenMetadata;
|
||||
newTokenCount++;
|
||||
}
|
||||
|
||||
this._tokenCount = newTokenCount;
|
||||
}
|
||||
|
||||
public acceptInsertText(deltaLine: number, character: number, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void {
|
||||
// Here are the cases I used to think about this:
|
||||
//
|
||||
// 1. The token is completely before the insertion point
|
||||
// ----------- |
|
||||
// 2. The token ends precisely at the insertion point
|
||||
// -----------|
|
||||
// 3. The token contains the insertion point
|
||||
// -----|------
|
||||
// 4. The token starts precisely at the insertion point
|
||||
// |-----------
|
||||
// 5. The token is completely after the insertion point
|
||||
// | -----------
|
||||
//
|
||||
const isInsertingPreciselyOneWordCharacter = (
|
||||
eolCount === 0
|
||||
&& firstLineLength === 1
|
||||
&& (
|
||||
(firstCharCode >= CharCode.Digit0 && firstCharCode <= CharCode.Digit9)
|
||||
|| (firstCharCode >= CharCode.A && firstCharCode <= CharCode.Z)
|
||||
|| (firstCharCode >= CharCode.a && firstCharCode <= CharCode.z)
|
||||
)
|
||||
);
|
||||
const tokens = this._tokens;
|
||||
const tokenCount = this._tokenCount;
|
||||
for (let i = 0; i < tokenCount; i++) {
|
||||
const offset = 4 * i;
|
||||
let tokenDeltaLine = tokens[offset];
|
||||
let tokenStartCharacter = tokens[offset + 1];
|
||||
let tokenEndCharacter = tokens[offset + 2];
|
||||
|
||||
if (tokenDeltaLine < deltaLine || (tokenDeltaLine === deltaLine && tokenEndCharacter < character)) {
|
||||
// 1. The token is completely before the insertion point
|
||||
// => nothing to do
|
||||
continue;
|
||||
} else if (tokenDeltaLine === deltaLine && tokenEndCharacter === character) {
|
||||
// 2. The token ends precisely at the insertion point
|
||||
// => expand the end character only if inserting precisely one character that is a word character
|
||||
if (isInsertingPreciselyOneWordCharacter) {
|
||||
tokenEndCharacter += 1;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
} else if (tokenDeltaLine === deltaLine && tokenStartCharacter < character && character < tokenEndCharacter) {
|
||||
// 3. The token contains the insertion point
|
||||
if (eolCount === 0) {
|
||||
// => just expand the end character
|
||||
tokenEndCharacter += firstLineLength;
|
||||
} else {
|
||||
// => cut off the token
|
||||
tokenEndCharacter = character;
|
||||
}
|
||||
} else {
|
||||
// 4. or 5.
|
||||
if (tokenDeltaLine === deltaLine && tokenStartCharacter === character) {
|
||||
// 4. The token starts precisely at the insertion point
|
||||
// => grow the token (by keeping its start constant) only if inserting precisely one character that is a word character
|
||||
// => otherwise behave as in case 5.
|
||||
if (isInsertingPreciselyOneWordCharacter) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
// => the token must move and keep its size constant
|
||||
tokenDeltaLine += eolCount;
|
||||
if (tokenDeltaLine === deltaLine) {
|
||||
// this token is on the line where the insertion is taking place
|
||||
if (eolCount === 0) {
|
||||
tokenStartCharacter += firstLineLength;
|
||||
tokenEndCharacter += firstLineLength;
|
||||
} else {
|
||||
const tokenLength = tokenEndCharacter - tokenStartCharacter;
|
||||
tokenStartCharacter = lastLineLength + (tokenStartCharacter - character);
|
||||
tokenEndCharacter = tokenStartCharacter + tokenLength;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tokens[offset] = tokenDeltaLine;
|
||||
tokens[offset + 1] = tokenStartCharacter;
|
||||
tokens[offset + 2] = tokenEndCharacter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class LineTokens2 {
|
||||
|
||||
private readonly _actual: IEncodedTokens;
|
||||
private readonly _startTokenIndex: number;
|
||||
private readonly _endTokenIndex: number;
|
||||
|
||||
constructor(actual: IEncodedTokens, startTokenIndex: number, endTokenIndex: number) {
|
||||
this._actual = actual;
|
||||
this._startTokenIndex = startTokenIndex;
|
||||
this._endTokenIndex = endTokenIndex;
|
||||
}
|
||||
|
||||
public getCount(): number {
|
||||
return this._endTokenIndex - this._startTokenIndex + 1;
|
||||
}
|
||||
|
||||
public getStartCharacter(tokenIndex: number): number {
|
||||
return this._actual.getStartCharacter(this._startTokenIndex + tokenIndex);
|
||||
}
|
||||
|
||||
public getEndCharacter(tokenIndex: number): number {
|
||||
return this._actual.getEndCharacter(this._startTokenIndex + tokenIndex);
|
||||
}
|
||||
|
||||
public getMetadata(tokenIndex: number): number {
|
||||
return this._actual.getMetadata(this._startTokenIndex + tokenIndex);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultilineTokens2 {
|
||||
|
||||
public startLineNumber: number;
|
||||
public endLineNumber: number;
|
||||
public tokens: IEncodedTokens;
|
||||
|
||||
constructor(startLineNumber: number, tokens: IEncodedTokens) {
|
||||
this.startLineNumber = startLineNumber;
|
||||
this.tokens = tokens;
|
||||
this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine();
|
||||
}
|
||||
|
||||
private _updateEndLineNumber(): void {
|
||||
this.endLineNumber = this.startLineNumber + this.tokens.getMaxDeltaLine();
|
||||
}
|
||||
|
||||
public getLineTokens(lineNumber: number): LineTokens2 | null {
|
||||
if (this.startLineNumber <= lineNumber && lineNumber <= this.endLineNumber) {
|
||||
const findResult = MultilineTokens2._findTokensWithLine(this.tokens, lineNumber - this.startLineNumber);
|
||||
if (findResult) {
|
||||
const [startTokenIndex, endTokenIndex] = findResult;
|
||||
return new LineTokens2(this.tokens, startTokenIndex, endTokenIndex);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findTokensWithLine(tokens: IEncodedTokens, deltaLine: number): [number, number] | null {
|
||||
let low = 0;
|
||||
let high = tokens.getTokenCount() - 1;
|
||||
|
||||
while (low < high) {
|
||||
const mid = low + Math.floor((high - low) / 2);
|
||||
const midDeltaLine = tokens.getDeltaLine(mid);
|
||||
|
||||
if (midDeltaLine < deltaLine) {
|
||||
low = mid + 1;
|
||||
} else if (midDeltaLine > deltaLine) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
let min = mid;
|
||||
while (min > low && tokens.getDeltaLine(min - 1) === deltaLine) {
|
||||
min--;
|
||||
}
|
||||
let max = mid;
|
||||
while (max < high && tokens.getDeltaLine(max + 1) === deltaLine) {
|
||||
max++;
|
||||
}
|
||||
return [min, max];
|
||||
}
|
||||
}
|
||||
|
||||
if (tokens.getDeltaLine(low) === deltaLine) {
|
||||
return [low, low];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public applyEdit(range: IRange, text: string): void {
|
||||
const [eolCount, firstLineLength, lastLineLength] = countEOL(text);
|
||||
this.acceptEdit(range, eolCount, firstLineLength, lastLineLength, text.length > 0 ? text.charCodeAt(0) : CharCode.Null);
|
||||
}
|
||||
|
||||
public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void {
|
||||
this._acceptDeleteRange(range);
|
||||
this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), eolCount, firstLineLength, lastLineLength, firstCharCode);
|
||||
this._updateEndLineNumber();
|
||||
}
|
||||
|
||||
private _acceptDeleteRange(range: IRange): void {
|
||||
if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
|
||||
// Nothing to delete
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLineIndex = range.startLineNumber - this.startLineNumber;
|
||||
const lastLineIndex = range.endLineNumber - this.startLineNumber;
|
||||
|
||||
if (lastLineIndex < 0) {
|
||||
// this deletion occurs entirely before this block, so we only need to adjust line numbers
|
||||
const deletedLinesCount = lastLineIndex - firstLineIndex;
|
||||
this.startLineNumber -= deletedLinesCount;
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine();
|
||||
|
||||
if (firstLineIndex >= tokenMaxDeltaLine + 1) {
|
||||
// this deletion occurs entirely after this block, so there is nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstLineIndex < 0 && lastLineIndex >= tokenMaxDeltaLine + 1) {
|
||||
// this deletion completely encompasses this block
|
||||
this.startLineNumber = 0;
|
||||
this.tokens.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstLineIndex < 0) {
|
||||
const deletedBefore = -firstLineIndex;
|
||||
this.startLineNumber -= deletedBefore;
|
||||
|
||||
this.tokens.acceptDeleteRange(0, 0, lastLineIndex, range.endColumn - 1);
|
||||
} else {
|
||||
this.tokens.acceptDeleteRange(firstLineIndex, range.startColumn - 1, lastLineIndex, range.endColumn - 1);
|
||||
}
|
||||
}
|
||||
|
||||
private _acceptInsertText(position: Position, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void {
|
||||
|
||||
if (eolCount === 0 && firstLineLength === 0) {
|
||||
// Nothing to insert
|
||||
return;
|
||||
}
|
||||
|
||||
const lineIndex = position.lineNumber - this.startLineNumber;
|
||||
|
||||
if (lineIndex < 0) {
|
||||
// this insertion occurs before this block, so we only need to adjust line numbers
|
||||
this.startLineNumber += eolCount;
|
||||
return;
|
||||
}
|
||||
|
||||
const tokenMaxDeltaLine = this.tokens.getMaxDeltaLine();
|
||||
|
||||
if (lineIndex >= tokenMaxDeltaLine + 1) {
|
||||
// this insertion occurs after this block, so there is nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
this.tokens.acceptInsertText(lineIndex, position.column - 1, eolCount, firstLineLength, lastLineLength, firstCharCode);
|
||||
}
|
||||
}
|
||||
|
||||
export class MultilineTokens {
|
||||
|
||||
public startLineNumber: number;
|
||||
@@ -193,6 +643,7 @@ export class MultilineTokens {
|
||||
// this deletion completely encompasses this block
|
||||
this.startLineNumber = 0;
|
||||
this.tokens = [];
|
||||
return;
|
||||
}
|
||||
|
||||
if (firstLineIndex === lastLineIndex) {
|
||||
@@ -289,6 +740,120 @@ function toUint32Array(arr: Uint32Array | ArrayBuffer): Uint32Array {
|
||||
}
|
||||
}
|
||||
|
||||
export class TokensStore2 {
|
||||
|
||||
private _pieces: MultilineTokens2[];
|
||||
|
||||
constructor() {
|
||||
this._pieces = [];
|
||||
}
|
||||
|
||||
public flush(): void {
|
||||
this._pieces = [];
|
||||
}
|
||||
|
||||
public set(pieces: MultilineTokens2[] | null) {
|
||||
this._pieces = pieces || [];
|
||||
}
|
||||
|
||||
public addSemanticTokens(lineNumber: number, aTokens: LineTokens): LineTokens {
|
||||
const pieces = this._pieces;
|
||||
|
||||
if (pieces.length === 0) {
|
||||
return aTokens;
|
||||
}
|
||||
|
||||
const pieceIndex = TokensStore2._findFirstPieceWithLine(pieces, lineNumber);
|
||||
const bTokens = this._pieces[pieceIndex].getLineTokens(lineNumber);
|
||||
|
||||
if (!bTokens) {
|
||||
return aTokens;
|
||||
}
|
||||
|
||||
const aLen = aTokens.getCount();
|
||||
const bLen = bTokens.getCount();
|
||||
|
||||
let aIndex = 0;
|
||||
let result: number[] = [], resultLen = 0;
|
||||
for (let bIndex = 0; bIndex < bLen; bIndex++) {
|
||||
const bStartCharacter = bTokens.getStartCharacter(bIndex);
|
||||
const bEndCharacter = bTokens.getEndCharacter(bIndex);
|
||||
const bMetadata = bTokens.getMetadata(bIndex);
|
||||
|
||||
// push any token from `a` that is before `b`
|
||||
while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bStartCharacter) {
|
||||
result[resultLen++] = aTokens.getEndOffset(aIndex);
|
||||
result[resultLen++] = aTokens.getMetadata(aIndex);
|
||||
aIndex++;
|
||||
}
|
||||
|
||||
// push the token from `a` if it intersects the token from `b`
|
||||
if (aIndex < aLen && aTokens.getStartOffset(aIndex) < bStartCharacter) {
|
||||
result[resultLen++] = bStartCharacter;
|
||||
result[resultLen++] = aTokens.getMetadata(aIndex);
|
||||
}
|
||||
|
||||
// skip any tokens from `a` that are contained inside `b`
|
||||
while (aIndex < aLen && aTokens.getEndOffset(aIndex) <= bEndCharacter) {
|
||||
aIndex++;
|
||||
}
|
||||
|
||||
const aMetadata = aTokens.getMetadata(aIndex - 1 > 0 ? aIndex - 1 : aIndex);
|
||||
const languageId = TokenMetadata.getLanguageId(aMetadata);
|
||||
const tokenType = TokenMetadata.getTokenType(aMetadata);
|
||||
|
||||
// push the token from `b`
|
||||
result[resultLen++] = bEndCharacter;
|
||||
result[resultLen++] = (
|
||||
(bMetadata & MetadataConsts.LANG_TTYPE_CMPL)
|
||||
| ((languageId << MetadataConsts.LANGUAGEID_OFFSET) >>> 0)
|
||||
| ((tokenType << MetadataConsts.TOKEN_TYPE_OFFSET) >>> 0)
|
||||
);
|
||||
}
|
||||
|
||||
// push the remaining tokens from `a`
|
||||
while (aIndex < aLen) {
|
||||
result[resultLen++] = aTokens.getEndOffset(aIndex);
|
||||
result[resultLen++] = aTokens.getMetadata(aIndex);
|
||||
aIndex++;
|
||||
}
|
||||
|
||||
return new LineTokens(new Uint32Array(result), aTokens.getLineContent());
|
||||
}
|
||||
|
||||
private static _findFirstPieceWithLine(pieces: MultilineTokens2[], lineNumber: number): number {
|
||||
let low = 0;
|
||||
let high = pieces.length - 1;
|
||||
|
||||
while (low < high) {
|
||||
let mid = low + Math.floor((high - low) / 2);
|
||||
|
||||
if (pieces[mid].endLineNumber < lineNumber) {
|
||||
low = mid + 1;
|
||||
} else if (pieces[mid].startLineNumber > lineNumber) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
while (mid > low && pieces[mid - 1].startLineNumber <= lineNumber && lineNumber <= pieces[mid - 1].endLineNumber) {
|
||||
mid--;
|
||||
}
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
//#region Editing
|
||||
|
||||
public acceptEdit(range: IRange, eolCount: number, firstLineLength: number, lastLineLength: number, firstCharCode: number): void {
|
||||
for (const piece of this._pieces) {
|
||||
piece.acceptEdit(range, eolCount, firstLineLength, lastLineLength, firstCharCode);
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
export class TokensStore {
|
||||
private _lineTokens: (Uint32Array | ArrayBuffer | null)[];
|
||||
private _len: number;
|
||||
|
||||
@@ -19,7 +19,6 @@ import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureR
|
||||
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
|
||||
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
import { keys } from 'vs/base/common/map';
|
||||
|
||||
/**
|
||||
* Open ended enum at runtime
|
||||
@@ -126,6 +125,8 @@ export const enum MetadataConsts {
|
||||
FOREGROUND_MASK = 0b00000000011111111100000000000000,
|
||||
BACKGROUND_MASK = 0b11111111100000000000000000000000,
|
||||
|
||||
LANG_TTYPE_CMPL = 0b11111111111111111111100000000000,
|
||||
|
||||
LANGUAGEID_OFFSET = 0,
|
||||
TOKEN_TYPE_OFFSET = 8,
|
||||
FONT_STYLE_OFFSET = 11,
|
||||
@@ -453,7 +454,7 @@ export interface CompletionItem {
|
||||
* *Note:* The range must be a [single line](#Range.isSingleLine) and it must
|
||||
* [contain](#Range.contains) the position at which completion has been [requested](#CompletionItemProvider.provideCompletionItems).
|
||||
*/
|
||||
range: IRange;
|
||||
range: IRange | { insert: IRange, replace: IRange };
|
||||
/**
|
||||
* An optional set of characters that when pressed while this completion is active will accept it first and
|
||||
* then type that character. *Note* that all commit characters should have `length=1` and that superfluous
|
||||
@@ -547,6 +548,7 @@ export interface CodeAction {
|
||||
diagnostics?: IMarkerData[];
|
||||
kind?: string;
|
||||
isPreferred?: boolean;
|
||||
disabled?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -938,12 +940,6 @@ export namespace SymbolKinds {
|
||||
export function fromString(value: string): SymbolKind | undefined {
|
||||
return byName.get(value);
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export function names(): readonly string[] {
|
||||
return keys(byName);
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -1451,14 +1447,6 @@ export interface IWebviewPanelOptions {
|
||||
readonly retainContextWhenHidden?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const enum WebviewContentState {
|
||||
Readonly = 1,
|
||||
Unchanged = 2,
|
||||
Dirty = 3,
|
||||
}
|
||||
|
||||
export interface CodeLens {
|
||||
range: IRange;
|
||||
@@ -1477,6 +1465,33 @@ export interface CodeLensProvider {
|
||||
resolveCodeLens?(model: model.ITextModel, codeLens: CodeLens, token: CancellationToken): ProviderResult<CodeLens>;
|
||||
}
|
||||
|
||||
export interface SemanticTokensLegend {
|
||||
readonly tokenTypes: string[];
|
||||
readonly tokenModifiers: string[];
|
||||
}
|
||||
|
||||
export interface SemanticTokens {
|
||||
readonly resultId?: string;
|
||||
readonly data: Uint32Array;
|
||||
}
|
||||
|
||||
export interface SemanticTokensEdit {
|
||||
readonly start: number;
|
||||
readonly deleteCount: number;
|
||||
readonly data?: Uint32Array;
|
||||
}
|
||||
|
||||
export interface SemanticTokensEdits {
|
||||
readonly resultId?: string;
|
||||
readonly edits: SemanticTokensEdit[];
|
||||
}
|
||||
|
||||
export interface SemanticTokensProvider {
|
||||
getLegend(): SemanticTokensLegend;
|
||||
provideSemanticTokens(model: model.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensEdits>;
|
||||
releaseSemanticTokens(resultId: string | undefined): void;
|
||||
}
|
||||
|
||||
// --- feature registries ------
|
||||
|
||||
/**
|
||||
@@ -1579,6 +1594,11 @@ export const SelectionRangeRegistry = new LanguageFeatureRegistry<SelectionRange
|
||||
*/
|
||||
export const FoldingRangeProviderRegistry = new LanguageFeatureRegistry<FoldingRangeProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const SemanticTokensProviderRegistry = new LanguageFeatureRegistry<SemanticTokensProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -228,6 +228,28 @@ export interface EnterAction {
|
||||
removeText?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface CompleteEnterAction {
|
||||
/**
|
||||
* Describe what to do with the indentation.
|
||||
*/
|
||||
indentAction: IndentAction;
|
||||
/**
|
||||
* Describes text to be appended after the new line and after the indentation.
|
||||
*/
|
||||
appendText: string;
|
||||
/**
|
||||
* Describes the number of characters to remove from the new line's indentation.
|
||||
*/
|
||||
removeText: number;
|
||||
/**
|
||||
* The line's indentation minus removeText
|
||||
*/
|
||||
indentation: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
@@ -12,13 +11,14 @@ import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { DEFAULT_WORD_REGEXP, ensureValidWordDefinition } from 'vs/editor/common/model/wordHelper';
|
||||
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { createScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
import { EnterAction, FoldingRules, IAutoClosingPair, IndentAction, IndentationRule, LanguageConfiguration, StandardAutoClosingPairConditional, CompleteEnterAction } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { createScopedLineTokens, ScopedLineTokens } from 'vs/editor/common/modes/supports';
|
||||
import { CharacterPairSupport } from 'vs/editor/common/modes/supports/characterPair';
|
||||
import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
|
||||
import { IndentConsts, IndentRulesSupport } from 'vs/editor/common/modes/supports/indentRules';
|
||||
import { IOnEnterSupportOptions, OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter';
|
||||
import { OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter';
|
||||
import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
/**
|
||||
* Interface used to support insertion of mode specific comments.
|
||||
@@ -48,11 +48,11 @@ export class RichEditSupport {
|
||||
private readonly _languageIdentifier: LanguageIdentifier;
|
||||
private _brackets: RichEditBrackets | null;
|
||||
private _electricCharacter: BracketElectricCharacterSupport | null;
|
||||
private readonly _onEnterSupport: OnEnterSupport | null;
|
||||
|
||||
public readonly comments: ICommentsConfiguration | null;
|
||||
public readonly characterPair: CharacterPairSupport;
|
||||
public readonly wordDefinition: RegExp;
|
||||
public readonly onEnter: OnEnterSupport | null;
|
||||
public readonly indentRulesSupport: IndentRulesSupport | null;
|
||||
public readonly indentationRules: IndentationRule | undefined;
|
||||
public readonly foldingRules: FoldingRules;
|
||||
@@ -70,8 +70,7 @@ export class RichEditSupport {
|
||||
|
||||
this._conf = RichEditSupport._mergeConf(prev, rawConf);
|
||||
|
||||
this.onEnter = RichEditSupport._handleOnEnter(this._conf);
|
||||
|
||||
this._onEnterSupport = (this._conf.brackets || this._conf.indentationRules || this._conf.onEnterRules ? new OnEnterSupport(this._conf) : null);
|
||||
this.comments = RichEditSupport._handleComments(this._conf);
|
||||
|
||||
this.characterPair = new CharacterPairSupport(this._conf);
|
||||
@@ -102,6 +101,13 @@ export class RichEditSupport {
|
||||
return this._electricCharacter;
|
||||
}
|
||||
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
if (!this._onEnterSupport) {
|
||||
return null;
|
||||
}
|
||||
return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
|
||||
}
|
||||
|
||||
private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration {
|
||||
return {
|
||||
comments: (prev ? current.comments || prev.comments : current.comments),
|
||||
@@ -117,29 +123,6 @@ export class RichEditSupport {
|
||||
};
|
||||
}
|
||||
|
||||
private static _handleOnEnter(conf: LanguageConfiguration): OnEnterSupport | null {
|
||||
// on enter
|
||||
let onEnter: IOnEnterSupportOptions = {};
|
||||
let empty = true;
|
||||
|
||||
if (conf.brackets) {
|
||||
empty = false;
|
||||
onEnter.brackets = conf.brackets;
|
||||
}
|
||||
if (conf.indentationRules) {
|
||||
empty = false;
|
||||
}
|
||||
if (conf.onEnterRules) {
|
||||
empty = false;
|
||||
onEnter.regExpRules = conf.onEnterRules;
|
||||
}
|
||||
|
||||
if (!empty) {
|
||||
return new OnEnterSupport(onEnter);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _handleComments(conf: LanguageConfiguration): ICommentsConfiguration | null {
|
||||
let commentRule = conf.comments;
|
||||
if (!commentRule) {
|
||||
@@ -351,8 +334,12 @@ export class LanguageConfigurationRegistryImpl {
|
||||
*
|
||||
* This function only return the inherited indent based on above lines, it doesn't check whether current line should decrease or not.
|
||||
*/
|
||||
public getInheritIndentForLine(model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null {
|
||||
let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id);
|
||||
public getInheritIndentForLine(autoIndent: EditorAutoIndentStrategy, model: IVirtualModel, lineNumber: number, honorIntentialIndent: boolean = true): { indentation: string; action: IndentAction | null; line?: number; } | null {
|
||||
if (autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
}
|
||||
@@ -364,7 +351,7 @@ export class LanguageConfigurationRegistryImpl {
|
||||
};
|
||||
}
|
||||
|
||||
let precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport);
|
||||
const precedingUnIgnoredLine = this.getPrecedingValidLine(model, lineNumber, indentRulesSupport);
|
||||
if (precedingUnIgnoredLine < 0) {
|
||||
return null;
|
||||
} else if (precedingUnIgnoredLine < 1) {
|
||||
@@ -374,8 +361,7 @@ export class LanguageConfigurationRegistryImpl {
|
||||
};
|
||||
}
|
||||
|
||||
let precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine);
|
||||
|
||||
const precedingUnIgnoredLineContent = model.getLineContent(precedingUnIgnoredLine);
|
||||
if (indentRulesSupport.shouldIncrease(precedingUnIgnoredLineContent) || indentRulesSupport.shouldIndentNextLine(precedingUnIgnoredLineContent)) {
|
||||
return {
|
||||
indentation: strings.getLeadingWhitespace(precedingUnIgnoredLineContent),
|
||||
@@ -402,9 +388,9 @@ export class LanguageConfigurationRegistryImpl {
|
||||
};
|
||||
}
|
||||
|
||||
let previousLine = precedingUnIgnoredLine - 1;
|
||||
const previousLine = precedingUnIgnoredLine - 1;
|
||||
|
||||
let previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine));
|
||||
const previousLineIndentMetadata = indentRulesSupport.getIndentMetadata(model.getLineContent(previousLine));
|
||||
if (!(previousLineIndentMetadata & (IndentConsts.INCREASE_MASK | IndentConsts.DECREASE_MASK)) &&
|
||||
(previousLineIndentMetadata & IndentConsts.INDENT_NEXTLINE_MASK)) {
|
||||
let stopLine = 0;
|
||||
@@ -432,7 +418,7 @@ export class LanguageConfigurationRegistryImpl {
|
||||
} else {
|
||||
// search from precedingUnIgnoredLine until we find one whose indent is not temporary
|
||||
for (let i = precedingUnIgnoredLine; i > 0; i--) {
|
||||
let lineContent = model.getLineContent(i);
|
||||
const lineContent = model.getLineContent(i);
|
||||
if (indentRulesSupport.shouldIncrease(lineContent)) {
|
||||
return {
|
||||
indentation: strings.getLeadingWhitespace(lineContent),
|
||||
@@ -472,27 +458,28 @@ export class LanguageConfigurationRegistryImpl {
|
||||
}
|
||||
}
|
||||
|
||||
public getGoodIndentForLine(virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null {
|
||||
let indentRulesSupport = this.getIndentRulesSupport(languageId);
|
||||
public getGoodIndentForLine(autoIndent: EditorAutoIndentStrategy, virtualModel: IVirtualModel, languageId: LanguageId, lineNumber: number, indentConverter: IIndentConverter): string | null {
|
||||
if (autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const richEditSupport = this._getRichEditSupport(languageId);
|
||||
if (!richEditSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const indentRulesSupport = this.getIndentRulesSupport(languageId);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let indent = this.getInheritIndentForLine(virtualModel, lineNumber);
|
||||
let lineContent = virtualModel.getLineContent(lineNumber);
|
||||
const indent = this.getInheritIndentForLine(autoIndent, virtualModel, lineNumber);
|
||||
const lineContent = virtualModel.getLineContent(lineNumber);
|
||||
|
||||
if (indent) {
|
||||
let inheritLine = indent.line;
|
||||
const inheritLine = indent.line;
|
||||
if (inheritLine !== undefined) {
|
||||
let onEnterSupport = this._getOnEnterSupport(languageId);
|
||||
let enterResult: EnterAction | null = null;
|
||||
try {
|
||||
if (onEnterSupport) {
|
||||
enterResult = onEnterSupport.onEnter('', virtualModel.getLineContent(inheritLine), '');
|
||||
}
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
const enterResult = richEditSupport.onEnter(autoIndent, '', virtualModel.getLineContent(inheritLine), '');
|
||||
|
||||
if (enterResult) {
|
||||
let indentation = strings.getLeadingWhitespace(virtualModel.getLineContent(inheritLine));
|
||||
@@ -539,16 +526,17 @@ export class LanguageConfigurationRegistryImpl {
|
||||
return null;
|
||||
}
|
||||
|
||||
public getIndentForEnter(model: ITextModel, range: Range, indentConverter: IIndentConverter, autoIndent: boolean): { beforeEnter: string, afterEnter: string } | null {
|
||||
public getIndentForEnter(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, indentConverter: IIndentConverter): { beforeEnter: string, afterEnter: string } | null {
|
||||
if (autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return null;
|
||||
}
|
||||
model.forceTokenization(range.startLineNumber);
|
||||
let lineTokens = model.getLineTokens(range.startLineNumber);
|
||||
|
||||
let beforeEnterText;
|
||||
let afterEnterText;
|
||||
let scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1);
|
||||
let scopedLineText = scopedLineTokens.getLineContent();
|
||||
const lineTokens = model.getLineTokens(range.startLineNumber);
|
||||
const scopedLineTokens = createScopedLineTokens(lineTokens, range.startColumn - 1);
|
||||
const scopedLineText = scopedLineTokens.getLineContent();
|
||||
|
||||
let embeddedLanguage = false;
|
||||
let beforeEnterText: string;
|
||||
if (scopedLineTokens.firstCharOffset > 0 && lineTokens.getLanguageId(0) !== scopedLineTokens.languageId) {
|
||||
// we are in the embeded language content
|
||||
embeddedLanguage = true; // if embeddedLanguage is true, then we don't touch the indentation of current line
|
||||
@@ -557,6 +545,7 @@ export class LanguageConfigurationRegistryImpl {
|
||||
beforeEnterText = lineTokens.getLineContent().substring(0, range.startColumn - 1);
|
||||
}
|
||||
|
||||
let afterEnterText: string;
|
||||
if (range.isEmpty()) {
|
||||
afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
} else {
|
||||
@@ -564,31 +553,15 @@ export class LanguageConfigurationRegistryImpl {
|
||||
afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
}
|
||||
|
||||
let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
|
||||
|
||||
const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let beforeEnterResult = beforeEnterText;
|
||||
let beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText);
|
||||
const beforeEnterResult = beforeEnterText;
|
||||
const beforeEnterIndent = strings.getLeadingWhitespace(beforeEnterText);
|
||||
|
||||
if (!autoIndent && !embeddedLanguage) {
|
||||
let beforeEnterIndentAction = this.getInheritIndentForLine(model, range.startLineNumber);
|
||||
|
||||
if (indentRulesSupport.shouldDecrease(beforeEnterText)) {
|
||||
if (beforeEnterIndentAction) {
|
||||
beforeEnterIndent = beforeEnterIndentAction.indentation;
|
||||
if (beforeEnterIndentAction.action !== IndentAction.Indent) {
|
||||
beforeEnterIndent = indentConverter.unshiftIndent(beforeEnterIndent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
beforeEnterResult = beforeEnterIndent + strings.ltrim(strings.ltrim(beforeEnterText, ' '), '\t');
|
||||
}
|
||||
|
||||
let virtualModel: IVirtualModel = {
|
||||
const virtualModel: IVirtualModel = {
|
||||
getLineTokens: (lineNumber: number) => {
|
||||
return model.getLineTokens(lineNumber);
|
||||
},
|
||||
@@ -607,10 +580,10 @@ export class LanguageConfigurationRegistryImpl {
|
||||
}
|
||||
};
|
||||
|
||||
let currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent());
|
||||
let afterEnterAction = this.getInheritIndentForLine(virtualModel, range.startLineNumber + 1);
|
||||
const currentLineIndent = strings.getLeadingWhitespace(lineTokens.getLineContent());
|
||||
const afterEnterAction = this.getInheritIndentForLine(autoIndent, virtualModel, range.startLineNumber + 1);
|
||||
if (!afterEnterAction) {
|
||||
let beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent;
|
||||
const beforeEnter = embeddedLanguage ? currentLineIndent : beforeEnterIndent;
|
||||
return {
|
||||
beforeEnter: beforeEnter,
|
||||
afterEnter: beforeEnter
|
||||
@@ -637,18 +610,21 @@ export class LanguageConfigurationRegistryImpl {
|
||||
* We should always allow intentional indentation. It means, if users change the indentation of `lineNumber` and the content of
|
||||
* this line doesn't match decreaseIndentPattern, we should not adjust the indentation.
|
||||
*/
|
||||
public getIndentActionForType(model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null {
|
||||
let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
|
||||
let indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
|
||||
public getIndentActionForType(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range, ch: string, indentConverter: IIndentConverter): string | null {
|
||||
if (autoIndent < EditorAutoIndentStrategy.Full) {
|
||||
return null;
|
||||
}
|
||||
const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
|
||||
const indentRulesSupport = this.getIndentRulesSupport(scopedLineTokens.languageId);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let scopedLineText = scopedLineTokens.getLineContent();
|
||||
let beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
let afterTypeText;
|
||||
const scopedLineText = scopedLineTokens.getLineContent();
|
||||
const beforeTypeText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
|
||||
// selection support
|
||||
let afterTypeText: string;
|
||||
if (range.isEmpty()) {
|
||||
afterTypeText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
} else {
|
||||
@@ -661,13 +637,12 @@ export class LanguageConfigurationRegistryImpl {
|
||||
if (!indentRulesSupport.shouldDecrease(beforeTypeText + afterTypeText) && indentRulesSupport.shouldDecrease(beforeTypeText + ch + afterTypeText)) {
|
||||
// after typing `ch`, the content matches decreaseIndentPattern, we should adjust the indent to a good manner.
|
||||
// 1. Get inherited indent action
|
||||
let r = this.getInheritIndentForLine(model, range.startLineNumber, false);
|
||||
const r = this.getInheritIndentForLine(autoIndent, model, range.startLineNumber, false);
|
||||
if (!r) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let indentation = r.indentation;
|
||||
|
||||
if (r.action !== IndentAction.Indent) {
|
||||
indentation = indentConverter.unshiftIndent(indentation);
|
||||
}
|
||||
@@ -679,15 +654,13 @@ export class LanguageConfigurationRegistryImpl {
|
||||
}
|
||||
|
||||
public getIndentMetadata(model: ITextModel, lineNumber: number): number | null {
|
||||
let indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id);
|
||||
const indentRulesSupport = this.getIndentRulesSupport(model.getLanguageIdentifier().id);
|
||||
if (!indentRulesSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (lineNumber < 1 || lineNumber > model.getLineCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return indentRulesSupport.getIndentMetadata(model.getLineContent(lineNumber));
|
||||
}
|
||||
|
||||
@@ -695,34 +668,18 @@ export class LanguageConfigurationRegistryImpl {
|
||||
|
||||
// begin onEnter
|
||||
|
||||
private _getOnEnterSupport(languageId: LanguageId): OnEnterSupport | null {
|
||||
let value = this._getRichEditSupport(languageId);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
return value.onEnter || null;
|
||||
}
|
||||
|
||||
public getRawEnterActionAtPosition(model: ITextModel, lineNumber: number, column: number): EnterAction | null {
|
||||
let r = this.getEnterAction(model, new Range(lineNumber, column, lineNumber, column));
|
||||
|
||||
return r ? r.enterAction : null;
|
||||
}
|
||||
|
||||
public getEnterAction(model: ITextModel, range: Range): { enterAction: EnterAction; indentation: string; } | null {
|
||||
let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
|
||||
|
||||
let scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
|
||||
let onEnterSupport = this._getOnEnterSupport(scopedLineTokens.languageId);
|
||||
if (!onEnterSupport) {
|
||||
public getEnterAction(autoIndent: EditorAutoIndentStrategy, model: ITextModel, range: Range): CompleteEnterAction | null {
|
||||
const scopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber, range.startColumn);
|
||||
const richEditSupport = this._getRichEditSupport(scopedLineTokens.languageId);
|
||||
if (!richEditSupport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let scopedLineText = scopedLineTokens.getLineContent();
|
||||
let beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
let afterEnterText;
|
||||
const scopedLineText = scopedLineTokens.getLineContent();
|
||||
const beforeEnterText = scopedLineText.substr(0, range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
|
||||
// selection support
|
||||
let afterEnterText: string;
|
||||
if (range.isEmpty()) {
|
||||
afterEnterText = scopedLineText.substr(range.startColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
} else {
|
||||
@@ -730,73 +687,70 @@ export class LanguageConfigurationRegistryImpl {
|
||||
afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
|
||||
}
|
||||
|
||||
let lineNumber = range.startLineNumber;
|
||||
let oneLineAboveText = '';
|
||||
|
||||
if (lineNumber > 1 && scopedLineTokens.firstCharOffset === 0) {
|
||||
if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) {
|
||||
// This is not the first line and the entire line belongs to this mode
|
||||
let oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, lineNumber - 1);
|
||||
const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1);
|
||||
if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) {
|
||||
// The line above ends with text belonging to the same mode
|
||||
oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent();
|
||||
}
|
||||
}
|
||||
|
||||
let enterResult: EnterAction | null = null;
|
||||
try {
|
||||
enterResult = onEnterSupport.onEnter(oneLineAboveText, beforeEnterText, afterEnterText);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
|
||||
const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
|
||||
if (!enterResult) {
|
||||
return null;
|
||||
} else {
|
||||
// Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation.
|
||||
if (!enterResult.appendText) {
|
||||
if (
|
||||
(enterResult.indentAction === IndentAction.Indent) ||
|
||||
(enterResult.indentAction === IndentAction.IndentOutdent)
|
||||
) {
|
||||
enterResult.appendText = '\t';
|
||||
} else {
|
||||
enterResult.appendText = '';
|
||||
}
|
||||
}
|
||||
|
||||
const indentAction = enterResult.indentAction;
|
||||
let appendText = enterResult.appendText;
|
||||
const removeText = enterResult.removeText || 0;
|
||||
|
||||
// Here we add `\t` to appendText first because enterAction is leveraging appendText and removeText to change indentation.
|
||||
if (!appendText) {
|
||||
if (
|
||||
(indentAction === IndentAction.Indent) ||
|
||||
(indentAction === IndentAction.IndentOutdent)
|
||||
) {
|
||||
appendText = '\t';
|
||||
} else {
|
||||
appendText = '';
|
||||
}
|
||||
}
|
||||
|
||||
if (enterResult.removeText) {
|
||||
indentation = indentation.substring(0, indentation.length - enterResult.removeText);
|
||||
let indentation = this.getIndentationAtPosition(model, range.startLineNumber, range.startColumn);
|
||||
if (removeText) {
|
||||
indentation = indentation.substring(0, indentation.length - removeText);
|
||||
}
|
||||
|
||||
return {
|
||||
enterAction: enterResult,
|
||||
indentation: indentation,
|
||||
indentAction: indentAction,
|
||||
appendText: appendText,
|
||||
removeText: removeText,
|
||||
indentation: indentation
|
||||
};
|
||||
}
|
||||
|
||||
public getIndentationAtPosition(model: ITextModel, lineNumber: number, column: number): string {
|
||||
let lineText = model.getLineContent(lineNumber);
|
||||
const lineText = model.getLineContent(lineNumber);
|
||||
let indentation = strings.getLeadingWhitespace(lineText);
|
||||
if (indentation.length > column - 1) {
|
||||
indentation = indentation.substring(0, column - 1);
|
||||
}
|
||||
|
||||
return indentation;
|
||||
}
|
||||
|
||||
private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number) {
|
||||
private getScopedLineTokens(model: ITextModel, lineNumber: number, columnNumber?: number): ScopedLineTokens {
|
||||
model.forceTokenization(lineNumber);
|
||||
let lineTokens = model.getLineTokens(lineNumber);
|
||||
let column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1);
|
||||
let scopedLineTokens = createScopedLineTokens(lineTokens, column);
|
||||
return scopedLineTokens;
|
||||
const lineTokens = model.getLineTokens(lineNumber);
|
||||
const column = (typeof columnNumber === 'undefined' ? model.getLineMaxColumn(lineNumber) - 1 : columnNumber - 1);
|
||||
return createScopedLineTokens(lineTokens, column);
|
||||
}
|
||||
|
||||
// end onEnter
|
||||
|
||||
public getBracketsSupport(languageId: LanguageId): RichEditBrackets | null {
|
||||
let value = this._getRichEditSupport(languageId);
|
||||
const value = this._getRichEditSupport(languageId);
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -264,6 +264,10 @@ export class LinkComputer {
|
||||
case CharCode.BackTick:
|
||||
chClass = (linkBeginChCode === CharCode.SingleQuote || linkBeginChCode === CharCode.DoubleQuote) ? CharacterClass.None : CharacterClass.ForceTermination;
|
||||
break;
|
||||
case CharCode.Asterisk:
|
||||
// `*` terminates a link if the link began with `*`
|
||||
chClass = (linkBeginChCode === CharCode.Asterisk) ? CharacterClass.ForceTermination : CharacterClass.None;
|
||||
break;
|
||||
default:
|
||||
chClass = classifier.get(chCode);
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { CharacterPair, EnterAction, IndentAction, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration';
|
||||
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
export interface IOnEnterSupportOptions {
|
||||
brackets?: CharacterPair[];
|
||||
regExpRules?: OnEnterRule[];
|
||||
onEnterRules?: OnEnterRule[];
|
||||
}
|
||||
|
||||
interface IProcessedBracketPair {
|
||||
@@ -24,7 +25,7 @@ export class OnEnterSupport {
|
||||
private readonly _brackets: IProcessedBracketPair[];
|
||||
private readonly _regExpRules: OnEnterRule[];
|
||||
|
||||
constructor(opts?: IOnEnterSupportOptions) {
|
||||
constructor(opts: IOnEnterSupportOptions) {
|
||||
opts = opts || {};
|
||||
opts.brackets = opts.brackets || [
|
||||
['(', ')'],
|
||||
@@ -45,49 +46,54 @@ export class OnEnterSupport {
|
||||
});
|
||||
}
|
||||
});
|
||||
this._regExpRules = opts.regExpRules || [];
|
||||
this._regExpRules = opts.onEnterRules || [];
|
||||
}
|
||||
|
||||
public onEnter(oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
|
||||
// (1): `regExpRules`
|
||||
for (let i = 0, len = this._regExpRules.length; i < len; i++) {
|
||||
let rule = this._regExpRules[i];
|
||||
const regResult = [{
|
||||
reg: rule.beforeText,
|
||||
text: beforeEnterText
|
||||
}, {
|
||||
reg: rule.afterText,
|
||||
text: afterEnterText
|
||||
}, {
|
||||
reg: rule.oneLineAboveText,
|
||||
text: oneLineAboveText
|
||||
}].every((obj): boolean => {
|
||||
return obj.reg ? obj.reg.test(obj.text) : true;
|
||||
});
|
||||
if (autoIndent >= EditorAutoIndentStrategy.Advanced) {
|
||||
for (let i = 0, len = this._regExpRules.length; i < len; i++) {
|
||||
let rule = this._regExpRules[i];
|
||||
const regResult = [{
|
||||
reg: rule.beforeText,
|
||||
text: beforeEnterText
|
||||
}, {
|
||||
reg: rule.afterText,
|
||||
text: afterEnterText
|
||||
}, {
|
||||
reg: rule.oneLineAboveText,
|
||||
text: oneLineAboveText
|
||||
}].every((obj): boolean => {
|
||||
return obj.reg ? obj.reg.test(obj.text) : true;
|
||||
});
|
||||
|
||||
if (regResult) {
|
||||
return rule.action;
|
||||
if (regResult) {
|
||||
return rule.action;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// (2): Special indent-outdent
|
||||
if (beforeEnterText.length > 0 && afterEnterText.length > 0) {
|
||||
for (let i = 0, len = this._brackets.length; i < len; i++) {
|
||||
let bracket = this._brackets[i];
|
||||
if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) {
|
||||
return { indentAction: IndentAction.IndentOutdent };
|
||||
if (autoIndent >= EditorAutoIndentStrategy.Brackets) {
|
||||
if (beforeEnterText.length > 0 && afterEnterText.length > 0) {
|
||||
for (let i = 0, len = this._brackets.length; i < len; i++) {
|
||||
let bracket = this._brackets[i];
|
||||
if (bracket.openRegExp.test(beforeEnterText) && bracket.closeRegExp.test(afterEnterText)) {
|
||||
return { indentAction: IndentAction.IndentOutdent };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// (4): Open bracket based logic
|
||||
if (beforeEnterText.length > 0) {
|
||||
for (let i = 0, len = this._brackets.length; i < len; i++) {
|
||||
let bracket = this._brackets[i];
|
||||
if (bracket.openRegExp.test(beforeEnterText)) {
|
||||
return { indentAction: IndentAction.Indent };
|
||||
if (autoIndent >= EditorAutoIndentStrategy.Brackets) {
|
||||
if (beforeEnterText.length > 0) {
|
||||
for (let i = 0, len = this._brackets.length; i < len; i++) {
|
||||
let bracket = this._brackets[i];
|
||||
if (bracket.openRegExp.test(beforeEnterText)) {
|
||||
return { indentAction: IndentAction.Indent };
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
|
||||
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
|
||||
tabsCharDelta += insertSpacesCount - 1;
|
||||
while (insertSpacesCount > 0) {
|
||||
partContent += useNbsp ? ' ' : ' ';
|
||||
partContent += useNbsp ? ' ' : ' ';
|
||||
insertSpacesCount--;
|
||||
}
|
||||
break;
|
||||
@@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
|
||||
break;
|
||||
|
||||
case CharCode.Space:
|
||||
partContent += useNbsp ? ' ' : ' ';
|
||||
partContent += useNbsp ? ' ' : ' ';
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
@@ -17,7 +17,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model';
|
||||
import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel';
|
||||
import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper';
|
||||
import { CompletionItem, CompletionItemKind, CompletionList, IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes';
|
||||
import { IInplaceReplaceSupportResult, ILink, TextEdit } from 'vs/editor/common/modes';
|
||||
import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkComputer';
|
||||
import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport';
|
||||
import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService';
|
||||
@@ -529,44 +529,38 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
|
||||
|
||||
private static readonly _suggestionsLimit = 10000;
|
||||
|
||||
public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<CompletionList | null> {
|
||||
public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<string[] | null> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (!model) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const seen: Record<string, boolean> = Object.create(null);
|
||||
const suggestions: CompletionItem[] = [];
|
||||
|
||||
const words: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
|
||||
const wordUntil = model.getWordUntilPosition(position, wordDefRegExp);
|
||||
|
||||
const wordAt = model.getWordAtPosition(position, wordDefRegExp);
|
||||
if (wordAt) {
|
||||
seen[model.getValueInRange(wordAt)] = true;
|
||||
seen.add(model.getValueInRange(wordAt));
|
||||
}
|
||||
|
||||
for (
|
||||
let iter = model.createWordIterator(wordDefRegExp), e = iter.next();
|
||||
!e.done && suggestions.length <= EditorSimpleWorker._suggestionsLimit;
|
||||
!e.done && seen.size <= EditorSimpleWorker._suggestionsLimit;
|
||||
e = iter.next()
|
||||
) {
|
||||
const word = e.value;
|
||||
if (seen[word]) {
|
||||
if (seen.has(word)) {
|
||||
continue;
|
||||
}
|
||||
seen[word] = true;
|
||||
seen.add(word);
|
||||
if (!isNaN(Number(word))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestions.push({
|
||||
kind: CompletionItemKind.Text,
|
||||
label: word,
|
||||
insertText: word,
|
||||
range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn }
|
||||
});
|
||||
words.push(word);
|
||||
}
|
||||
return { suggestions };
|
||||
return words;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri';
|
||||
import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/base/common/worker/simpleWorker';
|
||||
import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory';
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
@@ -144,7 +144,7 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider {
|
||||
this._modelService = modelService;
|
||||
}
|
||||
|
||||
provideCompletionItems(model: ITextModel, position: Position): Promise<modes.CompletionList | null> | undefined {
|
||||
async provideCompletionItems(model: ITextModel, position: Position): Promise<modes.CompletionList | undefined> {
|
||||
const { wordBasedSuggestions } = this._configurationService.getValue<{ wordBasedSuggestions?: boolean }>(model.uri, position, 'editor');
|
||||
if (!wordBasedSuggestions) {
|
||||
return undefined;
|
||||
@@ -152,7 +152,27 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider {
|
||||
if (!canSyncModel(this._modelService, model.uri)) {
|
||||
return undefined; // File too large
|
||||
}
|
||||
return this._workerManager.withWorker().then(client => client.textualSuggest(model.uri, position));
|
||||
|
||||
const word = model.getWordAtPosition(position);
|
||||
const replace = !word ? Range.fromPositions(position) : new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn);
|
||||
const insert = replace.setEndPosition(position.lineNumber, position.column);
|
||||
|
||||
const client = await this._workerManager.withWorker();
|
||||
const words = await client.textualSuggest(model.uri, position);
|
||||
if (!words) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: words.map((word): modes.CompletionItem => {
|
||||
return {
|
||||
kind: modes.CompletionItemKind.Text,
|
||||
label: word,
|
||||
insertText: word,
|
||||
range: { insert, replace }
|
||||
};
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +453,7 @@ export class EditorWorkerClient extends Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
public textualSuggest(resource: URI, position: IPosition): Promise<modes.CompletionList | null> {
|
||||
public textualSuggest(resource: URI, position: IPosition): Promise<string[] | null> {
|
||||
return this._withSyncedResources([resource]).then(proxy => {
|
||||
let model = this._modelService.getModel(resource);
|
||||
if (!model) {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { IMarkerService, IMarker, MarkerSeverity, MarkerTag } from 'vs/platform/markers/common/markers';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration } from 'vs/editor/common/model';
|
||||
import { IModelDeltaDecoration, ITextModel, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, IModelDecoration, MinimapPosition, IModelDecorationMinimapOptions } from 'vs/editor/common/model';
|
||||
import { ClassName } from 'vs/editor/common/model/intervalTree';
|
||||
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerWarning, overviewRulerInfo, overviewRulerError } from 'vs/editor/common/view/editorColorRegistry';
|
||||
@@ -17,6 +17,7 @@ import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDeco
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { withUndefinedAsNull } from 'vs/base/common/types';
|
||||
import { minimapWarning, minimapError } from 'vs/platform/theme/common/colorRegistry';
|
||||
|
||||
function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
@@ -189,6 +190,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor
|
||||
let color: ThemeColor | undefined = undefined;
|
||||
let zIndex: number;
|
||||
let inlineClassName: string | undefined = undefined;
|
||||
let minimap: IModelDecorationMinimapOptions | undefined;
|
||||
|
||||
switch (marker.severity) {
|
||||
case MarkerSeverity.Hint:
|
||||
@@ -203,6 +205,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor
|
||||
className = ClassName.EditorWarningDecoration;
|
||||
color = themeColorFromId(overviewRulerWarning);
|
||||
zIndex = 20;
|
||||
minimap = {
|
||||
color: themeColorFromId(minimapWarning),
|
||||
position: MinimapPosition.Inline
|
||||
};
|
||||
break;
|
||||
case MarkerSeverity.Info:
|
||||
className = ClassName.EditorInfoDecoration;
|
||||
@@ -214,6 +220,10 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor
|
||||
className = ClassName.EditorErrorDecoration;
|
||||
color = themeColorFromId(overviewRulerError);
|
||||
zIndex = 30;
|
||||
minimap = {
|
||||
color: themeColorFromId(minimapError),
|
||||
position: MinimapPosition.Inline
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -234,6 +244,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor
|
||||
color,
|
||||
position: OverviewRulerLane.Right
|
||||
},
|
||||
minimap,
|
||||
zIndex,
|
||||
inlineClassName,
|
||||
};
|
||||
|
||||
@@ -6,19 +6,24 @@
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
|
||||
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
|
||||
import { IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
|
||||
import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits } from 'vs/editor/common/modes';
|
||||
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
|
||||
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
@@ -114,7 +119,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
constructor(
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService
|
||||
@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IThemeService themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._configurationService = configurationService;
|
||||
@@ -124,6 +130,8 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
|
||||
this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions());
|
||||
this._updateModelOptions();
|
||||
|
||||
this._register(new SemanticColoringFeature(this, themeService));
|
||||
}
|
||||
|
||||
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
@@ -430,3 +438,467 @@ export class ModelServiceImpl extends Disposable implements IModelService {
|
||||
export interface ILineSequence {
|
||||
getLineContent(lineNumber: number): string;
|
||||
}
|
||||
|
||||
class SemanticColoringFeature extends Disposable {
|
||||
private _watchers: Record<string, ModelSemanticColoring>;
|
||||
private _semanticStyling: SemanticStyling;
|
||||
|
||||
constructor(modelService: IModelService, themeService: IThemeService) {
|
||||
super();
|
||||
this._watchers = Object.create(null);
|
||||
this._semanticStyling = this._register(new SemanticStyling(themeService));
|
||||
this._register(modelService.onModelAdded((model) => {
|
||||
this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling);
|
||||
}));
|
||||
this._register(modelService.onModelRemoved((model) => {
|
||||
this._watchers[model.uri.toString()].dispose();
|
||||
delete this._watchers[model.uri.toString()];
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
class SemanticStyling extends Disposable {
|
||||
|
||||
private _caches: WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>;
|
||||
|
||||
constructor(
|
||||
private readonly _themeService: IThemeService
|
||||
) {
|
||||
super();
|
||||
this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
|
||||
if (this._themeService) {
|
||||
// workaround for tests which use undefined... :/
|
||||
this._register(this._themeService.onThemeChange(() => {
|
||||
this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling {
|
||||
if (!this._caches.has(provider)) {
|
||||
this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService));
|
||||
}
|
||||
return this._caches.get(provider)!;
|
||||
}
|
||||
}
|
||||
|
||||
const enum Constants {
|
||||
NO_STYLING = 0b01111111111111111111111111111111
|
||||
}
|
||||
|
||||
class HashTableEntry {
|
||||
public readonly tokenTypeIndex: number;
|
||||
public readonly tokenModifierSet: number;
|
||||
public readonly metadata: number;
|
||||
public next: HashTableEntry | null;
|
||||
|
||||
constructor(tokenTypeIndex: number, tokenModifierSet: number, metadata: number) {
|
||||
this.tokenTypeIndex = tokenTypeIndex;
|
||||
this.tokenModifierSet = tokenModifierSet;
|
||||
this.metadata = metadata;
|
||||
this.next = null;
|
||||
}
|
||||
}
|
||||
|
||||
class HashTable {
|
||||
|
||||
private static _SIZES = [3, 7, 13, 31, 61, 127, 251, 509, 1021, 2039, 4093, 8191, 16381, 32749, 65521, 131071, 262139, 524287, 1048573, 2097143];
|
||||
|
||||
private _elementsCount: number;
|
||||
private _currentLengthIndex: number;
|
||||
private _currentLength: number;
|
||||
private _growCount: number;
|
||||
private _elements: (HashTableEntry | null)[];
|
||||
|
||||
constructor() {
|
||||
this._elementsCount = 0;
|
||||
this._currentLengthIndex = 0;
|
||||
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
|
||||
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
|
||||
this._elements = [];
|
||||
HashTable._nullOutEntries(this._elements, this._currentLength);
|
||||
}
|
||||
|
||||
private static _nullOutEntries(entries: (HashTableEntry | null)[], length: number): void {
|
||||
for (let i = 0; i < length; i++) {
|
||||
entries[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
private _hashFunc(tokenTypeIndex: number, tokenModifierSet: number): number {
|
||||
return ((((tokenTypeIndex << 5) - tokenTypeIndex) + tokenModifierSet) | 0) % this._currentLength; // tokenTypeIndex * 31 + tokenModifierSet, keep as int32
|
||||
}
|
||||
|
||||
public get(tokenTypeIndex: number, tokenModifierSet: number): HashTableEntry | null {
|
||||
const hash = this._hashFunc(tokenTypeIndex, tokenModifierSet);
|
||||
|
||||
let p = this._elements[hash];
|
||||
while (p) {
|
||||
if (p.tokenTypeIndex === tokenTypeIndex && p.tokenModifierSet === tokenModifierSet) {
|
||||
return p;
|
||||
}
|
||||
p = p.next;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public add(tokenTypeIndex: number, tokenModifierSet: number, metadata: number): void {
|
||||
this._elementsCount++;
|
||||
if (this._growCount !== 0 && this._elementsCount >= this._growCount) {
|
||||
// expand!
|
||||
const oldElements = this._elements;
|
||||
|
||||
this._currentLengthIndex++;
|
||||
this._currentLength = HashTable._SIZES[this._currentLengthIndex];
|
||||
this._growCount = Math.round(this._currentLengthIndex + 1 < HashTable._SIZES.length ? 2 / 3 * this._currentLength : 0);
|
||||
this._elements = [];
|
||||
HashTable._nullOutEntries(this._elements, this._currentLength);
|
||||
|
||||
for (const first of oldElements) {
|
||||
let p = first;
|
||||
while (p) {
|
||||
const oldNext = p.next;
|
||||
p.next = null;
|
||||
this._add(p);
|
||||
p = oldNext;
|
||||
}
|
||||
}
|
||||
}
|
||||
this._add(new HashTableEntry(tokenTypeIndex, tokenModifierSet, metadata));
|
||||
}
|
||||
|
||||
private _add(element: HashTableEntry): void {
|
||||
const hash = this._hashFunc(element.tokenTypeIndex, element.tokenModifierSet);
|
||||
element.next = this._elements[hash];
|
||||
this._elements[hash] = element;
|
||||
}
|
||||
}
|
||||
|
||||
class SemanticColoringProviderStyling {
|
||||
|
||||
private readonly _hashTable: HashTable;
|
||||
|
||||
constructor(
|
||||
private readonly _legend: SemanticTokensLegend,
|
||||
private readonly _themeService: IThemeService
|
||||
) {
|
||||
this._hashTable = new HashTable();
|
||||
}
|
||||
|
||||
public getMetadata(tokenTypeIndex: number, tokenModifierSet: number): number {
|
||||
const entry = this._hashTable.get(tokenTypeIndex, tokenModifierSet);
|
||||
if (entry) {
|
||||
return entry.metadata;
|
||||
}
|
||||
|
||||
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
|
||||
const tokenModifiers: string[] = [];
|
||||
for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
|
||||
if (tokenModifierSet & 1) {
|
||||
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
|
||||
}
|
||||
tokenModifierSet = tokenModifierSet >> 1;
|
||||
}
|
||||
|
||||
let metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
|
||||
if (typeof metadata === 'undefined') {
|
||||
metadata = Constants.NO_STYLING;
|
||||
}
|
||||
|
||||
this._hashTable.add(tokenTypeIndex, tokenModifierSet, metadata);
|
||||
return metadata;
|
||||
}
|
||||
}
|
||||
|
||||
const enum SemanticColoringConstants {
|
||||
/**
|
||||
* Let's aim at having 8KB buffers if possible...
|
||||
* So that would be 8192 / (5 * 4) = 409.6 tokens per area
|
||||
*/
|
||||
DesiredTokensPerArea = 400,
|
||||
|
||||
/**
|
||||
* Try to keep the total number of areas under 1024 if possible,
|
||||
* simply compensate by having more tokens per area...
|
||||
*/
|
||||
DesiredMaxAreas = 1024,
|
||||
}
|
||||
|
||||
class SemanticTokensResponse {
|
||||
constructor(
|
||||
private readonly _provider: SemanticTokensProvider,
|
||||
public readonly resultId: string | undefined,
|
||||
public readonly data: Uint32Array
|
||||
) { }
|
||||
|
||||
public dispose(): void {
|
||||
this._provider.releaseSemanticTokens(this.resultId);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelSemanticColoring extends Disposable {
|
||||
|
||||
private _isDisposed: boolean;
|
||||
private readonly _model: ITextModel;
|
||||
private readonly _semanticStyling: SemanticStyling;
|
||||
private readonly _fetchSemanticTokens: RunOnceScheduler;
|
||||
private _currentResponse: SemanticTokensResponse | null;
|
||||
private _currentRequestCancellationTokenSource: CancellationTokenSource | null;
|
||||
|
||||
constructor(model: ITextModel, themeService: IThemeService, stylingProvider: SemanticStyling) {
|
||||
super();
|
||||
|
||||
this._isDisposed = false;
|
||||
this._model = model;
|
||||
this._semanticStyling = stylingProvider;
|
||||
this._fetchSemanticTokens = this._register(new RunOnceScheduler(() => this._fetchSemanticTokensNow(), 500));
|
||||
this._currentResponse = null;
|
||||
this._currentRequestCancellationTokenSource = null;
|
||||
|
||||
this._register(this._model.onDidChangeContent(e => this._fetchSemanticTokens.schedule()));
|
||||
this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule()));
|
||||
if (themeService) {
|
||||
// workaround for tests which use undefined... :/
|
||||
this._register(themeService.onThemeChange(_ => {
|
||||
// clear out existing tokens
|
||||
this._setSemanticTokens(null, null, null, []);
|
||||
this._fetchSemanticTokens.schedule();
|
||||
}));
|
||||
}
|
||||
this._fetchSemanticTokens.schedule(0);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this._isDisposed = true;
|
||||
if (this._currentResponse) {
|
||||
this._currentResponse.dispose();
|
||||
this._currentResponse = null;
|
||||
}
|
||||
if (this._currentRequestCancellationTokenSource) {
|
||||
this._currentRequestCancellationTokenSource.cancel();
|
||||
this._currentRequestCancellationTokenSource = null;
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private _fetchSemanticTokensNow(): void {
|
||||
if (this._currentRequestCancellationTokenSource) {
|
||||
// there is already a request running, let it finish...
|
||||
return;
|
||||
}
|
||||
const provider = this._getSemanticColoringProvider();
|
||||
if (!provider) {
|
||||
return;
|
||||
}
|
||||
this._currentRequestCancellationTokenSource = new CancellationTokenSource();
|
||||
|
||||
const pendingChanges: IModelContentChangedEvent[] = [];
|
||||
const contentChangeListener = this._model.onDidChangeContent((e) => {
|
||||
pendingChanges.push(e);
|
||||
});
|
||||
|
||||
const styling = this._semanticStyling.get(provider);
|
||||
|
||||
const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null;
|
||||
const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token));
|
||||
|
||||
request.then((res) => {
|
||||
this._currentRequestCancellationTokenSource = null;
|
||||
contentChangeListener.dispose();
|
||||
this._setSemanticTokens(provider, res || null, styling, pendingChanges);
|
||||
}, (err) => {
|
||||
errors.onUnexpectedError(err);
|
||||
this._currentRequestCancellationTokenSource = null;
|
||||
contentChangeListener.dispose();
|
||||
this._setSemanticTokens(provider, null, styling, pendingChanges);
|
||||
});
|
||||
}
|
||||
|
||||
private static _isSemanticTokens(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokens {
|
||||
return v && !!((<SemanticTokens>v).data);
|
||||
}
|
||||
|
||||
private static _isSemanticTokensEdits(v: SemanticTokens | SemanticTokensEdits): v is SemanticTokensEdits {
|
||||
return v && Array.isArray((<SemanticTokensEdits>v).edits);
|
||||
}
|
||||
|
||||
private static _copy(src: Uint32Array, srcOffset: number, dest: Uint32Array, destOffset: number, length: number): void {
|
||||
for (let i = 0; i < length; i++) {
|
||||
dest[destOffset + i] = src[srcOffset + i];
|
||||
}
|
||||
}
|
||||
|
||||
private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
|
||||
const currentResponse = this._currentResponse;
|
||||
if (this._currentResponse) {
|
||||
this._currentResponse.dispose();
|
||||
this._currentResponse = null;
|
||||
}
|
||||
if (this._isDisposed) {
|
||||
// disposed!
|
||||
if (provider && tokens) {
|
||||
provider.releaseSemanticTokens(tokens.resultId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!provider || !tokens || !styling) {
|
||||
this._model.setSemanticTokens(null);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ModelSemanticColoring._isSemanticTokensEdits(tokens)) {
|
||||
if (!currentResponse) {
|
||||
// not possible!
|
||||
this._model.setSemanticTokens(null);
|
||||
return;
|
||||
}
|
||||
if (tokens.edits.length === 0) {
|
||||
// nothing to do!
|
||||
tokens = {
|
||||
resultId: tokens.resultId,
|
||||
data: currentResponse.data
|
||||
};
|
||||
} else {
|
||||
let deltaLength = 0;
|
||||
for (const edit of tokens.edits) {
|
||||
deltaLength += (edit.data ? edit.data.length : 0) - edit.deleteCount;
|
||||
}
|
||||
|
||||
const srcData = currentResponse.data;
|
||||
const destData = new Uint32Array(srcData.length + deltaLength);
|
||||
|
||||
let srcLastStart = srcData.length;
|
||||
let destLastStart = destData.length;
|
||||
for (let i = tokens.edits.length - 1; i >= 0; i--) {
|
||||
const edit = tokens.edits[i];
|
||||
|
||||
const copyCount = srcLastStart - (edit.start + edit.deleteCount);
|
||||
if (copyCount > 0) {
|
||||
ModelSemanticColoring._copy(srcData, srcLastStart - copyCount, destData, destLastStart - copyCount, copyCount);
|
||||
destLastStart -= copyCount;
|
||||
}
|
||||
|
||||
if (edit.data) {
|
||||
ModelSemanticColoring._copy(edit.data, 0, destData, destLastStart - edit.data.length, edit.data.length);
|
||||
destLastStart -= edit.data.length;
|
||||
}
|
||||
|
||||
srcLastStart = edit.start;
|
||||
}
|
||||
|
||||
if (srcLastStart > 0) {
|
||||
ModelSemanticColoring._copy(srcData, 0, destData, 0, srcLastStart);
|
||||
}
|
||||
|
||||
tokens = {
|
||||
resultId: tokens.resultId,
|
||||
data: destData
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (ModelSemanticColoring._isSemanticTokens(tokens)) {
|
||||
|
||||
this._currentResponse = new SemanticTokensResponse(provider, tokens.resultId, tokens.data);
|
||||
|
||||
const srcData = tokens.data;
|
||||
const tokenCount = (tokens.data.length / 5) | 0;
|
||||
const tokensPerArea = Math.max(Math.ceil(tokenCount / SemanticColoringConstants.DesiredMaxAreas), SemanticColoringConstants.DesiredTokensPerArea);
|
||||
|
||||
const result: MultilineTokens2[] = [];
|
||||
|
||||
let tokenIndex = 0;
|
||||
let lastLineNumber = 1;
|
||||
let lastStartCharacter = 0;
|
||||
while (tokenIndex < tokenCount) {
|
||||
const tokenStartIndex = tokenIndex;
|
||||
let tokenEndIndex = Math.min(tokenStartIndex + tokensPerArea, tokenCount);
|
||||
|
||||
// Keep tokens on the same line in the same area...
|
||||
if (tokenEndIndex < tokenCount) {
|
||||
|
||||
let smallTokenEndIndex = tokenEndIndex;
|
||||
while (smallTokenEndIndex - 1 > tokenStartIndex && srcData[5 * smallTokenEndIndex] === 0) {
|
||||
smallTokenEndIndex--;
|
||||
}
|
||||
|
||||
if (smallTokenEndIndex - 1 === tokenStartIndex) {
|
||||
// there are so many tokens on this line that our area would be empty, we must now go right
|
||||
let bigTokenEndIndex = tokenEndIndex;
|
||||
while (bigTokenEndIndex + 1 < tokenCount && srcData[5 * bigTokenEndIndex] === 0) {
|
||||
bigTokenEndIndex++;
|
||||
}
|
||||
tokenEndIndex = bigTokenEndIndex;
|
||||
} else {
|
||||
tokenEndIndex = smallTokenEndIndex;
|
||||
}
|
||||
}
|
||||
|
||||
let destData = new Uint32Array((tokenEndIndex - tokenStartIndex) * 4);
|
||||
let destOffset = 0;
|
||||
let areaLine = 0;
|
||||
while (tokenIndex < tokenEndIndex) {
|
||||
const srcOffset = 5 * tokenIndex;
|
||||
const deltaLine = srcData[srcOffset];
|
||||
const deltaCharacter = srcData[srcOffset + 1];
|
||||
const lineNumber = lastLineNumber + deltaLine;
|
||||
const startCharacter = (deltaLine === 0 ? lastStartCharacter + deltaCharacter : deltaCharacter);
|
||||
const length = srcData[srcOffset + 2];
|
||||
const tokenTypeIndex = srcData[srcOffset + 3];
|
||||
const tokenModifierSet = srcData[srcOffset + 4];
|
||||
const metadata = styling.getMetadata(tokenTypeIndex, tokenModifierSet);
|
||||
|
||||
if (metadata !== Constants.NO_STYLING) {
|
||||
if (areaLine === 0) {
|
||||
areaLine = lineNumber;
|
||||
}
|
||||
destData[destOffset] = lineNumber - areaLine;
|
||||
destData[destOffset + 1] = startCharacter;
|
||||
destData[destOffset + 2] = startCharacter + length;
|
||||
destData[destOffset + 3] = metadata;
|
||||
destOffset += 4;
|
||||
}
|
||||
|
||||
lastLineNumber = lineNumber;
|
||||
lastStartCharacter = startCharacter;
|
||||
tokenIndex++;
|
||||
}
|
||||
|
||||
if (destOffset !== destData.length) {
|
||||
destData = destData.subarray(0, destOffset);
|
||||
}
|
||||
|
||||
const tokens = new MultilineTokens2(areaLine, new SparseEncodedTokens(destData));
|
||||
result.push(tokens);
|
||||
}
|
||||
|
||||
// Adjust incoming semantic tokens
|
||||
if (pendingChanges.length > 0) {
|
||||
// More changes occurred while the request was running
|
||||
// We need to:
|
||||
// 1. Adjust incoming semantic tokens
|
||||
// 2. Request them again
|
||||
for (const change of pendingChanges) {
|
||||
for (const area of result) {
|
||||
for (const singleChange of change.changes) {
|
||||
area.applyEdit(singleChange.range, singleChange.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._fetchSemanticTokens.schedule();
|
||||
}
|
||||
|
||||
this._model.setSemanticTokens(result);
|
||||
return;
|
||||
}
|
||||
|
||||
this._model.setSemanticTokens(null);
|
||||
}
|
||||
|
||||
private _getSemanticColoringProvider(): SemanticTokensProvider | null {
|
||||
const result = SemanticTokensProviderRegistry.ordered(this._model);
|
||||
return (result.length > 0 ? result[0] : null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +52,9 @@ export interface ITextEditorModel extends IEditorModel {
|
||||
createSnapshot(this: IResolvedTextEditorModel): ITextSnapshot;
|
||||
createSnapshot(this: ITextEditorModel): ITextSnapshot | null;
|
||||
|
||||
/**
|
||||
* Signals if this model is readonly or not.
|
||||
*/
|
||||
isReadonly(): boolean;
|
||||
}
|
||||
|
||||
|
||||
@@ -233,7 +233,8 @@ export enum OverviewRulerLane {
|
||||
* Position in the minimap to render the decoration.
|
||||
*/
|
||||
export enum MinimapPosition {
|
||||
Inline = 1
|
||||
Inline = 1,
|
||||
Gutter = 2
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -15,6 +15,8 @@ export const editorLineHighlight = registerColor('editor.lineHighlightBackground
|
||||
export const editorLineHighlightBorder = registerColor('editor.lineHighlightBorder', { dark: '#282828', light: '#eeeeee', hc: '#f38518' }, nls.localize('lineHighlightBorderBox', 'Background color for the border around the line at the cursor position.'));
|
||||
export const editorRangeHighlight = registerColor('editor.rangeHighlightBackground', { dark: '#ffffff0b', light: '#fdff0033', hc: null }, nls.localize('rangeHighlight', 'Background color of highlighted ranges, like by quick open and find features. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
export const editorRangeHighlightBorder = registerColor('editor.rangeHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('rangeHighlightBorder', 'Background color of the border around highlighted ranges.'), true);
|
||||
export const editorSymbolHighlight = registerColor('editor.symbolHighlightBackground', { dark: editorRangeHighlight, light: editorRangeHighlight, hc: null }, nls.localize('symbolHighlight', 'Background color of highlighted symbol, like for go to definition or go next/previous symbol. The color must not be opaque so as not to hide underlying decorations.'), true);
|
||||
export const editorSymbolHighlightBorder = registerColor('editor.symbolHighlightBorder', { dark: null, light: null, hc: activeContrastBorder }, nls.localize('symbolHighlightBorder', 'Background color of the border around highlighted symbols.'), true);
|
||||
|
||||
export const editorCursorForeground = registerColor('editorCursor.foreground', { dark: '#AEAFAD', light: Color.black, hc: Color.white }, nls.localize('caret', 'Color of the editor cursor.'));
|
||||
export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.'));
|
||||
@@ -73,6 +75,16 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`);
|
||||
}
|
||||
|
||||
const symbolHighlight = theme.getColor(editorSymbolHighlight);
|
||||
if (symbolHighlight) {
|
||||
collector.addRule(`.monaco-editor .symbolHighlight { background-color: ${symbolHighlight}; }`);
|
||||
}
|
||||
|
||||
const symbolHighlightBorder = theme.getColor(editorSymbolHighlightBorder);
|
||||
if (symbolHighlightBorder) {
|
||||
collector.addRule(`.monaco-editor .symbolHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${symbolHighlightBorder}; }`);
|
||||
}
|
||||
|
||||
const invisibles = theme.getColor(editorWhitespaces);
|
||||
if (invisibles) {
|
||||
collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { IViewLayout, ViewModelDecoration } from 'vs/editor/common/viewModel/vie
|
||||
|
||||
export interface IViewLines {
|
||||
linesVisibleRangesForRange(range: Range, includeNewLines: boolean): LineVisibleRanges[] | null;
|
||||
visibleRangeForPosition(position: Position): HorizontalRange | null;
|
||||
visibleRangeForPosition(position: Position): HorizontalPosition | null;
|
||||
}
|
||||
|
||||
export abstract class RestrictedRenderingContext {
|
||||
@@ -77,26 +77,20 @@ export class RenderingContext extends RestrictedRenderingContext {
|
||||
return this._viewLines.linesVisibleRangesForRange(range, includeNewLines);
|
||||
}
|
||||
|
||||
public visibleRangeForPosition(position: Position): HorizontalRange | null {
|
||||
public visibleRangeForPosition(position: Position): HorizontalPosition | null {
|
||||
return this._viewLines.visibleRangeForPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
export class LineVisibleRanges {
|
||||
_lineVisibleRangesBrand: void;
|
||||
|
||||
public lineNumber: number;
|
||||
public ranges: HorizontalRange[];
|
||||
|
||||
constructor(lineNumber: number, ranges: HorizontalRange[]) {
|
||||
this.lineNumber = lineNumber;
|
||||
this.ranges = ranges;
|
||||
}
|
||||
constructor(
|
||||
public readonly outsideRenderedLine: boolean,
|
||||
public readonly lineNumber: number,
|
||||
public readonly ranges: HorizontalRange[]
|
||||
) { }
|
||||
}
|
||||
|
||||
export class HorizontalRange {
|
||||
_horizontalRangeBrand: void;
|
||||
|
||||
public left: number;
|
||||
public width: number;
|
||||
|
||||
@@ -109,3 +103,21 @@ export class HorizontalRange {
|
||||
return `[${this.left},${this.width}]`;
|
||||
}
|
||||
}
|
||||
|
||||
export class HorizontalPosition {
|
||||
public outsideRenderedLine: boolean;
|
||||
public left: number;
|
||||
|
||||
constructor(outsideRenderedLine: boolean, left: number) {
|
||||
this.outsideRenderedLine = outsideRenderedLine;
|
||||
this.left = Math.round(left);
|
||||
}
|
||||
}
|
||||
|
||||
export class VisibleRanges {
|
||||
constructor(
|
||||
public readonly outsideRenderedLine: boolean,
|
||||
public readonly ranges: HorizontalRange[]
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,44 +4,157 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace, WhitespaceComputer } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
import { IViewWhitespaceViewportData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export interface IEditorWhitespace {
|
||||
readonly id: string;
|
||||
readonly afterLineNumber: number;
|
||||
readonly height: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* An accessor that allows for whtiespace to be added, removed or changed in bulk.
|
||||
*/
|
||||
export interface IWhitespaceChangeAccessor {
|
||||
insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string;
|
||||
changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void;
|
||||
removeWhitespace(id: string): void;
|
||||
}
|
||||
|
||||
interface IPendingChange { id: string; newAfterLineNumber: number; newHeight: number; }
|
||||
interface IPendingRemove { id: string; }
|
||||
|
||||
class PendingChanges {
|
||||
private _hasPending: boolean;
|
||||
private _inserts: EditorWhitespace[];
|
||||
private _changes: IPendingChange[];
|
||||
private _removes: IPendingRemove[];
|
||||
|
||||
constructor() {
|
||||
this._hasPending = false;
|
||||
this._inserts = [];
|
||||
this._changes = [];
|
||||
this._removes = [];
|
||||
}
|
||||
|
||||
public insert(x: EditorWhitespace): void {
|
||||
this._hasPending = true;
|
||||
this._inserts.push(x);
|
||||
}
|
||||
|
||||
public change(x: IPendingChange): void {
|
||||
this._hasPending = true;
|
||||
this._changes.push(x);
|
||||
}
|
||||
|
||||
public remove(x: IPendingRemove): void {
|
||||
this._hasPending = true;
|
||||
this._removes.push(x);
|
||||
}
|
||||
|
||||
public mustCommit(): boolean {
|
||||
return this._hasPending;
|
||||
}
|
||||
|
||||
public commit(linesLayout: LinesLayout): void {
|
||||
if (!this._hasPending) {
|
||||
return;
|
||||
}
|
||||
|
||||
const inserts = this._inserts;
|
||||
const changes = this._changes;
|
||||
const removes = this._removes;
|
||||
|
||||
this._hasPending = false;
|
||||
this._inserts = [];
|
||||
this._changes = [];
|
||||
this._removes = [];
|
||||
|
||||
linesLayout._commitPendingChanges(inserts, changes, removes);
|
||||
}
|
||||
}
|
||||
|
||||
export class EditorWhitespace implements IEditorWhitespace {
|
||||
public id: string;
|
||||
public afterLineNumber: number;
|
||||
public ordinal: number;
|
||||
public height: number;
|
||||
public minWidth: number;
|
||||
public prefixSum: number;
|
||||
|
||||
constructor(id: string, afterLineNumber: number, ordinal: number, height: number, minWidth: number) {
|
||||
this.id = id;
|
||||
this.afterLineNumber = afterLineNumber;
|
||||
this.ordinal = ordinal;
|
||||
this.height = height;
|
||||
this.minWidth = minWidth;
|
||||
this.prefixSum = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Layouting of objects that take vertical space (by having a height) and push down other objects.
|
||||
*
|
||||
* These objects are basically either text (lines) or spaces between those lines (whitespaces).
|
||||
* This provides commodity operations for working with lines that contain whitespace that pushes lines lower (vertically).
|
||||
* This is written with no knowledge of an editor in mind.
|
||||
*/
|
||||
export class LinesLayout {
|
||||
|
||||
/**
|
||||
* Keep track of the total number of lines.
|
||||
* This is useful for doing binary searches or for doing hit-testing.
|
||||
*/
|
||||
private _lineCount: number;
|
||||
private static INSTANCE_COUNT = 0;
|
||||
|
||||
/**
|
||||
* The height of a line in pixels.
|
||||
*/
|
||||
private readonly _instanceId: string;
|
||||
private readonly _pendingChanges: PendingChanges;
|
||||
private _lastWhitespaceId: number;
|
||||
private _arr: EditorWhitespace[];
|
||||
private _prefixSumValidIndex: number;
|
||||
private _minWidth: number;
|
||||
private _lineCount: number;
|
||||
private _lineHeight: number;
|
||||
|
||||
/**
|
||||
* Contains whitespace information in pixels
|
||||
*/
|
||||
private readonly _whitespaces: WhitespaceComputer;
|
||||
|
||||
constructor(lineCount: number, lineHeight: number) {
|
||||
this._instanceId = strings.singleLetterHash(++LinesLayout.INSTANCE_COUNT);
|
||||
this._pendingChanges = new PendingChanges();
|
||||
this._lastWhitespaceId = 0;
|
||||
this._arr = [];
|
||||
this._prefixSumValidIndex = -1;
|
||||
this._minWidth = -1; /* marker for not being computed */
|
||||
this._lineCount = lineCount;
|
||||
this._lineHeight = lineHeight;
|
||||
this._whitespaces = new WhitespaceComputer();
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the insertion index for a new value inside a sorted array of values.
|
||||
* If the value is already present in the sorted array, the insertion index will be after the already existing value.
|
||||
*/
|
||||
public static findInsertionIndex(arr: EditorWhitespace[], afterLineNumber: number, ordinal: number): number {
|
||||
let low = 0;
|
||||
let high = arr.length;
|
||||
|
||||
while (low < high) {
|
||||
const mid = ((low + high) >>> 1);
|
||||
|
||||
if (afterLineNumber === arr[mid].afterLineNumber) {
|
||||
if (ordinal < arr[mid].ordinal) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
} else if (afterLineNumber < arr[mid].afterLineNumber) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the height of a line in pixels.
|
||||
*/
|
||||
public setLineHeight(lineHeight: number): void {
|
||||
this._checkPendingChanges();
|
||||
this._lineHeight = lineHeight;
|
||||
}
|
||||
|
||||
@@ -51,37 +164,153 @@ export class LinesLayout {
|
||||
* @param lineCount New number of lines.
|
||||
*/
|
||||
public onFlushed(lineCount: number): void {
|
||||
this._checkPendingChanges();
|
||||
this._lineCount = lineCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new whitespace of a certain height after a line number.
|
||||
* The whitespace has a "sticky" characteristic.
|
||||
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
|
||||
*
|
||||
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
|
||||
* @param heightInPx The height of the whitespace, in pixels.
|
||||
* @return An id that can be used later to mutate or delete the whitespace
|
||||
*/
|
||||
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
|
||||
return this._whitespaces.insertWhitespace(afterLineNumber, ordinal, heightInPx, minWidth);
|
||||
public changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T {
|
||||
try {
|
||||
const accessor = {
|
||||
insertWhitespace: (afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string => {
|
||||
afterLineNumber = afterLineNumber | 0;
|
||||
ordinal = ordinal | 0;
|
||||
heightInPx = heightInPx | 0;
|
||||
minWidth = minWidth | 0;
|
||||
|
||||
const id = this._instanceId + (++this._lastWhitespaceId);
|
||||
this._pendingChanges.insert(new EditorWhitespace(id, afterLineNumber, ordinal, heightInPx, minWidth));
|
||||
return id;
|
||||
},
|
||||
changeOneWhitespace: (id: string, newAfterLineNumber: number, newHeight: number): void => {
|
||||
newAfterLineNumber = newAfterLineNumber | 0;
|
||||
newHeight = newHeight | 0;
|
||||
|
||||
this._pendingChanges.change({ id, newAfterLineNumber, newHeight });
|
||||
},
|
||||
removeWhitespace: (id: string): void => {
|
||||
this._pendingChanges.remove({ id });
|
||||
}
|
||||
};
|
||||
return callback(accessor);
|
||||
} finally {
|
||||
this._pendingChanges.commit(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Change properties associated with a certain whitespace.
|
||||
*/
|
||||
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
return this._whitespaces.changeWhitespace(id, newAfterLineNumber, newHeight);
|
||||
public _commitPendingChanges(inserts: EditorWhitespace[], changes: IPendingChange[], removes: IPendingRemove[]): void {
|
||||
if (inserts.length > 0 || removes.length > 0) {
|
||||
this._minWidth = -1; /* marker for not being computed */
|
||||
}
|
||||
|
||||
if (inserts.length + changes.length + removes.length <= 1) {
|
||||
// when only one thing happened, handle it "delicately"
|
||||
for (const insert of inserts) {
|
||||
this._insertWhitespace(insert);
|
||||
}
|
||||
for (const change of changes) {
|
||||
this._changeOneWhitespace(change.id, change.newAfterLineNumber, change.newHeight);
|
||||
}
|
||||
for (const remove of removes) {
|
||||
const index = this._findWhitespaceIndex(remove.id);
|
||||
if (index === -1) {
|
||||
continue;
|
||||
}
|
||||
this._removeWhitespace(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// simply rebuild the entire datastructure
|
||||
|
||||
const toRemove = new Set<string>();
|
||||
for (const remove of removes) {
|
||||
toRemove.add(remove.id);
|
||||
}
|
||||
|
||||
const toChange = new Map<string, IPendingChange>();
|
||||
for (const change of changes) {
|
||||
toChange.set(change.id, change);
|
||||
}
|
||||
|
||||
const applyRemoveAndChange = (whitespaces: EditorWhitespace[]): EditorWhitespace[] => {
|
||||
let result: EditorWhitespace[] = [];
|
||||
for (const whitespace of whitespaces) {
|
||||
if (toRemove.has(whitespace.id)) {
|
||||
continue;
|
||||
}
|
||||
if (toChange.has(whitespace.id)) {
|
||||
const change = toChange.get(whitespace.id)!;
|
||||
whitespace.afterLineNumber = change.newAfterLineNumber;
|
||||
whitespace.height = change.newHeight;
|
||||
}
|
||||
result.push(whitespace);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const result = applyRemoveAndChange(this._arr).concat(applyRemoveAndChange(inserts));
|
||||
result.sort((a, b) => {
|
||||
if (a.afterLineNumber === b.afterLineNumber) {
|
||||
return a.ordinal - b.ordinal;
|
||||
}
|
||||
return a.afterLineNumber - b.afterLineNumber;
|
||||
});
|
||||
|
||||
this._arr = result;
|
||||
this._prefixSumValidIndex = -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an existing whitespace.
|
||||
*
|
||||
* @param id The whitespace to remove
|
||||
* @return Returns true if the whitespace is found and it is removed.
|
||||
*/
|
||||
public removeWhitespace(id: string): boolean {
|
||||
return this._whitespaces.removeWhitespace(id);
|
||||
private _checkPendingChanges(): void {
|
||||
if (this._pendingChanges.mustCommit()) {
|
||||
console.warn(`Commiting pending changes before change accessor leaves due to read access.`);
|
||||
this._pendingChanges.commit(this);
|
||||
}
|
||||
}
|
||||
|
||||
private _insertWhitespace(whitespace: EditorWhitespace): void {
|
||||
const insertIndex = LinesLayout.findInsertionIndex(this._arr, whitespace.afterLineNumber, whitespace.ordinal);
|
||||
this._arr.splice(insertIndex, 0, whitespace);
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
|
||||
}
|
||||
|
||||
private _findWhitespaceIndex(id: string): number {
|
||||
const arr = this._arr;
|
||||
for (let i = 0, len = arr.length; i < len; i++) {
|
||||
if (arr[i].id === id) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private _changeOneWhitespace(id: string, newAfterLineNumber: number, newHeight: number): void {
|
||||
const index = this._findWhitespaceIndex(id);
|
||||
if (index === -1) {
|
||||
return;
|
||||
}
|
||||
if (this._arr[index].height !== newHeight) {
|
||||
this._arr[index].height = newHeight;
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
|
||||
}
|
||||
if (this._arr[index].afterLineNumber !== newAfterLineNumber) {
|
||||
// `afterLineNumber` changed for this whitespace
|
||||
|
||||
// Record old whitespace
|
||||
const whitespace = this._arr[index];
|
||||
|
||||
// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
|
||||
this._removeWhitespace(index);
|
||||
|
||||
whitespace.afterLineNumber = newAfterLineNumber;
|
||||
|
||||
// And add it again
|
||||
this._insertWhitespace(whitespace);
|
||||
}
|
||||
}
|
||||
|
||||
private _removeWhitespace(removeIndex: number): void {
|
||||
this._arr.splice(removeIndex, 1);
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,8 +320,24 @@ export class LinesLayout {
|
||||
* @param toLineNumber The line number at which the deletion ended, inclusive
|
||||
*/
|
||||
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
|
||||
this._checkPendingChanges();
|
||||
fromLineNumber = fromLineNumber | 0;
|
||||
toLineNumber = toLineNumber | 0;
|
||||
|
||||
this._lineCount -= (toLineNumber - fromLineNumber + 1);
|
||||
this._whitespaces.onLinesDeleted(fromLineNumber, toLineNumber);
|
||||
for (let i = 0, len = this._arr.length; i < len; i++) {
|
||||
const afterLineNumber = this._arr[i].afterLineNumber;
|
||||
|
||||
if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
|
||||
// The line this whitespace was after has been deleted
|
||||
// => move whitespace to before first deleted line
|
||||
this._arr[i].afterLineNumber = fromLineNumber - 1;
|
||||
} else if (afterLineNumber > toLineNumber) {
|
||||
// The line this whitespace was after has been moved up
|
||||
// => move whitespace up
|
||||
this._arr[i].afterLineNumber -= (toLineNumber - fromLineNumber + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -102,8 +347,53 @@ export class LinesLayout {
|
||||
* @param toLineNumber The line number at which the insertion ended, inclusive.
|
||||
*/
|
||||
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
|
||||
this._checkPendingChanges();
|
||||
fromLineNumber = fromLineNumber | 0;
|
||||
toLineNumber = toLineNumber | 0;
|
||||
|
||||
this._lineCount += (toLineNumber - fromLineNumber + 1);
|
||||
this._whitespaces.onLinesInserted(fromLineNumber, toLineNumber);
|
||||
for (let i = 0, len = this._arr.length; i < len; i++) {
|
||||
const afterLineNumber = this._arr[i].afterLineNumber;
|
||||
|
||||
if (fromLineNumber <= afterLineNumber) {
|
||||
this._arr[i].afterLineNumber += (toLineNumber - fromLineNumber + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sum of all the whitespaces.
|
||||
*/
|
||||
public getWhitespacesTotalHeight(): number {
|
||||
this._checkPendingChanges();
|
||||
if (this._arr.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this.getWhitespacesAccumulatedHeight(this._arr.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sum of the heights of the whitespaces at [0..index].
|
||||
* This includes the whitespace at `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.
|
||||
*/
|
||||
public getWhitespacesAccumulatedHeight(index: number): number {
|
||||
this._checkPendingChanges();
|
||||
index = index | 0;
|
||||
|
||||
let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
|
||||
if (startIndex === 0) {
|
||||
this._arr[0].prefixSum = this._arr[0].height;
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
for (let i = startIndex; i <= index; i++) {
|
||||
this._arr[i].prefixSum = this._arr[i - 1].prefixSum + this._arr[i].height;
|
||||
}
|
||||
this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
|
||||
return this._arr[index].prefixSum;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,11 +402,81 @@ export class LinesLayout {
|
||||
* @return The sum of heights for all objects.
|
||||
*/
|
||||
public getLinesTotalHeight(): number {
|
||||
let linesHeight = this._lineHeight * this._lineCount;
|
||||
let whitespacesHeight = this._whitespaces.getTotalHeight();
|
||||
this._checkPendingChanges();
|
||||
const linesHeight = this._lineHeight * this._lineCount;
|
||||
const whitespacesHeight = this.getWhitespacesTotalHeight();
|
||||
return linesHeight + whitespacesHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accumulated height of whitespaces before the given line number.
|
||||
*
|
||||
* @param lineNumber The line number
|
||||
*/
|
||||
public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
|
||||
this._checkPendingChanges();
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
|
||||
|
||||
if (lastWhitespaceBeforeLineNumber === -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.getWhitespacesAccumulatedHeight(lastWhitespaceBeforeLineNumber);
|
||||
}
|
||||
|
||||
private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
// Find the whitespace before line number
|
||||
const arr = this._arr;
|
||||
let low = 0;
|
||||
let high = arr.length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
const delta = (high - low) | 0;
|
||||
const halfDelta = (delta / 2) | 0;
|
||||
const mid = (low + halfDelta) | 0;
|
||||
|
||||
if (arr[mid].afterLineNumber < lineNumber) {
|
||||
if (mid + 1 >= arr.length || arr[mid + 1].afterLineNumber >= lineNumber) {
|
||||
return mid;
|
||||
} else {
|
||||
low = (mid + 1) | 0;
|
||||
}
|
||||
} else {
|
||||
high = (mid - 1) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
const lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
|
||||
const firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
|
||||
|
||||
if (firstWhitespaceAfterLineNumber < this._arr.length) {
|
||||
return firstWhitespaceAfterLineNumber;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.
|
||||
* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.
|
||||
*/
|
||||
public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {
|
||||
this._checkPendingChanges();
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
return this._findFirstWhitespaceAfterLineNumber(lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the vertical offset (the sum of heights for all objects above) a certain line number.
|
||||
*
|
||||
@@ -124,6 +484,7 @@ export class LinesLayout {
|
||||
* @return The sum of heights for all objects above `lineNumber`.
|
||||
*/
|
||||
public getVerticalOffsetForLineNumber(lineNumber: number): number {
|
||||
this._checkPendingChanges();
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
let previousLinesHeight: number;
|
||||
@@ -133,36 +494,40 @@ export class LinesLayout {
|
||||
previousLinesHeight = 0;
|
||||
}
|
||||
|
||||
let previousWhitespacesHeight = this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
|
||||
const previousWhitespacesHeight = this.getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber);
|
||||
|
||||
return previousLinesHeight + previousWhitespacesHeight;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the accumulated height of whitespaces before the given line number.
|
||||
*
|
||||
* @param lineNumber The line number
|
||||
*/
|
||||
public getWhitespaceAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
|
||||
return this._whitespaces.getAccumulatedHeightBeforeLineNumber(lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if there is any whitespace in the document.
|
||||
*/
|
||||
public hasWhitespace(): boolean {
|
||||
return this._whitespaces.getCount() > 0;
|
||||
this._checkPendingChanges();
|
||||
return this.getWhitespacesCount() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum min width for all whitespaces.
|
||||
*/
|
||||
public getWhitespaceMinWidth(): number {
|
||||
return this._whitespaces.getMinWidth();
|
||||
this._checkPendingChanges();
|
||||
if (this._minWidth === -1) {
|
||||
let minWidth = 0;
|
||||
for (let i = 0, len = this._arr.length; i < len; i++) {
|
||||
minWidth = Math.max(minWidth, this._arr[i].minWidth);
|
||||
}
|
||||
this._minWidth = minWidth;
|
||||
}
|
||||
return this._minWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if `verticalOffset` is below all lines.
|
||||
*/
|
||||
public isAfterLines(verticalOffset: number): boolean {
|
||||
let totalHeight = this.getLinesTotalHeight();
|
||||
this._checkPendingChanges();
|
||||
const totalHeight = this.getLinesTotalHeight();
|
||||
return verticalOffset > totalHeight;
|
||||
}
|
||||
|
||||
@@ -175,6 +540,7 @@ export class LinesLayout {
|
||||
* @return The line number at or after vertical offset `verticalOffset`.
|
||||
*/
|
||||
public getLineNumberAtOrAfterVerticalOffset(verticalOffset: number): number {
|
||||
this._checkPendingChanges();
|
||||
verticalOffset = verticalOffset | 0;
|
||||
|
||||
if (verticalOffset < 0) {
|
||||
@@ -187,9 +553,9 @@ export class LinesLayout {
|
||||
let maxLineNumber = linesCount;
|
||||
|
||||
while (minLineNumber < maxLineNumber) {
|
||||
let midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
|
||||
const midLineNumber = ((minLineNumber + maxLineNumber) / 2) | 0;
|
||||
|
||||
let midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;
|
||||
const midLineNumberVerticalOffset = this.getVerticalOffsetForLineNumber(midLineNumber) | 0;
|
||||
|
||||
if (verticalOffset >= midLineNumberVerticalOffset + lineHeight) {
|
||||
// vertical offset is after mid line number
|
||||
@@ -218,6 +584,7 @@ export class LinesLayout {
|
||||
* @return A structure describing the lines positioned between `verticalOffset1` and `verticalOffset2`.
|
||||
*/
|
||||
public getLinesViewportData(verticalOffset1: number, verticalOffset2: number): IPartialViewLinesViewportData {
|
||||
this._checkPendingChanges();
|
||||
verticalOffset1 = verticalOffset1 | 0;
|
||||
verticalOffset2 = verticalOffset2 | 0;
|
||||
const lineHeight = this._lineHeight;
|
||||
@@ -230,8 +597,8 @@ export class LinesLayout {
|
||||
let endLineNumber = this._lineCount | 0;
|
||||
|
||||
// Also keep track of what whitespace we've got
|
||||
let whitespaceIndex = this._whitespaces.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
|
||||
const whitespaceCount = this._whitespaces.getCount() | 0;
|
||||
let whitespaceIndex = this.getFirstWhitespaceIndexAfterLineNumber(startLineNumber) | 0;
|
||||
const whitespaceCount = this.getWhitespacesCount() | 0;
|
||||
let currentWhitespaceHeight: number;
|
||||
let currentWhitespaceAfterLineNumber: number;
|
||||
|
||||
@@ -240,8 +607,8 @@ export class LinesLayout {
|
||||
currentWhitespaceAfterLineNumber = endLineNumber + 1;
|
||||
currentWhitespaceHeight = 0;
|
||||
} else {
|
||||
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
}
|
||||
|
||||
let currentVerticalOffset = startLineNumberVerticalOffset;
|
||||
@@ -258,7 +625,7 @@ export class LinesLayout {
|
||||
currentLineRelativeOffset -= bigNumbersDelta;
|
||||
}
|
||||
|
||||
let linesOffsets: number[] = [];
|
||||
const linesOffsets: number[] = [];
|
||||
|
||||
const verticalCenter = verticalOffset1 + (verticalOffset2 - verticalOffset1) / 2;
|
||||
let centeredLineNumber = -1;
|
||||
@@ -267,8 +634,8 @@ export class LinesLayout {
|
||||
for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) {
|
||||
|
||||
if (centeredLineNumber === -1) {
|
||||
let currentLineTop = currentVerticalOffset;
|
||||
let currentLineBottom = currentVerticalOffset + lineHeight;
|
||||
const currentLineTop = currentVerticalOffset;
|
||||
const currentLineBottom = currentVerticalOffset + lineHeight;
|
||||
if ((currentLineTop <= verticalCenter && verticalCenter < currentLineBottom) || currentLineTop > verticalCenter) {
|
||||
centeredLineNumber = lineNumber;
|
||||
}
|
||||
@@ -291,8 +658,8 @@ export class LinesLayout {
|
||||
if (whitespaceIndex >= whitespaceCount) {
|
||||
currentWhitespaceAfterLineNumber = endLineNumber + 1;
|
||||
} else {
|
||||
currentWhitespaceAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
currentWhitespaceHeight = this.getHeightForWhitespaceIndex(whitespaceIndex) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -335,9 +702,10 @@ export class LinesLayout {
|
||||
}
|
||||
|
||||
public getVerticalOffsetForWhitespaceIndex(whitespaceIndex: number): number {
|
||||
this._checkPendingChanges();
|
||||
whitespaceIndex = whitespaceIndex | 0;
|
||||
|
||||
let afterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
|
||||
const afterLineNumber = this.getAfterLineNumberForWhitespaceIndex(whitespaceIndex);
|
||||
|
||||
let previousLinesHeight: number;
|
||||
if (afterLineNumber >= 1) {
|
||||
@@ -348,7 +716,7 @@ export class LinesLayout {
|
||||
|
||||
let previousWhitespacesHeight: number;
|
||||
if (whitespaceIndex > 0) {
|
||||
previousWhitespacesHeight = this._whitespaces.getAccumulatedHeight(whitespaceIndex - 1);
|
||||
previousWhitespacesHeight = this.getWhitespacesAccumulatedHeight(whitespaceIndex - 1);
|
||||
} else {
|
||||
previousWhitespacesHeight = 0;
|
||||
}
|
||||
@@ -356,30 +724,28 @@ export class LinesLayout {
|
||||
}
|
||||
|
||||
public getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset: number): number {
|
||||
this._checkPendingChanges();
|
||||
verticalOffset = verticalOffset | 0;
|
||||
|
||||
let midWhitespaceIndex: number,
|
||||
minWhitespaceIndex = 0,
|
||||
maxWhitespaceIndex = this._whitespaces.getCount() - 1,
|
||||
midWhitespaceVerticalOffset: number,
|
||||
midWhitespaceHeight: number;
|
||||
let minWhitespaceIndex = 0;
|
||||
let maxWhitespaceIndex = this.getWhitespacesCount() - 1;
|
||||
|
||||
if (maxWhitespaceIndex < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Special case: nothing to be found
|
||||
let maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
|
||||
let maxWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(maxWhitespaceIndex);
|
||||
const maxWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(maxWhitespaceIndex);
|
||||
const maxWhitespaceHeight = this.getHeightForWhitespaceIndex(maxWhitespaceIndex);
|
||||
if (verticalOffset >= maxWhitespaceVerticalOffset + maxWhitespaceHeight) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (minWhitespaceIndex < maxWhitespaceIndex) {
|
||||
midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
|
||||
const midWhitespaceIndex = Math.floor((minWhitespaceIndex + maxWhitespaceIndex) / 2);
|
||||
|
||||
midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
|
||||
midWhitespaceHeight = this._whitespaces.getHeightForWhitespaceIndex(midWhitespaceIndex);
|
||||
const midWhitespaceVerticalOffset = this.getVerticalOffsetForWhitespaceIndex(midWhitespaceIndex);
|
||||
const midWhitespaceHeight = this.getHeightForWhitespaceIndex(midWhitespaceIndex);
|
||||
|
||||
if (verticalOffset >= midWhitespaceVerticalOffset + midWhitespaceHeight) {
|
||||
// vertical offset is after whitespace
|
||||
@@ -402,27 +768,28 @@ export class LinesLayout {
|
||||
* @return Precisely the whitespace that is layouted at `verticaloffset` or null.
|
||||
*/
|
||||
public getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null {
|
||||
this._checkPendingChanges();
|
||||
verticalOffset = verticalOffset | 0;
|
||||
|
||||
let candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
|
||||
const candidateIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset);
|
||||
|
||||
if (candidateIndex < 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (candidateIndex >= this._whitespaces.getCount()) {
|
||||
if (candidateIndex >= this.getWhitespacesCount()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
|
||||
const candidateTop = this.getVerticalOffsetForWhitespaceIndex(candidateIndex);
|
||||
|
||||
if (candidateTop > verticalOffset) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let candidateHeight = this._whitespaces.getHeightForWhitespaceIndex(candidateIndex);
|
||||
let candidateId = this._whitespaces.getIdForWhitespaceIndex(candidateIndex);
|
||||
let candidateAfterLineNumber = this._whitespaces.getAfterLineNumberForWhitespaceIndex(candidateIndex);
|
||||
const candidateHeight = this.getHeightForWhitespaceIndex(candidateIndex);
|
||||
const candidateId = this.getIdForWhitespaceIndex(candidateIndex);
|
||||
const candidateAfterLineNumber = this.getAfterLineNumberForWhitespaceIndex(candidateIndex);
|
||||
|
||||
return {
|
||||
id: candidateId,
|
||||
@@ -440,11 +807,12 @@ export class LinesLayout {
|
||||
* @return An array with all the whitespaces in the viewport. If no whitespace is in viewport, the array is empty.
|
||||
*/
|
||||
public getWhitespaceViewportData(verticalOffset1: number, verticalOffset2: number): IViewWhitespaceViewportData[] {
|
||||
this._checkPendingChanges();
|
||||
verticalOffset1 = verticalOffset1 | 0;
|
||||
verticalOffset2 = verticalOffset2 | 0;
|
||||
|
||||
let startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
|
||||
let endIndex = this._whitespaces.getCount() - 1;
|
||||
const startIndex = this.getWhitespaceIndexAtOrAfterVerticallOffset(verticalOffset1);
|
||||
const endIndex = this.getWhitespacesCount() - 1;
|
||||
|
||||
if (startIndex < 0) {
|
||||
return [];
|
||||
@@ -452,15 +820,15 @@ export class LinesLayout {
|
||||
|
||||
let result: IViewWhitespaceViewportData[] = [];
|
||||
for (let i = startIndex; i <= endIndex; i++) {
|
||||
let top = this.getVerticalOffsetForWhitespaceIndex(i);
|
||||
let height = this._whitespaces.getHeightForWhitespaceIndex(i);
|
||||
const top = this.getVerticalOffsetForWhitespaceIndex(i);
|
||||
const height = this.getHeightForWhitespaceIndex(i);
|
||||
if (top >= verticalOffset2) {
|
||||
break;
|
||||
}
|
||||
|
||||
result.push({
|
||||
id: this._whitespaces.getIdForWhitespaceIndex(i),
|
||||
afterLineNumber: this._whitespaces.getAfterLineNumberForWhitespaceIndex(i),
|
||||
id: this.getIdForWhitespaceIndex(i),
|
||||
afterLineNumber: this.getAfterLineNumberForWhitespaceIndex(i),
|
||||
verticalOffset: top,
|
||||
height: height
|
||||
});
|
||||
@@ -473,6 +841,54 @@ export class LinesLayout {
|
||||
* Get all whitespaces.
|
||||
*/
|
||||
public getWhitespaces(): IEditorWhitespace[] {
|
||||
return this._whitespaces.getWhitespaces(this._lineHeight);
|
||||
this._checkPendingChanges();
|
||||
return this._arr.slice(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of whitespaces.
|
||||
*/
|
||||
public getWhitespacesCount(): number {
|
||||
this._checkPendingChanges();
|
||||
return this._arr.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `id` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `id` of whitespace at `index`.
|
||||
*/
|
||||
public getIdForWhitespaceIndex(index: number): string {
|
||||
this._checkPendingChanges();
|
||||
index = index | 0;
|
||||
|
||||
return this._arr[index].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `afterLineNumber` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `afterLineNumber` of whitespace at `index`.
|
||||
*/
|
||||
public getAfterLineNumberForWhitespaceIndex(index: number): number {
|
||||
this._checkPendingChanges();
|
||||
index = index | 0;
|
||||
|
||||
return this._arr[index].afterLineNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `height` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `height` of whitespace at `index`.
|
||||
*/
|
||||
public getHeightForWhitespaceIndex(index: number): number {
|
||||
this._checkPendingChanges();
|
||||
index = index | 0;
|
||||
|
||||
return this._arr[index].height;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,11 @@
|
||||
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IScrollDimensions, IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
|
||||
|
||||
const SMOOTH_SCROLLING_TIME = 125;
|
||||
@@ -65,15 +64,23 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
}
|
||||
if (e.hasChanged(EditorOption.layoutInfo)) {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
const width = layoutInfo.contentWidth;
|
||||
const height = layoutInfo.contentHeight;
|
||||
const scrollDimensions = this.scrollable.getScrollDimensions();
|
||||
const scrollWidth = scrollDimensions.scrollWidth;
|
||||
const scrollHeight = this._getTotalHeight(width, height, scrollWidth);
|
||||
|
||||
this.scrollable.setScrollDimensions({
|
||||
width: layoutInfo.contentWidth,
|
||||
height: layoutInfo.contentHeight
|
||||
width: width,
|
||||
height: height,
|
||||
scrollHeight: scrollHeight
|
||||
});
|
||||
} else {
|
||||
this._updateHeight();
|
||||
}
|
||||
if (e.hasChanged(EditorOption.smoothScrolling)) {
|
||||
this._configureSmoothScrollDuration();
|
||||
}
|
||||
this._updateHeight();
|
||||
}
|
||||
public onFlushed(lineCount: number): void {
|
||||
this._linesLayout.onFlushed(lineCount);
|
||||
@@ -87,37 +94,41 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
|
||||
// ---- end view event handlers
|
||||
|
||||
private _getHorizontalScrollbarHeight(scrollDimensions: IScrollDimensions): number {
|
||||
private _getHorizontalScrollbarHeight(width: number, scrollWidth: number): number {
|
||||
const options = this._configuration.options;
|
||||
const scrollbar = options.get(EditorOption.scrollbar);
|
||||
if (scrollbar.horizontal === ScrollbarVisibility.Hidden) {
|
||||
// horizontal scrollbar not visible
|
||||
return 0;
|
||||
}
|
||||
if (scrollDimensions.width >= scrollDimensions.scrollWidth) {
|
||||
if (width >= scrollWidth) {
|
||||
// horizontal scrollbar not visible
|
||||
return 0;
|
||||
}
|
||||
return scrollbar.horizontalScrollbarSize;
|
||||
}
|
||||
|
||||
private _getTotalHeight(): number {
|
||||
private _getTotalHeight(width: number, height: number, scrollWidth: number): number {
|
||||
const options = this._configuration.options;
|
||||
const scrollDimensions = this.scrollable.getScrollDimensions();
|
||||
|
||||
let result = this._linesLayout.getLinesTotalHeight();
|
||||
if (options.get(EditorOption.scrollBeyondLastLine)) {
|
||||
result += scrollDimensions.height - options.get(EditorOption.lineHeight);
|
||||
result += height - options.get(EditorOption.lineHeight);
|
||||
} else {
|
||||
result += this._getHorizontalScrollbarHeight(scrollDimensions);
|
||||
result += this._getHorizontalScrollbarHeight(width, scrollWidth);
|
||||
}
|
||||
|
||||
return Math.max(scrollDimensions.height, result);
|
||||
return Math.max(height, result);
|
||||
}
|
||||
|
||||
private _updateHeight(): void {
|
||||
const scrollDimensions = this.scrollable.getScrollDimensions();
|
||||
const width = scrollDimensions.width;
|
||||
const height = scrollDimensions.height;
|
||||
const scrollWidth = scrollDimensions.scrollWidth;
|
||||
const scrollHeight = this._getTotalHeight(width, height, scrollWidth);
|
||||
this.scrollable.setScrollDimensions({
|
||||
scrollHeight: this._getTotalHeight()
|
||||
scrollHeight: scrollHeight
|
||||
});
|
||||
}
|
||||
|
||||
@@ -182,15 +193,8 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
}
|
||||
|
||||
// ---- IVerticalLayoutProvider
|
||||
|
||||
public addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string {
|
||||
return this._linesLayout.insertWhitespace(afterLineNumber, ordinal, height, minWidth);
|
||||
}
|
||||
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
return this._linesLayout.changeWhitespace(id, newAfterLineNumber, newHeight);
|
||||
}
|
||||
public removeWhitespace(id: string): boolean {
|
||||
return this._linesLayout.removeWhitespace(id);
|
||||
public changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T {
|
||||
return this._linesLayout.changeWhitespace(callback);
|
||||
}
|
||||
public getVerticalOffsetForLineNumber(lineNumber: number): number {
|
||||
return this._linesLayout.getVerticalOffsetForLineNumber(lineNumber);
|
||||
|
||||
@@ -783,7 +783,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
|
||||
if (!fontIsMonospace) {
|
||||
const partIsOnlyWhitespace = (partType === 'vs-whitespace');
|
||||
if (partIsOnlyWhitespace || !containsForeignElements) {
|
||||
sb.appendASCIIString(' style="width:');
|
||||
sb.appendASCIIString(' style="display:inline-block;width:');
|
||||
sb.appendASCIIString(String(spaceWidth * partContentCnt));
|
||||
sb.appendASCIIString('px"');
|
||||
}
|
||||
|
||||
@@ -1,493 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export interface IEditorWhitespace {
|
||||
readonly id: string;
|
||||
readonly afterLineNumber: number;
|
||||
readonly heightInLines: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represent whitespaces in between lines and provide fast CRUD management methods.
|
||||
* The whitespaces are sorted ascending by `afterLineNumber`.
|
||||
*/
|
||||
export class WhitespaceComputer {
|
||||
|
||||
private static INSTANCE_COUNT = 0;
|
||||
|
||||
private readonly _instanceId: string;
|
||||
|
||||
/**
|
||||
* heights[i] is the height in pixels for whitespace at index i
|
||||
*/
|
||||
private readonly _heights: number[];
|
||||
|
||||
/**
|
||||
* minWidths[i] is the min width in pixels for whitespace at index i
|
||||
*/
|
||||
private readonly _minWidths: number[];
|
||||
|
||||
/**
|
||||
* afterLineNumbers[i] is the line number whitespace at index i is after
|
||||
*/
|
||||
private readonly _afterLineNumbers: number[];
|
||||
|
||||
/**
|
||||
* ordinals[i] is the orinal of the whitespace at index i
|
||||
*/
|
||||
private readonly _ordinals: number[];
|
||||
|
||||
/**
|
||||
* prefixSum[i] = SUM(heights[j]), 1 <= j <= i
|
||||
*/
|
||||
private readonly _prefixSum: number[];
|
||||
|
||||
/**
|
||||
* prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted
|
||||
*/
|
||||
private _prefixSumValidIndex: number;
|
||||
|
||||
/**
|
||||
* ids[i] is the whitespace id of whitespace at index i
|
||||
*/
|
||||
private readonly _ids: string[];
|
||||
|
||||
/**
|
||||
* index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members)
|
||||
*/
|
||||
private readonly _whitespaceId2Index: {
|
||||
[id: string]: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* last whitespace id issued
|
||||
*/
|
||||
private _lastWhitespaceId: number;
|
||||
|
||||
private _minWidth: number;
|
||||
|
||||
constructor() {
|
||||
this._instanceId = strings.singleLetterHash(++WhitespaceComputer.INSTANCE_COUNT);
|
||||
this._heights = [];
|
||||
this._minWidths = [];
|
||||
this._ids = [];
|
||||
this._afterLineNumbers = [];
|
||||
this._ordinals = [];
|
||||
this._prefixSum = [];
|
||||
this._prefixSumValidIndex = -1;
|
||||
this._whitespaceId2Index = {};
|
||||
this._lastWhitespaceId = 0;
|
||||
this._minWidth = -1; /* marker for not being computed */
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the insertion index for a new value inside a sorted array of values.
|
||||
* If the value is already present in the sorted array, the insertion index will be after the already existing value.
|
||||
*/
|
||||
public static findInsertionIndex(sortedArray: number[], value: number, ordinals: number[], valueOrdinal: number): number {
|
||||
let low = 0;
|
||||
let high = sortedArray.length;
|
||||
|
||||
while (low < high) {
|
||||
let mid = ((low + high) >>> 1);
|
||||
|
||||
if (value === sortedArray[mid]) {
|
||||
if (valueOrdinal < ordinals[mid]) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
} else if (value < sortedArray[mid]) {
|
||||
high = mid;
|
||||
} else {
|
||||
low = mid + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new whitespace of a certain height after a line number.
|
||||
* The whitespace has a "sticky" characteristic.
|
||||
* Irrespective of edits above or below `afterLineNumber`, the whitespace will follow the initial line.
|
||||
*
|
||||
* @param afterLineNumber The conceptual position of this whitespace. The whitespace will follow this line as best as possible even when deleting/inserting lines above/below.
|
||||
* @param heightInPx The height of the whitespace, in pixels.
|
||||
* @return An id that can be used later to mutate or delete the whitespace
|
||||
*/
|
||||
public insertWhitespace(afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): string {
|
||||
afterLineNumber = afterLineNumber | 0;
|
||||
ordinal = ordinal | 0;
|
||||
heightInPx = heightInPx | 0;
|
||||
minWidth = minWidth | 0;
|
||||
|
||||
let id = this._instanceId + (++this._lastWhitespaceId);
|
||||
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, afterLineNumber, this._ordinals, ordinal);
|
||||
this._insertWhitespaceAtIndex(id, insertionIndex, afterLineNumber, ordinal, heightInPx, minWidth);
|
||||
this._minWidth = -1; /* marker for not being computed */
|
||||
return id;
|
||||
}
|
||||
|
||||
private _insertWhitespaceAtIndex(id: string, insertIndex: number, afterLineNumber: number, ordinal: number, heightInPx: number, minWidth: number): void {
|
||||
insertIndex = insertIndex | 0;
|
||||
afterLineNumber = afterLineNumber | 0;
|
||||
ordinal = ordinal | 0;
|
||||
heightInPx = heightInPx | 0;
|
||||
minWidth = minWidth | 0;
|
||||
|
||||
this._heights.splice(insertIndex, 0, heightInPx);
|
||||
this._minWidths.splice(insertIndex, 0, minWidth);
|
||||
this._ids.splice(insertIndex, 0, id);
|
||||
this._afterLineNumbers.splice(insertIndex, 0, afterLineNumber);
|
||||
this._ordinals.splice(insertIndex, 0, ordinal);
|
||||
this._prefixSum.splice(insertIndex, 0, 0);
|
||||
|
||||
let keys = Object.keys(this._whitespaceId2Index);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let sid = keys[i];
|
||||
let oldIndex = this._whitespaceId2Index[sid];
|
||||
if (oldIndex >= insertIndex) {
|
||||
this._whitespaceId2Index[sid] = oldIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
this._whitespaceId2Index[id] = insertIndex;
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, insertIndex - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change properties associated with a certain whitespace.
|
||||
*/
|
||||
public changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean {
|
||||
newAfterLineNumber = newAfterLineNumber | 0;
|
||||
newHeight = newHeight | 0;
|
||||
|
||||
let hasChanges = false;
|
||||
hasChanges = this.changeWhitespaceHeight(id, newHeight) || hasChanges;
|
||||
hasChanges = this.changeWhitespaceAfterLineNumber(id, newAfterLineNumber) || hasChanges;
|
||||
return hasChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the height of an existing whitespace
|
||||
*
|
||||
* @param id The whitespace to change
|
||||
* @param newHeightInPx The new height of the whitespace, in pixels
|
||||
* @return Returns true if the whitespace is found and if the new height is different than the old height
|
||||
*/
|
||||
public changeWhitespaceHeight(id: string, newHeightInPx: number): boolean {
|
||||
newHeightInPx = newHeightInPx | 0;
|
||||
|
||||
if (this._whitespaceId2Index.hasOwnProperty(id)) {
|
||||
let index = this._whitespaceId2Index[id];
|
||||
if (this._heights[index] !== newHeightInPx) {
|
||||
this._heights[index] = newHeightInPx;
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, index - 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the line number after which an existing whitespace flows.
|
||||
*
|
||||
* @param id The whitespace to change
|
||||
* @param newAfterLineNumber The new line number the whitespace will follow
|
||||
* @return Returns true if the whitespace is found and if the new line number is different than the old line number
|
||||
*/
|
||||
public changeWhitespaceAfterLineNumber(id: string, newAfterLineNumber: number): boolean {
|
||||
newAfterLineNumber = newAfterLineNumber | 0;
|
||||
|
||||
if (this._whitespaceId2Index.hasOwnProperty(id)) {
|
||||
let index = this._whitespaceId2Index[id];
|
||||
if (this._afterLineNumbers[index] !== newAfterLineNumber) {
|
||||
// `afterLineNumber` changed for this whitespace
|
||||
|
||||
// Record old ordinal
|
||||
let ordinal = this._ordinals[index];
|
||||
|
||||
// Record old height
|
||||
let heightInPx = this._heights[index];
|
||||
|
||||
// Record old min width
|
||||
let minWidth = this._minWidths[index];
|
||||
|
||||
// Since changing `afterLineNumber` can trigger a reordering, we're gonna remove this whitespace
|
||||
this.removeWhitespace(id);
|
||||
|
||||
// And add it again
|
||||
let insertionIndex = WhitespaceComputer.findInsertionIndex(this._afterLineNumbers, newAfterLineNumber, this._ordinals, ordinal);
|
||||
this._insertWhitespaceAtIndex(id, insertionIndex, newAfterLineNumber, ordinal, heightInPx, minWidth);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an existing whitespace.
|
||||
*
|
||||
* @param id The whitespace to remove
|
||||
* @return Returns true if the whitespace is found and it is removed.
|
||||
*/
|
||||
public removeWhitespace(id: string): boolean {
|
||||
if (this._whitespaceId2Index.hasOwnProperty(id)) {
|
||||
let index = this._whitespaceId2Index[id];
|
||||
delete this._whitespaceId2Index[id];
|
||||
this._removeWhitespaceAtIndex(index);
|
||||
this._minWidth = -1; /* marker for not being computed */
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private _removeWhitespaceAtIndex(removeIndex: number): void {
|
||||
removeIndex = removeIndex | 0;
|
||||
|
||||
this._heights.splice(removeIndex, 1);
|
||||
this._minWidths.splice(removeIndex, 1);
|
||||
this._ids.splice(removeIndex, 1);
|
||||
this._afterLineNumbers.splice(removeIndex, 1);
|
||||
this._ordinals.splice(removeIndex, 1);
|
||||
this._prefixSum.splice(removeIndex, 1);
|
||||
this._prefixSumValidIndex = Math.min(this._prefixSumValidIndex, removeIndex - 1);
|
||||
|
||||
let keys = Object.keys(this._whitespaceId2Index);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let sid = keys[i];
|
||||
let oldIndex = this._whitespaceId2Index[sid];
|
||||
if (oldIndex >= removeIndex) {
|
||||
this._whitespaceId2Index[sid] = oldIndex - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the computer that lines have been deleted (a continuous zone of lines).
|
||||
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
|
||||
*
|
||||
* @param fromLineNumber The line number at which the deletion started, inclusive
|
||||
* @param toLineNumber The line number at which the deletion ended, inclusive
|
||||
*/
|
||||
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
|
||||
fromLineNumber = fromLineNumber | 0;
|
||||
toLineNumber = toLineNumber | 0;
|
||||
|
||||
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
|
||||
let afterLineNumber = this._afterLineNumbers[i];
|
||||
|
||||
if (fromLineNumber <= afterLineNumber && afterLineNumber <= toLineNumber) {
|
||||
// The line this whitespace was after has been deleted
|
||||
// => move whitespace to before first deleted line
|
||||
this._afterLineNumbers[i] = fromLineNumber - 1;
|
||||
} else if (afterLineNumber > toLineNumber) {
|
||||
// The line this whitespace was after has been moved up
|
||||
// => move whitespace up
|
||||
this._afterLineNumbers[i] -= (toLineNumber - fromLineNumber + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the computer that lines have been inserted (a continuous zone of lines).
|
||||
* This gives it a chance to update `afterLineNumber` for whitespaces, giving the "sticky" characteristic.
|
||||
*
|
||||
* @param fromLineNumber The line number at which the insertion started, inclusive
|
||||
* @param toLineNumber The line number at which the insertion ended, inclusive.
|
||||
*/
|
||||
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
|
||||
fromLineNumber = fromLineNumber | 0;
|
||||
toLineNumber = toLineNumber | 0;
|
||||
|
||||
for (let i = 0, len = this._afterLineNumbers.length; i < len; i++) {
|
||||
let afterLineNumber = this._afterLineNumbers[i];
|
||||
|
||||
if (fromLineNumber <= afterLineNumber) {
|
||||
this._afterLineNumbers[i] += (toLineNumber - fromLineNumber + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the sum of all the whitespaces.
|
||||
*/
|
||||
public getTotalHeight(): number {
|
||||
if (this._heights.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
return this.getAccumulatedHeight(this._heights.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the sum of the heights of the whitespaces at [0..index].
|
||||
* This includes the whitespace at `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return The sum of the heights of all whitespaces before the one at `index`, including the one at `index`.
|
||||
*/
|
||||
public getAccumulatedHeight(index: number): number {
|
||||
index = index | 0;
|
||||
|
||||
let startIndex = Math.max(0, this._prefixSumValidIndex + 1);
|
||||
if (startIndex === 0) {
|
||||
this._prefixSum[0] = this._heights[0];
|
||||
startIndex++;
|
||||
}
|
||||
|
||||
for (let i = startIndex; i <= index; i++) {
|
||||
this._prefixSum[i] = this._prefixSum[i - 1] + this._heights[i];
|
||||
}
|
||||
this._prefixSumValidIndex = Math.max(this._prefixSumValidIndex, index);
|
||||
return this._prefixSum[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all whitespaces with `afterLineNumber` < `lineNumber` and return the sum of their heights.
|
||||
*
|
||||
* @param lineNumber The line number whitespaces should be before.
|
||||
* @return The sum of the heights of the whitespaces before `lineNumber`.
|
||||
*/
|
||||
public getAccumulatedHeightBeforeLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
|
||||
|
||||
if (lastWhitespaceBeforeLineNumber === -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return this.getAccumulatedHeight(lastWhitespaceBeforeLineNumber);
|
||||
}
|
||||
|
||||
private _findLastWhitespaceBeforeLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
// Find the whitespace before line number
|
||||
let afterLineNumbers = this._afterLineNumbers;
|
||||
let low = 0;
|
||||
let high = afterLineNumbers.length - 1;
|
||||
|
||||
while (low <= high) {
|
||||
let delta = (high - low) | 0;
|
||||
let halfDelta = (delta / 2) | 0;
|
||||
let mid = (low + halfDelta) | 0;
|
||||
|
||||
if (afterLineNumbers[mid] < lineNumber) {
|
||||
if (mid + 1 >= afterLineNumbers.length || afterLineNumbers[mid + 1] >= lineNumber) {
|
||||
return mid;
|
||||
} else {
|
||||
low = (mid + 1) | 0;
|
||||
}
|
||||
} else {
|
||||
high = (mid - 1) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private _findFirstWhitespaceAfterLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
let lastWhitespaceBeforeLineNumber = this._findLastWhitespaceBeforeLineNumber(lineNumber);
|
||||
let firstWhitespaceAfterLineNumber = lastWhitespaceBeforeLineNumber + 1;
|
||||
|
||||
if (firstWhitespaceAfterLineNumber < this._heights.length) {
|
||||
return firstWhitespaceAfterLineNumber;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the index of the first whitespace which has `afterLineNumber` >= `lineNumber`.
|
||||
* @return The index of the first whitespace with `afterLineNumber` >= `lineNumber` or -1 if no whitespace is found.
|
||||
*/
|
||||
public getFirstWhitespaceIndexAfterLineNumber(lineNumber: number): number {
|
||||
lineNumber = lineNumber | 0;
|
||||
|
||||
return this._findFirstWhitespaceAfterLineNumber(lineNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
* The number of whitespaces.
|
||||
*/
|
||||
public getCount(): number {
|
||||
return this._heights.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum min width for all whitespaces.
|
||||
*/
|
||||
public getMinWidth(): number {
|
||||
if (this._minWidth === -1) {
|
||||
let minWidth = 0;
|
||||
for (let i = 0, len = this._minWidths.length; i < len; i++) {
|
||||
minWidth = Math.max(minWidth, this._minWidths[i]);
|
||||
}
|
||||
this._minWidth = minWidth;
|
||||
}
|
||||
return this._minWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `afterLineNumber` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `afterLineNumber` of whitespace at `index`.
|
||||
*/
|
||||
public getAfterLineNumberForWhitespaceIndex(index: number): number {
|
||||
index = index | 0;
|
||||
|
||||
return this._afterLineNumbers[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `id` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `id` of whitespace at `index`.
|
||||
*/
|
||||
public getIdForWhitespaceIndex(index: number): string {
|
||||
index = index | 0;
|
||||
|
||||
return this._ids[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the `height` for whitespace at index `index`.
|
||||
*
|
||||
* @param index The index of the whitespace.
|
||||
* @return `height` of whitespace at `index`.
|
||||
*/
|
||||
public getHeightForWhitespaceIndex(index: number): number {
|
||||
index = index | 0;
|
||||
|
||||
return this._heights[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all whitespaces.
|
||||
*/
|
||||
public getWhitespaces(deviceLineHeight: number): IEditorWhitespace[] {
|
||||
deviceLineHeight = deviceLineHeight | 0;
|
||||
|
||||
let result: IEditorWhitespace[] = [];
|
||||
for (let i = 0; i < this._heights.length; i++) {
|
||||
result.push({
|
||||
id: this._ids[i],
|
||||
afterLineNumber: this._afterLineNumbers[i],
|
||||
heightInLines: this._heights[i] / deviceLineHeight
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -129,9 +129,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
|
||||
}
|
||||
|
||||
public convertModelRangeToViewRange(modelRange: Range): Range {
|
||||
let start = this._lines.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn);
|
||||
let end = this._lines.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn);
|
||||
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
return this._lines.convertModelRangeToViewRange(modelRange);
|
||||
}
|
||||
|
||||
public modelPositionIsVisible(modelPosition: Position): boolean {
|
||||
@@ -737,9 +735,9 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position {
|
||||
this._ensureValidState();
|
||||
|
||||
let validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
|
||||
let inputLineNumber = validPosition.lineNumber;
|
||||
let inputColumn = validPosition.column;
|
||||
const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
|
||||
const inputLineNumber = validPosition.lineNumber;
|
||||
const inputColumn = validPosition.column;
|
||||
|
||||
let lineIndex = inputLineNumber - 1, lineIndexChanged = false;
|
||||
while (lineIndex > 0 && !this.lines[lineIndex].isVisible()) {
|
||||
@@ -751,7 +749,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
// console.log('in -> out ' + inputLineNumber + ',' + inputColumn + ' ===> ' + 1 + ',' + 1);
|
||||
return new Position(1, 1);
|
||||
}
|
||||
let deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
|
||||
const deltaLineNumber = 1 + (lineIndex === 0 ? 0 : this.prefixSumComputer.getAccumulatedValue(lineIndex - 1));
|
||||
|
||||
let r: Position;
|
||||
if (lineIndexChanged) {
|
||||
@@ -764,6 +762,19 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
return r;
|
||||
}
|
||||
|
||||
public convertModelRangeToViewRange(modelRange: Range): Range {
|
||||
let start = this.convertModelPositionToViewPosition(modelRange.startLineNumber, modelRange.startColumn);
|
||||
let end = this.convertModelPositionToViewPosition(modelRange.endLineNumber, modelRange.endColumn);
|
||||
if (modelRange.startLineNumber === modelRange.endLineNumber && start.lineNumber !== end.lineNumber) {
|
||||
// This is a single line range that ends up taking more lines due to wrapping
|
||||
if (end.column === this.getViewLineMinColumn(end.lineNumber)) {
|
||||
// the end column lands on the first column of the next line
|
||||
return new Range(start.lineNumber, start.column, end.lineNumber - 1, this.getViewLineMaxColumn(end.lineNumber - 1));
|
||||
}
|
||||
}
|
||||
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
|
||||
}
|
||||
|
||||
private _getViewLineNumberForModelPosition(inputLineNumber: number, inputColumn: number): number {
|
||||
let lineIndex = inputLineNumber - 1;
|
||||
if (this.lines[lineIndex].isVisible()) {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { INewScrollPosition } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model';
|
||||
import { IViewEventListener } from 'vs/editor/common/view/viewEvents';
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
export interface IViewWhitespaceViewportData {
|
||||
@@ -69,20 +69,8 @@ export interface IViewLayout {
|
||||
getWhitespaceAtVerticalOffset(verticalOffset: number): IViewWhitespaceViewportData | null;
|
||||
|
||||
// --------------- Begin vertical whitespace management
|
||||
changeWhitespace<T>(callback: (accessor: IWhitespaceChangeAccessor) => T): T;
|
||||
|
||||
/**
|
||||
* Reserve rendering space.
|
||||
* @return an identifier that can be later used to remove or change the whitespace.
|
||||
*/
|
||||
addWhitespace(afterLineNumber: number, ordinal: number, height: number, minWidth: number): string;
|
||||
/**
|
||||
* Change the properties of a whitespace.
|
||||
*/
|
||||
changeWhitespace(id: string, newAfterLineNumber: number, newHeight: number): boolean;
|
||||
/**
|
||||
* Remove rendering space
|
||||
*/
|
||||
removeWhitespace(id: string): boolean;
|
||||
/**
|
||||
* Get the layout information for whitespaces currently in the viewport
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user