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:
Anthony Dresser
2019-12-04 19:28:22 -08:00
committed by GitHub
parent a8818ab0df
commit f5ce7fb2a5
1507 changed files with 42813 additions and 27370 deletions

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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()),

View File

@@ -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;

View File

@@ -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;

View File

@@ -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.
*/

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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) {

View File

@@ -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];
}
}

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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;

View File

@@ -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
*/

View File

@@ -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
*/

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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 };
}
}
}
}

View File

@@ -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 ? '&nbsp;' : ' ';
partContent += useNbsp ? '&#160;' : ' ';
insertSpacesCount--;
}
break;
@@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
break;
case CharCode.Space:
partContent += useNbsp ? '&nbsp;' : ' ';
partContent += useNbsp ? '&#160;' : ' ';
break;
default:

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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,
};

View File

@@ -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);
}
}

View File

@@ -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;
}

View File

@@ -233,7 +233,8 @@ export enum OverviewRulerLane {
* Position in the minimap to render the decoration.
*/
export enum MinimapPosition {
Inline = 1
Inline = 1,
Gutter = 2
}
/**

View File

@@ -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; }`);

View File

@@ -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[]
) {
}
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -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"');
}

View File

@@ -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;
}
}

View File

@@ -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()) {

View File

@@ -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
*/