mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-02 01:25:39 -05:00
Merge VS Code 1.23.1 (#1520)
This commit is contained in:
@@ -4,8 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -30,6 +31,7 @@ import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceCompute
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { ITextModel, EndOfLinePreference, IIdentifiedSingleEditOperation, IModelDecorationsChangeAccessor, IModelDecoration, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
let EDITOR_ID = 0;
|
||||
|
||||
@@ -65,20 +67,19 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
private readonly _onDidChangeCursorSelection: Emitter<ICursorSelectionChangedEvent> = this._register(new Emitter<ICursorSelectionChangedEvent>());
|
||||
public readonly onDidChangeCursorSelection: Event<ICursorSelectionChangedEvent> = this._onDidChangeCursorSelection.event;
|
||||
|
||||
private readonly _onDidAttemptReadOnlyEdit: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidAttemptReadOnlyEdit: Event<void> = this._onDidAttemptReadOnlyEdit.event;
|
||||
|
||||
private readonly _onDidLayoutChange: Emitter<editorOptions.EditorLayoutInfo> = this._register(new Emitter<editorOptions.EditorLayoutInfo>());
|
||||
public readonly onDidLayoutChange: Event<editorOptions.EditorLayoutInfo> = this._onDidLayoutChange.event;
|
||||
|
||||
protected readonly _onDidFocusEditorText: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidFocusEditorText: Event<void> = this._onDidFocusEditorText.event;
|
||||
protected _editorTextFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter());
|
||||
public readonly onDidFocusEditorText: Event<void> = this._editorTextFocus.onDidChangeToTrue;
|
||||
public readonly onDidBlurEditorText: Event<void> = this._editorTextFocus.onDidChangeToFalse;
|
||||
|
||||
protected readonly _onDidBlurEditorText: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidBlurEditorText: Event<void> = this._onDidBlurEditorText.event;
|
||||
|
||||
protected readonly _onDidFocusEditor: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidFocusEditor: Event<void> = this._onDidFocusEditor.event;
|
||||
|
||||
protected readonly _onDidBlurEditor: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidBlurEditor: Event<void> = this._onDidBlurEditor.event;
|
||||
protected _editorFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter());
|
||||
public readonly onDidFocusEditor: Event<void> = this._editorFocus.onDidChangeToTrue;
|
||||
public readonly onDidBlurEditor: Event<void> = this._editorFocus.onDidChangeToFalse;
|
||||
|
||||
private readonly _onWillType: Emitter<string> = this._register(new Emitter<string>());
|
||||
public readonly onWillType = this._onWillType.event;
|
||||
@@ -89,6 +90,7 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
private readonly _onDidPaste: Emitter<Range> = this._register(new Emitter<Range>());
|
||||
public readonly onDidPaste = this._onDidPaste.event;
|
||||
|
||||
public readonly isSimpleWidget: boolean;
|
||||
|
||||
protected readonly domElement: IContextKeyServiceTarget;
|
||||
protected readonly id: number;
|
||||
@@ -107,6 +109,7 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
|
||||
protected readonly _instantiationService: IInstantiationService;
|
||||
protected readonly _contextKeyService: IContextKeyService;
|
||||
protected readonly _notificationService: INotificationService;
|
||||
|
||||
/**
|
||||
* map from "parent" decoration type to live decoration ids.
|
||||
@@ -118,14 +121,17 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
constructor(
|
||||
domElement: IContextKeyServiceTarget,
|
||||
options: editorOptions.IEditorOptions,
|
||||
isSimpleWidget: boolean,
|
||||
instantiationService: IInstantiationService,
|
||||
contextKeyService: IContextKeyService
|
||||
contextKeyService: IContextKeyService,
|
||||
notificationService: INotificationService,
|
||||
) {
|
||||
super();
|
||||
this.domElement = domElement;
|
||||
this.id = (++EDITOR_ID);
|
||||
this._decorationTypeKeysToIds = {};
|
||||
this._decorationTypeSubtypes = {};
|
||||
this.isSimpleWidget = isSimpleWidget;
|
||||
|
||||
options = options || {};
|
||||
this._configuration = this._register(this._createConfiguration(options));
|
||||
@@ -138,6 +144,7 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
}));
|
||||
|
||||
this._contextKeyService = this._register(contextKeyService.createScoped(this.domElement));
|
||||
this._notificationService = notificationService;
|
||||
this._register(new EditorContextKeysManager(this, this._contextKeyService));
|
||||
this._register(new EditorModeContext(this, this._contextKeyService));
|
||||
|
||||
@@ -249,13 +256,6 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
public getCenteredRangeInViewport(): Range {
|
||||
if (!this.hasView) {
|
||||
return null;
|
||||
}
|
||||
return this.viewModel.getCenteredRangeInViewport();
|
||||
}
|
||||
|
||||
public getVisibleRanges(): Range[] {
|
||||
if (!this.hasView) {
|
||||
return [];
|
||||
@@ -626,19 +626,19 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
if (!this.cursor || !this.hasView) {
|
||||
return null;
|
||||
}
|
||||
let contributionsState: { [key: string]: any } = {};
|
||||
const contributionsState: { [key: string]: any } = {};
|
||||
|
||||
let keys = Object.keys(this._contributions);
|
||||
const keys = Object.keys(this._contributions);
|
||||
for (let i = 0, len = keys.length; i < len; i++) {
|
||||
let id = keys[i];
|
||||
let contribution = this._contributions[id];
|
||||
const id = keys[i];
|
||||
const contribution = this._contributions[id];
|
||||
if (typeof contribution.saveViewState === 'function') {
|
||||
contributionsState[id] = contribution.saveViewState();
|
||||
}
|
||||
}
|
||||
|
||||
let cursorState = this.cursor.saveState();
|
||||
let viewState = this.viewModel.viewLayout.saveState();
|
||||
const cursorState = this.cursor.saveState();
|
||||
const viewState = this.viewModel.saveState();
|
||||
return {
|
||||
cursorState: cursorState,
|
||||
viewState: viewState,
|
||||
@@ -798,7 +798,7 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
}
|
||||
|
||||
this.model.pushEditOperations(this.cursor.getSelections(), edits, () => {
|
||||
return endCursorState ? endCursorState : this.cursor.getSelections();
|
||||
return endCursorState ? endCursorState : null;
|
||||
});
|
||||
|
||||
if (endCursorState) {
|
||||
@@ -967,6 +967,14 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
|
||||
this._createView();
|
||||
|
||||
this.listenersToRemove.push(this.cursor.onDidReachMaxCursorCount(() => {
|
||||
this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT));
|
||||
}));
|
||||
|
||||
this.listenersToRemove.push(this.cursor.onDidAttemptReadOnlyEdit(() => {
|
||||
this._onDidAttemptReadOnlyEdit.fire(void 0);
|
||||
}));
|
||||
|
||||
this.listenersToRemove.push(this.cursor.onDidChange((e: CursorStateChangedEvent) => {
|
||||
|
||||
let positions: Position[] = [];
|
||||
@@ -1044,10 +1052,45 @@ export abstract class CommonCodeEditor extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
const enum BooleanEventValue {
|
||||
NotSet,
|
||||
False,
|
||||
True
|
||||
}
|
||||
|
||||
export class BooleanEventEmitter extends Disposable {
|
||||
private readonly _onDidChangeToTrue: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeToTrue: Event<void> = this._onDidChangeToTrue.event;
|
||||
|
||||
private readonly _onDidChangeToFalse: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidChangeToFalse: Event<void> = this._onDidChangeToFalse.event;
|
||||
|
||||
private _value: BooleanEventValue;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._value = BooleanEventValue.NotSet;
|
||||
}
|
||||
|
||||
public setValue(_value: boolean) {
|
||||
let value = (_value ? BooleanEventValue.True : BooleanEventValue.False);
|
||||
if (this._value === value) {
|
||||
return;
|
||||
}
|
||||
this._value = value;
|
||||
if (this._value === BooleanEventValue.True) {
|
||||
this._onDidChangeToTrue.fire();
|
||||
} else if (this._value === BooleanEventValue.False) {
|
||||
this._onDidChangeToFalse.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class EditorContextKeysManager extends Disposable {
|
||||
|
||||
private _editor: CommonCodeEditor;
|
||||
private _editorFocus: IContextKey<boolean>;
|
||||
private _textInputFocus: IContextKey<boolean>;
|
||||
private _editorTextFocus: IContextKey<boolean>;
|
||||
private _editorTabMovesFocus: IContextKey<boolean>;
|
||||
private _editorReadonly: IContextKey<boolean>;
|
||||
@@ -1064,7 +1107,8 @@ class EditorContextKeysManager extends Disposable {
|
||||
|
||||
contextKeyService.createKey('editorId', editor.getId());
|
||||
this._editorFocus = EditorContextKeys.focus.bindTo(contextKeyService);
|
||||
this._editorTextFocus = EditorContextKeys.textFocus.bindTo(contextKeyService);
|
||||
this._textInputFocus = EditorContextKeys.textInputFocus.bindTo(contextKeyService);
|
||||
this._editorTextFocus = EditorContextKeys.editorTextFocus.bindTo(contextKeyService);
|
||||
this._editorTabMovesFocus = EditorContextKeys.tabMovesFocus.bindTo(contextKeyService);
|
||||
this._editorReadonly = EditorContextKeys.readOnly.bindTo(contextKeyService);
|
||||
this._hasMultipleSelections = EditorContextKeys.hasMultipleSelections.bindTo(contextKeyService);
|
||||
@@ -1101,8 +1145,9 @@ class EditorContextKeysManager extends Disposable {
|
||||
}
|
||||
|
||||
private _updateFromFocus(): void {
|
||||
this._editorFocus.set(this._editor.hasWidgetFocus());
|
||||
this._editorTextFocus.set(this._editor.isFocused());
|
||||
this._editorFocus.set(this._editor.hasWidgetFocus() && !this._editor.isSimpleWidget);
|
||||
this._editorTextFocus.set(this._editor.isFocused() && !this._editor.isSimpleWidget);
|
||||
this._textInputFocus.set(this._editor.isFocused());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
@@ -34,8 +34,8 @@ export interface ITabFocus {
|
||||
export const TabFocus: ITabFocus = new class implements ITabFocus {
|
||||
private _tabFocus: boolean = false;
|
||||
|
||||
private _onDidChangeTabFocus: Emitter<boolean> = new Emitter<boolean>();
|
||||
public onDidChangeTabFocus: Event<boolean> = this._onDidChangeTabFocus.event;
|
||||
private readonly _onDidChangeTabFocus: Emitter<boolean> = new Emitter<boolean>();
|
||||
public readonly onDidChangeTabFocus: Event<boolean> = this._onDidChangeTabFocus.event;
|
||||
|
||||
public getTabFocusMode(): boolean {
|
||||
return this._tabFocus;
|
||||
@@ -70,7 +70,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
|
||||
private _lineNumbersDigitCount: number;
|
||||
|
||||
private _onDidChange = this._register(new Emitter<editorOptions.IConfigurationChangedEvent>());
|
||||
public onDidChange: Event<editorOptions.IConfigurationChangedEvent> = this._onDidChange.event;
|
||||
public readonly onDidChange: Event<editorOptions.IConfigurationChangedEvent> = this._onDidChange.event;
|
||||
|
||||
constructor(options: editorOptions.IEditorOptions) {
|
||||
super();
|
||||
@@ -166,7 +166,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
|
||||
|
||||
}
|
||||
|
||||
const configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
|
||||
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
|
||||
const editorConfiguration: IConfigurationNode = {
|
||||
'id': 'editor',
|
||||
'order': 5,
|
||||
@@ -211,7 +211,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
nls.localize('lineNumbers.interval', "Line numbers are rendered every 10 lines.")
|
||||
],
|
||||
'default': 'on',
|
||||
'description': nls.localize('lineNumbers', "Controls the display of line numbers. Possible values are 'on', 'off', 'relative' and 'interval'.")
|
||||
'description': nls.localize('lineNumbers', "Controls the display of line numbers.")
|
||||
},
|
||||
'editor.rulers': {
|
||||
'type': 'array',
|
||||
@@ -268,13 +268,13 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'type': 'string',
|
||||
'enum': ['left', 'right'],
|
||||
'default': EDITOR_DEFAULTS.viewInfo.minimap.side,
|
||||
'description': nls.localize('minimap.side', "Controls the side where to render the minimap. Possible values are \'right\' and \'left\'")
|
||||
'description': nls.localize('minimap.side', "Controls the side where to render the minimap.")
|
||||
},
|
||||
'editor.minimap.showSlider': {
|
||||
'type': 'string',
|
||||
'enum': ['always', 'mouseover'],
|
||||
'default': EDITOR_DEFAULTS.viewInfo.minimap.showSlider,
|
||||
'description': nls.localize('minimap.showSlider', "Controls whether the minimap slider is automatically hidden. Possible values are \'always\' and \'mouseover\'")
|
||||
'description': nls.localize('minimap.showSlider', "Controls whether the minimap slider is automatically hidden.")
|
||||
},
|
||||
'editor.minimap.renderCharacters': {
|
||||
'type': 'boolean',
|
||||
@@ -370,6 +370,11 @@ const editorConfiguration: IConfigurationNode = {
|
||||
]
|
||||
}, "The modifier to be used to add multiple cursors with the mouse. `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The Go To Definition and Open Link mouse gestures will adapt such that they do not conflict with the multicursor modifier.")
|
||||
},
|
||||
'editor.multiCursorMergeOverlapping': {
|
||||
'type': 'boolean',
|
||||
'default': EDITOR_DEFAULTS.multiCursorMergeOverlapping,
|
||||
'description': nls.localize('multiCursorMergeOverlapping', "Merge multiple cursors when they are overlapping.")
|
||||
},
|
||||
'editor.quickSuggestions': {
|
||||
'anyOf': [
|
||||
{
|
||||
@@ -515,7 +520,7 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'type': 'string',
|
||||
'enum': ['blink', 'smooth', 'phase', 'expand', 'solid'],
|
||||
'default': editorOptions.blinkingStyleToString(EDITOR_DEFAULTS.viewInfo.cursorBlinking),
|
||||
'description': nls.localize('cursorBlinking', "Control the cursor animation style, possible values are 'blink', 'smooth', 'phase', 'expand' and 'solid'")
|
||||
'description': nls.localize('cursorBlinking', "Control the cursor animation style.")
|
||||
},
|
||||
'editor.mouseWheelZoom': {
|
||||
'type': 'boolean',
|
||||
@@ -568,13 +573,23 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'editor.codeLens': {
|
||||
'type': 'boolean',
|
||||
'default': EDITOR_DEFAULTS.contribInfo.codeLens,
|
||||
'description': nls.localize('codeLens', "Controls if the editor shows code lenses")
|
||||
'description': nls.localize('codeLens', "Controls if the editor shows CodeLens")
|
||||
},
|
||||
'editor.folding': {
|
||||
'type': 'boolean',
|
||||
'default': EDITOR_DEFAULTS.contribInfo.folding,
|
||||
'description': nls.localize('folding', "Controls whether the editor has code folding enabled")
|
||||
},
|
||||
'editor.foldingStrategy': {
|
||||
'type': 'string',
|
||||
'enum': ['auto', 'indentation'],
|
||||
'enumDescriptions': [
|
||||
nls.localize('foldingStrategyAuto', 'If available, use a language specific folding strategy, otherwise falls back to the indentation based strategy.'),
|
||||
nls.localize('foldingStrategyIndentation', 'Always use the indentation based folding strategy')
|
||||
],
|
||||
'default': EDITOR_DEFAULTS.contribInfo.foldingStrategy,
|
||||
'description': nls.localize('foldingStrategy', "Controls the way folding ranges are computed. 'auto' picks uses a language specific folding strategy, if available. 'indentation' forces that the indentation based folding strategy is used.")
|
||||
},
|
||||
'editor.showFoldingControls': {
|
||||
'type': 'string',
|
||||
'enum': ['always', 'mouseover'],
|
||||
@@ -637,6 +652,25 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'default': EDITOR_DEFAULTS.contribInfo.lightbulbEnabled,
|
||||
'description': nls.localize('codeActions', "Enables the code action lightbulb")
|
||||
},
|
||||
'editor.codeActionsOnSave': {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'source.organizeImports': {
|
||||
'type': 'boolean',
|
||||
'description': nls.localize('codeActionsOnSave.organizeImports', "Run organize imports on save?")
|
||||
}
|
||||
},
|
||||
'additionalProperties': {
|
||||
'type': 'boolean'
|
||||
},
|
||||
'default': EDITOR_DEFAULTS.contribInfo.codeActionsOnSave,
|
||||
'description': nls.localize('codeActionsOnSave', "Code action kinds to be run on save.")
|
||||
},
|
||||
'editor.codeActionsOnSaveTimeout': {
|
||||
'type': 'number',
|
||||
'default': EDITOR_DEFAULTS.contribInfo.codeActionsOnSaveTimeout,
|
||||
'description': nls.localize('codeActionsOnSaveTimeout', "Timeout for code actions run on save.")
|
||||
},
|
||||
'editor.selectionClipboard': {
|
||||
'type': 'boolean',
|
||||
'default': EDITOR_DEFAULTS.contribInfo.selectionClipboard,
|
||||
@@ -653,6 +687,11 @@ const editorConfiguration: IConfigurationNode = {
|
||||
'default': true,
|
||||
'description': nls.localize('ignoreTrimWhitespace', "Controls if the diff editor shows changes in leading or trailing whitespace as diffs")
|
||||
},
|
||||
'editor.largeFileOptimizations': {
|
||||
'type': 'boolean',
|
||||
'default': EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
|
||||
'description': nls.localize('largeFileOptimizations', "Special handling for large files to disable certain memory intensive features.")
|
||||
},
|
||||
'diffEditor.renderIndicators': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
|
||||
@@ -10,6 +10,8 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { FontInfo } from 'vs/editor/common/config/fontInfo';
|
||||
import { Constants } from 'vs/editor/common/core/uint';
|
||||
import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
|
||||
/**
|
||||
* Configuration options for editor scrollbars
|
||||
@@ -135,6 +137,13 @@ export interface IEditorLightbulbOptions {
|
||||
enabled?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration map for codeActionsOnSave
|
||||
*/
|
||||
export interface ICodeActionsOnSaveOptions {
|
||||
[kind: string]: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration options for the editor.
|
||||
*/
|
||||
@@ -381,6 +390,11 @@ export interface IEditorOptions {
|
||||
* Defaults to 'alt'
|
||||
*/
|
||||
multiCursorModifier?: 'ctrlCmd' | 'alt';
|
||||
/**
|
||||
* Merge overlapping selections.
|
||||
* Defaults to true
|
||||
*/
|
||||
multiCursorMergeOverlapping?: boolean;
|
||||
/**
|
||||
* Configure the editor's accessibility support.
|
||||
* Defaults to 'auto'. It is best to leave this to 'auto'.
|
||||
@@ -460,7 +474,7 @@ export interface IEditorOptions {
|
||||
/**
|
||||
* The history mode for suggestions.
|
||||
*/
|
||||
suggestSelection?: string;
|
||||
suggestSelection?: 'first' | 'recentlyUsed' | 'recentlyUsedByPrefix';
|
||||
/**
|
||||
* The font size for the suggest widget.
|
||||
* Defaults to the editor font size.
|
||||
@@ -486,20 +500,28 @@ export interface IEditorOptions {
|
||||
* Defaults to true.
|
||||
*/
|
||||
codeLens?: boolean;
|
||||
/**
|
||||
* @deprecated - use codeLens instead
|
||||
* @internal
|
||||
*/
|
||||
referenceInfos?: boolean;
|
||||
/**
|
||||
* Control the behavior and rendering of the code action lightbulb.
|
||||
*/
|
||||
lightbulb?: IEditorLightbulbOptions;
|
||||
/**
|
||||
* Code action kinds to be run on save.
|
||||
*/
|
||||
codeActionsOnSave?: ICodeActionsOnSaveOptions;
|
||||
/**
|
||||
* Timeout for running code actions on save.
|
||||
*/
|
||||
codeActionsOnSaveTimeout?: number;
|
||||
/**
|
||||
* Enable code folding
|
||||
* Defaults to true in vscode and to false in monaco-editor.
|
||||
* Defaults to true.
|
||||
*/
|
||||
folding?: boolean;
|
||||
/**
|
||||
* Selects the folding strategy. 'auto' uses the strategies contributed for the current document, 'indentation' uses the indentation based folding strategy.
|
||||
* Defaults to 'auto'.
|
||||
*/
|
||||
foldingStrategy?: 'auto' | 'indentation';
|
||||
/**
|
||||
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
|
||||
* Defaults to 'mouseover'.
|
||||
@@ -838,11 +860,14 @@ export interface EditorContribOptions {
|
||||
readonly occurrencesHighlight: boolean;
|
||||
readonly codeLens: boolean;
|
||||
readonly folding: boolean;
|
||||
readonly foldingStrategy: 'auto' | 'indentation';
|
||||
readonly showFoldingControls: 'always' | 'mouseover';
|
||||
readonly matchBrackets: boolean;
|
||||
readonly find: InternalEditorFindOptions;
|
||||
readonly colorDecorators: boolean;
|
||||
readonly lightbulbEnabled: boolean;
|
||||
readonly codeActionsOnSave: ICodeActionsOnSaveOptions;
|
||||
readonly codeActionsOnSaveTimeout: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -872,6 +897,7 @@ export interface IValidatedEditorOptions {
|
||||
readonly emptySelectionClipboard: boolean;
|
||||
readonly useTabStops: boolean;
|
||||
readonly multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
|
||||
readonly multiCursorMergeOverlapping: boolean;
|
||||
readonly accessibilitySupport: 'auto' | 'off' | 'on';
|
||||
|
||||
readonly viewInfo: InternalEditorViewOptions;
|
||||
@@ -894,6 +920,7 @@ export class InternalEditorOptions {
|
||||
*/
|
||||
readonly accessibilitySupport: platform.AccessibilitySupport;
|
||||
readonly multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
|
||||
readonly multiCursorMergeOverlapping: boolean;
|
||||
|
||||
// ---- cursor options
|
||||
readonly wordSeparators: string;
|
||||
@@ -922,6 +949,7 @@ export class InternalEditorOptions {
|
||||
readOnly: boolean;
|
||||
accessibilitySupport: platform.AccessibilitySupport;
|
||||
multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey';
|
||||
multiCursorMergeOverlapping: boolean;
|
||||
wordSeparators: string;
|
||||
autoClosingBrackets: boolean;
|
||||
autoIndent: boolean;
|
||||
@@ -942,6 +970,7 @@ export class InternalEditorOptions {
|
||||
this.readOnly = source.readOnly;
|
||||
this.accessibilitySupport = source.accessibilitySupport;
|
||||
this.multiCursorModifier = source.multiCursorModifier;
|
||||
this.multiCursorMergeOverlapping = source.multiCursorMergeOverlapping;
|
||||
this.wordSeparators = source.wordSeparators;
|
||||
this.autoClosingBrackets = source.autoClosingBrackets;
|
||||
this.autoIndent = source.autoIndent;
|
||||
@@ -968,6 +997,7 @@ export class InternalEditorOptions {
|
||||
&& this.readOnly === other.readOnly
|
||||
&& this.accessibilitySupport === other.accessibilitySupport
|
||||
&& this.multiCursorModifier === other.multiCursorModifier
|
||||
&& this.multiCursorMergeOverlapping === other.multiCursorMergeOverlapping
|
||||
&& this.wordSeparators === other.wordSeparators
|
||||
&& this.autoClosingBrackets === other.autoClosingBrackets
|
||||
&& this.autoIndent === other.autoIndent
|
||||
@@ -995,6 +1025,7 @@ export class InternalEditorOptions {
|
||||
readOnly: (this.readOnly !== newOpts.readOnly),
|
||||
accessibilitySupport: (this.accessibilitySupport !== newOpts.accessibilitySupport),
|
||||
multiCursorModifier: (this.multiCursorModifier !== newOpts.multiCursorModifier),
|
||||
multiCursorMergeOverlapping: (this.multiCursorMergeOverlapping !== newOpts.multiCursorMergeOverlapping),
|
||||
wordSeparators: (this.wordSeparators !== newOpts.wordSeparators),
|
||||
autoClosingBrackets: (this.autoClosingBrackets !== newOpts.autoClosingBrackets),
|
||||
autoIndent: (this.autoIndent !== newOpts.autoIndent),
|
||||
@@ -1058,7 +1089,7 @@ export class InternalEditorOptions {
|
||||
return (
|
||||
a.extraEditorClassName === b.extraEditorClassName
|
||||
&& a.disableMonospaceOptimizations === b.disableMonospaceOptimizations
|
||||
&& this._equalsNumberArrays(a.rulers, b.rulers)
|
||||
&& arrays.equals(a.rulers, b.rulers)
|
||||
&& a.ariaLabel === b.ariaLabel
|
||||
&& a.renderLineNumbers === b.renderLineNumbers
|
||||
&& a.renderCustomLineNumbers === b.renderCustomLineNumbers
|
||||
@@ -1120,18 +1151,6 @@ export class InternalEditorOptions {
|
||||
);
|
||||
}
|
||||
|
||||
private static _equalsNumberArrays(a: number[], b: number[]): boolean {
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -1188,10 +1207,13 @@ export class InternalEditorOptions {
|
||||
&& a.occurrencesHighlight === b.occurrencesHighlight
|
||||
&& a.codeLens === b.codeLens
|
||||
&& a.folding === b.folding
|
||||
&& a.foldingStrategy === b.foldingStrategy
|
||||
&& a.showFoldingControls === b.showFoldingControls
|
||||
&& a.matchBrackets === b.matchBrackets
|
||||
&& this._equalFindOptions(a.find, b.find)
|
||||
&& a.colorDecorators === b.colorDecorators
|
||||
&& objects.equals(a.codeActionsOnSave, b.codeActionsOnSave)
|
||||
&& a.codeActionsOnSaveTimeout === b.codeActionsOnSaveTimeout
|
||||
&& a.lightbulbEnabled === b.lightbulbEnabled
|
||||
);
|
||||
}
|
||||
@@ -1347,6 +1369,7 @@ export interface IConfigurationChangedEvent {
|
||||
readonly readOnly: boolean;
|
||||
readonly accessibilitySupport: boolean;
|
||||
readonly multiCursorModifier: boolean;
|
||||
readonly multiCursorMergeOverlapping: boolean;
|
||||
readonly wordSeparators: boolean;
|
||||
readonly autoClosingBrackets: boolean;
|
||||
readonly autoIndent: boolean;
|
||||
@@ -1388,6 +1411,21 @@ function _boolean<T>(value: any, defaultValue: T): boolean | T {
|
||||
return Boolean(value);
|
||||
}
|
||||
|
||||
function _booleanMap(value: { [key: string]: boolean }, defaultValue: { [key: string]: boolean }): { [key: string]: boolean } {
|
||||
if (!value) {
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
const out = Object.create(null);
|
||||
for (const k of Object.keys(value)) {
|
||||
const v = value[k];
|
||||
if (typeof v === 'boolean') {
|
||||
out[k] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function _string(value: any, defaultValue: string): string {
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
@@ -1395,14 +1433,14 @@ function _string(value: any, defaultValue: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
function _stringSet<T>(value: any, defaultValue: T, allowedValues: string[]): T {
|
||||
function _stringSet<T>(value: T, defaultValue: T, allowedValues: T[]): T {
|
||||
if (typeof value !== 'string') {
|
||||
return defaultValue;
|
||||
}
|
||||
if (allowedValues.indexOf(value) === -1) {
|
||||
return defaultValue;
|
||||
}
|
||||
return <T><any>value;
|
||||
return value;
|
||||
}
|
||||
|
||||
function _clampedInt(value: any, defaultValue: number, minimum: number, maximum: number): number {
|
||||
@@ -1532,6 +1570,7 @@ export class EditorOptionsValidator {
|
||||
emptySelectionClipboard: _boolean(opts.emptySelectionClipboard, defaults.emptySelectionClipboard),
|
||||
useTabStops: _boolean(opts.useTabStops, defaults.useTabStops),
|
||||
multiCursorModifier: multiCursorModifier,
|
||||
multiCursorMergeOverlapping: _boolean(opts.multiCursorMergeOverlapping, defaults.multiCursorMergeOverlapping),
|
||||
accessibilitySupport: _stringSet<'auto' | 'on' | 'off'>(opts.accessibilitySupport, defaults.accessibilitySupport, ['auto', 'on', 'off']),
|
||||
viewInfo: viewInfo,
|
||||
contribInfo: contribInfo,
|
||||
@@ -1652,7 +1691,11 @@ export class EditorOptionsValidator {
|
||||
renderLineHighlight = _stringSet<'none' | 'gutter' | 'line' | 'all'>(opts.renderLineHighlight, defaults.renderLineHighlight, ['none', 'gutter', 'line', 'all']);
|
||||
}
|
||||
|
||||
const mouseWheelScrollSensitivity = _float(opts.mouseWheelScrollSensitivity, defaults.scrollbar.mouseWheelScrollSensitivity);
|
||||
let mouseWheelScrollSensitivity = _float(opts.mouseWheelScrollSensitivity, defaults.scrollbar.mouseWheelScrollSensitivity);
|
||||
if (mouseWheelScrollSensitivity === 0) {
|
||||
// Disallow 0, as it would prevent/block scrolling
|
||||
mouseWheelScrollSensitivity = 1;
|
||||
}
|
||||
const scrollbar = this._sanitizeScrollbarOpts(opts.scrollbar, defaults.scrollbar, mouseWheelScrollSensitivity);
|
||||
const minimap = this._sanitizeMinimapOpts(opts.minimap, defaults.minimap);
|
||||
|
||||
@@ -1695,6 +1738,10 @@ export class EditorOptionsValidator {
|
||||
} else {
|
||||
quickSuggestions = _boolean(opts.quickSuggestions, defaults.quickSuggestions);
|
||||
}
|
||||
// Compatibility support for acceptSuggestionOnEnter
|
||||
if (typeof opts.acceptSuggestionOnEnter === 'boolean') {
|
||||
opts.acceptSuggestionOnEnter = opts.acceptSuggestionOnEnter ? 'on' : 'off';
|
||||
}
|
||||
const find = this._santizeFindOpts(opts.find, defaults.find);
|
||||
return {
|
||||
selectionClipboard: _boolean(opts.selectionClipboard, defaults.selectionClipboard),
|
||||
@@ -1708,7 +1755,7 @@ export class EditorOptionsValidator {
|
||||
formatOnType: _boolean(opts.formatOnType, defaults.formatOnType),
|
||||
formatOnPaste: _boolean(opts.formatOnPaste, defaults.formatOnPaste),
|
||||
suggestOnTriggerCharacters: _boolean(opts.suggestOnTriggerCharacters, defaults.suggestOnTriggerCharacters),
|
||||
acceptSuggestionOnEnter: typeof opts.acceptSuggestionOnEnter === 'string' ? _stringSet<'on' | 'smart' | 'off'>(opts.acceptSuggestionOnEnter, defaults.acceptSuggestionOnEnter, ['on', 'smart', 'off']) : opts.acceptSuggestionOnEnter ? 'on' : 'off',
|
||||
acceptSuggestionOnEnter: _stringSet<'on' | 'smart' | 'off'>(opts.acceptSuggestionOnEnter, defaults.acceptSuggestionOnEnter, ['on', 'smart', 'off']),
|
||||
acceptSuggestionOnCommitCharacter: _boolean(opts.acceptSuggestionOnCommitCharacter, defaults.acceptSuggestionOnCommitCharacter),
|
||||
snippetSuggestions: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippetSuggestions, ['top', 'bottom', 'inline', 'none']),
|
||||
wordBasedSuggestions: _boolean(opts.wordBasedSuggestions, defaults.wordBasedSuggestions),
|
||||
@@ -1717,13 +1764,16 @@ export class EditorOptionsValidator {
|
||||
suggestLineHeight: _clampedInt(opts.suggestLineHeight, defaults.suggestLineHeight, 0, 1000),
|
||||
selectionHighlight: _boolean(opts.selectionHighlight, defaults.selectionHighlight),
|
||||
occurrencesHighlight: _boolean(opts.occurrencesHighlight, defaults.occurrencesHighlight),
|
||||
codeLens: _boolean(opts.codeLens, defaults.codeLens) && _boolean(opts.referenceInfos, true),
|
||||
codeLens: _boolean(opts.codeLens, defaults.codeLens),
|
||||
folding: _boolean(opts.folding, defaults.folding),
|
||||
foldingStrategy: _stringSet<'auto' | 'indentation'>(opts.foldingStrategy, defaults.foldingStrategy, ['auto', 'indentation']),
|
||||
showFoldingControls: _stringSet<'always' | 'mouseover'>(opts.showFoldingControls, defaults.showFoldingControls, ['always', 'mouseover']),
|
||||
matchBrackets: _boolean(opts.matchBrackets, defaults.matchBrackets),
|
||||
find: find,
|
||||
colorDecorators: _boolean(opts.colorDecorators, defaults.colorDecorators),
|
||||
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled)
|
||||
lightbulbEnabled: _boolean(opts.lightbulb ? opts.lightbulb.enabled : false, defaults.lightbulbEnabled),
|
||||
codeActionsOnSave: _booleanMap(opts.codeActionsOnSave, {}),
|
||||
codeActionsOnSaveTimeout: _clampedInt(opts.codeActionsOnSaveTimeout, defaults.codeActionsOnSaveTimeout, 1, 10000)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1758,6 +1808,7 @@ export class InternalEditorOptionsFactory {
|
||||
emptySelectionClipboard: opts.emptySelectionClipboard,
|
||||
useTabStops: opts.useTabStops,
|
||||
multiCursorModifier: opts.multiCursorModifier,
|
||||
multiCursorMergeOverlapping: opts.multiCursorMergeOverlapping,
|
||||
accessibilitySupport: opts.accessibilitySupport,
|
||||
|
||||
viewInfo: {
|
||||
@@ -1820,11 +1871,14 @@ export class InternalEditorOptionsFactory {
|
||||
occurrencesHighlight: (accessibilityIsOn ? false : opts.contribInfo.occurrencesHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED
|
||||
codeLens: (accessibilityIsOn ? false : opts.contribInfo.codeLens), // DISABLED WHEN SCREEN READER IS ATTACHED
|
||||
folding: (accessibilityIsOn ? false : opts.contribInfo.folding), // DISABLED WHEN SCREEN READER IS ATTACHED
|
||||
foldingStrategy: opts.contribInfo.foldingStrategy,
|
||||
showFoldingControls: opts.contribInfo.showFoldingControls,
|
||||
matchBrackets: (accessibilityIsOn ? false : opts.contribInfo.matchBrackets), // DISABLED WHEN SCREEN READER IS ATTACHED
|
||||
find: opts.contribInfo.find,
|
||||
colorDecorators: opts.contribInfo.colorDecorators,
|
||||
lightbulbEnabled: opts.contribInfo.lightbulbEnabled
|
||||
lightbulbEnabled: opts.contribInfo.lightbulbEnabled,
|
||||
codeActionsOnSave: opts.contribInfo.codeActionsOnSave,
|
||||
codeActionsOnSaveTimeout: opts.contribInfo.codeActionsOnSaveTimeout
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1964,6 +2018,7 @@ export class InternalEditorOptionsFactory {
|
||||
readOnly: opts.readOnly,
|
||||
accessibilitySupport: accessibilitySupport,
|
||||
multiCursorModifier: opts.multiCursorModifier,
|
||||
multiCursorMergeOverlapping: opts.multiCursorMergeOverlapping,
|
||||
wordSeparators: opts.wordSeparators,
|
||||
autoClosingBrackets: opts.autoClosingBrackets,
|
||||
autoIndent: opts.autoIndent,
|
||||
@@ -1984,31 +2039,31 @@ export class InternalEditorOptionsFactory {
|
||||
* @internal
|
||||
*/
|
||||
export interface IEditorLayoutProviderOpts {
|
||||
outerWidth: number;
|
||||
outerHeight: number;
|
||||
readonly outerWidth: number;
|
||||
readonly outerHeight: number;
|
||||
|
||||
showGlyphMargin: boolean;
|
||||
lineHeight: number;
|
||||
readonly showGlyphMargin: boolean;
|
||||
readonly lineHeight: number;
|
||||
|
||||
showLineNumbers: boolean;
|
||||
lineNumbersMinChars: number;
|
||||
lineNumbersDigitCount: number;
|
||||
readonly showLineNumbers: boolean;
|
||||
readonly lineNumbersMinChars: number;
|
||||
readonly lineNumbersDigitCount: number;
|
||||
|
||||
lineDecorationsWidth: number;
|
||||
readonly lineDecorationsWidth: number;
|
||||
|
||||
typicalHalfwidthCharacterWidth: number;
|
||||
maxDigitWidth: number;
|
||||
readonly typicalHalfwidthCharacterWidth: number;
|
||||
readonly maxDigitWidth: number;
|
||||
|
||||
verticalScrollbarWidth: number;
|
||||
verticalScrollbarHasArrows: boolean;
|
||||
scrollbarArrowSize: number;
|
||||
horizontalScrollbarHeight: number;
|
||||
readonly verticalScrollbarWidth: number;
|
||||
readonly verticalScrollbarHasArrows: boolean;
|
||||
readonly scrollbarArrowSize: number;
|
||||
readonly horizontalScrollbarHeight: number;
|
||||
|
||||
minimap: boolean;
|
||||
minimapSide: string;
|
||||
minimapRenderCharacters: boolean;
|
||||
minimapMaxColumn: number;
|
||||
pixelRatio: number;
|
||||
readonly minimap: boolean;
|
||||
readonly minimapSide: string;
|
||||
readonly minimapRenderCharacters: boolean;
|
||||
readonly minimapMaxColumn: number;
|
||||
readonly pixelRatio: number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2074,18 +2129,19 @@ export class EditorLayoutProvider {
|
||||
}
|
||||
|
||||
// Given:
|
||||
// viewportColumn = (contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth
|
||||
// (leaving 2px for the cursor to have space after the last character)
|
||||
// viewportColumn = (contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth
|
||||
// minimapWidth = viewportColumn * minimapCharWidth
|
||||
// contentWidth = remainingWidth - minimapWidth
|
||||
// What are good values for contentWidth and minimapWidth ?
|
||||
|
||||
// minimapWidth = ((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth) * minimapCharWidth
|
||||
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth) * minimapCharWidth
|
||||
// minimapWidth = ((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
|
||||
// minimapWidth = ((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (contentWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// typicalHalfwidthCharacterWidth * minimapWidth = (remainingWidth - minimapWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// (typicalHalfwidthCharacterWidth + minimapCharWidth) * minimapWidth = (remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth
|
||||
// minimapWidth = ((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)
|
||||
|
||||
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
|
||||
minimapWidth = Math.max(0, Math.floor(((remainingWidth - verticalScrollbarWidth - 2) * minimapCharWidth) / (typicalHalfwidthCharacterWidth + minimapCharWidth)));
|
||||
let minimapColumns = minimapWidth / minimapCharWidth;
|
||||
if (minimapColumns > minimapMaxColumn) {
|
||||
minimapWidth = Math.floor(minimapMaxColumn * minimapCharWidth);
|
||||
@@ -2103,7 +2159,8 @@ export class EditorLayoutProvider {
|
||||
}
|
||||
}
|
||||
|
||||
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth) / typicalHalfwidthCharacterWidth));
|
||||
// (leaving 2px for the cursor to have space after the last character)
|
||||
const viewportColumn = Math.max(1, Math.floor((contentWidth - verticalScrollbarWidth - 2) / typicalHalfwidthCharacterWidth));
|
||||
|
||||
const verticalArrowSize = (verticalScrollbarHasArrows ? scrollbarArrowSize : 0);
|
||||
|
||||
@@ -2172,7 +2229,8 @@ export const EDITOR_MODEL_DEFAULTS = {
|
||||
tabSize: 4,
|
||||
insertSpaces: true,
|
||||
detectIndentation: true,
|
||||
trimAutoWhitespace: true
|
||||
trimAutoWhitespace: true,
|
||||
largeFileOptimizations: true
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2200,6 +2258,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
|
||||
emptySelectionClipboard: true,
|
||||
useTabStops: true,
|
||||
multiCursorModifier: 'altKey',
|
||||
multiCursorMergeOverlapping: true,
|
||||
accessibilitySupport: 'auto',
|
||||
|
||||
viewInfo: {
|
||||
@@ -2276,6 +2335,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
|
||||
occurrencesHighlight: true,
|
||||
codeLens: true,
|
||||
folding: true,
|
||||
foldingStrategy: 'auto',
|
||||
showFoldingControls: 'mouseover',
|
||||
matchBrackets: true,
|
||||
find: {
|
||||
@@ -2284,6 +2344,8 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = {
|
||||
globalFindClipboard: false
|
||||
},
|
||||
colorDecorators: true,
|
||||
lightbulbEnabled: true
|
||||
lightbulbEnabled: true,
|
||||
codeActionsOnSave: {},
|
||||
codeActionsOnSaveTimeout: 750
|
||||
},
|
||||
};
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
export interface IEditorZoom {
|
||||
onDidChangeZoomLevel: Event<number>;
|
||||
@@ -16,8 +16,8 @@ export const EditorZoom: IEditorZoom = new class implements IEditorZoom {
|
||||
|
||||
private _zoomLevel: number = 0;
|
||||
|
||||
private _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
|
||||
public onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
|
||||
private readonly _onDidChangeZoomLevel: Emitter<number> = new Emitter<number>();
|
||||
public readonly onDidChangeZoomLevel: Event<number> = this._onDidChangeZoomLevel.event;
|
||||
|
||||
public getZoomLevel(): number {
|
||||
return this._zoomLevel;
|
||||
|
||||
@@ -19,7 +19,7 @@ import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
|
||||
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ITextModel, IIdentifiedSingleEditOperation, TrackedRangeStickiness } from 'vs/editor/common/model';
|
||||
|
||||
function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean {
|
||||
@@ -87,6 +87,14 @@ export class CursorModelState {
|
||||
|
||||
export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
public static MAX_CURSOR_COUNT = 10000;
|
||||
|
||||
private readonly _onDidReachMaxCursorCount: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidReachMaxCursorCount: Event<void> = this._onDidReachMaxCursorCount.event;
|
||||
|
||||
private readonly _onDidAttemptReadOnlyEdit: Emitter<void> = this._register(new Emitter<void>());
|
||||
public readonly onDidAttemptReadOnlyEdit: Event<void> = this._onDidAttemptReadOnlyEdit.event;
|
||||
|
||||
private readonly _onDidChange: Emitter<CursorStateChangedEvent> = this._register(new Emitter<CursorStateChangedEvent>());
|
||||
public readonly onDidChange: Event<CursorStateChangedEvent> = this._onDidChange.event;
|
||||
|
||||
@@ -185,6 +193,11 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
}
|
||||
|
||||
public setStates(source: string, reason: CursorChangeReason, states: CursorState[]): void {
|
||||
if (states.length > Cursor.MAX_CURSOR_COUNT) {
|
||||
states = states.slice(0, Cursor.MAX_CURSOR_COUNT);
|
||||
this._onDidReachMaxCursorCount.fire(void 0);
|
||||
}
|
||||
|
||||
const oldState = new CursorModelState(this._model, this);
|
||||
|
||||
this._cursors.setStates(states);
|
||||
@@ -361,7 +374,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
private _interpretCommandResult(cursorState: Selection[]): void {
|
||||
if (!cursorState || cursorState.length === 0) {
|
||||
return;
|
||||
cursorState = this._cursors.readSelectionFromMarkers();
|
||||
}
|
||||
|
||||
this._columnSelectData = null;
|
||||
@@ -456,12 +469,19 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
if (this._configuration.editor.readOnly) {
|
||||
// All the remaining handlers will try to edit the model,
|
||||
// but we cannot edit when read only...
|
||||
this._onDidAttemptReadOnlyEdit.fire(void 0);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldState = new CursorModelState(this._model, this);
|
||||
let cursorChangeReason = CursorChangeReason.NotSet;
|
||||
|
||||
if (handlerId !== H.Undo && handlerId !== H.Redo) {
|
||||
// TODO@Alex: if the undo/redo stack contains non-null selections
|
||||
// it would also be OK to stop tracking selections here
|
||||
this._cursors.stopTrackingSelections();
|
||||
}
|
||||
|
||||
// ensure valid state on all cursors
|
||||
this._cursors.ensureValidState();
|
||||
|
||||
@@ -510,6 +530,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
|
||||
|
||||
this._isHandling = false;
|
||||
|
||||
if (handlerId !== H.Undo && handlerId !== H.Redo) {
|
||||
this._cursors.startTrackingSelections();
|
||||
}
|
||||
|
||||
if (this._emitStateChangedIfNecessary(source, cursorChangeReason, oldState)) {
|
||||
this._revealRange(RevealTarget.Primary, viewEvents.VerticalRevealType.Simple, true, editorCommon.ScrollType.Smooth);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,20 @@ export class CursorCollection {
|
||||
this.killSecondaryCursors();
|
||||
}
|
||||
|
||||
public startTrackingSelections(): void {
|
||||
this.primaryCursor.startTrackingSelection(this.context);
|
||||
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
|
||||
this.secondaryCursors[i].startTrackingSelection(this.context);
|
||||
}
|
||||
}
|
||||
|
||||
public stopTrackingSelections(): void {
|
||||
this.primaryCursor.stopTrackingSelection(this.context);
|
||||
for (let i = 0, len = this.secondaryCursors.length; i < len; i++) {
|
||||
this.secondaryCursors[i].stopTrackingSelection(this.context);
|
||||
}
|
||||
}
|
||||
|
||||
public updateContext(context: CursorContext): void {
|
||||
this.context = context;
|
||||
}
|
||||
@@ -193,6 +207,10 @@ export class CursorCollection {
|
||||
const currentViewSelection = current.viewSelection;
|
||||
const nextViewSelection = next.viewSelection;
|
||||
|
||||
if (!this.context.config.multiCursorMergeOverlapping) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let shouldMergeCursors: boolean;
|
||||
if (nextViewSelection.isEmpty() || currentViewSelection.isEmpty()) {
|
||||
// Merge touching cursors if one of them is collapsed
|
||||
|
||||
@@ -78,6 +78,7 @@ export class CursorConfiguration {
|
||||
public readonly useTabStops: boolean;
|
||||
public readonly wordSeparators: string;
|
||||
public readonly emptySelectionClipboard: boolean;
|
||||
public readonly multiCursorMergeOverlapping: boolean;
|
||||
public readonly autoClosingBrackets: boolean;
|
||||
public readonly autoIndent: boolean;
|
||||
public readonly autoClosingPairsOpen: CharacterMap;
|
||||
@@ -92,6 +93,7 @@ export class CursorConfiguration {
|
||||
e.layoutInfo
|
||||
|| e.wordSeparators
|
||||
|| e.emptySelectionClipboard
|
||||
|| e.multiCursorMergeOverlapping
|
||||
|| e.autoClosingBrackets
|
||||
|| e.useTabStops
|
||||
|| e.lineHeight
|
||||
@@ -118,6 +120,7 @@ export class CursorConfiguration {
|
||||
this.useTabStops = c.useTabStops;
|
||||
this.wordSeparators = c.wordSeparators;
|
||||
this.emptySelectionClipboard = c.emptySelectionClipboard;
|
||||
this.multiCursorMergeOverlapping = c.multiCursorMergeOverlapping;
|
||||
this.autoClosingBrackets = c.autoClosingBrackets;
|
||||
this.autoIndent = c.autoIndent;
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@ import { IndentAction, EnterAction } from 'vs/editor/common/modes/languageConfig
|
||||
import { SurroundSelectionCommand } from 'vs/editor/common/commands/surroundSelectionCommand';
|
||||
import { IElectricAction } from 'vs/editor/common/modes/supports/electricCharacter';
|
||||
import { getMapForWordSeparators, WordCharacterClass } from 'vs/editor/common/controller/wordCharacterClassifier';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export class TypeOperations {
|
||||
|
||||
@@ -123,6 +124,15 @@ export class TypeOperations {
|
||||
return multicursorText;
|
||||
}
|
||||
|
||||
// Remove trailing \n if present
|
||||
if (text.charCodeAt(text.length - 1) === CharCode.LineFeed) {
|
||||
text = text.substr(0, text.length - 1);
|
||||
}
|
||||
let lines = text.split(/\r\n|\r|\n/);
|
||||
if (lines.length === selections.length) {
|
||||
return lines;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,10 @@ interface IFindWordResult {
|
||||
* The word type.
|
||||
*/
|
||||
wordType: WordType;
|
||||
/**
|
||||
* The reason the word ended.
|
||||
*/
|
||||
nextCharClass: WordCharacterClass;
|
||||
}
|
||||
|
||||
const enum WordType {
|
||||
@@ -39,9 +43,9 @@ export const enum WordNavigationType {
|
||||
|
||||
export class WordOperations {
|
||||
|
||||
private static _createWord(lineContent: string, wordType: WordType, start: number, end: number): IFindWordResult {
|
||||
private static _createWord(lineContent: string, wordType: WordType, nextCharClass: WordCharacterClass, start: number, end: number): IFindWordResult {
|
||||
// console.log('WORD ==> ' + start + ' => ' + end + ':::: <<<' + lineContent.substring(start, end) + '>>>');
|
||||
return { start: start, end: end, wordType: wordType };
|
||||
return { start: start, end: end, wordType: wordType, nextCharClass: nextCharClass };
|
||||
}
|
||||
|
||||
private static _findPreviousWordOnLine(wordSeparators: WordCharacterClassifier, model: ICursorSimpleModel, position: Position): IFindWordResult {
|
||||
@@ -57,23 +61,23 @@ export class WordOperations {
|
||||
|
||||
if (chClass === WordCharacterClass.Regular) {
|
||||
if (wordType === WordType.Separator) {
|
||||
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
}
|
||||
wordType = WordType.Regular;
|
||||
} else if (chClass === WordCharacterClass.WordSeparator) {
|
||||
if (wordType === WordType.Regular) {
|
||||
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
}
|
||||
wordType = WordType.Separator;
|
||||
} else if (chClass === WordCharacterClass.Whitespace) {
|
||||
if (wordType !== WordType.None) {
|
||||
return this._createWord(lineContent, wordType, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
return this._createWord(lineContent, wordType, chClass, chIndex + 1, this._findEndOfWord(lineContent, wordSeparators, wordType, chIndex + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.None) {
|
||||
return this._createWord(lineContent, wordType, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0));
|
||||
return this._createWord(lineContent, wordType, WordCharacterClass.Whitespace, 0, this._findEndOfWord(lineContent, wordSeparators, wordType, 0));
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -113,23 +117,23 @@ export class WordOperations {
|
||||
|
||||
if (chClass === WordCharacterClass.Regular) {
|
||||
if (wordType === WordType.Separator) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.Regular;
|
||||
} else if (chClass === WordCharacterClass.WordSeparator) {
|
||||
if (wordType === WordType.Regular) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
wordType = WordType.Separator;
|
||||
} else if (chClass === WordCharacterClass.Whitespace) {
|
||||
if (wordType !== WordType.None) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
return this._createWord(lineContent, wordType, chClass, this._findStartOfWord(lineContent, wordSeparators, wordType, chIndex - 1), chIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (wordType !== WordType.None) {
|
||||
return this._createWord(lineContent, wordType, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len);
|
||||
return this._createWord(lineContent, wordType, WordCharacterClass.Whitespace, this._findStartOfWord(lineContent, wordSeparators, wordType, len - 1), len);
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -167,6 +171,12 @@ export class WordOperations {
|
||||
let prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, column));
|
||||
|
||||
if (wordNavigationType === WordNavigationType.WordStart) {
|
||||
if (prevWordOnLine && prevWordOnLine.wordType === WordType.Separator) {
|
||||
if (prevWordOnLine.end - prevWordOnLine.start === 1 && prevWordOnLine.nextCharClass === WordCharacterClass.Regular) {
|
||||
// Skip over a word made up of one single separator and followed by a regular character
|
||||
prevWordOnLine = WordOperations._findPreviousWordOnLine(wordSeparators, model, new Position(lineNumber, prevWordOnLine.start + 1));
|
||||
}
|
||||
}
|
||||
if (prevWordOnLine) {
|
||||
column = prevWordOnLine.start + 1;
|
||||
} else {
|
||||
@@ -200,6 +210,12 @@ export class WordOperations {
|
||||
let nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, column));
|
||||
|
||||
if (wordNavigationType === WordNavigationType.WordEnd) {
|
||||
if (nextWordOnLine && nextWordOnLine.wordType === WordType.Separator) {
|
||||
if (nextWordOnLine.end - nextWordOnLine.start === 1 && nextWordOnLine.nextCharClass === WordCharacterClass.Regular) {
|
||||
// Skip over a word made up of one single separator and followed by a regular character
|
||||
nextWordOnLine = WordOperations._findNextWordOnLine(wordSeparators, model, new Position(lineNumber, nextWordOnLine.end + 1));
|
||||
}
|
||||
}
|
||||
if (nextWordOnLine) {
|
||||
column = nextWordOnLine.end + 1;
|
||||
} else {
|
||||
@@ -374,20 +390,20 @@ export class WordOperations {
|
||||
public static word(config: CursorConfiguration, model: ICursorSimpleModel, cursor: SingleCursorState, inSelectionMode: boolean, position: Position): SingleCursorState {
|
||||
const wordSeparators = getMapForWordSeparators(config.wordSeparators);
|
||||
let prevWord = WordOperations._findPreviousWordOnLine(wordSeparators, model, position);
|
||||
let isInPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 <= prevWord.end);
|
||||
let nextWord = WordOperations._findNextWordOnLine(wordSeparators, model, position);
|
||||
let isInNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 <= nextWord.end);
|
||||
|
||||
if (!inSelectionMode) {
|
||||
// Entering word selection for the first time
|
||||
const isTouchingPrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start <= position.column - 1 && position.column - 1 <= prevWord.end);
|
||||
const isTouchingNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start <= position.column - 1 && position.column - 1 <= nextWord.end);
|
||||
|
||||
let startColumn: number;
|
||||
let endColumn: number;
|
||||
|
||||
if (isInPrevWord) {
|
||||
if (isTouchingPrevWord) {
|
||||
startColumn = prevWord.start + 1;
|
||||
endColumn = prevWord.end + 1;
|
||||
} else if (isInNextWord) {
|
||||
} else if (isTouchingNextWord) {
|
||||
startColumn = nextWord.start + 1;
|
||||
endColumn = nextWord.end + 1;
|
||||
} else {
|
||||
@@ -409,13 +425,16 @@ export class WordOperations {
|
||||
);
|
||||
}
|
||||
|
||||
const isInsidePrevWord = (prevWord && prevWord.wordType === WordType.Regular && prevWord.start < position.column - 1 && position.column - 1 < prevWord.end);
|
||||
const isInsideNextWord = (nextWord && nextWord.wordType === WordType.Regular && nextWord.start < position.column - 1 && position.column - 1 < nextWord.end);
|
||||
|
||||
let startColumn: number;
|
||||
let endColumn: number;
|
||||
|
||||
if (isInPrevWord) {
|
||||
if (isInsidePrevWord) {
|
||||
startColumn = prevWord.start + 1;
|
||||
endColumn = prevWord.end + 1;
|
||||
} else if (isInNextWord) {
|
||||
} else if (isInsideNextWord) {
|
||||
startColumn = nextWord.start + 1;
|
||||
endColumn = nextWord.end + 1;
|
||||
} else {
|
||||
|
||||
@@ -16,12 +16,14 @@ export class OneCursor {
|
||||
public viewState: SingleCursorState;
|
||||
|
||||
private _selTrackedRange: string;
|
||||
private _trackSelection: boolean;
|
||||
|
||||
constructor(context: CursorContext) {
|
||||
this.modelState = null;
|
||||
this.viewState = null;
|
||||
|
||||
this._selTrackedRange = null;
|
||||
this._trackSelection = true;
|
||||
|
||||
this._setState(
|
||||
context,
|
||||
@@ -31,6 +33,28 @@ export class OneCursor {
|
||||
}
|
||||
|
||||
public dispose(context: CursorContext): void {
|
||||
this._removeTrackedRange(context);
|
||||
}
|
||||
|
||||
public startTrackingSelection(context: CursorContext): void {
|
||||
this._trackSelection = true;
|
||||
this._updateTrackedRange(context);
|
||||
}
|
||||
|
||||
public stopTrackingSelection(context: CursorContext): void {
|
||||
this._trackSelection = false;
|
||||
this._removeTrackedRange(context);
|
||||
}
|
||||
|
||||
private _updateTrackedRange(context: CursorContext): void {
|
||||
if (!this._trackSelection) {
|
||||
// don't track the selection
|
||||
return;
|
||||
}
|
||||
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
|
||||
}
|
||||
|
||||
private _removeTrackedRange(context: CursorContext): void {
|
||||
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, null, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
|
||||
}
|
||||
|
||||
@@ -93,14 +117,9 @@ export class OneCursor {
|
||||
viewState = new SingleCursorState(viewSelectionStart, modelState.selectionStartLeftoverVisibleColumns, viewPosition, modelState.leftoverVisibleColumns);
|
||||
}
|
||||
|
||||
if (this.modelState && this.viewState && this.modelState.equals(modelState) && this.viewState.equals(viewState)) {
|
||||
// No-op, early return
|
||||
return;
|
||||
}
|
||||
|
||||
this.modelState = modelState;
|
||||
this.viewState = viewState;
|
||||
|
||||
this._selTrackedRange = context.model._setTrackedRange(this._selTrackedRange, this.modelState.selection, TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges);
|
||||
this._updateTrackedRange(context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ export interface IViewLineTokens {
|
||||
getEndOffset(tokenIndex: number): number;
|
||||
getClassName(tokenIndex: number): string;
|
||||
getInlineStyle(tokenIndex: number, colorMap: string[]): string;
|
||||
findTokenIndexAtOffset(offset: number): number;
|
||||
}
|
||||
|
||||
export class LineTokens implements IViewLineTokens {
|
||||
@@ -207,4 +208,8 @@ export class SlicedLineTokens implements IViewLineTokens {
|
||||
public getInlineStyle(tokenIndex: number, colorMap: string[]): string {
|
||||
return this._source.getInlineStyle(this._firstTokenIndex + tokenIndex, colorMap);
|
||||
}
|
||||
|
||||
public findTokenIndexAtOffset(offset: number): number {
|
||||
return this._source.findTokenIndexAtOffset(offset + this._startOffset - this._deltaOffset) - this._firstTokenIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,7 @@ class LineChange implements ILineChange {
|
||||
const originalCharSequence = originalLineSequence.getCharSequence(diffChange.originalStart, diffChange.originalStart + diffChange.originalLength - 1);
|
||||
const modifiedCharSequence = modifiedLineSequence.getCharSequence(diffChange.modifiedStart, diffChange.modifiedStart + diffChange.modifiedLength - 1);
|
||||
|
||||
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, false);
|
||||
let rawChanges = computeDiff(originalCharSequence, modifiedCharSequence, continueProcessingPredicate, true);
|
||||
|
||||
if (shouldPostProcessCharChanges) {
|
||||
rawChanges = postProcessCharChanges(rawChanges);
|
||||
|
||||
@@ -199,9 +199,13 @@ export interface ICursorState {
|
||||
* A (serializable) state of the view.
|
||||
*/
|
||||
export interface IViewState {
|
||||
scrollTop: number;
|
||||
scrollTopWithoutViewZones: number;
|
||||
/** written by previous versions */
|
||||
scrollTop?: number;
|
||||
/** written by previous versions */
|
||||
scrollTopWithoutViewZones?: number;
|
||||
scrollLeft: number;
|
||||
firstPosition: IPosition;
|
||||
firstPositionDeltaTop: number;
|
||||
}
|
||||
/**
|
||||
* A (serializable) state of the code editor.
|
||||
@@ -505,6 +509,7 @@ export interface IThemeDecorationRenderOptions {
|
||||
textDecoration?: string;
|
||||
cursor?: string;
|
||||
color?: string | ThemeColor;
|
||||
opacity?: number;
|
||||
letterSpacing?: string;
|
||||
|
||||
gutterIconPath?: string | UriComponents;
|
||||
|
||||
@@ -10,12 +10,17 @@ export namespace EditorContextKeys {
|
||||
/**
|
||||
* A context key that is set when the editor's text has focus (cursor is blinking).
|
||||
*/
|
||||
export const textFocus = new RawContextKey<boolean>('editorTextFocus', false);
|
||||
export const editorTextFocus = new RawContextKey<boolean>('editorTextFocus', false);
|
||||
/**
|
||||
* A context key that is set when the editor's text or an editor's widget has focus.
|
||||
*/
|
||||
export const focus = new RawContextKey<boolean>('editorFocus', false);
|
||||
|
||||
/**
|
||||
* A context key that is set when any editor input has focus (regular editor, repl input...).
|
||||
*/
|
||||
export const textInputFocus = new RawContextKey<boolean>('textInputFocus', false);
|
||||
|
||||
export const readOnly = new RawContextKey<boolean>('editorReadonly', false);
|
||||
export const writable: ContextKeyExpr = readOnly.toNegated();
|
||||
export const hasNonEmptySelection = new RawContextKey<boolean>('editorHasSelection', false);
|
||||
|
||||
@@ -15,6 +15,7 @@ import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { ModelRawContentChangedEvent, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent, IModelContentChange } from 'vs/editor/common/model/textModelEvents';
|
||||
import { ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
|
||||
/**
|
||||
* Vertical Lane in the overview ruler of the editor.
|
||||
@@ -80,7 +81,12 @@ export interface IModelDecorationOptions {
|
||||
* Always render the decoration (even when the range it encompasses is collapsed).
|
||||
* @internal
|
||||
*/
|
||||
readonly showIfCollapsed?: boolean;
|
||||
showIfCollapsed?: boolean;
|
||||
/**
|
||||
* Specifies the stack order of a decoration.
|
||||
* A decoration with greater stack order is always in front of a decoration with a lower stack order.
|
||||
*/
|
||||
zIndex?: number;
|
||||
/**
|
||||
* If set, render this decoration in the overview ruler.
|
||||
*/
|
||||
@@ -103,6 +109,10 @@ export interface IModelDecorationOptions {
|
||||
* to have a background color decoration.
|
||||
*/
|
||||
inlineClassName?: string;
|
||||
/**
|
||||
* If there is an `inlineClassName` which affects letter spacing.
|
||||
*/
|
||||
inlineClassNameAffectsLetterSpacing?: boolean;
|
||||
/**
|
||||
* If set, the decoration will be rendered before the text with this CSS class name.
|
||||
*/
|
||||
@@ -389,6 +399,8 @@ export interface ITextModelCreationOptions {
|
||||
detectIndentation: boolean;
|
||||
trimAutoWhitespace: boolean;
|
||||
defaultEOL: DefaultEndOfLine;
|
||||
isForSimpleWidget: boolean;
|
||||
largeFileOptimizations: boolean;
|
||||
}
|
||||
|
||||
export interface ITextModelUpdateOptions {
|
||||
@@ -433,6 +445,15 @@ export enum TrackedRangeStickiness {
|
||||
GrowsOnlyWhenTypingAfter = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IActiveIndentGuideInfo {
|
||||
startLineNumber: number;
|
||||
endLineNumber: number;
|
||||
indent: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* A model.
|
||||
*/
|
||||
@@ -448,6 +469,12 @@ export interface ITextModel {
|
||||
*/
|
||||
readonly id: string;
|
||||
|
||||
/**
|
||||
* This model is constructed for a simple widget code editor.
|
||||
* @internal
|
||||
*/
|
||||
readonly isForSimpleWidget: boolean;
|
||||
|
||||
/**
|
||||
* If true, the text model might contain RTL.
|
||||
* If false, the text model **contains only** contain LTR.
|
||||
@@ -553,6 +580,10 @@ export interface ITextModel {
|
||||
*/
|
||||
getLineContent(lineNumber: number): string;
|
||||
|
||||
/**
|
||||
* Get the text length for a certain line.
|
||||
*/
|
||||
getLineLength(lineNumber: number): number;
|
||||
|
||||
/**
|
||||
* Get the text for all lines.
|
||||
@@ -643,11 +674,16 @@ export interface ITextModel {
|
||||
isDisposed(): boolean;
|
||||
|
||||
/**
|
||||
* Only basic mode supports allowed on this model because it is simply too large.
|
||||
* (tokenization is allowed and other basic supports)
|
||||
* @internal
|
||||
*/
|
||||
isTooLargeForHavingARichMode(): boolean;
|
||||
tokenizeViewport(startLineNumber: number, endLineNumber: number): void;
|
||||
|
||||
/**
|
||||
* This model is so large that it would not be a good idea to sync it over
|
||||
* to web workers or other places.
|
||||
* @internal
|
||||
*/
|
||||
isTooLargeForSyncing(): boolean;
|
||||
|
||||
/**
|
||||
* The file is so large, that even tokenization is disabled.
|
||||
@@ -829,6 +865,11 @@ export interface ITextModel {
|
||||
*/
|
||||
matchBracket(position: IPosition): [Range, Range];
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
@@ -989,6 +1030,13 @@ export interface ITextModel {
|
||||
*/
|
||||
redo(): Selection[];
|
||||
|
||||
/**
|
||||
* @deprecated Please use `onDidChangeContent` instead.
|
||||
* An event emitted when the contents of the model have changed.
|
||||
* @internal
|
||||
* @event
|
||||
*/
|
||||
onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable;
|
||||
/**
|
||||
* @deprecated Please use `onDidChangeContent` instead.
|
||||
* An event emitted when the contents of the model have changed.
|
||||
@@ -1054,6 +1102,12 @@ export interface ITextModel {
|
||||
* @internal
|
||||
*/
|
||||
isAttachedToEditor(): boolean;
|
||||
|
||||
/**
|
||||
* Returns the count of editors this model is attached to.
|
||||
* @internal
|
||||
*/
|
||||
getAttachedEditorCount(): number;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1100,6 +1154,7 @@ export interface ITextBuffer {
|
||||
|
||||
setEOL(newEOL: '\r\n' | '\n'): void;
|
||||
applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult;
|
||||
findMatchesLineByLine?(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1120,6 +1175,5 @@ export class ApplyEditsResult {
|
||||
*/
|
||||
export interface IInternalModelContentChange extends IModelContentChange {
|
||||
range: Range;
|
||||
rangeOffset: number;
|
||||
forceMoveMarkers: boolean;
|
||||
}
|
||||
|
||||
@@ -1,323 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export class LeafOffsetLenEdit {
|
||||
constructor(
|
||||
public readonly start: number,
|
||||
public readonly length: number,
|
||||
public readonly text: string
|
||||
) { }
|
||||
}
|
||||
|
||||
export class BufferPiece {
|
||||
private readonly _str: string;
|
||||
public get text(): string { return this._str; }
|
||||
|
||||
private readonly _lineStarts: Uint32Array;
|
||||
|
||||
constructor(str: string, lineStarts: Uint32Array = null) {
|
||||
this._str = str;
|
||||
if (lineStarts === null) {
|
||||
this._lineStarts = createLineStartsFast(str);
|
||||
} else {
|
||||
this._lineStarts = lineStarts;
|
||||
}
|
||||
}
|
||||
|
||||
public length(): number {
|
||||
return this._str.length;
|
||||
}
|
||||
|
||||
public newLineCount(): number {
|
||||
return this._lineStarts.length;
|
||||
}
|
||||
|
||||
public lineStartFor(relativeLineIndex: number): number {
|
||||
return this._lineStarts[relativeLineIndex];
|
||||
}
|
||||
|
||||
public charCodeAt(index: number): number {
|
||||
return this._str.charCodeAt(index);
|
||||
}
|
||||
|
||||
public substr(from: number, length: number): string {
|
||||
return this._str.substr(from, length);
|
||||
}
|
||||
|
||||
public findLineStartBeforeOffset(offset: number): number {
|
||||
if (this._lineStarts.length === 0 || offset < this._lineStarts[0]) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let low = 0, high = this._lineStarts.length - 1;
|
||||
|
||||
while (low < high) {
|
||||
let mid = low + Math.ceil((high - low) / 2);
|
||||
let lineStart = this._lineStarts[mid];
|
||||
|
||||
if (offset === lineStart) {
|
||||
return mid;
|
||||
} else if (offset < lineStart) {
|
||||
high = mid - 1;
|
||||
} else {
|
||||
low = mid;
|
||||
}
|
||||
}
|
||||
|
||||
return low;
|
||||
}
|
||||
|
||||
public findLineFirstNonWhitespaceIndex(searchStartOffset: number): number {
|
||||
for (let i = searchStartOffset, len = this._str.length; i < len; i++) {
|
||||
const chCode = this._str.charCodeAt(i);
|
||||
if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) {
|
||||
// Reached EOL
|
||||
return -2;
|
||||
}
|
||||
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public findLineLastNonWhitespaceIndex(searchStartOffset: number): number {
|
||||
for (let i = searchStartOffset - 1; i >= 0; i--) {
|
||||
const chCode = this._str.charCodeAt(i);
|
||||
if (chCode === CharCode.CarriageReturn || chCode === CharCode.LineFeed) {
|
||||
// Reached EOL
|
||||
return -2;
|
||||
}
|
||||
if (chCode !== CharCode.Space && chCode !== CharCode.Tab) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static normalizeEOL(target: BufferPiece, eol: '\r\n' | '\n'): BufferPiece {
|
||||
return new BufferPiece(target._str.replace(/\r\n|\r|\n/g, eol));
|
||||
}
|
||||
|
||||
public static deleteLastChar(target: BufferPiece): BufferPiece {
|
||||
const targetCharsLength = target.length();
|
||||
const targetLineStartsLength = target.newLineCount();
|
||||
const targetLineStarts = target._lineStarts;
|
||||
|
||||
let newLineStartsLength;
|
||||
if (targetLineStartsLength > 0 && targetLineStarts[targetLineStartsLength - 1] === targetCharsLength) {
|
||||
newLineStartsLength = targetLineStartsLength - 1;
|
||||
} else {
|
||||
newLineStartsLength = targetLineStartsLength;
|
||||
}
|
||||
|
||||
let newLineStarts = new Uint32Array(newLineStartsLength);
|
||||
newLineStarts.set(targetLineStarts);
|
||||
|
||||
return new BufferPiece(
|
||||
target._str.substr(0, targetCharsLength - 1),
|
||||
newLineStarts
|
||||
);
|
||||
}
|
||||
|
||||
public static insertFirstChar(target: BufferPiece, character: number): BufferPiece {
|
||||
const targetLineStartsLength = target.newLineCount();
|
||||
const targetLineStarts = target._lineStarts;
|
||||
const insertLineStart = ((character === CharCode.CarriageReturn && (targetLineStartsLength === 0 || targetLineStarts[0] !== 1 || target.charCodeAt(0) !== CharCode.LineFeed)) || (character === CharCode.LineFeed));
|
||||
|
||||
const newLineStartsLength = (insertLineStart ? targetLineStartsLength + 1 : targetLineStartsLength);
|
||||
let newLineStarts = new Uint32Array(newLineStartsLength);
|
||||
|
||||
if (insertLineStart) {
|
||||
newLineStarts[0] = 1;
|
||||
for (let i = 0; i < targetLineStartsLength; i++) {
|
||||
newLineStarts[i + 1] = targetLineStarts[i] + 1;
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < targetLineStartsLength; i++) {
|
||||
newLineStarts[i] = targetLineStarts[i] + 1;
|
||||
}
|
||||
}
|
||||
|
||||
return new BufferPiece(
|
||||
String.fromCharCode(character) + target._str,
|
||||
newLineStarts
|
||||
);
|
||||
}
|
||||
|
||||
public static join(first: BufferPiece, second: BufferPiece): BufferPiece {
|
||||
const firstCharsLength = first._str.length;
|
||||
|
||||
const firstLineStartsLength = first._lineStarts.length;
|
||||
const secondLineStartsLength = second._lineStarts.length;
|
||||
|
||||
const firstLineStarts = first._lineStarts;
|
||||
const secondLineStarts = second._lineStarts;
|
||||
|
||||
const newLineStartsLength = firstLineStartsLength + secondLineStartsLength;
|
||||
let newLineStarts = new Uint32Array(newLineStartsLength);
|
||||
newLineStarts.set(firstLineStarts, 0);
|
||||
for (let i = 0; i < secondLineStartsLength; i++) {
|
||||
newLineStarts[i + firstLineStartsLength] = secondLineStarts[i] + firstCharsLength;
|
||||
}
|
||||
|
||||
return new BufferPiece(first._str + second._str, newLineStarts);
|
||||
}
|
||||
|
||||
public static replaceOffsetLen(target: BufferPiece, edits: LeafOffsetLenEdit[], idealLeafLength: number, maxLeafLength: number, result: BufferPiece[]): void {
|
||||
const editsSize = edits.length;
|
||||
const originalCharsLength = target.length();
|
||||
if (editsSize === 1 && edits[0].text.length === 0 && edits[0].start === 0 && edits[0].length === originalCharsLength) {
|
||||
// special case => deleting everything
|
||||
return;
|
||||
}
|
||||
|
||||
let pieces: string[] = new Array<string>(2 * editsSize + 1);
|
||||
let originalFromIndex = 0;
|
||||
let piecesTextLength = 0;
|
||||
for (let i = 0; i < editsSize; i++) {
|
||||
const edit = edits[i];
|
||||
|
||||
const originalText = target._str.substr(originalFromIndex, edit.start - originalFromIndex);
|
||||
pieces[2 * i] = originalText;
|
||||
piecesTextLength += originalText.length;
|
||||
|
||||
originalFromIndex = edit.start + edit.length;
|
||||
pieces[2 * i + 1] = edit.text;
|
||||
piecesTextLength += edit.text.length;
|
||||
}
|
||||
|
||||
// maintain the chars that survive to the right of the last edit
|
||||
let text = target._str.substr(originalFromIndex, originalCharsLength - originalFromIndex);
|
||||
pieces[2 * editsSize] = text;
|
||||
piecesTextLength += text.length;
|
||||
|
||||
let targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength;
|
||||
let targetDataOffset = 0;
|
||||
|
||||
let data: string = '';
|
||||
|
||||
for (let pieceIndex = 0, pieceCount = pieces.length; pieceIndex < pieceCount; pieceIndex++) {
|
||||
const pieceText = pieces[pieceIndex];
|
||||
const pieceLength = pieceText.length;
|
||||
if (pieceLength === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let pieceOffset = 0;
|
||||
while (pieceOffset < pieceLength) {
|
||||
if (targetDataOffset >= targetDataLength) {
|
||||
result.push(new BufferPiece(data));
|
||||
targetDataLength = piecesTextLength > maxLeafLength ? idealLeafLength : piecesTextLength;
|
||||
targetDataOffset = 0;
|
||||
data = '';
|
||||
}
|
||||
|
||||
let writingCnt = min(pieceLength - pieceOffset, targetDataLength - targetDataOffset);
|
||||
data += pieceText.substr(pieceOffset, writingCnt);
|
||||
pieceOffset += writingCnt;
|
||||
targetDataOffset += writingCnt;
|
||||
piecesTextLength -= writingCnt;
|
||||
|
||||
// check that the buffer piece does not end in a \r or high surrogate
|
||||
if (targetDataOffset === targetDataLength && piecesTextLength > 0) {
|
||||
const lastChar = data.charCodeAt(targetDataLength - 1);
|
||||
if (lastChar === CharCode.CarriageReturn || (0xD800 <= lastChar && lastChar <= 0xDBFF)) {
|
||||
// move lastChar over to next buffer piece
|
||||
targetDataLength -= 1;
|
||||
pieceOffset -= 1;
|
||||
targetDataOffset -= 1;
|
||||
piecesTextLength += 1;
|
||||
data = data.substr(0, data.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push(new BufferPiece(data));
|
||||
}
|
||||
}
|
||||
|
||||
function min(a: number, b: number): number {
|
||||
return (a < b ? a : b);
|
||||
}
|
||||
|
||||
export function createUint32Array(arr: number[]): Uint32Array {
|
||||
let r = new Uint32Array(arr.length);
|
||||
r.set(arr, 0);
|
||||
return r;
|
||||
}
|
||||
|
||||
export class LineStarts {
|
||||
constructor(
|
||||
public readonly lineStarts: Uint32Array,
|
||||
public readonly cr: number,
|
||||
public readonly lf: number,
|
||||
public readonly crlf: number,
|
||||
public readonly isBasicASCII: boolean
|
||||
) { }
|
||||
}
|
||||
|
||||
export function createLineStartsFast(str: string): Uint32Array {
|
||||
let r: number[] = [], rLength = 0;
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
const chr = str.charCodeAt(i);
|
||||
|
||||
if (chr === CharCode.CarriageReturn) {
|
||||
if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) {
|
||||
// \r\n... case
|
||||
r[rLength++] = i + 2;
|
||||
i++; // skip \n
|
||||
} else {
|
||||
// \r... case
|
||||
r[rLength++] = i + 1;
|
||||
}
|
||||
} else if (chr === CharCode.LineFeed) {
|
||||
r[rLength++] = i + 1;
|
||||
}
|
||||
}
|
||||
return createUint32Array(r);
|
||||
}
|
||||
|
||||
export function createLineStarts(r: number[], str: string): LineStarts {
|
||||
r.length = 0;
|
||||
|
||||
let rLength = 0;
|
||||
let cr = 0, lf = 0, crlf = 0;
|
||||
let isBasicASCII = true;
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
const chr = str.charCodeAt(i);
|
||||
|
||||
if (chr === CharCode.CarriageReturn) {
|
||||
if (i + 1 < len && str.charCodeAt(i + 1) === CharCode.LineFeed) {
|
||||
// \r\n... case
|
||||
crlf++;
|
||||
r[rLength++] = i + 2;
|
||||
i++; // skip \n
|
||||
} else {
|
||||
cr++;
|
||||
// \r... case
|
||||
r[rLength++] = i + 1;
|
||||
}
|
||||
} else if (chr === CharCode.LineFeed) {
|
||||
lf++;
|
||||
r[rLength++] = i + 1;
|
||||
} else {
|
||||
if (isBasicASCII) {
|
||||
if (chr !== CharCode.Tab && (chr < 32 || chr > 126)) {
|
||||
isBasicASCII = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const result = new LineStarts(createUint32Array(r), cr, lf, crlf, isBasicASCII);
|
||||
r.length = 0;
|
||||
|
||||
return result;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,186 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { BufferPiece, createLineStarts } from 'vs/editor/common/model/chunksTextBuffer/bufferPiece';
|
||||
import { ChunksTextBuffer } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBuffer';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
|
||||
export class TextBufferFactory implements ITextBufferFactory {
|
||||
|
||||
constructor(
|
||||
private readonly _pieces: BufferPiece[],
|
||||
private readonly _averageChunkSize: number,
|
||||
private readonly _BOM: string,
|
||||
private readonly _cr: number,
|
||||
private readonly _lf: number,
|
||||
private readonly _crlf: number,
|
||||
private readonly _containsRTL: boolean,
|
||||
private readonly _isBasicASCII: boolean,
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* if text source is empty or with precisely one line, returns null. No end of line is detected.
|
||||
* if text source contains more lines ending with '\r\n', returns '\r\n'.
|
||||
* Otherwise returns '\n'. More lines end with '\n'.
|
||||
*/
|
||||
private _getEOL(defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {
|
||||
const totalEOLCount = this._cr + this._lf + this._crlf;
|
||||
const totalCRCount = this._cr + this._crlf;
|
||||
if (totalEOLCount === 0) {
|
||||
// This is an empty file or a file with precisely one line
|
||||
return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');
|
||||
}
|
||||
if (totalCRCount > totalEOLCount / 2) {
|
||||
// More than half of the file contains \r\n ending lines
|
||||
return '\r\n';
|
||||
}
|
||||
// At least one line more ends in \n
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public create(defaultEOL: DefaultEndOfLine): ITextBuffer {
|
||||
const eol = this._getEOL(defaultEOL);
|
||||
let pieces = this._pieces;
|
||||
|
||||
if (
|
||||
(eol === '\r\n' && (this._cr > 0 || this._lf > 0))
|
||||
|| (eol === '\n' && (this._cr > 0 || this._crlf > 0))
|
||||
) {
|
||||
// Normalize pieces
|
||||
for (let i = 0, len = pieces.length; i < len; i++) {
|
||||
pieces[i] = BufferPiece.normalizeEOL(pieces[i], eol);
|
||||
}
|
||||
}
|
||||
return new ChunksTextBuffer(pieces, this._averageChunkSize, this._BOM, eol, this._containsRTL, this._isBasicASCII);
|
||||
}
|
||||
|
||||
public getFirstLineText(lengthLimit: number): string {
|
||||
const firstPiece = this._pieces[0];
|
||||
if (firstPiece.newLineCount() === 0) {
|
||||
return firstPiece.substr(0, lengthLimit);
|
||||
}
|
||||
|
||||
const firstEOLOffset = firstPiece.lineStartFor(0);
|
||||
return firstPiece.substr(0, Math.min(lengthLimit, firstEOLOffset));
|
||||
}
|
||||
}
|
||||
|
||||
export class ChunksTextBufferBuilder implements ITextBufferBuilder {
|
||||
|
||||
private _rawPieces: BufferPiece[];
|
||||
private _hasPreviousChar: boolean;
|
||||
private _previousChar: number;
|
||||
private _averageChunkSize: number;
|
||||
private _tmpLineStarts: number[];
|
||||
|
||||
private BOM: string;
|
||||
private cr: number;
|
||||
private lf: number;
|
||||
private crlf: number;
|
||||
private containsRTL: boolean;
|
||||
private isBasicASCII: boolean;
|
||||
|
||||
constructor() {
|
||||
this._rawPieces = [];
|
||||
this._hasPreviousChar = false;
|
||||
this._previousChar = 0;
|
||||
this._averageChunkSize = 0;
|
||||
this._tmpLineStarts = [];
|
||||
|
||||
this.BOM = '';
|
||||
this.cr = 0;
|
||||
this.lf = 0;
|
||||
this.crlf = 0;
|
||||
this.containsRTL = false;
|
||||
this.isBasicASCII = true;
|
||||
}
|
||||
|
||||
public acceptChunk(chunk: string): void {
|
||||
if (chunk.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._rawPieces.length === 0) {
|
||||
if (strings.startsWithUTF8BOM(chunk)) {
|
||||
this.BOM = strings.UTF8_BOM_CHARACTER;
|
||||
chunk = chunk.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
this._averageChunkSize = (this._averageChunkSize * this._rawPieces.length + chunk.length) / (this._rawPieces.length + 1);
|
||||
|
||||
const lastChar = chunk.charCodeAt(chunk.length - 1);
|
||||
if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xd800 && lastChar <= 0xdbff)) {
|
||||
// last character is \r or a high surrogate => keep it back
|
||||
this._acceptChunk1(chunk.substr(0, chunk.length - 1), false);
|
||||
this._hasPreviousChar = true;
|
||||
this._previousChar = lastChar;
|
||||
} else {
|
||||
this._acceptChunk1(chunk, false);
|
||||
this._hasPreviousChar = false;
|
||||
this._previousChar = lastChar;
|
||||
}
|
||||
}
|
||||
|
||||
private _acceptChunk1(chunk: string, allowEmptyStrings: boolean): void {
|
||||
if (!allowEmptyStrings && chunk.length === 0) {
|
||||
// Nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._hasPreviousChar) {
|
||||
this._acceptChunk2(chunk + String.fromCharCode(this._previousChar));
|
||||
} else {
|
||||
this._acceptChunk2(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
private _acceptChunk2(chunk: string): void {
|
||||
const lineStarts = createLineStarts(this._tmpLineStarts, chunk);
|
||||
|
||||
this._rawPieces.push(new BufferPiece(chunk, lineStarts.lineStarts));
|
||||
this.cr += lineStarts.cr;
|
||||
this.lf += lineStarts.lf;
|
||||
this.crlf += lineStarts.crlf;
|
||||
|
||||
if (this.isBasicASCII) {
|
||||
this.isBasicASCII = lineStarts.isBasicASCII;
|
||||
}
|
||||
if (!this.isBasicASCII && !this.containsRTL) {
|
||||
// No need to check if is basic ASCII
|
||||
this.containsRTL = strings.containsRTL(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
public finish(): TextBufferFactory {
|
||||
this._finish();
|
||||
return new TextBufferFactory(this._rawPieces, this._averageChunkSize, this.BOM, this.cr, this.lf, this.crlf, this.containsRTL, this.isBasicASCII);
|
||||
}
|
||||
|
||||
private _finish(): void {
|
||||
if (this._rawPieces.length === 0) {
|
||||
// no chunks => forcefully go through accept chunk
|
||||
this._acceptChunk1('', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._hasPreviousChar) {
|
||||
this._hasPreviousChar = false;
|
||||
|
||||
// recreate last chunk
|
||||
const lastPiece = this._rawPieces[this._rawPieces.length - 1];
|
||||
const tmp = new BufferPiece(String.fromCharCode(this._previousChar));
|
||||
const newLastPiece = BufferPiece.join(lastPiece, tmp);
|
||||
this._rawPieces[this._rawPieces.length - 1] = newLastPiece;
|
||||
if (this._previousChar === CharCode.CarriageReturn) {
|
||||
this.cr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,13 +12,11 @@ import { IModelDecoration } from 'vs/editor/common/model';
|
||||
// The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest.
|
||||
//
|
||||
|
||||
/**
|
||||
* The class name sort order must match the severity order. Highest severity last.
|
||||
*/
|
||||
export const ClassName = {
|
||||
EditorInfoDecoration: 'squiggly-a-info',
|
||||
EditorWarningDecoration: 'squiggly-b-warning',
|
||||
EditorErrorDecoration: 'squiggly-c-error'
|
||||
EditorHintDecoration: 'squiggly-hint',
|
||||
EditorInfoDecoration: 'squiggly-info',
|
||||
EditorWarningDecoration: 'squiggly-warning',
|
||||
EditorErrorDecoration: 'squiggly-error'
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,660 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
|
||||
import { ISingleEditOperationIdentifier, IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange } from 'vs/editor/common/model';
|
||||
import { ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
|
||||
export interface IValidatedEditOperation {
|
||||
sortIndex: number;
|
||||
identifier: ISingleEditOperationIdentifier;
|
||||
range: Range;
|
||||
rangeOffset: number;
|
||||
rangeLength: number;
|
||||
lines: string[];
|
||||
forceMoveMarkers: boolean;
|
||||
isAutoWhitespaceEdit: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* A processed string with its EOL resolved ready to be turned into an editor model.
|
||||
*/
|
||||
export interface ITextSource {
|
||||
/**
|
||||
* The text split into lines.
|
||||
*/
|
||||
readonly lines: string[];
|
||||
/**
|
||||
* The BOM (leading character sequence of the file).
|
||||
*/
|
||||
readonly BOM: string;
|
||||
/**
|
||||
* The end of line sequence.
|
||||
*/
|
||||
readonly EOL: string;
|
||||
/**
|
||||
* The text contains Unicode characters classified as "R" or "AL".
|
||||
*/
|
||||
readonly containsRTL: boolean;
|
||||
/**
|
||||
* The text contains only characters inside the ASCII range 32-126 or \t \r \n
|
||||
*/
|
||||
readonly isBasicASCII: boolean;
|
||||
}
|
||||
|
||||
class LinesTextBufferSnapshot implements ITextSnapshot {
|
||||
|
||||
private readonly _lines: string[];
|
||||
private readonly _linesLength: number;
|
||||
private readonly _eol: string;
|
||||
private readonly _bom: string;
|
||||
private _lineIndex: number;
|
||||
|
||||
constructor(lines: string[], eol: string, bom: string) {
|
||||
this._lines = lines;
|
||||
this._linesLength = this._lines.length;
|
||||
this._eol = eol;
|
||||
this._bom = bom;
|
||||
this._lineIndex = 0;
|
||||
}
|
||||
|
||||
public read(): string {
|
||||
if (this._lineIndex >= this._linesLength) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let result: string = null;
|
||||
|
||||
if (this._lineIndex === 0) {
|
||||
result = this._bom + this._lines[this._lineIndex];
|
||||
} else {
|
||||
result = this._lines[this._lineIndex];
|
||||
}
|
||||
|
||||
this._lineIndex++;
|
||||
|
||||
if (this._lineIndex < this._linesLength) {
|
||||
result += this._eol;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export class LinesTextBuffer implements ITextBuffer {
|
||||
|
||||
private _lines: string[];
|
||||
private _BOM: string;
|
||||
private _EOL: string;
|
||||
private _mightContainRTL: boolean;
|
||||
private _mightContainNonBasicASCII: boolean;
|
||||
private _lineStarts: PrefixSumComputer;
|
||||
|
||||
constructor(textSource: ITextSource) {
|
||||
this._lines = textSource.lines.slice(0);
|
||||
this._BOM = textSource.BOM;
|
||||
this._EOL = textSource.EOL;
|
||||
this._mightContainRTL = textSource.containsRTL;
|
||||
this._mightContainNonBasicASCII = !textSource.isBasicASCII;
|
||||
this._constructLineStarts();
|
||||
}
|
||||
|
||||
private _constructLineStarts(): void {
|
||||
const eolLength = this._EOL.length;
|
||||
const linesLength = this._lines.length;
|
||||
const lineStartValues = new Uint32Array(linesLength);
|
||||
for (let i = 0; i < linesLength; i++) {
|
||||
lineStartValues[i] = this._lines[i].length + eolLength;
|
||||
}
|
||||
this._lineStarts = new PrefixSumComputer(lineStartValues);
|
||||
}
|
||||
|
||||
public equals(other: ITextBuffer): boolean {
|
||||
if (!(other instanceof LinesTextBuffer)) {
|
||||
return false;
|
||||
}
|
||||
if (this._BOM !== other._BOM) {
|
||||
return false;
|
||||
}
|
||||
if (this._EOL !== other._EOL) {
|
||||
return false;
|
||||
}
|
||||
if (this._lines.length !== other._lines.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0, len = this._lines.length; i < len; i++) {
|
||||
if (this._lines[i] !== other._lines[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public mightContainRTL(): boolean {
|
||||
return this._mightContainRTL;
|
||||
}
|
||||
|
||||
public mightContainNonBasicASCII(): boolean {
|
||||
return this._mightContainNonBasicASCII;
|
||||
}
|
||||
|
||||
public getBOM(): string {
|
||||
return this._BOM;
|
||||
}
|
||||
|
||||
public getEOL(): string {
|
||||
return this._EOL;
|
||||
}
|
||||
|
||||
public getOffsetAt(lineNumber: number, column: number): number {
|
||||
return this._lineStarts.getAccumulatedValue(lineNumber - 2) + column - 1;
|
||||
}
|
||||
|
||||
public getPositionAt(offset: number): Position {
|
||||
offset = Math.floor(offset);
|
||||
offset = Math.max(0, offset);
|
||||
|
||||
let out = this._lineStarts.getIndexOf(offset);
|
||||
|
||||
let lineLength = this._lines[out.index].length;
|
||||
|
||||
// Ensure we return a valid position
|
||||
return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1));
|
||||
}
|
||||
|
||||
public getRangeAt(offset: number, length: number): Range {
|
||||
const startResult = this._lineStarts.getIndexOf(offset);
|
||||
const startLineLength = this._lines[startResult.index].length;
|
||||
const startColumn = Math.min(startResult.remainder + 1, startLineLength + 1);
|
||||
|
||||
const endResult = this._lineStarts.getIndexOf(offset + length);
|
||||
const endLineLength = this._lines[endResult.index].length;
|
||||
const endColumn = Math.min(endResult.remainder + 1, endLineLength + 1);
|
||||
|
||||
return new Range(startResult.index + 1, startColumn, endResult.index + 1, endColumn);
|
||||
}
|
||||
|
||||
private _getEndOfLine(eol: EndOfLinePreference): string {
|
||||
switch (eol) {
|
||||
case EndOfLinePreference.LF:
|
||||
return '\n';
|
||||
case EndOfLinePreference.CRLF:
|
||||
return '\r\n';
|
||||
case EndOfLinePreference.TextDefined:
|
||||
return this.getEOL();
|
||||
}
|
||||
throw new Error('Unknown EOL preference');
|
||||
}
|
||||
|
||||
public getValueInRange(range: Range, eol: EndOfLinePreference): string {
|
||||
if (range.isEmpty()) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (range.startLineNumber === range.endLineNumber) {
|
||||
return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1);
|
||||
}
|
||||
|
||||
const lineEnding = this._getEndOfLine(eol);
|
||||
const startLineIndex = range.startLineNumber - 1;
|
||||
const endLineIndex = range.endLineNumber - 1;
|
||||
let resultLines: string[] = [];
|
||||
|
||||
resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1));
|
||||
for (let i = startLineIndex + 1; i < endLineIndex; i++) {
|
||||
resultLines.push(this._lines[i]);
|
||||
}
|
||||
resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1));
|
||||
|
||||
return resultLines.join(lineEnding);
|
||||
}
|
||||
|
||||
public createSnapshot(preserveBOM: boolean): ITextSnapshot {
|
||||
return new LinesTextBufferSnapshot(this._lines.slice(0), this._EOL, preserveBOM ? this._BOM : '');
|
||||
}
|
||||
|
||||
public getValueLengthInRange(range: Range, eol: EndOfLinePreference): number {
|
||||
if (range.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (range.startLineNumber === range.endLineNumber) {
|
||||
return (range.endColumn - range.startColumn);
|
||||
}
|
||||
|
||||
let startOffset = this.getOffsetAt(range.startLineNumber, range.startColumn);
|
||||
let endOffset = this.getOffsetAt(range.endLineNumber, range.endColumn);
|
||||
return endOffset - startOffset;
|
||||
}
|
||||
|
||||
public getLineCount(): number {
|
||||
return this._lines.length;
|
||||
}
|
||||
|
||||
public getLinesContent(): string[] {
|
||||
return this._lines.slice(0);
|
||||
}
|
||||
|
||||
public getLength(): number {
|
||||
return this._lineStarts.getTotalValue();
|
||||
}
|
||||
|
||||
public getLineContent(lineNumber: number): string {
|
||||
return this._lines[lineNumber - 1];
|
||||
}
|
||||
|
||||
public getLineCharCode(lineNumber: number, index: number): number {
|
||||
return this._lines[lineNumber - 1].charCodeAt(index);
|
||||
}
|
||||
|
||||
public getLineLength(lineNumber: number): number {
|
||||
return this._lines[lineNumber - 1].length;
|
||||
}
|
||||
|
||||
public getLineFirstNonWhitespaceColumn(lineNumber: number): number {
|
||||
const result = strings.firstNonWhitespaceIndex(this._lines[lineNumber - 1]);
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 1;
|
||||
}
|
||||
|
||||
public getLineLastNonWhitespaceColumn(lineNumber: number): number {
|
||||
const result = strings.lastNonWhitespaceIndex(this._lines[lineNumber - 1]);
|
||||
if (result === -1) {
|
||||
return 0;
|
||||
}
|
||||
return result + 2;
|
||||
}
|
||||
|
||||
//#region Editing
|
||||
|
||||
public setEOL(newEOL: '\r\n' | '\n'): void {
|
||||
this._EOL = newEOL;
|
||||
this._constructLineStarts();
|
||||
}
|
||||
|
||||
private static _sortOpsAscending(a: IValidatedEditOperation, b: IValidatedEditOperation): number {
|
||||
let r = Range.compareRangesUsingEnds(a.range, b.range);
|
||||
if (r === 0) {
|
||||
return a.sortIndex - b.sortIndex;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private static _sortOpsDescending(a: IValidatedEditOperation, b: IValidatedEditOperation): number {
|
||||
let r = Range.compareRangesUsingEnds(a.range, b.range);
|
||||
if (r === 0) {
|
||||
return b.sortIndex - a.sortIndex;
|
||||
}
|
||||
return -r;
|
||||
}
|
||||
|
||||
public applyEdits(rawOperations: IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult {
|
||||
if (rawOperations.length === 0) {
|
||||
return new ApplyEditsResult([], [], []);
|
||||
}
|
||||
|
||||
let mightContainRTL = this._mightContainRTL;
|
||||
let mightContainNonBasicASCII = this._mightContainNonBasicASCII;
|
||||
let canReduceOperations = true;
|
||||
|
||||
let operations: IValidatedEditOperation[] = [];
|
||||
for (let i = 0; i < rawOperations.length; i++) {
|
||||
let op = rawOperations[i];
|
||||
if (canReduceOperations && op._isTracked) {
|
||||
canReduceOperations = false;
|
||||
}
|
||||
let validatedRange = op.range;
|
||||
if (!mightContainRTL && op.text) {
|
||||
// check if the new inserted text contains RTL
|
||||
mightContainRTL = strings.containsRTL(op.text);
|
||||
}
|
||||
if (!mightContainNonBasicASCII && op.text) {
|
||||
mightContainNonBasicASCII = !strings.isBasicASCII(op.text);
|
||||
}
|
||||
operations[i] = {
|
||||
sortIndex: i,
|
||||
identifier: op.identifier || null,
|
||||
range: validatedRange,
|
||||
rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn),
|
||||
rangeLength: this.getValueLengthInRange(validatedRange, EndOfLinePreference.TextDefined),
|
||||
lines: op.text ? op.text.split(/\r\n|\r|\n/) : null,
|
||||
forceMoveMarkers: op.forceMoveMarkers || false,
|
||||
isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false
|
||||
};
|
||||
}
|
||||
|
||||
// Sort operations ascending
|
||||
operations.sort(LinesTextBuffer._sortOpsAscending);
|
||||
|
||||
for (let i = 0, count = operations.length - 1; i < count; i++) {
|
||||
let rangeEnd = operations[i].range.getEndPosition();
|
||||
let nextRangeStart = operations[i + 1].range.getStartPosition();
|
||||
|
||||
if (nextRangeStart.isBefore(rangeEnd)) {
|
||||
// overlapping ranges
|
||||
throw new Error('Overlapping ranges are not allowed!');
|
||||
}
|
||||
}
|
||||
|
||||
if (canReduceOperations) {
|
||||
operations = this._reduceOperations(operations);
|
||||
}
|
||||
|
||||
// Delta encode operations
|
||||
let reverseRanges = LinesTextBuffer._getInverseEditRanges(operations);
|
||||
let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = [];
|
||||
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
|
||||
if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) {
|
||||
// Record already the future line numbers that might be auto whitespace removal candidates on next edit
|
||||
for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) {
|
||||
let currentLineContent = '';
|
||||
if (lineNumber === reverseRange.startLineNumber) {
|
||||
currentLineContent = this.getLineContent(op.range.startLineNumber);
|
||||
if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let reverseOperations: IIdentifiedSingleEditOperation[] = [];
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
|
||||
reverseOperations[i] = {
|
||||
identifier: op.identifier,
|
||||
range: reverseRange,
|
||||
text: this.getValueInRange(op.range, EndOfLinePreference.TextDefined),
|
||||
forceMoveMarkers: op.forceMoveMarkers
|
||||
};
|
||||
}
|
||||
|
||||
this._mightContainRTL = mightContainRTL;
|
||||
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
|
||||
|
||||
const contentChanges = this._doApplyEdits(operations);
|
||||
|
||||
let trimAutoWhitespaceLineNumbers: number[] = null;
|
||||
if (recordTrimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) {
|
||||
// sort line numbers auto whitespace removal candidates for next edit descending
|
||||
newTrimAutoWhitespaceCandidates.sort((a, b) => b.lineNumber - a.lineNumber);
|
||||
|
||||
trimAutoWhitespaceLineNumbers = [];
|
||||
for (let i = 0, len = newTrimAutoWhitespaceCandidates.length; i < len; i++) {
|
||||
let lineNumber = newTrimAutoWhitespaceCandidates[i].lineNumber;
|
||||
if (i > 0 && newTrimAutoWhitespaceCandidates[i - 1].lineNumber === lineNumber) {
|
||||
// Do not have the same line number twice
|
||||
continue;
|
||||
}
|
||||
|
||||
let prevContent = newTrimAutoWhitespaceCandidates[i].oldContent;
|
||||
let lineContent = this.getLineContent(lineNumber);
|
||||
|
||||
if (lineContent.length === 0 || lineContent === prevContent || strings.firstNonWhitespaceIndex(lineContent) !== -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
trimAutoWhitespaceLineNumbers.push(lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
return new ApplyEditsResult(
|
||||
reverseOperations,
|
||||
contentChanges,
|
||||
trimAutoWhitespaceLineNumbers
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform operations such that they represent the same logic edit,
|
||||
* but that they also do not cause OOM crashes.
|
||||
*/
|
||||
private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] {
|
||||
if (operations.length < 1000) {
|
||||
// We know from empirical testing that a thousand edits work fine regardless of their shape.
|
||||
return operations;
|
||||
}
|
||||
|
||||
// At one point, due to how events are emitted and how each operation is handled,
|
||||
// some operations can trigger a high ammount of temporary string allocations,
|
||||
// that will immediately get edited again.
|
||||
// e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line
|
||||
// Therefore, the strategy is to collapse all the operations into a huge single edit operation
|
||||
return [this._toSingleEditOperation(operations)];
|
||||
}
|
||||
|
||||
_toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation {
|
||||
let forceMoveMarkers = false,
|
||||
firstEditRange = operations[0].range,
|
||||
lastEditRange = operations[operations.length - 1].range,
|
||||
entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn),
|
||||
lastEndLineNumber = firstEditRange.startLineNumber,
|
||||
lastEndColumn = firstEditRange.startColumn,
|
||||
result: string[] = [];
|
||||
|
||||
for (let i = 0, len = operations.length; i < len; i++) {
|
||||
let operation = operations[i],
|
||||
range = operation.range;
|
||||
|
||||
forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers;
|
||||
|
||||
// (1) -- Push old text
|
||||
for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) {
|
||||
if (lineNumber === lastEndLineNumber) {
|
||||
result.push(this._lines[lineNumber - 1].substring(lastEndColumn - 1));
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push(this._lines[lineNumber - 1]);
|
||||
}
|
||||
}
|
||||
|
||||
if (range.startLineNumber === lastEndLineNumber) {
|
||||
result.push(this._lines[range.startLineNumber - 1].substring(lastEndColumn - 1, range.startColumn - 1));
|
||||
} else {
|
||||
result.push('\n');
|
||||
result.push(this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1));
|
||||
}
|
||||
|
||||
// (2) -- Push new text
|
||||
if (operation.lines) {
|
||||
for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) {
|
||||
if (j !== 0) {
|
||||
result.push('\n');
|
||||
}
|
||||
result.push(operation.lines[j]);
|
||||
}
|
||||
}
|
||||
|
||||
lastEndLineNumber = operation.range.endLineNumber;
|
||||
lastEndColumn = operation.range.endColumn;
|
||||
}
|
||||
|
||||
return {
|
||||
sortIndex: 0,
|
||||
identifier: operations[0].identifier,
|
||||
range: entireEditRange,
|
||||
rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn),
|
||||
rangeLength: this.getValueLengthInRange(entireEditRange, EndOfLinePreference.TextDefined),
|
||||
lines: result.join('').split('\n'),
|
||||
forceMoveMarkers: forceMoveMarkers,
|
||||
isAutoWhitespaceEdit: false
|
||||
};
|
||||
}
|
||||
|
||||
private _setLineContent(lineNumber: number, content: string): void {
|
||||
this._lines[lineNumber - 1] = content;
|
||||
this._lineStarts.changeValue(lineNumber - 1, content.length + this._EOL.length);
|
||||
}
|
||||
|
||||
private _doApplyEdits(operations: IValidatedEditOperation[]): IInternalModelContentChange[] {
|
||||
|
||||
// Sort operations descending
|
||||
operations.sort(LinesTextBuffer._sortOpsDescending);
|
||||
|
||||
let contentChanges: IInternalModelContentChange[] = [];
|
||||
|
||||
for (let i = 0, len = operations.length; i < len; i++) {
|
||||
const op = operations[i];
|
||||
|
||||
const startLineNumber = op.range.startLineNumber;
|
||||
const startColumn = op.range.startColumn;
|
||||
const endLineNumber = op.range.endLineNumber;
|
||||
const endColumn = op.range.endColumn;
|
||||
|
||||
if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) {
|
||||
// no-op
|
||||
continue;
|
||||
}
|
||||
|
||||
const deletingLinesCnt = endLineNumber - startLineNumber;
|
||||
const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0);
|
||||
const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt);
|
||||
|
||||
for (let j = editingLinesCnt; j >= 0; j--) {
|
||||
const editLineNumber = startLineNumber + j;
|
||||
let editText = (op.lines ? op.lines[j] : '');
|
||||
|
||||
if (editLineNumber === startLineNumber || editLineNumber === endLineNumber) {
|
||||
const editStartColumn = (editLineNumber === startLineNumber ? startColumn : 1);
|
||||
const editEndColumn = (editLineNumber === endLineNumber ? endColumn : this.getLineLength(editLineNumber) + 1);
|
||||
|
||||
editText = (
|
||||
this._lines[editLineNumber - 1].substring(0, editStartColumn - 1)
|
||||
+ editText
|
||||
+ this._lines[editLineNumber - 1].substring(editEndColumn - 1)
|
||||
);
|
||||
}
|
||||
|
||||
this._setLineContent(editLineNumber, editText);
|
||||
}
|
||||
|
||||
if (editingLinesCnt < deletingLinesCnt) {
|
||||
// Must delete some lines
|
||||
|
||||
const spliceStartLineNumber = startLineNumber + editingLinesCnt;
|
||||
const endLineRemains = this._lines[endLineNumber - 1].substring(endColumn - 1);
|
||||
|
||||
// Reconstruct first line
|
||||
this._setLineContent(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1] + endLineRemains);
|
||||
|
||||
this._lines.splice(spliceStartLineNumber, endLineNumber - spliceStartLineNumber);
|
||||
this._lineStarts.removeValues(spliceStartLineNumber, endLineNumber - spliceStartLineNumber);
|
||||
}
|
||||
|
||||
if (editingLinesCnt < insertingLinesCnt) {
|
||||
// Must insert some lines
|
||||
|
||||
const spliceLineNumber = startLineNumber + editingLinesCnt;
|
||||
let spliceColumn = (spliceLineNumber === startLineNumber ? startColumn : 1);
|
||||
if (op.lines) {
|
||||
spliceColumn += op.lines[editingLinesCnt].length;
|
||||
}
|
||||
|
||||
// Split last line
|
||||
const leftoverLine = this._lines[spliceLineNumber - 1].substring(spliceColumn - 1);
|
||||
|
||||
this._setLineContent(spliceLineNumber, this._lines[spliceLineNumber - 1].substring(0, spliceColumn - 1));
|
||||
|
||||
// Lines in the middle
|
||||
let newLines: string[] = new Array<string>(insertingLinesCnt - editingLinesCnt);
|
||||
let newLinesLengths = new Uint32Array(insertingLinesCnt - editingLinesCnt);
|
||||
for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) {
|
||||
newLines[j - editingLinesCnt - 1] = op.lines[j];
|
||||
newLinesLengths[j - editingLinesCnt - 1] = op.lines[j].length + this._EOL.length;
|
||||
}
|
||||
newLines[newLines.length - 1] += leftoverLine;
|
||||
newLinesLengths[newLines.length - 1] += leftoverLine.length;
|
||||
this._lines = arrays.arrayInsert(this._lines, startLineNumber + editingLinesCnt, newLines);
|
||||
this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths);
|
||||
}
|
||||
|
||||
const contentChangeRange = new Range(startLineNumber, startColumn, endLineNumber, endColumn);
|
||||
const text = (op.lines ? op.lines.join(this.getEOL()) : '');
|
||||
contentChanges.push({
|
||||
range: contentChangeRange,
|
||||
rangeLength: op.rangeLength,
|
||||
text: text,
|
||||
rangeOffset: op.rangeOffset,
|
||||
forceMoveMarkers: op.forceMoveMarkers
|
||||
});
|
||||
}
|
||||
|
||||
return contentChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Assumes `operations` are validated and sorted ascending
|
||||
*/
|
||||
public static _getInverseEditRanges(operations: IValidatedEditOperation[]): Range[] {
|
||||
let result: Range[] = [];
|
||||
|
||||
let prevOpEndLineNumber: number;
|
||||
let prevOpEndColumn: number;
|
||||
let prevOp: IValidatedEditOperation = null;
|
||||
for (let i = 0, len = operations.length; i < len; i++) {
|
||||
let op = operations[i];
|
||||
|
||||
let startLineNumber: number;
|
||||
let startColumn: number;
|
||||
|
||||
if (prevOp) {
|
||||
if (prevOp.range.endLineNumber === op.range.startLineNumber) {
|
||||
startLineNumber = prevOpEndLineNumber;
|
||||
startColumn = prevOpEndColumn + (op.range.startColumn - prevOp.range.endColumn);
|
||||
} else {
|
||||
startLineNumber = prevOpEndLineNumber + (op.range.startLineNumber - prevOp.range.endLineNumber);
|
||||
startColumn = op.range.startColumn;
|
||||
}
|
||||
} else {
|
||||
startLineNumber = op.range.startLineNumber;
|
||||
startColumn = op.range.startColumn;
|
||||
}
|
||||
|
||||
let resultRange: Range;
|
||||
|
||||
if (op.lines && op.lines.length > 0) {
|
||||
// the operation inserts something
|
||||
let lineCount = op.lines.length;
|
||||
let firstLine = op.lines[0];
|
||||
let lastLine = op.lines[lineCount - 1];
|
||||
|
||||
if (lineCount === 1) {
|
||||
// single line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length);
|
||||
} else {
|
||||
// multi line insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1);
|
||||
}
|
||||
} else {
|
||||
// There is nothing to insert
|
||||
resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn);
|
||||
}
|
||||
|
||||
prevOpEndLineNumber = resultRange.endLineNumber;
|
||||
prevOpEndColumn = resultRange.endColumn;
|
||||
|
||||
result.push(resultRange);
|
||||
prevOp = op;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
@@ -1,139 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import { ITextBufferBuilder, ITextBufferFactory, ITextBuffer, DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { IRawTextSource, TextSource } from 'vs/editor/common/model/linesTextBuffer/textSource';
|
||||
import { LinesTextBuffer } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
|
||||
|
||||
export class TextBufferFactory implements ITextBufferFactory {
|
||||
|
||||
constructor(public readonly rawTextSource: IRawTextSource) {
|
||||
}
|
||||
|
||||
public create(defaultEOL: DefaultEndOfLine): ITextBuffer {
|
||||
const textSource = TextSource.fromRawTextSource(this.rawTextSource, defaultEOL);
|
||||
return new LinesTextBuffer(textSource);
|
||||
}
|
||||
|
||||
public getFirstLineText(lengthLimit: number): string {
|
||||
return this.rawTextSource.lines[0].substr(0, lengthLimit);
|
||||
}
|
||||
}
|
||||
|
||||
class ModelLineBasedBuilder {
|
||||
|
||||
private BOM: string;
|
||||
private lines: string[];
|
||||
private currLineIndex: number;
|
||||
|
||||
constructor() {
|
||||
this.BOM = '';
|
||||
this.lines = [];
|
||||
this.currLineIndex = 0;
|
||||
}
|
||||
|
||||
public acceptLines(lines: string[]): void {
|
||||
if (this.currLineIndex === 0) {
|
||||
// Remove the BOM (if present)
|
||||
if (strings.startsWithUTF8BOM(lines[0])) {
|
||||
this.BOM = strings.UTF8_BOM_CHARACTER;
|
||||
lines[0] = lines[0].substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0, len = lines.length; i < len; i++) {
|
||||
this.lines[this.currLineIndex++] = lines[i];
|
||||
}
|
||||
}
|
||||
|
||||
public finish(carriageReturnCnt: number, containsRTL: boolean, isBasicASCII: boolean): TextBufferFactory {
|
||||
return new TextBufferFactory({
|
||||
BOM: this.BOM,
|
||||
lines: this.lines,
|
||||
containsRTL: containsRTL,
|
||||
totalCRCount: carriageReturnCnt,
|
||||
isBasicASCII,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class LinesTextBufferBuilder implements ITextBufferBuilder {
|
||||
|
||||
private leftoverPrevChunk: string;
|
||||
private leftoverEndsInCR: boolean;
|
||||
private totalCRCount: number;
|
||||
private lineBasedBuilder: ModelLineBasedBuilder;
|
||||
private containsRTL: boolean;
|
||||
private isBasicASCII: boolean;
|
||||
|
||||
constructor() {
|
||||
this.leftoverPrevChunk = '';
|
||||
this.leftoverEndsInCR = false;
|
||||
this.totalCRCount = 0;
|
||||
this.lineBasedBuilder = new ModelLineBasedBuilder();
|
||||
this.containsRTL = false;
|
||||
this.isBasicASCII = true;
|
||||
}
|
||||
|
||||
private _updateCRCount(chunk: string): void {
|
||||
// Count how many \r are present in chunk to determine the majority EOL sequence
|
||||
let chunkCarriageReturnCnt = 0;
|
||||
let lastCarriageReturnIndex = -1;
|
||||
while ((lastCarriageReturnIndex = chunk.indexOf('\r', lastCarriageReturnIndex + 1)) !== -1) {
|
||||
chunkCarriageReturnCnt++;
|
||||
}
|
||||
this.totalCRCount += chunkCarriageReturnCnt;
|
||||
}
|
||||
|
||||
public acceptChunk(chunk: string): void {
|
||||
if (chunk.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._updateCRCount(chunk);
|
||||
|
||||
if (!this.containsRTL) {
|
||||
this.containsRTL = strings.containsRTL(chunk);
|
||||
}
|
||||
if (this.isBasicASCII) {
|
||||
this.isBasicASCII = strings.isBasicASCII(chunk);
|
||||
}
|
||||
|
||||
// Avoid dealing with a chunk that ends in \r (push the \r to the next chunk)
|
||||
if (this.leftoverEndsInCR) {
|
||||
chunk = '\r' + chunk;
|
||||
}
|
||||
if (chunk.charCodeAt(chunk.length - 1) === CharCode.CarriageReturn) {
|
||||
this.leftoverEndsInCR = true;
|
||||
chunk = chunk.substr(0, chunk.length - 1);
|
||||
} else {
|
||||
this.leftoverEndsInCR = false;
|
||||
}
|
||||
|
||||
let lines = chunk.split(/\r\n|\r|\n/);
|
||||
|
||||
if (lines.length === 1) {
|
||||
// no \r or \n encountered
|
||||
this.leftoverPrevChunk += lines[0];
|
||||
return;
|
||||
}
|
||||
|
||||
lines[0] = this.leftoverPrevChunk + lines[0];
|
||||
this.lineBasedBuilder.acceptLines(lines.slice(0, lines.length - 1));
|
||||
this.leftoverPrevChunk = lines[lines.length - 1];
|
||||
}
|
||||
|
||||
public finish(): TextBufferFactory {
|
||||
let finalLines = [this.leftoverPrevChunk];
|
||||
if (this.leftoverEndsInCR) {
|
||||
finalLines.push('');
|
||||
}
|
||||
this.lineBasedBuilder.acceptLines(finalLines);
|
||||
return this.lineBasedBuilder.finish(this.totalCRCount, this.containsRTL, this.isBasicASCII);
|
||||
}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { DefaultEndOfLine } from 'vs/editor/common/model';
|
||||
import { ITextSource } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
|
||||
|
||||
/**
|
||||
* A processed string ready to be turned into an editor model.
|
||||
*/
|
||||
export interface IRawTextSource {
|
||||
/**
|
||||
* The text split into lines.
|
||||
*/
|
||||
readonly lines: string[];
|
||||
/**
|
||||
* The BOM (leading character sequence of the file).
|
||||
*/
|
||||
readonly BOM: string;
|
||||
/**
|
||||
* The number of lines ending with '\r\n'
|
||||
*/
|
||||
readonly totalCRCount: number;
|
||||
/**
|
||||
* The text contains Unicode characters classified as "R" or "AL".
|
||||
*/
|
||||
readonly containsRTL: boolean;
|
||||
/**
|
||||
* The text contains only characters inside the ASCII range 32-126 or \t \r \n
|
||||
*/
|
||||
readonly isBasicASCII: boolean;
|
||||
}
|
||||
|
||||
export class TextSource {
|
||||
|
||||
/**
|
||||
* if text source is empty or with precisely one line, returns null. No end of line is detected.
|
||||
* if text source contains more lines ending with '\r\n', returns '\r\n'.
|
||||
* Otherwise returns '\n'. More lines end with '\n'.
|
||||
*/
|
||||
private static _getEOL(rawTextSource: IRawTextSource, defaultEOL: DefaultEndOfLine): '\r\n' | '\n' {
|
||||
const lineFeedCnt = rawTextSource.lines.length - 1;
|
||||
if (lineFeedCnt === 0) {
|
||||
// This is an empty file or a file with precisely one line
|
||||
return (defaultEOL === DefaultEndOfLine.LF ? '\n' : '\r\n');
|
||||
}
|
||||
if (rawTextSource.totalCRCount > lineFeedCnt / 2) {
|
||||
// More than half of the file contains \r\n ending lines
|
||||
return '\r\n';
|
||||
}
|
||||
// At least one line more ends in \n
|
||||
return '\n';
|
||||
}
|
||||
|
||||
public static fromRawTextSource(rawTextSource: IRawTextSource, defaultEOL: DefaultEndOfLine): ITextSource {
|
||||
return {
|
||||
lines: rawTextSource.lines,
|
||||
BOM: rawTextSource.BOM,
|
||||
EOL: TextSource._getEOL(rawTextSource, defaultEOL),
|
||||
containsRTL: rawTextSource.containsRTL,
|
||||
isBasicASCII: rawTextSource.isBasicASCII,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,8 +9,11 @@ import { CharCode } from 'vs/base/common/charCode';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { leftest, righttest, updateTreeMetadata, rbDelete, fixInsert, NodeColor, SENTINEL, TreeNode } from 'vs/editor/common/model/pieceTreeTextBuffer/rbTreeBase';
|
||||
import { SearchData, isValidMatch, Searcher, createFindMatch } from 'vs/editor/common/model/textModelSearch';
|
||||
import { FindMatch } from 'vs/editor/common/model';
|
||||
|
||||
// const lfRegex = new RegExp(/\r\n|\r|\n/g);
|
||||
export const AverageBufferSize = 65535;
|
||||
|
||||
export function createUintArray(arr: number[]): Uint32Array | Uint16Array {
|
||||
let r;
|
||||
@@ -33,7 +36,7 @@ export class LineStarts {
|
||||
) { }
|
||||
}
|
||||
|
||||
export function createLineStartsFast(str: string, readonly: boolean = true): Uint32Array | number[] {
|
||||
export function createLineStartsFast(str: string, readonly: boolean = true): Uint32Array | Uint16Array | number[] {
|
||||
let r: number[] = [0], rLength = 1;
|
||||
|
||||
for (let i = 0, len = str.length; i < len; i++) {
|
||||
@@ -309,7 +312,7 @@ export class PieceTreeBase {
|
||||
}
|
||||
|
||||
normalizeEOL(eol: '\r\n' | '\n') {
|
||||
let averageBufferSize = 65536;
|
||||
let averageBufferSize = AverageBufferSize;
|
||||
let min = averageBufferSize - Math.floor(averageBufferSize / 3);
|
||||
let max = min * 2;
|
||||
|
||||
@@ -446,7 +449,7 @@ export class PieceTreeBase {
|
||||
return new Position(1, 1);
|
||||
}
|
||||
|
||||
public getValueInRange(range: Range): string {
|
||||
public getValueInRange(range: Range, eol?: string): string {
|
||||
if (range.startLineNumber === range.endLineNumber && range.startColumn === range.endColumn) {
|
||||
return '';
|
||||
}
|
||||
@@ -454,7 +457,21 @@ export class PieceTreeBase {
|
||||
let startPosition = this.nodeAt2(range.startLineNumber, range.startColumn);
|
||||
let endPosition = this.nodeAt2(range.endLineNumber, range.endColumn);
|
||||
|
||||
return this.getValueInRange2(startPosition, endPosition);
|
||||
let value = this.getValueInRange2(startPosition, endPosition);
|
||||
if (eol) {
|
||||
if (eol !== this._EOL || !this._EOLNormalized) {
|
||||
return value.replace(/\r\n|\r|\n/g, eol);
|
||||
}
|
||||
|
||||
if (eol === this.getEOL() && this._EOLNormalized) {
|
||||
if (eol === '\r\n') {
|
||||
|
||||
}
|
||||
return value;
|
||||
}
|
||||
return value.replace(/\r\n|\r|\n/g, eol);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public getValueInRange2(startPosition: NodePosition, endPosition: NodePosition): string {
|
||||
@@ -520,11 +537,23 @@ export class PieceTreeBase {
|
||||
|
||||
public getLineCharCode(lineNumber: number, index: number): number {
|
||||
let nodePos = this.nodeAt2(lineNumber, index + 1);
|
||||
let buffer = this._buffers[nodePos.node.piece.bufferIndex];
|
||||
let startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start);
|
||||
let targetOffset = startOffset + index;
|
||||
if (nodePos.remainder === nodePos.node.piece.length) {
|
||||
// the char we want to fetch is at the head of next node.
|
||||
let matchingNode = nodePos.node.next();
|
||||
if (!matchingNode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return buffer.buffer.charCodeAt(targetOffset);
|
||||
let buffer = this._buffers[matchingNode.piece.bufferIndex];
|
||||
let startOffset = this.offsetInBuffer(matchingNode.piece.bufferIndex, matchingNode.piece.start);
|
||||
return buffer.buffer.charCodeAt(startOffset);
|
||||
} else {
|
||||
let buffer = this._buffers[nodePos.node.piece.bufferIndex];
|
||||
let startOffset = this.offsetInBuffer(nodePos.node.piece.bufferIndex, nodePos.node.piece.start);
|
||||
let targetOffset = startOffset + nodePos.remainder;
|
||||
|
||||
return buffer.buffer.charCodeAt(targetOffset);
|
||||
}
|
||||
}
|
||||
|
||||
public getLineLength(lineNumber: number): number {
|
||||
@@ -535,6 +564,151 @@ export class PieceTreeBase {
|
||||
return this.getOffsetAt(lineNumber + 1, 1) - this.getOffsetAt(lineNumber, 1) - this._EOLLength;
|
||||
}
|
||||
|
||||
public findMatchesInNode(node: TreeNode, searcher: Searcher, startLineNumber: number, startColumn: number, startCursor: BufferCursor, endCursor: BufferCursor, searchData: SearchData, captureMatches: boolean, limitResultCount: number, resultLen: number, result: FindMatch[]) {
|
||||
let buffer = this._buffers[node.piece.bufferIndex];
|
||||
let startOffsetInBuffer = this.offsetInBuffer(node.piece.bufferIndex, node.piece.start);
|
||||
let start = this.offsetInBuffer(node.piece.bufferIndex, startCursor);
|
||||
let end = this.offsetInBuffer(node.piece.bufferIndex, endCursor);
|
||||
|
||||
let m: RegExpExecArray;
|
||||
// Reset regex to search from the beginning
|
||||
searcher.reset(start);
|
||||
let ret: BufferCursor = { line: 0, column: 0 };
|
||||
|
||||
do {
|
||||
m = searcher.next(buffer.buffer);
|
||||
|
||||
if (m) {
|
||||
if (m.index >= end) {
|
||||
return resultLen;
|
||||
}
|
||||
this.positionInBuffer(node, m.index - startOffsetInBuffer, ret);
|
||||
let lineFeedCnt = this.getLineFeedCnt(node.piece.bufferIndex, startCursor, ret);
|
||||
let retStartColumn = ret.line === startCursor.line ? ret.column - startCursor.column + startColumn : ret.column + 1;
|
||||
let retEndColumn = retStartColumn + m[0].length;
|
||||
result[resultLen++] = createFindMatch(new Range(startLineNumber + lineFeedCnt, retStartColumn, startLineNumber + lineFeedCnt, retEndColumn), m, captureMatches);
|
||||
|
||||
if (m.index + m[0].length >= end) {
|
||||
return resultLen;
|
||||
}
|
||||
if (resultLen >= limitResultCount) {
|
||||
return resultLen;
|
||||
}
|
||||
}
|
||||
|
||||
} while (m);
|
||||
|
||||
return resultLen;
|
||||
}
|
||||
|
||||
public findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
|
||||
const result: FindMatch[] = [];
|
||||
let resultLen = 0;
|
||||
const searcher = new Searcher(searchData.wordSeparators, searchData.regex);
|
||||
|
||||
let startPostion = this.nodeAt2(searchRange.startLineNumber, searchRange.startColumn);
|
||||
if (startPostion === null) {
|
||||
return [];
|
||||
}
|
||||
let endPosition = this.nodeAt2(searchRange.endLineNumber, searchRange.endColumn);
|
||||
if (endPosition === null) {
|
||||
return [];
|
||||
}
|
||||
let start = this.positionInBuffer(startPostion.node, startPostion.remainder);
|
||||
let end = this.positionInBuffer(endPosition.node, endPosition.remainder);
|
||||
|
||||
if (startPostion.node === endPosition.node) {
|
||||
this.findMatchesInNode(startPostion.node, searcher, searchRange.startLineNumber, searchRange.startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
let startLineNumber = searchRange.startLineNumber;
|
||||
|
||||
let currentNode = startPostion.node;
|
||||
while (currentNode !== endPosition.node) {
|
||||
let lineBreakCnt = this.getLineFeedCnt(currentNode.piece.bufferIndex, start, currentNode.piece.end);
|
||||
|
||||
if (lineBreakCnt >= 1) {
|
||||
// last line break position
|
||||
let lineStarts = this._buffers[currentNode.piece.bufferIndex].lineStarts;
|
||||
let startOffsetInBuffer = this.offsetInBuffer(currentNode.piece.bufferIndex, currentNode.piece.start);
|
||||
let nextLineStartOffset = lineStarts[start.line + lineBreakCnt];
|
||||
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1;
|
||||
resultLen = this.findMatchesInNode(currentNode, searcher, startLineNumber, startColumn, start, this.positionInBuffer(currentNode, nextLineStartOffset - startOffsetInBuffer), searchData, captureMatches, limitResultCount, resultLen, result);
|
||||
|
||||
if (resultLen >= limitResultCount) {
|
||||
return result;
|
||||
}
|
||||
|
||||
startLineNumber += lineBreakCnt;
|
||||
}
|
||||
|
||||
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0;
|
||||
// search for the remaining content
|
||||
if (startLineNumber === searchRange.endLineNumber) {
|
||||
const text = this.getLineContent(startLineNumber).substring(startColumn, searchRange.endColumn - 1);
|
||||
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
|
||||
return result;
|
||||
}
|
||||
|
||||
resultLen = this._findMatchesInLine(searchData, searcher, this.getLineContent(startLineNumber).substr(startColumn), startLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
|
||||
|
||||
if (resultLen >= limitResultCount) {
|
||||
return result;
|
||||
}
|
||||
|
||||
startLineNumber++;
|
||||
startPostion = this.nodeAt2(startLineNumber, 1);
|
||||
currentNode = startPostion.node;
|
||||
start = this.positionInBuffer(startPostion.node, startPostion.remainder);
|
||||
}
|
||||
|
||||
if (startLineNumber === searchRange.endLineNumber) {
|
||||
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn - 1 : 0;
|
||||
const text = this.getLineContent(startLineNumber).substring(startColumn, searchRange.endColumn - 1);
|
||||
resultLen = this._findMatchesInLine(searchData, searcher, text, searchRange.endLineNumber, startColumn, resultLen, result, captureMatches, limitResultCount);
|
||||
return result;
|
||||
}
|
||||
|
||||
let startColumn = startLineNumber === searchRange.startLineNumber ? searchRange.startColumn : 1;
|
||||
resultLen = this.findMatchesInNode(endPosition.node, searcher, startLineNumber, startColumn, start, end, searchData, captureMatches, limitResultCount, resultLen, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
private _findMatchesInLine(searchData: SearchData, searcher: Searcher, text: string, lineNumber: number, deltaOffset: number, resultLen: number, result: FindMatch[], captureMatches: boolean, limitResultCount: number): number {
|
||||
const wordSeparators = searchData.wordSeparators;
|
||||
if (!captureMatches && searchData.simpleSearch) {
|
||||
const searchString = searchData.simpleSearch;
|
||||
const searchStringLen = searchString.length;
|
||||
const textLength = text.length;
|
||||
|
||||
let lastMatchIndex = -searchStringLen;
|
||||
while ((lastMatchIndex = text.indexOf(searchString, lastMatchIndex + searchStringLen)) !== -1) {
|
||||
if (!wordSeparators || isValidMatch(wordSeparators, text, textLength, lastMatchIndex, searchStringLen)) {
|
||||
result[resultLen++] = new FindMatch(new Range(lineNumber, lastMatchIndex + 1 + deltaOffset, lineNumber, lastMatchIndex + 1 + searchStringLen + deltaOffset), null);
|
||||
if (resultLen >= limitResultCount) {
|
||||
return resultLen;
|
||||
}
|
||||
}
|
||||
}
|
||||
return resultLen;
|
||||
}
|
||||
|
||||
let m: RegExpExecArray;
|
||||
// Reset regex to search from the beginning
|
||||
searcher.reset(0);
|
||||
do {
|
||||
m = searcher.next(text);
|
||||
if (m) {
|
||||
result[resultLen++] = createFindMatch(new Range(lineNumber, m.index + 1 + deltaOffset, lineNumber, m.index + 1 + m[0].length + deltaOffset), m, captureMatches);
|
||||
if (resultLen >= limitResultCount) {
|
||||
return resultLen;
|
||||
}
|
||||
}
|
||||
} while (m);
|
||||
return resultLen;
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region Piece Table
|
||||
@@ -551,7 +725,8 @@ export class PieceTreeBase {
|
||||
if (node.piece.bufferIndex === 0 &&
|
||||
piece.end.line === this._lastChangeBufferPos.line &&
|
||||
piece.end.column === this._lastChangeBufferPos.column &&
|
||||
(nodeStartOffset + piece.length === offset)
|
||||
(nodeStartOffset + piece.length === offset) &&
|
||||
value.length < AverageBufferSize
|
||||
) {
|
||||
// changed buffer
|
||||
this.appendToNode(node, value);
|
||||
@@ -608,19 +783,27 @@ export class PieceTreeBase {
|
||||
this.deleteNodeTail(node, insertPosInBuffer);
|
||||
}
|
||||
|
||||
let newPiece = this.createNewPiece(value);
|
||||
let newPieces = this.createNewPieces(value);
|
||||
if (newRightPiece.length > 0) {
|
||||
this.rbInsertRight(node, newRightPiece);
|
||||
}
|
||||
this.rbInsertRight(node, newPiece);
|
||||
|
||||
let tmpNode = node;
|
||||
for (let k = 0; k < newPieces.length; k++) {
|
||||
tmpNode = this.rbInsertRight(tmpNode, newPieces[k]);
|
||||
}
|
||||
this.deleteNodes(nodesToDel);
|
||||
} else {
|
||||
this.insertContentToNodeRight(value, node);
|
||||
}
|
||||
} else {
|
||||
// insert new node
|
||||
let piece = this.createNewPiece(value);
|
||||
this.rbInsertLeft(null, piece);
|
||||
let pieces = this.createNewPieces(value);
|
||||
let node = this.rbInsertLeft(null, pieces[0]);
|
||||
|
||||
for (let k = 1; k < pieces.length; k++) {
|
||||
node = this.rbInsertRight(node, pieces[k]);
|
||||
}
|
||||
}
|
||||
|
||||
// todo, this is too brutal. Total line feed count should be updated the same way as lf_left.
|
||||
@@ -726,8 +909,11 @@ export class PieceTreeBase {
|
||||
}
|
||||
}
|
||||
|
||||
let newPiece = this.createNewPiece(value);
|
||||
let newNode = this.rbInsertLeft(node, newPiece);
|
||||
let newPieces = this.createNewPieces(value);
|
||||
let newNode = this.rbInsertLeft(node, newPieces[newPieces.length - 1]);
|
||||
for (let k = newPieces.length - 2; k >= 0; k--) {
|
||||
newNode = this.rbInsertLeft(newNode, newPieces[k]);
|
||||
}
|
||||
this.validateCRLFWithPrevNode(newNode);
|
||||
this.deleteNodes(nodesToDel);
|
||||
}
|
||||
@@ -739,12 +925,18 @@ export class PieceTreeBase {
|
||||
value += '\n';
|
||||
}
|
||||
|
||||
let newPiece = this.createNewPiece(value);
|
||||
let newNode = this.rbInsertRight(node, newPiece);
|
||||
let newPieces = this.createNewPieces(value);
|
||||
let newNode = this.rbInsertRight(node, newPieces[0]);
|
||||
let tmpNode = newNode;
|
||||
|
||||
for (let k = 1; k < newPieces.length; k++) {
|
||||
tmpNode = this.rbInsertRight(tmpNode, newPieces[k]);
|
||||
}
|
||||
|
||||
this.validateCRLFWithPrevNode(newNode);
|
||||
}
|
||||
|
||||
positionInBuffer(node: TreeNode, remainder: number): BufferCursor {
|
||||
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor {
|
||||
let piece = node.piece;
|
||||
let bufferIndex = node.piece.bufferIndex;
|
||||
let lineStarts = this._buffers[bufferIndex].lineStarts;
|
||||
@@ -780,6 +972,12 @@ export class PieceTreeBase {
|
||||
}
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
ret.line = mid;
|
||||
ret.column = offset - midStart;
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
line: mid,
|
||||
column: offset - midStart
|
||||
@@ -827,7 +1025,47 @@ export class PieceTreeBase {
|
||||
}
|
||||
}
|
||||
|
||||
createNewPiece(text: string): Piece {
|
||||
createNewPieces(text: string): Piece[] {
|
||||
if (text.length > AverageBufferSize) {
|
||||
// the content is large, operations like substring, charCode becomes slow
|
||||
// so here we split it into smaller chunks, just like what we did for CR/LF normalization
|
||||
let newPieces = [];
|
||||
while (text.length > AverageBufferSize) {
|
||||
const lastChar = text.charCodeAt(AverageBufferSize - 1);
|
||||
let splitText;
|
||||
if (lastChar === CharCode.CarriageReturn || (lastChar >= 0xd800 && lastChar <= 0xdbff)) {
|
||||
// last character is \r or a high surrogate => keep it back
|
||||
splitText = text.substring(0, AverageBufferSize - 1);
|
||||
text = text.substring(AverageBufferSize - 1);
|
||||
} else {
|
||||
splitText = text.substring(0, AverageBufferSize);
|
||||
text = text.substring(AverageBufferSize);
|
||||
}
|
||||
|
||||
let lineStarts = createLineStartsFast(splitText);
|
||||
newPieces.push(new Piece(
|
||||
this._buffers.length, /* buffer index */
|
||||
{ line: 0, column: 0 },
|
||||
{ line: lineStarts.length - 1, column: splitText.length - lineStarts[lineStarts.length - 1] },
|
||||
lineStarts.length - 1,
|
||||
splitText.length
|
||||
));
|
||||
this._buffers.push(new StringBuffer(splitText, lineStarts));
|
||||
}
|
||||
|
||||
let lineStarts = createLineStartsFast(text);
|
||||
newPieces.push(new Piece(
|
||||
this._buffers.length, /* buffer index */
|
||||
{ line: 0, column: 0 },
|
||||
{ line: lineStarts.length - 1, column: text.length - lineStarts[lineStarts.length - 1] },
|
||||
lineStarts.length - 1,
|
||||
text.length
|
||||
));
|
||||
this._buffers.push(new StringBuffer(text, lineStarts));
|
||||
|
||||
return newPieces;
|
||||
}
|
||||
|
||||
let startOffset = this._buffers[0].buffer.length;
|
||||
const lineStarts = createLineStartsFast(text, false);
|
||||
|
||||
@@ -862,14 +1100,14 @@ export class PieceTreeBase {
|
||||
let endColumn = endOffset - this._buffers[0].lineStarts[endIndex];
|
||||
let endPos = { line: endIndex, column: endColumn };
|
||||
let newPiece = new Piece(
|
||||
0,
|
||||
0, /** todo */
|
||||
start,
|
||||
endPos,
|
||||
this.getLineFeedCnt(0, start, endPos),
|
||||
endOffset - startOffset
|
||||
);
|
||||
this._lastChangeBufferPos = endPos;
|
||||
return newPiece;
|
||||
return [newPiece];
|
||||
}
|
||||
|
||||
getLinesRawContent(): string {
|
||||
@@ -1352,8 +1590,8 @@ export class PieceTreeBase {
|
||||
}
|
||||
|
||||
// create new piece which contains \r\n
|
||||
let piece = this.createNewPiece('\r\n');
|
||||
this.rbInsertRight(prev, piece);
|
||||
let pieces = this.createNewPieces('\r\n');
|
||||
this.rbInsertRight(prev, pieces[0]);
|
||||
// delete empty nodes
|
||||
|
||||
for (let i = 0; i < nodesToDel.length; i++) {
|
||||
|
||||
@@ -7,10 +7,25 @@
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IValidatedEditOperation } from 'vs/editor/common/model/linesTextBuffer/linesTextBuffer';
|
||||
import { PieceTreeBase, StringBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase';
|
||||
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange } from 'vs/editor/common/model';
|
||||
import { IIdentifiedSingleEditOperation, EndOfLinePreference, ITextBuffer, ApplyEditsResult, IInternalModelContentChange, FindMatch, ISingleEditOperationIdentifier } from 'vs/editor/common/model';
|
||||
import { ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
|
||||
export interface IValidatedEditOperation {
|
||||
sortIndex: number;
|
||||
identifier: ISingleEditOperationIdentifier;
|
||||
range: Range;
|
||||
rangeOffset: number;
|
||||
rangeLength: number;
|
||||
lines: string[];
|
||||
forceMoveMarkers: boolean;
|
||||
isAutoWhitespaceEdit: boolean;
|
||||
}
|
||||
|
||||
export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperation {
|
||||
sortIndex: number;
|
||||
}
|
||||
|
||||
export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
private _pieceTree: PieceTreeBase;
|
||||
@@ -76,8 +91,7 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
}
|
||||
|
||||
const lineEnding = this._getEndOfLine(eol);
|
||||
const text = this._pieceTree.getValueInRange(range);
|
||||
return text.replace(/\r\n|\r|\n/g, lineEnding);
|
||||
return this._pieceTree.getValueInRange(range, lineEnding);
|
||||
}
|
||||
|
||||
public getValueLengthInRange(range: Range, eol: EndOfLinePreference = EndOfLinePreference.TextDefined): number {
|
||||
@@ -192,13 +206,17 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
// Sort operations ascending
|
||||
operations.sort(PieceTreeTextBuffer._sortOpsAscending);
|
||||
|
||||
let hasTouchingRanges = false;
|
||||
for (let i = 0, count = operations.length - 1; i < count; i++) {
|
||||
let rangeEnd = operations[i].range.getEndPosition();
|
||||
let nextRangeStart = operations[i + 1].range.getStartPosition();
|
||||
|
||||
if (nextRangeStart.isBefore(rangeEnd)) {
|
||||
// overlapping ranges
|
||||
throw new Error('Overlapping ranges are not allowed!');
|
||||
if (nextRangeStart.isBeforeOrEqual(rangeEnd)) {
|
||||
if (nextRangeStart.isBefore(rangeEnd)) {
|
||||
// overlapping ranges
|
||||
throw new Error('Overlapping ranges are not allowed!');
|
||||
}
|
||||
hasTouchingRanges = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,12 +247,13 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
}
|
||||
}
|
||||
|
||||
let reverseOperations: IIdentifiedSingleEditOperation[] = [];
|
||||
let reverseOperations: IReverseSingleEditOperation[] = [];
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
let op = operations[i];
|
||||
let reverseRange = reverseRanges[i];
|
||||
|
||||
reverseOperations[i] = {
|
||||
sortIndex: op.sortIndex,
|
||||
identifier: op.identifier,
|
||||
range: reverseRange,
|
||||
text: this.getValueInRange(op.range),
|
||||
@@ -242,6 +261,11 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
};
|
||||
}
|
||||
|
||||
// Can only sort reverse operations when the order is not significant
|
||||
if (!hasTouchingRanges) {
|
||||
reverseOperations.sort((a, b) => a.sortIndex - b.sortIndex);
|
||||
}
|
||||
|
||||
this._mightContainRTL = mightContainRTL;
|
||||
this._mightContainNonBasicASCII = mightContainNonBasicASCII;
|
||||
|
||||
@@ -279,9 +303,9 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform operations such that they represent the same logic edit,
|
||||
* but that they also do not cause OOM crashes.
|
||||
*/
|
||||
* Transform operations such that they represent the same logic edit,
|
||||
* but that they also do not cause OOM crashes.
|
||||
*/
|
||||
private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] {
|
||||
if (operations.length < 1000) {
|
||||
// We know from empirical testing that a thousand edits work fine regardless of their shape.
|
||||
@@ -410,6 +434,10 @@ export class PieceTreeTextBuffer implements ITextBuffer {
|
||||
return contentChanges;
|
||||
}
|
||||
|
||||
findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): FindMatch[] {
|
||||
return this._pieceTree.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
// #endregion
|
||||
|
||||
// #region helper
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import * as model from 'vs/editor/common/model';
|
||||
import { LanguageIdentifier, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes';
|
||||
import { EditStack } from 'vs/editor/common/model/editStack';
|
||||
@@ -30,31 +30,13 @@ import { getWordAtText } from 'vs/editor/common/model/wordHelper';
|
||||
import { ModelLinesTokens, ModelTokensChangedEventBuilder } from 'vs/editor/common/model/textModelTokens';
|
||||
import { guessIndentation } from 'vs/editor/common/model/indentationGuesser';
|
||||
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
|
||||
import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch';
|
||||
import { TextModelSearch, SearchParams, SearchData } from 'vs/editor/common/model/textModelSearch';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IStringStream, ITextSnapshot } from 'vs/platform/files/common/files';
|
||||
import { LinesTextBufferBuilder } from 'vs/editor/common/model/linesTextBuffer/linesTextBufferBuilder';
|
||||
import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder';
|
||||
import { ChunksTextBufferBuilder } from 'vs/editor/common/model/chunksTextBuffer/chunksTextBufferBuilder';
|
||||
|
||||
export enum TextBufferType {
|
||||
LinesArray,
|
||||
PieceTree,
|
||||
Chunks
|
||||
}
|
||||
// Here is the master switch for the text buffer implementation:
|
||||
export const OPTIONS = {
|
||||
TEXT_BUFFER_IMPLEMENTATION: TextBufferType.PieceTree
|
||||
};
|
||||
|
||||
function createTextBufferBuilder() {
|
||||
if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.PieceTree) {
|
||||
return new PieceTreeTextBufferBuilder();
|
||||
}
|
||||
if (OPTIONS.TEXT_BUFFER_IMPLEMENTATION === TextBufferType.Chunks) {
|
||||
return new ChunksTextBufferBuilder();
|
||||
}
|
||||
return new LinesTextBufferBuilder();
|
||||
return new PieceTreeTextBufferBuilder();
|
||||
}
|
||||
|
||||
export function createTextBufferFactory(text: string): model.ITextBufferFactory {
|
||||
@@ -173,15 +155,17 @@ class TextModelSnapshot implements ITextSnapshot {
|
||||
export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
|
||||
private static readonly MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB
|
||||
private static readonly MANY_MANY_LINES = 300 * 1000; // 300K lines
|
||||
private static readonly LARGE_FILE_SIZE_THRESHOLD = 20 * 1024 * 1024; // 20 MB;
|
||||
private static readonly LARGE_FILE_LINE_COUNT_THRESHOLD = 300 * 1000; // 300K lines
|
||||
|
||||
public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = {
|
||||
isForSimpleWidget: false,
|
||||
tabSize: EDITOR_MODEL_DEFAULTS.tabSize,
|
||||
insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces,
|
||||
detectIndentation: false,
|
||||
defaultEOL: model.DefaultEndOfLine.LF,
|
||||
trimAutoWhitespace: EDITOR_MODEL_DEFAULTS.trimAutoWhitespace,
|
||||
largeFileOptimizations: EDITOR_MODEL_DEFAULTS.largeFileOptimizations,
|
||||
};
|
||||
|
||||
public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): TextModel {
|
||||
@@ -228,15 +212,19 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
public readonly onDidChangeOptions: Event<IModelOptionsChangedEvent> = this._onDidChangeOptions.event;
|
||||
|
||||
private readonly _eventEmitter: DidChangeContentEmitter = this._register(new DidChangeContentEmitter());
|
||||
public onDidChangeRawContentFast(listener: (e: ModelRawContentChangedEvent) => void): IDisposable {
|
||||
return this._eventEmitter.fastEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
|
||||
}
|
||||
public onDidChangeRawContent(listener: (e: ModelRawContentChangedEvent) => void): IDisposable {
|
||||
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
|
||||
return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.rawContentChangedEvent));
|
||||
}
|
||||
public onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable {
|
||||
return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
|
||||
return this._eventEmitter.slowEvent((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent));
|
||||
}
|
||||
//#endregion
|
||||
|
||||
public readonly id: string;
|
||||
public readonly isForSimpleWidget: boolean;
|
||||
private readonly _associatedResource: URI;
|
||||
private _attachedEditorCount: number;
|
||||
private _buffer: model.ITextBuffer;
|
||||
@@ -249,7 +237,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
* Unlike, versionId, this can go down (via undo) or go to previous values (via redo)
|
||||
*/
|
||||
private _alternativeVersionId: number;
|
||||
private readonly _shouldSimplifyMode: boolean;
|
||||
private readonly _isTooLargeForSyncing: boolean;
|
||||
private readonly _isTooLargeForTokenization: boolean;
|
||||
|
||||
//#region Editing
|
||||
@@ -284,6 +272,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// Generate a new unique model id
|
||||
MODEL_ID++;
|
||||
this.id = '$model' + MODEL_ID;
|
||||
this.isForSimpleWidget = creationOptions.isForSimpleWidget;
|
||||
if (typeof associatedResource === 'undefined' || associatedResource === null) {
|
||||
this._associatedResource = URI.parse('inmemory://model/' + MODEL_ID);
|
||||
} else {
|
||||
@@ -297,18 +286,20 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
const bufferLineCount = this._buffer.getLineCount();
|
||||
const bufferTextLength = this._buffer.getValueLengthInRange(new Range(1, 1, bufferLineCount, this._buffer.getLineLength(bufferLineCount) + 1), model.EndOfLinePreference.TextDefined);
|
||||
|
||||
// !!! Make a decision in the ctor and permanently respect this decision !!!
|
||||
// If a model is too large at construction time, it will never get tokenized,
|
||||
// under no circumstances.
|
||||
this._isTooLargeForTokenization = (
|
||||
(bufferTextLength > TextModel.MODEL_TOKENIZATION_LIMIT)
|
||||
|| (bufferLineCount > TextModel.MANY_MANY_LINES)
|
||||
);
|
||||
if (creationOptions.largeFileOptimizations) {
|
||||
this._isTooLargeForTokenization = (
|
||||
(bufferTextLength > TextModel.LARGE_FILE_SIZE_THRESHOLD)
|
||||
|| (bufferLineCount > TextModel.LARGE_FILE_LINE_COUNT_THRESHOLD)
|
||||
);
|
||||
} else {
|
||||
this._isTooLargeForTokenization = false;
|
||||
}
|
||||
|
||||
this._shouldSimplifyMode = (
|
||||
this._isTooLargeForTokenization
|
||||
|| (bufferTextLength > TextModel.MODEL_SYNC_LIMIT)
|
||||
);
|
||||
this._isTooLargeForSyncing = (bufferTextLength > TextModel.MODEL_SYNC_LIMIT);
|
||||
|
||||
this._setVersionId(1);
|
||||
this._isDisposed = false;
|
||||
@@ -398,10 +389,11 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
this.setValueFromTextBuffer(textBuffer);
|
||||
}
|
||||
|
||||
private _createContentChanged2(startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent {
|
||||
private _createContentChanged2(range: Range, rangeOffset: number, rangeLength: number, text: string, isUndoing: boolean, isRedoing: boolean, isFlush: boolean): IModelContentChangedEvent {
|
||||
return {
|
||||
changes: [{
|
||||
range: new Range(startLineNumber, startColumn, endLineNumber, endColumn),
|
||||
range: range,
|
||||
rangeOffset: rangeOffset,
|
||||
rangeLength: rangeLength,
|
||||
text: text,
|
||||
}],
|
||||
@@ -447,7 +439,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
false,
|
||||
false
|
||||
),
|
||||
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, true)
|
||||
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -478,7 +470,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
false,
|
||||
false
|
||||
),
|
||||
this._createContentChanged2(1, 1, endLineNumber, endColumn, oldModelValueLength, this.getValue(), false, false, false)
|
||||
this._createContentChanged2(new Range(1, 1, endLineNumber, endColumn), 0, oldModelValueLength, this.getValue(), false, false, false)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -548,8 +540,12 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return this._attachedEditorCount > 0;
|
||||
}
|
||||
|
||||
public isTooLargeForHavingARichMode(): boolean {
|
||||
return this._shouldSimplifyMode;
|
||||
public getAttachedEditorCount(): number {
|
||||
return this._attachedEditorCount;
|
||||
}
|
||||
|
||||
public isTooLargeForSyncing(): boolean {
|
||||
return this._isTooLargeForSyncing;
|
||||
}
|
||||
|
||||
public isTooLargeForTokenization(): boolean {
|
||||
@@ -779,6 +775,15 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return this._buffer.getLineContent(lineNumber);
|
||||
}
|
||||
|
||||
public getLineLength(lineNumber: number): number {
|
||||
this._assertNotDisposed();
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value for lineNumber');
|
||||
}
|
||||
|
||||
return this._buffer.getLineLength(lineNumber);
|
||||
}
|
||||
|
||||
public getLinesContent(): string[] {
|
||||
this._assertNotDisposed();
|
||||
return this._buffer.getLinesContent();
|
||||
@@ -889,6 +894,41 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict Do NOT allow a position inside a high-low surrogate pair
|
||||
*/
|
||||
private _isValidPosition(lineNumber: number, column: number, strict: boolean): boolean {
|
||||
|
||||
if (lineNumber < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const lineCount = this._buffer.getLineCount();
|
||||
if (lineNumber > lineCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (column < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const maxColumn = this.getLineMaxColumn(lineNumber);
|
||||
if (column > maxColumn) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
if (column > 1) {
|
||||
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
|
||||
if (strings.isHighSurrogate(charCodeBefore)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict Do NOT allow a position inside a high-low surrogate pair
|
||||
*/
|
||||
@@ -929,11 +969,60 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
public validatePosition(position: IPosition): Position {
|
||||
this._assertNotDisposed();
|
||||
|
||||
// Avoid object allocation and cover most likely case
|
||||
if (position instanceof Position) {
|
||||
if (this._isValidPosition(position.lineNumber, position.column, true)) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
|
||||
return this._validatePosition(position.lineNumber, position.column, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param strict Do NOT allow a range to have its boundaries inside a high-low surrogate pair
|
||||
*/
|
||||
private _isValidRange(range: Range, strict: boolean): boolean {
|
||||
const startLineNumber = range.startLineNumber;
|
||||
const startColumn = range.startColumn;
|
||||
const endLineNumber = range.endLineNumber;
|
||||
const endColumn = range.endColumn;
|
||||
|
||||
if (!this._isValidPosition(startLineNumber, startColumn, false)) {
|
||||
return false;
|
||||
}
|
||||
if (!this._isValidPosition(endLineNumber, endColumn, false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strict) {
|
||||
const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0);
|
||||
const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0);
|
||||
|
||||
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
|
||||
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
|
||||
|
||||
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public validateRange(_range: IRange): Range {
|
||||
this._assertNotDisposed();
|
||||
|
||||
// Avoid object allocation and cover most likely case
|
||||
if ((_range instanceof Range) && !(_range instanceof Selection)) {
|
||||
if (this._isValidRange(_range, true)) {
|
||||
return _range;
|
||||
}
|
||||
}
|
||||
|
||||
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, false);
|
||||
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, false);
|
||||
|
||||
@@ -983,6 +1072,10 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount));
|
||||
}
|
||||
|
||||
private findMatchesLineByLine(searchRange: Range, searchData: SearchData, captureMatches: boolean, limitResultCount: number): model.FindMatch[] {
|
||||
return this._buffer.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): model.FindMatch[] {
|
||||
this._assertNotDisposed();
|
||||
|
||||
@@ -993,12 +1086,46 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
searchRange = this.getFullModelRange();
|
||||
}
|
||||
|
||||
if (!isRegex && searchString.indexOf('\n') < 0) {
|
||||
// not regex, not multi line
|
||||
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
|
||||
const searchData = searchParams.parseSearchRequest();
|
||||
|
||||
if (!searchData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.findMatchesLineByLine(searchRange, searchData, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
return TextModelSearch.findMatches(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchRange, captureMatches, limitResultCount);
|
||||
}
|
||||
|
||||
public findNextMatch(searchString: string, rawSearchStart: IPosition, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean): model.FindMatch {
|
||||
this._assertNotDisposed();
|
||||
const searchStart = this.validatePosition(rawSearchStart);
|
||||
|
||||
if (!isRegex && searchString.indexOf('\n') < 0) {
|
||||
const searchParams = new SearchParams(searchString, isRegex, matchCase, wordSeparators);
|
||||
const searchData = searchParams.parseSearchRequest();
|
||||
const lineCount = this.getLineCount();
|
||||
let searchRange = new Range(searchStart.lineNumber, searchStart.column, lineCount, this.getLineMaxColumn(lineCount));
|
||||
let ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
|
||||
TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
|
||||
if (ret.length > 0) {
|
||||
return ret[0];
|
||||
}
|
||||
|
||||
searchRange = new Range(1, 1, searchStart.lineNumber, this.getLineMaxColumn(searchStart.lineNumber));
|
||||
ret = this.findMatchesLineByLine(searchRange, searchData, captureMatches, 1);
|
||||
|
||||
if (ret.length > 0) {
|
||||
return ret[0];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return TextModelSearch.findNextMatch(this, new SearchParams(searchString, isRegex, matchCase, wordSeparators), searchStart, captureMatches);
|
||||
}
|
||||
|
||||
@@ -1584,6 +1711,88 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
//#region Tokenization
|
||||
|
||||
public tokenizeViewport(startLineNumber: number, endLineNumber: number): void {
|
||||
if (!this._tokens.tokenizationSupport) {
|
||||
return;
|
||||
}
|
||||
|
||||
// we tokenize `this._tokens.inValidLineStartIndex` lines in around 20ms so it's a good baseline.
|
||||
const contextBefore = Math.floor(this._tokens.inValidLineStartIndex * 0.3);
|
||||
startLineNumber = Math.max(1, startLineNumber - contextBefore);
|
||||
|
||||
if (startLineNumber <= this._tokens.inValidLineStartIndex) {
|
||||
this.forceTokenization(endLineNumber);
|
||||
return;
|
||||
}
|
||||
|
||||
const eventBuilder = new ModelTokensChangedEventBuilder();
|
||||
let nonWhitespaceColumn = this.getLineFirstNonWhitespaceColumn(startLineNumber);
|
||||
let fakeLines = [];
|
||||
let i = startLineNumber - 1;
|
||||
let initialState = null;
|
||||
if (nonWhitespaceColumn > 0) {
|
||||
while (nonWhitespaceColumn > 0 && i >= 1) {
|
||||
let newNonWhitespaceIndex = this.getLineFirstNonWhitespaceColumn(i);
|
||||
|
||||
if (newNonWhitespaceIndex === 0) {
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newNonWhitespaceIndex < nonWhitespaceColumn) {
|
||||
initialState = this._tokens._getState(i - 1);
|
||||
if (initialState) {
|
||||
break;
|
||||
}
|
||||
fakeLines.push(this.getLineContent(i));
|
||||
nonWhitespaceColumn = newNonWhitespaceIndex;
|
||||
}
|
||||
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (!initialState) {
|
||||
initialState = this._tokens.tokenizationSupport.getInitialState();
|
||||
}
|
||||
|
||||
let state = initialState.clone();
|
||||
for (let i = fakeLines.length - 1; i >= 0; i--) {
|
||||
let r = this._tokens._tokenizeText(this._buffer, fakeLines[i], state);
|
||||
if (r) {
|
||||
state = r.endState.clone();
|
||||
} else {
|
||||
state = initialState.clone();
|
||||
}
|
||||
}
|
||||
|
||||
const contextAfter = Math.floor(this._tokens.inValidLineStartIndex * 0.4);
|
||||
endLineNumber = Math.min(this.getLineCount(), endLineNumber + contextAfter);
|
||||
for (let i = startLineNumber; i <= endLineNumber; i++) {
|
||||
let text = this.getLineContent(i);
|
||||
let r = this._tokens._tokenizeText(this._buffer, text, state);
|
||||
if (r) {
|
||||
this._tokens._setTokens(this._tokens.languageIdentifier.id, i - 1, text.length, r.tokens);
|
||||
/*
|
||||
* we think it's valid and give it a state but we don't update `_invalidLineStartIndex` then the top-to-bottom tokenization
|
||||
* goes through the viewport, it can skip them if they already have correct tokens and state, and the lines after the viewport
|
||||
* can still be tokenized.
|
||||
*/
|
||||
this._tokens._setIsInvalid(i - 1, false);
|
||||
this._tokens._setState(i - 1, state);
|
||||
state = r.endState.clone();
|
||||
eventBuilder.registerChangedTokens(i);
|
||||
} else {
|
||||
state = initialState.clone();
|
||||
}
|
||||
}
|
||||
|
||||
const e = eventBuilder.build();
|
||||
if (e) {
|
||||
this._onDidChangeTokens.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
public forceTokenization(lineNumber: number): void {
|
||||
if (lineNumber < 1 || lineNumber > this.getLineCount()) {
|
||||
throw new Error('Illegal value for lineNumber');
|
||||
@@ -1726,8 +1935,39 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
const position = this.validatePosition(_position);
|
||||
const lineContent = this.getLineContent(position.lineNumber);
|
||||
const lineTokens = this._getLineTokens(position.lineNumber);
|
||||
const offset = position.column - 1;
|
||||
const tokenIndex = lineTokens.findTokenIndexAtOffset(offset);
|
||||
const tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1);
|
||||
|
||||
// (1). First try checking right biased word
|
||||
const [rbStartOffset, rbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex);
|
||||
const rightBiasedWord = getWordAtText(
|
||||
position.column,
|
||||
LanguageConfigurationRegistry.getWordDefinition(lineTokens.getLanguageId(tokenIndex)),
|
||||
lineContent.substring(rbStartOffset, rbEndOffset),
|
||||
rbStartOffset
|
||||
);
|
||||
if (rightBiasedWord) {
|
||||
return rightBiasedWord;
|
||||
}
|
||||
|
||||
// (2). Else, if we were at a language boundary, check the left biased word
|
||||
if (tokenIndex > 0 && rbStartOffset === position.column - 1) {
|
||||
// edge case, where `position` sits between two tokens belonging to two different languages
|
||||
const [lbStartOffset, lbEndOffset] = TextModel._findLanguageBoundaries(lineTokens, tokenIndex - 1);
|
||||
const leftBiasedWord = getWordAtText(
|
||||
position.column,
|
||||
LanguageConfigurationRegistry.getWordDefinition(lineTokens.getLanguageId(tokenIndex - 1)),
|
||||
lineContent.substring(lbStartOffset, lbEndOffset),
|
||||
lbStartOffset
|
||||
);
|
||||
if (leftBiasedWord) {
|
||||
return leftBiasedWord;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static _findLanguageBoundaries(lineTokens: LineTokens, tokenIndex: number): [number, number] {
|
||||
const languageId = lineTokens.getLanguageId(tokenIndex);
|
||||
|
||||
// go left until a different language is hit
|
||||
@@ -1742,12 +1982,7 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
endOffset = lineTokens.getEndOffset(i);
|
||||
}
|
||||
|
||||
return getWordAtText(
|
||||
position.column,
|
||||
LanguageConfigurationRegistry.getWordDefinition(languageId),
|
||||
lineContent.substring(startOffset, endOffset),
|
||||
startOffset
|
||||
);
|
||||
return [startOffset, endOffset];
|
||||
}
|
||||
|
||||
public getWordUntilPosition(position: IPosition): model.IWordAtPosition {
|
||||
@@ -1809,25 +2044,13 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
// limit search to not go after `maxBracketLength`
|
||||
const searchEndOffset = Math.min(lineTokens.getEndOffset(tokenIndex), position.column - 1 + currentModeBrackets.maxBracketLength);
|
||||
|
||||
// first, check if there is a bracket to the right of `position`
|
||||
let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, position.column - 1, searchEndOffset);
|
||||
if (foundBracket && foundBracket.startColumn === position.column) {
|
||||
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
|
||||
if (r) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
// it might still be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
|
||||
// it might be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets
|
||||
// `bestResult` will contain the most right-side result
|
||||
let bestResult: [Range, Range] = null;
|
||||
while (true) {
|
||||
let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset);
|
||||
if (!foundBracket) {
|
||||
// there are no brackets in this text
|
||||
// there are no more brackets in this text
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1840,12 +2063,16 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
|
||||
// check that we can actually match this bracket
|
||||
if (r) {
|
||||
return r;
|
||||
bestResult = r;
|
||||
}
|
||||
}
|
||||
|
||||
searchStartOffset = foundBracket.endColumn - 1;
|
||||
}
|
||||
|
||||
if (bestResult) {
|
||||
return bestResult;
|
||||
}
|
||||
}
|
||||
|
||||
// If position is in between two tokens, try also looking in the previous token
|
||||
@@ -1879,6 +2106,10 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
|
||||
private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] {
|
||||
if (!data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isOpen) {
|
||||
let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition());
|
||||
if (matched) {
|
||||
@@ -2158,6 +2389,173 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
return TextModel.computeIndentLevel(this._buffer.getLineContent(lineIndex + 1), this._options.tabSize);
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): model.IActiveIndentGuideInfo {
|
||||
this._assertNotDisposed();
|
||||
const lineCount = this.getLineCount();
|
||||
|
||||
if (lineNumber < 1 || lineNumber > lineCount) {
|
||||
throw new Error('Illegal value for lineNumber');
|
||||
}
|
||||
|
||||
const foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id);
|
||||
const offSide = foldingRules && foldingRules.offSide;
|
||||
|
||||
let up_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
|
||||
let up_aboveContentLineIndent = -1;
|
||||
let up_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
|
||||
let up_belowContentLineIndent = -1;
|
||||
const up_resolveIndents = (lineNumber: number) => {
|
||||
if (up_aboveContentLineIndex !== -1 && (up_aboveContentLineIndex === -2 || up_aboveContentLineIndex > lineNumber - 1)) {
|
||||
up_aboveContentLineIndex = -1;
|
||||
up_aboveContentLineIndent = -1;
|
||||
|
||||
// must find previous line with content
|
||||
for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
|
||||
let indent = this._computeIndentLevel(lineIndex);
|
||||
if (indent >= 0) {
|
||||
up_aboveContentLineIndex = lineIndex;
|
||||
up_aboveContentLineIndent = indent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (up_belowContentLineIndex === -2) {
|
||||
up_belowContentLineIndex = -1;
|
||||
up_belowContentLineIndent = -1;
|
||||
|
||||
// must find next line with content
|
||||
for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
|
||||
let indent = this._computeIndentLevel(lineIndex);
|
||||
if (indent >= 0) {
|
||||
up_belowContentLineIndex = lineIndex;
|
||||
up_belowContentLineIndent = indent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let down_aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */
|
||||
let down_aboveContentLineIndent = -1;
|
||||
let down_belowContentLineIndex = -2; /* -2 is a marker for not having computed it */
|
||||
let down_belowContentLineIndent = -1;
|
||||
const down_resolveIndents = (lineNumber: number) => {
|
||||
if (down_aboveContentLineIndex === -2) {
|
||||
down_aboveContentLineIndex = -1;
|
||||
down_aboveContentLineIndent = -1;
|
||||
|
||||
// must find previous line with content
|
||||
for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) {
|
||||
let indent = this._computeIndentLevel(lineIndex);
|
||||
if (indent >= 0) {
|
||||
down_aboveContentLineIndex = lineIndex;
|
||||
down_aboveContentLineIndent = indent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (down_belowContentLineIndex !== -1 && (down_belowContentLineIndex === -2 || down_belowContentLineIndex < lineNumber - 1)) {
|
||||
down_belowContentLineIndex = -1;
|
||||
down_belowContentLineIndent = -1;
|
||||
|
||||
// must find next line with content
|
||||
for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) {
|
||||
let indent = this._computeIndentLevel(lineIndex);
|
||||
if (indent >= 0) {
|
||||
down_belowContentLineIndex = lineIndex;
|
||||
down_belowContentLineIndent = indent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let startLineNumber = 0;
|
||||
let goUp = true;
|
||||
let endLineNumber = 0;
|
||||
let goDown = true;
|
||||
let indent = 0;
|
||||
|
||||
for (let distance = 0; goUp || goDown; distance++) {
|
||||
const upLineNumber = lineNumber - distance;
|
||||
const downLineNumber = lineNumber + distance;
|
||||
|
||||
if (upLineNumber < 1 || upLineNumber < minLineNumber) {
|
||||
goUp = false;
|
||||
}
|
||||
if (downLineNumber > lineCount || downLineNumber > maxLineNumber) {
|
||||
goDown = false;
|
||||
}
|
||||
if (distance > 50000) {
|
||||
// stop processing
|
||||
goUp = false;
|
||||
goDown = false;
|
||||
}
|
||||
|
||||
if (goUp) {
|
||||
// compute indent level going up
|
||||
let upLineIndentLevel: number;
|
||||
|
||||
const currentIndent = this._computeIndentLevel(upLineNumber - 1);
|
||||
if (currentIndent >= 0) {
|
||||
// This line has content (besides whitespace)
|
||||
// Use the line's indent
|
||||
up_belowContentLineIndex = upLineNumber - 1;
|
||||
up_belowContentLineIndent = currentIndent;
|
||||
upLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize);
|
||||
} else {
|
||||
up_resolveIndents(upLineNumber);
|
||||
upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent);
|
||||
}
|
||||
|
||||
if (distance === 0) {
|
||||
// This is the initial line number
|
||||
startLineNumber = upLineNumber;
|
||||
endLineNumber = downLineNumber;
|
||||
indent = upLineIndentLevel;
|
||||
if (indent === 0) {
|
||||
// No need to continue
|
||||
return { startLineNumber, endLineNumber, indent };
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (upLineIndentLevel >= indent) {
|
||||
startLineNumber = upLineNumber;
|
||||
} else {
|
||||
goUp = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (goDown) {
|
||||
// compute indent level going down
|
||||
let downLineIndentLevel: number;
|
||||
|
||||
const currentIndent = this._computeIndentLevel(downLineNumber - 1);
|
||||
if (currentIndent >= 0) {
|
||||
// This line has content (besides whitespace)
|
||||
// Use the line's indent
|
||||
down_aboveContentLineIndex = downLineNumber - 1;
|
||||
down_aboveContentLineIndent = currentIndent;
|
||||
downLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize);
|
||||
} else {
|
||||
down_resolveIndents(downLineNumber);
|
||||
downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent);
|
||||
}
|
||||
|
||||
if (downLineIndentLevel >= indent) {
|
||||
endLineNumber = downLineNumber;
|
||||
} else {
|
||||
goDown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { startLineNumber, endLineNumber, indent };
|
||||
}
|
||||
|
||||
public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {
|
||||
this._assertNotDisposed();
|
||||
const lineCount = this.getLineCount();
|
||||
@@ -2223,32 +2621,38 @@ export class TextModel extends Disposable implements model.ITextModel {
|
||||
}
|
||||
}
|
||||
|
||||
if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
|
||||
// At the top or bottom of the file
|
||||
result[resultIndex] = 0;
|
||||
result[resultIndex] = this._getIndentLevelForWhitespaceLine(offSide, aboveContentLineIndent, belowContentLineIndent);
|
||||
|
||||
} else if (aboveContentLineIndent < belowContentLineIndent) {
|
||||
// we are inside the region above
|
||||
result[resultIndex] = (1 + Math.floor(aboveContentLineIndent / this._options.tabSize));
|
||||
|
||||
} else if (aboveContentLineIndent === belowContentLineIndent) {
|
||||
// we are in between two regions
|
||||
result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize);
|
||||
|
||||
} else {
|
||||
|
||||
if (offSide) {
|
||||
// same level as region below
|
||||
result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize);
|
||||
} else {
|
||||
// we are inside the region that ends below
|
||||
result[resultIndex] = (1 + Math.floor(belowContentLineIndent / this._options.tabSize));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private _getIndentLevelForWhitespaceLine(offSide: boolean, aboveContentLineIndent: number, belowContentLineIndent: number): number {
|
||||
if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) {
|
||||
// At the top or bottom of the file
|
||||
return 0;
|
||||
|
||||
} else if (aboveContentLineIndent < belowContentLineIndent) {
|
||||
// we are inside the region above
|
||||
return (1 + Math.floor(aboveContentLineIndent / this._options.tabSize));
|
||||
|
||||
} else if (aboveContentLineIndent === belowContentLineIndent) {
|
||||
// we are in between two regions
|
||||
return Math.ceil(belowContentLineIndent / this._options.tabSize);
|
||||
|
||||
} else {
|
||||
|
||||
if (offSide) {
|
||||
// same level as region below
|
||||
return Math.ceil(belowContentLineIndent / this._options.tabSize);
|
||||
} else {
|
||||
// we are inside the region that ends below
|
||||
return (1 + Math.floor(belowContentLineIndent / this._options.tabSize));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
@@ -2363,22 +2767,20 @@ export class ModelDecorationOverviewRulerOptions implements model.IModelDecorati
|
||||
}
|
||||
}
|
||||
|
||||
let lastStaticId = 0;
|
||||
|
||||
export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
||||
|
||||
public static EMPTY: ModelDecorationOptions;
|
||||
|
||||
public static register(options: model.IModelDecorationOptions): ModelDecorationOptions {
|
||||
return new ModelDecorationOptions(++lastStaticId, options);
|
||||
return new ModelDecorationOptions(options);
|
||||
}
|
||||
|
||||
public static createDynamic(options: model.IModelDecorationOptions): ModelDecorationOptions {
|
||||
return new ModelDecorationOptions(0, options);
|
||||
return new ModelDecorationOptions(options);
|
||||
}
|
||||
|
||||
readonly staticId: number;
|
||||
readonly stickiness: model.TrackedRangeStickiness;
|
||||
readonly zIndex: number;
|
||||
readonly className: string;
|
||||
readonly hoverMessage: IMarkdownString | IMarkdownString[];
|
||||
readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[];
|
||||
@@ -2389,12 +2791,13 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
||||
readonly linesDecorationsClassName: string;
|
||||
readonly marginClassName: string;
|
||||
readonly inlineClassName: string;
|
||||
readonly inlineClassNameAffectsLetterSpacing: boolean;
|
||||
readonly beforeContentClassName: string;
|
||||
readonly afterContentClassName: string;
|
||||
|
||||
private constructor(staticId: number, options: model.IModelDecorationOptions) {
|
||||
this.staticId = staticId;
|
||||
private constructor(options: model.IModelDecorationOptions) {
|
||||
this.stickiness = options.stickiness || model.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges;
|
||||
this.zIndex = options.zIndex || 0;
|
||||
this.className = options.className ? cleanClassName(options.className) : strings.empty;
|
||||
this.hoverMessage = options.hoverMessage || [];
|
||||
this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || [];
|
||||
@@ -2405,6 +2808,7 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
|
||||
this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : strings.empty;
|
||||
this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : strings.empty;
|
||||
this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : strings.empty;
|
||||
this.inlineClassNameAffectsLetterSpacing = options.inlineClassNameAffectsLetterSpacing || false;
|
||||
this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : strings.empty;
|
||||
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : strings.empty;
|
||||
}
|
||||
@@ -2465,8 +2869,13 @@ export class DidChangeDecorationsEmitter extends Disposable {
|
||||
|
||||
export class DidChangeContentEmitter extends Disposable {
|
||||
|
||||
private readonly _actual: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
|
||||
public readonly event: Event<InternalModelContentChangeEvent> = this._actual.event;
|
||||
/**
|
||||
* Both `fastEvent` and `slowEvent` work the same way and contain the same events, but first we invoke `fastEvent` and then `slowEvent`.
|
||||
*/
|
||||
private readonly _fastEmitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
|
||||
public readonly fastEvent: Event<InternalModelContentChangeEvent> = this._fastEmitter.event;
|
||||
private readonly _slowEmitter: Emitter<InternalModelContentChangeEvent> = this._register(new Emitter<InternalModelContentChangeEvent>());
|
||||
public readonly slowEvent: Event<InternalModelContentChangeEvent> = this._slowEmitter.event;
|
||||
|
||||
private _deferredCnt: number;
|
||||
private _deferredEvent: InternalModelContentChangeEvent;
|
||||
@@ -2487,7 +2896,8 @@ export class DidChangeContentEmitter extends Disposable {
|
||||
if (this._deferredEvent !== null) {
|
||||
const e = this._deferredEvent;
|
||||
this._deferredEvent = null;
|
||||
this._actual.fire(e);
|
||||
this._fastEmitter.fire(e);
|
||||
this._slowEmitter.fire(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2501,6 +2911,7 @@ export class DidChangeContentEmitter extends Disposable {
|
||||
}
|
||||
return;
|
||||
}
|
||||
this._actual.fire(e);
|
||||
this._fastEmitter.fire(e);
|
||||
this._slowEmitter.fire(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,10 @@ export interface IModelContentChange {
|
||||
* The range that got replaced.
|
||||
*/
|
||||
readonly range: IRange;
|
||||
/**
|
||||
* The offset of the range that got replaced.
|
||||
*/
|
||||
readonly rangeOffset: number;
|
||||
/**
|
||||
* The length of the range that got replaced.
|
||||
*/
|
||||
|
||||
@@ -116,7 +116,7 @@ export class SearchData {
|
||||
}
|
||||
}
|
||||
|
||||
function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
|
||||
export function createFindMatch(range: Range, rawMatches: RegExpExecArray, captureMatches: boolean): FindMatch {
|
||||
if (!captureMatches) {
|
||||
return new FindMatch(range, null);
|
||||
}
|
||||
@@ -434,6 +434,11 @@ function leftIsWordBounday(wordSeparators: WordCharacterClassifier, text: string
|
||||
return true;
|
||||
}
|
||||
|
||||
if (charBefore === CharCode.CarriageReturn || charBefore === CharCode.LineFeed) {
|
||||
// The character before the match is line break or carriage return.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchLength > 0) {
|
||||
const firstCharInMatch = text.charCodeAt(matchStartIndex);
|
||||
if (wordSeparators.get(firstCharInMatch) !== WordCharacterClass.Regular) {
|
||||
@@ -457,6 +462,11 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
|
||||
return true;
|
||||
}
|
||||
|
||||
if (charAfter === CharCode.CarriageReturn || charAfter === CharCode.LineFeed) {
|
||||
// The character after the match is line break or carriage return.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (matchLength > 0) {
|
||||
const lastCharInMatch = text.charCodeAt(matchStartIndex + matchLength - 1);
|
||||
if (wordSeparators.get(lastCharInMatch) !== WordCharacterClass.Regular) {
|
||||
@@ -468,14 +478,14 @@ function rightIsWordBounday(wordSeparators: WordCharacterClassifier, text: strin
|
||||
return false;
|
||||
}
|
||||
|
||||
function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
|
||||
export function isValidMatch(wordSeparators: WordCharacterClassifier, text: string, textLength: number, matchStartIndex: number, matchLength: number): boolean {
|
||||
return (
|
||||
leftIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
|
||||
&& rightIsWordBounday(wordSeparators, text, textLength, matchStartIndex, matchLength)
|
||||
);
|
||||
}
|
||||
|
||||
class Searcher {
|
||||
export class Searcher {
|
||||
private _wordSeparators: WordCharacterClassifier;
|
||||
private _searchRegex: RegExp;
|
||||
private _prevMatchStartIndex: number;
|
||||
|
||||
@@ -25,7 +25,7 @@ function getDefaultMetadata(topLevelLanguageId: LanguageId): number {
|
||||
) >>> 0;
|
||||
}
|
||||
|
||||
const EMPTY_LINE_TOKENS = new Uint32Array(0);
|
||||
const EMPTY_LINE_TOKENS = (new Uint32Array(0)).buffer;
|
||||
|
||||
class ModelLineTokens {
|
||||
_state: IState;
|
||||
@@ -196,9 +196,13 @@ export class ModelLinesTokens {
|
||||
this._lastState = null;
|
||||
}
|
||||
|
||||
public get inValidLineStartIndex() {
|
||||
return this._invalidLineStartIndex;
|
||||
}
|
||||
|
||||
public getTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineText: string): LineTokens {
|
||||
let rawLineTokens: ArrayBuffer = null;
|
||||
if (lineIndex < this._tokens.length) {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
rawLineTokens = this._tokens[lineIndex]._lineTokens;
|
||||
}
|
||||
|
||||
@@ -229,21 +233,21 @@ export class ModelLinesTokens {
|
||||
}
|
||||
}
|
||||
|
||||
private _setIsInvalid(lineIndex: number, invalid: boolean): void {
|
||||
if (lineIndex < this._tokens.length) {
|
||||
_setIsInvalid(lineIndex: number, invalid: boolean): void {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
this._tokens[lineIndex]._invalid = invalid;
|
||||
}
|
||||
}
|
||||
|
||||
_isInvalid(lineIndex: number): boolean {
|
||||
if (lineIndex < this._tokens.length) {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
return this._tokens[lineIndex]._invalid;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
_getState(lineIndex: number): IState {
|
||||
if (lineIndex < this._tokens.length) {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
return this._tokens[lineIndex]._state;
|
||||
}
|
||||
return null;
|
||||
@@ -251,7 +255,7 @@ export class ModelLinesTokens {
|
||||
|
||||
_setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, tokens: Uint32Array): void {
|
||||
let target: ModelLineTokens;
|
||||
if (lineIndex < this._tokens.length) {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
target = this._tokens[lineIndex];
|
||||
} else {
|
||||
target = new ModelLineTokens(null);
|
||||
@@ -274,8 +278,8 @@ export class ModelLinesTokens {
|
||||
target._lineTokens = tokens.buffer;
|
||||
}
|
||||
|
||||
private _setState(lineIndex: number, state: IState): void {
|
||||
if (lineIndex < this._tokens.length) {
|
||||
_setState(lineIndex: number, state: IState): void {
|
||||
if (lineIndex < this._tokens.length && this._tokens[lineIndex]) {
|
||||
this._tokens[lineIndex]._state = state;
|
||||
} else {
|
||||
const tmp = new ModelLineTokens(state);
|
||||
@@ -376,6 +380,21 @@ export class ModelLinesTokens {
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
public _tokenizeText(buffer: ITextBuffer, text: string, state: IState): TokenizationResult2 {
|
||||
let r: TokenizationResult2 = null;
|
||||
|
||||
try {
|
||||
r = this.tokenizationSupport.tokenize2(text, state, 0);
|
||||
} catch (e) {
|
||||
onUnexpectedError(e);
|
||||
}
|
||||
|
||||
if (!r) {
|
||||
r = nullTokenize2(this.languageIdentifier.id, text, state, 0);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public _updateTokensUntilLine(buffer: ITextBuffer, eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void {
|
||||
if (!this.tokenizationSupport) {
|
||||
this._invalidLineStartIndex = buffer.getLineCount();
|
||||
|
||||
@@ -12,7 +12,7 @@ import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegis
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IMarkerData } from 'vs/platform/markers/common/markers';
|
||||
@@ -230,7 +230,7 @@ export interface Hover {
|
||||
* editor will use the range at the current position or the
|
||||
* current position itself.
|
||||
*/
|
||||
range: IRange;
|
||||
range?: IRange;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -364,6 +364,11 @@ export interface CodeActionProvider {
|
||||
* Provide commands for the given document and range.
|
||||
*/
|
||||
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeAction[] | Thenable<CodeAction[]>;
|
||||
|
||||
/**
|
||||
* Optional list of of CodeActionKinds that this provider returns.
|
||||
*/
|
||||
providedCodeActionKinds?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -825,66 +830,60 @@ export interface DocumentColorProvider {
|
||||
*/
|
||||
provideColorPresentations(model: model.ITextModel, colorInfo: IColorInformation, token: CancellationToken): IColorPresentation[] | Thenable<IColorPresentation[]>;
|
||||
}
|
||||
|
||||
export interface FoldingContext {
|
||||
}
|
||||
/**
|
||||
* A provider of colors for editor models.
|
||||
*/
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface FoldingProvider {
|
||||
export interface FoldingRangeProvider {
|
||||
/**
|
||||
* Provides the color ranges for a specific model.
|
||||
*/
|
||||
provideFoldingRanges(model: model.ITextModel, token: CancellationToken): IFoldingRangeList | Thenable<IFoldingRangeList>;
|
||||
provideFoldingRanges(model: model.ITextModel, context: FoldingContext, token: CancellationToken): FoldingRange[] | Thenable<FoldingRange[]>;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IFoldingRangeList {
|
||||
|
||||
ranges: IFoldingRange[];
|
||||
export interface FoldingRange {
|
||||
|
||||
/**
|
||||
* The zero-based start line of the range to fold. The folded area starts after the line's last character.
|
||||
*/
|
||||
start: number;
|
||||
|
||||
/**
|
||||
* The zero-based end line of the range to fold. The folded area ends with the line's last character.
|
||||
*/
|
||||
end: number;
|
||||
|
||||
/**
|
||||
* Describes the [Kind](#FoldingRangeKind) of the folding range such as [Comment](#FoldingRangeKind.Comment) or
|
||||
* [Region](#FoldingRangeKind.Region). The kind is used to categorize folding ranges and used by commands
|
||||
* like 'Fold all comments'. See
|
||||
* [FoldingRangeKind](#FoldingRangeKind) for an enumeration of standardized kinds.
|
||||
*/
|
||||
kind?: FoldingRangeKind;
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IFoldingRange {
|
||||
export class FoldingRangeKind {
|
||||
/**
|
||||
* Kind for folding range representing a comment. The value of the kind is 'comment'.
|
||||
*/
|
||||
static readonly Comment = new FoldingRangeKind('comment');
|
||||
/**
|
||||
* Kind for folding range representing a import. The value of the kind is 'imports'.
|
||||
*/
|
||||
static readonly Imports = new FoldingRangeKind('imports');
|
||||
/**
|
||||
* Kind for folding range representing regions (for example marked by `#region`, `#endregion`).
|
||||
* The value of the kind is 'region'.
|
||||
*/
|
||||
static readonly Region = new FoldingRangeKind('region');
|
||||
|
||||
/**
|
||||
* The start line number
|
||||
* Creates a new [FoldingRangeKind](#FoldingRangeKind).
|
||||
*
|
||||
* @param value of the kind.
|
||||
*/
|
||||
startLineNumber: number;
|
||||
|
||||
/**
|
||||
* The end line number
|
||||
*/
|
||||
endLineNumber: number;
|
||||
|
||||
/**
|
||||
* The optional type of the folding range
|
||||
*/
|
||||
type?: FoldingRangeType | string;
|
||||
|
||||
// auto-collapse
|
||||
// header span
|
||||
|
||||
}
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export enum FoldingRangeType {
|
||||
/**
|
||||
* Folding range for a comment
|
||||
*/
|
||||
Comment = 'comment',
|
||||
/**
|
||||
* Folding range for a imports or includes
|
||||
*/
|
||||
Imports = 'imports',
|
||||
/**
|
||||
* Folding range for a region (e.g. `#region`)
|
||||
*/
|
||||
Region = 'region'
|
||||
public constructor(public value: string) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -917,14 +916,14 @@ export interface WorkspaceEdit {
|
||||
rejectReason?: string; // TODO@joh, move to rename
|
||||
}
|
||||
|
||||
export interface RenameInitialValue {
|
||||
export interface RenameLocation {
|
||||
range: IRange;
|
||||
text?: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface RenameProvider {
|
||||
provideRenameEdits(model: model.ITextModel, position: Position, newName: string, token: CancellationToken): WorkspaceEdit | Thenable<WorkspaceEdit>;
|
||||
resolveInitialRenameValue?(model: model.ITextModel, position: Position, token: CancellationToken): RenameInitialValue | Thenable<RenameInitialValue>;
|
||||
resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): RenameLocation | Thenable<RenameLocation>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1035,7 +1034,7 @@ export const ColorProviderRegistry = new LanguageFeatureRegistry<DocumentColorPr
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export const FoldingProviderRegistry = new LanguageFeatureRegistry<FoldingProvider>();
|
||||
export const FoldingRangeProviderRegistry = new LanguageFeatureRegistry<FoldingRangeProvider>();
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
||||
@@ -9,7 +9,7 @@ import { BracketElectricCharacterSupport, IElectricAction } from 'vs/editor/comm
|
||||
import { IOnEnterSupportOptions, OnEnterSupport } from 'vs/editor/common/modes/supports/onEnter';
|
||||
import { IndentRulesSupport, IndentConsts } from 'vs/editor/common/modes/supports/indentRules';
|
||||
import { RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
@@ -177,8 +177,8 @@ export class LanguageConfigurationRegistryImpl {
|
||||
|
||||
private _entries: RichEditSupport[];
|
||||
|
||||
private _onDidChange: Emitter<LanguageConfigurationChangeEvent> = new Emitter<LanguageConfigurationChangeEvent>();
|
||||
public onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
|
||||
private readonly _onDidChange: Emitter<LanguageConfigurationChangeEvent> = new Emitter<LanguageConfigurationChangeEvent>();
|
||||
public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;
|
||||
|
||||
constructor() {
|
||||
this._entries = [];
|
||||
|
||||
@@ -5,10 +5,11 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { LanguageSelector, score } from 'vs/editor/common/modes/languageSelector';
|
||||
import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService';
|
||||
|
||||
interface Entry<T> {
|
||||
selector: LanguageSelector;
|
||||
@@ -17,11 +18,21 @@ interface Entry<T> {
|
||||
_time: number;
|
||||
}
|
||||
|
||||
function isExclusive(selector: LanguageSelector): boolean {
|
||||
if (typeof selector === 'string') {
|
||||
return false;
|
||||
} else if (Array.isArray(selector)) {
|
||||
return selector.every(isExclusive);
|
||||
} else {
|
||||
return selector.exclusive;
|
||||
}
|
||||
}
|
||||
|
||||
export default class LanguageFeatureRegistry<T> {
|
||||
|
||||
private _clock: number = 0;
|
||||
private _entries: Entry<T>[] = [];
|
||||
private _onDidChange: Emitter<number> = new Emitter<number>();
|
||||
private readonly _onDidChange: Emitter<number> = new Emitter<number>();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
@@ -63,7 +74,7 @@ export default class LanguageFeatureRegistry<T> {
|
||||
}
|
||||
|
||||
all(model: ITextModel): T[] {
|
||||
if (!model || model.isTooLargeForHavingARichMode()) {
|
||||
if (!model) {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -106,7 +117,7 @@ export default class LanguageFeatureRegistry<T> {
|
||||
|
||||
private _orderedForEach(model: ITextModel, callback: (provider: Entry<T>) => any): void {
|
||||
|
||||
if (!model || model.isTooLargeForHavingARichMode()) {
|
||||
if (!model) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -140,7 +151,17 @@ export default class LanguageFeatureRegistry<T> {
|
||||
this._lastCandidate = candidate;
|
||||
|
||||
for (let entry of this._entries) {
|
||||
entry._score = score(entry.selector, model.uri, model.getLanguageIdentifier().language);
|
||||
entry._score = score(entry.selector, model.uri, model.getLanguageIdentifier().language, shouldSynchronizeModel(model));
|
||||
|
||||
if (isExclusive(entry.selector) && entry._score > 0) {
|
||||
// support for one exclusive selector that overwrites
|
||||
// any other selector
|
||||
for (let entry of this._entries) {
|
||||
entry._score = 0;
|
||||
}
|
||||
entry._score = 1000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// needs sorting
|
||||
|
||||
@@ -12,17 +12,22 @@ export interface LanguageFilter {
|
||||
language?: string;
|
||||
scheme?: string;
|
||||
pattern?: string | IRelativePattern;
|
||||
/**
|
||||
* This provider is implemented in the UI thread.
|
||||
*/
|
||||
hasAccessToAllModels?: boolean;
|
||||
exclusive?: boolean;
|
||||
}
|
||||
|
||||
export type LanguageSelector = string | LanguageFilter | (string | LanguageFilter)[];
|
||||
|
||||
export function score(selector: LanguageSelector, candidateUri: URI, candidateLanguage: string): number {
|
||||
export function score(selector: LanguageSelector, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean): number {
|
||||
|
||||
if (Array.isArray(selector)) {
|
||||
// array -> take max individual value
|
||||
let ret = 0;
|
||||
for (const filter of selector) {
|
||||
const value = score(filter, candidateUri, candidateLanguage);
|
||||
const value = score(filter, candidateUri, candidateLanguage, candidateIsSynchronized);
|
||||
if (value === 10) {
|
||||
return value; // already at the highest
|
||||
}
|
||||
@@ -33,9 +38,14 @@ export function score(selector: LanguageSelector, candidateUri: URI, candidateLa
|
||||
return ret;
|
||||
|
||||
} else if (typeof selector === 'string') {
|
||||
|
||||
if (!candidateIsSynchronized) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// short-hand notion, desugars to
|
||||
// 'fooLang' -> [{ language: 'fooLang', scheme: 'file' }, { language: 'fooLang', scheme: 'untitled' }]
|
||||
// '*' -> { language: '*', scheme: '*' }
|
||||
// 'fooLang' -> { language: 'fooLang'}
|
||||
// '*' -> { language: '*' }
|
||||
if (selector === '*') {
|
||||
return 5;
|
||||
} else if (selector === candidateLanguage) {
|
||||
@@ -46,7 +56,11 @@ export function score(selector: LanguageSelector, candidateUri: URI, candidateLa
|
||||
|
||||
} else if (selector) {
|
||||
// filter -> select accordingly, use defaults for scheme
|
||||
const { language, pattern, scheme } = selector;
|
||||
const { language, pattern, scheme, hasAccessToAllModels } = selector;
|
||||
|
||||
if (!candidateIsSynchronized && !hasAccessToAllModels) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let ret = 0;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
|
||||
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
@@ -20,8 +20,8 @@ export class EditorModesRegistry {
|
||||
|
||||
private _languages: ILanguageExtensionPoint[];
|
||||
|
||||
private _onDidAddLanguages: Emitter<ILanguageExtensionPoint[]> = new Emitter<ILanguageExtensionPoint[]>();
|
||||
public onDidAddLanguages: Event<ILanguageExtensionPoint[]> = this._onDidAddLanguages.event;
|
||||
private readonly _onDidAddLanguages: Emitter<ILanguageExtensionPoint[]> = new Emitter<ILanguageExtensionPoint[]>();
|
||||
public readonly onDidAddLanguages: Event<ILanguageExtensionPoint[]> = this._onDidAddLanguages.event;
|
||||
|
||||
constructor() {
|
||||
this._languages = [];
|
||||
|
||||
@@ -85,14 +85,14 @@ function once<T, R>(keyFn: (input: T) => string, computeFn: (input: T) => R): (i
|
||||
const getRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
|
||||
(input) => `${input.open};${input.close}`,
|
||||
(input) => {
|
||||
return createOrRegex([input.open, input.close]);
|
||||
return createBracketOrRegExp([input.open, input.close]);
|
||||
}
|
||||
);
|
||||
|
||||
const getReversedRegexForBracketPair = once<ISimpleInternalBracket, RegExp>(
|
||||
(input) => `${input.open};${input.close}`,
|
||||
(input) => {
|
||||
return createOrRegex([toReversedString(input.open), toReversedString(input.close)]);
|
||||
return createBracketOrRegExp([toReversedString(input.open), toReversedString(input.close)]);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -104,7 +104,7 @@ const getRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
|
||||
pieces.push(b.open);
|
||||
pieces.push(b.close);
|
||||
});
|
||||
return createOrRegex(pieces);
|
||||
return createBracketOrRegExp(pieces);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -116,12 +116,19 @@ const getReversedRegexForBrackets = once<ISimpleInternalBracket[], RegExp>(
|
||||
pieces.push(toReversedString(b.open));
|
||||
pieces.push(toReversedString(b.close));
|
||||
});
|
||||
return createOrRegex(pieces);
|
||||
return createBracketOrRegExp(pieces);
|
||||
}
|
||||
);
|
||||
|
||||
function createOrRegex(pieces: string[]): RegExp {
|
||||
let regexStr = `(${pieces.map(strings.escapeRegExpCharacters).join(')|(')})`;
|
||||
function prepareBracketForRegExp(str: string): string {
|
||||
// This bracket pair uses letters like e.g. "begin" - "end"
|
||||
const insertWordBoundaries = (/^[\w]+$/.test(str));
|
||||
str = strings.escapeRegExpCharacters(str);
|
||||
return (insertWordBoundaries ? `\\b${str}\\b` : str);
|
||||
}
|
||||
|
||||
function createBracketOrRegExp(pieces: string[]): RegExp {
|
||||
let regexStr = `(${pieces.map(prepareBracketForRegExp).join(')|(')})`;
|
||||
return strings.createRegExp(regexStr, true);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { ColorId, ITokenizationRegistry, ITokenizationSupport, ITokenizationSupportChangedEvent } from 'vs/editor/common/modes';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
|
||||
@@ -13,8 +13,8 @@ export class TokenizationRegistryImpl implements ITokenizationRegistry {
|
||||
|
||||
private _map: { [language: string]: ITokenizationSupport };
|
||||
|
||||
private _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
|
||||
public onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
|
||||
private readonly _onDidChange: Emitter<ITokenizationSupportChangedEvent> = new Emitter<ITokenizationSupportChangedEvent>();
|
||||
public readonly onDidChange: Event<ITokenizationSupportChangedEvent> = this._onDidChange.event;
|
||||
|
||||
private _colorMap: Color[];
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import { getWordAtText, ensureValidWordDefinition } from 'vs/editor/common/model
|
||||
import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase';
|
||||
import { IWordAtPosition, EndOfLineSequence } from 'vs/editor/common/model';
|
||||
import { globals } from 'vs/base/common/platform';
|
||||
import { IIterator } from 'vs/base/common/iterator';
|
||||
|
||||
export interface IMirrorModel {
|
||||
readonly uri: URI;
|
||||
@@ -58,8 +59,8 @@ export interface ICommonModel {
|
||||
getLinesContent(): string[];
|
||||
getLineCount(): number;
|
||||
getLineContent(lineNumber: number): string;
|
||||
createWordIterator(wordDefinition: RegExp): IIterator<string>;
|
||||
getWordUntilPosition(position: IPosition, wordDefinition: RegExp): IWordAtPosition;
|
||||
getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[];
|
||||
getValueInRange(range: IRange): string;
|
||||
getWordAtPosition(position: IPosition, wordDefinition: RegExp): Range;
|
||||
offsetAt(position: IPosition): number;
|
||||
@@ -146,30 +147,37 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel {
|
||||
};
|
||||
}
|
||||
|
||||
private _getAllWords(wordDefinition: RegExp): string[] {
|
||||
let result: string[] = [];
|
||||
this._lines.forEach((line) => {
|
||||
this._wordenize(line, wordDefinition).forEach((info) => {
|
||||
result.push(line.substring(info.start, info.end));
|
||||
});
|
||||
});
|
||||
return result;
|
||||
}
|
||||
public createWordIterator(wordDefinition: RegExp): IIterator<string> {
|
||||
let obj = {
|
||||
done: false,
|
||||
value: ''
|
||||
};
|
||||
let lineNumber = 0;
|
||||
let lineText: string;
|
||||
let wordRangesIdx = 0;
|
||||
let wordRanges: IWordRange[] = [];
|
||||
let next = () => {
|
||||
|
||||
if (wordRangesIdx < wordRanges.length) {
|
||||
obj.done = false;
|
||||
obj.value = lineText.substring(wordRanges[wordRangesIdx].start, wordRanges[wordRangesIdx].end);
|
||||
wordRangesIdx += 1;
|
||||
|
||||
} else if (lineNumber >= this._lines.length) {
|
||||
obj.done = true;
|
||||
obj.value = undefined;
|
||||
|
||||
public getAllUniqueWords(wordDefinition: RegExp, skipWordOnce?: string): string[] {
|
||||
let foundSkipWord = false;
|
||||
let uniqueWords = Object.create(null);
|
||||
return this._getAllWords(wordDefinition).filter((word) => {
|
||||
if (skipWordOnce && !foundSkipWord && skipWordOnce === word) {
|
||||
foundSkipWord = true;
|
||||
return false;
|
||||
} else if (uniqueWords[word]) {
|
||||
return false;
|
||||
} else {
|
||||
uniqueWords[word] = true;
|
||||
return true;
|
||||
lineText = this._lines[lineNumber];
|
||||
wordRanges = this._wordenize(lineText, wordDefinition);
|
||||
wordRangesIdx = 0;
|
||||
lineNumber += 1;
|
||||
return next();
|
||||
}
|
||||
});
|
||||
|
||||
return obj;
|
||||
};
|
||||
return { next };
|
||||
}
|
||||
|
||||
private _wordenize(content: string, wordDefinition: RegExp): IWordRange[] {
|
||||
@@ -288,13 +296,24 @@ class MirrorModel extends BaseMirrorModel implements ICommonModel {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export interface IForeignModuleFactory {
|
||||
(ctx: IWorkerContext, createData: any): any;
|
||||
}
|
||||
|
||||
declare var require;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export abstract class BaseEditorSimpleWorker {
|
||||
private _foreignModuleFactory: IForeignModuleFactory;
|
||||
private _foreignModule: any;
|
||||
|
||||
constructor() {
|
||||
constructor(foreignModuleFactory: IForeignModuleFactory) {
|
||||
this._foreignModuleFactory = foreignModuleFactory;
|
||||
this._foreignModule = null;
|
||||
}
|
||||
|
||||
@@ -416,6 +435,8 @@ export abstract class BaseEditorSimpleWorker {
|
||||
|
||||
// ---- BEGIN suggest --------------------------------------------------------------------------
|
||||
|
||||
private static readonly _suggestionsLimit = 10000;
|
||||
|
||||
public textualSuggest(modelUrl: string, position: IPosition, wordDef: string, wordDefFlags: string): TPromise<ISuggestResult> {
|
||||
const model = this._getModel(modelUrl);
|
||||
if (model) {
|
||||
@@ -423,17 +444,32 @@ export abstract class BaseEditorSimpleWorker {
|
||||
const wordDefRegExp = new RegExp(wordDef, wordDefFlags);
|
||||
const currentWord = model.getWordUntilPosition(position, wordDefRegExp).word;
|
||||
|
||||
for (const word of model.getAllUniqueWords(wordDefRegExp)) {
|
||||
if (word !== currentWord && isNaN(Number(word))) {
|
||||
suggestions.push({
|
||||
type: 'text',
|
||||
label: word,
|
||||
insertText: word,
|
||||
noAutoAccept: true,
|
||||
overwriteBefore: currentWord.length
|
||||
});
|
||||
const seen: Record<string, boolean> = Object.create(null);
|
||||
seen[currentWord] = true;
|
||||
|
||||
for (
|
||||
let iter = model.createWordIterator(wordDefRegExp), e = iter.next();
|
||||
!e.done && suggestions.length <= BaseEditorSimpleWorker._suggestionsLimit;
|
||||
e = iter.next()
|
||||
) {
|
||||
const word = e.value;
|
||||
if (seen[word]) {
|
||||
continue;
|
||||
}
|
||||
seen[word] = true;
|
||||
if (!isNaN(Number(word))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestions.push({
|
||||
type: 'text',
|
||||
label: word,
|
||||
insertText: word,
|
||||
noAutoAccept: true,
|
||||
overwriteBefore: currentWord.length
|
||||
});
|
||||
}
|
||||
|
||||
return TPromise.as({ suggestions });
|
||||
}
|
||||
return undefined;
|
||||
@@ -474,14 +510,25 @@ export abstract class BaseEditorSimpleWorker {
|
||||
// ---- BEGIN foreign module support --------------------------------------------------------------------------
|
||||
|
||||
public loadForeignModule(moduleId: string, createData: any): TPromise<string[]> {
|
||||
let ctx: IWorkerContext = {
|
||||
getMirrorModels: (): IMirrorModel[] => {
|
||||
return this._getModels();
|
||||
}
|
||||
};
|
||||
|
||||
if (this._foreignModuleFactory) {
|
||||
this._foreignModule = this._foreignModuleFactory(ctx, createData);
|
||||
// static foreing module
|
||||
let methods: string[] = [];
|
||||
for (let prop in this._foreignModule) {
|
||||
if (typeof this._foreignModule[prop] === 'function') {
|
||||
methods.push(prop);
|
||||
}
|
||||
}
|
||||
return TPromise.as(methods);
|
||||
}
|
||||
return new TPromise<any>((c, e) => {
|
||||
// Use the global require to be sure to get the global config
|
||||
(<any>self).require([moduleId], (foreignModule: { create: (ctx: IWorkerContext, createData: any) => any; }) => {
|
||||
let ctx: IWorkerContext = {
|
||||
getMirrorModels: (): IMirrorModel[] => {
|
||||
return this._getModels();
|
||||
}
|
||||
};
|
||||
require([moduleId], (foreignModule: { create: IForeignModuleFactory }) => {
|
||||
this._foreignModule = foreignModule.create(ctx, createData);
|
||||
|
||||
let methods: string[] = [];
|
||||
@@ -521,8 +568,8 @@ export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IR
|
||||
|
||||
private _models: { [uri: string]: MirrorModel; };
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor(foreignModuleFactory: IForeignModuleFactory) {
|
||||
super(foreignModuleFactory);
|
||||
this._models = Object.create(null);
|
||||
}
|
||||
|
||||
@@ -565,9 +612,12 @@ export class EditorSimpleWorkerImpl extends BaseEditorSimpleWorker implements IR
|
||||
* @internal
|
||||
*/
|
||||
export function create(): IRequestHandler {
|
||||
return new EditorSimpleWorkerImpl();
|
||||
return new EditorSimpleWorkerImpl(null);
|
||||
}
|
||||
|
||||
// This is only available in a Web Worker
|
||||
declare function importScripts(...urls: string[]): void;
|
||||
|
||||
if (typeof importScripts === 'function') {
|
||||
// Running in a web worker
|
||||
globals.monaco = createMonacoBaseAPI();
|
||||
|
||||
@@ -351,7 +351,7 @@ export class EditorWorkerClient extends Disposable {
|
||||
));
|
||||
} catch (err) {
|
||||
logOnceWebWorkerWarning(err);
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
|
||||
}
|
||||
}
|
||||
return this._worker;
|
||||
@@ -360,7 +360,7 @@ export class EditorWorkerClient extends Disposable {
|
||||
protected _getProxy(): TPromise<EditorSimpleWorkerImpl> {
|
||||
return new ShallowCancelThenPromise(this._getOrCreateWorker().getProxyObject().then(null, (err) => {
|
||||
logOnceWebWorkerWarning(err);
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl());
|
||||
this._worker = new SynchronousWorkerClient(new EditorSimpleWorkerImpl(null));
|
||||
return this._getOrCreateWorker().getProxyObject();
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -111,13 +111,9 @@ export class LanguagesRegistry {
|
||||
|
||||
let primaryMime: string = null;
|
||||
|
||||
if (typeof lang.mimetypes !== 'undefined' && Array.isArray(lang.mimetypes)) {
|
||||
for (let i = 0; i < lang.mimetypes.length; i++) {
|
||||
if (!primaryMime) {
|
||||
primaryMime = lang.mimetypes[i];
|
||||
}
|
||||
resolvedLanguage.mimetypes.push(lang.mimetypes[i]);
|
||||
}
|
||||
if (Array.isArray(lang.mimetypes) && lang.mimetypes.length > 0) {
|
||||
resolvedLanguage.mimetypes.push(...lang.mimetypes);
|
||||
primaryMime = lang.mimetypes[0];
|
||||
}
|
||||
|
||||
if (!primaryMime) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IMode, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { FrankensteinMode } from 'vs/editor/common/modes/abstractMode';
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
@@ -16,7 +16,7 @@ export const IModelService = createDecorator<IModelService>('modelService');
|
||||
export interface IModelService {
|
||||
_serviceBrand: any;
|
||||
|
||||
createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI): ITextModel;
|
||||
createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI, isForSimpleWidget?: boolean): ITextModel;
|
||||
|
||||
updateModel(model: ITextModel, value: string | ITextBufferFactory): void;
|
||||
|
||||
@@ -26,7 +26,7 @@ export interface IModelService {
|
||||
|
||||
getModels(): ITextModel[];
|
||||
|
||||
getCreationOptions(language: string, resource: URI): ITextModelCreationOptions;
|
||||
getCreationOptions(language: string, resource: URI, isForSimpleWidget: boolean): ITextModelCreationOptions;
|
||||
|
||||
getModel(resource: URI): ITextModel;
|
||||
|
||||
@@ -36,3 +36,9 @@ export interface IModelService {
|
||||
|
||||
onModelModeChanged: Event<{ model: ITextModel; oldModeId: string; }>;
|
||||
}
|
||||
|
||||
export function shouldSynchronizeModel(model: ITextModel): boolean {
|
||||
return (
|
||||
!model.isTooLargeForSyncing() && !model.isForSimpleWidget
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,16 +5,14 @@
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import network = require('vs/base/common/network');
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import * as network from 'vs/base/common/network';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IMarker, IMarkerService } from 'vs/platform/markers/common/markers';
|
||||
import { IMarker, IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
|
||||
import { IMode, LanguageIdentifier } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
@@ -28,6 +26,8 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { themeColorFromId, ThemeColor } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerWarning, overviewRulerError, overviewRulerInfo } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { ITextModel, IModelDeltaDecoration, IModelDecorationOptions, TrackedRangeStickiness, OverviewRulerLane, DefaultEndOfLine, ITextModelCreationOptions, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBufferFactory, ITextBuffer, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
|
||||
function MODEL_ID(resource: URI): string {
|
||||
return resource.toString();
|
||||
@@ -82,15 +82,23 @@ class ModelMarkerHandler {
|
||||
}
|
||||
|
||||
private static _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range {
|
||||
let marker = model.validateRange(new Range(rawMarker.startLineNumber, rawMarker.startColumn, rawMarker.endLineNumber, rawMarker.endColumn));
|
||||
let ret: Range = new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn);
|
||||
|
||||
let ret = Range.lift(rawMarker);
|
||||
|
||||
if (rawMarker.severity === MarkerSeverity.Hint && Range.spansMultipleLines(ret)) {
|
||||
// never render hints on multiple lines
|
||||
ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn);
|
||||
}
|
||||
|
||||
ret = model.validateRange(ret);
|
||||
|
||||
if (ret.isEmpty()) {
|
||||
let word = model.getWordAtPosition(ret.getStartPosition());
|
||||
if (word) {
|
||||
ret = new Range(ret.startLineNumber, word.startColumn, ret.endLineNumber, word.endColumn);
|
||||
} else {
|
||||
let maxColumn = model.getLineLastNonWhitespaceColumn(marker.startLineNumber) ||
|
||||
model.getLineMaxColumn(marker.startLineNumber);
|
||||
let maxColumn = model.getLineLastNonWhitespaceColumn(ret.startLineNumber) ||
|
||||
model.getLineMaxColumn(ret.startLineNumber);
|
||||
|
||||
if (maxColumn === 1) {
|
||||
// empty line
|
||||
@@ -118,31 +126,36 @@ class ModelMarkerHandler {
|
||||
let className: string;
|
||||
let color: ThemeColor;
|
||||
let darkColor: ThemeColor;
|
||||
let zIndex: number;
|
||||
|
||||
switch (marker.severity) {
|
||||
case Severity.Ignore:
|
||||
// do something
|
||||
case MarkerSeverity.Hint:
|
||||
className = ClassName.EditorHintDecoration;
|
||||
zIndex = 0;
|
||||
break;
|
||||
case Severity.Warning:
|
||||
case MarkerSeverity.Warning:
|
||||
className = ClassName.EditorWarningDecoration;
|
||||
color = themeColorFromId(overviewRulerWarning);
|
||||
darkColor = themeColorFromId(overviewRulerWarning);
|
||||
zIndex = 20;
|
||||
break;
|
||||
case Severity.Info:
|
||||
case MarkerSeverity.Info:
|
||||
className = ClassName.EditorInfoDecoration;
|
||||
color = themeColorFromId(overviewRulerInfo);
|
||||
darkColor = themeColorFromId(overviewRulerInfo);
|
||||
zIndex = 10;
|
||||
break;
|
||||
case Severity.Error:
|
||||
case MarkerSeverity.Error:
|
||||
default:
|
||||
className = ClassName.EditorErrorDecoration;
|
||||
color = themeColorFromId(overviewRulerError);
|
||||
darkColor = themeColorFromId(overviewRulerError);
|
||||
zIndex = 30;
|
||||
break;
|
||||
}
|
||||
|
||||
let hoverMessage: MarkdownString = null;
|
||||
let { message, source } = marker;
|
||||
let { message, source, relatedInformation } = marker;
|
||||
|
||||
if (typeof message === 'string') {
|
||||
message = message.trim();
|
||||
@@ -156,6 +169,16 @@ class ModelMarkerHandler {
|
||||
}
|
||||
|
||||
hoverMessage = new MarkdownString().appendCodeblock('_', message);
|
||||
|
||||
if (!isFalsyOrEmpty(relatedInformation)) {
|
||||
hoverMessage.appendMarkdown('\n');
|
||||
for (const { message, resource, startLineNumber, startColumn } of relatedInformation) {
|
||||
hoverMessage.appendMarkdown(
|
||||
`* [${basename(resource.path)}(${startLineNumber}, ${startColumn})](${resource.toString(false)}#${startLineNumber},${startColumn}): \`${message}\` \n`
|
||||
);
|
||||
}
|
||||
hoverMessage.appendMarkdown('\n');
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -167,7 +190,8 @@ class ModelMarkerHandler {
|
||||
color,
|
||||
darkColor,
|
||||
position: OverviewRulerLane.Right
|
||||
}
|
||||
},
|
||||
zIndex
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -181,6 +205,8 @@ interface IRawConfig {
|
||||
insertSpaces?: any;
|
||||
detectIndentation?: any;
|
||||
trimAutoWhitespace?: any;
|
||||
creationOptions?: any;
|
||||
largeFileOptimizations?: any;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -194,9 +220,9 @@ export class ModelServiceImpl implements IModelService {
|
||||
private _configurationService: IConfigurationService;
|
||||
private _configurationServiceSubscription: IDisposable;
|
||||
|
||||
private _onModelAdded: Emitter<ITextModel>;
|
||||
private _onModelRemoved: Emitter<ITextModel>;
|
||||
private _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }>;
|
||||
private readonly _onModelAdded: Emitter<ITextModel>;
|
||||
private readonly _onModelRemoved: Emitter<ITextModel>;
|
||||
private readonly _onModelModeChanged: Emitter<{ model: ITextModel; oldModeId: string; }>;
|
||||
|
||||
private _modelCreationOptionsByLanguageAndResource: {
|
||||
[languageAndResource: string]: ITextModelCreationOptions;
|
||||
@@ -227,7 +253,7 @@ export class ModelServiceImpl implements IModelService {
|
||||
this._updateModelOptions();
|
||||
}
|
||||
|
||||
private static _readModelOptions(config: IRawConfig): ITextModelCreationOptions {
|
||||
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
let tabSize = EDITOR_MODEL_DEFAULTS.tabSize;
|
||||
if (config.editor && typeof config.editor.tabSize !== 'undefined') {
|
||||
let parsedTabSize = parseInt(config.editor.tabSize, 10);
|
||||
@@ -259,19 +285,26 @@ export class ModelServiceImpl implements IModelService {
|
||||
detectIndentation = (config.editor.detectIndentation === 'false' ? false : Boolean(config.editor.detectIndentation));
|
||||
}
|
||||
|
||||
let largeFileOptimizations = EDITOR_MODEL_DEFAULTS.largeFileOptimizations;
|
||||
if (config.editor && typeof config.editor.largeFileOptimizations !== 'undefined') {
|
||||
largeFileOptimizations = (config.editor.largeFileOptimizations === 'false' ? false : Boolean(config.editor.largeFileOptimizations));
|
||||
}
|
||||
|
||||
return {
|
||||
isForSimpleWidget: isForSimpleWidget,
|
||||
tabSize: tabSize,
|
||||
insertSpaces: insertSpaces,
|
||||
detectIndentation: detectIndentation,
|
||||
defaultEOL: newDefaultEOL,
|
||||
trimAutoWhitespace: trimAutoWhitespace
|
||||
trimAutoWhitespace: trimAutoWhitespace,
|
||||
largeFileOptimizations: largeFileOptimizations
|
||||
};
|
||||
}
|
||||
|
||||
public getCreationOptions(language: string, resource: URI): ITextModelCreationOptions {
|
||||
public getCreationOptions(language: string, resource: URI, isForSimpleWidget: boolean): ITextModelCreationOptions {
|
||||
let creationOptions = this._modelCreationOptionsByLanguageAndResource[language + resource];
|
||||
if (!creationOptions) {
|
||||
creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getValue({ overrideIdentifier: language, resource }));
|
||||
creationOptions = ModelServiceImpl._readModelOptions(this._configurationService.getValue({ overrideIdentifier: language, resource }), isForSimpleWidget);
|
||||
this._modelCreationOptionsByLanguageAndResource[language + resource] = creationOptions;
|
||||
}
|
||||
return creationOptions;
|
||||
@@ -289,7 +322,7 @@ export class ModelServiceImpl implements IModelService {
|
||||
const language = modelData.model.getLanguageIdentifier().language;
|
||||
const uri = modelData.model.uri;
|
||||
const oldOptions = oldOptionsByLanguageAndResource[language + uri];
|
||||
const newOptions = this.getCreationOptions(language, uri);
|
||||
const newOptions = this.getCreationOptions(language, uri, modelData.model.isForSimpleWidget);
|
||||
ModelServiceImpl._setModelOptionsForModel(modelData.model, newOptions, oldOptions);
|
||||
}
|
||||
}
|
||||
@@ -353,9 +386,9 @@ export class ModelServiceImpl implements IModelService {
|
||||
|
||||
// --- begin IModelService
|
||||
|
||||
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI): ModelData {
|
||||
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI, isForSimpleWidget: boolean): ModelData {
|
||||
// create & save the model
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource);
|
||||
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
|
||||
const model: TextModel = new TextModel(value, options, languageIdentifier, resource);
|
||||
const modelId = MODEL_ID(model.uri);
|
||||
|
||||
@@ -375,7 +408,7 @@ export class ModelServiceImpl implements IModelService {
|
||||
}
|
||||
|
||||
public updateModel(model: ITextModel, value: string | ITextBufferFactory): void {
|
||||
const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri);
|
||||
const options = this.getCreationOptions(model.getLanguageIdentifier().language, model.uri, model.isForSimpleWidget);
|
||||
const textBuffer = createTextBuffer(value, options.defaultEOL);
|
||||
|
||||
// Return early if the text is already set in that form
|
||||
@@ -384,12 +417,14 @@ export class ModelServiceImpl implements IModelService {
|
||||
}
|
||||
|
||||
// Otherwise find a diff between the values and update model
|
||||
model.pushStackElement();
|
||||
model.setEOL(textBuffer.getEOL() === '\r\n' ? EndOfLineSequence.CRLF : EndOfLineSequence.LF);
|
||||
model.pushEditOperations(
|
||||
[new Selection(1, 1, 1, 1)],
|
||||
[],
|
||||
ModelServiceImpl._computeEdits(model, textBuffer),
|
||||
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => [new Selection(1, 1, 1, 1)]
|
||||
(inverseEditOperations: IIdentifiedSingleEditOperation[]) => []
|
||||
);
|
||||
model.pushStackElement();
|
||||
}
|
||||
|
||||
private static _commonPrefix(a: ILineSequence, aLen: number, aDelta: number, b: ILineSequence, bLen: number, bDelta: number): number {
|
||||
@@ -439,17 +474,17 @@ export class ModelServiceImpl implements IModelService {
|
||||
newRange = new Range(1, 1, textBufferLineCount, 1 + textBuffer.getLineLength(textBufferLineCount));
|
||||
}
|
||||
|
||||
return [EditOperation.replace(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
|
||||
return [EditOperation.replaceMove(oldRange, textBuffer.getValueInRange(newRange, EndOfLinePreference.TextDefined))];
|
||||
}
|
||||
|
||||
public createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI): ITextModel {
|
||||
public createModel(value: string | ITextBufferFactory, modeOrPromise: TPromise<IMode> | IMode, resource: URI, isForSimpleWidget: boolean = false): ITextModel {
|
||||
let modelData: ModelData;
|
||||
|
||||
if (!modeOrPromise || TPromise.is(modeOrPromise)) {
|
||||
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource);
|
||||
modelData = this._createModelData(value, PLAINTEXT_LANGUAGE_IDENTIFIER, resource, isForSimpleWidget);
|
||||
this.setMode(modelData.model, modeOrPromise);
|
||||
} else {
|
||||
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource);
|
||||
modelData = this._createModelData(value, modeOrPromise.getLanguageIdentifier(), resource, isForSimpleWidget);
|
||||
}
|
||||
|
||||
// handle markers (marker service => model)
|
||||
@@ -535,8 +570,8 @@ export class ModelServiceImpl implements IModelService {
|
||||
private _onDidChangeLanguage(model: ITextModel, e: IModelLanguageChangedEvent): void {
|
||||
const oldModeId = e.oldLanguage;
|
||||
const newModeId = model.getLanguageIdentifier().language;
|
||||
const oldOptions = this.getCreationOptions(oldModeId, model.uri);
|
||||
const newOptions = this.getCreationOptions(newModeId, model.uri);
|
||||
const oldOptions = this.getCreationOptions(oldModeId, model.uri, model.isForSimpleWidget);
|
||||
const newOptions = this.getCreationOptions(newModeId, model.uri, model.isForSimpleWidget);
|
||||
ModelServiceImpl._setModelOptionsForModel(model, newOptions, oldOptions);
|
||||
this._onModelModeChanged.fire({ model, oldModeId });
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IPosition } from 'vs/editor/common/core/position';
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
@@ -11,6 +11,7 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res
|
||||
import { IPosition, Position } from 'vs/editor/common/core/position';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { basename } from 'vs/base/common/paths';
|
||||
|
||||
export class TextResourceConfigurationService extends Disposable implements ITextResourceConfigurationService {
|
||||
|
||||
@@ -42,6 +43,6 @@ export class TextResourceConfigurationService extends Disposable implements ITex
|
||||
if (model) {
|
||||
return position ? this.modeService.getLanguageIdentifier(model.getLanguageIdAtPosition(position.lineNumber, position.column)).language : model.getLanguageIdentifier().language;
|
||||
}
|
||||
return this.modeService.getModeIdByFilenameOrFirstLine(resource.fsPath);
|
||||
return this.modeService.getModeIdByFilenameOrFirstLine(basename(resource.path));
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,13 @@ export enum Severity {
|
||||
Error = 3,
|
||||
}
|
||||
|
||||
export enum MarkerSeverity {
|
||||
Hint = 1,
|
||||
Info = 2,
|
||||
Warning = 4,
|
||||
Error = 8,
|
||||
}
|
||||
|
||||
// --------------------------------------------
|
||||
// This is repeated here so it can be exported
|
||||
// because TS inlines const enums
|
||||
@@ -238,6 +245,7 @@ export function createMonacoBaseAPI(): typeof monaco {
|
||||
Selection: Selection,
|
||||
SelectionDirection: SelectionDirection,
|
||||
Severity: Severity,
|
||||
MarkerSeverity: MarkerSeverity,
|
||||
Promise: TPromise,
|
||||
Uri: <any>URI,
|
||||
Token: Token
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import nls = require('vs/nls');
|
||||
import * as nls from 'vs/nls';
|
||||
import { registerColor, editorBackground, activeContrastBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { Color, RGBA } from 'vs/base/common/color';
|
||||
@@ -20,8 +20,12 @@ export const editorCursorForeground = registerColor('editorCursor.foreground', {
|
||||
export const editorCursorBackground = registerColor('editorCursor.background', null, nls.localize('editorCursorBackground', 'The background color of the editor cursor. Allows customizing the color of a character overlapped by a block cursor.'));
|
||||
export const editorWhitespaces = registerColor('editorWhitespace.foreground', { dark: '#e3e4e229', light: '#33333333', hc: '#e3e4e229' }, nls.localize('editorWhitespaces', 'Color of whitespace characters in the editor.'));
|
||||
export const editorIndentGuides = registerColor('editorIndentGuide.background', { dark: editorWhitespaces, light: editorWhitespaces, hc: editorWhitespaces }, nls.localize('editorIndentGuides', 'Color of the editor indentation guides.'));
|
||||
export const editorActiveIndentGuides = registerColor('editorIndentGuide.activeBackground', { dark: editorWhitespaces, light: editorWhitespaces, hc: editorWhitespaces }, nls.localize('editorActiveIndentGuide', 'Color of the active editor indentation guides.'));
|
||||
export const editorLineNumbers = registerColor('editorLineNumber.foreground', { dark: '#5A5A5A', light: '#2B91AF', hc: Color.white }, nls.localize('editorLineNumbers', 'Color of editor line numbers.'));
|
||||
export const editorActiveLineNumber = registerColor('editorActiveLineNumber.foreground', { dark: null, light: null, hc: null }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'));
|
||||
|
||||
const deprecatedEditorActiveLineNumber = registerColor('editorActiveLineNumber.foreground', { dark: null, light: null, hc: null }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'), false, nls.localize('deprecatedEditorActiveLineNumber', 'Id is deprecated. Use \'editorLineNumber.activeForeground\' instead.'));
|
||||
export const editorActiveLineNumber = registerColor('editorLineNumber.activeForeground', { dark: deprecatedEditorActiveLineNumber, light: deprecatedEditorActiveLineNumber, hc: deprecatedEditorActiveLineNumber }, nls.localize('editorActiveLineNumber', 'Color of editor active line number'));
|
||||
|
||||
export const editorRuler = registerColor('editorRuler.foreground', { dark: '#5A5A5A', light: Color.lightgrey, hc: Color.white }, nls.localize('editorRuler', 'Color of the editor rulers.'));
|
||||
|
||||
export const editorCodeLensForeground = registerColor('editorCodeLens.foreground', { dark: '#999999', light: '#999999', hc: '#999999' }, nls.localize('editorCodeLensForeground', 'Foreground color of editor code lenses'));
|
||||
@@ -42,36 +46,44 @@ export const editorWarningBorder = registerColor('editorWarning.border', { dark:
|
||||
export const editorInfoForeground = registerColor('editorInfo.foreground', { dark: '#008000', light: '#008000', hc: null }, nls.localize('infoForeground', 'Foreground color of info squigglies in the editor.'));
|
||||
export const editorInfoBorder = registerColor('editorInfo.border', { dark: null, light: null, hc: Color.fromHex('#71B771').transparent(0.8) }, nls.localize('infoBorder', 'Border color of info squigglies in the editor.'));
|
||||
|
||||
export const editorHintForeground = registerColor('editorHint.foreground', { dark: Color.fromHex('#eeeeee').transparent(0.7), light: '#6c6c6c', hc: null }, nls.localize('hintForeground', 'Foreground color of hint squigglies in the editor.'));
|
||||
export const editorHintBorder = registerColor('editorHint.border', { dark: null, light: null, hc: Color.fromHex('#eeeeee').transparent(0.8) }, nls.localize('hintBorder', 'Border color of hint squigglies in the editor.'));
|
||||
|
||||
const rulerRangeDefault = new Color(new RGBA(0, 122, 204, 0.6));
|
||||
export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights.'));
|
||||
export const overviewRulerRangeHighlight = registerColor('editorOverviewRuler.rangeHighlightForeground', { dark: rulerRangeDefault, light: rulerRangeDefault, hc: rulerRangeDefault }, nls.localize('overviewRulerRangeHighlight', 'Overview ruler marker color for range highlights. The color must not be opaque to not hide underlying decorations.'), true);
|
||||
export const overviewRulerError = registerColor('editorOverviewRuler.errorForeground', { dark: new Color(new RGBA(255, 18, 18, 0.7)), light: new Color(new RGBA(255, 18, 18, 0.7)), hc: new Color(new RGBA(255, 50, 50, 1)) }, nls.localize('overviewRuleError', 'Overview ruler marker color for errors.'));
|
||||
export const overviewRulerWarning = registerColor('editorOverviewRuler.warningForeground', { dark: new Color(new RGBA(18, 136, 18, 0.7)), light: new Color(new RGBA(18, 136, 18, 0.7)), hc: new Color(new RGBA(50, 255, 50, 1)) }, nls.localize('overviewRuleWarning', 'Overview ruler marker color for warnings.'));
|
||||
export const overviewRulerInfo = registerColor('editorOverviewRuler.infoForeground', { dark: new Color(new RGBA(18, 18, 136, 0.7)), light: new Color(new RGBA(18, 18, 136, 0.7)), hc: new Color(new RGBA(50, 50, 255, 1)) }, nls.localize('overviewRuleInfo', 'Overview ruler marker color for infos.'));
|
||||
|
||||
// contains all color rules that used to defined in editor/browser/widget/editor.css
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
let background = theme.getColor(editorBackground);
|
||||
const background = theme.getColor(editorBackground);
|
||||
if (background) {
|
||||
collector.addRule(`.monaco-editor, .monaco-editor-background, .monaco-editor .inputarea.ime-input { background-color: ${background}; }`);
|
||||
}
|
||||
let foreground = theme.getColor(editorForeground);
|
||||
|
||||
const foreground = theme.getColor(editorForeground);
|
||||
if (foreground) {
|
||||
collector.addRule(`.monaco-editor, .monaco-editor .inputarea.ime-input { color: ${foreground}; }`);
|
||||
}
|
||||
let gutter = theme.getColor(editorGutter);
|
||||
|
||||
const gutter = theme.getColor(editorGutter);
|
||||
if (gutter) {
|
||||
collector.addRule(`.monaco-editor .margin { background-color: ${gutter}; }`);
|
||||
}
|
||||
let rangeHighlight = theme.getColor(editorRangeHighlight);
|
||||
|
||||
const rangeHighlight = theme.getColor(editorRangeHighlight);
|
||||
if (rangeHighlight) {
|
||||
collector.addRule(`.monaco-editor .rangeHighlight { background-color: ${rangeHighlight}; }`);
|
||||
}
|
||||
let rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder);
|
||||
|
||||
const rangeHighlightBorder = theme.getColor(editorRangeHighlightBorder);
|
||||
if (rangeHighlightBorder) {
|
||||
collector.addRule(`.monaco-editor .rangeHighlight { border: 1px dotted ${rangeHighlightBorder}; }`);
|
||||
collector.addRule(`.monaco-editor .rangeHighlight { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${rangeHighlightBorder}; }`);
|
||||
}
|
||||
let invisibles = theme.getColor(editorWhitespaces);
|
||||
|
||||
const invisibles = theme.getColor(editorWhitespaces);
|
||||
if (invisibles) {
|
||||
collector.addRule(`.vs-whitespace { color: ${invisibles} !important; }`);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
'use strict';
|
||||
|
||||
import { ColorId, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import Event, { Emitter } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { RGBA8 } from 'vs/editor/common/core/rgba';
|
||||
|
||||
export class MinimapTokensColorTracker {
|
||||
@@ -21,7 +21,7 @@ export class MinimapTokensColorTracker {
|
||||
private _backgroundIsLight: boolean;
|
||||
|
||||
private _onDidChange = new Emitter<void>();
|
||||
public onDidChange: Event<void> = this._onDidChange.event;
|
||||
public readonly onDidChange: Event<void> = this._onDidChange.event;
|
||||
|
||||
private constructor() {
|
||||
this._updateColorMap();
|
||||
|
||||
@@ -58,7 +58,7 @@ export class LineDecoration {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (range.isEmpty() && d.type === InlineDecorationType.Regular) {
|
||||
if (range.isEmpty() && (d.type === InlineDecorationType.Regular || d.type === InlineDecorationType.RegularAffectingLetterSpacing)) {
|
||||
// Ignore empty range decorations
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import { LinesLayout } from 'vs/editor/common/viewLayout/linesLayout';
|
||||
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
import Event from 'vs/base/common/event';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
|
||||
|
||||
const SMOOTH_SCROLLING_TIME = 125;
|
||||
@@ -75,15 +75,12 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
}
|
||||
public onFlushed(lineCount: number): void {
|
||||
this._linesLayout.onFlushed(lineCount);
|
||||
this._updateHeight();
|
||||
}
|
||||
public onLinesDeleted(fromLineNumber: number, toLineNumber: number): void {
|
||||
this._linesLayout.onLinesDeleted(fromLineNumber, toLineNumber);
|
||||
this._updateHeight();
|
||||
}
|
||||
public onLinesInserted(fromLineNumber: number, toLineNumber: number): void {
|
||||
this._linesLayout.onLinesInserted(fromLineNumber, toLineNumber);
|
||||
this._updateHeight();
|
||||
}
|
||||
|
||||
// ---- end view event handlers
|
||||
@@ -163,7 +160,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
|
||||
// ---- view state
|
||||
|
||||
public saveState(): editorCommon.IViewState {
|
||||
public saveState(): { scrollTop: number; scrollTopWithoutViewZones: number; scrollLeft: number; } {
|
||||
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
|
||||
let scrollTop = currentScrollPosition.scrollTop;
|
||||
let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
|
||||
@@ -175,17 +172,6 @@ export class ViewLayout extends Disposable implements IViewLayout {
|
||||
};
|
||||
}
|
||||
|
||||
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
|
||||
let restoreScrollTop = state.scrollTop;
|
||||
if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) {
|
||||
restoreScrollTop = state.scrollTopWithoutViewZones;
|
||||
}
|
||||
return {
|
||||
scrollLeft: state.scrollLeft,
|
||||
scrollTop: restoreScrollTop
|
||||
};
|
||||
}
|
||||
|
||||
// ---- IVerticalLayoutProvider
|
||||
|
||||
public addWhitespace(afterLineNumber: number, ordinal: number, height: number): number {
|
||||
|
||||
@@ -36,7 +36,8 @@ export class RenderLineInput {
|
||||
|
||||
public readonly useMonospaceOptimizations: boolean;
|
||||
public readonly lineContent: string;
|
||||
public readonly mightContainRTL: boolean;
|
||||
public readonly isBasicASCII: boolean;
|
||||
public readonly containsRTL: boolean;
|
||||
public readonly fauxIndentLength: number;
|
||||
public readonly lineTokens: IViewLineTokens;
|
||||
public readonly lineDecorations: LineDecoration[];
|
||||
@@ -50,7 +51,8 @@ export class RenderLineInput {
|
||||
constructor(
|
||||
useMonospaceOptimizations: boolean,
|
||||
lineContent: string,
|
||||
mightContainRTL: boolean,
|
||||
isBasicASCII: boolean,
|
||||
containsRTL: boolean,
|
||||
fauxIndentLength: number,
|
||||
lineTokens: IViewLineTokens,
|
||||
lineDecorations: LineDecoration[],
|
||||
@@ -63,7 +65,8 @@ export class RenderLineInput {
|
||||
) {
|
||||
this.useMonospaceOptimizations = useMonospaceOptimizations;
|
||||
this.lineContent = lineContent;
|
||||
this.mightContainRTL = mightContainRTL;
|
||||
this.isBasicASCII = isBasicASCII;
|
||||
this.containsRTL = containsRTL;
|
||||
this.fauxIndentLength = fauxIndentLength;
|
||||
this.lineTokens = lineTokens;
|
||||
this.lineDecorations = lineDecorations;
|
||||
@@ -85,7 +88,8 @@ export class RenderLineInput {
|
||||
return (
|
||||
this.useMonospaceOptimizations === other.useMonospaceOptimizations
|
||||
&& this.lineContent === other.lineContent
|
||||
&& this.mightContainRTL === other.mightContainRTL
|
||||
&& this.isBasicASCII === other.isBasicASCII
|
||||
&& this.containsRTL === other.containsRTL
|
||||
&& this.fauxIndentLength === other.fauxIndentLength
|
||||
&& this.tabSize === other.tabSize
|
||||
&& this.spaceWidth === other.spaceWidth
|
||||
@@ -217,14 +221,20 @@ export class CharacterMapping {
|
||||
}
|
||||
}
|
||||
|
||||
export const enum ForeignElementType {
|
||||
None = 0,
|
||||
Before = 1,
|
||||
After = 2
|
||||
}
|
||||
|
||||
export class RenderLineOutput {
|
||||
_renderLineOutputBrand: void;
|
||||
|
||||
readonly characterMapping: CharacterMapping;
|
||||
readonly containsRTL: boolean;
|
||||
readonly containsForeignElements: boolean;
|
||||
readonly containsForeignElements: ForeignElementType;
|
||||
|
||||
constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) {
|
||||
constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) {
|
||||
this.characterMapping = characterMapping;
|
||||
this.containsRTL = containsRTL;
|
||||
this.containsForeignElements = containsForeignElements;
|
||||
@@ -234,7 +244,7 @@ export class RenderLineOutput {
|
||||
export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput {
|
||||
if (input.lineContent.length === 0) {
|
||||
|
||||
let containsForeignElements = false;
|
||||
let containsForeignElements = ForeignElementType.None;
|
||||
|
||||
// This is basically for IE's hit test to work
|
||||
let content: string = '<span><span>\u00a0</span></span>';
|
||||
@@ -244,13 +254,17 @@ export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): Rend
|
||||
let classNames: string[] = [];
|
||||
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
|
||||
const lineDecoration = input.lineDecorations[i];
|
||||
if (lineDecoration.type !== InlineDecorationType.Regular) {
|
||||
if (lineDecoration.type === InlineDecorationType.Before) {
|
||||
classNames.push(input.lineDecorations[i].className);
|
||||
containsForeignElements = true;
|
||||
containsForeignElements |= ForeignElementType.Before;
|
||||
}
|
||||
if (lineDecoration.type === InlineDecorationType.After) {
|
||||
classNames.push(input.lineDecorations[i].className);
|
||||
containsForeignElements |= ForeignElementType.After;
|
||||
}
|
||||
}
|
||||
|
||||
if (containsForeignElements) {
|
||||
if (containsForeignElements !== ForeignElementType.None) {
|
||||
content = `<span><span class="${classNames.join(' ')}"></span></span>`;
|
||||
}
|
||||
}
|
||||
@@ -271,7 +285,7 @@ export class RenderLineOutput2 {
|
||||
public readonly characterMapping: CharacterMapping,
|
||||
public readonly html: string,
|
||||
public readonly containsRTL: boolean,
|
||||
public readonly containsForeignElements: boolean
|
||||
public readonly containsForeignElements: ForeignElementType
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -289,7 +303,7 @@ class ResolvedRenderLineInput {
|
||||
public readonly len: number,
|
||||
public readonly isOverflowing: boolean,
|
||||
public readonly parts: LinePart[],
|
||||
public readonly containsForeignElements: boolean,
|
||||
public readonly containsForeignElements: ForeignElementType,
|
||||
public readonly tabSize: number,
|
||||
public readonly containsRTL: boolean,
|
||||
public readonly spaceWidth: number,
|
||||
@@ -319,22 +333,22 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
|
||||
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary) {
|
||||
tokens = _applyRenderWhitespace(lineContent, len, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.renderWhitespace === RenderWhitespace.Boundary);
|
||||
}
|
||||
let containsForeignElements = false;
|
||||
let containsForeignElements = ForeignElementType.None;
|
||||
if (input.lineDecorations.length > 0) {
|
||||
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
|
||||
const lineDecoration = input.lineDecorations[i];
|
||||
if (lineDecoration.type !== InlineDecorationType.Regular) {
|
||||
containsForeignElements = true;
|
||||
break;
|
||||
if (lineDecoration.type === InlineDecorationType.RegularAffectingLetterSpacing) {
|
||||
// Pretend there are foreign elements... although not 100% accurate.
|
||||
containsForeignElements |= ForeignElementType.Before;
|
||||
} else if (lineDecoration.type === InlineDecorationType.Before) {
|
||||
containsForeignElements |= ForeignElementType.Before;
|
||||
} else if (lineDecoration.type === InlineDecorationType.After) {
|
||||
containsForeignElements |= ForeignElementType.After;
|
||||
}
|
||||
}
|
||||
tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
|
||||
}
|
||||
let containsRTL = false;
|
||||
if (input.mightContainRTL) {
|
||||
containsRTL = strings.containsRTL(lineContent);
|
||||
}
|
||||
if (!containsRTL && !input.fontLigatures) {
|
||||
if (input.isBasicASCII && !input.fontLigatures) {
|
||||
tokens = splitLargeTokens(lineContent, tokens);
|
||||
}
|
||||
|
||||
@@ -346,7 +360,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
|
||||
tokens,
|
||||
containsForeignElements,
|
||||
input.tabSize,
|
||||
containsRTL,
|
||||
input.containsRTL,
|
||||
input.spaceWidth,
|
||||
input.renderWhitespace,
|
||||
input.renderControlCharacters
|
||||
@@ -406,11 +420,6 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[]): LinePart[] {
|
||||
const piecesCount = Math.ceil(diff / Constants.LongToken);
|
||||
for (let j = 1; j < piecesCount; j++) {
|
||||
let pieceEndIndex = lastTokenEndIndex + (j * Constants.LongToken);
|
||||
let lastCharInPiece = lineContent.charCodeAt(pieceEndIndex - 1);
|
||||
if (strings.isHighSurrogate(lastCharInPiece)) {
|
||||
// Don't cut in the middle of a surrogate pair
|
||||
pieceEndIndex--;
|
||||
}
|
||||
result[resultLen++] = new LinePart(pieceEndIndex, tokenType);
|
||||
}
|
||||
result[resultLen++] = new LinePart(tokenEndIndex, tokenType);
|
||||
|
||||
@@ -14,7 +14,7 @@ import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
|
||||
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ThemeColor, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { IModelDecoration, ITextModel, IModelDeltaDecoration, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { IModelDecoration, ITextModel, IModelDeltaDecoration, EndOfLinePreference, IActiveIndentGuideInfo } from 'vs/editor/common/model';
|
||||
|
||||
export class OutputPosition {
|
||||
_outputPositionBrand: void;
|
||||
@@ -41,6 +41,7 @@ export interface ILineMapperFactory {
|
||||
export interface ISimpleModel {
|
||||
getLineTokens(lineNumber: number): LineTokens;
|
||||
getLineContent(lineNumber: number): string;
|
||||
getLineLength(lineNumber: number): number;
|
||||
getLineMinColumn(lineNumber: number): number;
|
||||
getLineMaxColumn(lineNumber: number): number;
|
||||
getValueInRange(range: IRange, eol?: EndOfLinePreference): string;
|
||||
@@ -52,6 +53,7 @@ export interface ISplitLine {
|
||||
|
||||
getViewLineCount(): number;
|
||||
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
|
||||
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
|
||||
getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
|
||||
getViewLineMaxColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
|
||||
getViewLineData(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): ViewLineData;
|
||||
@@ -80,8 +82,10 @@ export interface IViewModelLinesCollection {
|
||||
|
||||
getViewLineCount(): number;
|
||||
warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void;
|
||||
getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
|
||||
getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[];
|
||||
getViewLineContent(viewLineNumber: number): string;
|
||||
getViewLineLength(viewLineNumber: number): number;
|
||||
getViewLineMinColumn(viewLineNumber: number): number;
|
||||
getViewLineMaxColumn(viewLineNumber: number): number;
|
||||
getViewLineData(viewLineNumber: number): ViewLineData;
|
||||
@@ -501,6 +505,26 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1);
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
minLineNumber = this._toValidViewLineNumber(minLineNumber);
|
||||
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
|
||||
|
||||
const modelPosition = this.convertViewPositionToModelPosition(viewLineNumber, this.getViewLineMinColumn(viewLineNumber));
|
||||
const modelMinPosition = this.convertViewPositionToModelPosition(minLineNumber, this.getViewLineMinColumn(minLineNumber));
|
||||
const modelMaxPosition = this.convertViewPositionToModelPosition(maxLineNumber, this.getViewLineMinColumn(maxLineNumber));
|
||||
const result = this.model.getActiveIndentGuide(modelPosition.lineNumber, modelMinPosition.lineNumber, modelMaxPosition.lineNumber);
|
||||
|
||||
const viewStartPosition = this.convertModelPositionToViewPosition(result.startLineNumber, 1);
|
||||
const viewEndPosition = this.convertModelPositionToViewPosition(result.endLineNumber, 1);
|
||||
return {
|
||||
startLineNumber: viewStartPosition.lineNumber,
|
||||
endLineNumber: viewEndPosition.lineNumber,
|
||||
indent: result.indent
|
||||
};
|
||||
}
|
||||
|
||||
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
|
||||
this._ensureValidState();
|
||||
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
|
||||
@@ -570,6 +594,16 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
|
||||
return this.lines[lineIndex].getViewLineContent(this.model, lineIndex + 1, remainder);
|
||||
}
|
||||
|
||||
public getViewLineLength(viewLineNumber: number): number {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
|
||||
let lineIndex = r.index;
|
||||
let remainder = r.remainder;
|
||||
|
||||
return this.lines[lineIndex].getViewLineLength(this.model, lineIndex + 1, remainder);
|
||||
}
|
||||
|
||||
public getViewLineMinColumn(viewLineNumber: number): number {
|
||||
this._ensureValidState();
|
||||
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
|
||||
@@ -815,6 +849,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
|
||||
return model.getLineContent(modelLineNumber);
|
||||
}
|
||||
|
||||
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
return model.getLineLength(modelLineNumber);
|
||||
}
|
||||
|
||||
public getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
return model.getLineMinColumn(modelLineNumber);
|
||||
}
|
||||
@@ -880,6 +918,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
|
||||
public getViewLineMinColumn(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
@@ -973,6 +1015,21 @@ export class SplitLine implements ISplitLine {
|
||||
return r;
|
||||
}
|
||||
|
||||
public getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
if (!this._isVisible) {
|
||||
throw new Error('Not supported');
|
||||
}
|
||||
let startOffset = this.getInputStartOffsetOfOutputLineIndex(outputLineIndex);
|
||||
let endOffset = this.getInputEndOffsetOfOutputLineIndex(model, modelLineNumber, outputLineIndex);
|
||||
let r = endOffset - startOffset;
|
||||
|
||||
if (outputLineIndex > 0) {
|
||||
r = this.wrappedIndent.length + r;
|
||||
}
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
public getViewLineMinColumn(model: ITextModel, modelLineNumber: number, outputLineIndex: number): number {
|
||||
if (!this._isVisible) {
|
||||
throw new Error('Not supported');
|
||||
@@ -1205,6 +1262,14 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
||||
public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void {
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
|
||||
return {
|
||||
startLineNumber: viewLineNumber,
|
||||
endLineNumber: viewLineNumber,
|
||||
indent: 0
|
||||
};
|
||||
}
|
||||
|
||||
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
|
||||
const viewLineCount = viewEndLineNumber - viewStartLineNumber + 1;
|
||||
let result = new Array<number>(viewLineCount);
|
||||
@@ -1218,6 +1283,10 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
|
||||
return this.model.getLineContent(viewLineNumber);
|
||||
}
|
||||
|
||||
public getViewLineLength(viewLineNumber: number): number {
|
||||
return this.model.getLineLength(viewLineNumber);
|
||||
}
|
||||
|
||||
public getViewLineMinColumn(viewLineNumber: number): number {
|
||||
return this.model.getLineMinColumn(viewLineNumber);
|
||||
}
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { INewScrollPosition, IViewState } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLinePreference, IModelDecorationOptions } from 'vs/editor/common/model';
|
||||
import { INewScrollPosition } from 'vs/editor/common/editorCommon';
|
||||
import { EndOfLinePreference, IModelDecorationOptions, IActiveIndentGuideInfo } from 'vs/editor/common/model';
|
||||
import { IViewLineTokens } from 'vs/editor/common/core/lineTokens';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -15,6 +15,7 @@ import { Scrollable, IScrollPosition } from 'vs/base/common/scrollable';
|
||||
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
|
||||
import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
|
||||
export interface IViewWhitespaceViewportData {
|
||||
readonly id: number;
|
||||
@@ -63,9 +64,6 @@ export interface IViewLayout {
|
||||
getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData;
|
||||
getWhitespaces(): IEditorWhitespace[];
|
||||
|
||||
saveState(): IViewState;
|
||||
reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; };
|
||||
|
||||
isAfterLines(verticalOffset: number): boolean;
|
||||
getLineNumberAtVerticalOffset(verticalOffset: number): number;
|
||||
getVerticalOffsetForLineNumber(lineNumber: number): number;
|
||||
@@ -122,9 +120,11 @@ export interface IViewModel {
|
||||
* Gives a hint that a lot of requests are about to come in for these line numbers.
|
||||
*/
|
||||
setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void;
|
||||
setHasFocus(hasFocus: boolean): void;
|
||||
|
||||
getDecorationsInViewport(visibleRange: Range): ViewModelDecoration[];
|
||||
getViewLineRenderingData(visibleRange: Range, lineNumber: number): ViewLineRenderingData;
|
||||
getViewLineData(lineNumber: number): ViewLineData;
|
||||
getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData;
|
||||
getCompletelyVisibleViewRange(): Range;
|
||||
getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range;
|
||||
@@ -132,6 +132,8 @@ export interface IViewModel {
|
||||
getTabSize(): number;
|
||||
getLineCount(): number;
|
||||
getLineContent(lineNumber: number): string;
|
||||
getLineLength(lineNumber: number): number;
|
||||
getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo;
|
||||
getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[];
|
||||
getLineMinColumn(lineNumber: number): number;
|
||||
getLineMaxColumn(lineNumber: number): number;
|
||||
@@ -146,7 +148,7 @@ export interface IViewModel {
|
||||
|
||||
deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position;
|
||||
getEOL(): string;
|
||||
getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[];
|
||||
getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[];
|
||||
getHTMLToCopy(ranges: Range[], emptySelectionClipboard: boolean): string;
|
||||
}
|
||||
|
||||
@@ -210,13 +212,13 @@ export class ViewLineRenderingData {
|
||||
*/
|
||||
public readonly content: string;
|
||||
/**
|
||||
* If set to false, it is guaranteed that `content` contains only LTR chars.
|
||||
* Describes if `content` contains RTL characters.
|
||||
*/
|
||||
public readonly mightContainRTL: boolean;
|
||||
public readonly containsRTL: boolean;
|
||||
/**
|
||||
* If set to false, it is guaranteed that `content` contains only basic ASCII chars.
|
||||
* Describes if `content` contains non basic ASCII chars.
|
||||
*/
|
||||
public readonly mightContainNonBasicASCII: boolean;
|
||||
public readonly isBasicASCII: boolean;
|
||||
/**
|
||||
* The tokens at this view line.
|
||||
*/
|
||||
@@ -243,18 +245,35 @@ export class ViewLineRenderingData {
|
||||
this.minColumn = minColumn;
|
||||
this.maxColumn = maxColumn;
|
||||
this.content = content;
|
||||
this.mightContainRTL = mightContainRTL;
|
||||
this.mightContainNonBasicASCII = mightContainNonBasicASCII;
|
||||
|
||||
this.isBasicASCII = ViewLineRenderingData.isBasicASCII(content, mightContainNonBasicASCII);
|
||||
this.containsRTL = ViewLineRenderingData.containsRTL(content, this.isBasicASCII, mightContainRTL);
|
||||
|
||||
this.tokens = tokens;
|
||||
this.inlineDecorations = inlineDecorations;
|
||||
this.tabSize = tabSize;
|
||||
}
|
||||
|
||||
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {
|
||||
if (mightContainNonBasicASCII) {
|
||||
return strings.isBasicASCII(lineContent);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static containsRTL(lineContent: string, isBasicASCII: boolean, mightContainRTL: boolean): boolean {
|
||||
if (!isBasicASCII && mightContainRTL) {
|
||||
return strings.containsRTL(lineContent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export const enum InlineDecorationType {
|
||||
Regular = 0,
|
||||
Before = 1,
|
||||
After = 2
|
||||
After = 2,
|
||||
RegularAffectingLetterSpacing = 3
|
||||
}
|
||||
|
||||
export class InlineDecoration {
|
||||
|
||||
@@ -124,7 +124,7 @@ export class ViewModelDecorations implements IDisposable {
|
||||
decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration;
|
||||
|
||||
if (decorationOptions.inlineClassName) {
|
||||
let inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, InlineDecorationType.Regular);
|
||||
let inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular);
|
||||
let intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber);
|
||||
let intersectedEndLineNumber = Math.min(endLineNumber, viewRange.endLineNumber);
|
||||
for (let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) {
|
||||
|
||||
@@ -11,7 +11,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon';
|
||||
import { TokenizationRegistry, ColorId, LanguageId } from 'vs/editor/common/modes';
|
||||
import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
|
||||
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
|
||||
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, IOverviewRulerDecorations } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { MinimapLinesRenderingData, ViewLineRenderingData, ViewModelDecoration, IViewModel, ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
|
||||
import { SplitLinesCollection, IViewModelLinesCollection, IdentityLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
|
||||
import * as viewEvents from 'vs/editor/common/view/viewEvents';
|
||||
import { MinimapTokensColorTracker } from 'vs/editor/common/view/minimapCharRenderer';
|
||||
@@ -23,7 +23,7 @@ import { Color } from 'vs/base/common/color';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
|
||||
import { ITextModel, EndOfLinePreference } from 'vs/editor/common/model';
|
||||
import { ITextModel, EndOfLinePreference, TrackedRangeStickiness, IActiveIndentGuideInfo } from 'vs/editor/common/model';
|
||||
|
||||
const USE_IDENTITY_LINES_COLLECTION = true;
|
||||
|
||||
@@ -32,20 +32,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
private readonly editorId: number;
|
||||
private readonly configuration: editorCommon.IConfiguration;
|
||||
private readonly model: ITextModel;
|
||||
private hasFocus: boolean;
|
||||
private viewportStartLine: number;
|
||||
private viewportStartLineTrackedRange: string;
|
||||
private viewportStartLineTop: number;
|
||||
private readonly lines: IViewModelLinesCollection;
|
||||
public readonly coordinatesConverter: ICoordinatesConverter;
|
||||
public readonly viewLayout: ViewLayout;
|
||||
|
||||
private readonly decorations: ViewModelDecorations;
|
||||
|
||||
private _centeredViewLine: number;
|
||||
|
||||
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
|
||||
super();
|
||||
|
||||
this.editorId = editorId;
|
||||
this.configuration = configuration;
|
||||
this.model = model;
|
||||
this.hasFocus = false;
|
||||
this.viewportStartLine = -1;
|
||||
this.viewportStartLineTrackedRange = null;
|
||||
this.viewportStartLineTop = 0;
|
||||
|
||||
if (USE_IDENTITY_LINES_COLLECTION && this.model.isTooLargeForTokenization()) {
|
||||
|
||||
@@ -83,8 +89,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
}
|
||||
}));
|
||||
|
||||
this._centeredViewLine = -1;
|
||||
|
||||
this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter);
|
||||
|
||||
this._registerModelEvents();
|
||||
@@ -114,13 +118,22 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
super.dispose();
|
||||
this.decorations.dispose();
|
||||
this.lines.dispose();
|
||||
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
|
||||
}
|
||||
|
||||
public setHasFocus(hasFocus: boolean): void {
|
||||
this.hasFocus = hasFocus;
|
||||
}
|
||||
|
||||
private _onConfigurationChanged(eventsCollector: viewEvents.ViewEventsCollector, e: IConfigurationChangedEvent): void {
|
||||
|
||||
// We might need to restore the current centered view range, so save it (if available)
|
||||
const previousCenteredModelRange = this.getCenteredRangeInViewport();
|
||||
let revealPreviousCenteredModelRange = false;
|
||||
let previousViewportStartModelPosition: Position = null;
|
||||
if (this.viewportStartLine !== -1) {
|
||||
let previousViewportStartViewPosition = new Position(this.viewportStartLine, this.getLineMinColumn(this.viewportStartLine));
|
||||
previousViewportStartModelPosition = this.coordinatesConverter.convertViewPositionToModelPosition(previousViewportStartViewPosition);
|
||||
}
|
||||
let restorePreviousViewportStart = false;
|
||||
|
||||
const conf = this.configuration.editor;
|
||||
|
||||
@@ -133,7 +146,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
|
||||
if (this.viewLayout.getCurrentScrollTop() !== 0) {
|
||||
// Never change the scroll position from 0 to something else...
|
||||
revealPreviousCenteredModelRange = true;
|
||||
restorePreviousViewportStart = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,23 +159,16 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
eventsCollector.emit(new viewEvents.ViewConfigurationChangedEvent(e));
|
||||
this.viewLayout.onConfigurationChanged(e);
|
||||
|
||||
if (revealPreviousCenteredModelRange && previousCenteredModelRange) {
|
||||
// modelLine -> viewLine
|
||||
const newCenteredViewRange = this.coordinatesConverter.convertModelRangeToViewRange(previousCenteredModelRange);
|
||||
|
||||
// Send a reveal event to restore the centered content
|
||||
eventsCollector.emit(new viewEvents.ViewRevealRangeRequestEvent(
|
||||
newCenteredViewRange,
|
||||
viewEvents.VerticalRevealType.Center,
|
||||
false,
|
||||
editorCommon.ScrollType.Immediate
|
||||
));
|
||||
if (restorePreviousViewportStart && previousViewportStartModelPosition) {
|
||||
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(previousViewportStartModelPosition);
|
||||
const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
|
||||
this.viewLayout.deltaScrollNow(0, viewPositionTop - this.viewportStartLineTop);
|
||||
}
|
||||
}
|
||||
|
||||
private _registerModelEvents(): void {
|
||||
|
||||
this._register(this.model.onDidChangeRawContent((e) => {
|
||||
this._register(this.model.onDidChangeRawContentFast((e) => {
|
||||
try {
|
||||
const eventsCollector = this._beginEmit();
|
||||
|
||||
@@ -225,6 +231,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
}
|
||||
}
|
||||
this.lines.acceptVersionId(versionId);
|
||||
this.viewLayout.onHeightMaybeChanged();
|
||||
|
||||
if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {
|
||||
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
|
||||
@@ -236,8 +243,18 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
}
|
||||
|
||||
// Update the configuration and reset the centered view line
|
||||
this._centeredViewLine = -1;
|
||||
this.viewportStartLine = -1;
|
||||
this.configuration.setMaxLineNumber(this.model.getLineCount());
|
||||
|
||||
// Recover viewport
|
||||
if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) {
|
||||
const modelRange = this.model._getTrackedRange(this.viewportStartLineTrackedRange);
|
||||
if (modelRange) {
|
||||
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelRange.getStartPosition());
|
||||
const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
|
||||
this.viewLayout.deltaScrollNow(0, viewPositionTop - this.viewportStartLineTop);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.model.onDidChangeTokens((e) => {
|
||||
@@ -311,16 +328,6 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
}
|
||||
}
|
||||
|
||||
public getCenteredRangeInViewport(): Range {
|
||||
if (this._centeredViewLine === -1) {
|
||||
// Never got rendered or not rendered since last content change event
|
||||
return null;
|
||||
}
|
||||
let viewLineNumber = this._centeredViewLine;
|
||||
let currentCenteredViewRange = new Range(viewLineNumber, this.getLineMinColumn(viewLineNumber), viewLineNumber, this.getLineMaxColumn(viewLineNumber));
|
||||
return this.coordinatesConverter.convertViewRangeToModelRange(currentCenteredViewRange);
|
||||
}
|
||||
|
||||
public getVisibleRanges(): Range[] {
|
||||
const visibleViewRange = this.getCompletelyVisibleViewRange();
|
||||
const visibleRange = this.coordinatesConverter.convertViewRangeToModelRange(visibleViewRange);
|
||||
@@ -388,6 +395,43 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
);
|
||||
}
|
||||
|
||||
public saveState(): editorCommon.IViewState {
|
||||
const compatViewState = this.viewLayout.saveState();
|
||||
|
||||
const scrollTop = compatViewState.scrollTop;
|
||||
const firstViewLineNumber = this.viewLayout.getLineNumberAtVerticalOffset(scrollTop);
|
||||
const firstPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(firstViewLineNumber, this.getLineMinColumn(firstViewLineNumber)));
|
||||
const firstPositionDeltaTop = this.viewLayout.getVerticalOffsetForLineNumber(firstViewLineNumber) - scrollTop;
|
||||
|
||||
return {
|
||||
scrollLeft: compatViewState.scrollLeft,
|
||||
firstPosition: firstPosition,
|
||||
firstPositionDeltaTop: firstPositionDeltaTop
|
||||
};
|
||||
}
|
||||
|
||||
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
|
||||
if (typeof state.firstPosition === 'undefined') {
|
||||
// This is a view state serialized by an older version
|
||||
return this._reduceRestoreStateCompatibility(state);
|
||||
}
|
||||
|
||||
const modelPosition = this.model.validatePosition(state.firstPosition);
|
||||
const viewPosition = this.coordinatesConverter.convertModelPositionToViewPosition(modelPosition);
|
||||
const scrollTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber) - state.firstPositionDeltaTop;
|
||||
return {
|
||||
scrollLeft: state.scrollLeft,
|
||||
scrollTop: scrollTop
|
||||
};
|
||||
}
|
||||
|
||||
private _reduceRestoreStateCompatibility(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
|
||||
return {
|
||||
scrollLeft: state.scrollLeft,
|
||||
scrollTop: state.scrollTopWithoutViewZones
|
||||
};
|
||||
}
|
||||
|
||||
public getTabSize(): number {
|
||||
return this.model.getOptions().tabSize;
|
||||
}
|
||||
@@ -400,8 +444,16 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
* Gives a hint that a lot of requests are about to come in for these line numbers.
|
||||
*/
|
||||
public setViewport(startLineNumber: number, endLineNumber: number, centeredLineNumber: number): void {
|
||||
this._centeredViewLine = centeredLineNumber;
|
||||
this.lines.warmUpLookupCache(startLineNumber, endLineNumber);
|
||||
|
||||
this.viewportStartLine = startLineNumber;
|
||||
let position = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(startLineNumber, this.getLineMinColumn(startLineNumber)));
|
||||
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, new Range(position.lineNumber, position.column, position.lineNumber, position.column), TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
|
||||
this.viewportStartLineTop = this.viewLayout.getVerticalOffsetForLineNumber(startLineNumber);
|
||||
}
|
||||
|
||||
public getActiveIndentGuide(lineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
|
||||
return this.lines.getActiveIndentGuide(lineNumber, minLineNumber, maxLineNumber);
|
||||
}
|
||||
|
||||
public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] {
|
||||
@@ -412,6 +464,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
return this.lines.getViewLineContent(lineNumber);
|
||||
}
|
||||
|
||||
public getLineLength(lineNumber: number): number {
|
||||
return this.lines.getViewLineLength(lineNumber);
|
||||
}
|
||||
|
||||
public getLineMinColumn(lineNumber: number): number {
|
||||
return this.lines.getViewLineMinColumn(lineNumber);
|
||||
}
|
||||
@@ -460,6 +516,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
);
|
||||
}
|
||||
|
||||
public getViewLineData(lineNumber: number): ViewLineData {
|
||||
return this.lines.getViewLineData(lineNumber);
|
||||
}
|
||||
|
||||
public getMinimapLinesRenderingData(startLineNumber: number, endLineNumber: number, needed: boolean[]): MinimapLinesRenderingData {
|
||||
let result = this.lines.getViewLinesData(startLineNumber, endLineNumber, needed);
|
||||
return new MinimapLinesRenderingData(
|
||||
@@ -514,8 +574,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
return this.model.getEOL();
|
||||
}
|
||||
|
||||
public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[] {
|
||||
const newLineCharacter = this.model.getEOL();
|
||||
public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean, forceCRLF: boolean): string | string[] {
|
||||
const newLineCharacter = forceCRLF ? '\r\n' : this.model.getEOL();
|
||||
|
||||
ranges = ranges.slice(0);
|
||||
ranges.sort(Range.compareRangesUsingStarts);
|
||||
@@ -543,7 +603,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
|
||||
|
||||
let result: string[] = [];
|
||||
for (let i = 0; i < nonEmptyRanges.length; i++) {
|
||||
result.push(this.getValueInRange(nonEmptyRanges[i], EndOfLinePreference.TextDefined));
|
||||
result.push(this.getValueInRange(nonEmptyRanges[i], forceCRLF ? EndOfLinePreference.CRLF : EndOfLinePreference.TextDefined));
|
||||
}
|
||||
return result.length === 1 ? result[0] : result;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user