Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998 (#7880)

* Merge from vscode c58aaab8a1cc22a7139b761166a0d4f37d41e998

* fix pipelines

* fix strict-null-checks

* add missing files
This commit is contained in:
Anthony Dresser
2019-10-21 22:12:22 -07:00
committed by GitHub
parent 7c9be74970
commit 1e22f47304
913 changed files with 18898 additions and 16536 deletions

View File

@@ -36,6 +36,27 @@ export class ReplaceCommand implements ICommand {
}
}
export class ReplaceCommandThatSelectsText implements ICommand {
private readonly _range: Range;
private readonly _text: string;
constructor(range: Range, text: string) {
this._range = range;
this._text = text;
}
public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
builder.addTrackedEditOperation(this._range, this._text);
}
public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection {
const inverseEditOperations = helper.getInverseEditOperations();
const srcRange = inverseEditOperations[0].range;
return new Selection(srcRange.startLineNumber, srcRange.startColumn, srcRange.endLineNumber, srcRange.endColumn);
}
}
export class ReplaceCommandWithoutChangingPosition implements ICommand {
private readonly _range: Range;

View File

@@ -207,6 +207,17 @@ function migrateOptions(options: IEditorOptions): void {
enabled: false
};
}
const parameterHints = options.parameterHints;
if (<any>parameterHints === true) {
options.parameterHints = {
enabled: true
};
} else if (<any>parameterHints === false) {
options.parameterHints = {
enabled: false
};
}
}
function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions {
@@ -463,6 +474,11 @@ const editorConfiguration: IConfigurationNode = {
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,
description: nls.localize('maxComputationTime', "Timeout in milliseconds after which diff computation is cancelled. Use 0 for no timeout.")
},
'diffEditor.renderSideBySide': {
type: 'boolean',
default: true,

View File

@@ -7,7 +7,7 @@ import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
import { Constants } from 'vs/editor/common/core/uint';
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';
@@ -178,7 +178,7 @@ export interface IEditorOptions {
* Enable font ligatures.
* Defaults to false.
*/
fontLigatures?: boolean;
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.
@@ -538,6 +538,11 @@ export interface IDiffEditorOptions extends IEditorOptions {
* Defaults to true.
*/
renderSideBySide?: boolean;
/**
* Timeout in milliseconds after which diff computation is cancelled.
* Defaults to 5000.
*/
maxComputationTime?: number;
/**
* Compute the diff by ignoring leading/trailing whitespace
* Defaults to true.
@@ -647,6 +652,9 @@ export interface IEditorOption<K1 extends EditorOption, V> {
type PossibleKeyName0<V> = { [K in keyof IEditorOptions]: IEditorOptions[K] extends V | undefined ? K : never }[keyof IEditorOptions];
type PossibleKeyName<V> = NonNullable<PossibleKeyName0<V>>;
/**
* @internal
*/
abstract class BaseEditorOption<K1 extends EditorOption, V> implements IEditorOption<K1, V> {
public readonly id: K1;
@@ -1050,7 +1058,7 @@ function _cursorStyleFromString(cursorStyle: 'line' | 'block' | 'underline' | 'l
class EditorClassName extends ComputedEditorOption<EditorOption.editorClassName, string> {
constructor() {
super(EditorOption.editorClassName, [EditorOption.mouseStyle, EditorOption.fontLigatures, EditorOption.extraEditorClassName]);
super(EditorOption.editorClassName, [EditorOption.mouseStyle, EditorOption.extraEditorClassName]);
}
public compute(env: IEnvironmentalOptions, options: IComputedEditorOptions, _: string): string {
@@ -1061,9 +1069,6 @@ class EditorClassName extends ComputedEditorOption<EditorOption.editorClassName,
if (env.extraEditorClassName) {
className += ' ' + env.extraEditorClassName;
}
if (options.get(EditorOption.fontLigatures)) {
className += ' enable-ligatures';
}
if (options.get(EditorOption.mouseStyle) === 'default') {
className += ' mouse-default';
} else if (options.get(EditorOption.mouseStyle) === 'copy') {
@@ -1173,6 +1178,57 @@ class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions>
//#endregion
//#region fontLigatures
/**
* @internal
*/
export class EditorFontLigatures extends BaseEditorOption<EditorOption.fontLigatures, string> {
public static OFF = '"liga" off, "calt" off';
public static ON = '"liga" on, "calt" on';
constructor() {
super(
EditorOption.fontLigatures, 'fontLigatures', EditorFontLigatures.OFF,
{
anyOf: [
{
type: 'boolean',
description: nls.localize('fontLigatures', "Enables/Disables font ligatures."),
},
{
type: 'string',
description: nls.localize('fontFeatureSettings', "Explicit font-feature-settings.")
}
],
default: false
}
);
}
public validate(input: any): string {
if (typeof input === 'undefined') {
return this.defaultValue;
}
if (typeof input === 'string') {
if (input === 'false') {
return EditorFontLigatures.OFF;
}
if (input === 'true') {
return EditorFontLigatures.ON;
}
return input;
}
if (Boolean(input)) {
return EditorFontLigatures.ON;
}
return EditorFontLigatures.OFF;
}
}
//#endregion
//#region fontInfo
class EditorFontInfo extends ComputedEditorOption<EditorOption.fontInfo, FontInfo> {
@@ -1364,10 +1420,8 @@ export interface OverviewRulerPosition {
export const enum RenderMinimap {
None = 0,
Small = 1,
Large = 2,
SmallBlocks = 3,
LargeBlocks = 4,
Text = 1,
Blocks = 2,
}
/**
@@ -1523,6 +1577,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
const minimapEnabled = minimap.enabled;
const minimapSide = minimap.side;
const minimapRenderCharacters = minimap.renderCharacters;
const minimapScale = (pixelRatio >= 2 ? Math.round(minimap.scale * 2) : minimap.scale);
const minimapMaxColumn = minimap.maxColumn | 0;
const scrollbar = options.get(EditorOption.scrollbar);
@@ -1573,14 +1628,10 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
renderMinimap = RenderMinimap.None;
contentWidth = remainingWidth;
} else {
let minimapCharWidth: number;
if (pixelRatio >= 2) {
renderMinimap = minimapRenderCharacters ? RenderMinimap.Large : RenderMinimap.LargeBlocks;
minimapCharWidth = 2 / pixelRatio;
} else {
renderMinimap = minimapRenderCharacters ? RenderMinimap.Small : RenderMinimap.SmallBlocks;
minimapCharWidth = 1 / pixelRatio;
}
// The minimapScale is also the pixel width of each character. Adjust
// for the pixel ratio of the screen.
const minimapCharWidth = minimapScale / pixelRatio;
renderMinimap = minimapRenderCharacters ? RenderMinimap.Text : RenderMinimap.Blocks;
// Given:
// (leaving 2px for the cursor to have space after the last character)
@@ -1756,6 +1807,11 @@ export interface IEditorMinimapOptions {
* Defaults to 120.
*/
maxColumn?: number;
/**
* Relative size of the font in the minimap. Defaults to 1.
*/
scale?: number;
}
export type EditorMinimapOptions = Readonly<Required<IEditorMinimapOptions>>;
@@ -1769,6 +1825,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
showSlider: 'mouseover',
renderCharacters: true,
maxColumn: 120,
scale: 1,
};
super(
EditorOption.minimap, 'minimap', defaults,
@@ -1788,7 +1845,14 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
type: 'string',
enum: ['always', 'mouseover'],
default: defaults.showSlider,
description: nls.localize('minimap.showSlider', "Controls whether the minimap slider is automatically hidden.")
description: nls.localize('minimap.showSlider', "Controls when the minimap slider is shown.")
},
'editor.minimap.scale': {
type: 'number',
default: defaults.scale,
minimum: 1,
maximum: 3,
description: nls.localize('minimap.scale', "Scale of content drawn in the minimap.")
},
'editor.minimap.renderCharacters': {
type: 'boolean',
@@ -1814,6 +1878,7 @@ class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimap
side: EditorStringEnumOption.stringSet<'right' | 'left'>(input.side, this.defaultValue.side, ['right', 'left']),
showSlider: EditorStringEnumOption.stringSet<'always' | 'mouseover'>(input.showSlider, this.defaultValue.showSlider, ['always', 'mouseover']),
renderCharacters: EditorBooleanOption.boolean(input.renderCharacters, this.defaultValue.renderCharacters),
scale: EditorIntOption.clampedInt(input.scale, 1, 1, 3),
maxColumn: EditorIntOption.clampedInt(input.maxColumn, this.defaultValue.maxColumn, 1, 10000),
};
}
@@ -1960,6 +2025,7 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
description: nls.localize('quickSuggestions', "Controls whether suggestions should automatically show up while typing.")
}
);
this.defaultValue = defaults;
}
public validate(_input: any): ValidQuickSuggestionsOptions {
@@ -2226,6 +2292,10 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
* Configuration options for editor suggest widget
*/
export interface ISuggestOptions {
/**
* Overwrite word ends on accept. Default to false.
*/
overwriteOnAccept?: boolean;
/**
* Enable graceful matching. Defaults to true.
*/
@@ -2262,6 +2332,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
constructor() {
const defaults: InternalSuggestOptions = {
overwriteOnAccept: false,
filterGraceful: true,
snippetsPreventQuickSuggestions: true,
localityBonus: false,
@@ -2273,6 +2344,11 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
super(
EditorOption.suggest, 'suggest', defaults,
{
'editor.suggest.overwriteOnAccept': {
type: 'boolean',
default: defaults.overwriteOnAccept,
description: nls.localize('suggest.overwriteOnAccept', "Controls whether words are overwritten when accepting completions.")
},
'editor.suggest.filterGraceful': {
type: 'boolean',
default: defaults.filterGraceful,
@@ -2305,142 +2381,135 @@ 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': {
type: 'object',
default: { keyword: true, snippet: true },
markdownDescription: nls.localize('suggest.filtered', "Controls whether some suggestion types should be filtered from IntelliSense. A list of suggestion types can be found here: https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions."),
properties: {
method: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.method', "When set to `false` IntelliSense never shows `method` suggestions.")
},
function: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.function', "When set to `false` IntelliSense never shows `function` suggestions.")
},
constructor: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.constructor', "When set to `false` IntelliSense never shows `constructor` suggestions.")
},
field: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.field', "When set to `false` IntelliSense never shows `field` suggestions.")
},
variable: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.variable', "When set to `false` IntelliSense never shows `variable` suggestions.")
},
class: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.class', "When set to `false` IntelliSense never shows `class` suggestions.")
},
struct: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.struct', "When set to `false` IntelliSense never shows `struct` suggestions.")
},
interface: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.interface', "When set to `false` IntelliSense never shows `interface` suggestions.")
},
module: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.module', "When set to `false` IntelliSense never shows `module` suggestions.")
},
property: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.property', "When set to `false` IntelliSense never shows `property` suggestions.")
},
event: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.event', "When set to `false` IntelliSense never shows `event` suggestions.")
},
operator: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.operator', "When set to `false` IntelliSense never shows `operator` suggestions.")
},
unit: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.unit', "When set to `false` IntelliSense never shows `unit` suggestions.")
},
value: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.value', "When set to `false` IntelliSense never shows `value` suggestions.")
},
constant: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.constant', "When set to `false` IntelliSense never shows `constant` suggestions.")
},
enum: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.enum', "When set to `false` IntelliSense never shows `enum` suggestions.")
},
enumMember: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.enumMember', "When set to `false` IntelliSense never shows `enumMember` suggestions.")
},
keyword: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.keyword', "When set to `false` IntelliSense never shows `keyword` suggestions.")
},
text: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.text', "When set to `false` IntelliSense never shows `text` suggestions.")
},
color: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.color', "When set to `false` IntelliSense never shows `color` suggestions.")
},
file: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.file', "When set to `false` IntelliSense never shows `file` suggestions.")
},
reference: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.reference', "When set to `false` IntelliSense never shows `reference` suggestions.")
},
customcolor: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.customcolor', "When set to `false` IntelliSense never shows `customcolor` suggestions.")
},
folder: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.folder', "When set to `false` IntelliSense never shows `folder` suggestions.")
},
typeParameter: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.typeParameter', "When set to `false` IntelliSense never shows `typeParameter` suggestions.")
},
snippet: {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.snippet', "When set to `false` IntelliSense never shows `snippet` suggestions.")
},
}
'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.function': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.function', "When set to `false` IntelliSense never shows `function` suggestions.")
},
'editor.suggest.filteredTypes.constructor': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.constructor', "When set to `false` IntelliSense never shows `constructor` suggestions.")
},
'editor.suggest.filteredTypes.field': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.field', "When set to `false` IntelliSense never shows `field` suggestions.")
},
'editor.suggest.filteredTypes.variable': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.variable', "When set to `false` IntelliSense never shows `variable` suggestions.")
},
'editor.suggest.filteredTypes.class': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.class', "When set to `false` IntelliSense never shows `class` suggestions.")
},
'editor.suggest.filteredTypes.struct': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.struct', "When set to `false` IntelliSense never shows `struct` suggestions.")
},
'editor.suggest.filteredTypes.interface': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.interface', "When set to `false` IntelliSense never shows `interface` suggestions.")
},
'editor.suggest.filteredTypes.module': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.module', "When set to `false` IntelliSense never shows `module` suggestions.")
},
'editor.suggest.filteredTypes.property': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.property', "When set to `false` IntelliSense never shows `property` suggestions.")
},
'editor.suggest.filteredTypes.event': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.event', "When set to `false` IntelliSense never shows `event` suggestions.")
},
'editor.suggest.filteredTypes.operator': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.operator', "When set to `false` IntelliSense never shows `operator` suggestions.")
},
'editor.suggest.filteredTypes.unit': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.unit', "When set to `false` IntelliSense never shows `unit` suggestions.")
},
'editor.suggest.filteredTypes.value': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.value', "When set to `false` IntelliSense never shows `value` suggestions.")
},
'editor.suggest.filteredTypes.constant': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.constant', "When set to `false` IntelliSense never shows `constant` suggestions.")
},
'editor.suggest.filteredTypes.enum': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.enum', "When set to `false` IntelliSense never shows `enum` suggestions.")
},
'editor.suggest.filteredTypes.enumMember': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.enumMember', "When set to `false` IntelliSense never shows `enumMember` suggestions.")
},
'editor.suggest.filteredTypes.keyword': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.keyword', "When set to `false` IntelliSense never shows `keyword` suggestions.")
},
'editor.suggest.filteredTypes.text': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.text', "When set to `false` IntelliSense never shows `text` suggestions.")
},
'editor.suggest.filteredTypes.color': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.color', "When set to `false` IntelliSense never shows `color` suggestions.")
},
'editor.suggest.filteredTypes.file': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.file', "When set to `false` IntelliSense never shows `file` suggestions.")
},
'editor.suggest.filteredTypes.reference': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.reference', "When set to `false` IntelliSense never shows `reference` suggestions.")
},
'editor.suggest.filteredTypes.customcolor': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.customcolor', "When set to `false` IntelliSense never shows `customcolor` suggestions.")
},
'editor.suggest.filteredTypes.folder': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.folder', "When set to `false` IntelliSense never shows `folder` suggestions.")
},
'editor.suggest.filteredTypes.typeParameter': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.typeParameter', "When set to `false` IntelliSense never shows `typeParameter` suggestions.")
},
'editor.suggest.filteredTypes.snippet': {
type: 'boolean',
default: true,
markdownDescription: nls.localize('suggest.filtered.snippet', "When set to `false` IntelliSense never shows `snippet` suggestions.")
},
}
);
@@ -2452,6 +2521,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
}
const input = _input as ISuggestOptions;
return {
overwriteOnAccept: EditorBooleanOption.boolean(input.overwriteOnAccept, this.defaultValue.overwriteOnAccept),
filterGraceful: EditorBooleanOption.boolean(input.filterGraceful, this.defaultValue.filterGraceful),
snippetsPreventQuickSuggestions: EditorBooleanOption.boolean(input.snippetsPreventQuickSuggestions, this.defaultValue.filterGraceful),
localityBonus: EditorBooleanOption.boolean(input.localityBonus, this.defaultValue.localityBonus),
@@ -2917,10 +2987,7 @@ export const EditorOptions = {
{ description: nls.localize('fontFamily', "Controls the font family.") }
)),
fontInfo: register(new EditorFontInfo()),
fontLigatures: register(new EditorBooleanOption(
EditorOption.fontLigatures, 'fontLigatures', false,
{ description: nls.localize('fontLigatures', "Enables/Disables font ligatures.") }
)),
fontLigatures2: register(new EditorFontLigatures()),
fontSize: register(new EditorFontSize()),
fontWeight: register(new EditorStringOption(
EditorOption.fontWeight, 'fontWeight', EDITOR_FONT_DEFAULTS.fontWeight,

View File

@@ -28,27 +28,29 @@ export class BareFontInfo {
const fontFamily = options.get(EditorOption.fontFamily);
const fontWeight = options.get(EditorOption.fontWeight);
const fontSize = options.get(EditorOption.fontSize);
const fontFeatureSettings = options.get(EditorOption.fontLigatures);
const lineHeight = options.get(EditorOption.lineHeight);
const letterSpacing = options.get(EditorOption.letterSpacing);
return BareFontInfo._create(fontFamily, fontWeight, fontSize, lineHeight, letterSpacing, zoomLevel, ignoreEditorZoom);
return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, lineHeight, letterSpacing, zoomLevel, ignoreEditorZoom);
}
/**
* @internal
*/
public static createFromRawSettings(opts: { fontFamily?: string; fontWeight?: string; fontSize?: number; lineHeight?: number; letterSpacing?: number; }, zoomLevel: number, ignoreEditorZoom: boolean = false): BareFontInfo {
public static createFromRawSettings(opts: { fontFamily?: string; fontWeight?: string; fontSize?: number; fontLigatures?: boolean | string; lineHeight?: number; letterSpacing?: number; }, zoomLevel: number, ignoreEditorZoom: boolean = false): BareFontInfo {
const fontFamily = EditorOptions.fontFamily.validate(opts.fontFamily);
const fontWeight = EditorOptions.fontWeight.validate(opts.fontWeight);
const fontSize = EditorOptions.fontSize.validate(opts.fontSize);
const fontFeatureSettings = EditorOptions.fontLigatures2.validate(opts.fontLigatures);
const lineHeight = EditorOptions.lineHeight.validate(opts.lineHeight);
const letterSpacing = EditorOptions.letterSpacing.validate(opts.letterSpacing);
return BareFontInfo._create(fontFamily, fontWeight, fontSize, lineHeight, letterSpacing, zoomLevel, ignoreEditorZoom);
return BareFontInfo._create(fontFamily, fontWeight, fontSize, fontFeatureSettings, lineHeight, letterSpacing, zoomLevel, ignoreEditorZoom);
}
/**
* @internal
*/
private static _create(fontFamily: string, fontWeight: string, fontSize: number, lineHeight: number, letterSpacing: number, zoomLevel: number, ignoreEditorZoom: boolean): BareFontInfo {
private static _create(fontFamily: string, fontWeight: string, fontSize: number, fontFeatureSettings: string, lineHeight: number, letterSpacing: number, zoomLevel: number, ignoreEditorZoom: boolean): BareFontInfo {
if (lineHeight === 0) {
lineHeight = Math.round(GOLDEN_LINE_HEIGHT_RATIO * fontSize);
} else if (lineHeight < MINIMUM_LINE_HEIGHT) {
@@ -64,6 +66,7 @@ export class BareFontInfo {
fontFamily: fontFamily,
fontWeight: fontWeight,
fontSize: fontSize,
fontFeatureSettings: fontFeatureSettings,
lineHeight: lineHeight,
letterSpacing: letterSpacing
});
@@ -73,6 +76,7 @@ export class BareFontInfo {
readonly fontFamily: string;
readonly fontWeight: string;
readonly fontSize: number;
readonly fontFeatureSettings: string;
readonly lineHeight: number;
readonly letterSpacing: number;
@@ -84,6 +88,7 @@ export class BareFontInfo {
fontFamily: string;
fontWeight: string;
fontSize: number;
fontFeatureSettings: string;
lineHeight: number;
letterSpacing: number;
}) {
@@ -91,6 +96,7 @@ export class BareFontInfo {
this.fontFamily = String(opts.fontFamily);
this.fontWeight = String(opts.fontWeight);
this.fontSize = opts.fontSize;
this.fontFeatureSettings = opts.fontFeatureSettings;
this.lineHeight = opts.lineHeight | 0;
this.letterSpacing = opts.letterSpacing;
}
@@ -99,7 +105,7 @@ export class BareFontInfo {
* @internal
*/
public getId(): string {
return this.zoomLevel + '-' + this.fontFamily + '-' + this.fontWeight + '-' + this.fontSize + '-' + this.lineHeight + '-' + this.letterSpacing;
return this.zoomLevel + '-' + this.fontFamily + '-' + this.fontWeight + '-' + this.fontSize + '-' + this.fontFeatureSettings + '-' + this.lineHeight + '-' + this.letterSpacing;
}
/**
@@ -138,6 +144,7 @@ export class FontInfo extends BareFontInfo {
fontFamily: string;
fontWeight: string;
fontSize: number;
fontFeatureSettings: string;
lineHeight: number;
letterSpacing: number;
isMonospace: boolean;
@@ -165,6 +172,7 @@ export class FontInfo extends BareFontInfo {
this.fontFamily === other.fontFamily
&& this.fontWeight === other.fontWeight
&& this.fontSize === other.fontSize
&& this.fontFeatureSettings === other.fontFeatureSettings
&& this.lineHeight === other.lineHeight
&& this.letterSpacing === other.letterSpacing
&& this.typicalHalfwidthCharacterWidth === other.typicalHalfwidthCharacterWidth

View File

@@ -153,7 +153,7 @@ class AutoClosedAction {
export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
public static MAX_CURSOR_COUNT = 10000;
public static readonly MAX_CURSOR_COUNT = 10000;
private readonly _onDidReachMaxCursorCount: Emitter<void> = this._register(new Emitter<void>());
public readonly onDidReachMaxCursorCount: Event<void> = this._onDidReachMaxCursorCount.event;

View File

@@ -549,6 +549,29 @@ export class CursorColumns {
return result;
}
public static toStatusbarColumn(lineContent: string, column: number, tabSize: number): number {
let endOffset = lineContent.length;
if (endOffset > column - 1) {
endOffset = column - 1;
}
let result = 0;
for (let i = 0; i < endOffset; i++) {
let charCode = lineContent.charCodeAt(i);
if (charCode === CharCode.Tab) {
result = this.nextRenderTabStop(result, tabSize);
} else {
if (strings.isHighSurrogate(charCode)) {
result = result + 1;
i = i + 1;
} else {
result = result + 1;
}
}
}
return result + 1;
}
public static visibleColumnFromColumn2(config: CursorConfiguration, model: ICursorSimpleModel, position: Position): number {
return this.visibleColumnFromColumn(model.getLineContent(position.lineNumber), position.column, config.tabSize);
}

View File

@@ -122,20 +122,19 @@ export class CursorMoveCommands {
for (let i = 0, len = cursors.length; i < len; i++) {
const cursor = cursors[i];
const viewSelection = cursor.viewState.selection;
const startLineNumber = viewSelection.startLineNumber;
const lineCount = context.viewModel.getLineCount();
const startLineNumber = cursor.modelState.selection.startLineNumber;
const lineCount = context.model.getLineCount();
let endLineNumber = viewSelection.endLineNumber;
let endLineNumber = cursor.modelState.selection.endLineNumber;
let endColumn: number;
if (endLineNumber === lineCount) {
endColumn = context.viewModel.getLineMaxColumn(lineCount);
endColumn = context.model.getLineMaxColumn(lineCount);
} else {
endLineNumber++;
endColumn = 1;
}
result[i] = CursorState.fromViewState(new SingleCursorState(
result[i] = CursorState.fromModelState(new SingleCursorState(
new Range(startLineNumber, 1, startLineNumber, 1), 0,
new Position(endLineNumber, endColumn), 0
));

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 { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition } from 'vs/editor/common/commands/replaceCommand';
import { ReplaceCommand, ReplaceCommandWithOffsetCursorState, ReplaceCommandWithoutChangingPosition, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand';
import { ShiftCommand } from 'vs/editor/common/commands/shiftCommand';
import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand';
import { CursorColumns, CursorConfiguration, EditOperationResult, EditOperationType, ICursorSimpleModel, isQuote } from 'vs/editor/common/controller/cursorCommon';
@@ -81,20 +81,17 @@ export class TypeOperations {
const selection = selections[i];
let position = selection.getPosition();
if (pasteOnNewLine && !selection.isEmpty()) {
pasteOnNewLine = false;
}
if (pasteOnNewLine && text.indexOf('\n') !== text.length - 1) {
pasteOnNewLine = false;
}
if (pasteOnNewLine && selection.startLineNumber !== selection.endLineNumber) {
pasteOnNewLine = false;
}
if (pasteOnNewLine && selection.startColumn === model.getLineMinColumn(selection.startLineNumber) && selection.endColumn === model.getLineMaxColumn(selection.startLineNumber)) {
pasteOnNewLine = false;
}
if (pasteOnNewLine) {
// Paste entire line at the beginning of line
let typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1);
commands[i] = new ReplaceCommand(typeSelection, text);
commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection);
} else {
commands[i] = new ReplaceCommand(selection, text);
}
@@ -462,6 +459,12 @@ export class TypeOperations {
return false;
}
// Do not over-type after a backslash
const beforeCharacter = position.column > 2 ? lineText.charCodeAt(position.column - 2) : CharCode.Null;
if (beforeCharacter === CharCode.Backslash) {
return false;
}
// Must over-type a closing character typed by the editor
if (config.autoClosingOvertype === 'auto') {
let found = false;

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toUint8 } from 'vs/editor/common/core/uint';
import { toUint8 } from 'vs/base/common/uint';
/**
* A fast character classifier that uses a compact array for ASCII values.

View File

@@ -10,7 +10,7 @@
export class RGBA8 {
_rgba8Brand: void;
static Empty = new RGBA8(0, 0, 0, 0);
static readonly Empty = new RGBA8(0, 0, 0, 0);
/**
* Red: integer in [0-255]

View File

@@ -73,13 +73,6 @@ export class Selection extends Range {
this.positionColumn = positionColumn;
}
/**
* Clone this selection.
*/
public clone(): Selection {
return new Selection(this.selectionStartLineNumber, this.selectionStartColumn, this.positionLineNumber, this.positionColumn);
}
/**
* Transform to a human-readable representation.
*/

View File

@@ -1,94 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Uint8Matrix {
private readonly _data: Uint8Array;
public readonly rows: number;
public readonly cols: number;
constructor(rows: number, cols: number, defaultValue: number) {
const data = new Uint8Array(rows * cols);
for (let i = 0, len = rows * cols; i < len; i++) {
data[i] = defaultValue;
}
this._data = data;
this.rows = rows;
this.cols = cols;
}
public get(row: number, col: number): number {
return this._data[row * this.cols + col];
}
public set(row: number, col: number, value: number): void {
this._data[row * this.cols + col] = value;
}
}
export const enum Constants {
/**
* MAX SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MAX_SAFE_SMALL_INTEGER = 1 << 30,
/**
* MIN SMI (SMall Integer) as defined in v8.
* one bit is lost for boxing/unboxing flag.
* one bit is lost for sign flag.
* See https://thibaultlaurens.github.io/javascript/2013/04/29/how-the-v8-engine-works/#tagged-values
*/
MIN_SAFE_SMALL_INTEGER = -(1 << 30),
/**
* Max unsigned integer that fits on 8 bits.
*/
MAX_UINT_8 = 255, // 2^8 - 1
/**
* Max unsigned integer that fits on 16 bits.
*/
MAX_UINT_16 = 65535, // 2^16 - 1
/**
* Max unsigned integer that fits on 32 bits.
*/
MAX_UINT_32 = 4294967295, // 2^32 - 1
}
export function toUint8(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_8) {
return Constants.MAX_UINT_8;
}
return v | 0;
}
export function toUint32(v: number): number {
if (v < 0) {
return 0;
}
if (v > Constants.MAX_UINT_32) {
return Constants.MAX_UINT_32;
}
return v | 0;
}
export function toUint32Array(arr: number[]): Uint32Array {
const len = arr.length;
const r = new Uint32Array(len);
for (let i = 0; i < len; i++) {
r[i] = toUint32(arr[i]);
}
return r;
}

View File

@@ -3,83 +3,63 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDiffChange, ISequence, LcsDiff } from 'vs/base/common/diff/diff';
import { IDiffChange, ISequence, LcsDiff, IDiffResult } from 'vs/base/common/diff/diff';
import * as strings from 'vs/base/common/strings';
import { ICharChange, ILineChange } from 'vs/editor/common/editorCommon';
const MAXIMUM_RUN_TIME = 5000; // 5 seconds
const MINIMUM_MATCHING_CHARACTER_LENGTH = 3;
function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffChange[] {
export interface IDiffComputerResult {
quitEarly: boolean;
changes: ILineChange[];
}
function computeDiff(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: () => boolean, pretty: boolean): IDiffResult {
const diffAlgo = new LcsDiff(originalSequence, modifiedSequence, continueProcessingPredicate);
return diffAlgo.ComputeDiff(pretty);
}
class LineMarkerSequence implements ISequence {
class LineSequence implements ISequence {
private readonly _lines: string[];
public readonly lines: string[];
private readonly _startColumns: number[];
private readonly _endColumns: number[];
constructor(lines: string[]) {
let startColumns: number[] = [];
let endColumns: number[] = [];
const startColumns: number[] = [];
const endColumns: number[] = [];
for (let i = 0, length = lines.length; i < length; i++) {
startColumns[i] = LineMarkerSequence._getFirstNonBlankColumn(lines[i], 1);
endColumns[i] = LineMarkerSequence._getLastNonBlankColumn(lines[i], 1);
startColumns[i] = getFirstNonBlankColumn(lines[i], 1);
endColumns[i] = getLastNonBlankColumn(lines[i], 1);
}
this._lines = lines;
this.lines = lines;
this._startColumns = startColumns;
this._endColumns = endColumns;
}
public getLength(): number {
return this._lines.length;
}
public getElementAtIndex(i: number): string {
return this._lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);
public getElements(): Int32Array | number[] | string[] {
const elements: string[] = [];
for (let i = 0, len = this.lines.length; i < len; i++) {
elements[i] = this.lines[i].substring(this._startColumns[i] - 1, this._endColumns[i] - 1);
}
return elements;
}
public getStartLineNumber(i: number): number {
return i + 1;
}
public getStartColumn(i: number): number {
return this._startColumns[i];
}
public getEndLineNumber(i: number): number {
return i + 1;
}
public getEndColumn(i: number): number {
return this._endColumns[i];
}
public static _getFirstNonBlankColumn(txt: string, defaultValue: number): number {
const r = strings.firstNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 1;
}
public static _getLastNonBlankColumn(txt: string, defaultValue: number): number {
const r = strings.lastNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 2;
}
public getCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence {
let charCodes: number[] = [];
let lineNumbers: number[] = [];
let columns: number[] = [];
public createCharSequence(shouldIgnoreTrimWhitespace: boolean, startIndex: number, endIndex: number): CharSequence {
const charCodes: number[] = [];
const lineNumbers: number[] = [];
const columns: number[] = [];
let len = 0;
for (let index = startIndex; index <= endIndex; index++) {
const lineContent = this._lines[index];
const lineContent = this.lines[index];
const startColumn = (shouldIgnoreTrimWhitespace ? this._startColumns[index] : 1);
const endColumn = (shouldIgnoreTrimWhitespace ? this._endColumns[index] : lineContent.length + 1);
for (let col = startColumn; col < endColumn; col++) {
@@ -105,12 +85,8 @@ class CharSequence implements ISequence {
this._columns = columns;
}
public getLength(): number {
return this._charCodes.length;
}
public getElementAtIndex(i: number): number {
return this._charCodes[i];
public getElements(): Int32Array | number[] | string[] {
return this._charCodes;
}
public getStartLineNumber(i: number): number {
@@ -208,7 +184,7 @@ function postProcessCharChanges(rawChanges: IDiffChange[]): IDiffChange[] {
return rawChanges;
}
let result = [rawChanges[0]];
const result = [rawChanges[0]];
let prevChange = result[0];
for (let i = 1, len = rawChanges.length; i < len; i++) {
@@ -254,7 +230,7 @@ class LineChange implements ILineChange {
this.charChanges = charChanges;
}
public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineMarkerSequence, modifiedLineSequence: LineMarkerSequence, continueProcessingPredicate: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange {
public static createFromDiffResult(shouldIgnoreTrimWhitespace: boolean, diffChange: IDiffChange, originalLineSequence: LineSequence, modifiedLineSequence: LineSequence, continueCharDiff: () => boolean, shouldComputeCharChanges: boolean, shouldPostProcessCharChanges: boolean): LineChange {
let originalStartLineNumber: number;
let originalEndLineNumber: number;
let modifiedStartLineNumber: number;
@@ -277,11 +253,12 @@ class LineChange implements ILineChange {
modifiedEndLineNumber = modifiedLineSequence.getEndLineNumber(diffChange.modifiedStart + diffChange.modifiedLength - 1);
}
if (shouldComputeCharChanges && diffChange.originalLength !== 0 && diffChange.modifiedLength !== 0 && continueProcessingPredicate()) {
const originalCharSequence = originalLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
const modifiedCharSequence = modifiedLineSequence.getCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
if (shouldComputeCharChanges && diffChange.originalLength > 0 && diffChange.originalLength < 20 && diffChange.modifiedLength > 0 && diffChange.modifiedLength < 20 && continueCharDiff()) {
// Compute character changes for diff chunks of at most 20 lines...
const originalCharSequence = originalLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
const modifiedCharSequence = modifiedLineSequence.createCharSequence(shouldIgnoreTrimWhitespace, diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, true);
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueCharDiff, true).changes;
if (shouldPostProcessCharChanges) {
rawChanges = postProcessCharChanges(rawChanges);
@@ -302,6 +279,7 @@ export interface IDiffComputerOpts {
shouldPostProcessCharChanges: boolean;
shouldIgnoreTrimWhitespace: boolean;
shouldMakePrettyDiff: boolean;
maxComputationTime: number;
}
export class DiffComputer {
@@ -310,88 +288,96 @@ export class DiffComputer {
private readonly shouldPostProcessCharChanges: boolean;
private readonly shouldIgnoreTrimWhitespace: boolean;
private readonly shouldMakePrettyDiff: boolean;
private readonly maximumRunTimeMs: number;
private readonly originalLines: string[];
private readonly modifiedLines: string[];
private readonly original: LineMarkerSequence;
private readonly modified: LineMarkerSequence;
private computationStartTime: number;
private readonly original: LineSequence;
private readonly modified: LineSequence;
private readonly continueLineDiff: () => boolean;
private readonly continueCharDiff: () => boolean;
constructor(originalLines: string[], modifiedLines: string[], opts: IDiffComputerOpts) {
this.shouldComputeCharChanges = opts.shouldComputeCharChanges;
this.shouldPostProcessCharChanges = opts.shouldPostProcessCharChanges;
this.shouldIgnoreTrimWhitespace = opts.shouldIgnoreTrimWhitespace;
this.shouldMakePrettyDiff = opts.shouldMakePrettyDiff;
this.maximumRunTimeMs = MAXIMUM_RUN_TIME;
this.originalLines = originalLines;
this.modifiedLines = modifiedLines;
this.original = new LineMarkerSequence(originalLines);
this.modified = new LineMarkerSequence(modifiedLines);
this.original = new LineSequence(originalLines);
this.modified = new LineSequence(modifiedLines);
this.computationStartTime = (new Date()).getTime();
this.continueLineDiff = createContinueProcessingPredicate(opts.maxComputationTime);
this.continueCharDiff = createContinueProcessingPredicate(opts.maxComputationTime === 0 ? 0 : Math.min(opts.maxComputationTime, 5000)); // never run after 5s for character changes...
}
public computeDiff(): ILineChange[] {
public computeDiff(): IDiffComputerResult {
if (this.original.getLength() === 1 && this.original.getElementAtIndex(0).length === 0) {
if (this.original.lines.length === 1 && this.original.lines[0].length === 0) {
// empty original => fast path
return [{
originalStartLineNumber: 1,
originalEndLineNumber: 1,
modifiedStartLineNumber: 1,
modifiedEndLineNumber: this.modified.getLength(),
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
return {
quitEarly: false,
changes: [{
originalStartLineNumber: 1,
originalEndLineNumber: 1,
modifiedStartLineNumber: 1,
modifiedEndLineNumber: this.modified.lines.length,
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
}]
}]
}];
};
}
if (this.modified.getLength() === 1 && this.modified.getElementAtIndex(0).length === 0) {
if (this.modified.lines.length === 1 && this.modified.lines[0].length === 0) {
// empty modified => fast path
return [{
originalStartLineNumber: 1,
originalEndLineNumber: this.original.getLength(),
modifiedStartLineNumber: 1,
modifiedEndLineNumber: 1,
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
return {
quitEarly: false,
changes: [{
originalStartLineNumber: 1,
originalEndLineNumber: this.original.lines.length,
modifiedStartLineNumber: 1,
modifiedEndLineNumber: 1,
charChanges: [{
modifiedEndColumn: 0,
modifiedEndLineNumber: 0,
modifiedStartColumn: 0,
modifiedStartLineNumber: 0,
originalEndColumn: 0,
originalEndLineNumber: 0,
originalStartColumn: 0,
originalStartLineNumber: 0
}]
}]
}];
};
}
this.computationStartTime = (new Date()).getTime();
let rawChanges = computeDiff(this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldMakePrettyDiff);
const diffResult = computeDiff(this.original, this.modified, this.continueLineDiff, this.shouldMakePrettyDiff);
const rawChanges = diffResult.changes;
const quitEarly = diffResult.quitEarly;
// The diff is always computed with ignoring trim whitespace
// This ensures we get the prettiest diff
if (this.shouldIgnoreTrimWhitespace) {
let lineChanges: LineChange[] = [];
const lineChanges: LineChange[] = [];
for (let i = 0, length = rawChanges.length; i < length; i++) {
lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
lineChanges.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, rawChanges[i], this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
}
return lineChanges;
return {
quitEarly: quitEarly,
changes: lineChanges
};
}
// Need to post-process and introduce changes where the trim whitespace is different
// Note that we are looping starting at -1 to also cover the lines before the first change
let result: LineChange[] = [];
const result: LineChange[] = [];
let originalLineIndex = 0;
let modifiedLineIndex = 0;
@@ -409,8 +395,8 @@ export class DiffComputer {
// Check the leading whitespace
{
let originalStartColumn = LineMarkerSequence._getFirstNonBlankColumn(originalLine, 1);
let modifiedStartColumn = LineMarkerSequence._getFirstNonBlankColumn(modifiedLine, 1);
let originalStartColumn = getFirstNonBlankColumn(originalLine, 1);
let modifiedStartColumn = getFirstNonBlankColumn(modifiedLine, 1);
while (originalStartColumn > 1 && modifiedStartColumn > 1) {
const originalChar = originalLine.charCodeAt(originalStartColumn - 2);
const modifiedChar = modifiedLine.charCodeAt(modifiedStartColumn - 2);
@@ -431,8 +417,8 @@ export class DiffComputer {
// Check the trailing whitespace
{
let originalEndColumn = LineMarkerSequence._getLastNonBlankColumn(originalLine, 1);
let modifiedEndColumn = LineMarkerSequence._getLastNonBlankColumn(modifiedLine, 1);
let originalEndColumn = getLastNonBlankColumn(originalLine, 1);
let modifiedEndColumn = getLastNonBlankColumn(modifiedLine, 1);
const originalMaxColumn = originalLine.length + 1;
const modifiedMaxColumn = modifiedLine.length + 1;
while (originalEndColumn < originalMaxColumn && modifiedEndColumn < modifiedMaxColumn) {
@@ -459,14 +445,17 @@ export class DiffComputer {
if (nextChange) {
// Emit the actual change
result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this._continueProcessingPredicate.bind(this), this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
result.push(LineChange.createFromDiffResult(this.shouldIgnoreTrimWhitespace, nextChange, this.original, this.modified, this.continueCharDiff, this.shouldComputeCharChanges, this.shouldPostProcessCharChanges));
originalLineIndex += nextChange.originalLength;
modifiedLineIndex += nextChange.modifiedLength;
}
}
return result;
return {
quitEarly: quitEarly,
changes: result
};
}
private _pushTrimWhitespaceCharChange(
@@ -513,8 +502,8 @@ export class DiffComputer {
if (prevChange.originalEndLineNumber + 1 === originalLineNumber && prevChange.modifiedEndLineNumber + 1 === modifiedLineNumber) {
prevChange.originalEndLineNumber = originalLineNumber;
prevChange.modifiedEndLineNumber = modifiedLineNumber;
if (this.shouldComputeCharChanges) {
prevChange.charChanges!.push(new CharChange(
if (this.shouldComputeCharChanges && prevChange.charChanges) {
prevChange.charChanges.push(new CharChange(
originalLineNumber, originalStartColumn, originalLineNumber, originalEndColumn,
modifiedLineNumber, modifiedStartColumn, modifiedLineNumber, modifiedEndColumn
));
@@ -524,13 +513,31 @@ export class DiffComputer {
return false;
}
}
private _continueProcessingPredicate(): boolean {
if (this.maximumRunTimeMs === 0) {
return true;
}
const now = (new Date()).getTime();
return now - this.computationStartTime < this.maximumRunTimeMs;
function getFirstNonBlankColumn(txt: string, defaultValue: number): number {
const r = strings.firstNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 1;
}
function getLastNonBlankColumn(txt: string, defaultValue: number): number {
const r = strings.lastNonWhitespaceIndex(txt);
if (r === -1) {
return defaultValue;
}
return r + 2;
}
function createContinueProcessingPredicate(maximumRuntime: number): () => boolean {
if (maximumRuntime === 0) {
return () => true;
}
const startTime = Date.now();
return () => {
return Date.now() - startTime < maximumRuntime;
};
}

View File

@@ -315,6 +315,12 @@ export interface IEditor {
*/
getVisibleColumnFromPosition(position: IPosition): number;
/**
* Given a position, returns a column number that takes tab-widths into account.
* @internal
*/
getStatusbarColumn(position: IPosition): number;
/**
* Returns the primary position of the cursor.
*/
@@ -488,10 +494,6 @@ export interface IDiffEditor extends IEditor {
* An editor contribution that gets created every time a new editor gets created and gets disposed when the editor gets disposed.
*/
export interface IEditorContribution {
/**
* Get a unique identifier for this contribution.
*/
getId(): string;
/**
* Dispose this contribution.
*/
@@ -506,6 +508,17 @@ export interface IEditorContribution {
restoreViewState?(state: any): void;
}
/**
* A diff editor contribution that gets created every time a new diffeditor gets created and gets disposed when the diff editor gets disposed.
* @internal
*/
export interface IDiffEditorContribution {
/**
* Dispose this contribution.
*/
dispose(): void;
}
/**
* @internal
*/

View File

@@ -459,8 +459,8 @@ export class FindMatch {
*/
export interface IFoundBracket {
range: Range;
open: string;
close: string;
open: string[];
close: string[];
isOpen: boolean;
}
@@ -608,6 +608,12 @@ export interface ITextModel {
*/
getValueLengthInRange(range: IRange): number;
/**
* Get the character count of text in a certain range.
* @param range The range describing what text length to get.
*/
getCharacterCountInRange(range: IRange): number;
/**
* Splits characters in two buckets. First bucket (A) is of characters that
* sit in lines with length < `LONG_LINE_BOUNDARY`. Second bucket (B) is of
@@ -881,6 +887,13 @@ export interface ITextModel {
*/
findNextBracket(position: IPosition): IFoundBracket | null;
/**
* Find the enclosing brackets that contain `position`.
* @param position The position at which to start the search.
* @internal
*/
findEnclosingBrackets(position: IPosition): [Range, Range] | null;
/**
* Given a `position`, if the position is on top or near a bracket,
* find the matching bracket of that bracket and return the ranges of both brackets.
@@ -1196,6 +1209,7 @@ export interface ITextBuffer {
getValueInRange(range: Range, eol: EndOfLinePreference): string;
createSnapshot(preserveBOM: boolean): ITextSnapshot;
getValueLengthInRange(range: Range, eol: EndOfLinePreference): number;
getCharacterCountInRange(range: Range, eol: EndOfLinePreference): number;
getLength(): number;
getLineCount(): number;
getLinesContent(): string[];

View File

@@ -106,6 +106,37 @@ export class PieceTreeTextBuffer implements ITextBuffer {
return endOffset - startOffset;
}
public getCharacterCountInRange(range: Range, eol: EndOfLinePreference = EndOfLinePreference.TextDefined): number {
if (this._mightContainNonBasicASCII) {
// we must count by iterating
let result = 0;
const fromLineNumber = range.startLineNumber;
const toLineNumber = range.endLineNumber;
for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) {
const lineContent = this.getLineContent(lineNumber);
const fromOffset = (lineNumber === fromLineNumber ? range.startColumn - 1 : 0);
const toOffset = (lineNumber === toLineNumber ? range.endColumn - 1 : lineContent.length);
for (let offset = fromOffset; offset < toOffset; offset++) {
if (strings.isHighSurrogate(lineContent.charCodeAt(offset))) {
result = result + 1;
offset = offset + 1;
} else {
result = result + 1;
}
}
}
result += this._getEndOfLine(eol).length * (toLineNumber - fromLineNumber);
return result;
}
return this.getValueLengthInRange(range, eol);
}
public getLength(): number {
return this._pieceTree.getLength();
}

View File

@@ -726,6 +726,11 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._buffer.getValueLengthInRange(this.validateRange(rawRange), eol);
}
public getCharacterCountInRange(rawRange: IRange, eol: model.EndOfLinePreference = model.EndOfLinePreference.TextDefined): number {
this._assertNotDisposed();
return this._buffer.getCharacterCountInRange(this.validateRange(rawRange), eol);
}
public getLineCount(): number {
this._assertNotDisposed();
return this._buffer.getLineCount();
@@ -1910,7 +1915,7 @@ export class TextModel extends Disposable implements model.ITextModel {
const lineTokens = this._getLineTokens(lineNumber);
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
if (tokenIndex < 0) {
return null;
}
@@ -1919,15 +1924,15 @@ export class TextModel extends Disposable implements model.ITextModel {
// check that the token is not to be ignored
if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
// limit search to not go before `maxBracketLength`
let searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - currentModeBrackets.maxBracketLength);
let searchStartOffset = Math.max(0, position.column - 1 - currentModeBrackets.maxBracketLength);
// limit search to not go after `maxBracketLength`
const searchEndOffset = Math.min(lineTokens.getEndOffset(tokenIndex), position.column - 1 + currentModeBrackets.maxBracketLength);
const searchEndOffset = Math.min(lineText.length, position.column - 1 + currentModeBrackets.maxBracketLength);
// it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
// `bestResult` will contain the most right-side result
let bestResult: [Range, Range] | null = null;
while (true) {
let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
const foundBracket = BracketsUtils.findNextBracketInRange(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!foundBracket) {
// there are no more brackets in this text
break;
@@ -1935,12 +1940,8 @@ export class TextModel extends Disposable implements model.ITextModel {
// check that we didn't hit a bracket too far away from position
if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1);
foundBracketText = foundBracketText.toLowerCase();
let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]);
// check that we can actually match this bracket
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
const r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]);
if (r) {
bestResult = r;
}
@@ -1956,24 +1957,20 @@ export class TextModel extends Disposable implements model.ITextModel {
// If position is in between two tokens, try also looking in the previous token
if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) {
const searchEndOffset = lineTokens.getStartOffset(tokenIndex);
tokenIndex--;
const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(tokenIndex));
const prevTokenIndex = tokenIndex - 1;
const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(prevTokenIndex));
// check that previous token is not to be ignored
if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) {
if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(prevTokenIndex))) {
// limit search in case previous token is very large, there's no need to go beyond `maxBracketLength`
const searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - prevModeBrackets.maxBracketLength);
const foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
const searchStartOffset = Math.max(0, position.column - 1 - prevModeBrackets.maxBracketLength);
const searchEndOffset = Math.min(lineText.length, position.column - 1 + prevModeBrackets.maxBracketLength);
const foundBracket = BracketsUtils.findPrevBracketInRange(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
// check that we didn't hit a bracket too far away from position
if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) {
let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1);
foundBracketText = foundBracketText.toLowerCase();
let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]);
// check that we can actually match this bracket
const foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1).toLowerCase();
const r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]);
if (r) {
return r;
}
@@ -2011,54 +2008,76 @@ export class TextModel extends Disposable implements model.ITextModel {
const reversedBracketRegex = bracket.reversedRegex;
let count = -1;
const searchPrevMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
while (true) {
const r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!r) {
break;
}
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
if (bracket.isOpen(hitText)) {
count++;
} else if (bracket.isClose(hitText)) {
count--;
}
if (count === 0) {
return r;
}
searchEndOffset = r.startColumn - 1;
}
return null;
};
for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) {
const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = tokenCount - 1;
let searchStopOffset = -1;
let searchStartOffset = lineText.length;
let searchEndOffset = lineText.length;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStopOffset = position.column - 1;
searchStartOffset = position.column - 1;
searchEndOffset = position.column - 1;
}
let prevSearchInToken = true;
for (; tokenIndex >= 0; tokenIndex--) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchStopOffset === -1) {
searchStopOffset = tokenEndOffset;
}
if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) {
while (true) {
let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset);
if (!r) {
break;
}
let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1);
hitText = hitText.toLowerCase();
if (hitText === bracket.open) {
count++;
} else if (hitText === bracket.close) {
count--;
}
if (count === 0) {
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchStartOffset
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
searchStopOffset = r.startColumn - 1;
}
}
searchStopOffset = -1;
prevSearchInToken = searchInToken;
}
if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchPrevMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}
@@ -2072,53 +2091,77 @@ export class TextModel extends Disposable implements model.ITextModel {
const bracketRegex = bracket.forwardRegex;
let count = 1;
for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) {
const searchNextMatchingBracketInRange = (lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): Range | null => {
while (true) {
const r = BracketsUtils.findNextBracketInRange(bracketRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!r) {
break;
}
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
if (bracket.isOpen(hitText)) {
count++;
} else if (bracket.isClose(hitText)) {
count--;
}
if (count === 0) {
return r;
}
searchStartOffset = r.endColumn - 1;
}
return null;
};
const lineCount = this.getLineCount();
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = 0;
let searchStartOffset = 0;
let searchEndOffset = 0;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStartOffset = position.column - 1;
searchEndOffset = position.column - 1;
}
let prevSearchInToken = true;
for (; tokenIndex < tokenCount; tokenIndex++) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const searchInToken = (lineTokens.getLanguageId(tokenIndex) === languageId && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchStartOffset === 0) {
searchStartOffset = tokenStartOffset;
}
if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) {
while (true) {
let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset);
if (!r) {
break;
}
let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1);
hitText = hitText.toLowerCase();
if (hitText === bracket.open) {
count++;
} else if (hitText === bracket.close) {
count--;
}
if (count === 0) {
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchEndOffset
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
searchStartOffset = r.endColumn - 1;
}
}
searchStartOffset = 0;
prevSearchInToken = searchInToken;
}
if (prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchNextMatchingBracketInRange(lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}
@@ -2136,33 +2179,66 @@ export class TextModel extends Disposable implements model.ITextModel {
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = tokenCount - 1;
let searchStopOffset = -1;
let searchStartOffset = lineText.length;
let searchEndOffset = lineText.length;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStopOffset = position.column - 1;
}
for (; tokenIndex >= 0; tokenIndex--) {
searchStartOffset = position.column - 1;
searchEndOffset = position.column - 1;
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
if (searchStopOffset === -1) {
searchStopOffset = tokenEndOffset;
}
if (languageId !== tokenLanguageId) {
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
}
if (modeBrackets && !ignoreBracketsInToken(tokenType)) {
let r = BracketsUtils.findPrevBracketInToken(modeBrackets.reversedRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
let prevSearchInToken = true;
for (; tokenIndex >= 0; tokenIndex--) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
// language id change!
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
prevSearchInToken = false;
}
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
}
const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchStartOffset
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
}
}
searchStopOffset = -1;
prevSearchInToken = searchInToken;
}
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findPrevBracketInRange(modeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
}
}
@@ -2171,43 +2247,187 @@ export class TextModel extends Disposable implements model.ITextModel {
public findNextBracket(_position: IPosition): model.IFoundBracket | null {
const position = this.validatePosition(_position);
const lineCount = this.getLineCount();
let languageId: LanguageId = -1;
let modeBrackets: RichEditBrackets | null = null;
for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) {
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = 0;
let searchStartOffset = 0;
let searchEndOffset = 0;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStartOffset = position.column - 1;
}
for (; tokenIndex < tokenCount; tokenIndex++) {
searchEndOffset = position.column - 1;
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
if (searchStartOffset === 0) {
searchStartOffset = tokenStartOffset;
}
if (languageId !== tokenLanguageId) {
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
}
if (modeBrackets && !ignoreBracketsInToken(tokenType)) {
let r = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
let prevSearchInToken = true;
for (; tokenIndex < tokenCount; tokenIndex++) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
// language id change!
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
prevSearchInToken = false;
}
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
}
const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchEndOffset
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
}
}
searchStartOffset = 0;
prevSearchInToken = searchInToken;
}
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return this._toFoundBracket(modeBrackets, r);
}
}
}
return null;
}
public findEnclosingBrackets(_position: IPosition): [Range, Range] | null {
const position = this.validatePosition(_position);
const lineCount = this.getLineCount();
let counts: number[] = [];
const resetCounts = (modeBrackets: RichEditBrackets | null) => {
counts = [];
for (let i = 0, len = modeBrackets ? modeBrackets.brackets.length : 0; i < len; i++) {
counts[i] = 0;
}
};
const searchInRange = (modeBrackets: RichEditBrackets, lineNumber: number, lineText: string, searchStartOffset: number, searchEndOffset: number): [Range, Range] | null => {
while (true) {
const r = BracketsUtils.findNextBracketInRange(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (!r) {
break;
}
const hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1).toLowerCase();
const bracket = modeBrackets.textIsBracket[hitText];
if (bracket) {
if (bracket.isOpen(hitText)) {
counts[bracket.index]++;
} else if (bracket.isClose(hitText)) {
counts[bracket.index]--;
}
if (counts[bracket.index] === -1) {
return this._matchFoundBracket(r, bracket, false);
}
}
searchStartOffset = r.endColumn - 1;
}
return null;
};
let languageId: LanguageId = -1;
let modeBrackets: RichEditBrackets | null = null;
for (let lineNumber = position.lineNumber; lineNumber <= lineCount; lineNumber++) {
const lineTokens = this._getLineTokens(lineNumber);
const tokenCount = lineTokens.getCount();
const lineText = this._buffer.getLineContent(lineNumber);
let tokenIndex = 0;
let searchStartOffset = 0;
let searchEndOffset = 0;
if (lineNumber === position.lineNumber) {
tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
searchStartOffset = position.column - 1;
searchEndOffset = position.column - 1;
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
resetCounts(modeBrackets);
}
}
let prevSearchInToken = true;
for (; tokenIndex < tokenCount; tokenIndex++) {
const tokenLanguageId = lineTokens.getLanguageId(tokenIndex);
if (languageId !== tokenLanguageId) {
// language id change!
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
prevSearchInToken = false;
}
languageId = tokenLanguageId;
modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId);
resetCounts(modeBrackets);
}
const searchInToken = (!!modeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex)));
if (searchInToken) {
// this token should be searched
if (prevSearchInToken) {
// the previous token should be searched, simply extend searchEndOffset
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
} else {
// the previous token should not be searched
searchStartOffset = lineTokens.getStartOffset(tokenIndex);
searchEndOffset = lineTokens.getEndOffset(tokenIndex);
}
} else {
// this token should not be searched
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}
prevSearchInToken = searchInToken;
}
if (modeBrackets && prevSearchInToken && searchStartOffset !== searchEndOffset) {
const r = searchInRange(modeBrackets, lineNumber, lineText, searchStartOffset, searchEndOffset);
if (r) {
return r;
}
}
}

View File

@@ -85,7 +85,7 @@ export function isMultilineRegexSource(searchString: string): boolean {
}
const nextChCode = searchString.charCodeAt(i);
if (nextChCode === CharCode.n || nextChCode === CharCode.r || nextChCode === CharCode.W) {
if (nextChCode === CharCode.n || nextChCode === CharCode.r || nextChCode === CharCode.W || nextChCode === CharCode.w) {
return true;
}
}

View File

@@ -16,6 +16,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { Disposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore';
import * as platform from 'vs/base/common/platform';
const enum Constants {
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
@@ -196,14 +197,14 @@ export class TextModelTokenization extends Disposable {
private readonly _textModel: TextModel;
private readonly _tokenizationStateStore: TokenizationStateStore;
private _revalidateTokensTimeout: any;
private _isDisposed: boolean;
private _tokenizationSupport: ITokenizationSupport | null;
constructor(textModel: TextModel) {
super();
this._isDisposed = false;
this._textModel = textModel;
this._tokenizationStateStore = new TokenizationStateStore();
this._revalidateTokensTimeout = -1;
this._tokenizationSupport = null;
this._register(TokenizationRegistry.onDidChange((e) => {
@@ -246,19 +247,11 @@ export class TextModelTokenization extends Disposable {
}
public dispose(): void {
this._clearTimers();
this._isDisposed = true;
super.dispose();
}
private _clearTimers(): void {
if (this._revalidateTokensTimeout !== -1) {
clearTimeout(this._revalidateTokensTimeout);
this._revalidateTokensTimeout = -1;
}
}
private _resetTokenizationState(): void {
this._clearTimers();
const [tokenizationSupport, initialState] = initializeTokenization(this._textModel);
this._tokenizationSupport = tokenizationSupport;
this._tokenizationStateStore.flush(initialState);
@@ -266,16 +259,19 @@ export class TextModelTokenization extends Disposable {
}
private _beginBackgroundTokenization(): void {
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize() && this._revalidateTokensTimeout === -1) {
this._revalidateTokensTimeout = setTimeout(() => {
this._revalidateTokensTimeout = -1;
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize()) {
platform.setImmediate(() => {
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow();
}, 0);
});
}
}
private _revalidateTokensNow(toLineNumber: number = this._textModel.getLineCount()): void {
const MAX_ALLOWED_TIME = 20;
const MAX_ALLOWED_TIME = 1;
const builder = new MultilineTokensBuilder();
const sw = StopWatch.create(false);

View File

@@ -19,6 +19,7 @@ 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
@@ -874,40 +875,88 @@ export const enum SymbolTag {
/**
* @internal
*/
export const symbolKindToCssClass = (function () {
export namespace SymbolKinds {
const _fromMapping: { [n: number]: string } = Object.create(null);
_fromMapping[SymbolKind.File] = 'file';
_fromMapping[SymbolKind.Module] = 'module';
_fromMapping[SymbolKind.Namespace] = 'namespace';
_fromMapping[SymbolKind.Package] = 'package';
_fromMapping[SymbolKind.Class] = 'class';
_fromMapping[SymbolKind.Method] = 'method';
_fromMapping[SymbolKind.Property] = 'property';
_fromMapping[SymbolKind.Field] = 'field';
_fromMapping[SymbolKind.Constructor] = 'constructor';
_fromMapping[SymbolKind.Enum] = 'enum';
_fromMapping[SymbolKind.Interface] = 'interface';
_fromMapping[SymbolKind.Function] = 'function';
_fromMapping[SymbolKind.Variable] = 'variable';
_fromMapping[SymbolKind.Constant] = 'constant';
_fromMapping[SymbolKind.String] = 'string';
_fromMapping[SymbolKind.Number] = 'number';
_fromMapping[SymbolKind.Boolean] = 'boolean';
_fromMapping[SymbolKind.Array] = 'array';
_fromMapping[SymbolKind.Object] = 'object';
_fromMapping[SymbolKind.Key] = 'key';
_fromMapping[SymbolKind.Null] = 'null';
_fromMapping[SymbolKind.EnumMember] = 'enum-member';
_fromMapping[SymbolKind.Struct] = 'struct';
_fromMapping[SymbolKind.Event] = 'event';
_fromMapping[SymbolKind.Operator] = 'operator';
_fromMapping[SymbolKind.TypeParameter] = 'type-parameter';
const byName = new Map<string, SymbolKind>();
byName.set('file', SymbolKind.File);
byName.set('module', SymbolKind.Module);
byName.set('namespace', SymbolKind.Namespace);
byName.set('package', SymbolKind.Package);
byName.set('class', SymbolKind.Class);
byName.set('method', SymbolKind.Method);
byName.set('property', SymbolKind.Property);
byName.set('field', SymbolKind.Field);
byName.set('constructor', SymbolKind.Constructor);
byName.set('enum', SymbolKind.Enum);
byName.set('interface', SymbolKind.Interface);
byName.set('function', SymbolKind.Function);
byName.set('variable', SymbolKind.Variable);
byName.set('constant', SymbolKind.Constant);
byName.set('string', SymbolKind.String);
byName.set('number', SymbolKind.Number);
byName.set('boolean', SymbolKind.Boolean);
byName.set('array', SymbolKind.Array);
byName.set('object', SymbolKind.Object);
byName.set('key', SymbolKind.Key);
byName.set('null', SymbolKind.Null);
byName.set('enum-member', SymbolKind.EnumMember);
byName.set('struct', SymbolKind.Struct);
byName.set('event', SymbolKind.Event);
byName.set('operator', SymbolKind.Operator);
byName.set('type-parameter', SymbolKind.TypeParameter);
return function toCssClassName(kind: SymbolKind, inline?: boolean): string {
return `symbol-icon ${inline ? 'inline' : 'block'} ${_fromMapping[kind] || 'property'}`;
};
})();
const byKind = new Map<SymbolKind, string>();
byKind.set(SymbolKind.File, 'file');
byKind.set(SymbolKind.Module, 'module');
byKind.set(SymbolKind.Namespace, 'namespace');
byKind.set(SymbolKind.Package, 'package');
byKind.set(SymbolKind.Class, 'class');
byKind.set(SymbolKind.Method, 'method');
byKind.set(SymbolKind.Property, 'property');
byKind.set(SymbolKind.Field, 'field');
byKind.set(SymbolKind.Constructor, 'constructor');
byKind.set(SymbolKind.Enum, 'enum');
byKind.set(SymbolKind.Interface, 'interface');
byKind.set(SymbolKind.Function, 'function');
byKind.set(SymbolKind.Variable, 'variable');
byKind.set(SymbolKind.Constant, 'constant');
byKind.set(SymbolKind.String, 'string');
byKind.set(SymbolKind.Number, 'number');
byKind.set(SymbolKind.Boolean, 'boolean');
byKind.set(SymbolKind.Array, 'array');
byKind.set(SymbolKind.Object, 'object');
byKind.set(SymbolKind.Key, 'key');
byKind.set(SymbolKind.Null, 'null');
byKind.set(SymbolKind.EnumMember, 'enum-member');
byKind.set(SymbolKind.Struct, 'struct');
byKind.set(SymbolKind.Event, 'event');
byKind.set(SymbolKind.Operator, 'operator');
byKind.set(SymbolKind.TypeParameter, 'type-parameter');
/**
* @internal
*/
export function fromString(value: string): SymbolKind | undefined {
return byName.get(value);
}
/**
* @internal
*/
export function names(): readonly string[] {
return keys(byName);
}
/**
* @internal
*/
export function toString(kind: SymbolKind): string | undefined {
return byKind.get(kind);
}
/**
* @internal
*/
export function toCssClassName(kind: SymbolKind, inline?: boolean): string {
return `symbol-icon ${inline ? 'inline' : 'block'} ${byKind.get(kind) || 'property'}`;
}
}
export interface DocumentSymbol {
name: string;

View File

@@ -247,7 +247,7 @@ export class StandardAutoClosingPairConditional {
if (Array.isArray(source.notIn)) {
for (let i = 0, len = source.notIn.length; i < len; i++) {
let notIn = source.notIn[i];
const notIn: string = source.notIn[i];
switch (notIn) {
case 'string':
this._standardTokenMask |= StandardTokenType.String;

View File

@@ -5,7 +5,6 @@
import { CharCode } from 'vs/base/common/charCode';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { Uint8Matrix } from 'vs/editor/common/core/uint';
import { ILink } from 'vs/editor/common/modes';
export interface ILinkComputerTarget {
@@ -33,6 +32,32 @@ export const enum State {
export type Edge = [State, number, State];
export class Uint8Matrix {
private readonly _data: Uint8Array;
public readonly rows: number;
public readonly cols: number;
constructor(rows: number, cols: number, defaultValue: number) {
const data = new Uint8Array(rows * cols);
for (let i = 0, len = rows * cols; i < len; i++) {
data[i] = defaultValue;
}
this._data = data;
this.rows = rows;
this.cols = cols;
}
public get(row: number, col: number): number {
return this._data[row * this.cols + col];
}
public set(row: number, col: number, value: number): void {
this._data[row * this.cols + col] = value;
}
}
export class StateMachine {
private readonly _states: Uint8Matrix;

View File

@@ -60,5 +60,17 @@ LanguageConfigurationRegistry.register(PLAINTEXT_LANGUAGE_IDENTIFIER, {
['(', ')'],
['[', ']'],
['{', '}'],
]
],
surroundingPairs: [
{ open: '{', close: '}' },
{ open: '[', close: ']' },
{ open: '(', close: ')' },
{ open: '<', close: '>' },
{ open: '\"', close: '\"' },
{ open: '\'', close: '\'' },
{ open: '`', close: '`' },
],
folding: {
offSide: true
}
});

View File

@@ -28,10 +28,11 @@ export class BracketElectricCharacterSupport {
let result: string[] = [];
if (this._richEditBrackets) {
for (let i = 0, len = this._richEditBrackets.brackets.length; i < len; i++) {
let bracketPair = this._richEditBrackets.brackets[i];
let lastChar = bracketPair.close.charAt(bracketPair.close.length - 1);
result.push(lastChar);
for (const bracket of this._richEditBrackets.brackets) {
for (const close of bracket.close) {
const lastChar = close.charAt(close.length - 1);
result.push(lastChar);
}
}
}
@@ -56,7 +57,7 @@ export class BracketElectricCharacterSupport {
let reversedBracketRegex = this._richEditBrackets.reversedRegex;
let text = context.getLineContent().substring(0, column - 1) + character;
let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, text, 0, text.length);
let r = BracketsUtils.findPrevBracketInRange(reversedBracketRegex, 1, text, 0, text.length);
if (!r) {
return null;
}

View File

@@ -8,27 +8,107 @@ import { Range } from 'vs/editor/common/core/range';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration';
interface ISimpleInternalBracket {
open: string;
close: string;
interface InternalBracket {
open: string[];
close: string[];
}
export class RichEditBracket {
_richEditBracketBrand: void;
readonly languageIdentifier: LanguageIdentifier;
readonly open: string;
readonly close: string;
readonly index: number;
readonly open: string[];
readonly close: string[];
readonly forwardRegex: RegExp;
readonly reversedRegex: RegExp;
private readonly _openSet: Set<string>;
private readonly _closeSet: Set<string>;
constructor(languageIdentifier: LanguageIdentifier, open: string, close: string, forwardRegex: RegExp, reversedRegex: RegExp) {
constructor(languageIdentifier: LanguageIdentifier, index: number, open: string[], close: string[], forwardRegex: RegExp, reversedRegex: RegExp) {
this.languageIdentifier = languageIdentifier;
this.index = index;
this.open = open;
this.close = close;
this.forwardRegex = forwardRegex;
this.reversedRegex = reversedRegex;
this._openSet = RichEditBracket._toSet(this.open);
this._closeSet = RichEditBracket._toSet(this.close);
}
public isOpen(text: string) {
return this._openSet.has(text);
}
public isClose(text: string) {
return this._closeSet.has(text);
}
private static _toSet(arr: string[]): Set<string> {
const result = new Set<string>();
for (const element of arr) {
result.add(element);
}
return result;
}
}
function groupFuzzyBrackets(brackets: CharacterPair[]): InternalBracket[] {
const N = brackets.length;
brackets = brackets.map(b => [b[0].toLowerCase(), b[1].toLowerCase()]);
const group: number[] = [];
for (let i = 0; i < N; i++) {
group[i] = i;
}
const areOverlapping = (a: CharacterPair, b: CharacterPair) => {
const [aOpen, aClose] = a;
const [bOpen, bClose] = b;
return (aOpen === bOpen || aOpen === bClose || aClose === bOpen || aClose === bClose);
};
const mergeGroups = (g1: number, g2: number) => {
const newG = Math.min(g1, g2);
const oldG = Math.max(g1, g2);
for (let i = 0; i < N; i++) {
if (group[i] === oldG) {
group[i] = newG;
}
}
};
// group together brackets that have the same open or the same close sequence
for (let i = 0; i < N; i++) {
const a = brackets[i];
for (let j = i + 1; j < N; j++) {
const b = brackets[j];
if (areOverlapping(a, b)) {
mergeGroups(group[i], group[j]);
}
}
}
const result: InternalBracket[] = [];
for (let g = 0; g < N; g++) {
let currentOpen: string[] = [];
let currentClose: string[] = [];
for (let i = 0; i < N; i++) {
if (group[i] === g) {
const [open, close] = brackets[i];
currentOpen.push(open);
currentClose.push(close);
}
}
if (currentOpen.length > 0) {
result.push({
open: currentOpen,
close: currentClose
});
}
}
return result;
}
export class RichEditBrackets {
@@ -41,87 +121,140 @@ export class RichEditBrackets {
public readonly textIsBracket: { [text: string]: RichEditBracket; };
public readonly textIsOpenBracket: { [text: string]: boolean; };
constructor(languageIdentifier: LanguageIdentifier, brackets: CharacterPair[]) {
this.brackets = brackets.map((b) => {
constructor(languageIdentifier: LanguageIdentifier, _brackets: CharacterPair[]) {
const brackets = groupFuzzyBrackets(_brackets);
this.brackets = brackets.map((b, index) => {
return new RichEditBracket(
languageIdentifier,
b[0],
b[1],
getRegexForBracketPair({ open: b[0], close: b[1] }),
getReversedRegexForBracketPair({ open: b[0], close: b[1] })
index,
b.open,
b.close,
getRegexForBracketPair(b.open, b.close, brackets, index),
getReversedRegexForBracketPair(b.open, b.close, brackets, index)
);
});
this.forwardRegex = getRegexForBrackets(this.brackets);
this.reversedRegex = getReversedRegexForBrackets(this.brackets);
this.textIsBracket = {};
this.textIsOpenBracket = {};
let maxBracketLength = 0;
this.brackets.forEach((b) => {
this.textIsBracket[b.open.toLowerCase()] = b;
this.textIsBracket[b.close.toLowerCase()] = b;
this.textIsOpenBracket[b.open.toLowerCase()] = true;
this.textIsOpenBracket[b.close.toLowerCase()] = false;
maxBracketLength = Math.max(maxBracketLength, b.open.length);
maxBracketLength = Math.max(maxBracketLength, b.close.length);
});
this.maxBracketLength = maxBracketLength;
}
}
function once<T, R>(keyFn: (input: T) => string, computeFn: (input: T) => R): (input: T) => R {
let cache: { [key: string]: R; } = {};
return (input: T): R => {
let key = keyFn(input);
if (!cache.hasOwnProperty(key)) {
cache[key] = computeFn(input);
this.maxBracketLength = 0;
for (const bracket of this.brackets) {
for (const open of bracket.open) {
this.textIsBracket[open] = bracket;
this.textIsOpenBracket[open] = true;
this.maxBracketLength = Math.max(this.maxBracketLength, open.length);
}
for (const close of bracket.close) {
this.textIsBracket[close] = bracket;
this.textIsOpenBracket[close] = false;
this.maxBracketLength = Math.max(this.maxBracketLength, close.length);
}
}
return cache[key];
};
}
}
const getRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
(input) => `${input.open};${input.close}`,
(input) => {
return createBracketOrRegExp([input.open, input.close]);
function collectSuperstrings(str: string, brackets: InternalBracket[], currentIndex: number, dest: string[]): void {
for (let i = 0, len = brackets.length; i < len; i++) {
if (i === currentIndex) {
continue;
}
const bracket = brackets[i];
for (const open of bracket.open) {
if (open.indexOf(str) >= 0) {
dest.push(open);
}
}
for (const close of bracket.close) {
if (close.indexOf(str) >= 0) {
dest.push(close);
}
}
}
);
}
const getReversedRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
(input) => `${input.open};${input.close}`,
(input) => {
return createBracketOrRegExp([toReversedString(input.open), toReversedString(input.close)]);
}
);
function lengthcmp(a: string, b: string) {
return a.length - b.length;
}
const getRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
(input) => input.map(b => `${b.open};${b.close}`).join(';'),
(input) => {
let pieces: string[] = [];
input.forEach((b) => {
pieces.push(b.open);
pieces.push(b.close);
});
return createBracketOrRegExp(pieces);
function unique(arr: string[]): string[] {
if (arr.length <= 1) {
return arr;
}
);
const result: string[] = [];
const seen = new Set<string>();
for (const element of arr) {
if (seen.has(element)) {
continue;
}
result.push(element);
seen.add(element);
}
return result;
}
const getReversedRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
(input) => input.map(b => `${b.open};${b.close}`).join(';'),
(input) => {
let pieces: string[] = [];
input.forEach((b) => {
pieces.push(toReversedString(b.open));
pieces.push(toReversedString(b.close));
});
return createBracketOrRegExp(pieces);
function getRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp {
// search in all brackets for other brackets that are a superstring of these brackets
let pieces: string[] = [];
pieces = pieces.concat(open);
pieces = pieces.concat(close);
for (let i = 0, len = pieces.length; i < len; i++) {
collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
}
);
pieces = unique(pieces);
pieces.sort(lengthcmp);
pieces.reverse();
return createBracketOrRegExp(pieces);
}
function getReversedRegexForBracketPair(open: string[], close: string[], brackets: InternalBracket[], currentIndex: number): RegExp {
// search in all brackets for other brackets that are a superstring of these brackets
let pieces: string[] = [];
pieces = pieces.concat(open);
pieces = pieces.concat(close);
for (let i = 0, len = pieces.length; i < len; i++) {
collectSuperstrings(pieces[i], brackets, currentIndex, pieces);
}
pieces = unique(pieces);
pieces.sort(lengthcmp);
pieces.reverse();
return createBracketOrRegExp(pieces.map(toReversedString));
}
function getRegexForBrackets(brackets: RichEditBracket[]): RegExp {
let pieces: string[] = [];
for (const bracket of brackets) {
for (const open of bracket.open) {
pieces.push(open);
}
for (const close of bracket.close) {
pieces.push(close);
}
}
pieces = unique(pieces);
return createBracketOrRegExp(pieces);
}
function getReversedRegexForBrackets(brackets: RichEditBracket[]): RegExp {
let pieces: string[] = [];
for (const bracket of brackets) {
for (const open of bracket.open) {
pieces.push(open);
}
for (const close of bracket.close) {
pieces.push(close);
}
}
pieces = unique(pieces);
return createBracketOrRegExp(pieces.map(toReversedString));
}
function prepareBracketForRegExp(str: string): string {
// This bracket pair uses letters like e.g. "begin" - "end"
const insertWordBoundaries = (/^[\w]+$/.test(str));
const insertWordBoundaries = (/^[\w ]+$/.test(str));
str = strings.escapeRegExpCharacters(str);
return (insertWordBoundaries ? `\\b${str}\\b` : str);
}
@@ -168,12 +301,11 @@ export class BracketsUtils {
return new Range(lineNumber, absoluteMatchOffset - matchLength + 1, lineNumber, absoluteMatchOffset + 1);
}
public static findPrevBracketInToken(reversedBracketRegex: RegExp, lineNumber: number, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null {
public static findPrevBracketInRange(reversedBracketRegex: RegExp, lineNumber: number, lineText: string, startOffset: number, endOffset: number): Range | null {
// Because JS does not support backwards regex search, we search forwards in a reversed string with a reversed regex ;)
let reversedLineText = toReversedString(lineText);
let reversedTokenText = reversedLineText.substring(lineText.length - currentTokenEnd, lineText.length - currentTokenStart);
return this._findPrevBracketInText(reversedBracketRegex, lineNumber, reversedTokenText, currentTokenStart);
const reversedLineText = toReversedString(lineText);
const reversedSubstr = reversedLineText.substring(lineText.length - endOffset, lineText.length - startOffset);
return this._findPrevBracketInText(reversedBracketRegex, lineNumber, reversedSubstr, startOffset);
}
public static findNextBracketInText(bracketRegex: RegExp, lineNumber: number, text: string, offset: number): Range | null {
@@ -193,10 +325,8 @@ export class BracketsUtils {
return new Range(lineNumber, absoluteMatchOffset + 1, lineNumber, absoluteMatchOffset + 1 + matchLength);
}
public static findNextBracketInToken(bracketRegex: RegExp, lineNumber: number, lineText: string, currentTokenStart: number, currentTokenEnd: number): Range | null {
let currentTokenText = lineText.substring(currentTokenStart, currentTokenEnd);
return this.findNextBracketInText(bracketRegex, lineNumber, currentTokenText, currentTokenStart);
public static findNextBracketInRange(bracketRegex: RegExp, lineNumber: number, lineText: string, startOffset: number, endOffset: number): Range | null {
const substr = lineText.substring(startOffset, endOffset);
return this.findNextBracketInText(bracketRegex, lineNumber, substr, startOffset);
}
}

View File

@@ -24,7 +24,7 @@ export function tokenizeToString(text: string, tokenizationSupport: IReducedToke
return _tokenizeToString(text, tokenizationSupport || fallback);
}
export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number): string {
export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number, useNbsp: boolean): string {
let result = `<div>`;
let charIndex = startOffset;
let tabsCharDelta = 0;
@@ -46,7 +46,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
while (insertSpacesCount > 0) {
partContent += '&nbsp;';
partContent += useNbsp ? '&nbsp;' : ' ';
insertSpacesCount--;
}
break;
@@ -78,7 +78,7 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens
break;
case CharCode.Space:
partContent += '&nbsp;';
partContent += useNbsp ? '&nbsp;' : ' ';
break;
default:

View File

@@ -377,11 +377,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
// ---- BEGIN diff --------------------------------------------------------------------------
public computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IDiffComputationResult | null> {
public async computeDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
const original = this._getModel(originalUrl);
const modified = this._getModel(modifiedUrl);
if (!original || !modified) {
return Promise.resolve(null);
return null;
}
const originalLines = original.getLinesContent();
@@ -390,15 +390,17 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
shouldComputeCharChanges: true,
shouldPostProcessCharChanges: true,
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
shouldMakePrettyDiff: true
shouldMakePrettyDiff: true,
maxComputationTime: maxComputationTime
});
const changes = diffComputer.computeDiff();
let identical = (changes.length > 0 ? false : this._modelsAreIdentical(original, modified));
return Promise.resolve({
const diffResult = diffComputer.computeDiff();
const identical = (diffResult.changes.length > 0 ? false : this._modelsAreIdentical(original, modified));
return {
quitEarly: diffResult.quitEarly,
identical: identical,
changes: changes
});
changes: diffResult.changes
};
}
private _modelsAreIdentical(original: ICommonModel, modified: ICommonModel): boolean {
@@ -417,11 +419,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return true;
}
public computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<editorCommon.IChange[] | null> {
public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<editorCommon.IChange[] | null> {
let original = this._getModel(originalUrl);
let modified = this._getModel(modifiedUrl);
if (!original || !modified) {
return Promise.resolve(null);
return null;
}
let originalLines = original.getLinesContent();
@@ -430,9 +432,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
shouldComputeCharChanges: false,
shouldPostProcessCharChanges: false,
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
shouldMakePrettyDiff: true
shouldMakePrettyDiff: true,
maxComputationTime: 1000
});
return Promise.resolve(diffComputer.computeDiff());
return diffComputer.computeDiff().changes;
}
// ---- END diff --------------------------------------------------------------------------
@@ -442,10 +445,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
private static readonly _diffLimit = 100000;
public computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise<TextEdit[]> {
public async computeMoreMinimalEdits(modelUrl: string, edits: TextEdit[]): Promise<TextEdit[]> {
const model = this._getModel(modelUrl);
if (!model) {
return Promise.resolve(edits);
return edits;
}
const result: TextEdit[] = [];
@@ -508,28 +511,28 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
result.push({ eol: lastEol, text: '', range: { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 } });
}
return Promise.resolve(result);
return result;
}
// ---- END minimal edits ---------------------------------------------------------------
public computeLinks(modelUrl: string): Promise<ILink[] | null> {
public async computeLinks(modelUrl: string): Promise<ILink[] | null> {
let model = this._getModel(modelUrl);
if (!model) {
return Promise.resolve(null);
return null;
}
return Promise.resolve(computeLinks(model));
return computeLinks(model);
}
// ---- BEGIN suggest --------------------------------------------------------------------------
private static readonly _suggestionsLimit = 10000;
public textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<CompletionList | null> {
public async textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): Promise<CompletionList | null> {
const model = this._getModel(modelUrl);
if (!model) {
return Promise.resolve(null);
return null;
}
const seen: Record<string, boolean> = Object.create(null);
@@ -563,7 +566,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn }
});
}
return Promise.resolve({ suggestions });
return { suggestions };
}
@@ -571,10 +574,10 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
//#region -- word ranges --
computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> {
public async computeWordRanges(modelUrl: string, range: IRange, wordDef: string, wordDefFlags: string): Promise<{ [word: string]: IRange[] }> {
let model = this._getModel(modelUrl);
if (!model) {
return Promise.resolve(Object.create(null));
return Object.create(null);
}
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
const result: { [word: string]: IRange[] } = Object.create(null);
@@ -597,15 +600,15 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
});
}
}
return Promise.resolve(result);
return result;
}
//#endregion
public navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> {
public async navigateValueSet(modelUrl: string, range: IRange, up: boolean, wordDef: string, wordDefFlags: string): Promise<IInplaceReplaceSupportResult | null> {
let model = this._getModel(modelUrl);
if (!model) {
return Promise.resolve(null);
return null;
}
let wordDefRegExp = new RegExp(wordDef, wordDefFlags);
@@ -623,11 +626,11 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
let wordRange = model.getWordAtPosition({ lineNumber: range.startLineNumber, column: range.startColumn }, wordDefRegExp);
if (!wordRange) {
return Promise.resolve(null);
return null;
}
let word = model.getValueInRange(wordRange);
let result = BasicInplaceReplace.INSTANCE.navigateValueSet(range, selectionText, wordRange, word, up);
return Promise.resolve(result);
return result;
}
// ---- BEGIN foreign module support --------------------------------------------------------------------------

View File

@@ -13,6 +13,7 @@ export const ID_EDITOR_WORKER_SERVICE = 'editorWorkerService';
export const IEditorWorkerService = createDecorator<IEditorWorkerService>(ID_EDITOR_WORKER_SERVICE);
export interface IDiffComputationResult {
quitEarly: boolean;
identical: boolean;
changes: ILineChange[];
}
@@ -21,7 +22,7 @@ export interface IEditorWorkerService {
_serviceBrand: undefined;
canComputeDiff(original: URI, modified: URI): boolean;
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IDiffComputationResult | null>;
computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null>;
canComputeDirtyDiff(original: URI, modified: URI): boolean;
computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null>;

View File

@@ -82,8 +82,8 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
}
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IDiffComputationResult | null> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace));
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace, maxComputationTime));
}
public canComputeDirtyDiff(original: URI, modified: URI): boolean {
@@ -409,9 +409,9 @@ export class EditorWorkerClient extends Disposable {
});
}
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IDiffComputationResult | null> {
public computeDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean, maxComputationTime: number): Promise<IDiffComputationResult | null> {
return this._withSyncedResources([original, modified]).then(proxy => {
return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
return proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace, maxComputationTime);
});
}

View File

@@ -218,6 +218,10 @@ export class ModelServiceImpl extends Disposable implements IModelService {
}
private static _setModelOptionsForModel(model: ITextModel, newOptions: ITextModelCreationOptions, currentOptions: ITextModelCreationOptions): void {
if (currentOptions && currentOptions.defaultEOL !== newOptions.defaultEOL && model.getLineCount() === 1) {
model.setEOL(newOptions.defaultEOL === DefaultEndOfLine.LF ? EndOfLineSequence.LF : EndOfLineSequence.CRLF);
}
if (currentOptions
&& (currentOptions.detectIndentation === newOptions.detectIndentation)
&& (currentOptions.insertSpaces === newOptions.insertSpaces)

View File

@@ -403,10 +403,8 @@ export enum TextEditorCursorStyle {
export enum RenderMinimap {
None = 0,
Small = 1,
Large = 2,
SmallBlocks = 3,
LargeBlocks = 4
Text = 1,
Blocks = 2
}
export enum RenderLineNumbersType {

View File

@@ -1,349 +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 { Emitter, Event } from 'vs/base/common/event';
import { RGBA8 } from 'vs/editor/common/core/rgba';
import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes';
export class MinimapTokensColorTracker {
private static _INSTANCE: MinimapTokensColorTracker | null = null;
public static getInstance(): MinimapTokensColorTracker {
if (!this._INSTANCE) {
this._INSTANCE = new MinimapTokensColorTracker();
}
return this._INSTANCE;
}
private _colors!: RGBA8[];
private _backgroundIsLight!: boolean;
private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange: Event<void> = this._onDidChange.event;
private constructor() {
this._updateColorMap();
TokenizationRegistry.onDidChange((e) => {
if (e.changedColorMap) {
this._updateColorMap();
}
});
}
private _updateColorMap(): void {
const colorMap = TokenizationRegistry.getColorMap();
if (!colorMap) {
this._colors = [RGBA8.Empty];
this._backgroundIsLight = true;
return;
}
this._colors = [RGBA8.Empty];
for (let colorId = 1; colorId < colorMap.length; colorId++) {
const source = colorMap[colorId].rgba;
// Use a VM friendly data-type
this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255));
}
let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance();
this._backgroundIsLight = (backgroundLuminosity >= 0.5);
this._onDidChange.fire(undefined);
}
public getColor(colorId: ColorId): RGBA8 {
if (colorId < 1 || colorId >= this._colors.length) {
// background color (basically invisible)
colorId = ColorId.DefaultBackground;
}
return this._colors[colorId];
}
public backgroundIsLight(): boolean {
return this._backgroundIsLight;
}
}
export const enum Constants {
START_CH_CODE = 32, // Space
END_CH_CODE = 126, // Tilde (~)
CHAR_COUNT = END_CH_CODE - START_CH_CODE + 1,
SAMPLED_CHAR_HEIGHT = 16,
SAMPLED_CHAR_WIDTH = 10,
SAMPLED_HALF_CHAR_WIDTH = SAMPLED_CHAR_WIDTH / 2,
x2_CHAR_HEIGHT = 4,
x2_CHAR_WIDTH = 2,
x1_CHAR_HEIGHT = 2,
x1_CHAR_WIDTH = 1,
RGBA_CHANNELS_CNT = 4,
}
export class MinimapCharRenderer {
_minimapCharRendererBrand: void;
public readonly x2charData: Uint8ClampedArray;
public readonly x1charData: Uint8ClampedArray;
public readonly x2charDataLight: Uint8ClampedArray;
public readonly x1charDataLight: Uint8ClampedArray;
constructor(x2CharData: Uint8ClampedArray, x1CharData: Uint8ClampedArray) {
const x2ExpectedLen = Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH * Constants.CHAR_COUNT;
if (x2CharData.length !== x2ExpectedLen) {
throw new Error('Invalid x2CharData');
}
const x1ExpectedLen = Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH * Constants.CHAR_COUNT;
if (x1CharData.length !== x1ExpectedLen) {
throw new Error('Invalid x1CharData');
}
this.x2charData = x2CharData;
this.x1charData = x1CharData;
this.x2charDataLight = MinimapCharRenderer.soften(x2CharData, 12 / 15);
this.x1charDataLight = MinimapCharRenderer.soften(x1CharData, 50 / 60);
}
private static soften(input: Uint8ClampedArray, ratio: number): Uint8ClampedArray {
let result = new Uint8ClampedArray(input.length);
for (let i = 0, len = input.length; i < len; i++) {
result[i] = input[i] * ratio;
}
return result;
}
private static _getChIndex(chCode: number): number {
chCode -= Constants.START_CH_CODE;
if (chCode < 0) {
chCode += Constants.CHAR_COUNT;
}
return (chCode % Constants.CHAR_COUNT);
}
public x2RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void {
if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const x2CharData = useLighterFont ? this.x2charDataLight : this.x2charData;
const chIndex = MinimapCharRenderer._getChIndex(chCode);
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const dest = target.data;
const sourceOffset = chIndex * Constants.x2_CHAR_HEIGHT * Constants.x2_CHAR_WIDTH;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
const c = x2CharData[sourceOffset] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 1] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 2] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 3] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 4] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 5] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x2CharData[sourceOffset + 6] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
{
const c = x2CharData[sourceOffset + 7] / 255;
dest[destOffset + 4] = backgroundR + deltaR * c;
dest[destOffset + 5] = backgroundG + deltaG * c;
dest[destOffset + 6] = backgroundB + deltaB * c;
}
}
public x1RenderChar(target: ImageData, dx: number, dy: number, chCode: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void {
if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const x1CharData = useLighterFont ? this.x1charDataLight : this.x1charData;
const chIndex = MinimapCharRenderer._getChIndex(chCode);
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const dest = target.data;
const sourceOffset = chIndex * Constants.x1_CHAR_HEIGHT * Constants.x1_CHAR_WIDTH;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
const c = x1CharData[sourceOffset] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
destOffset += outWidth;
{
const c = x1CharData[sourceOffset + 1] / 255;
dest[destOffset + 0] = backgroundR + deltaR * c;
dest[destOffset + 1] = backgroundG + deltaG * c;
dest[destOffset + 2] = backgroundB + deltaB * c;
}
}
public x2BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void {
if (dx + Constants.x2_CHAR_WIDTH > target.width || dy + Constants.x2_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const c = 0.5;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const colorR = backgroundR + deltaR * c;
const colorG = backgroundG + deltaG * c;
const colorB = backgroundB + deltaB * c;
const dest = target.data;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
{
dest[destOffset + 4] = colorR;
dest[destOffset + 5] = colorG;
dest[destOffset + 6] = colorB;
}
destOffset += outWidth;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
{
dest[destOffset + 4] = colorR;
dest[destOffset + 5] = colorG;
dest[destOffset + 6] = colorB;
}
destOffset += outWidth;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
{
dest[destOffset + 4] = colorR;
dest[destOffset + 5] = colorG;
dest[destOffset + 6] = colorB;
}
destOffset += outWidth;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
{
dest[destOffset + 4] = colorR;
dest[destOffset + 5] = colorG;
dest[destOffset + 6] = colorB;
}
}
public x1BlockRenderChar(target: ImageData, dx: number, dy: number, color: RGBA8, backgroundColor: RGBA8, useLighterFont: boolean): void {
if (dx + Constants.x1_CHAR_WIDTH > target.width || dy + Constants.x1_CHAR_HEIGHT > target.height) {
console.warn('bad render request outside image data');
return;
}
const outWidth = target.width * Constants.RGBA_CHANNELS_CNT;
const c = 0.5;
const backgroundR = backgroundColor.r;
const backgroundG = backgroundColor.g;
const backgroundB = backgroundColor.b;
const deltaR = color.r - backgroundR;
const deltaG = color.g - backgroundG;
const deltaB = color.b - backgroundB;
const colorR = backgroundR + deltaR * c;
const colorG = backgroundG + deltaG * c;
const colorB = backgroundB + deltaB * c;
const dest = target.data;
let destOffset = dy * outWidth + dx * Constants.RGBA_CHANNELS_CNT;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
destOffset += outWidth;
{
dest[destOffset + 0] = colorR;
dest[destOffset + 1] = colorG;
dest[destOffset + 2] = colorB;
}
}
}

View File

@@ -1,985 +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 { MinimapCharRenderer } from 'vs/editor/common/view/minimapCharRenderer';
function toUint8ClampedArrat(arr: number[]): Uint8ClampedArray {
let r = new Uint8ClampedArray(arr.length);
for (let i = 0, len = arr.length; i < len; i++) {
r[i] = arr[i];
}
return r;
}
let minimapCharRenderer: MinimapCharRenderer | null = null;
export function getOrCreateMinimapCharRenderer(): MinimapCharRenderer {
if (!minimapCharRenderer) {
let _x1Data = toUint8ClampedArrat(x1Data!);
x1Data = null;
let _x2Data = toUint8ClampedArrat(x2Data!);
x2Data = null;
minimapCharRenderer = new MinimapCharRenderer(_x2Data, _x1Data);
}
return minimapCharRenderer;
}
let x2Data: number[] | null = [
//
0, 0,
0, 0,
0, 0,
0, 0,
// !
39, 14,
39, 14,
14, 5,
29, 10,
// "
96, 96,
29, 29,
0, 0,
0, 0,
// #
49, 113,
195, 214,
227, 166,
135, 42,
// $
40, 29,
194, 38,
75, 148,
197, 187,
// %
145, 0,
160, 61,
75, 143,
2, 183,
// &
138, 58,
163, 6,
177, 223,
197, 227,
// '
38, 13,
11, 4,
0, 0,
0, 0,
// (
10, 54,
52, 8,
62, 4,
71, 122,
// )
73, 2,
19, 40,
10, 50,
155, 36,
// *
79, 70,
145, 121,
7, 5,
0, 0,
// +
2, 1,
36, 12,
204, 166,
16, 5,
// ,
0, 0,
0, 0,
1, 0,
154, 34,
// -
0, 0,
0, 0,
96, 83,
0, 0,
// .
0, 0,
0, 0,
0, 0,
46, 34,
// /
0, 82,
2, 56,
53, 3,
146, 0,
// 0
146, 119,
152, 132,
152, 131,
145, 119,
// 1
170, 42,
15, 42,
15, 42,
172, 194,
// 2
131, 132,
0, 139,
80, 28,
227, 143,
// 3
159, 135,
15, 118,
11, 126,
171, 144,
// 4
20, 124,
88, 106,
217, 196,
0, 106,
// 5
189, 92,
168, 43,
5, 130,
164, 133,
// 6
130, 115,
183, 65,
134, 120,
141, 141,
// 7
170, 196,
2, 106,
31, 32,
105, 2,
// 8
145, 130,
116, 114,
132, 135,
138, 140,
// 9
138, 113,
147, 137,
81, 183,
129, 94,
// :
0, 0,
21, 16,
4, 3,
46, 34,
// ;
0, 0,
45, 34,
1, 0,
160, 49,
// <
0, 0,
43, 143,
203, 23,
1, 76,
// =
0, 0,
38, 28,
131, 96,
38, 28,
// >
0, 0,
168, 31,
29, 191,
98, 0,
// ?
118, 139,
5, 113,
45, 13,
37, 6,
// @
97, 115,
161, 179,
204, 105,
223, 224,
// A
83, 52,
111, 100,
184, 186,
120, 132,
// B
212, 145,
180, 139,
174, 161,
212, 182,
// C
104, 162,
131, 0,
131, 0,
104, 161,
// D
219, 120,
110, 116,
110, 116,
219, 120,
// E
207, 154,
163, 40,
147, 22,
207, 154,
// F
202, 159,
161, 47,
145, 23,
111, 0,
// G
139, 154,
144, 30,
144, 135,
139, 187,
// H
110, 110,
168, 161,
150, 145,
110, 110,
// I
185, 162,
43, 16,
43, 16,
185, 162,
// J
73, 129,
0, 110,
0, 110,
191, 87,
// K
149, 149,
236, 48,
195, 91,
146, 149,
// L
146, 0,
146, 0,
146, 0,
187, 173,
// M
200, 201,
222, 215,
172, 147,
95, 95,
// N
193, 97,
224, 129,
159, 206,
97, 192,
// O
155, 139,
153, 115,
153, 115,
156, 140,
// P
189, 158,
123, 136,
190, 64,
111, 0,
// Q
155, 139,
153, 115,
153, 114,
156, 241,
// R
197, 148,
150, 152,
170, 116,
110, 157,
// S
156, 128,
169, 14,
13, 159,
158, 149,
// T
212, 189,
43, 16,
43, 16,
43, 16,
// U
148, 110,
148, 110,
147, 109,
182, 151,
// V
133, 121,
106, 118,
114, 103,
89, 66,
// W
94, 94,
211, 188,
205, 207,
139, 168,
// X
151, 152,
87, 76,
101, 79,
151, 152,
// Y
130, 156,
125, 116,
47, 29,
43, 16,
// Z
169, 228,
11, 103,
120, 6,
230, 176,
// [
55, 49,
55, 6,
55, 6,
193, 102,
// \
92, 0,
71, 0,
13, 30,
0, 147,
// ]
63, 43,
12, 43,
12, 43,
142, 152,
// ^
71, 53,
61, 61,
0, 0,
0, 0,
// _
0, 0,
0, 0,
0, 0,
158, 146,
// `
25, 2,
0, 0,
0, 0,
0, 0,
// a
0, 0,
107, 130,
170, 194,
176, 188,
// b
109, 0,
203, 159,
113, 111,
202, 158,
// c
0, 0,
135, 135,
114, 0,
136, 135,
// d
0, 109,
187, 190,
148, 126,
177, 187,
// e
0, 0,
149, 130,
218, 105,
169, 135,
// f
37, 113,
146, 113,
49, 13,
49, 13,
// g
0, 0,
178, 195,
147, 114,
255, 255,
// h
109, 0,
193, 149,
110, 109,
109, 109,
// i
12, 15,
125, 41,
33, 41,
144, 188,
// j
1, 6,
75, 53,
10, 53,
210, 161,
// k
110, 0,
152, 148,
210, 60,
110, 156,
// l
213, 5,
63, 5,
63, 5,
45, 111,
// m
0, 0,
232, 172,
190, 168,
190, 169,
// n
0, 0,
190, 144,
109, 109,
109, 109,
// o
0, 0,
168, 140,
148, 111,
168, 140,
// p
0, 0,
200, 151,
113, 110,
255, 158,
// q
0, 0,
184, 188,
147, 139,
186, 255,
// r
0, 0,
122, 130,
111, 0,
109, 0,
// s
0, 0,
132, 69,
109, 93,
110, 136,
// t
51, 5,
205, 103,
61, 6,
47, 106,
// u
0, 0,
110, 109,
110, 122,
155, 179,
// v
0, 0,
132, 120,
113, 114,
84, 63,
// w
0, 0,
124, 108,
202, 189,
160, 174,
// x
0, 0,
144, 142,
79, 57,
159, 146,
// y
0, 0,
138, 138,
119, 117,
255, 69,
// z
0, 0,
97, 198,
47, 38,
208, 84,
// {
23, 112,
41, 14,
157, 7,
121, 192,
// |
35, 11,
35, 11,
35, 11,
160, 61,
// }
129, 9,
40, 19,
20, 139,
236, 44,
// ~
0, 0,
15, 3,
97, 93,
0, 0,
];
let x1Data: number[] | null = [
//
0,
0,
// !
23,
12,
// "
53,
0,
// #
130,
127,
// $
58,
149,
// %
67,
77,
// &
72,
198,
// '
13,
0,
// (
25,
51,
// )
25,
49,
// *
94,
2,
// +
8,
64,
// ,
0,
24,
// -
0,
21,
// .
0,
9,
// /
19,
27,
// 0
126,
126,
// 1
51,
80,
// 2
72,
105,
// 3
87,
98,
// 4
73,
93,
// 5
106,
85,
// 6
111,
123,
// 7
87,
30,
// 8
116,
126,
// 9
123,
110,
// :
4,
16,
// ;
9,
28,
// <
21,
53,
// =
8,
62,
// >
23,
52,
// ?
73,
21,
// @
132,
183,
// A
78,
142,
// B
168,
175,
// C
70,
70,
// D
128,
128,
// E
123,
110,
// F
125,
43,
// G
100,
139,
// H
125,
119,
// I
78,
78,
// J
54,
77,
// K
139,
139,
// L
33,
87,
// M
201,
117,
// N
162,
149,
// O
130,
130,
// P
138,
60,
// Q
130,
172,
// R
149,
127,
// S
95,
98,
// T
95,
25,
// U
118,
135,
// V
110,
85,
// W
147,
175,
// X
105,
110,
// Y
121,
30,
// Z
101,
113,
// [
34,
68,
// \
20,
26,
// ]
34,
68,
// ^
56,
0,
// _
0,
44,
// `
3,
0,
// a
27,
175,
// b
80,
133,
// c
31,
66,
// d
85,
147,
// e
32,
150,
// f
90,
25,
// g
45,
230,
// h
77,
101,
// i
36,
83,
// j
22,
84,
// k
71,
118,
// l
44,
44,
// m
52,
172,
// n
38,
101,
// o
35,
130,
// p
40,
197,
// q
43,
197,
// r
29,
26,
// s
23,
103,
// t
67,
44,
// u
25,
129,
// v
29,
85,
// w
27,
177,
// x
33,
97,
// y
32,
145,
// z
33,
77,
// {
38,
96,
// |
20,
55,
// }
36,
95,
// ~
2,
22,
];

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as strings from 'vs/base/common/strings';
import { Constants } from 'vs/editor/common/core/uint';
import { Constants } from 'vs/base/common/uint';
import { InlineDecoration, InlineDecorationType } from 'vs/editor/common/viewModel/viewModel';
export class LineDecoration {

View File

@@ -7,7 +7,7 @@ import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { toUint32Array } from 'vs/editor/common/core/uint';
import { toUint32Array } from 'vs/base/common/uint';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { RGBA8 } from 'vs/editor/common/core/rgba';
import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes';
export class MinimapTokensColorTracker {
private static _INSTANCE: MinimapTokensColorTracker | null = null;
public static getInstance(): MinimapTokensColorTracker {
if (!this._INSTANCE) {
this._INSTANCE = new MinimapTokensColorTracker();
}
return this._INSTANCE;
}
private _colors!: RGBA8[];
private _backgroundIsLight!: boolean;
private readonly _onDidChange = new Emitter<void>();
public readonly onDidChange: Event<void> = this._onDidChange.event;
private constructor() {
this._updateColorMap();
TokenizationRegistry.onDidChange(e => {
if (e.changedColorMap) {
this._updateColorMap();
}
});
}
private _updateColorMap(): void {
const colorMap = TokenizationRegistry.getColorMap();
if (!colorMap) {
this._colors = [RGBA8.Empty];
this._backgroundIsLight = true;
return;
}
this._colors = [RGBA8.Empty];
for (let colorId = 1; colorId < colorMap.length; colorId++) {
const source = colorMap[colorId].rgba;
// Use a VM friendly data-type
this._colors[colorId] = new RGBA8(source.r, source.g, source.b, Math.round(source.a * 255));
}
let backgroundLuminosity = colorMap[ColorId.DefaultBackground].getRelativeLuminance();
this._backgroundIsLight = backgroundLuminosity >= 0.5;
this._onDidChange.fire(undefined);
}
public getColor(colorId: ColorId): RGBA8 {
if (colorId < 1 || colorId >= this._colors.length) {
// background color (basically invisible)
colorId = ColorId.DefaultBackground;
}
return this._colors[colorId];
}
public backgroundIsLight(): boolean {
return this._backgroundIsLight;
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { toUint32 } from 'vs/editor/common/core/uint';
import { toUint32 } from 'vs/base/common/uint';
export class PrefixSumIndexOfResult {
_prefixSumIndexOfResultBrand: void;

View File

@@ -15,7 +15,7 @@ import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } fr
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
import { ColorId, LanguageId, TokenizationRegistry } from 'vs/editor/common/modes';
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
@@ -24,6 +24,7 @@ import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLi
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as platform from 'vs/base/common/platform';
const USE_IDENTITY_LINES_COLLECTION = true;
@@ -128,6 +129,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
super.dispose();
this.decorations.dispose();
this.lines.dispose();
this.invalidateMinimapColorCache();
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
}
@@ -626,9 +628,19 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
ranges = ranges.slice(0);
ranges.sort(Range.compareRangesUsingStarts);
const nonEmptyRanges = ranges.filter((r) => !r.isEmpty());
if (nonEmptyRanges.length === 0) {
let hasEmptyRange = false;
let hasNonEmptyRange = false;
for (const range of ranges) {
if (range.isEmpty()) {
hasEmptyRange = true;
} else {
hasNonEmptyRange = true;
}
}
if (!hasNonEmptyRange) {
// all ranges are empty
if (!emptySelectionClipboard) {
return '';
}
@@ -648,9 +660,29 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
return result;
}
if (hasEmptyRange && emptySelectionClipboard) {
// mixed empty selections and non-empty selections
let result: string[] = [];
let prevModelLineNumber = 0;
for (const range of ranges) {
const modelLineNumber = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber;
if (range.isEmpty()) {
if (modelLineNumber !== prevModelLineNumber) {
result.push(this.model.getLineContent(modelLineNumber));
}
} else {
result.push(this.getValueInRange(range, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));
}
prevModelLineNumber = modelLineNumber;
}
return result.length === 1 ? result[0] : result;
}
let result: string[] = [];
for (const nonEmptyRange of nonEmptyRanges) {
result.push(this.getValueInRange(nonEmptyRange, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));
for (const range of ranges) {
if (!range.isEmpty()) {
result.push(this.getValueInRange(range, forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));
}
}
return result.length === 1 ? result[0] : result;
}
@@ -713,7 +745,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (lineContent === '') {
result += '<br>';
} else {
result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize);
result += tokenizeLineToHTML(lineContent, lineTokens.inflate(), colorMap, startOffset, endOffset, tabSize, platform.isWindows);
}
}