Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2 (#8911)

* Merge from vscode a234f13c45b40a0929777cb440ee011b7549eed2

* update distro

* fix layering

* update distro

* fix tests
This commit is contained in:
Anthony Dresser
2020-01-22 13:42:37 -08:00
committed by GitHub
parent 977111eb21
commit bd7aac8ee0
895 changed files with 24651 additions and 14520 deletions

View File

@@ -122,17 +122,19 @@ export class ReplaceCommandThatPreservesSelection implements ICommand {
private readonly _range: Range;
private readonly _text: string;
private readonly _initialSelection: Selection;
private readonly _forceMoveMarkers: boolean;
private _selectionId: string | null;
constructor(editRange: Range, text: string, initialSelection: Selection) {
constructor(editRange: Range, text: string, initialSelection: Selection, forceMoveMarkers: boolean = false) {
this._range = editRange;
this._text = text;
this._initialSelection = initialSelection;
this._forceMoveMarkers = forceMoveMarkers;
this._selectionId = null;
}
public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void {
builder.addEditOperation(this._range, this._text);
builder.addTrackedEditOperation(this._range, this._text, this._forceMoveMarkers);
this._selectionId = builder.trackSelection(this._initialSelection);
}

View File

@@ -113,7 +113,7 @@ export class ShiftCommand implements ICommand {
if (this._opts.useTabStops) {
// keep track of previous line's "miss-alignment"
let previousLineExtraSpaces = 0, extraSpaces = 0;
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++ , previousLineExtraSpaces = extraSpaces) {
for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++, previousLineExtraSpaces = extraSpaces) {
extraSpaces = 0;
let lineText = model.getLineContent(lineNumber);
let indentationEndIndex = strings.firstNonWhitespaceIndex(lineText);

View File

@@ -11,7 +11,7 @@ import * as arrays from 'vs/base/common/arrays';
import { IEditorOptions, editorOptionsRegistry, ValidatedEditorOptions, IEnvironmentalOptions, IComputedEditorOptions, ConfigurationChangedEvent, EDITOR_MODEL_DEFAULTS, EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions';
import { EditorZoom } from 'vs/editor/common/config/editorZoom';
import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IConfiguration, IDimension } from 'vs/editor/common/editorCommon';
import { ConfigurationScope, Extensions, IConfigurationNode, IConfigurationRegistry, IConfigurationPropertySchema } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
@@ -278,7 +278,7 @@ function deepCloneAndMigrateOptions(_options: IEditorOptions): IEditorOptions {
return options;
}
export abstract class CommonEditorConfiguration extends Disposable implements editorCommon.IConfiguration {
export abstract class CommonEditorConfiguration extends Disposable implements IConfiguration {
private _onDidChange = this._register(new Emitter<ConfigurationChangedEvent>());
public readonly onDidChange: Event<ConfigurationChangedEvent> = this._onDidChange.event;
@@ -308,7 +308,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed
this._register(TabFocus.onDidChangeTabFocus(_ => this._recomputeOptions()));
}
public observeReferenceElement(dimension?: editorCommon.IDimension): void {
public observeReferenceElement(dimension?: IDimension): void {
}
public dispose(): void {

View File

@@ -33,7 +33,6 @@ export type EditorAutoClosingOvertypeStrategy = 'always' | 'auto' | 'never';
/**
* Configuration options for auto indentation in the editor
* @internal
*/
export const enum EditorAutoIndentStrategy {
None = 0,
@@ -262,21 +261,21 @@ export interface IEditorOptions {
* Defaults to 'same' in vscode and to 'none' in monaco-editor.
*/
wrappingIndent?: 'none' | 'same' | 'indent' | 'deepIndent';
/**
* Controls the wrapping algorithm to use.
* Defaults to 'monospace'.
*/
wrappingAlgorithm?: 'monospace' | 'dom';
/**
* Configure word wrapping characters. A break will be introduced before these characters.
* Defaults to '{([+'.
* Defaults to '([{‘“〈《「『【〔([{「£¥$£¥+'.
*/
wordWrapBreakBeforeCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters.
* Defaults to ' \t})]?|&,;'.
* Defaults to ' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」'.
*/
wordWrapBreakAfterCharacters?: string;
/**
* Configure word wrapping characters. A break will be introduced after these characters only if no `wordWrapBreakBeforeCharacters` or `wordWrapBreakAfterCharacters` were found.
* Defaults to '.'.
*/
wordWrapBreakObtrusiveCharacters?: string;
/**
* Performance guard: Stop rendering a line after x characters.
* Defaults to 10000.
@@ -465,7 +464,7 @@ export interface IEditorOptions {
*/
codeActionsOnSaveTimeout?: number;
/**
* Enable code folding
* Enable code folding.
* Defaults to true.
*/
folding?: boolean;
@@ -474,6 +473,11 @@ export interface IEditorOptions {
* Defaults to 'auto'.
*/
foldingStrategy?: 'auto' | 'indentation';
/**
* Enable highlight for folded regions.
* Defaults to true.
*/
foldingHighlight?: boolean;
/**
* Controls whether the fold actions in the gutter stay always visible or hide unless the mouse is over the gutter.
* Defaults to 'mouseover'.
@@ -537,6 +541,11 @@ export interface IEditorOptions {
* Controls fading out of unused variables.
*/
showUnused?: boolean;
/**
* Controls whether to focus the inline editor in the peek widget by default.
* Defaults to false.
*/
peekWidgetFocusInlineEditor?: boolean;
}
export interface IEditorConstructionOptions extends IEditorOptions {
@@ -632,7 +641,7 @@ export class ValidatedEditorOptions {
}
/**
* @internal
* All computed editor options.
*/
export interface IComputedEditorOptions {
get<T extends EditorOption>(id: T): FindComputedEditorOptionValueById<T>;
@@ -656,13 +665,10 @@ export interface IEnvironmentalOptions {
readonly accessibilitySupport: AccessibilitySupport;
}
/**
* @internal
*/
export interface IEditorOption<K1 extends EditorOption, V> {
readonly id: K1;
readonly name: string;
readonly defaultValue: V;
defaultValue: V;
/**
* @internal
*/
@@ -996,7 +1002,6 @@ class EditorAccessibilitySupport extends BaseEditorOption<EditorOption.accessibi
/**
* The kind of animation in which the editor's cursor should be rendered.
* @internal
*/
export const enum TextEditorCursorBlinkingStyle {
/**
@@ -1041,7 +1046,6 @@ function _cursorBlinkingStyleFromString(cursorBlinkingStyle: 'blink' | 'smooth'
/**
* The style in which the editor's cursor should be rendered.
* @internal
*/
export enum TextEditorCursorStyle {
/**
@@ -1170,9 +1174,6 @@ export interface IEditorFindOptions {
globalFindClipboard?: boolean;
}
/**
* @internal
*/
export type EditorFindOptions = Readonly<Required<IEditorFindOptions>>;
class EditorFind extends BaseEditorOption<EditorOption.find, EditorFindOptions> {
@@ -1360,9 +1361,6 @@ export interface IGotoLocationOptions {
alternativeReferenceCommand?: string;
}
/**
* @internal
*/
export type GoToLocationOptions = Readonly<Required<IGotoLocationOptions>>;
class EditorGoToLocation extends BaseEditorOption<EditorOption.gotoLocation, GoToLocationOptions> {
@@ -1492,9 +1490,6 @@ export interface IEditorHoverOptions {
sticky?: boolean;
}
/**
* @internal
*/
export type EditorHoverOptions = Readonly<Required<IEditorHoverOptions>>;
class EditorHover extends BaseEditorOption<EditorOption.hover, EditorHoverOptions> {
@@ -1542,6 +1537,55 @@ class EditorHover extends BaseEditorOption<EditorOption.hover, EditorHoverOption
//#endregion
//#region semantic highlighting
/**
* Configuration options for semantic highlighting
*/
export interface IEditorSemanticHighlightingOptions {
/**
* Enable semantic highlighting.
* Defaults to true.
*/
enabled?: boolean;
}
/**
* @internal
*/
export type EditorSemanticHighlightingOptions = Readonly<Required<IEditorSemanticHighlightingOptions>>;
class EditorSemanticHighlighting extends BaseEditorOption<EditorOption.semanticHighlighting, EditorSemanticHighlightingOptions> {
constructor() {
const defaults: EditorSemanticHighlightingOptions = {
enabled: true
};
super(
EditorOption.semanticHighlighting, 'semanticHighlighting', defaults,
{
'editor.semanticHighlighting.enabled': {
type: 'boolean',
default: defaults.enabled,
description: nls.localize('semanticHighlighting.enabled', "Controls whether the semanticHighlighting is shown for the languages that support it.")
}
}
);
}
public validate(_input: any): EditorSemanticHighlightingOptions {
if (typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IEditorSemanticHighlightingOptions;
return {
enabled: EditorBooleanOption.boolean(input.enabled, this.defaultValue.enabled)
};
}
}
//#endregion
//#region layoutInfo
/**
@@ -1594,10 +1638,6 @@ export interface EditorLayoutInfo {
* The width of the glyph margin.
*/
readonly glyphMarginWidth: number;
/**
* The height of the glyph margin.
*/
readonly glyphMarginHeight: number;
/**
* Left position for the line numbers.
@@ -1607,10 +1647,6 @@ export interface EditorLayoutInfo {
* The width of the line numbers.
*/
readonly lineNumbersWidth: number;
/**
* The height of the line numbers.
*/
readonly lineNumbersHeight: number;
/**
* Left position for the line decorations.
@@ -1620,10 +1656,6 @@ export interface EditorLayoutInfo {
* The width of the line decorations.
*/
readonly decorationsWidth: number;
/**
* The height of the line decorations.
*/
readonly decorationsHeight: number;
/**
* Left position for the content (actual text)
@@ -1633,10 +1665,6 @@ export interface EditorLayoutInfo {
* The width of the content (actual text)
*/
readonly contentWidth: number;
/**
* The height of the content (actual height)
*/
readonly contentHeight: number;
/**
* The position for the minimap
@@ -1823,19 +1851,15 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
glyphMarginLeft: glyphMarginLeft,
glyphMarginWidth: glyphMarginWidth,
glyphMarginHeight: outerHeight,
lineNumbersLeft: lineNumbersLeft,
lineNumbersWidth: lineNumbersWidth,
lineNumbersHeight: outerHeight,
decorationsLeft: decorationsLeft,
decorationsWidth: lineDecorationsWidth,
decorationsHeight: outerHeight,
contentLeft: contentLeft,
contentWidth: contentWidth,
contentHeight: outerHeight,
renderMinimap: renderMinimap,
minimapLeft: minimapLeft,
@@ -1871,9 +1895,6 @@ export interface IEditorLightbulbOptions {
enabled?: boolean;
}
/**
* @internal
*/
export type EditorLightbulbOptions = Readonly<Required<IEditorLightbulbOptions>>;
class EditorLightbulb extends BaseEditorOption<EditorOption.lightbulb, EditorLightbulbOptions> {
@@ -1965,9 +1986,6 @@ export interface IEditorMinimapOptions {
scale?: number;
}
/**
* @internal
*/
export type EditorMinimapOptions = Readonly<Required<IEditorMinimapOptions>>;
class EditorMinimap extends BaseEditorOption<EditorOption.minimap, EditorMinimapOptions> {
@@ -2069,9 +2087,6 @@ export interface IEditorParameterHintOptions {
cycle?: boolean;
}
/**
* @internal
*/
export type InternalParameterHintOptions = Readonly<Required<IEditorParameterHintOptions>>;
class EditorParameterHints extends BaseEditorOption<EditorOption.parameterHints, InternalParameterHintOptions> {
@@ -2138,9 +2153,6 @@ export interface IQuickSuggestionsOptions {
strings: boolean;
}
/**
* @internal
*/
export type ValidQuickSuggestionsOptions = boolean | Readonly<Required<IQuickSuggestionsOptions>>;
class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggestions, ValidQuickSuggestionsOptions> {
@@ -2217,9 +2229,6 @@ class EditorQuickSuggestions extends BaseEditorOption<EditorOption.quickSuggesti
export type LineNumbersType = 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string);
/**
* @internal
*/
export const enum RenderLineNumbersType {
Off = 0,
On = 1,
@@ -2228,9 +2237,6 @@ export const enum RenderLineNumbersType {
Custom = 4
}
/**
* @internal
*/
export interface InternalEditorRenderLineNumbersOptions {
readonly renderType: RenderLineNumbersType;
readonly renderFn: ((lineNumber: number) => string) | null;
@@ -2386,9 +2392,6 @@ export interface IEditorScrollbarOptions {
horizontalSliderSize?: number;
}
/**
* @internal
*/
export interface InternalEditorScrollbarOptions {
readonly arrowSize: number;
readonly vertical: ScrollbarVisibility;
@@ -2603,9 +2606,6 @@ export interface ISuggestOptions {
showSnippets?: boolean;
}
/**
* @internal
*/
export type InternalSuggestOptions = Readonly<Required<ISuggestOptions>>;
class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSuggestOptions> {
@@ -2829,7 +2829,7 @@ class EditorSuggest extends BaseEditorOption<EditorOption.suggest, InternalSugge
type: 'boolean',
default: true,
markdownDescription: nls.localize('editor.suggest.showSnippets', "When enabled IntelliSense shows `snippet`-suggestions.")
},
}
}
);
}
@@ -2899,7 +2899,6 @@ class EditorTabFocusMode extends ComputedEditorOption<EditorOption.tabFocusMode,
/**
* Describes how to indent wrapped lines.
* @internal
*/
export const enum WrappingIndent {
/**
@@ -2933,9 +2932,6 @@ function _wrappingIndentFromString(wrappingIndent: 'none' | 'same' | 'indent' |
//#region wrappingInfo
/**
* @internal
*/
export interface EditorWrappingInfo {
readonly isDominatedByLongLines: boolean;
readonly isWordWrapMinified: boolean;
@@ -3056,9 +3052,6 @@ function register<K1 extends EditorOption, V>(option: IEditorOption<K1, V>): IEd
return option;
}
/**
* @internal
*/
export const enum EditorOption {
acceptSuggestionOnCommitCharacter,
acceptSuggestionOnEnter,
@@ -3091,6 +3084,7 @@ export const enum EditorOption {
fixedOverflowWidgets,
folding,
foldingStrategy,
foldingHighlight,
fontFamily,
fontInfo,
fontLigatures,
@@ -3123,6 +3117,7 @@ export const enum EditorOption {
overviewRulerBorder,
overviewRulerLanes,
parameterHints,
peekWidgetFocusInlineEditor,
quickSuggestions,
quickSuggestionsDelay,
readOnly,
@@ -3140,6 +3135,7 @@ export const enum EditorOption {
selectionClipboard,
selectionHighlight,
selectOnLineNumbers,
semanticHighlighting,
showFoldingControls,
showUnused,
snippetSuggestions,
@@ -3156,10 +3152,10 @@ export const enum EditorOption {
wordWrap,
wordWrapBreakAfterCharacters,
wordWrapBreakBeforeCharacters,
wordWrapBreakObtrusiveCharacters,
wordWrapColumn,
wordWrapMinified,
wrappingIndent,
wrappingAlgorithm,
// Leave these at the end (because they have dependencies!)
editorClassName,
@@ -3170,7 +3166,18 @@ export const enum EditorOption {
}
/**
* @internal
* WORKAROUND: TS emits "any" for complex editor options values (anything except string, bool, enum, etc. ends up being "any")
* @monacodtsreplace
* /accessibilitySupport, any/accessibilitySupport, AccessibilitySupport/
* /find, any/find, EditorFindOptions/
* /fontInfo, any/fontInfo, FontInfo/
* /gotoLocation, any/gotoLocation, GoToLocationOptions/
* /hover, any/hover, EditorHoverOptions/
* /lightbulb, any/lightbulb, EditorLightbulbOptions/
* /minimap, any/minimap, EditorMinimapOptions/
* /parameterHints, any/parameterHints, InternalParameterHintOptions/
* /quickSuggestions, any/quickSuggestions, ValidQuickSuggestionsOptions/
* /suggest, any/suggest, InternalSuggestOptions/
*/
export const EditorOptions = {
acceptSuggestionOnCommitCharacter: register(new EditorBooleanOption(
@@ -3358,6 +3365,10 @@ export const EditorOptions = {
['auto', 'indentation'] as const,
{ markdownDescription: nls.localize('foldingStrategy', "Controls the strategy for computing folding ranges. `auto` uses a language specific folding strategy, if available. `indentation` uses the indentation based folding strategy.") }
)),
foldingHighlight: register(new EditorBooleanOption(
EditorOption.foldingHighlight, 'foldingHighlight', true,
{ description: nls.localize('foldingHighlight', "Controls whether the editor should highlight folded ranges.") }
)),
fontFamily: register(new EditorStringOption(
EditorOption.fontFamily, 'fontFamily', EDITOR_FONT_DEFAULTS.fontFamily,
{ description: nls.localize('fontFamily', "Controls the font family.") }
@@ -3483,6 +3494,10 @@ export const EditorOptions = {
3, 0, 3
)),
parameterHints: register(new EditorParameterHints()),
peekWidgetFocusInlineEditor: register(new EditorBooleanOption(
EditorOption.peekWidgetFocusInlineEditor, 'peekWidgetFocusInlineEditor', false,
{ description: nls.localize('peekWidgetFocusInlineEditor', "Controls whether to focus the inline editor in the peek widget by default.") }
)),
quickSuggestions: register(new EditorQuickSuggestions()),
quickSuggestionsDelay: register(new EditorIntOption(
EditorOption.quickSuggestionsDelay, 'quickSuggestionsDelay',
@@ -3565,6 +3580,7 @@ export const EditorOptions = {
selectOnLineNumbers: register(new EditorBooleanOption(
EditorOption.selectOnLineNumbers, 'selectOnLineNumbers', true,
)),
semanticHighlighting: register(new EditorSemanticHighlighting()),
showFoldingControls: register(new EditorStringEnumOption(
EditorOption.showFoldingControls, 'showFoldingControls',
'mouseover' as 'always' | 'mouseover',
@@ -3679,16 +3695,12 @@ export const EditorOptions = {
)),
wordWrapBreakAfterCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakAfterCharacters, 'wordWrapBreakAfterCharacters',
' \t})]?|/&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
' \t})]?|/&.,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー”〉》」』】〕)]}」',
)),
wordWrapBreakBeforeCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakBeforeCharacters, 'wordWrapBreakBeforeCharacters',
'([{‘“〈《「『【〔([{「£¥$£¥+'
)),
wordWrapBreakObtrusiveCharacters: register(new EditorStringOption(
EditorOption.wordWrapBreakObtrusiveCharacters, 'wordWrapBreakObtrusiveCharacters',
'.'
)),
wordWrapColumn: register(new EditorIntOption(
EditorOption.wordWrapColumn, 'wordWrapColumn',
80, 1, Constants.MAX_SAFE_SMALL_INTEGER,
@@ -3720,28 +3732,28 @@ export const EditorOptions = {
description: nls.localize('wrappingIndent', "Controls the indentation of wrapped lines."),
}
)),
wrappingAlgorithm: register(new EditorStringEnumOption(
EditorOption.wrappingAlgorithm, 'wrappingAlgorithm',
'monospace' as 'monospace' | 'dom',
['monospace', 'dom'] as const,
{
enumDescriptions: [
nls.localize('wrappingAlgorithm.monospace', "Assumes that all characters are of the same width. This is a fast algorithm."),
nls.localize('wrappingAlgorithm.dom', "Delegates wrapping points computation to the DOM. This is a slow algorithm, that might cause freezes for large files.")
],
description: nls.localize('wrappingAlgorithm', "Controls the algorithm that computes wrapping points.")
}
)),
// Leave these at the end (because they have dependencies!)
editorClassName: register(new EditorClassName()),
pixelRatio: register(new EditorPixelRatio()),
tabFocusMode: register(new EditorTabFocusMode()),
layoutInfo: register(new EditorLayoutInfoComputer()),
wrappingInfo: register(new EditorWrappingInfoComputer()),
wrappingInfo: register(new EditorWrappingInfoComputer())
};
/**
* @internal
*/
type EditorOptionsType = typeof EditorOptions;
/**
* @internal
*/
type FindEditorOptionsKeyById<T extends EditorOption> = { [K in keyof EditorOptionsType]: EditorOptionsType[K]['id'] extends T ? K : never }[keyof EditorOptionsType];
/**
* @internal
*/
type ComputedEditorOptionValue<T extends IEditorOption<any, any>> = T extends IEditorOption<any, infer R> ? R : never;
/**
* @internal
*/
export type FindComputedEditorOptionValueById<T extends EditorOption> = NonNullable<ComputedEditorOptionValue<EditorOptionsType[FindEditorOptionsKeyById<T>]>>;

View File

@@ -727,7 +727,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
case H.Paste:
cursorChangeReason = CursorChangeReason.Paste;
this._paste(<string>payload.text, <boolean>payload.pasteOnNewLine, <string[]>payload.multicursorText);
this._paste(<string>payload.text, <boolean>payload.pasteOnNewLine, <string[]>payload.multicursorText || []);
break;
case H.Cut:
@@ -1000,7 +1000,7 @@ class CommandExecutor {
let operations: IIdentifiedSingleEditOperation[] = [];
let operationMinor = 0;
const addEditOperation = (selection: Range, text: string | null) => {
const addEditOperation = (selection: Range, text: string | null, forceMoveMarkers: boolean = false) => {
if (selection.isEmpty() && text === '') {
// This command wants to add a no-op => no thank you
return;
@@ -1012,15 +1012,15 @@ class CommandExecutor {
},
range: selection,
text: text,
forceMoveMarkers: false,
forceMoveMarkers: forceMoveMarkers,
isAutoWhitespaceEdit: command.insertsAutoWhitespace
});
};
let hadTrackedEditOperation = false;
const addTrackedEditOperation = (selection: Range, text: string | null) => {
const addTrackedEditOperation = (selection: Range, text: string | null, forceMoveMarkers?: boolean) => {
hadTrackedEditOperation = true;
addEditOperation(selection, text);
addEditOperation(selection, text, forceMoveMarkers);
};
const trackSelection = (selection: Selection, trackPreviousOnEmpty?: boolean) => {

View File

@@ -94,7 +94,7 @@ export class TypeOperations {
if (pasteOnNewLine) {
// Paste entire line at the beginning of line
let typeSelection = new Range(position.lineNumber, 1, position.lineNumber, 1);
commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection);
commands[i] = new ReplaceCommandThatPreservesSelection(typeSelection, text, selection, true);
} else {
commands[i] = new ReplaceCommand(selection, text);
}

View File

@@ -12,14 +12,14 @@ export class CharacterClassifier<T extends number> {
/**
* Maintain a compact (fully initialized ASCII map for quickly classifying ASCII characters - used more often in code).
*/
private _asciiMap: Uint8Array;
protected _asciiMap: Uint8Array;
/**
* The entire map (sparse array).
*/
private _map: Map<number, number>;
protected _map: Map<number, number>;
private _defaultValue: number;
protected _defaultValue: number;
constructor(_defaultValue: T) {
let defaultValue = toUint8(_defaultValue);

View File

@@ -5,7 +5,7 @@
import * as strings from 'vs/base/common/strings';
declare var TextDecoder: any; // TODO@TypeScript
declare const TextDecoder: any; // TODO@TypeScript
interface TextDecoder {
decode(view: Uint16Array): string;
}

View File

@@ -22,7 +22,7 @@ export interface IEditOperationBuilder {
* @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete.
*/
addEditOperation(range: Range, text: string | null): void;
addEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
/**
* Add a new edit operation (a replace operation).
@@ -30,7 +30,7 @@ export interface IEditOperationBuilder {
* @param range The range to replace (delete). May be empty to represent a simple insert.
* @param text The text to replace with. May be null to represent a simple delete.
*/
addTrackedEditOperation(range: Range, text: string | null): void;
addTrackedEditOperation(range: Range, text: string | null, forceMoveMarkers?: boolean): void;
/**
* Track `selection` when applying edit operations.
@@ -174,6 +174,14 @@ export interface IScrollEvent {
readonly scrollHeightChanged: boolean;
}
export interface IContentSizeChangedEvent {
readonly contentWidth: number;
readonly contentHeight: number;
readonly contentWidthChanged: boolean;
readonly contentHeightChanged: boolean;
}
export interface INewScrollPosition {
scrollLeft?: number;
scrollTop?: number;

View File

@@ -511,7 +511,93 @@ export class PieceTreeBase {
}
public getLinesContent(): string[] {
return this.getContentOfSubTree(this.root).split(/\r\n|\r|\n/);
let lines: string[] = [];
let linesLength = 0;
let currentLine = '';
let danglingCR = false;
this.iterate(this.root, node => {
if (node === SENTINEL) {
return true;
}
const piece = node.piece;
let pieceLength = piece.length;
if (pieceLength === 0) {
return true;
}
const buffer = this._buffers[piece.bufferIndex].buffer;
const lineStarts = this._buffers[piece.bufferIndex].lineStarts;
const pieceStartLine = piece.start.line;
const pieceEndLine = piece.end.line;
let pieceStartOffset = lineStarts[pieceStartLine] + piece.start.column;
if (danglingCR) {
if (buffer.charCodeAt(pieceStartOffset) === CharCode.LineFeed) {
// pretend the \n was in the previous piece..
pieceStartOffset++;
pieceLength--;
}
lines[linesLength++] = currentLine;
currentLine = '';
danglingCR = false;
if (pieceLength === 0) {
return true;
}
}
if (pieceStartLine === pieceEndLine) {
// this piece has no new lines
if (!this._EOLNormalized && buffer.charCodeAt(pieceStartOffset + pieceLength - 1) === CharCode.CarriageReturn) {
danglingCR = true;
currentLine += buffer.substr(pieceStartOffset, pieceLength - 1);
} else {
currentLine += buffer.substr(pieceStartOffset, pieceLength);
}
return true;
}
// add the text before the first line start in this piece
currentLine += (
this._EOLNormalized
? buffer.substring(pieceStartOffset, Math.max(pieceStartOffset, lineStarts[pieceStartLine + 1] - this._EOLLength))
: buffer.substring(pieceStartOffset, lineStarts[pieceStartLine + 1]).replace(/(\r\n|\r|\n)$/, '')
);
lines[linesLength++] = currentLine;
for (let line = pieceStartLine + 1; line < pieceEndLine; line++) {
currentLine = (
this._EOLNormalized
? buffer.substring(lineStarts[line], lineStarts[line + 1] - this._EOLLength)
: buffer.substring(lineStarts[line], lineStarts[line + 1]).replace(/(\r\n|\r|\n)$/, '')
);
lines[linesLength++] = currentLine;
}
if (!this._EOLNormalized && buffer.charCodeAt(lineStarts[pieceEndLine] + piece.end.column - 1) === CharCode.CarriageReturn) {
danglingCR = true;
if (piece.end.column === 0) {
// The last line ended with a \r, let's undo the push, it will be pushed by next iteration
linesLength--;
} else {
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column - 1);
}
} else {
currentLine = buffer.substr(lineStarts[pieceEndLine], piece.end.column);
}
return true;
});
if (danglingCR) {
lines[linesLength++] = currentLine;
currentLine = '';
}
lines[linesLength++] = currentLine;
return lines;
}
public getLength(): number {
@@ -728,7 +814,7 @@ export class PieceTreeBase {
// #endregion
// #region Piece Table
insert(offset: number, value: string, eolNormalized: boolean = false): void {
public insert(offset: number, value: string, eolNormalized: boolean = false): void {
this._EOLNormalized = this._EOLNormalized && eolNormalized;
this._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = '';
@@ -826,7 +912,7 @@ export class PieceTreeBase {
this.computeBufferMetadata();
}
delete(offset: number, cnt: number): void {
public delete(offset: number, cnt: number): void {
this._lastVisitedLine.lineNumber = 0;
this._lastVisitedLine.value = '';
@@ -899,7 +985,7 @@ export class PieceTreeBase {
this.computeBufferMetadata();
}
insertContentToNodeLeft(value: string, node: TreeNode) {
private insertContentToNodeLeft(value: string, node: TreeNode) {
// we are inserting content to the beginning of node
let nodesToDel: TreeNode[] = [];
if (this.shouldCheckCRLF() && this.endWithCR(value) && this.startWithLF(node)) {
@@ -934,7 +1020,7 @@ export class PieceTreeBase {
this.deleteNodes(nodesToDel);
}
insertContentToNodeRight(value: string, node: TreeNode) {
private insertContentToNodeRight(value: string, node: TreeNode) {
// we are inserting to the right of this node.
if (this.adjustCarriageReturnFromNext(value, node)) {
// move \n to the new node.
@@ -952,9 +1038,9 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode);
}
positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
private positionInBuffer(node: TreeNode, remainder: number): BufferCursor;
private positionInBuffer(node: TreeNode, remainder: number, ret: BufferCursor): null;
private positionInBuffer(node: TreeNode, remainder: number, ret?: BufferCursor): BufferCursor | null {
let piece = node.piece;
let bufferIndex = node.piece.bufferIndex;
let lineStarts = this._buffers[bufferIndex].lineStarts;
@@ -1002,7 +1088,7 @@ export class PieceTreeBase {
};
}
getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number {
private getLineFeedCnt(bufferIndex: number, start: BufferCursor, end: BufferCursor): number {
// we don't need to worry about start: abc\r|\n, or abc|\r, or abc|\n, or abc|\r\n doesn't change the fact that, there is one line break after start.
// now let's take care of end: abc\r|\n, if end is in between \r and \n, we need to add line feed count by 1
if (end.column === 0) {
@@ -1032,18 +1118,18 @@ export class PieceTreeBase {
}
}
offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number {
private offsetInBuffer(bufferIndex: number, cursor: BufferCursor): number {
let lineStarts = this._buffers[bufferIndex].lineStarts;
return lineStarts[cursor.line] + cursor.column;
}
deleteNodes(nodes: TreeNode[]): void {
private deleteNodes(nodes: TreeNode[]): void {
for (let i = 0; i < nodes.length; i++) {
rbDelete(this, nodes[i]);
}
}
createNewPieces(text: string): Piece[] {
private 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
@@ -1128,11 +1214,11 @@ export class PieceTreeBase {
return [newPiece];
}
getLinesRawContent(): string {
public getLinesRawContent(): string {
return this.getContentOfSubTree(this.root);
}
getLineRawContent(lineNumber: number, endOffset: number = 0): string {
public getLineRawContent(lineNumber: number, endOffset: number = 0): string {
let x = this.root;
let ret = '';
@@ -1204,7 +1290,7 @@ export class PieceTreeBase {
return ret;
}
computeBufferMetadata() {
private computeBufferMetadata() {
let x = this.root;
let lfCnt = 1;
@@ -1222,7 +1308,7 @@ export class PieceTreeBase {
}
// #region node operations
getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } {
private getIndexOf(node: TreeNode, accumulatedValue: number): { index: number, remainder: number } {
let piece = node.piece;
let pos = this.positionInBuffer(node, accumulatedValue);
let lineCnt = pos.line - piece.start.line;
@@ -1239,7 +1325,7 @@ export class PieceTreeBase {
return { index: lineCnt, remainder: pos.column };
}
getAccumulatedValue(node: TreeNode, index: number) {
private getAccumulatedValue(node: TreeNode, index: number) {
if (index < 0) {
return 0;
}
@@ -1253,7 +1339,7 @@ export class PieceTreeBase {
}
}
deleteNodeTail(node: TreeNode, pos: BufferCursor) {
private deleteNodeTail(node: TreeNode, pos: BufferCursor) {
const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt;
const originalEndOffset = this.offsetInBuffer(piece.bufferIndex, piece.end);
@@ -1277,7 +1363,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, size_delta, lf_delta);
}
deleteNodeHead(node: TreeNode, pos: BufferCursor) {
private deleteNodeHead(node: TreeNode, pos: BufferCursor) {
const piece = node.piece;
const originalLFCnt = piece.lineFeedCnt;
const originalStartOffset = this.offsetInBuffer(piece.bufferIndex, piece.start);
@@ -1299,7 +1385,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, size_delta, lf_delta);
}
shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) {
private shrinkNode(node: TreeNode, start: BufferCursor, end: BufferCursor) {
const piece = node.piece;
const originalStartPos = piece.start;
const originalEndPos = piece.end;
@@ -1334,7 +1420,7 @@ export class PieceTreeBase {
this.validateCRLFWithPrevNode(newNode);
}
appendToNode(node: TreeNode, value: string): void {
private appendToNode(node: TreeNode, value: string): void {
if (this.adjustCarriageReturnFromNext(value, node)) {
value += '\n';
}
@@ -1374,7 +1460,7 @@ export class PieceTreeBase {
updateTreeMetadata(this, node, value.length, lf_delta);
}
nodeAt(offset: number): NodePosition {
private nodeAt(offset: number): NodePosition {
let x = this.root;
let cache = this._searchCache.get(offset);
if (cache) {
@@ -1409,7 +1495,7 @@ export class PieceTreeBase {
return null!;
}
nodeAt2(lineNumber: number, column: number): NodePosition {
private nodeAt2(lineNumber: number, column: number): NodePosition {
let x = this.root;
let nodeStartOffset = 0;
@@ -1476,7 +1562,7 @@ export class PieceTreeBase {
return null!;
}
nodeCharCodeAt(node: TreeNode, offset: number): number {
private nodeCharCodeAt(node: TreeNode, offset: number): number {
if (node.piece.lineFeedCnt < 1) {
return -1;
}
@@ -1485,7 +1571,7 @@ export class PieceTreeBase {
return buffer.buffer.charCodeAt(newOffset);
}
offsetOfNode(node: TreeNode): number {
private offsetOfNode(node: TreeNode): number {
if (!node) {
return 0;
}
@@ -1504,11 +1590,11 @@ export class PieceTreeBase {
// #endregion
// #region CRLF
shouldCheckCRLF() {
private shouldCheckCRLF() {
return !(this._EOLNormalized && this._EOL === '\n');
}
startWithLF(val: string | TreeNode): boolean {
private startWithLF(val: string | TreeNode): boolean {
if (typeof val === 'string') {
return val.charCodeAt(0) === 10;
}
@@ -1532,7 +1618,7 @@ export class PieceTreeBase {
return this._buffers[piece.bufferIndex].buffer.charCodeAt(startOffset) === 10;
}
endWithCR(val: string | TreeNode): boolean {
private endWithCR(val: string | TreeNode): boolean {
if (typeof val === 'string') {
return val.charCodeAt(val.length - 1) === 13;
}
@@ -1544,7 +1630,7 @@ export class PieceTreeBase {
return this.nodeCharCodeAt(val, val.piece.length - 1) === 13;
}
validateCRLFWithPrevNode(nextNode: TreeNode) {
private validateCRLFWithPrevNode(nextNode: TreeNode) {
if (this.shouldCheckCRLF() && this.startWithLF(nextNode)) {
let node = nextNode.prev();
if (this.endWithCR(node)) {
@@ -1553,7 +1639,7 @@ export class PieceTreeBase {
}
}
validateCRLFWithNextNode(node: TreeNode) {
private validateCRLFWithNextNode(node: TreeNode) {
if (this.shouldCheckCRLF() && this.endWithCR(node)) {
let nextNode = node.next();
if (this.startWithLF(nextNode)) {
@@ -1562,7 +1648,7 @@ export class PieceTreeBase {
}
}
fixCRLF(prev: TreeNode, next: TreeNode) {
private fixCRLF(prev: TreeNode, next: TreeNode) {
let nodesToDel: TreeNode[] = [];
// update node
let lineStarts = this._buffers[prev.piece.bufferIndex].lineStarts;
@@ -1617,7 +1703,7 @@ export class PieceTreeBase {
}
}
adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean {
private adjustCarriageReturnFromNext(value: string, node: TreeNode): boolean {
if (this.shouldCheckCRLF() && this.endWithCR(value)) {
let nextNode = node.next();
if (this.startWithLF(nextNode)) {
@@ -1667,7 +1753,7 @@ export class PieceTreeBase {
return callback(node) && this.iterate(node.right, callback);
}
getNodeContent(node: TreeNode) {
private getNodeContent(node: TreeNode) {
if (node === SENTINEL) {
return '';
}
@@ -1695,7 +1781,7 @@ export class PieceTreeBase {
* /
* z
*/
rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
private rbInsertRight(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
@@ -1727,7 +1813,7 @@ export class PieceTreeBase {
* \
* z
*/
rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
private rbInsertLeft(node: TreeNode | null, p: Piece): TreeNode {
let z = new TreeNode(p, NodeColor.Red);
z.left = SENTINEL;
z.right = SENTINEL;
@@ -1751,7 +1837,7 @@ export class PieceTreeBase {
return z;
}
getContentOfSubTree(node: TreeNode): string {
private getContentOfSubTree(node: TreeNode): string {
let str = '';
this.iterate(node, node => {

View File

@@ -158,6 +158,17 @@ class TextModelSnapshot implements model.ITextSnapshot {
const invalidFunc = () => { throw new Error(`Invalid change accessor`); };
const enum StringOffsetValidationType {
/**
* Even allowed in surrogate pairs
*/
Relaxed = 0,
/**
* Not allowed in surrogate pairs
*/
SurrogatePairs = 1,
}
export class TextModel extends Disposable implements model.ITextModel {
private static readonly MODEL_SYNC_LIMIT = 50 * 1024 * 1024; // 50 MB
@@ -673,7 +684,7 @@ export class TextModel extends Disposable implements model.ITextModel {
public getOffsetAt(rawPosition: IPosition): number {
this._assertNotDisposed();
let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, false);
let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, StringOffsetValidationType.Relaxed);
return this._buffer.getOffsetAt(position.lineNumber, position.column);
}
@@ -868,10 +879,7 @@ 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 {
private _isValidPosition(lineNumber: number, column: number, validationType: StringOffsetValidationType): boolean {
if (typeof lineNumber !== 'number' || typeof column !== 'number') {
return false;
}
@@ -893,14 +901,19 @@ export class TextModel extends Disposable implements model.ITextModel {
return false;
}
if (column === 1) {
return true;
}
const maxColumn = this.getLineMaxColumn(lineNumber);
if (column > maxColumn) {
return false;
}
if (strict) {
const [charStartOffset,] = strings.getCharContainingOffset(this._buffer.getLineContent(lineNumber), column - 1);
if (column !== charStartOffset + 1) {
if (validationType === StringOffsetValidationType.SurrogatePairs) {
// !!At this point, column > 1
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
if (strings.isHighSurrogate(charCodeBefore)) {
return false;
}
}
@@ -908,10 +921,7 @@ export class TextModel extends Disposable implements model.ITextModel {
return true;
}
/**
* @param strict Do NOT allow a position inside a high-low surrogate pair
*/
private _validatePosition(_lineNumber: number, _column: number, strict: boolean): Position {
private _validatePosition(_lineNumber: number, _column: number, validationType: StringOffsetValidationType): Position {
const lineNumber = Math.floor((typeof _lineNumber === 'number' && !isNaN(_lineNumber)) ? _lineNumber : 1);
const column = Math.floor((typeof _column === 'number' && !isNaN(_column)) ? _column : 1);
const lineCount = this._buffer.getLineCount();
@@ -933,10 +943,13 @@ export class TextModel extends Disposable implements model.ITextModel {
return new Position(lineNumber, maxColumn);
}
if (strict) {
const [charStartOffset,] = strings.getCharContainingOffset(this._buffer.getLineContent(lineNumber), column - 1);
if (column !== charStartOffset + 1) {
return new Position(lineNumber, charStartOffset + 1);
if (validationType === StringOffsetValidationType.SurrogatePairs) {
// If the position would end up in the middle of a high-low surrogate pair,
// we move it to before the pair
// !!At this point, column > 1
const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2);
if (strings.isHighSurrogate(charCodeBefore)) {
return new Position(lineNumber, column - 1);
}
}
@@ -944,94 +957,95 @@ export class TextModel extends Disposable implements model.ITextModel {
}
public validatePosition(position: IPosition): Position {
const validationType = StringOffsetValidationType.SurrogatePairs;
this._assertNotDisposed();
// Avoid object allocation and cover most likely case
if (position instanceof Position) {
if (this._isValidPosition(position.lineNumber, position.column, true)) {
if (this._isValidPosition(position.lineNumber, position.column, validationType)) {
return position;
}
}
return this._validatePosition(position.lineNumber, position.column, true);
return this._validatePosition(position.lineNumber, position.column, validationType);
}
/**
* @param strict Do NOT allow a range to have its boundaries inside a high-low surrogate pair
*/
private _isValidRange(range: Range, strict: boolean): boolean {
private _isValidRange(range: Range, validationType: StringOffsetValidationType): boolean {
const startLineNumber = range.startLineNumber;
const startColumn = range.startColumn;
const endLineNumber = range.endLineNumber;
const endColumn = range.endColumn;
if (!this._isValidPosition(startLineNumber, startColumn, false)) {
if (!this._isValidPosition(startLineNumber, startColumn, StringOffsetValidationType.Relaxed)) {
return false;
}
if (!this._isValidPosition(endLineNumber, endColumn, false)) {
if (!this._isValidPosition(endLineNumber, endColumn, StringOffsetValidationType.Relaxed)) {
return false;
}
if (strict) {
const startLineContent = this._buffer.getLineContent(startLineNumber);
if (startColumn < startLineContent.length + 1) {
const [charStartOffset,] = strings.getCharContainingOffset(startLineContent, startColumn - 1);
if (startColumn !== charStartOffset + 1) {
return false;
}
}
if (validationType === StringOffsetValidationType.SurrogatePairs) {
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);
if (endColumn >= 2) {
const endLineContent = (endLineNumber === startLineNumber ? startLineContent : this._buffer.getLineContent(endLineNumber));
const [, charEndOffset] = strings.getCharContainingOffset(endLineContent, endColumn - 2);
if (endColumn !== charEndOffset + 1) {
return false;
}
}
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
return true;
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
return true;
}
return false;
}
return true;
}
public validateRange(_range: IRange): Range {
const validationType = StringOffsetValidationType.SurrogatePairs;
this._assertNotDisposed();
// Avoid object allocation and cover most likely case
if ((_range instanceof Range) && !(_range instanceof Selection)) {
if (this._isValidRange(_range, true)) {
if (this._isValidRange(_range, validationType)) {
return _range;
}
}
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, false);
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, false);
const start = this._validatePosition(_range.startLineNumber, _range.startColumn, StringOffsetValidationType.Relaxed);
const end = this._validatePosition(_range.endLineNumber, _range.endColumn, StringOffsetValidationType.Relaxed);
const startLineNumber = start.lineNumber;
let startColumn = start.column;
const startColumn = start.column;
const endLineNumber = end.lineNumber;
let endColumn = end.column;
const isEmpty = (startLineNumber === endLineNumber && startColumn === endColumn);
const endColumn = end.column;
const startLineContent = this._buffer.getLineContent(startLineNumber);
if (startColumn < startLineContent.length + 1) {
const [charStartOffset,] = strings.getCharContainingOffset(startLineContent, startColumn - 1);
if (startColumn !== charStartOffset + 1) {
if (isEmpty) {
// do not expand a collapsed range, simply move it to a valid location
return new Range(startLineNumber, charStartOffset + 1, startLineNumber, charStartOffset + 1);
}
startColumn = charStartOffset + 1;
}
}
if (validationType === StringOffsetValidationType.SurrogatePairs) {
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);
if (endColumn >= 2) {
const endLineContent = (endLineNumber === startLineNumber ? startLineContent : this._buffer.getLineContent(endLineNumber));
const [, charEndOffset] = strings.getCharContainingOffset(endLineContent, endColumn - 2);
if (endColumn !== charEndOffset + 1) {
endColumn = charEndOffset + 1;
const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart);
const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd);
if (!startInsideSurrogatePair && !endInsideSurrogatePair) {
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);
}
if (startLineNumber === endLineNumber && startColumn === endColumn) {
// do not expand a collapsed range, simply move it to a valid location
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn - 1);
}
if (startInsideSurrogatePair && endInsideSurrogatePair) {
// expand range at both ends
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn + 1);
}
if (startInsideSurrogatePair) {
// only expand range at the start
return new Range(startLineNumber, startColumn - 1, endLineNumber, endColumn);
}
// only expand range at the end
return new Range(startLineNumber, startColumn, endLineNumber, endColumn + 1);
}
return new Range(startLineNumber, startColumn, endLineNumber, endColumn);

View File

@@ -481,6 +481,7 @@ export interface CompletionItem {
export interface CompletionList {
suggestions: CompletionItem[];
incomplete?: boolean;
isDetailsResolved?: boolean;
dispose?(): void;
}
@@ -554,8 +555,8 @@ export interface CodeAction {
/**
* @internal
*/
export const enum CodeActionTrigger {
Automatic = 1,
export const enum CodeActionTriggerType {
Auto = 1,
Manual = 2,
}
@@ -564,7 +565,7 @@ export const enum CodeActionTrigger {
*/
export interface CodeActionContext {
only?: string;
trigger: CodeActionTrigger;
trigger: CodeActionTriggerType;
}
export interface CodeActionList extends IDisposable {
@@ -586,6 +587,11 @@ export interface CodeActionProvider {
* Optional list of CodeActionKinds that this provider returns.
*/
providedCodeActionKinds?: ReadonlyArray<string>;
/**
* @internal
*/
_getAdditionalMenuItems?(context: CodeActionContext, actions: readonly CodeAction[]): Command[];
}
/**
@@ -1234,31 +1240,41 @@ export class FoldingRangeKind {
/**
* @internal
*/
export function isResourceFileEdit(thing: any): thing is ResourceFileEdit {
return isObject(thing) && (Boolean((<ResourceFileEdit>thing).newUri) || Boolean((<ResourceFileEdit>thing).oldUri));
export namespace WorkspaceFileEdit {
/**
* @internal
*/
export function is(thing: any): thing is WorkspaceFileEdit {
return isObject(thing) && (Boolean((<WorkspaceFileEdit>thing).newUri) || Boolean((<WorkspaceFileEdit>thing).oldUri));
}
}
/**
* @internal
*/
export function isResourceTextEdit(thing: any): thing is ResourceTextEdit {
return isObject(thing) && (<ResourceTextEdit>thing).resource && Array.isArray((<ResourceTextEdit>thing).edits);
export namespace WorkspaceTextEdit {
/**
* @internal
*/
export function is(thing: any): thing is WorkspaceTextEdit {
return isObject(thing) && (<WorkspaceTextEdit>thing).resource && Array.isArray((<WorkspaceTextEdit>thing).edits);
}
}
export interface ResourceFileEdit {
export interface WorkspaceFileEdit {
oldUri?: URI;
newUri?: URI;
options?: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean };
}
export interface ResourceTextEdit {
export interface WorkspaceTextEdit {
resource: URI;
modelVersionId?: number;
edits: TextEdit[];
}
export interface WorkspaceEdit {
edits: Array<ResourceTextEdit | ResourceFileEdit>;
edits: Array<WorkspaceTextEdit | WorkspaceFileEdit>;
}
export interface Rejection {
@@ -1274,6 +1290,14 @@ export interface RenameProvider {
resolveRenameLocation?(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult<RenameLocation & Rejection>;
}
/**
* @internal
*/
export interface Session {
id: string;
accessToken: string;
displayName: string;
}
export interface Command {
id: string;
@@ -1486,10 +1510,15 @@ export interface SemanticTokensEdits {
readonly edits: SemanticTokensEdit[];
}
export interface SemanticTokensProvider {
export interface DocumentSemanticTokensProvider {
getLegend(): SemanticTokensLegend;
provideSemanticTokens(model: model.ITextModel, lastResultId: string | null, ranges: Range[] | null, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensEdits>;
releaseSemanticTokens(resultId: string | undefined): void;
provideDocumentSemanticTokens(model: model.ITextModel, lastResultId: string | null, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensEdits>;
releaseDocumentSemanticTokens(resultId: string | undefined): void;
}
export interface DocumentRangeSemanticTokensProvider {
getLegend(): SemanticTokensLegend;
provideDocumentRangeSemanticTokens(model: model.ITextModel, range: Range, token: CancellationToken): ProviderResult<SemanticTokens>;
}
// --- feature registries ------
@@ -1597,7 +1626,12 @@ export const FoldingRangeProviderRegistry = new LanguageFeatureRegistry<FoldingR
/**
* @internal
*/
export const SemanticTokensProviderRegistry = new LanguageFeatureRegistry<SemanticTokensProvider>();
export const DocumentSemanticTokensProviderRegistry = new LanguageFeatureRegistry<DocumentSemanticTokensProvider>();
/**
* @internal
*/
export const DocumentRangeSemanticTokensProviderRegistry = new LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>();
/**
* @internal

View File

@@ -153,7 +153,7 @@ export class LanguageConfigurationChangeEvent {
export class LanguageConfigurationRegistryImpl {
private readonly _entries = new Map<LanguageId, RichEditSupport>();
private readonly _entries = new Map<LanguageId, RichEditSupport | undefined>();
private readonly _onDidChange = new Emitter<LanguageConfigurationChangeEvent>();
public readonly onDidChange: Event<LanguageConfigurationChangeEvent> = this._onDidChange.event;

View File

@@ -13,7 +13,7 @@ import { IRequestHandler } from 'vs/base/common/worker/simpleWorker';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { DiffComputer } from 'vs/editor/common/diff/diffComputer';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IChange } from 'vs/editor/common/editorCommon';
import { EndOfLineSequence, IWordAtPosition } from 'vs/editor/common/model';
import { IModelChangedEvent, MirrorTextModel as BaseMirrorModel } from 'vs/editor/common/model/mirrorTextModel';
import { ensureValidWordDefinition, getWordAtText } from 'vs/editor/common/model/wordHelper';
@@ -322,7 +322,7 @@ export interface IForeignModuleFactory {
(ctx: IWorkerContext, createData: any): any;
}
declare var require: any;
declare const require: any;
/**
* @internal
@@ -419,7 +419,7 @@ export class EditorSimpleWorker implements IRequestHandler, IDisposable {
return true;
}
public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<editorCommon.IChange[] | null> {
public async computeDirtyDiff(originalUrl: string, modifiedUrl: string, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
let original = this._getModel(originalUrl);
let modified = this._getModel(modifiedUrl);
if (!original || !modified) {

View File

@@ -10,7 +10,7 @@ import { SimpleWorkerClient, logOnceWebWorkerWarning, IWorkerClient } from 'vs/b
import { DefaultWorkerFactory } from 'vs/base/worker/defaultWorkerFactory';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IChange } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import * as modes from 'vs/editor/common/modes';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
@@ -90,7 +90,7 @@ export class EditorWorkerServiceImpl extends Disposable implements IEditorWorker
return (canSyncModel(this._modelService, original) && canSyncModel(this._modelService, modified));
}
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<editorCommon.IChange[] | null> {
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
}
@@ -437,7 +437,7 @@ export class EditorWorkerClient extends Disposable {
});
}
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<editorCommon.IChange[] | null> {
public computeDirtyDiff(original: URI, modified: URI, ignoreTrimWhitespace: boolean): Promise<IChange[] | null> {
return this._withSyncedResources([original, modified]).then(proxy => {
return proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
});

View File

@@ -38,7 +38,9 @@ class MarkerDecorations extends Disposable {
}
public update(markers: IMarker[], newDecorations: IModelDeltaDecoration[]): void {
const ids = this.model.deltaDecorations(keys(this._markersData), newDecorations);
const oldIds = keys(this._markersData);
this._markersData.clear();
const ids = this.model.deltaDecorations(oldIds, newDecorations);
for (let index = 0; index < ids.length; index++) {
this._markersData.set(ids[index], markers[index]);
}

View File

@@ -8,13 +8,13 @@ import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecyc
import * as platform from 'vs/base/common/platform';
import * as errors from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { EDITOR_MODEL_DEFAULTS, IEditorSemanticHighlightingOptions } from 'vs/editor/common/config/editorOptions';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { DefaultEndOfLine, EndOfLinePreference, EndOfLineSequence, IIdentifiedSingleEditOperation, ITextBuffer, ITextBufferFactory, ITextModel, ITextModelCreationOptions } from 'vs/editor/common/model';
import { TextModel, createTextBuffer } from 'vs/editor/common/model/textModel';
import { IModelLanguageChangedEvent, IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { LanguageIdentifier, SemanticTokensProviderRegistry, SemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata } from 'vs/editor/common/modes';
import { LanguageIdentifier, DocumentSemanticTokensProviderRegistry, DocumentSemanticTokensProvider, SemanticTokensLegend, SemanticTokens, SemanticTokensEdits, TokenMetadata } from 'vs/editor/common/modes';
import { PLAINTEXT_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/modesRegistry';
import { ILanguageSelection } from 'vs/editor/common/services/modeService';
import { IModelService } from 'vs/editor/common/services/modelService';
@@ -133,7 +133,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
this._configurationServiceSubscription = this._configurationService.onDidChangeConfiguration(e => this._updateModelOptions());
this._updateModelOptions();
this._register(new SemanticColoringFeature(this, themeService, logService));
this._register(new SemanticColoringFeature(this, themeService, configurationService, logService));
}
private static _readModelOptions(config: IRawConfig, isForSimpleWidget: boolean): ITextModelCreationOptions {
@@ -442,42 +442,79 @@ export interface ILineSequence {
}
class SemanticColoringFeature extends Disposable {
private static readonly SETTING_ID = 'editor.semanticHighlighting';
private _watchers: Record<string, ModelSemanticColoring>;
private _semanticStyling: SemanticStyling;
private _configurationService: IConfigurationService;
constructor(modelService: IModelService, themeService: IThemeService, logService: ILogService) {
constructor(modelService: IModelService, themeService: IThemeService, configurationService: IConfigurationService, logService: ILogService) {
super();
this._configurationService = configurationService;
this._watchers = Object.create(null);
this._semanticStyling = this._register(new SemanticStyling(themeService, logService));
this._register(modelService.onModelAdded((model) => {
const isSemanticColoringEnabled = (model: ITextModel) => {
const options = configurationService.getValue<IEditorSemanticHighlightingOptions>(SemanticColoringFeature.SETTING_ID, { overrideIdentifier: model.getLanguageIdentifier().language, resource: model.uri });
return options && options.enabled;
};
const register = (model: ITextModel) => {
this._watchers[model.uri.toString()] = new ModelSemanticColoring(model, themeService, this._semanticStyling);
};
const deregister = (model: ITextModel, modelSemanticColoring: ModelSemanticColoring) => {
modelSemanticColoring.dispose();
delete this._watchers[model.uri.toString()];
};
this._register(modelService.onModelAdded((model) => {
if (isSemanticColoringEnabled(model)) {
register(model);
}
}));
this._register(modelService.onModelRemoved((model) => {
this._watchers[model.uri.toString()].dispose();
delete this._watchers[model.uri.toString()];
const curr = this._watchers[model.uri.toString()];
if (curr) {
deregister(model, curr);
}
}));
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration(SemanticColoringFeature.SETTING_ID)) {
for (let model of modelService.getModels()) {
const curr = this._watchers[model.uri.toString()];
if (isSemanticColoringEnabled(model)) {
if (!curr) {
register(model);
}
} else {
if (curr) {
deregister(model, curr);
}
}
}
}
});
}
}
class SemanticStyling extends Disposable {
private _caches: WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>;
private _caches: WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>;
constructor(
private readonly _themeService: IThemeService,
private readonly _logService: ILogService
) {
super();
this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
if (this._themeService) {
// workaround for tests which use undefined... :/
this._register(this._themeService.onThemeChange(() => {
this._caches = new WeakMap<SemanticTokensProvider, SemanticColoringProviderStyling>();
this._caches = new WeakMap<DocumentSemanticTokensProvider, SemanticColoringProviderStyling>();
}));
}
}
public get(provider: SemanticTokensProvider): SemanticColoringProviderStyling {
public get(provider: DocumentSemanticTokensProvider): SemanticColoringProviderStyling {
if (!this._caches.has(provider)) {
this._caches.set(provider, new SemanticColoringProviderStyling(provider.getLegend(), this._themeService, this._logService));
}
@@ -598,11 +635,12 @@ class SemanticColoringProviderStyling {
} else {
const tokenType = this._legend.tokenTypes[tokenTypeIndex];
const tokenModifiers: string[] = [];
for (let modifierIndex = 0; tokenModifierSet !== 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (tokenModifierSet & 1) {
let modifierSet = tokenModifierSet;
for (let modifierIndex = 0; modifierSet > 0 && modifierIndex < this._legend.tokenModifiers.length; modifierIndex++) {
if (modifierSet & 1) {
tokenModifiers.push(this._legend.tokenModifiers[modifierIndex]);
}
tokenModifierSet = tokenModifierSet >> 1;
modifierSet = modifierSet >> 1;
}
metadata = this._themeService.getTheme().getTokenStyleMetadata(tokenType, tokenModifiers);
@@ -638,13 +676,13 @@ const enum SemanticColoringConstants {
class SemanticTokensResponse {
constructor(
private readonly _provider: SemanticTokensProvider,
private readonly _provider: DocumentSemanticTokensProvider,
public readonly resultId: string | undefined,
public readonly data: Uint32Array
) { }
public dispose(): void {
this._provider.releaseSemanticTokens(this.resultId);
this._provider.releaseDocumentSemanticTokens(this.resultId);
}
}
@@ -672,7 +710,7 @@ class ModelSemanticColoring extends Disposable {
this._fetchSemanticTokens.schedule();
}
}));
this._register(SemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule()));
this._register(DocumentSemanticTokensProviderRegistry.onDidChange(e => this._fetchSemanticTokens.schedule()));
if (themeService) {
// workaround for tests which use undefined... :/
this._register(themeService.onThemeChange(_ => {
@@ -685,7 +723,6 @@ class ModelSemanticColoring extends Disposable {
}
public dispose(): void {
this._isDisposed = true;
if (this._currentResponse) {
this._currentResponse.dispose();
this._currentResponse = null;
@@ -694,6 +731,9 @@ class ModelSemanticColoring extends Disposable {
this._currentRequestCancellationTokenSource.cancel();
this._currentRequestCancellationTokenSource = null;
}
this._setSemanticTokens(null, null, null, []);
this._isDisposed = true;
super.dispose();
}
@@ -716,7 +756,7 @@ class ModelSemanticColoring extends Disposable {
const styling = this._semanticStyling.get(provider);
const lastResultId = this._currentResponse ? this._currentResponse.resultId || null : null;
const request = Promise.resolve(provider.provideSemanticTokens(this._model, lastResultId, null, this._currentRequestCancellationTokenSource.token));
const request = Promise.resolve(provider.provideDocumentSemanticTokens(this._model, lastResultId, this._currentRequestCancellationTokenSource.token));
request.then((res) => {
this._currentRequestCancellationTokenSource = null;
@@ -744,7 +784,7 @@ class ModelSemanticColoring extends Disposable {
}
}
private _setSemanticTokens(provider: SemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
private _setSemanticTokens(provider: DocumentSemanticTokensProvider | null, tokens: SemanticTokens | SemanticTokensEdits | null, styling: SemanticColoringProviderStyling | null, pendingChanges: IModelContentChangedEvent[]): void {
const currentResponse = this._currentResponse;
if (this._currentResponse) {
this._currentResponse.dispose();
@@ -753,7 +793,7 @@ class ModelSemanticColoring extends Disposable {
if (this._isDisposed) {
// disposed!
if (provider && tokens) {
provider.releaseSemanticTokens(tokens.resultId);
provider.releaseDocumentSemanticTokens(tokens.resultId);
}
return;
}
@@ -914,8 +954,8 @@ class ModelSemanticColoring extends Disposable {
this._model.setSemanticTokens(null);
}
private _getSemanticColoringProvider(): SemanticTokensProvider | null {
const result = SemanticTokensProviderRegistry.ordered(this._model);
private _getSemanticColoringProvider(): DocumentSemanticTokensProvider | null {
const result = DocumentSemanticTokensProviderRegistry.ordered(this._model);
return (result.length > 0 ? result[0] : null);
}
}

View File

@@ -53,9 +53,14 @@ export interface ITextEditorModel extends IEditorModel {
createSnapshot(this: ITextEditorModel): ITextSnapshot | null;
/**
* Signals if this model is readonly or not.
* Signals if this model is readonly or not.
*/
isReadonly(): boolean;
/**
* Figure out if this model is resolved or not.
*/
isResolved(): this is IResolvedTextEditorModel;
}
export interface IResolvedTextEditorModel extends ITextEditorModel {

View File

@@ -6,16 +6,329 @@
// THIS IS A GENERATED FILE. DO NOT EDIT DIRECTLY.
export enum MarkerTag {
Unnecessary = 1,
Deprecated = 2
export enum AccessibilitySupport {
/**
* This should be the browser case where it is not known if a screen reader is attached or no.
*/
Unknown = 0,
Disabled = 1,
Enabled = 2
}
export enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8
export enum CompletionItemInsertTextRule {
/**
* Adjust whitespace/indentation of multiline insert texts to
* match the current line indentation.
*/
KeepWhitespace = 1,
/**
* `insertText` is a snippet.
*/
InsertAsSnippet = 4
}
export enum CompletionItemKind {
Method = 0,
Function = 1,
Constructor = 2,
Field = 3,
Variable = 4,
Class = 5,
Struct = 6,
Interface = 7,
Module = 8,
Property = 9,
Event = 10,
Operator = 11,
Unit = 12,
Value = 13,
Constant = 14,
Enum = 15,
EnumMember = 16,
Keyword = 17,
Text = 18,
Color = 19,
File = 20,
Reference = 21,
Customcolor = 22,
Folder = 23,
TypeParameter = 24,
Snippet = 25
}
export enum CompletionItemTag {
Deprecated = 1
}
/**
* How a suggest provider was triggered.
*/
export enum CompletionTriggerKind {
Invoke = 0,
TriggerCharacter = 1,
TriggerForIncompleteCompletions = 2
}
/**
* A positioning preference for rendering content widgets.
*/
export enum ContentWidgetPositionPreference {
/**
* Place the content widget exactly at a position
*/
EXACT = 0,
/**
* Place the content widget above a position
*/
ABOVE = 1,
/**
* Place the content widget below a position
*/
BELOW = 2
}
/**
* Describes the reason the cursor has changed its position.
*/
export enum CursorChangeReason {
/**
* Unknown or not set.
*/
NotSet = 0,
/**
* A `model.setValue()` was called.
*/
ContentFlush = 1,
/**
* The `model` has been changed outside of this cursor and the cursor recovers its position from associated markers.
*/
RecoverFromMarkers = 2,
/**
* There was an explicit user gesture.
*/
Explicit = 3,
/**
* There was a Paste.
*/
Paste = 4,
/**
* There was an Undo.
*/
Undo = 5,
/**
* There was a Redo.
*/
Redo = 6
}
/**
* The default end of line to use when instantiating models.
*/
export enum DefaultEndOfLine {
/**
* Use line feed (\n) as the end of line character.
*/
LF = 1,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 2
}
/**
* A document highlight kind.
*/
export enum DocumentHighlightKind {
/**
* A textual occurrence.
*/
Text = 0,
/**
* Read-access of a symbol, like reading a variable.
*/
Read = 1,
/**
* Write-access of a symbol, like writing to a variable.
*/
Write = 2
}
/**
* Configuration options for auto indentation in the editor
*/
export enum EditorAutoIndentStrategy {
None = 0,
Keep = 1,
Brackets = 2,
Advanced = 3,
Full = 4
}
export enum EditorOption {
acceptSuggestionOnCommitCharacter = 0,
acceptSuggestionOnEnter = 1,
accessibilitySupport = 2,
accessibilityPageSize = 3,
ariaLabel = 4,
autoClosingBrackets = 5,
autoClosingOvertype = 6,
autoClosingQuotes = 7,
autoIndent = 8,
automaticLayout = 9,
autoSurround = 10,
codeLens = 11,
colorDecorators = 12,
contextmenu = 13,
copyWithSyntaxHighlighting = 14,
cursorBlinking = 15,
cursorSmoothCaretAnimation = 16,
cursorStyle = 17,
cursorSurroundingLines = 18,
cursorSurroundingLinesStyle = 19,
cursorWidth = 20,
disableLayerHinting = 21,
disableMonospaceOptimizations = 22,
dragAndDrop = 23,
emptySelectionClipboard = 24,
extraEditorClassName = 25,
fastScrollSensitivity = 26,
find = 27,
fixedOverflowWidgets = 28,
folding = 29,
foldingStrategy = 30,
foldingHighlight = 31,
fontFamily = 32,
fontInfo = 33,
fontLigatures = 34,
fontSize = 35,
fontWeight = 36,
formatOnPaste = 37,
formatOnType = 38,
glyphMargin = 39,
gotoLocation = 40,
hideCursorInOverviewRuler = 41,
highlightActiveIndentGuide = 42,
hover = 43,
inDiffEditor = 44,
letterSpacing = 45,
lightbulb = 46,
lineDecorationsWidth = 47,
lineHeight = 48,
lineNumbers = 49,
lineNumbersMinChars = 50,
links = 51,
matchBrackets = 52,
minimap = 53,
mouseStyle = 54,
mouseWheelScrollSensitivity = 55,
mouseWheelZoom = 56,
multiCursorMergeOverlapping = 57,
multiCursorModifier = 58,
multiCursorPaste = 59,
occurrencesHighlight = 60,
overviewRulerBorder = 61,
overviewRulerLanes = 62,
parameterHints = 63,
peekWidgetFocusInlineEditor = 64,
quickSuggestions = 65,
quickSuggestionsDelay = 66,
readOnly = 67,
renderControlCharacters = 68,
renderIndentGuides = 69,
renderFinalNewline = 70,
renderLineHighlight = 71,
renderWhitespace = 72,
revealHorizontalRightPadding = 73,
roundedSelection = 74,
rulers = 75,
scrollbar = 76,
scrollBeyondLastColumn = 77,
scrollBeyondLastLine = 78,
selectionClipboard = 79,
selectionHighlight = 80,
selectOnLineNumbers = 81,
semanticHighlighting = 82,
showFoldingControls = 83,
showUnused = 84,
snippetSuggestions = 85,
smoothScrolling = 86,
stopRenderingLineAfter = 87,
suggest = 88,
suggestFontSize = 89,
suggestLineHeight = 90,
suggestOnTriggerCharacters = 91,
suggestSelection = 92,
tabCompletion = 93,
useTabStops = 94,
wordSeparators = 95,
wordWrap = 96,
wordWrapBreakAfterCharacters = 97,
wordWrapBreakBeforeCharacters = 98,
wordWrapColumn = 99,
wordWrapMinified = 100,
wrappingIndent = 101,
wrappingAlgorithm = 102,
editorClassName = 103,
pixelRatio = 104,
tabFocusMode = 105,
layoutInfo = 106,
wrappingInfo = 107
}
/**
* End of line character preference.
*/
export enum EndOfLinePreference {
/**
* Use the end of line character identified in the text buffer.
*/
TextDefined = 0,
/**
* Use line feed (\n) as the end of line character.
*/
LF = 1,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 2
}
/**
* End of line character preference.
*/
export enum EndOfLineSequence {
/**
* Use line feed (\n) as the end of line character.
*/
LF = 0,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 1
}
/**
* Describes what to do with the indentation when pressing Enter.
*/
export enum IndentAction {
/**
* Insert new line and copy the previous line's indentation.
*/
None = 0,
/**
* Insert new line and indent once (relative to the previous line's indentation).
*/
Indent = 1,
/**
* Insert two new lines:
* - the first one indented which will hold the cursor
* - the second one at the same indentation level
*/
IndentOutdent = 2,
/**
* Insert new line and outdent once (relative to the previous line's indentation).
*/
Outdent = 3
}
/**
@@ -199,34 +512,16 @@ export enum KeyCode {
MAX_VALUE = 112
}
/**
* The direction of a selection.
*/
export enum SelectionDirection {
/**
* The selection starts above where it ends.
*/
LTR = 0,
/**
* The selection starts below where it ends.
*/
RTL = 1
export enum MarkerSeverity {
Hint = 1,
Info = 2,
Warning = 4,
Error = 8
}
export enum ScrollbarVisibility {
Auto = 1,
Hidden = 2,
Visible = 3
}
/**
* Vertical Lane in the overview ruler of the editor.
*/
export enum OverviewRulerLane {
Left = 1,
Center = 2,
Right = 4,
Full = 7
export enum MarkerTag {
Unnecessary = 1,
Deprecated = 2
}
/**
@@ -237,144 +532,6 @@ export enum MinimapPosition {
Gutter = 2
}
/**
* End of line character preference.
*/
export enum EndOfLinePreference {
/**
* Use the end of line character identified in the text buffer.
*/
TextDefined = 0,
/**
* Use line feed (\n) as the end of line character.
*/
LF = 1,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 2
}
/**
* The default end of line to use when instantiating models.
*/
export enum DefaultEndOfLine {
/**
* Use line feed (\n) as the end of line character.
*/
LF = 1,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 2
}
/**
* End of line character preference.
*/
export enum EndOfLineSequence {
/**
* Use line feed (\n) as the end of line character.
*/
LF = 0,
/**
* Use carriage return and line feed (\r\n) as the end of line character.
*/
CRLF = 1
}
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
*/
export enum TrackedRangeStickiness {
AlwaysGrowsWhenTypingAtEdges = 0,
NeverGrowsWhenTypingAtEdges = 1,
GrowsOnlyWhenTypingBefore = 2,
GrowsOnlyWhenTypingAfter = 3
}
export enum ScrollType {
Smooth = 0,
Immediate = 1
}
/**
* Describes the reason the cursor has changed its position.
*/
export enum CursorChangeReason {
/**
* Unknown or not set.
*/
NotSet = 0,
/**
* A `model.setValue()` was called.
*/
ContentFlush = 1,
/**
* The `model` has been changed outside of this cursor and the cursor recovers its position from associated markers.
*/
RecoverFromMarkers = 2,
/**
* There was an explicit user gesture.
*/
Explicit = 3,
/**
* There was a Paste.
*/
Paste = 4,
/**
* There was an Undo.
*/
Undo = 5,
/**
* There was a Redo.
*/
Redo = 6
}
export enum RenderMinimap {
None = 0,
Text = 1,
Blocks = 2
}
/**
* A positioning preference for rendering content widgets.
*/
export enum ContentWidgetPositionPreference {
/**
* Place the content widget exactly at a position
*/
EXACT = 0,
/**
* Place the content widget above a position
*/
ABOVE = 1,
/**
* Place the content widget below a position
*/
BELOW = 2
}
/**
* A positioning preference for rendering overlay widgets.
*/
export enum OverlayWidgetPositionPreference {
/**
* Position the overlay widget in the top right corner
*/
TOP_RIGHT_CORNER = 0,
/**
* Position the overlay widget in the bottom right corner
*/
BOTTOM_RIGHT_CORNER = 1,
/**
* Position the overlay widget in the top center
*/
TOP_CENTER = 2
}
/**
* Type of hit element with the mouse in the editor.
*/
@@ -438,81 +595,70 @@ export enum MouseTargetType {
}
/**
* Describes what to do with the indentation when pressing Enter.
* A positioning preference for rendering overlay widgets.
*/
export enum IndentAction {
export enum OverlayWidgetPositionPreference {
/**
* Insert new line and copy the previous line's indentation.
* Position the overlay widget in the top right corner
*/
None = 0,
TOP_RIGHT_CORNER = 0,
/**
* Insert new line and indent once (relative to the previous line's indentation).
* Position the overlay widget in the bottom right corner
*/
Indent = 1,
BOTTOM_RIGHT_CORNER = 1,
/**
* Insert two new lines:
* - the first one indented which will hold the cursor
* - the second one at the same indentation level
* Position the overlay widget in the top center
*/
IndentOutdent = 2,
/**
* Insert new line and outdent once (relative to the previous line's indentation).
*/
Outdent = 3
}
export enum CompletionItemKind {
Method = 0,
Function = 1,
Constructor = 2,
Field = 3,
Variable = 4,
Class = 5,
Struct = 6,
Interface = 7,
Module = 8,
Property = 9,
Event = 10,
Operator = 11,
Unit = 12,
Value = 13,
Constant = 14,
Enum = 15,
EnumMember = 16,
Keyword = 17,
Text = 18,
Color = 19,
File = 20,
Reference = 21,
Customcolor = 22,
Folder = 23,
TypeParameter = 24,
Snippet = 25
}
export enum CompletionItemTag {
Deprecated = 1
}
export enum CompletionItemInsertTextRule {
/**
* Adjust whitespace/indentation of multiline insert texts to
* match the current line indentation.
*/
KeepWhitespace = 1,
/**
* `insertText` is a snippet.
*/
InsertAsSnippet = 4
TOP_CENTER = 2
}
/**
* How a suggest provider was triggered.
* Vertical Lane in the overview ruler of the editor.
*/
export enum CompletionTriggerKind {
Invoke = 0,
TriggerCharacter = 1,
TriggerForIncompleteCompletions = 2
export enum OverviewRulerLane {
Left = 1,
Center = 2,
Right = 4,
Full = 7
}
export enum RenderLineNumbersType {
Off = 0,
On = 1,
Relative = 2,
Interval = 3,
Custom = 4
}
export enum RenderMinimap {
None = 0,
Text = 1,
Blocks = 2
}
export enum ScrollType {
Smooth = 0,
Immediate = 1
}
export enum ScrollbarVisibility {
Auto = 1,
Hidden = 2,
Visible = 3
}
/**
* The direction of a selection.
*/
export enum SelectionDirection {
/**
* The selection starts above where it ends.
*/
LTR = 0,
/**
* The selection starts below where it ends.
*/
RTL = 1
}
export enum SignatureHelpTriggerKind {
@@ -521,24 +667,6 @@ export enum SignatureHelpTriggerKind {
ContentChange = 3
}
/**
* A document highlight kind.
*/
export enum DocumentHighlightKind {
/**
* A textual occurrence.
*/
Text = 0,
/**
* Read-access of a symbol, like reading a variable.
*/
Read = 1,
/**
* Write-access of a symbol, like writing to a variable.
*/
Write = 2
}
/**
* A symbol kind.
*/
@@ -573,4 +701,97 @@ export enum SymbolKind {
export enum SymbolTag {
Deprecated = 1
}
/**
* The kind of animation in which the editor's cursor should be rendered.
*/
export enum TextEditorCursorBlinkingStyle {
/**
* Hidden
*/
Hidden = 0,
/**
* Blinking
*/
Blink = 1,
/**
* Blinking with smooth fading
*/
Smooth = 2,
/**
* Blinking with prolonged filled state and smooth fading
*/
Phase = 3,
/**
* Expand collapse animation on the y axis
*/
Expand = 4,
/**
* No-Blinking
*/
Solid = 5
}
/**
* The style in which the editor's cursor should be rendered.
*/
export enum TextEditorCursorStyle {
/**
* As a vertical line (sitting between two characters).
*/
Line = 1,
/**
* As a block (sitting on top of a character).
*/
Block = 2,
/**
* As a horizontal line (sitting under a character).
*/
Underline = 3,
/**
* As a thin vertical line (sitting between two characters).
*/
LineThin = 4,
/**
* As an outlined block (sitting on top of a character).
*/
BlockOutline = 5,
/**
* As a thin horizontal line (sitting under a character).
*/
UnderlineThin = 6
}
/**
* Describes the behavior of decorations when typing/editing near their edges.
* Note: Please do not edit the values, as they very carefully match `DecorationRangeBehavior`
*/
export enum TrackedRangeStickiness {
AlwaysGrowsWhenTypingAtEdges = 0,
NeverGrowsWhenTypingAtEdges = 1,
GrowsOnlyWhenTypingBefore = 2,
GrowsOnlyWhenTypingAfter = 3
}
/**
* Describes how to indent wrapped lines.
*/
export enum WrappingIndent {
/**
* No indentation => wrapped lines begin at column 1.
*/
None = 0,
/**
* Same => wrapped lines get the same indentation as the parent.
*/
Same = 1,
/**
* Indent => wrapped lines get +1 indentation toward the parent.
*/
Indent = 2,
/**
* DeepIndent => wrapped lines get +2 indentation toward the parent.
*/
DeepIndent = 3
}

View File

@@ -9,25 +9,26 @@ import { ScrollEvent } from 'vs/base/common/scrollable';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import { ScrollType } from 'vs/editor/common/editorCommon';
import { ScrollType, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
export const enum ViewEventType {
ViewConfigurationChanged = 1,
ViewCursorStateChanged = 2,
ViewDecorationsChanged = 3,
ViewFlushed = 4,
ViewFocusChanged = 5,
ViewLineMappingChanged = 6,
ViewLinesChanged = 7,
ViewLinesDeleted = 8,
ViewLinesInserted = 9,
ViewRevealRangeRequest = 10,
ViewScrollChanged = 11,
ViewTokensChanged = 12,
ViewTokensColorsChanged = 13,
ViewZonesChanged = 14,
ViewThemeChanged = 15,
ViewLanguageConfigurationChanged = 16
ViewContentSizeChanged = 2,
ViewCursorStateChanged = 3,
ViewDecorationsChanged = 4,
ViewFlushed = 5,
ViewFocusChanged = 6,
ViewLanguageConfigurationChanged = 7,
ViewLineMappingChanged = 8,
ViewLinesChanged = 9,
ViewLinesDeleted = 10,
ViewLinesInserted = 11,
ViewRevealRangeRequest = 12,
ViewScrollChanged = 13,
ViewThemeChanged = 14,
ViewTokensChanged = 15,
ViewTokensColorsChanged = 16,
ViewZonesChanged = 17,
}
export class ViewConfigurationChangedEvent {
@@ -45,6 +46,25 @@ export class ViewConfigurationChangedEvent {
}
}
export class ViewContentSizeChangedEvent implements IContentSizeChangedEvent {
public readonly type = ViewEventType.ViewContentSizeChanged;
public readonly contentWidth: number;
public readonly contentHeight: number;
public readonly contentWidthChanged: boolean;
public readonly contentHeightChanged: boolean;
constructor(source: IContentSizeChangedEvent) {
this.contentWidth = source.contentWidth;
this.contentHeight = source.contentHeight;
this.contentWidthChanged = source.contentWidthChanged;
this.contentHeightChanged = source.contentHeightChanged;
}
}
export class ViewCursorStateChangedEvent {
public readonly type = ViewEventType.ViewCursorStateChanged;
@@ -88,6 +108,11 @@ export class ViewFocusChangedEvent {
}
}
export class ViewLanguageConfigurationEvent {
public readonly type = ViewEventType.ViewLanguageConfigurationChanged;
}
export class ViewLineMappingChangedEvent {
public readonly type = ViewEventType.ViewLineMappingChanged;
@@ -221,6 +246,11 @@ export class ViewScrollChangedEvent {
}
}
export class ViewThemeChangedEvent {
public readonly type = ViewEventType.ViewThemeChanged;
}
export class ViewTokensChangedEvent {
public readonly type = ViewEventType.ViewTokensChanged;
@@ -241,11 +271,6 @@ export class ViewTokensChangedEvent {
}
}
export class ViewThemeChangedEvent {
public readonly type = ViewEventType.ViewThemeChanged;
}
export class ViewTokensColorsChangedEvent {
public readonly type = ViewEventType.ViewTokensColorsChanged;
@@ -264,28 +289,24 @@ export class ViewZonesChangedEvent {
}
}
export class ViewLanguageConfigurationEvent {
public readonly type = ViewEventType.ViewLanguageConfigurationChanged;
}
export type ViewEvent = (
ViewConfigurationChangedEvent
| ViewContentSizeChangedEvent
| ViewCursorStateChangedEvent
| ViewDecorationsChangedEvent
| ViewFlushedEvent
| ViewFocusChangedEvent
| ViewLinesChangedEvent
| ViewLanguageConfigurationEvent
| ViewLineMappingChangedEvent
| ViewLinesChangedEvent
| ViewLinesDeletedEvent
| ViewLinesInsertedEvent
| ViewRevealRangeRequestEvent
| ViewScrollChangedEvent
| ViewThemeChangedEvent
| ViewTokensChangedEvent
| ViewTokensColorsChangedEvent
| ViewZonesChangedEvent
| ViewThemeChangedEvent
| ViewLanguageConfigurationEvent
);
export interface IViewEventListener {

View File

@@ -3,26 +3,159 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility, INewScrollPosition } from 'vs/base/common/scrollable';
import { ConfigurationChangedEvent, EditorOption } from 'vs/editor/common/config/editorOptions';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IConfiguration, IContentSizeChangedEvent } from 'vs/editor/common/editorCommon';
import { LinesLayout, IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IViewLayout, IViewWhitespaceViewportData, Viewport } from 'vs/editor/common/viewModel/viewModel';
const SMOOTH_SCROLLING_TIME = 125;
export class ViewLayout extends Disposable implements IViewLayout {
class EditorScrollDimensions {
private readonly _configuration: editorCommon.IConfiguration;
private readonly _linesLayout: LinesLayout;
public readonly width: number;
public readonly contentWidth: number;
public readonly scrollWidth: number;
public readonly height: number;
public readonly contentHeight: number;
public readonly scrollHeight: number;
constructor(
width: number,
contentWidth: number,
height: number,
contentHeight: number,
) {
width = width | 0;
contentWidth = contentWidth | 0;
height = height | 0;
contentHeight = contentHeight | 0;
if (width < 0) {
width = 0;
}
if (contentWidth < 0) {
contentWidth = 0;
}
if (height < 0) {
height = 0;
}
if (contentHeight < 0) {
contentHeight = 0;
}
this.width = width;
this.contentWidth = contentWidth;
this.scrollWidth = Math.max(width, contentWidth);
this.height = height;
this.contentHeight = contentHeight;
this.scrollHeight = Math.max(height, contentHeight);
}
public equals(other: EditorScrollDimensions): boolean {
return (
this.width === other.width
&& this.contentWidth === other.contentWidth
&& this.height === other.height
&& this.contentHeight === other.contentHeight
);
}
}
class EditorScrollable extends Disposable {
private readonly _scrollable: Scrollable;
private _dimensions: EditorScrollDimensions;
public readonly scrollable: Scrollable;
public readonly onDidScroll: Event<ScrollEvent>;
constructor(configuration: editorCommon.IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
private readonly _onDidContentSizeChange = this._register(new Emitter<IContentSizeChangedEvent>());
public readonly onDidContentSizeChange: Event<IContentSizeChangedEvent> = this._onDidContentSizeChange.event;
constructor(smoothScrollDuration: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
super();
this._dimensions = new EditorScrollDimensions(0, 0, 0, 0);
this._scrollable = this._register(new Scrollable(smoothScrollDuration, scheduleAtNextAnimationFrame));
this.onDidScroll = this._scrollable.onScroll;
}
public getScrollable(): Scrollable {
return this._scrollable;
}
public setSmoothScrollDuration(smoothScrollDuration: number): void {
this._scrollable.setSmoothScrollDuration(smoothScrollDuration);
}
public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {
return this._scrollable.validateScrollPosition(scrollPosition);
}
public getScrollDimensions(): EditorScrollDimensions {
return this._dimensions;
}
public setScrollDimensions(dimensions: EditorScrollDimensions): void {
if (this._dimensions.equals(dimensions)) {
return;
}
const oldDimensions = this._dimensions;
this._dimensions = dimensions;
this._scrollable.setScrollDimensions({
width: dimensions.width,
scrollWidth: dimensions.scrollWidth,
height: dimensions.height,
scrollHeight: dimensions.scrollHeight
});
const contentWidthChanged = (oldDimensions.contentWidth !== dimensions.contentWidth);
const contentHeightChanged = (oldDimensions.contentHeight !== dimensions.contentHeight);
if (contentWidthChanged || contentHeightChanged) {
this._onDidContentSizeChange.fire({
contentWidth: dimensions.contentWidth,
contentHeight: dimensions.contentHeight,
contentWidthChanged: contentWidthChanged,
contentHeightChanged: contentHeightChanged
});
}
}
public getFutureScrollPosition(): IScrollPosition {
return this._scrollable.getFutureScrollPosition();
}
public getCurrentScrollPosition(): IScrollPosition {
return this._scrollable.getCurrentScrollPosition();
}
public setScrollPositionNow(update: INewScrollPosition): void {
this._scrollable.setScrollPositionNow(update);
}
public setScrollPositionSmooth(update: INewScrollPosition): void {
this._scrollable.setScrollPositionSmooth(update);
}
}
export class ViewLayout extends Disposable implements IViewLayout {
private readonly _configuration: IConfiguration;
private readonly _linesLayout: LinesLayout;
private readonly _scrollable: EditorScrollable;
public readonly onDidScroll: Event<ScrollEvent>;
public readonly onDidContentSizeChange: Event<IContentSizeChangedEvent>;
constructor(configuration: IConfiguration, lineCount: number, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
super();
this._configuration = configuration;
@@ -31,14 +164,17 @@ export class ViewLayout extends Disposable implements IViewLayout {
this._linesLayout = new LinesLayout(lineCount, options.get(EditorOption.lineHeight));
this.scrollable = this._register(new Scrollable(0, scheduleAtNextAnimationFrame));
this._scrollable = this._register(new EditorScrollable(0, scheduleAtNextAnimationFrame));
this._configureSmoothScrollDuration();
this.scrollable.setScrollDimensions({
width: layoutInfo.contentWidth,
height: layoutInfo.contentHeight
});
this.onDidScroll = this.scrollable.onScroll;
this._scrollable.setScrollDimensions(new EditorScrollDimensions(
layoutInfo.contentWidth,
0,
layoutInfo.height,
0
));
this.onDidScroll = this._scrollable.onDidScroll;
this.onDidContentSizeChange = this._scrollable.onDidContentSizeChange;
this._updateHeight();
}
@@ -47,12 +183,16 @@ export class ViewLayout extends Disposable implements IViewLayout {
super.dispose();
}
public getScrollable(): Scrollable {
return this._scrollable.getScrollable();
}
public onHeightMaybeChanged(): void {
this._updateHeight();
}
private _configureSmoothScrollDuration(): void {
this.scrollable.setSmoothScrollDuration(this._configuration.options.get(EditorOption.smoothScrolling) ? SMOOTH_SCROLLING_TIME : 0);
this._scrollable.setSmoothScrollDuration(this._configuration.options.get(EditorOption.smoothScrolling) ? SMOOTH_SCROLLING_TIME : 0);
}
// ---- begin view event handlers
@@ -65,16 +205,15 @@ export class ViewLayout extends Disposable implements IViewLayout {
if (e.hasChanged(EditorOption.layoutInfo)) {
const layoutInfo = options.get(EditorOption.layoutInfo);
const width = layoutInfo.contentWidth;
const height = layoutInfo.contentHeight;
const scrollDimensions = this.scrollable.getScrollDimensions();
const height = layoutInfo.height;
const scrollDimensions = this._scrollable.getScrollDimensions();
const scrollWidth = scrollDimensions.scrollWidth;
const scrollHeight = this._getTotalHeight(width, height, scrollWidth);
this.scrollable.setScrollDimensions({
width: width,
height: height,
scrollHeight: scrollHeight
});
this._scrollable.setScrollDimensions(new EditorScrollDimensions(
width,
scrollDimensions.contentWidth,
height,
this._getContentHeight(width, height, scrollWidth)
));
} else {
this._updateHeight();
}
@@ -108,7 +247,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
return scrollbar.horizontalScrollbarSize;
}
private _getTotalHeight(width: number, height: number, scrollWidth: number): number {
private _getContentHeight(width: number, height: number, scrollWidth: number): number {
const options = this._configuration.options;
let result = this._linesLayout.getLinesTotalHeight();
@@ -118,25 +257,27 @@ export class ViewLayout extends Disposable implements IViewLayout {
result += this._getHorizontalScrollbarHeight(width, scrollWidth);
}
return Math.max(height, result);
return result;
}
private _updateHeight(): void {
const scrollDimensions = this.scrollable.getScrollDimensions();
const scrollDimensions = this._scrollable.getScrollDimensions();
const width = scrollDimensions.width;
const height = scrollDimensions.height;
const scrollWidth = scrollDimensions.scrollWidth;
const scrollHeight = this._getTotalHeight(width, height, scrollWidth);
this.scrollable.setScrollDimensions({
scrollHeight: scrollHeight
});
this._scrollable.setScrollDimensions(new EditorScrollDimensions(
width,
scrollDimensions.contentWidth,
height,
this._getContentHeight(width, height, scrollWidth)
));
}
// ---- Layouting logic
public getCurrentViewport(): Viewport {
const scrollDimensions = this.scrollable.getScrollDimensions();
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
const scrollDimensions = this._scrollable.getScrollDimensions();
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return new Viewport(
currentScrollPosition.scrollTop,
currentScrollPosition.scrollLeft,
@@ -146,8 +287,8 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
public getFutureViewport(): Viewport {
const scrollDimensions = this.scrollable.getScrollDimensions();
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
const scrollDimensions = this._scrollable.getScrollDimensions();
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
return new Viewport(
currentScrollPosition.scrollTop,
currentScrollPosition.scrollLeft,
@@ -156,23 +297,27 @@ export class ViewLayout extends Disposable implements IViewLayout {
);
}
private _computeScrollWidth(maxLineWidth: number, viewportWidth: number): number {
private _computeContentWidth(maxLineWidth: number): number {
const options = this._configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
let isViewportWrapping = wrappingInfo.isViewportWrapping;
if (!isViewportWrapping) {
const extraHorizontalSpace = options.get(EditorOption.scrollBeyondLastColumn) * options.get(EditorOption.fontInfo).typicalHalfwidthCharacterWidth;
const whitespaceMinWidth = this._linesLayout.getWhitespaceMinWidth();
return Math.max(maxLineWidth + extraHorizontalSpace, viewportWidth, whitespaceMinWidth);
return Math.max(maxLineWidth + extraHorizontalSpace, whitespaceMinWidth);
}
return Math.max(maxLineWidth, viewportWidth);
return maxLineWidth;
}
public onMaxLineWidthChanged(maxLineWidth: number): void {
let newScrollWidth = this._computeScrollWidth(maxLineWidth, this.getCurrentViewport().width);
this.scrollable.setScrollDimensions({
scrollWidth: newScrollWidth
});
const scrollDimensions = this._scrollable.getScrollDimensions();
// const newScrollWidth = ;
this._scrollable.setScrollDimensions(new EditorScrollDimensions(
scrollDimensions.width,
this._computeContentWidth(maxLineWidth),
scrollDimensions.height,
scrollDimensions.contentHeight
));
// The height might depend on the fact that there is a horizontal scrollbar or not
this._updateHeight();
@@ -181,7 +326,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- view state
public saveState(): { scrollTop: number; scrollTopWithoutViewZones: number; scrollLeft: number; } {
const currentScrollPosition = this.scrollable.getFutureScrollPosition();
const currentScrollPosition = this._scrollable.getFutureScrollPosition();
let scrollTop = currentScrollPosition.scrollTop;
let firstLineNumberInViewport = this._linesLayout.getLineNumberAtOrAfterVerticalOffset(scrollTop);
let whitespaceAboveFirstLine = this._linesLayout.getWhitespaceAccumulatedHeightBeforeLineNumber(firstLineNumberInViewport);
@@ -215,7 +360,7 @@ export class ViewLayout extends Disposable implements IViewLayout {
}
public getLinesViewportDataAtScrollTop(scrollTop: number): IPartialViewLinesViewportData {
// do some minimal validations on scrollTop
const scrollDimensions = this.scrollable.getScrollDimensions();
const scrollDimensions = this._scrollable.getScrollDimensions();
if (scrollTop + scrollDimensions.height > scrollDimensions.scrollHeight) {
scrollTop = scrollDimensions.scrollHeight - scrollDimensions.height;
}
@@ -234,40 +379,47 @@ export class ViewLayout extends Disposable implements IViewLayout {
// ---- IScrollingProvider
public getContentWidth(): number {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.contentWidth;
}
public getScrollWidth(): number {
const scrollDimensions = this.scrollable.getScrollDimensions();
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.scrollWidth;
}
public getContentHeight(): number {
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.contentHeight;
}
public getScrollHeight(): number {
const scrollDimensions = this.scrollable.getScrollDimensions();
const scrollDimensions = this._scrollable.getScrollDimensions();
return scrollDimensions.scrollHeight;
}
public getCurrentScrollLeft(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollLeft;
}
public getCurrentScrollTop(): number {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
return currentScrollPosition.scrollTop;
}
public validateScrollPosition(scrollPosition: editorCommon.INewScrollPosition): IScrollPosition {
return this.scrollable.validateScrollPosition(scrollPosition);
public validateScrollPosition(scrollPosition: INewScrollPosition): IScrollPosition {
return this._scrollable.validateScrollPosition(scrollPosition);
}
public setScrollPositionNow(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionNow(position);
public setScrollPositionNow(position: INewScrollPosition): void {
this._scrollable.setScrollPositionNow(position);
}
public setScrollPositionSmooth(position: editorCommon.INewScrollPosition): void {
this.scrollable.setScrollPositionSmooth(position);
public setScrollPositionSmooth(position: INewScrollPosition): void {
this._scrollable.setScrollPositionSmooth(position);
}
public deltaScrollNow(deltaScrollLeft: number, deltaScrollTop: number): void {
const currentScrollPosition = this.scrollable.getCurrentScrollPosition();
this.scrollable.setScrollPositionNow({
const currentScrollPosition = this._scrollable.getCurrentScrollPosition();
this._scrollable.setScrollPositionNow({
scrollLeft: currentScrollPosition.scrollLeft + deltaScrollLeft,
scrollTop: currentScrollPosition.scrollTop + deltaScrollTop
});

View File

@@ -66,6 +66,7 @@ export class RenderLineInput {
public readonly lineTokens: IViewLineTokens;
public readonly lineDecorations: LineDecoration[];
public readonly tabSize: number;
public readonly startVisibleColumn: number;
public readonly spaceWidth: number;
public readonly stopRenderingLineAfter: number;
public readonly renderWhitespace: RenderWhitespace;
@@ -89,6 +90,7 @@ export class RenderLineInput {
lineTokens: IViewLineTokens,
lineDecorations: LineDecoration[],
tabSize: number,
startVisibleColumn: number,
spaceWidth: number,
stopRenderingLineAfter: number,
renderWhitespace: 'none' | 'boundary' | 'selection' | 'all',
@@ -106,6 +108,7 @@ export class RenderLineInput {
this.lineTokens = lineTokens;
this.lineDecorations = lineDecorations;
this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
this.spaceWidth = spaceWidth;
this.stopRenderingLineAfter = stopRenderingLineAfter;
this.renderWhitespace = (
@@ -154,6 +157,7 @@ export class RenderLineInput {
&& this.containsRTL === other.containsRTL
&& this.fauxIndentLength === other.fauxIndentLength
&& this.tabSize === other.tabSize
&& this.startVisibleColumn === other.startVisibleColumn
&& this.spaceWidth === other.spaceWidth
&& this.stopRenderingLineAfter === other.stopRenderingLineAfter
&& this.renderWhitespace === other.renderWhitespace
@@ -314,21 +318,24 @@ export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): Rend
if (input.lineDecorations.length > 0) {
// This line is empty, but it contains inline decorations
let classNames: string[] = [];
const beforeClassNames: string[] = [];
const afterClassNames: string[] = [];
for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
const lineDecoration = input.lineDecorations[i];
if (lineDecoration.type === InlineDecorationType.Before) {
classNames.push(input.lineDecorations[i].className);
beforeClassNames.push(input.lineDecorations[i].className);
containsForeignElements |= ForeignElementType.Before;
}
if (lineDecoration.type === InlineDecorationType.After) {
classNames.push(input.lineDecorations[i].className);
afterClassNames.push(input.lineDecorations[i].className);
containsForeignElements |= ForeignElementType.After;
}
}
if (containsForeignElements !== ForeignElementType.None) {
content = `<span><span class="${classNames.join(' ')}"></span></span>`;
const beforeSpan = (beforeClassNames.length > 0 ? `<span class="${beforeClassNames.join(' ')}"></span>` : ``);
const afterSpan = (afterClassNames.length > 0 ? `<span class="${afterClassNames.join(' ')}"></span>` : ``);
content = `<span>${beforeSpan}${afterSpan}</span>`;
}
}
@@ -368,7 +375,9 @@ class ResolvedRenderLineInput {
public readonly isOverflowing: boolean,
public readonly parts: LinePart[],
public readonly containsForeignElements: ForeignElementType,
public readonly fauxIndentLength: number,
public readonly tabSize: number,
public readonly startVisibleColumn: number,
public readonly containsRTL: boolean,
public readonly spaceWidth: number,
public readonly renderWhitespace: RenderWhitespace,
@@ -395,7 +404,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
if (input.renderWhitespace === RenderWhitespace.All || input.renderWhitespace === RenderWhitespace.Boundary || (input.renderWhitespace === RenderWhitespace.Selection && !!input.selectionsOnLine)) {
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
tokens = _applyRenderWhitespace(lineContent, len, input.continuesWithWrappedLine, tokens, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, useMonospaceOptimizations, input.selectionsOnLine, input.renderWhitespace === RenderWhitespace.Boundary);
}
let containsForeignElements = ForeignElementType.None;
if (input.lineDecorations.length > 0) {
@@ -425,7 +434,9 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput
isOverflowing,
tokens,
containsForeignElements,
input.fauxIndentLength,
input.tabSize,
input.startVisibleColumn,
input.containsRTL,
input.spaceWidth,
input.renderWhitespace,
@@ -537,7 +548,7 @@ function splitLargeTokens(lineContent: string, tokens: LinePart[], onlyAtSpaces:
* Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (&rarr; or &middot;) do not have the same width as &nbsp;.
* The rendering phase will generate `style="width:..."` for these tokens.
*/
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
function _applyRenderWhitespace(lineContent: string, len: number, continuesWithWrappedLine: boolean, tokens: LinePart[], fauxIndentLength: number, tabSize: number, startVisibleColumn: number, useMonospaceOptimizations: boolean, selections: LineRange[] | null, onlyBoundary: boolean): LinePart[] {
let result: LinePart[] = [], resultLen = 0;
let tokenIndex = 0;
@@ -555,21 +566,10 @@ function _applyRenderWhitespace(lineContent: string, len: number, continuesWithW
lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
}
let tmpIndent = 0;
for (let charIndex = 0; charIndex < fauxIndentLength; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
if (chCode === CharCode.Tab) {
tmpIndent = tabSize;
} else if (strings.isFullWidthCharacter(chCode)) {
tmpIndent += 2;
} else {
tmpIndent++;
}
}
tmpIndent = tmpIndent % tabSize;
let wasInWhitespace = false;
let currentSelectionIndex = 0;
let currentSelection = selections && selections[currentSelectionIndex];
let tmpIndent = startVisibleColumn % tabSize;
for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
const chCode = lineContent.charCodeAt(charIndex);
@@ -729,7 +729,9 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const len = input.len;
const isOverflowing = input.isOverflowing;
const parts = input.parts;
const fauxIndentLength = input.fauxIndentLength;
const tabSize = input.tabSize;
const startVisibleColumn = input.startVisibleColumn;
const containsRTL = input.containsRTL;
const spaceWidth = input.spaceWidth;
const renderWhitespace = input.renderWhitespace;
@@ -738,7 +740,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
const characterMapping = new CharacterMapping(len + 1, parts.length);
let charIndex = 0;
let tabsCharDelta = 0;
let visibleColumn = startVisibleColumn;
let charOffsetInPart = 0;
let prevPartContentCnt = 0;
@@ -764,18 +766,14 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
let partContentCnt = 0;
{
let _charIndex = charIndex;
let _tabsCharDelta = tabsCharDelta;
let _visibleColumn = visibleColumn;
for (; _charIndex < partEndIndex; _charIndex++) {
const charCode = lineContent.charCodeAt(_charIndex);
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (_charIndex + _tabsCharDelta) % tabSize;
_tabsCharDelta += insertSpacesCount - 1;
partContentCnt += insertSpacesCount;
} else {
// must be CharCode.Space
partContentCnt++;
const charWidth = (charCode === CharCode.Tab ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
partContentCnt += charWidth;
if (_charIndex >= fauxIndentLength) {
_visibleColumn += charWidth;
}
}
}
@@ -793,29 +791,30 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
for (; charIndex < partEndIndex; charIndex++) {
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
let charWidth: number;
if (charCode === CharCode.Tab) {
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
if (insertSpacesCount > 0) {
if (!canUseHalfwidthRightwardsArrow || insertSpacesCount > 1) {
sb.write1(0x2192); // RIGHTWARDS ARROW
} else {
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
}
insertSpacesCount--;
charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
sb.write1(0x2192); // RIGHTWARDS ARROW
} else {
sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
}
while (insertSpacesCount > 0) {
for (let space = 2; space <= charWidth; space++) {
sb.write1(0xA0); // &nbsp;
insertSpacesCount--;
}
} else {
// must be CharCode.Space
} else { // must be CharCode.Space
charWidth = 1;
sb.write1(0xB7); // &middot;
}
charOffsetInPart++;
charOffsetInPart += charWidth;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
}
prevPartContentCnt = partContentCnt;
@@ -833,63 +832,59 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset);
const charCode = lineContent.charCodeAt(charIndex);
let producedCharacters = 1;
let charWidth = 1;
switch (charCode) {
case CharCode.Tab:
let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize;
tabsCharDelta += insertSpacesCount - 1;
charOffsetInPart += insertSpacesCount - 1;
while (insertSpacesCount > 0) {
producedCharacters = (tabSize - (visibleColumn % tabSize));
charWidth = producedCharacters;
for (let space = 1; space <= producedCharacters; space++) {
sb.write1(0xA0); // &nbsp;
partContentCnt++;
insertSpacesCount--;
}
break;
case CharCode.Space:
sb.write1(0xA0); // &nbsp;
partContentCnt++;
break;
case CharCode.LessThan:
sb.appendASCIIString('&lt;');
partContentCnt++;
break;
case CharCode.GreaterThan:
sb.appendASCIIString('&gt;');
partContentCnt++;
break;
case CharCode.Ampersand:
sb.appendASCIIString('&amp;');
partContentCnt++;
break;
case CharCode.Null:
sb.appendASCIIString('&#00;');
partContentCnt++;
break;
case CharCode.UTF8_BOM:
case CharCode.LINE_SEPARATOR_2028:
sb.write1(0xFFFD);
partContentCnt++;
break;
default:
if (strings.isFullWidthCharacter(charCode)) {
tabsCharDelta++;
charWidth++;
}
if (renderControlCharacters && charCode < 32) {
sb.write1(9216 + charCode);
partContentCnt++;
} else {
sb.write1(charCode);
partContentCnt++;
}
}
charOffsetInPart++;
charOffsetInPart += producedCharacters;
partContentCnt += producedCharacters;
if (charIndex >= fauxIndentLength) {
visibleColumn += charWidth;
}
}
prevPartContentCnt = partContentCnt;

View File

@@ -1,293 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { toUint32Array } from 'vs/base/common/uint';
import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ILineMapperFactory, ILineMapping, OutputPosition } from 'vs/editor/common/viewModel/splitLinesCollection';
const enum CharacterClass {
NONE = 0,
BREAK_BEFORE = 1,
BREAK_AFTER = 2,
BREAK_OBTRUSIVE = 3,
BREAK_IDEOGRAPHIC = 4 // for Han and Kana.
}
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
constructor(BREAK_BEFORE: string, BREAK_AFTER: string, BREAK_OBTRUSIVE: string) {
super(CharacterClass.NONE);
for (let i = 0; i < BREAK_BEFORE.length; i++) {
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
}
for (let i = 0; i < BREAK_AFTER.length; i++) {
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
}
for (let i = 0; i < BREAK_OBTRUSIVE.length; i++) {
this.set(BREAK_OBTRUSIVE.charCodeAt(i), CharacterClass.BREAK_OBTRUSIVE);
}
}
public get(charCode: number): CharacterClass {
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
if (
(charCode >= 0x3040 && charCode <= 0x30FF)
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
) {
return CharacterClass.BREAK_IDEOGRAPHIC;
}
return super.get(charCode);
}
}
export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory {
private readonly classifier: WrappingCharacterClassifier;
constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) {
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, breakObtrusiveChars);
}
// TODO@Alex -> duplicated in lineCommentCommand
private static nextVisibleColumn(currentVisibleColumn: number, tabSize: number, isTab: boolean, columnSize: number): number {
currentVisibleColumn = +currentVisibleColumn; //@perf
tabSize = +tabSize; //@perf
columnSize = +columnSize; //@perf
if (isTab) {
return currentVisibleColumn + (tabSize - (currentVisibleColumn % tabSize));
}
return currentVisibleColumn + columnSize;
}
public createLineMapping(lineText: string, tabSize: number, breakingColumn: number, columnsForFullWidthChar: number, hardWrappingIndent: WrappingIndent): ILineMapping | null {
if (breakingColumn === -1) {
return null;
}
tabSize = +tabSize; //@perf
breakingColumn = +breakingColumn; //@perf
columnsForFullWidthChar = +columnsForFullWidthChar; //@perf
hardWrappingIndent = +hardWrappingIndent; //@perf
let wrappedTextIndentVisibleColumn = 0;
let wrappedTextIndent = '';
let firstNonWhitespaceIndex = -1;
if (hardWrappingIndent !== WrappingIndent.None) {
firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
if (firstNonWhitespaceIndex !== -1) {
// Track existing indent
wrappedTextIndent = lineText.substring(0, firstNonWhitespaceIndex);
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, lineText.charCodeAt(i) === CharCode.Tab, 1);
}
// Increase indent of continuation lines, if desired
let numberOfAdditionalTabs = 0;
if (hardWrappingIndent === WrappingIndent.Indent) {
numberOfAdditionalTabs = 1;
} else if (hardWrappingIndent === WrappingIndent.DeepIndent) {
numberOfAdditionalTabs = 2;
}
for (let i = 0; i < numberOfAdditionalTabs; i++) {
wrappedTextIndent += '\t';
wrappedTextIndentVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(wrappedTextIndentVisibleColumn, tabSize, true, 1);
}
// Force sticking to beginning of line if no character would fit except for the indentation
if (wrappedTextIndentVisibleColumn + columnsForFullWidthChar > breakingColumn) {
wrappedTextIndent = '';
wrappedTextIndentVisibleColumn = 0;
}
}
}
let classifier = this.classifier;
let lastBreakingOffset = 0; // Last 0-based offset in the lineText at which a break happened
let breakingLengths: number[] = []; // The length of each broken-up line text
let breakingLengthsIndex: number = 0; // The count of breaks already done
let visibleColumn = 0; // Visible column since the beginning of the current line
let niceBreakOffset = -1; // Last index of a character that indicates a break should happen before it (more desirable)
let niceBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `niceBreakOffset`
let obtrusiveBreakOffset = -1; // Last index of a character that indicates a break should happen before it (less desirable)
let obtrusiveBreakVisibleColumn = 0; // visible column if a break were to be later introduced before `obtrusiveBreakOffset`
let len = lineText.length;
for (let i = 0; i < len; i++) {
// At this point, there is a certainty that the character before `i` fits on the current line,
// but the character at `i` might not fit
let charCode = lineText.charCodeAt(i);
let charCodeIsTab = (charCode === CharCode.Tab);
let charCodeClass = classifier.get(charCode);
if (strings.isLowSurrogate(charCode)/* && i + 1 < len */) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
// => advance visibleColumn by 1 and advance to next char code...
visibleColumn = visibleColumn + 1;
continue;
}
if (charCodeClass === CharacterClass.BREAK_BEFORE) {
// This is a character that indicates that a break should happen before it
// Since we are certain the character before `i` fits, there's no extra checking needed,
// just mark it as a nice breaking opportunity
niceBreakOffset = i;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
// CJK breaking : before break
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i > 0) {
let prevCode = lineText.charCodeAt(i - 1);
let prevClass = classifier.get(prevCode);
if (prevClass !== CharacterClass.BREAK_BEFORE) { // Kinsoku Shori: Don't break after a leading character, like an open bracket
niceBreakOffset = i;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
let charColumnSize = 1;
if (strings.isFullWidthCharacter(charCode)) {
charColumnSize = columnsForFullWidthChar;
}
// Advance visibleColumn with character at `i`
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(visibleColumn, tabSize, charCodeIsTab, charColumnSize);
if (visibleColumn > breakingColumn && i !== 0) {
// We need to break at least before character at `i`:
// - break before niceBreakLastOffset if it exists (and re-establish a correct visibleColumn by using niceBreakVisibleColumn + charAt(i))
// - otherwise, break before obtrusiveBreakLastOffset if it exists (and re-establish a correct visibleColumn by using obtrusiveBreakVisibleColumn + charAt(i))
// - otherwise, break before i (and re-establish a correct visibleColumn by charAt(i))
let breakBeforeOffset: number;
let restoreVisibleColumnFrom: number;
if (niceBreakOffset !== -1 && niceBreakVisibleColumn <= breakingColumn) {
// We will break before `niceBreakLastOffset`
breakBeforeOffset = niceBreakOffset;
restoreVisibleColumnFrom = niceBreakVisibleColumn;
} else if (obtrusiveBreakOffset !== -1 && obtrusiveBreakVisibleColumn <= breakingColumn) {
// We will break before `obtrusiveBreakLastOffset`
breakBeforeOffset = obtrusiveBreakOffset;
restoreVisibleColumnFrom = obtrusiveBreakVisibleColumn;
} else {
// We will break before `i`
breakBeforeOffset = i;
restoreVisibleColumnFrom = wrappedTextIndentVisibleColumn;
}
// Break before character at `breakBeforeOffset`
breakingLengths[breakingLengthsIndex++] = breakBeforeOffset - lastBreakingOffset;
lastBreakingOffset = breakBeforeOffset;
// Re-establish visibleColumn by taking character at `i` into account
visibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(restoreVisibleColumnFrom, tabSize, charCodeIsTab, charColumnSize);
// Reset markers
niceBreakOffset = -1;
niceBreakVisibleColumn = 0;
obtrusiveBreakOffset = -1;
obtrusiveBreakVisibleColumn = 0;
}
// At this point, there is a certainty that the character at `i` fits on the current line
if (niceBreakOffset !== -1) {
// Advance niceBreakVisibleColumn
niceBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(niceBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
}
if (obtrusiveBreakOffset !== -1) {
// Advance obtrusiveBreakVisibleColumn
obtrusiveBreakVisibleColumn = CharacterHardWrappingLineMapperFactory.nextVisibleColumn(obtrusiveBreakVisibleColumn, tabSize, charCodeIsTab, charColumnSize);
}
if (charCodeClass === CharacterClass.BREAK_AFTER && (hardWrappingIndent === WrappingIndent.None || i >= firstNonWhitespaceIndex)) {
// This is a character that indicates that a break should happen after it
niceBreakOffset = i + 1;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
// CJK breaking : after break
if (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && i < len - 1) {
let nextCode = lineText.charCodeAt(i + 1);
let nextClass = classifier.get(nextCode);
if (nextClass !== CharacterClass.BREAK_AFTER) { // Kinsoku Shori: Don't break before a trailing character, like a period
niceBreakOffset = i + 1;
niceBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
if (charCodeClass === CharacterClass.BREAK_OBTRUSIVE) {
// This is an obtrusive character that indicates that a break should happen after it
obtrusiveBreakOffset = i + 1;
obtrusiveBreakVisibleColumn = wrappedTextIndentVisibleColumn;
}
}
if (breakingLengthsIndex === 0) {
return null;
}
// Add last segment
breakingLengths[breakingLengthsIndex++] = len - lastBreakingOffset;
return new CharacterHardWrappingLineMapping(
new PrefixSumComputer(toUint32Array(breakingLengths)),
wrappedTextIndent
);
}
}
export class CharacterHardWrappingLineMapping implements ILineMapping {
private readonly _prefixSums: PrefixSumComputer;
private readonly _wrappedLinesIndent: string;
constructor(prefixSums: PrefixSumComputer, wrappedLinesIndent: string) {
this._prefixSums = prefixSums;
this._wrappedLinesIndent = wrappedLinesIndent;
}
public getOutputLineCount(): number {
return this._prefixSums.getCount();
}
public getWrappedLinesIndent(): string {
return this._wrappedLinesIndent;
}
public getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number {
if (outputLineIndex === 0) {
return outputOffset;
} else {
return this._prefixSums.getAccumulatedValue(outputLineIndex - 1) + outputOffset;
}
}
public getOutputPositionOfInputOffset(inputOffset: number): OutputPosition {
let r = this._prefixSums.getIndexOf(inputOffset);
return new OutputPosition(r.index, r.remainder);
}
}

View File

@@ -0,0 +1,472 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CharCode } from 'vs/base/common/charCode';
import * as strings from 'vs/base/common/strings';
import { WrappingIndent, IComputedEditorOptions, EditorOption } from 'vs/editor/common/config/editorOptions';
import { CharacterClassifier } from 'vs/editor/common/core/characterClassifier';
import { ILineBreaksComputerFactory, LineBreakData, ILineBreaksComputer } from 'vs/editor/common/viewModel/splitLinesCollection';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
const enum CharacterClass {
NONE = 0,
BREAK_BEFORE = 1,
BREAK_AFTER = 2,
BREAK_IDEOGRAPHIC = 3 // for Han and Kana.
}
class WrappingCharacterClassifier extends CharacterClassifier<CharacterClass> {
constructor(BREAK_BEFORE: string, BREAK_AFTER: string) {
super(CharacterClass.NONE);
for (let i = 0; i < BREAK_BEFORE.length; i++) {
this.set(BREAK_BEFORE.charCodeAt(i), CharacterClass.BREAK_BEFORE);
}
for (let i = 0; i < BREAK_AFTER.length; i++) {
this.set(BREAK_AFTER.charCodeAt(i), CharacterClass.BREAK_AFTER);
}
}
public get(charCode: number): CharacterClass {
if (charCode >= 0 && charCode < 256) {
return <CharacterClass>this._asciiMap[charCode];
} else {
// Initialize CharacterClass.BREAK_IDEOGRAPHIC for these Unicode ranges:
// 1. CJK Unified Ideographs (0x4E00 -- 0x9FFF)
// 2. CJK Unified Ideographs Extension A (0x3400 -- 0x4DBF)
// 3. Hiragana and Katakana (0x3040 -- 0x30FF)
if (
(charCode >= 0x3040 && charCode <= 0x30FF)
|| (charCode >= 0x3400 && charCode <= 0x4DBF)
|| (charCode >= 0x4E00 && charCode <= 0x9FFF)
) {
return CharacterClass.BREAK_IDEOGRAPHIC;
}
return <CharacterClass>(this._map.get(charCode) || this._defaultValue);
}
}
}
let arrPool1: number[] = [];
let arrPool2: number[] = [];
export class MonospaceLineBreaksComputerFactory implements ILineBreaksComputerFactory {
public static create(options: IComputedEditorOptions): MonospaceLineBreaksComputerFactory {
return new MonospaceLineBreaksComputerFactory(
options.get(EditorOption.wordWrapBreakBeforeCharacters),
options.get(EditorOption.wordWrapBreakAfterCharacters)
);
}
private readonly classifier: WrappingCharacterClassifier;
constructor(breakBeforeChars: string, breakAfterChars: string) {
this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars);
}
public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer {
tabSize = tabSize | 0; //@perf
wrappingColumn = +wrappingColumn; //@perf
let requests: string[] = [];
let previousBreakingData: (LineBreakData | null)[] = [];
return {
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
requests.push(lineText);
previousBreakingData.push(previousLineBreakData);
},
finalize: () => {
const columnsForFullWidthChar = fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth; //@perf
let result: (LineBreakData | null)[] = [];
for (let i = 0, len = requests.length; i < len; i++) {
const previousLineBreakData = previousBreakingData[i];
if (previousLineBreakData) {
result[i] = createLineBreaksFromPreviousLineBreaks(this.classifier, previousLineBreakData, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
} else {
result[i] = createLineBreaks(this.classifier, requests[i], tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
}
}
arrPool1.length = 0;
arrPool2.length = 0;
return result;
}
};
}
}
function createLineBreaksFromPreviousLineBreaks(classifier: WrappingCharacterClassifier, previousBreakingData: LineBreakData, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
if (firstLineBreakColumn === -1) {
return null;
}
const len = lineText.length;
if (len <= 1) {
return null;
}
const prevBreakingOffsets = previousBreakingData.breakOffsets;
const prevBreakingOffsetsVisibleColumn = previousBreakingData.breakOffsetsVisibleColumn;
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
let breakingOffsets: number[] = arrPool1;
let breakingOffsetsVisibleColumn: number[] = arrPool2;
let breakingOffsetsCount: number = 0;
let breakingColumn = firstLineBreakColumn;
const prevLen = prevBreakingOffsets.length;
let prevIndex = 0;
if (prevIndex >= 0) {
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
while (prevIndex + 1 < prevLen) {
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
if (distance >= bestDistance) {
break;
}
bestDistance = distance;
prevIndex++;
}
}
while (prevIndex < prevLen) {
// Allow for prevIndex to be -1 (for the case where we hit a tab when walking backwards from the first break)
const prevBreakOffset = prevIndex < 0 ? 0 : prevBreakingOffsets[prevIndex];
const prevBreakoffsetVisibleColumn = prevIndex < 0 ? 0 : prevBreakingOffsetsVisibleColumn[prevIndex];
let breakOffset = 0;
let breakOffsetVisibleColumn = 0;
let forcedBreakOffset = 0;
let forcedBreakOffsetVisibleColumn = 0;
// initially, we search as much as possible to the right (if it fits)
if (prevBreakoffsetVisibleColumn <= breakingColumn) {
let visibleColumn = prevBreakoffsetVisibleColumn;
let prevCharCode = lineText.charCodeAt(prevBreakOffset - 1);
let prevCharCodeClass = classifier.get(prevCharCode);
let entireLineFits = true;
for (let i = prevBreakOffset; i < len; i++) {
const charStartOffset = i;
const charCode = lineText.charCodeAt(i);
let charCodeClass: number;
let charWidth: number;
if (strings.isHighSurrogate(charCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i++;
charCodeClass = CharacterClass.NONE;
charWidth = 2;
} else {
charCodeClass = classifier.get(charCode);
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
}
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
}
visibleColumn += charWidth;
// check if adding character at `i` will go over the breaking column
if (visibleColumn > breakingColumn) {
// We need to break at least before character at `i`:
forcedBreakOffset = charStartOffset;
forcedBreakOffsetVisibleColumn = visibleColumn - charWidth;
if (visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
// Cannot break at `breakOffset` => reset it if it was set
breakOffset = 0;
}
entireLineFits = false;
break;
}
prevCharCode = charCode;
prevCharCodeClass = charCodeClass;
}
if (entireLineFits) {
// there is no more need to break => stop the outer loop!
if (breakingOffsetsCount > 0) {
// Add last segment
breakingOffsets[breakingOffsetsCount] = prevBreakingOffsets[prevBreakingOffsets.length - 1];
breakingOffsetsVisibleColumn[breakingOffsetsCount] = prevBreakingOffsetsVisibleColumn[prevBreakingOffsets.length - 1];
breakingOffsetsCount++;
}
break;
}
}
if (breakOffset === 0) {
// must search left
let visibleColumn = prevBreakoffsetVisibleColumn;
let charCode = lineText.charCodeAt(prevBreakOffset);
let charCodeClass = classifier.get(charCode);
let hitATabCharacter = false;
for (let i = prevBreakOffset - 1; i >= 0; i--) {
const charStartOffset = i + 1;
const prevCharCode = lineText.charCodeAt(i);
if (prevCharCode === CharCode.Tab) {
// cannot determine the width of a tab when going backwards, so we must go forwards
hitATabCharacter = true;
break;
}
let prevCharCodeClass: number;
let prevCharWidth: number;
if (strings.isLowSurrogate(prevCharCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i--;
prevCharCodeClass = CharacterClass.NONE;
prevCharWidth = 2;
} else {
prevCharCodeClass = classifier.get(prevCharCode);
prevCharWidth = (strings.isFullWidthCharacter(prevCharCode) ? columnsForFullWidthChar : 1);
}
if (visibleColumn <= breakingColumn) {
if (forcedBreakOffset === 0) {
forcedBreakOffset = charStartOffset;
forcedBreakOffsetVisibleColumn = visibleColumn;
}
if (visibleColumn <= breakingColumn - wrappedLineBreakColumn) {
// went too far!
break;
}
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
break;
}
}
visibleColumn -= prevCharWidth;
charCode = prevCharCode;
charCodeClass = prevCharCodeClass;
}
if (breakOffset !== 0) {
const remainingWidthOfNextLine = wrappedLineBreakColumn - (forcedBreakOffsetVisibleColumn - breakOffsetVisibleColumn);
if (remainingWidthOfNextLine <= tabSize) {
const charCodeAtForcedBreakOffset = lineText.charCodeAt(forcedBreakOffset);
let charWidth: number;
if (strings.isHighSurrogate(charCodeAtForcedBreakOffset)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
charWidth = 2;
} else {
charWidth = computeCharWidth(charCodeAtForcedBreakOffset, forcedBreakOffsetVisibleColumn, tabSize, columnsForFullWidthChar);
}
if (remainingWidthOfNextLine - charWidth < 0) {
// it is not worth it to break at breakOffset, it just introduces an extra needless line!
breakOffset = 0;
}
}
}
if (hitATabCharacter) {
// cannot determine the width of a tab when going backwards, so we must go forwards from the previous break
prevIndex--;
continue;
}
}
if (breakOffset === 0) {
// Could not find a good breaking point
breakOffset = forcedBreakOffset;
breakOffsetVisibleColumn = forcedBreakOffsetVisibleColumn;
}
breakingOffsets[breakingOffsetsCount] = breakOffset;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
breakingOffsetsCount++;
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
while (prevIndex < 0 || (prevIndex < prevLen && prevBreakingOffsetsVisibleColumn[prevIndex] < breakOffsetVisibleColumn)) {
prevIndex++;
}
let bestDistance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex] - breakingColumn);
while (prevIndex + 1 < prevLen) {
const distance = Math.abs(prevBreakingOffsetsVisibleColumn[prevIndex + 1] - breakingColumn);
if (distance >= bestDistance) {
break;
}
bestDistance = distance;
prevIndex++;
}
}
if (breakingOffsetsCount === 0) {
return null;
}
// Doing here some object reuse which ends up helping a huge deal with GC pauses!
breakingOffsets.length = breakingOffsetsCount;
breakingOffsetsVisibleColumn.length = breakingOffsetsCount;
arrPool1 = previousBreakingData.breakOffsets;
arrPool2 = previousBreakingData.breakOffsetsVisibleColumn;
previousBreakingData.breakOffsets = breakingOffsets;
previousBreakingData.breakOffsetsVisibleColumn = breakingOffsetsVisibleColumn;
previousBreakingData.wrappedTextIndentLength = wrappedTextIndentLength;
return previousBreakingData;
}
function createLineBreaks(classifier: WrappingCharacterClassifier, lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): LineBreakData | null {
if (firstLineBreakColumn === -1) {
return null;
}
const len = lineText.length;
if (len <= 1) {
return null;
}
const wrappedTextIndentLength = computeWrappedTextIndentLength(lineText, tabSize, firstLineBreakColumn, columnsForFullWidthChar, wrappingIndent);
const wrappedLineBreakColumn = firstLineBreakColumn - wrappedTextIndentLength;
let breakingOffsets: number[] = [];
let breakingOffsetsVisibleColumn: number[] = [];
let breakingOffsetsCount: number = 0;
let breakOffset = 0;
let breakOffsetVisibleColumn = 0;
let breakingColumn = firstLineBreakColumn;
let prevCharCode = lineText.charCodeAt(0);
let prevCharCodeClass = classifier.get(prevCharCode);
let visibleColumn = computeCharWidth(prevCharCode, 0, tabSize, columnsForFullWidthChar);
let startOffset = 1;
if (strings.isHighSurrogate(prevCharCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
visibleColumn += 1;
prevCharCode = lineText.charCodeAt(1);
prevCharCodeClass = classifier.get(prevCharCode);
startOffset++;
}
for (let i = startOffset; i < len; i++) {
const charStartOffset = i;
const charCode = lineText.charCodeAt(i);
let charCodeClass: number;
let charWidth: number;
if (strings.isHighSurrogate(charCode)) {
// A surrogate pair must always be considered as a single unit, so it is never to be broken
i++;
charCodeClass = CharacterClass.NONE;
charWidth = 2;
} else {
charCodeClass = classifier.get(charCode);
charWidth = computeCharWidth(charCode, visibleColumn, tabSize, columnsForFullWidthChar);
}
if (canBreak(prevCharCode, prevCharCodeClass, charCode, charCodeClass)) {
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn;
}
visibleColumn += charWidth;
// check if adding character at `i` will go over the breaking column
if (visibleColumn > breakingColumn) {
// We need to break at least before character at `i`:
if (breakOffset === 0 || visibleColumn - breakOffsetVisibleColumn > wrappedLineBreakColumn) {
// Cannot break at `breakOffset`, must break at `i`
breakOffset = charStartOffset;
breakOffsetVisibleColumn = visibleColumn - charWidth;
}
breakingOffsets[breakingOffsetsCount] = breakOffset;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = breakOffsetVisibleColumn;
breakingOffsetsCount++;
breakingColumn = breakOffsetVisibleColumn + wrappedLineBreakColumn;
breakOffset = 0;
}
prevCharCode = charCode;
prevCharCodeClass = charCodeClass;
}
if (breakingOffsetsCount === 0) {
return null;
}
// Add last segment
breakingOffsets[breakingOffsetsCount] = len;
breakingOffsetsVisibleColumn[breakingOffsetsCount] = visibleColumn;
return new LineBreakData(breakingOffsets, breakingOffsetsVisibleColumn, wrappedTextIndentLength);
}
function computeCharWidth(charCode: number, visibleColumn: number, tabSize: number, columnsForFullWidthChar: number): number {
if (charCode === CharCode.Tab) {
return (tabSize - (visibleColumn % tabSize));
}
if (strings.isFullWidthCharacter(charCode)) {
return columnsForFullWidthChar;
}
return 1;
}
function tabCharacterWidth(visibleColumn: number, tabSize: number): number {
return (tabSize - (visibleColumn % tabSize));
}
/**
* Kinsoku Shori : Don't break after a leading character, like an open bracket
* Kinsoku Shori : Don't break before a trailing character, like a period
*/
function canBreak(prevCharCode: number, prevCharCodeClass: CharacterClass, charCode: number, charCodeClass: CharacterClass): boolean {
return (
charCode !== CharCode.Space
&& (
(prevCharCodeClass === CharacterClass.BREAK_AFTER)
|| (prevCharCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && charCodeClass !== CharacterClass.BREAK_AFTER)
|| (charCodeClass === CharacterClass.BREAK_BEFORE)
|| (charCodeClass === CharacterClass.BREAK_IDEOGRAPHIC && prevCharCodeClass !== CharacterClass.BREAK_BEFORE)
)
);
}
function computeWrappedTextIndentLength(lineText: string, tabSize: number, firstLineBreakColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): number {
let wrappedTextIndentLength = 0;
if (wrappingIndent !== WrappingIndent.None) {
const firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineText);
if (firstNonWhitespaceIndex !== -1) {
// Track existing indent
for (let i = 0; i < firstNonWhitespaceIndex; i++) {
const charWidth = (lineText.charCodeAt(i) === CharCode.Tab ? tabCharacterWidth(wrappedTextIndentLength, tabSize) : 1);
wrappedTextIndentLength += charWidth;
}
// Increase indent of continuation lines, if desired
const numberOfAdditionalTabs = (wrappingIndent === WrappingIndent.DeepIndent ? 2 : wrappingIndent === WrappingIndent.Indent ? 1 : 0);
for (let i = 0; i < numberOfAdditionalTabs; i++) {
const charWidth = tabCharacterWidth(wrappedTextIndentLength, tabSize);
wrappedTextIndentLength += charWidth;
}
// Force sticking to beginning of line if no character would fit except for the indentation
if (wrappedTextIndentLength + columnsForFullWidthChar > firstLineBreakColumn) {
wrappedTextIndentLength = 0;
}
}
}
return wrappedTextIndentLength;
}

View File

@@ -187,73 +187,3 @@ export class PrefixSumComputer {
return new PrefixSumIndexOfResult(mid, accumulatedValue - midStart);
}
}
export class PrefixSumComputerWithCache {
private readonly _actual: PrefixSumComputer;
private _cacheAccumulatedValueStart: number = 0;
private _cache: PrefixSumIndexOfResult[] | null = null;
constructor(values: Uint32Array) {
this._actual = new PrefixSumComputer(values);
this._bustCache();
}
private _bustCache(): void {
this._cacheAccumulatedValueStart = 0;
this._cache = null;
}
public insertValues(insertIndex: number, insertValues: Uint32Array): void {
if (this._actual.insertValues(insertIndex, insertValues)) {
this._bustCache();
}
}
public changeValue(index: number, value: number): void {
if (this._actual.changeValue(index, value)) {
this._bustCache();
}
}
public removeValues(startIndex: number, cnt: number): void {
if (this._actual.removeValues(startIndex, cnt)) {
this._bustCache();
}
}
public getTotalValue(): number {
return this._actual.getTotalValue();
}
public getAccumulatedValue(index: number): number {
return this._actual.getAccumulatedValue(index);
}
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
accumulatedValue = Math.floor(accumulatedValue); //@perf
if (this._cache !== null) {
let cacheIndex = accumulatedValue - this._cacheAccumulatedValueStart;
if (cacheIndex >= 0 && cacheIndex < this._cache.length) {
// Cache hit!
return this._cache[cacheIndex];
}
}
// Cache miss!
return this._actual.getIndexOf(accumulatedValue);
}
/**
* Gives a hint that a lot of requests are about to come in for these accumulated values.
*/
public warmUpCache(accumulatedValueStart: number, accumulatedValueEnd: number): void {
let newCache: PrefixSumIndexOfResult[] = [];
for (let accumulatedValue = accumulatedValueStart; accumulatedValue <= accumulatedValueEnd; accumulatedValue++) {
newCache[accumulatedValue - accumulatedValueStart] = this.getIndexOf(accumulatedValue);
}
this._cache = newCache;
this._cacheAccumulatedValueStart = accumulatedValueStart;
}
}

View File

@@ -3,6 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as arrays from 'vs/base/common/arrays';
import { WrappingIndent } from 'vs/editor/common/config/editorOptions';
import { LineTokens } from 'vs/editor/common/core/lineTokens';
import { Position } from 'vs/editor/common/core/position';
@@ -10,13 +11,13 @@ import { IRange, Range } from 'vs/editor/common/core/range';
import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecoration, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSumComputer';
import { PrefixSumIndexOfResult } from 'vs/editor/common/viewModel/prefixSumComputer';
import { ICoordinatesConverter, IOverviewRulerDecorations, ViewLineData } from 'vs/editor/common/viewModel/viewModel';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { FontInfo } from 'vs/editor/common/config/fontInfo';
export class OutputPosition {
_outputPositionBrand: void;
outputLineIndex: number;
outputOffset: number;
@@ -26,15 +27,56 @@ export class OutputPosition {
}
}
export interface ILineMapping {
getOutputLineCount(): number;
getWrappedLinesIndent(): string;
getInputOffsetOfOutputPosition(outputLineIndex: number, outputOffset: number): number;
getOutputPositionOfInputOffset(inputOffset: number): OutputPosition;
export class LineBreakData {
constructor(
public breakOffsets: number[],
public breakOffsetsVisibleColumn: number[],
public wrappedTextIndentLength: number
) { }
public static getInputOffsetOfOutputPosition(breakOffsets: number[], outputLineIndex: number, outputOffset: number): number {
if (outputLineIndex === 0) {
return outputOffset;
} else {
return breakOffsets[outputLineIndex - 1] + outputOffset;
}
}
public static getOutputPositionOfInputOffset(breakOffsets: number[], inputOffset: number): OutputPosition {
let low = 0;
let high = breakOffsets.length - 1;
let mid = 0;
let midStart = 0;
while (low <= high) {
mid = low + ((high - low) / 2) | 0;
const midStop = breakOffsets[mid];
midStart = mid > 0 ? breakOffsets[mid - 1] : 0;
if (inputOffset < midStart) {
high = mid - 1;
} else if (inputOffset >= midStop) {
low = mid + 1;
} else {
break;
}
}
return new OutputPosition(mid, inputOffset - midStart);
}
}
export interface ILineMapperFactory {
createLineMapping(lineText: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent): ILineMapping | null;
export interface ILineBreaksComputer {
/**
* Pass in `previousLineBreakData` if the only difference is in breaking columns!!!
*/
addRequest(lineText: string, previousLineBreakData: LineBreakData | null): void;
finalize(): (LineBreakData | null)[];
}
export interface ILineBreaksComputerFactory {
createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent): ILineBreaksComputer;
}
export interface ISimpleModel {
@@ -50,6 +92,7 @@ export interface ISplitLine {
isVisible(): boolean;
setVisible(isVisible: boolean): ISplitLine;
getLineBreakData(): LineBreakData | null;
getViewLineCount(): number;
getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string;
getViewLineLength(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number;
@@ -66,19 +109,19 @@ export interface ISplitLine {
export interface IViewModelLinesCollection extends IDisposable {
createCoordinatesConverter(): ICoordinatesConverter;
setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean;
setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean;
setTabSize(newTabSize: number): boolean;
getHiddenAreas(): Range[];
setHiddenAreas(_ranges: Range[]): boolean;
createLineBreaksComputer(): ILineBreaksComputer;
onModelFlushed(): void;
onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null;
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null;
onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
onModelLinesInserted(versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null;
onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null];
acceptVersionId(versionId: number): void;
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;
@@ -107,9 +150,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
}
public convertViewRangeToModelRange(viewRange: Range): Range {
let start = this._lines.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
let end = this._lines.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
return this._lines.convertViewRangeToModelRange(viewRange);
}
public validateViewPosition(viewPosition: Position, expectedModelPosition: Position): Position {
@@ -117,9 +158,7 @@ export class CoordinatesConverter implements ICoordinatesConverter {
}
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
const validViewStart = this._lines.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
const validViewEnd = this._lines.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
return this._lines.validateViewRange(viewRange, expectedModelRange);
}
// Model -> View conversion and related methods
@@ -135,7 +174,6 @@ export class CoordinatesConverter implements ICoordinatesConverter {
public modelPositionIsVisible(modelPosition: Position): boolean {
return this._lines.modelPositionIsVisible(modelPosition.lineNumber, modelPosition.column);
}
}
const enum IndentGuideRepeatOption {
@@ -144,33 +182,129 @@ const enum IndentGuideRepeatOption {
BlockAll = 2
}
class LineNumberMapper {
private _counts: number[];
private _isValid: boolean;
private _validEndIndex: number;
private _modelToView: number[];
private _viewToModel: number[];
constructor(viewLineCounts: number[]) {
this._counts = viewLineCounts;
this._isValid = false;
this._validEndIndex = -1;
this._modelToView = [];
this._viewToModel = [];
}
private _invalidate(index: number): void {
this._isValid = false;
this._validEndIndex = Math.min(this._validEndIndex, index - 1);
}
private _ensureValid(): void {
if (this._isValid) {
return;
}
for (let i = this._validEndIndex + 1, len = this._counts.length; i < len; i++) {
const viewLineCount = this._counts[i];
const viewLinesAbove = (i > 0 ? this._modelToView[i - 1] : 0);
this._modelToView[i] = viewLinesAbove + viewLineCount;
for (let j = 0; j < viewLineCount; j++) {
this._viewToModel[viewLinesAbove + j] = i;
}
}
// trim things
this._modelToView.length = this._counts.length;
this._viewToModel.length = this._modelToView[this._modelToView.length - 1];
// mark as valid
this._isValid = true;
this._validEndIndex = this._counts.length - 1;
}
public changeValue(index: number, value: number): void {
if (this._counts[index] === value) {
// no change
return;
}
this._counts[index] = value;
this._invalidate(index);
}
public removeValues(start: number, deleteCount: number): void {
this._counts.splice(start, deleteCount);
this._invalidate(start);
}
public insertValues(insertIndex: number, insertArr: number[]): void {
this._counts = arrays.arrayInsert(this._counts, insertIndex, insertArr);
this._invalidate(insertIndex);
}
public getTotalValue(): number {
this._ensureValid();
return this._viewToModel.length;
}
public getAccumulatedValue(index: number): number {
this._ensureValid();
return this._modelToView[index];
}
public getIndexOf(accumulatedValue: number): PrefixSumIndexOfResult {
this._ensureValid();
const modelLineIndex = this._viewToModel[accumulatedValue];
const viewLinesAbove = (modelLineIndex > 0 ? this._modelToView[modelLineIndex - 1] : 0);
return new PrefixSumIndexOfResult(modelLineIndex, accumulatedValue - viewLinesAbove);
}
}
export class SplitLinesCollection implements IViewModelLinesCollection {
private readonly model: ITextModel;
private _validModelVersionId: number;
private wrappingColumn: number;
private columnsForFullWidthChar: number;
private wrappingIndent: WrappingIndent;
private readonly _domLineBreaksComputerFactory: ILineBreaksComputerFactory;
private readonly _monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory;
private fontInfo: FontInfo;
private tabSize: number;
private wrappingColumn: number;
private wrappingIndent: WrappingIndent;
private wrappingAlgorithm: 'monospace' | 'dom';
private lines!: ISplitLine[];
private prefixSumComputer!: PrefixSumComputerWithCache;
private readonly linePositionMapperFactory: ILineMapperFactory;
private prefixSumComputer!: LineNumberMapper;
private hiddenAreasIds!: string[];
constructor(model: ITextModel, linePositionMapperFactory: ILineMapperFactory, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent) {
constructor(
model: ITextModel,
domLineBreaksComputerFactory: ILineBreaksComputerFactory,
monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,
fontInfo: FontInfo,
tabSize: number,
wrappingAlgorithm: 'monospace' | 'dom',
wrappingColumn: number,
wrappingIndent: WrappingIndent,
) {
this.model = model;
this._validModelVersionId = -1;
this._domLineBreaksComputerFactory = domLineBreaksComputerFactory;
this._monospaceLineBreaksComputerFactory = monospaceLineBreaksComputerFactory;
this.fontInfo = fontInfo;
this.tabSize = tabSize;
this.wrappingAlgorithm = wrappingAlgorithm;
this.wrappingColumn = wrappingColumn;
this.columnsForFullWidthChar = columnsForFullWidthChar;
this.wrappingIndent = wrappingIndent;
this.linePositionMapperFactory = linePositionMapperFactory;
this._constructLines(true);
this._constructLines(/*resetHiddenAreas*/true, null);
}
public dispose(): void {
@@ -181,19 +315,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new CoordinatesConverter(this);
}
private _ensureValidState(): void {
let modelVersion = this.model.getVersionId();
if (modelVersion !== this._validModelVersionId) {
// This is pretty bad, it means we lost track of the model...
throw new Error(`ViewModel is out of sync with Model!`);
}
if (this.lines.length !== this.model.getLineCount()) {
// This is pretty bad, it means we lost track of the model...
this._constructLines(false);
}
}
private _constructLines(resetHiddenAreas: boolean): void {
private _constructLines(resetHiddenAreas: boolean, previousLineBreaks: ((LineBreakData | null)[]) | null): void {
this.lines = [];
if (resetHiddenAreas) {
@@ -201,8 +323,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
let linesContent = this.model.getLinesContent();
let lineCount = linesContent.length;
let values = new Uint32Array(lineCount);
const lineCount = linesContent.length;
const lineBreaksComputer = this.createLineBreaksComputer();
for (let i = 0; i < lineCount; i++) {
lineBreaksComputer.addRequest(linesContent[i], previousLineBreaks ? previousLineBreaks[i] : null);
}
const linesBreaks = lineBreaksComputer.finalize();
let values: number[] = [];
let hiddenAreas = this.hiddenAreasIds.map((areaId) => this.model.getDecorationRange(areaId)!).sort(Range.compareRangesUsingStarts);
let hiddenAreaStart = 1, hiddenAreaEnd = 0;
@@ -220,14 +348,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
let isInHiddenArea = (lineNumber >= hiddenAreaStart && lineNumber <= hiddenAreaEnd);
let line = createSplitLine(this.linePositionMapperFactory, linesContent[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
let line = createSplitLine(linesBreaks[i], !isInHiddenArea);
values[i] = line.getViewLineCount();
this.lines[i] = line;
}
this._validModelVersionId = this.model.getVersionId();
this.prefixSumComputer = new PrefixSumComputerWithCache(values);
this.prefixSumComputer = new LineNumberMapper(values);
}
public getHiddenAreas(): Range[] {
@@ -351,27 +479,51 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
this.tabSize = newTabSize;
this._constructLines(false);
this._constructLines(/*resetHiddenAreas*/false, null);
return true;
}
public setWrappingSettings(wrappingIndent: WrappingIndent, wrappingColumn: number, columnsForFullWidthChar: number): boolean {
if (this.wrappingIndent === wrappingIndent && this.wrappingColumn === wrappingColumn && this.columnsForFullWidthChar === columnsForFullWidthChar) {
public setWrappingSettings(fontInfo: FontInfo, wrappingAlgorithm: 'monospace' | 'dom', wrappingColumn: number, wrappingIndent: WrappingIndent): boolean {
const equalFontInfo = this.fontInfo.equals(fontInfo);
const equalWrappingAlgorithm = (this.wrappingAlgorithm === wrappingAlgorithm);
const equalWrappingColumn = (this.wrappingColumn === wrappingColumn);
const equalWrappingIndent = (this.wrappingIndent === wrappingIndent);
if (equalFontInfo && equalWrappingAlgorithm && equalWrappingColumn && equalWrappingIndent) {
return false;
}
this.wrappingIndent = wrappingIndent;
this.wrappingColumn = wrappingColumn;
this.columnsForFullWidthChar = columnsForFullWidthChar;
const onlyWrappingColumnChanged = (equalFontInfo && equalWrappingAlgorithm && !equalWrappingColumn && equalWrappingIndent);
this._constructLines(false);
this.fontInfo = fontInfo;
this.wrappingAlgorithm = wrappingAlgorithm;
this.wrappingColumn = wrappingColumn;
this.wrappingIndent = wrappingIndent;
let previousLineBreaks: ((LineBreakData | null)[]) | null = null;
if (onlyWrappingColumnChanged) {
previousLineBreaks = [];
for (let i = 0, len = this.lines.length; i < len; i++) {
previousLineBreaks[i] = this.lines[i].getLineBreakData();
}
}
this._constructLines(/*resetHiddenAreas*/false, previousLineBreaks);
return true;
}
public createLineBreaksComputer(): ILineBreaksComputer {
const lineBreaksComputerFactory = (
this.wrappingAlgorithm === 'dom'
? this._domLineBreaksComputerFactory
: this._monospaceLineBreaksComputerFactory
);
return lineBreaksComputerFactory.createLineBreaksComputer(this.fontInfo, this.tabSize, this.wrappingColumn, this.wrappingIndent);
}
public onModelFlushed(): void {
this._constructLines(true);
this._constructLines(/*resetHiddenAreas*/true, null);
}
public onModelLinesDeleted(versionId: number, fromLineNumber: number, toLineNumber: number): viewEvents.ViewLinesDeletedEvent | null {
@@ -390,7 +542,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesDeletedEvent(outputFromLineNumber, outputToLineNumber);
}
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, text: string[]): viewEvents.ViewLinesInsertedEvent | null {
public onModelLinesInserted(versionId: number, fromLineNumber: number, _toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
if (versionId <= this._validModelVersionId) {
// Here we check for versionId in case the lines were reconstructed in the meantime.
// We don't want to apply stale change events on top of a newer read model state.
@@ -411,10 +563,10 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let totalOutputLineCount = 0;
let insertLines: ISplitLine[] = [];
let insertPrefixSumValues = new Uint32Array(text.length);
let insertPrefixSumValues: number[] = [];
for (let i = 0, len = text.length; i < len; i++) {
let line = createSplitLine(this.linePositionMapperFactory, text[i], this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, !isInHiddenArea);
for (let i = 0, len = lineBreaks.length; i < len; i++) {
let line = createSplitLine(lineBreaks[i], !isInHiddenArea);
insertLines.push(line);
let outputLineCount = line.getViewLineCount();
@@ -430,7 +582,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesInsertedEvent(outputFromLineNumber, outputFromLineNumber + totalOutputLineCount - 1);
}
public onModelLineChanged(versionId: number, lineNumber: number, newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
public onModelLineChanged(versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
if (versionId <= this._validModelVersionId) {
// Here we check for versionId in case the lines were reconstructed in the meantime.
// We don't want to apply stale change events on top of a newer read model state.
@@ -441,7 +593,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
let oldOutputLineCount = this.lines[lineIndex].getViewLineCount();
let isVisible = this.lines[lineIndex].isVisible();
let line = createSplitLine(this.linePositionMapperFactory, newText, this.tabSize, this.wrappingColumn, this.columnsForFullWidthChar, this.wrappingIndent, isVisible);
let line = createSplitLine(lineBreakData, isVisible);
this.lines[lineIndex] = line;
let newOutputLineCount = this.lines[lineIndex].getViewLineCount();
@@ -488,7 +640,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineCount(): number {
this._ensureValidState();
return this.prefixSumComputer.getTotalValue();
}
@@ -496,22 +647,14 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
if (viewLineNumber < 1) {
return 1;
}
let viewLineCount = this.getViewLineCount();
const viewLineCount = this.getViewLineCount();
if (viewLineNumber > viewLineCount) {
return viewLineCount;
}
return viewLineNumber;
}
/**
* Gives a hint that a lot of requests are about to come in for these line numbers.
*/
public warmUpLookupCache(viewStartLineNumber: number, viewEndLineNumber: number): void {
this.prefixSumComputer.warmUpCache(viewStartLineNumber - 1, viewEndLineNumber - 1);
return viewLineNumber | 0;
}
public getActiveIndentGuide(viewLineNumber: number, minLineNumber: number, maxLineNumber: number): IActiveIndentGuideInfo {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
minLineNumber = this._toValidViewLineNumber(minLineNumber);
maxLineNumber = this._toValidViewLineNumber(maxLineNumber);
@@ -531,7 +674,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLinesIndentGuides(viewStartLineNumber: number, viewEndLineNumber: number): number[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
@@ -602,7 +744,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineContent(viewLineNumber: number): string {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -612,7 +753,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineLength(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -622,7 +762,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineMinColumn(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -632,7 +771,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineMaxColumn(viewLineNumber: number): number {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -642,7 +780,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLineData(viewLineNumber: number): ViewLineData {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
let lineIndex = r.index;
@@ -652,7 +789,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public getViewLinesData(viewStartLineNumber: number, viewEndLineNumber: number, needed: boolean[]): ViewLineData[] {
this._ensureValidState();
viewStartLineNumber = this._toValidViewLineNumber(viewStartLineNumber);
viewEndLineNumber = this._toValidViewLineNumber(viewEndLineNumber);
@@ -691,7 +827,6 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
}
public validateViewPosition(viewLineNumber: number, viewColumn: number, expectedModelPosition: Position): Position {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@@ -719,8 +854,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return this.convertModelPositionToViewPosition(expectedModelPosition.lineNumber, expectedModelPosition.column);
}
public validateViewRange(viewRange: Range, expectedModelRange: Range): Range {
const validViewStart = this.validateViewPosition(viewRange.startLineNumber, viewRange.startColumn, expectedModelRange.getStartPosition());
const validViewEnd = this.validateViewPosition(viewRange.endLineNumber, viewRange.endColumn, expectedModelRange.getEndPosition());
return new Range(validViewStart.lineNumber, validViewStart.column, validViewEnd.lineNumber, validViewEnd.column);
}
public convertViewPositionToModelPosition(viewLineNumber: number, viewColumn: number): Position {
this._ensureValidState();
viewLineNumber = this._toValidViewLineNumber(viewLineNumber);
let r = this.prefixSumComputer.getIndexOf(viewLineNumber - 1);
@@ -732,8 +872,13 @@ export class SplitLinesCollection implements IViewModelLinesCollection {
return this.model.validatePosition(new Position(lineIndex + 1, inputColumn));
}
public convertViewRangeToModelRange(viewRange: Range): Range {
const start = this.convertViewPositionToModelPosition(viewRange.startLineNumber, viewRange.startColumn);
const end = this.convertViewPositionToModelPosition(viewRange.endLineNumber, viewRange.endColumn);
return new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
public convertModelPositionToViewPosition(_modelLineNumber: number, _modelColumn: number): Position {
this._ensureValidState();
const validPosition = this.model.validatePosition(new Position(_modelLineNumber, _modelColumn));
const inputLineNumber = validPosition.lineNumber;
@@ -898,6 +1043,10 @@ class VisibleIdentitySplitLine implements ISplitLine {
return InvisibleIdentitySplitLine.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
return null;
}
public getViewLineCount(): number {
return 1;
}
@@ -926,6 +1075,7 @@ class VisibleIdentitySplitLine implements ISplitLine {
false,
1,
lineContent.length + 1,
0,
lineTokens.inflate()
);
}
@@ -968,6 +1118,10 @@ class InvisibleIdentitySplitLine implements ISplitLine {
return VisibleIdentitySplitLine.INSTANCE;
}
public getLineBreakData(): LineBreakData | null {
return null;
}
public getViewLineCount(): number {
return 0;
}
@@ -1011,18 +1165,11 @@ class InvisibleIdentitySplitLine implements ISplitLine {
export class SplitLine implements ISplitLine {
private readonly positionMapper: ILineMapping;
private readonly outputLineCount: number;
private readonly wrappedIndent: string;
private readonly wrappedIndentLength: number;
private readonly _lineBreakData: LineBreakData;
private _isVisible: boolean;
constructor(positionMapper: ILineMapping, isVisible: boolean) {
this.positionMapper = positionMapper;
this.wrappedIndent = this.positionMapper.getWrappedLinesIndent();
this.wrappedIndentLength = this.wrappedIndent.length;
this.outputLineCount = this.positionMapper.getOutputLineCount();
constructor(lineBreakData: LineBreakData, isVisible: boolean) {
this._lineBreakData = lineBreakData;
this._isVisible = isVisible;
}
@@ -1035,22 +1182,26 @@ export class SplitLine implements ISplitLine {
return this;
}
public getLineBreakData(): LineBreakData | null {
return this._lineBreakData;
}
public getViewLineCount(): number {
if (!this._isVisible) {
return 0;
}
return this.outputLineCount;
return this._lineBreakData.breakOffsets.length;
}
private getInputStartOffsetOfOutputLineIndex(outputLineIndex: number): number {
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, 0);
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, 0);
}
private getInputEndOffsetOfOutputLineIndex(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): number {
if (outputLineIndex + 1 === this.outputLineCount) {
if (outputLineIndex + 1 === this._lineBreakData.breakOffsets.length) {
return model.getLineMaxColumn(modelLineNumber) - 1;
}
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex + 1, 0);
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex + 1, 0);
}
public getViewLineContent(model: ISimpleModel, modelLineNumber: number, outputLineIndex: number): string {
@@ -1067,7 +1218,7 @@ export class SplitLine implements ISplitLine {
});
if (outputLineIndex > 0) {
r = this.wrappedIndent + r;
r = spaces(this._lineBreakData.wrappedTextIndentLength) + r;
}
return r;
@@ -1082,7 +1233,7 @@ export class SplitLine implements ISplitLine {
let r = endOffset - startOffset;
if (outputLineIndex > 0) {
r = this.wrappedIndent.length + r;
r = this._lineBreakData.wrappedTextIndentLength + r;
}
return r;
@@ -1093,7 +1244,7 @@ export class SplitLine implements ISplitLine {
throw new Error('Not supported');
}
if (outputLineIndex > 0) {
return this.wrappedIndentLength + 1;
return this._lineBreakData.wrappedTextIndentLength + 1;
}
return 1;
}
@@ -1121,25 +1272,28 @@ export class SplitLine implements ISplitLine {
});
if (outputLineIndex > 0) {
lineContent = this.wrappedIndent + lineContent;
lineContent = spaces(this._lineBreakData.wrappedTextIndentLength) + lineContent;
}
let minColumn = (outputLineIndex > 0 ? this.wrappedIndentLength + 1 : 1);
let minColumn = (outputLineIndex > 0 ? this._lineBreakData.wrappedTextIndentLength + 1 : 1);
let maxColumn = lineContent.length + 1;
let continuesWithWrappedLine = (outputLineIndex + 1 < this.getViewLineCount());
let deltaStartIndex = 0;
if (outputLineIndex > 0) {
deltaStartIndex = this.wrappedIndentLength;
deltaStartIndex = this._lineBreakData.wrappedTextIndentLength;
}
let lineTokens = model.getLineTokens(modelLineNumber);
const startVisibleColumn = (outputLineIndex === 0 ? 0 : this._lineBreakData.breakOffsetsVisibleColumn[outputLineIndex - 1]);
return new ViewLineData(
lineContent,
continuesWithWrappedLine,
minColumn,
maxColumn,
startVisibleColumn,
lineTokens.sliceAndInflate(startOffset, endOffset, deltaStartIndex)
);
}
@@ -1165,25 +1319,25 @@ export class SplitLine implements ISplitLine {
}
let adjustedColumn = outputColumn - 1;
if (outputLineIndex > 0) {
if (adjustedColumn < this.wrappedIndentLength) {
if (adjustedColumn < this._lineBreakData.wrappedTextIndentLength) {
adjustedColumn = 0;
} else {
adjustedColumn -= this.wrappedIndentLength;
adjustedColumn -= this._lineBreakData.wrappedTextIndentLength;
}
}
return this.positionMapper.getInputOffsetOfOutputPosition(outputLineIndex, adjustedColumn) + 1;
return LineBreakData.getInputOffsetOfOutputPosition(this._lineBreakData.breakOffsets, outputLineIndex, adjustedColumn) + 1;
}
public getViewPositionOfModelPosition(deltaLineNumber: number, inputColumn: number): Position {
if (!this._isVisible) {
throw new Error('Not supported');
}
let r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
let r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
let outputLineIndex = r.outputLineIndex;
let outputColumn = r.outputOffset + 1;
if (outputLineIndex > 0) {
outputColumn += this.wrappedIndentLength;
outputColumn += this._lineBreakData.wrappedTextIndentLength;
}
// console.log('in -> out ' + deltaLineNumber + ',' + inputColumn + ' ===> ' + (deltaLineNumber+outputLineIndex) + ',' + outputColumn);
@@ -1194,21 +1348,33 @@ export class SplitLine implements ISplitLine {
if (!this._isVisible) {
throw new Error('Not supported');
}
const r = this.positionMapper.getOutputPositionOfInputOffset(inputColumn - 1);
const r = LineBreakData.getOutputPositionOfInputOffset(this._lineBreakData.breakOffsets, inputColumn - 1);
return (deltaLineNumber + r.outputLineIndex);
}
}
function createSplitLine(linePositionMapperFactory: ILineMapperFactory, text: string, tabSize: number, wrappingColumn: number, columnsForFullWidthChar: number, wrappingIndent: WrappingIndent, isVisible: boolean): ISplitLine {
let positionMapper = linePositionMapperFactory.createLineMapping(text, tabSize, wrappingColumn, columnsForFullWidthChar, wrappingIndent);
if (positionMapper === null) {
let _spaces: string[] = [''];
function spaces(count: number): string {
if (count >= _spaces.length) {
for (let i = 1; i <= count; i++) {
_spaces[i] = _makeSpaces(i);
}
}
return _spaces[count];
}
function _makeSpaces(count: number): string {
return new Array(count + 1).join(' ');
}
function createSplitLine(lineBreakData: LineBreakData | null, isVisible: boolean): ISplitLine {
if (lineBreakData === null) {
// No mapping needed
if (isVisible) {
return VisibleIdentitySplitLine.INSTANCE;
}
return InvisibleIdentitySplitLine.INSTANCE;
} else {
return new SplitLine(positionMapper, isVisible);
return new SplitLine(lineBreakData, isVisible);
}
}
@@ -1294,10 +1460,22 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return false;
}
public setWrappingSettings(_wrappingIndent: WrappingIndent, _wrappingColumn: number, _columnsForFullWidthChar: number): boolean {
public setWrappingSettings(_fontInfo: FontInfo, _wrappingAlgorithm: 'monospace' | 'dom', _wrappingColumn: number, _wrappingIndent: WrappingIndent): boolean {
return false;
}
public createLineBreaksComputer(): ILineBreaksComputer {
let result: null[] = [];
return {
addRequest: (lineText: string, previousLineBreakData: LineBreakData | null) => {
result.push(null);
},
finalize: () => {
return result;
}
};
}
public onModelFlushed(): void {
}
@@ -1305,11 +1483,11 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return new viewEvents.ViewLinesDeletedEvent(fromLineNumber, toLineNumber);
}
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, _text: string[]): viewEvents.ViewLinesInsertedEvent | null {
public onModelLinesInserted(_versionId: number, fromLineNumber: number, toLineNumber: number, lineBreaks: (LineBreakData | null)[]): viewEvents.ViewLinesInsertedEvent | null {
return new viewEvents.ViewLinesInsertedEvent(fromLineNumber, toLineNumber);
}
public onModelLineChanged(_versionId: number, lineNumber: number, _newText: string): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
public onModelLineChanged(_versionId: number, lineNumber: number, lineBreakData: LineBreakData | null): [boolean, viewEvents.ViewLinesChangedEvent | null, viewEvents.ViewLinesInsertedEvent | null, viewEvents.ViewLinesDeletedEvent | null] {
return [false, new viewEvents.ViewLinesChangedEvent(lineNumber, lineNumber), null, null];
}
@@ -1320,9 +1498,6 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
return this.model.getLineCount();
}
public warmUpLookupCache(_viewStartLineNumber: number, _viewEndLineNumber: number): void {
}
public getActiveIndentGuide(viewLineNumber: number, _minLineNumber: number, _maxLineNumber: number): IActiveIndentGuideInfo {
return {
startLineNumber: viewLineNumber,
@@ -1364,6 +1539,7 @@ export class IdentityLinesCollection implements IViewModelLinesCollection {
false,
1,
lineContent.length + 1,
0,
lineTokens.inflate()
);
}

View File

@@ -36,6 +36,9 @@ export class ViewEventHandler extends Disposable {
public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean {
return false;
}
public onContentSizeChanged(e: viewEvents.ViewContentSizeChangedEvent): boolean {
return false;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
return false;
}
@@ -69,6 +72,9 @@ export class ViewEventHandler extends Disposable {
public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean {
return false;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
return false;
}
public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean {
return false;
}
@@ -78,9 +84,6 @@ export class ViewEventHandler extends Disposable {
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
return false;
}
public onThemeChanged(e: viewEvents.ViewThemeChangedEvent): boolean {
return false;
}
// --- end event handlers
@@ -99,6 +102,12 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewContentSizeChanged:
if (this.onContentSizeChanged(e)) {
shouldRender = true;
}
break;
case viewEvents.ViewEventType.ViewCursorStateChanged:
if (this.onCursorStateChanged(e)) {
shouldRender = true;
@@ -171,6 +180,12 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewThemeChanged:
if (this.onThemeChanged(e)) {
shouldRender = true;
}
break;
case viewEvents.ViewEventType.ViewTokensColorsChanged:
if (this.onTokensColorsChanged(e)) {
shouldRender = true;
@@ -183,12 +198,6 @@ export class ViewEventHandler extends Disposable {
}
break;
case viewEvents.ViewEventType.ViewThemeChanged:
if (this.onThemeChanged(e)) {
shouldRender = true;
}
break;
default:
console.info('View received unknown event: ');
console.info(e);

View File

@@ -41,7 +41,7 @@ export class Viewport {
export interface IViewLayout {
readonly scrollable: Scrollable;
getScrollable(): Scrollable;
onMaxLineWidthChanged(width: number): void;
@@ -174,6 +174,10 @@ export class ViewLineData {
* The maximum allowed column at this view line.
*/
public readonly maxColumn: number;
/**
* The visible column at the start of the line (after the fauxIndent).
*/
public readonly startVisibleColumn: number;
/**
* The tokens at this view line.
*/
@@ -184,12 +188,14 @@ export class ViewLineData {
continuesWithWrappedLine: boolean,
minColumn: number,
maxColumn: number,
startVisibleColumn: number,
tokens: IViewLineTokens
) {
this.content = content;
this.continuesWithWrappedLine = continuesWithWrappedLine;
this.minColumn = minColumn;
this.maxColumn = maxColumn;
this.startVisibleColumn = startVisibleColumn;
this.tokens = tokens;
}
}
@@ -231,6 +237,10 @@ export class ViewLineRenderingData {
* The tab size for this view model.
*/
public readonly tabSize: number;
/**
* The visible column at the start of the line (after the fauxIndent)
*/
public readonly startVisibleColumn: number;
constructor(
minColumn: number,
@@ -241,7 +251,8 @@ export class ViewLineRenderingData {
mightContainNonBasicASCII: boolean,
tokens: IViewLineTokens,
inlineDecorations: InlineDecoration[],
tabSize: number
tabSize: number,
startVisibleColumn: number
) {
this.minColumn = minColumn;
this.maxColumn = maxColumn;
@@ -254,6 +265,7 @@ export class ViewLineRenderingData {
this.tokens = tokens;
this.inlineDecorations = inlineDecorations;
this.tabSize = tabSize;
this.startVisibleColumn = startVisibleColumn;
}
public static isBasicASCII(lineContent: string, mightContainNonBasicASCII: boolean): boolean {

View File

@@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings';
import { ConfigurationChangedEvent, EDITOR_FONT_DEFAULTS, EditorOption } from 'vs/editor/common/config/editorOptions';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { IConfiguration, IViewState } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness, TextModelResolvedOptions } from 'vs/editor/common/model';
import { ModelDecorationOverviewRulerOptions, ModelDecorationMinimapOptions } from 'vs/editor/common/model/textModel';
import * as textModelEvents from 'vs/editor/common/model/textModelEvents';
@@ -18,8 +18,7 @@ import { tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer';
import { MinimapTokensColorTracker } from 'vs/editor/common/viewModel/minimapTokensColorTracker';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout';
import { CharacterHardWrappingLineMapperFactory } from 'vs/editor/common/viewModel/characterHardWrappingLineMapper';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection } from 'vs/editor/common/viewModel/splitLinesCollection';
import { IViewModelLinesCollection, IdentityLinesCollection, SplitLinesCollection, ILineBreaksComputerFactory } from 'vs/editor/common/viewModel/splitLinesCollection';
import { ICoordinatesConverter, IOverviewRulerDecorations, IViewModel, MinimapLinesRenderingData, ViewLineData, ViewLineRenderingData, ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel';
import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecorations';
import { ITheme } from 'vs/platform/theme/common/themeService';
@@ -31,7 +30,7 @@ const USE_IDENTITY_LINES_COLLECTION = true;
export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel {
private readonly editorId: number;
private readonly configuration: editorCommon.IConfiguration;
private readonly configuration: IConfiguration;
private readonly model: ITextModel;
private readonly _tokenizeViewportSoon: RunOnceScheduler;
private hasFocus: boolean;
@@ -43,7 +42,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
public readonly viewLayout: ViewLayout;
private readonly decorations: ViewModelDecorations;
constructor(editorId: number, configuration: editorCommon.IConfiguration, model: ITextModel, scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable) {
constructor(
editorId: number,
configuration: IConfiguration,
model: ITextModel,
domLineBreaksComputerFactory: ILineBreaksComputerFactory,
monospaceLineBreaksComputerFactory: ILineBreaksComputerFactory,
scheduleAtNextAnimationFrame: (callback: () => void) => IDisposable
) {
super();
this.editorId = editorId;
@@ -61,25 +67,19 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} else {
const options = this.configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const fontInfo = options.get(EditorOption.fontInfo);
const wordWrapBreakAfterCharacters = options.get(EditorOption.wordWrapBreakAfterCharacters);
const wordWrapBreakBeforeCharacters = options.get(EditorOption.wordWrapBreakBeforeCharacters);
const wordWrapBreakObtrusiveCharacters = options.get(EditorOption.wordWrapBreakObtrusiveCharacters);
const wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm);
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const wrappingIndent = options.get(EditorOption.wrappingIndent);
let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory(
wordWrapBreakBeforeCharacters,
wordWrapBreakAfterCharacters,
wordWrapBreakObtrusiveCharacters
);
this.lines = new SplitLinesCollection(
this.model,
hardWrappingLineMapperFactory,
domLineBreaksComputerFactory,
monospaceLineBreaksComputerFactory,
fontInfo,
this.model.getOptions().tabSize,
wrappingAlgorithm,
wrappingInfo.wrappingColumn,
fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth,
wrappingIndent
);
}
@@ -100,6 +100,15 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
}));
this._register(this.viewLayout.onDidContentSizeChange((e) => {
try {
const eventsCollector = this._beginEmit();
eventsCollector.emit(new viewEvents.ViewContentSizeChangedEvent(e));
} finally {
this._endEmit();
}
}));
this.decorations = new ViewModelDecorations(this.editorId, this.model, this.configuration, this.lines, this.coordinatesConverter);
this._registerModelEvents();
@@ -155,11 +164,12 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
let restorePreviousViewportStart = false;
const options = this.configuration.options;
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const fontInfo = options.get(EditorOption.fontInfo);
const wrappingAlgorithm = options.get(EditorOption.wrappingAlgorithm);
const wrappingInfo = options.get(EditorOption.wrappingInfo);
const wrappingIndent = options.get(EditorOption.wrappingIndent);
if (this.lines.setWrappingSettings(wrappingIndent, wrappingInfo.wrappingColumn, fontInfo.typicalFullwidthCharacterWidth / fontInfo.typicalHalfwidthCharacterWidth)) {
if (this.lines.setWrappingSettings(fontInfo, wrappingAlgorithm, wrappingInfo.wrappingColumn, wrappingIndent)) {
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent());
@@ -200,8 +210,26 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const changes = e.changes;
const versionId = e.versionId;
for (let j = 0, lenJ = changes.length; j < lenJ; j++) {
const change = changes[j];
// Do a first pass to compute line mappings, and a second pass to actually interpret them
const lineBreaksComputer = this.lines.createLineBreaksComputer();
for (const change of changes) {
switch (change.changeType) {
case textModelEvents.RawContentChangedType.LinesInserted: {
for (const line of change.detail) {
lineBreaksComputer.addRequest(line, null);
}
break;
}
case textModelEvents.RawContentChangedType.LineChanged: {
lineBreaksComputer.addRequest(change.detail, null);
break;
}
}
}
const lineBreaks = lineBreaksComputer.finalize();
let lineBreaksOffset = 0;
for (const change of changes) {
switch (change.changeType) {
case textModelEvents.RawContentChangedType.Flush: {
@@ -222,7 +250,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break;
}
case textModelEvents.RawContentChangedType.LinesInserted: {
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail);
const insertedLineBreaks = lineBreaks.slice(lineBreaksOffset, lineBreaksOffset + change.detail.length);
lineBreaksOffset += change.detail.length;
const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, insertedLineBreaks);
if (linesInsertedEvent !== null) {
eventsCollector.emit(linesInsertedEvent);
this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber);
@@ -231,7 +262,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
break;
}
case textModelEvents.RawContentChangedType.LineChanged: {
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, change.detail);
const changedLineBreakData = lineBreaks[lineBreaksOffset];
lineBreaksOffset++;
const [lineMappingChanged, linesChangedEvent, linesInsertedEvent, linesDeletedEvent] = this.lines.onModelLineChanged(versionId, change.lineNumber, changedLineBreakData);
hadModelLineChangeThatChangedLineMapping = lineMappingChanged;
if (linesChangedEvent) {
eventsCollector.emit(linesChangedEvent);
@@ -422,7 +456,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
);
}
public saveState(): editorCommon.IViewState {
public saveState(): IViewState {
const compatViewState = this.viewLayout.saveState();
const scrollTop = compatViewState.scrollTop;
@@ -437,7 +471,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
};
}
public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
public reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; } {
if (typeof state.firstPosition === 'undefined') {
// This is a view state serialized by an older version
return this._reduceRestoreStateCompatibility(state);
@@ -452,7 +486,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
};
}
private _reduceRestoreStateCompatibility(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } {
private _reduceRestoreStateCompatibility(state: IViewState): { scrollLeft: number; scrollTop: number; } {
return {
scrollLeft: state.scrollLeft,
scrollTop: state.scrollTopWithoutViewZones!
@@ -475,8 +509,6 @@ 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.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);
@@ -546,7 +578,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
mightContainNonBasicASCII,
lineData.tokens,
inlineDecorations,
tabSize
tabSize,
lineData.startVisibleColumn
);
}