Have the cursor be driven (with events) by the view model

This commit is contained in:
Alex Dima 2020-05-14 14:53:28 +02:00
parent cda6e7d7a5
commit 798a33f9c6
No known key found for this signature in database
GPG key ID: 6E58D7B045760DA0
9 changed files with 126 additions and 125 deletions

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Selection } from 'vs/editor/common/core/selection';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { onUnexpectedError } from 'vs/base/common/errors';
@ -36,7 +37,6 @@ import { ScrollDecorationViewPart } from 'vs/editor/browser/viewParts/scrollDeco
import { SelectionsOverlay } from 'vs/editor/browser/viewParts/selections/selections';
import { ViewCursors } from 'vs/editor/browser/viewParts/viewCursors/viewCursors';
import { ViewZones } from 'vs/editor/browser/viewParts/viewZones/viewZones';
import { Cursor } from 'vs/editor/common/controller/cursor';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
@ -68,7 +68,7 @@ export class View extends ViewEventHandler {
private _scrollbar: EditorScrollbar;
private readonly _context: ViewContext;
private readonly _cursor: Cursor;
private _selections: Selection[];
// The view lines
private viewLines: ViewLines;
@ -98,11 +98,10 @@ export class View extends ViewEventHandler {
configuration: IConfiguration,
themeService: IThemeService,
model: IViewModel,
cursor: Cursor,
outgoingEvents: ViewOutgoingEvents
) {
super();
this._cursor = cursor;
this._selections = [new Selection(1, 1, 1, 1)];
this._renderAnimationFrame = null;
this.outgoingEvents = outgoingEvents;
@ -229,10 +228,6 @@ export class View extends ViewEventHandler {
this._register(model.addViewEventListener((events: viewEvents.ViewEvent[]) => {
this.eventDispatcher.emitMany(events);
}));
this._register(this._cursor.addViewEventListener((events: viewEvents.ViewEvent[]) => {
this.eventDispatcher.emitMany(events);
}));
}
private _flushAccumulatedAndRenderNow(): void {
@ -315,6 +310,10 @@ export class View extends ViewEventHandler {
this.outgoingEvents.emitContentSizeChange(e);
return false;
}
public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean {
this._selections = e.selections;
return false;
}
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
this.domNode.setClassName(this.getEditorClassName());
this._context.model.setHasFocus(e.isFocused);
@ -404,7 +403,7 @@ export class View extends ViewEventHandler {
this._context.model.setViewport(partialViewportData.startLineNumber, partialViewportData.endLineNumber, partialViewportData.centeredLineNumber);
const viewportData = new ViewportData(
this._cursor.getViewSelections(),
this._selections,
partialViewportData,
this._context.viewLayout.getWhitespaceViewportData(),
this._context.model

View file

@ -80,15 +80,13 @@ export interface ICodeEditorWidgetOptions {
class ModelData {
public readonly model: ITextModel;
public readonly viewModel: ViewModel;
public readonly cursor: Cursor;
public readonly view: View;
public readonly hasRealView: boolean;
public readonly listenersToRemove: IDisposable[];
constructor(model: ITextModel, viewModel: ViewModel, cursor: Cursor, view: View, hasRealView: boolean, listenersToRemove: IDisposable[]) {
constructor(model: ITextModel, viewModel: ViewModel, view: View, hasRealView: boolean, listenersToRemove: IDisposable[]) {
this.model = model;
this.viewModel = viewModel;
this.cursor = cursor;
this.view = view;
this.hasRealView = hasRealView;
this.listenersToRemove = listenersToRemove;
@ -100,7 +98,6 @@ class ModelData {
if (this.hasRealView) {
this.view.dispose();
}
this.cursor.dispose();
this.viewModel.dispose();
}
}
@ -530,7 +527,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (!this._modelData) {
return null;
}
return this._modelData.cursor.getPosition();
return this._modelData.viewModel.cursor.getPosition();
}
public setPosition(position: IPosition): void {
@ -540,7 +537,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (!Position.isIPosition(position)) {
throw new Error('Invalid arguments');
}
this._modelData.cursor.setSelections('api', [{
this._modelData.viewModel.cursor.setSelections('api', [{
selectionStartLineNumber: position.lineNumber,
selectionStartColumn: position.column,
positionLineNumber: position.lineNumber,
@ -558,7 +555,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
const validatedModelRange = this._modelData.model.validateRange(modelRange);
const viewRange = this._modelData.viewModel.coordinatesConverter.convertModelRangeToViewRange(validatedModelRange);
this._modelData.cursor.emitCursorRevealRange('api', viewRange, null, verticalType, revealHorizontal, scrollType);
this._modelData.viewModel.cursor.emitCursorRevealRange('api', viewRange, null, verticalType, revealHorizontal, scrollType);
}
public revealLine(lineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
@ -643,14 +640,14 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (!this._modelData) {
return null;
}
return this._modelData.cursor.getSelection();
return this._modelData.viewModel.cursor.getSelection();
}
public getSelections(): Selection[] | null {
if (!this._modelData) {
return null;
}
return this._modelData.cursor.getSelections();
return this._modelData.viewModel.cursor.getSelections();
}
public setSelection(range: IRange): void;
@ -684,7 +681,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return;
}
const selection = new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn);
this._modelData.cursor.setSelections('api', [selection]);
this._modelData.viewModel.cursor.setSelections('api', [selection]);
}
public revealLines(startLineNumber: number, endLineNumber: number, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
@ -815,7 +812,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
throw new Error('Invalid arguments');
}
}
this._modelData.cursor.setSelections(source, ranges);
this._modelData.viewModel.cursor.setSelections(source, ranges);
}
public getContentWidth(): number {
@ -901,7 +898,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
}
}
const cursorState = this._modelData.cursor.saveState();
const cursorState = this._modelData.viewModel.cursor.saveState();
const viewState = this._modelData.viewModel.saveState();
return {
cursorState: cursorState,
@ -918,10 +915,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (codeEditorState && codeEditorState.cursorState && codeEditorState.viewState) {
const cursorState = <any>codeEditorState.cursorState;
if (Array.isArray(cursorState)) {
this._modelData.cursor.restoreState(<editorCommon.ICursorState[]>cursorState);
this._modelData.viewModel.cursor.restoreState(<editorCommon.ICursorState[]>cursorState);
} else {
// Backwards compatibility
this._modelData.cursor.restoreState([<editorCommon.ICursorState>cursorState]);
this._modelData.viewModel.cursor.restoreState([<editorCommon.ICursorState>cursorState]);
}
const contributionsState = codeEditorState.contributionsState || {};
@ -988,7 +985,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (source === 'keyboard') {
this._onWillType.fire(payload.text);
}
this._modelData.cursor.trigger(source, handlerId, payload);
this._modelData.viewModel.cursor.trigger(source, handlerId, payload);
if (source === 'keyboard') {
this._onDidType.fire(payload.text);
}
@ -1001,9 +998,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
// nothing to do
return;
}
const startPosition = this._modelData.cursor.getSelection().getStartPosition();
this._modelData.cursor.trigger(source, handlerId, payload);
const endPosition = this._modelData.cursor.getSelection().getStartPosition();
const startPosition = this._modelData.viewModel.cursor.getSelection().getStartPosition();
this._modelData.viewModel.cursor.trigger(source, handlerId, payload);
const endPosition = this._modelData.viewModel.cursor.getSelection().getStartPosition();
if (source === 'keyboard') {
this._onDidPaste.fire(
{
@ -1029,7 +1026,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
return;
}
this._modelData.cursor.trigger(source, handlerId, payload);
this._modelData.viewModel.cursor.trigger(source, handlerId, payload);
if (handlerId === editorCommon.Handler.CompositionStart) {
this._onDidCompositionStart.fire();
@ -1057,7 +1054,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (!this._modelData) {
return null;
}
return this._modelData.cursor;
return this._modelData.viewModel.cursor;
}
public _getViewModel(): IViewModel | null {
@ -1097,7 +1094,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
cursorStateComputer = endCursorState;
}
this._modelData.cursor.executeEdits(source, edits, cursorStateComputer);
this._modelData.viewModel.cursor.executeEdits(source, edits, cursorStateComputer);
return true;
}
@ -1105,14 +1102,14 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (!this._modelData) {
return;
}
this._modelData.cursor.trigger(source, editorCommon.Handler.ExecuteCommand, command);
this._modelData.viewModel.cursor.trigger(source, editorCommon.Handler.ExecuteCommand, command);
}
public executeCommands(source: string | null | undefined, commands: editorCommon.ICommand[]): void {
if (!this._modelData) {
return;
}
this._modelData.cursor.trigger(source, editorCommon.Handler.ExecuteCommands, commands);
this._modelData.viewModel.cursor.trigger(source, editorCommon.Handler.ExecuteCommands, commands);
}
public changeDecorations(callback: (changeAccessor: IModelDecorationsChangeAccessor) => any): any {
@ -1445,17 +1442,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
// Someone might destroy the model from under the editor, so prevent any exceptions by setting a null model
listenersToRemove.push(model.onWillDispose(() => this.setModel(null)));
const cursor = new Cursor(this._configuration, model, viewModel, viewModel.coordinatesConverter);
listenersToRemove.push(cursor.onDidReachMaxCursorCount(() => {
listenersToRemove.push(viewModel.cursor.onDidReachMaxCursorCount(() => {
this._notificationService.warn(nls.localize('cursors.maximum', "The number of cursors has been limited to {0}.", Cursor.MAX_CURSOR_COUNT));
}));
listenersToRemove.push(cursor.onDidAttemptReadOnlyEdit(() => {
listenersToRemove.push(viewModel.cursor.onDidAttemptReadOnlyEdit(() => {
this._onDidAttemptReadOnlyEdit.fire(undefined);
}));
listenersToRemove.push(cursor.onDidChange((e: CursorStateChangedEvent) => {
listenersToRemove.push(viewModel.cursor.onDidChange((e: CursorStateChangedEvent) => {
const positions: Position[] = [];
for (let i = 0, len = e.selections.length; i < len; i++) {
positions[i] = e.selections[i].getPosition();
@ -1481,7 +1476,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
this._onDidChangeCursorSelection.fire(e2);
}));
const [view, hasRealView] = this._createView(viewModel, cursor);
const [view, hasRealView] = this._createView(viewModel);
if (hasRealView) {
this._domElement.appendChild(view.domNode.domNode);
@ -1501,15 +1496,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
view.domNode.domNode.setAttribute('data-uri', model.uri.toString());
}
this._modelData = new ModelData(model, viewModel, cursor, view, hasRealView, listenersToRemove);
this._modelData = new ModelData(model, viewModel, view, hasRealView, listenersToRemove);
}
protected _createView(viewModel: ViewModel, cursor: Cursor): [View, boolean] {
protected _createView(viewModel: ViewModel): [View, boolean] {
let commandDelegate: ICommandDelegate;
if (this.isSimpleWidget) {
commandDelegate = {
executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => {
editorCommand.runCoreEditorCommand(cursor, args);
editorCommand.runCoreEditorCommand(viewModel.cursor, args);
},
paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => {
this.trigger('keyboard', editorCommon.Handler.Paste, { text, pasteOnNewLine, multicursorText, mode });
@ -1533,7 +1528,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
} else {
commandDelegate = {
executeEditorCommand: (editorCommand: CoreEditorCommand, args: any): void => {
editorCommand.runCoreEditorCommand(cursor, args);
editorCommand.runCoreEditorCommand(viewModel.cursor, args);
},
paste: (text: string, pasteOnNewLine: boolean, multicursorText: string[] | null, mode: string | null) => {
this._commandService.executeCommand(editorCommon.Handler.Paste, {
@ -1568,7 +1563,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
const onDidChangeTextFocus = (textFocus: boolean) => {
if (this._modelData) {
this._modelData.cursor.setHasFocus(textFocus);
this._modelData.viewModel.cursor.setHasFocus(textFocus);
}
this._editorTextFocus.setValue(textFocus);
};
@ -1594,7 +1589,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
this._configuration,
this._themeService,
viewModel,
cursor,
viewOutgoingEvents
);

View file

@ -7,7 +7,7 @@ import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import * as strings from 'vs/base/common/strings';
import { CursorCollection } from 'vs/editor/common/controller/cursorCollection';
import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult, EditOperationType, IColumnSelectData, ICursors, PartialCursorState, RevealTarget, IReducedViewModel } from 'vs/editor/common/controller/cursorCommon';
import { CursorColumns, CursorConfiguration, CursorContext, CursorState, EditOperationResult, EditOperationType, IColumnSelectData, ICursors, PartialCursorState, RevealTarget, ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon';
import { DeleteOperations } from 'vs/editor/common/controller/cursorDeleteOperations';
import { CursorChangeReason } from 'vs/editor/common/controller/cursorEvents';
import { TypeOperations, TypeWithAutoClosingCommand } from 'vs/editor/common/controller/cursorTypeOperations';
@ -16,21 +16,12 @@ import { Range, IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { RawContentChangedType, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import { RawContentChangedType, ModelRawContentChangedEvent, IModelLanguageChangedEvent } from 'vs/editor/common/model/textModelEvents';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { dispose } from 'vs/base/common/lifecycle';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
import { EditorOption, ConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
function containsLineMappingChanged(events: viewEvents.ViewEvent[]): boolean {
for (let i = 0, len = events.length; i < len; i++) {
if (events[i].type === viewEvents.ViewEventType.ViewLineMappingChanged) {
return true;
}
}
return false;
}
export class CursorStateChangedEvent {
/**
* The new selections.
@ -182,7 +173,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
private readonly _configuration: editorCommon.IConfiguration;
private readonly _model: ITextModel;
private _knownModelVersionId: number;
private readonly _viewModel: IReducedViewModel;
private readonly _viewModel: ICursorSimpleModel;
private readonly _coordinatesConverter: ICoordinatesConverter;
public context: CursorContext;
private _cursors: CursorCollection;
@ -195,7 +186,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
private _autoClosedActions: AutoClosedAction[];
private _prevEditOperationType: EditOperationType;
constructor(configuration: editorCommon.IConfiguration, model: ITextModel, viewModel: IReducedViewModel, coordinatesConverter: ICoordinatesConverter) {
constructor(configuration: editorCommon.IConfiguration, model: ITextModel, viewModel: ICursorSimpleModel, coordinatesConverter: ICoordinatesConverter) {
super();
this._configuration = configuration;
this._model = model;
@ -212,53 +203,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._columnSelectData = null;
this._autoClosedActions = [];
this._prevEditOperationType = EditOperationType.Other;
this._register(this._model.onDidChangeRawContent((e) => {
this._knownModelVersionId = e.versionId;
if (this._isHandling) {
return;
}
this._onModelContentChanged(e);
}));
this._register(viewModel.addViewEventListener((events: viewEvents.ViewEvent[]) => {
if (!containsLineMappingChanged(events)) {
return;
}
if (this._knownModelVersionId !== this._model.getVersionId()) {
// There are model change events that I didn't yet receive.
//
// This can happen when editing the model, and the view model receives the change events first,
// and the view model emits line mapping changed events, all before the cursor gets a chance to
// recover from markers.
//
// The model change listener above will be called soon and we'll ensure a valid cursor state there.
return;
}
// Ensure valid state
this.setStates('viewModel', CursorChangeReason.NotSet, this.getAll());
}));
const updateCursorContext = () => {
this.context = new CursorContext(this._configuration, this._model, this._viewModel, this._coordinatesConverter);
this._cursors.updateContext(this.context);
};
this._register(this._model.onDidChangeLanguage((e) => {
updateCursorContext();
}));
this._register(this._model.onDidChangeLanguageConfiguration(() => {
updateCursorContext();
}));
this._register(this._model.onDidChangeOptions(() => {
updateCursorContext();
}));
this._register(this._configuration.onDidChange((e) => {
if (CursorConfiguration.shouldRecreate(e)) {
updateCursorContext();
}
}));
}
public dispose(): void {
@ -267,6 +211,44 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
super.dispose();
}
private _updateCursorContext(): void {
this.context = new CursorContext(this._configuration, this._model, this._viewModel, this._coordinatesConverter);
this._cursors.updateContext(this.context);
}
public onLineMappingChanged(): void {
if (this._knownModelVersionId !== this._model.getVersionId()) {
// There are model change events that I didn't yet receive.
//
// This can happen when editing the model, and the view model receives the change events first,
// and the view model emits line mapping changed events, all before the cursor gets a chance to
// recover from markers.
//
// The model change listener above will be called soon and we'll ensure a valid cursor state there.
return;
}
// Ensure valid state
this.setStates('viewModel', CursorChangeReason.NotSet, this.getAll());
}
public onDidChangeModelLanguage(e: IModelLanguageChangedEvent): void {
this._updateCursorContext();
}
public onDidChangeModelLanguageConfiguration(): void {
this._updateCursorContext();
}
public onDidChangeModelOptions(): void {
this._updateCursorContext();
}
public onDidChangeConfiguration(e: ConfigurationChangedEvent): void {
if (CursorConfiguration.shouldRecreate(e)) {
this._updateCursorContext();
}
}
public setHasFocus(hasFocus: boolean): void {
this._hasFocus = hasFocus;
}
@ -393,7 +375,12 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.reveal('restoreState', true, RevealTarget.Primary, editorCommon.ScrollType.Immediate);
}
private _onModelContentChanged(e: ModelRawContentChangedEvent): void {
public onModelContentChanged(e: ModelRawContentChangedEvent): void {
this._knownModelVersionId = e.versionId;
if (this._isHandling) {
return;
}
const hadFlushEvent = e.containsEvent(RawContentChangedType.Flush);
this._prevEditOperationType = EditOperationType.Other;
@ -441,10 +428,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
return this._cursors.getSelections();
}
public getViewSelections(): Selection[] {
return this._cursors.getViewSelections();
}
public getPosition(): Position {
return this._cursors.getPrimaryCursor().modelState.position;
}

View file

@ -17,7 +17,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { LanguageIdentifier } from 'vs/editor/common/modes';
import { IAutoClosingPair, StandardAutoClosingPairConditional } from 'vs/editor/common/modes/languageConfiguration';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { VerticalRevealType, IViewEventEmitter } from 'vs/editor/common/view/viewEvents';
import { VerticalRevealType } from 'vs/editor/common/view/viewEvents';
import { ICoordinatesConverter } from 'vs/editor/common/viewModel/viewModel';
import { Constants } from 'vs/base/common/uint';
@ -351,18 +351,15 @@ export class SingleCursorState {
}
}
export interface IReducedViewModel extends ICursorSimpleModel, IViewEventEmitter {
}
export class CursorContext {
_cursorContextBrand: void;
public readonly model: ITextModel;
public readonly viewModel: IReducedViewModel;
public readonly viewModel: ICursorSimpleModel;
public readonly coordinatesConverter: ICoordinatesConverter;
public readonly config: CursorConfiguration;
constructor(configuration: IConfiguration, model: ITextModel, viewModel: IReducedViewModel, coordinatesConverter: ICoordinatesConverter) {
constructor(configuration: IConfiguration, model: ITextModel, viewModel: ICursorSimpleModel, coordinatesConverter: ICoordinatesConverter) {
this.model = model;
this.viewModel = viewModel;
this.coordinatesConverter = coordinatesConverter;

View file

@ -14,7 +14,7 @@ import { IViewEventEmitter } from 'vs/editor/common/view/viewEvents';
import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData';
import { IEditorWhitespace, IWhitespaceChangeAccessor } from 'vs/editor/common/viewLayout/linesLayout';
import { EditorTheme } from 'vs/editor/common/view/viewContext';
import { IReducedViewModel } from 'vs/editor/common/controller/cursorCommon';
import { ICursorSimpleModel } from 'vs/editor/common/controller/cursorCommon';
export interface IViewWhitespaceViewportData {
readonly id: string;
@ -94,7 +94,7 @@ export interface ICoordinatesConverter {
modelPositionIsVisible(modelPosition: Position): boolean;
}
export interface IViewModel extends IViewEventEmitter, IReducedViewModel {
export interface IViewModel extends IViewEventEmitter, ICursorSimpleModel {
readonly coordinatesConverter: ICoordinatesConverter;

View file

@ -24,11 +24,11 @@ import { ViewModelDecorations } from 'vs/editor/common/viewModel/viewModelDecora
import { RunOnceScheduler } from 'vs/base/common/async';
import * as platform from 'vs/base/common/platform';
import { EditorTheme } from 'vs/editor/common/view/viewContext';
import { IReducedViewModel } from 'vs/editor/common/controller/cursorCommon';
import { Cursor } from 'vs/editor/common/controller/cursor';
const USE_IDENTITY_LINES_COLLECTION = true;
export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel, IReducedViewModel {
export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel {
private readonly editorId: number;
private readonly configuration: IConfiguration;
@ -42,6 +42,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
private readonly lines: IViewModelLinesCollection;
public readonly coordinatesConverter: ICoordinatesConverter;
public readonly viewLayout: ViewLayout;
public readonly cursor: Cursor;
private readonly decorations: ViewModelDecorations;
constructor(
@ -89,6 +90,18 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.coordinatesConverter = this.lines.createCoordinatesConverter();
this.cursor = this._register(new Cursor(this.configuration, model, this, this.coordinatesConverter));
this._register(this.cursor.addViewEventListener((events) => {
try {
const eventsCollector = this._beginEmitViewEvents();
for (const event of events) {
eventsCollector.emit(event);
}
} finally {
this._endEmitViewEvents();
}
}));
this.viewLayout = this._register(new ViewLayout(this.configuration, this.getLineCount(), scheduleAtNextAnimationFrame));
this._register(this.viewLayout.onDidScroll((e) => {
@ -167,6 +180,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.cursor.onLineMappingChanged();
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
@ -192,6 +206,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
const viewPositionTop = this.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber);
this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.viewportStartLineDelta }, ScrollType.Immediate);
}
this.cursor.onDidChangeConfiguration(e);
}
private _registerModelEvents(): void {
@ -288,6 +304,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
if (!hadOtherModelChange && hadModelLineChangeThatChangedLineMapping) {
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.cursor.onLineMappingChanged();
this.decorations.onLineMappingChanged();
}
} finally {
@ -308,6 +325,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.viewLayout.setScrollPosition({ scrollTop: viewPositionTop + this.viewportStartLineDelta }, ScrollType.Immediate);
}
}
this.cursor.onModelContentChanged(e);
}));
this._register(this.model.onDidChangeTokens((e) => {
@ -330,11 +349,17 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this._register(this.model.onDidChangeLanguageConfiguration((e) => {
this._emitSingleViewEvent(new viewEvents.ViewLanguageConfigurationEvent());
this.cursor.onDidChangeModelLanguageConfiguration();
}));
this._register(this.model.onDidChangeLanguage((e) => {
this.cursor.onDidChangeModelLanguage(e);
}));
this._register(this.model.onDidChangeOptions((e) => {
// A tab size change causes a line mapping changed event => all view parts will repaint OK, no further event needed here
if (this.lines.setTabSize(this.model.getOptions().tabSize)) {
this.cursor.onLineMappingChanged();
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
try {
@ -347,6 +372,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
}
this._updateConfigurationViewLineCount.schedule();
}
this.cursor.onDidChangeModelOptions();
}));
this._register(this.model.onDidChangeDecorations((e) => {
@ -363,6 +390,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
eventsCollector.emit(new viewEvents.ViewFlushedEvent());
eventsCollector.emit(new viewEvents.ViewLineMappingChangedEvent());
eventsCollector.emit(new viewEvents.ViewDecorationsChangedEvent(null));
this.cursor.onLineMappingChanged();
this.decorations.onLineMappingChanged();
this.viewLayout.onFlushed(this.getLineCount());
this.viewLayout.onHeightMaybeChanged();

View file

@ -33,7 +33,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor {
protected _createConfiguration(options: editorOptions.IEditorConstructionOptions): IConfiguration {
return new TestConfiguration(options);
}
protected _createView(viewModel: ViewModel, cursor: Cursor): [View, boolean] {
protected _createView(viewModel: ViewModel): [View, boolean] {
// Never create a view
return [null! as View, false];
}
@ -41,7 +41,7 @@ export class TestCodeEditor extends CodeEditorWidget implements ICodeEditor {
//#region Testing utils
public getCursor(): Cursor | undefined {
return this._modelData ? this._modelData.cursor : undefined;
return this._modelData ? this._modelData.viewModel.cursor : undefined;
}
public registerAndInstantiateContribution<T extends IEditorContribution, Services extends BrandedService[]>(id: string, ctor: new (editor: ICodeEditor, ...services: Services) => T): T {
const r: T = this._instantiationService.createInstance(ctor as IEditorContributionCtor, this);

View file

@ -77,7 +77,7 @@ suite('ViewModelDecorations', () => {
new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3))
).map((dec) => {
return dec.options.className;
});
}).filter(Boolean);
assert.deepEqual(actualDecorations, [
'dec1',
@ -292,7 +292,7 @@ suite('ViewModelDecorations', () => {
let decorations = viewModel.getDecorationsInViewport(
new Range(2, viewModel.getLineMinColumn(2), 3, viewModel.getLineMaxColumn(3))
);
).filter(x => Boolean(x.options.beforeContentClassName));
assert.deepEqual(decorations, []);
let inlineDecorations1 = viewModel.getViewLineRenderingData(

View file

@ -70,7 +70,7 @@ suite('ViewModel', () => {
model.undo();
viewLineCount.push(viewModel.getLineCount());
assert.deepEqual(viewLineCount, [4, 1, 1, 1]);
assert.deepEqual(viewLineCount, [4, 1, 1, 1, 1]);
});
});