| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675 |
- /**
- * @license Copyright (c) 2003-2017, CKSource - Frederico Knabben. All rights reserved.
- * For licensing, see LICENSE.md.
- */
- /**
- * @module engine/dev-utils/enableenginedebug
- */
- /* global console */
- import DeltaReplayer from './deltareplayer';
- import ModelPosition from '../model/position';
- import ModelRange from '../model/range';
- import ModelText from '../model/text';
- import ModelTextProxy from '../model/textproxy';
- import ModelElement from '../model/element';
- import Operation from '../model/operation/operation';
- import AttributeOperation from '../model/operation/attributeoperation';
- import DetachOperation from '../model/operation/detachoperation';
- import InsertOperation from '../model/operation/insertoperation';
- import MarkerOperation from '../model/operation/markeroperation';
- import MoveOperation from '../model/operation/moveoperation';
- import NoOperation from '../model/operation/nooperation';
- import RenameOperation from '../model/operation/renameoperation';
- import RootAttributeOperation from '../model/operation/rootattributeoperation';
- import Delta from '../model/delta/delta';
- import AttributeDelta from '../model/delta/attributedelta';
- import InsertDelta from '../model/delta/insertdelta';
- import MarkerDelta from '../model/delta/markerdelta';
- import MergeDelta from '../model/delta/mergedelta';
- import MoveDelta from '../model/delta/movedelta';
- import RenameDelta from '../model/delta/renamedelta';
- import RootAttributeDelta from '../model/delta/rootattributedelta';
- import SplitDelta from '../model/delta/splitdelta';
- import UnwrapDelta from '../model/delta/unwrapdelta';
- import WrapDelta from '../model/delta/wrapdelta';
- import deltaTransform from '../model/delta/transform';
- import ModelDocument from '../model/document';
- import ModelDocumentFragment from '../model/documentfragment';
- import ModelRootElement from '../model/rootelement';
- import ViewDocument from '../view/document';
- import ViewElement from '../view/element';
- import ViewText from '../view/text';
- import ViewTextProxy from '../view/textproxy';
- import ViewDocumentFragment from '../view/documentfragment';
- import Plugin from '@ckeditor/ckeditor5-core/src/plugin';
- import Editor from '@ckeditor/ckeditor5-core/src/editor/editor';
- import clone from '@ckeditor/ckeditor5-utils/src/lib/lodash/clone';
- const treeDump = Symbol( '_treeDump' );
- // Maximum number of stored states of model and view document.
- const maxTreeDumpLength = 20;
- // Separator used to separate stringified deltas
- const LOG_SEPARATOR = '-------';
- // Specified whether debug tools were already enabled.
- let enabled = false;
- // Logging function used to log debug messages.
- let logger = console;
- /**
- * Enhances model classes with logging methods. Returns a plugin that should be loaded in the editor to
- * enable debugging features.
- *
- * Every operation applied on {@link module:engine/model/document~Document model.Document} is logged.
- *
- * Following classes are expanded with `log` and meaningful `toString` methods:
- * * {@link module:engine/model/position~Position model.Position},
- * * {@link module:engine/model/range~Range model.Range},
- * * {@link module:engine/model/text~Text model.Text},
- * * {@link module:engine/model/element~Element model.Element},
- * * {@link module:engine/model/rootelement~RootElement model.RootElement},
- * * {@link module:engine/model/documentfragment~DocumentFragment model.DocumentFragment},
- * * {@link module:engine/model/document~Document model.Document},
- * * all {@link module:engine/model/operation/operation~Operation operations}
- * * all {@link module:engine/model/delta/delta~Delta deltas},
- * * {@link module:engine/view/element~Element view.Element},
- * * {@link module:engine/view/documentfragment~DocumentFragment view.DocumentFragment},
- * * {@link module:engine/view/document~Document view.Document}.
- *
- * Additionally, following logging utility methods are added:
- * * {@link module:engine/model/text~Text model.Text} `logExtended`,
- * * {@link module:engine/model/element~Element model.Element} `logExtended`,
- * * {@link module:engine/model/element~Element model.Element} `logAll`,
- * * {@link module:engine/model/delta/delta~Delta model.Delta} `logAll`.
- *
- * Additionally, following classes are expanded with `logTree` and `printTree` methods:
- * * {@link module:engine/model/element~Element model.Element},
- * * {@link module:engine/model/documentfragment~DocumentFragment model.DocumentFragment},
- * * {@link module:engine/view/element~Element view.Element},
- * * {@link module:engine/view/documentfragment~DocumentFragment view.DocumentFragment}.
- *
- * Finally, following methods are added to {@link module:core/editor/editor~Editor}: `logModel`, `logView`, `logDocuments`.
- * All those methods take one parameter, which is a version of {@link module:engine/model/document~Document model document}
- * for which model or view document state should be logged.
- *
- * @param {Object} [_logger] Object with functions used to log messages and errors. By default messages are logged to console.
- * If specified, it is expected to have `log()` and `error()` methods.
- * @returns {module:engine/dev-utils/enableenginedebug~DebugPlugin} Plugin to be loaded in the editor.
- */
- export default function enableEngineDebug( _logger = console ) {
- logger = _logger;
- if ( !enabled ) {
- enabled = true;
- enableLoggingTools();
- enableDocumentTools();
- enableReplayerTools();
- }
- return DebugPlugin;
- }
- function enableLoggingTools() {
- ModelPosition.prototype.toString = function() {
- return `${ this.root } [ ${ this.path.join( ', ' ) } ]`;
- };
- ModelPosition.prototype.log = function() {
- logger.log( 'ModelPosition: ' + this );
- };
- ModelRange.prototype.toString = function() {
- return `${ this.root } [ ${ this.start.path.join( ', ' ) } ] - [ ${ this.end.path.join( ', ' ) } ]`;
- };
- ModelRange.prototype.log = function() {
- logger.log( 'ModelRange: ' + this );
- };
- ModelText.prototype.toString = function() {
- return `#${ this.data }`;
- };
- ModelText.prototype.logExtended = function() {
- logger.log( `ModelText: ${ this }, attrs: ${ mapString( this.getAttributes() ) }` );
- };
- ModelText.prototype.log = function() {
- logger.log( 'ModelText: ' + this );
- };
- ModelTextProxy.prototype.toString = function() {
- return `#${ this.data }`;
- };
- ModelTextProxy.prototype.logExtended = function() {
- logger.log( `ModelTextProxy: ${ this }, attrs: ${ mapString( this.getAttributes() ) }` );
- };
- ModelTextProxy.prototype.log = function() {
- logger.log( 'ModelTextProxy: ' + this );
- };
- ModelElement.prototype.toString = function() {
- return `<${ this.rootName || this.name }>`;
- };
- ModelElement.prototype.log = function() {
- logger.log( 'ModelElement: ' + this );
- };
- ModelElement.prototype.logExtended = function() {
- logger.log( `ModelElement: ${ this }, ${ this.childCount } children, attrs: ${ mapString( this.getAttributes() ) }` );
- };
- ModelElement.prototype.logAll = function() {
- logger.log( '--------------------' );
- this.logExtended();
- logger.log( 'List of children:' );
- for ( const child of this.getChildren() ) {
- child.log();
- }
- };
- ModelElement.prototype.printTree = function( level = 0 ) {
- let string = '';
- string += '\t'.repeat( level ) + `<${ this.rootName || this.name }${ mapToTags( this.getAttributes() ) }>`;
- for ( const child of this.getChildren() ) {
- string += '\n';
- if ( child.is( 'text' ) ) {
- const textAttrs = mapToTags( child._attrs );
- string += '\t'.repeat( level + 1 );
- if ( textAttrs !== '' ) {
- string += `<$text${ textAttrs }>` + child.data + '</$text>';
- } else {
- string += child.data;
- }
- } else {
- string += child.printTree( level + 1 );
- }
- }
- if ( this.childCount ) {
- string += '\n' + '\t'.repeat( level );
- }
- string += `</${ this.rootName || this.name }>`;
- return string;
- };
- ModelElement.prototype.logTree = function() {
- logger.log( this.printTree() );
- };
- ModelRootElement.prototype.toString = function() {
- return this.rootName;
- };
- ModelRootElement.prototype.log = function() {
- logger.log( 'ModelRootElement: ' + this );
- };
- ModelDocumentFragment.prototype.toString = function() {
- return 'documentFragment';
- };
- ModelDocumentFragment.prototype.log = function() {
- logger.log( 'ModelDocumentFragment: ' + this );
- };
- ModelDocumentFragment.prototype.printTree = function() {
- let string = 'ModelDocumentFragment: [';
- for ( const child of this.getChildren() ) {
- string += '\n';
- if ( child.is( 'text' ) ) {
- const textAttrs = mapToTags( child._attrs );
- string += '\t'.repeat( 1 );
- if ( textAttrs !== '' ) {
- string += `<$text${ textAttrs }>` + child.data + '</$text>';
- } else {
- string += child.data;
- }
- } else {
- string += child.printTree( 1 );
- }
- }
- string += '\n]';
- return string;
- };
- ModelDocumentFragment.prototype.logTree = function() {
- logger.log( this.printTree() );
- };
- Operation.prototype.log = function() {
- logger.log( this.toString() );
- };
- AttributeOperation.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ this.key }": ${ JSON.stringify( this.oldValue ) } -> ${ JSON.stringify( this.newValue ) }, ${ this.range }`;
- };
- DetachOperation.prototype.toString = function() {
- const range = ModelRange.createFromPositionAndShift( this.sourcePosition, this.howMany );
- const nodes = Array.from( range.getItems() );
- return getClassName( this ) + `( ${ this.baseVersion } ): ${ nodes.length > 1 ? range : nodes[ 0 ] + ' ' + range }`;
- };
- InsertOperation.prototype.toString = function() {
- const nodeString = this.nodes.length > 1 ? `[ ${ this.nodes.length } ]` : this.nodes.getNode( 0 );
- return getClassName( this ) + `( ${ this.baseVersion } ): ${ nodeString } -> ${ this.position }`;
- };
- MarkerOperation.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ this.name }": ${ this.oldRange } -> ${ this.newRange }`;
- };
- MoveOperation.prototype.toString = function() {
- const range = ModelRange.createFromPositionAndShift( this.sourcePosition, this.howMany );
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `${ range } -> ${ this.targetPosition }${ this.isSticky ? ' (sticky)' : '' }`;
- };
- NoOperation.prototype.toString = function() {
- return `NoOperation( ${ this.baseVersion } )`;
- };
- RenameOperation.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `${ this.position }: "${ this.oldName }" -> "${ this.newName }"`;
- };
- RootAttributeOperation.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ this.key }": ${ JSON.stringify( this.oldValue ) } -> ${ JSON.stringify( this.newValue ) }, ${ this.root.rootName }`;
- };
- Delta.prototype.log = function() {
- logger.log( this.toString() );
- };
- Delta.prototype.logAll = function() {
- logger.log( '--------------------' );
- this.log();
- for ( const op of this.operations ) {
- op.log();
- }
- };
- Delta.prototype._saveHistory = function( itemToSave ) {
- const history = itemToSave.before.history ? itemToSave.before.history : [];
- itemToSave.before = clone( itemToSave.before );
- delete itemToSave.before.history;
- itemToSave.before = JSON.stringify( itemToSave.before );
- itemToSave.transformedBy = clone( itemToSave.transformedBy );
- delete itemToSave.transformedBy.history;
- itemToSave.transformedBy = JSON.stringify( itemToSave.transformedBy );
- this.history = history.concat( itemToSave );
- };
- const _deltaTransformTransform = deltaTransform.transform;
- deltaTransform.transform = function( a, b, context ) {
- let results;
- try {
- results = _deltaTransformTransform( a, b, context );
- } catch ( e ) {
- logger.error( 'Error during delta transformation!' );
- logger.error( a.toString() + ( context.isStrong ? ' (important)' : '' ) );
- logger.error( b.toString() + ( context.isStrong ? '' : ' (important)' ) );
- throw e;
- }
- for ( let i = 0; i < results.length; i++ ) {
- results[ i ]._saveHistory( {
- before: a,
- transformedBy: b,
- wasImportant: !!context.isStrong,
- resultIndex: i,
- resultsTotal: results.length
- } );
- }
- return results;
- };
- AttributeDelta.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ this.key }": -> ${ JSON.stringify( this.value ) }, ${ this.range }, ${ this.operations.length } ops`;
- };
- InsertDelta.prototype.toString = function() {
- const op = this._insertOperation;
- const nodeString = op.nodes.length > 1 ? `[ ${ op.nodes.length } ]` : op.nodes.getNode( 0 );
- return getClassName( this ) + `( ${ this.baseVersion } ): ${ nodeString } -> ${ op.position }`;
- };
- MarkerDelta.prototype.toString = function() {
- const op = this.operations[ 0 ];
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ op.name }": ${ op.oldRange } -> ${ op.newRange }`;
- };
- MergeDelta.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- ( this.position ?
- this.position.toString() :
- `(move from ${ this.operations[ 0 ].sourcePosition })`
- );
- };
- MoveDelta.prototype.toString = function() {
- const opStrings = [];
- for ( const op of this.operations ) {
- const range = ModelRange.createFromPositionAndShift( op.sourcePosition, op.howMany );
- opStrings.push( `${ range } -> ${ op.targetPosition }` );
- }
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- opStrings.join( '; ' );
- };
- RenameDelta.prototype.toString = function() {
- const op = this.operations[ 0 ];
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `${ op.position }: "${ op.oldName }" -> "${ op.newName }"`;
- };
- RootAttributeDelta.prototype.toString = function() {
- const op = this.operations[ 0 ];
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `"${ op.key }": ${ JSON.stringify( op.oldValue ) } -> ${ JSON.stringify( op.newValue ) }, ${ op.root.rootName }`;
- };
- SplitDelta.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- ( this.position ?
- this.position.toString() :
- `(clone to ${ this._cloneOperation.position || this._cloneOperation.targetPosition })`
- );
- };
- UnwrapDelta.prototype.toString = function() {
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- this.position.toString();
- };
- WrapDelta.prototype.toString = function() {
- const wrapElement = this._insertOperation.nodes.getNode( 0 );
- return getClassName( this ) + `( ${ this.baseVersion } ): ` +
- `${ this.range } -> ${ wrapElement }`;
- };
- ViewText.prototype.toString = function() {
- return `#${ this.data }`;
- };
- ViewText.prototype.logExtended = function() {
- logger.log( 'ViewText: ' + this );
- };
- ViewText.prototype.log = function() {
- logger.log( 'ViewText: ' + this );
- };
- ViewTextProxy.prototype.toString = function() {
- return `#${ this.data }`;
- };
- ViewTextProxy.prototype.logExtended = function() {
- logger.log( 'ViewTextProxy: ' + this );
- };
- ViewTextProxy.prototype.log = function() {
- logger.log( 'ViewTextProxy: ' + this );
- };
- ViewElement.prototype.printTree = function( level = 0 ) {
- let string = '';
- string += '\t'.repeat( level ) + `<${ this.name }${ mapToTags( this.getAttributes() ) }>`;
- for ( const child of this.getChildren() ) {
- if ( child.is( 'text' ) ) {
- string += '\n' + '\t'.repeat( level + 1 ) + child.data;
- } else {
- string += '\n' + child.printTree( level + 1 );
- }
- }
- if ( this.childCount ) {
- string += '\n' + '\t'.repeat( level );
- }
- string += `</${ this.name }>`;
- return string;
- };
- ViewElement.prototype.logTree = function() {
- logger.log( this.printTree() );
- };
- ViewDocumentFragment.prototype.printTree = function() {
- let string = 'ViewDocumentFragment: [';
- for ( const child of this.getChildren() ) {
- if ( child.is( 'text' ) ) {
- string += '\n' + '\t'.repeat( 1 ) + child.data;
- } else {
- string += '\n' + child.printTree( 1 );
- }
- }
- string += '\n]';
- return string;
- };
- ViewDocumentFragment.prototype.logTree = function() {
- logger.log( this.printTree() );
- };
- }
- function enableReplayerTools() {
- const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation;
- ModelDocument.prototype.applyOperation = function( operation ) {
- if ( !this._lastDelta ) {
- this._appliedDeltas = [];
- } else if ( this._lastDelta !== operation.delta ) {
- this._appliedDeltas.push( this._lastDelta.toJSON() );
- }
- this._lastDelta = operation.delta;
- _modelDocumentApplyOperation.call( this, operation );
- };
- ModelDocument.prototype.getAppliedDeltas = function() {
- // No deltas has been applied yet, return empty string.
- if ( !this._lastDelta ) {
- return '';
- }
- const appliedDeltas = this._appliedDeltas.concat( this._lastDelta );
- return appliedDeltas.map( JSON.stringify ).join( LOG_SEPARATOR );
- };
- ModelDocument.prototype.createReplayer = function( stringifiedDeltas ) {
- return new DeltaReplayer( this, LOG_SEPARATOR, stringifiedDeltas );
- };
- }
- function enableDocumentTools() {
- const _modelDocumentApplyOperation = ModelDocument.prototype.applyOperation;
- ModelDocument.prototype.applyOperation = function( operation ) {
- logger.log( 'Applying ' + operation );
- if ( !this._operationLogs ) {
- this._operationLogs = [];
- }
- this._operationLogs.push( JSON.stringify( operation.toJSON() ) );
- _modelDocumentApplyOperation.call( this, operation );
- };
- ModelDocument.prototype.log = function( version = null ) {
- version = version === null ? this.version : version;
- logDocument( this, version );
- };
- ViewDocument.prototype.log = function( version ) {
- logDocument( this, version );
- };
- Editor.prototype.logModel = function( version = null ) {
- version = version === null ? this.document.version : version;
- this.document.log( version );
- };
- Editor.prototype.logView = function( version ) {
- this.editing.view.log( version );
- };
- Editor.prototype.logDocuments = function( version = null ) {
- version = version === null ? this.document.version : version;
- this.logModel( version );
- this.logView( version );
- };
- function logDocument( document, version ) {
- logger.log( '--------------------' );
- if ( document[ treeDump ][ version ] ) {
- logger.log( document[ treeDump ][ version ] );
- } else {
- logger.log( 'Tree log unavailable for given version: ' + version );
- }
- }
- }
- /**
- * Plugin that enables debugging features on the editor's model and view documents.
- */
- class DebugPlugin extends Plugin {
- constructor( editor ) {
- super( editor );
- const modelDocument = this.editor.document;
- const viewDocument = this.editor.editing.view;
- modelDocument[ treeDump ] = [];
- viewDocument[ treeDump ] = [];
- dumpTrees( modelDocument, modelDocument.version );
- dumpTrees( viewDocument, modelDocument.version );
- modelDocument.on( 'change', () => {
- dumpTrees( modelDocument, modelDocument.version );
- dumpTrees( viewDocument, modelDocument.version );
- }, { priority: 'lowest' } );
- }
- }
- // Helper function, stores `document` state for given `version` as a string in private property.
- function dumpTrees( document, version ) {
- let string = '';
- for ( const root of document.roots.values() ) {
- string += root.printTree() + '\n';
- }
- document[ treeDump ][ version ] = string.substr( 0, string.length - 1 ); // Remove the last "\n".
- const overflow = document[ treeDump ].length - maxTreeDumpLength;
- if ( overflow > 0 ) {
- document[ treeDump ][ overflow - 1 ] = null;
- }
- }
- // Helper function, returns class name of given `Delta` or `Operation`.
- // @param {module:engine/model/delta/delta~Delta|module:engine/model/operation/operation~Operation}
- // @returns {String} Class name.
- function getClassName( obj ) {
- const path = obj.constructor.className.split( '.' );
- return path[ path.length - 1 ];
- }
- // Helper function, converts map to {"key1":"value1","key2":"value2"} format.
- // @param {Map} map Map to convert.
- // @returns {String} Converted map.
- function mapString( map ) {
- const obj = {};
- for ( const entry of map ) {
- obj[ entry[ 0 ] ] = entry[ 1 ];
- }
- return JSON.stringify( obj );
- }
- // Helper function, converts map to key1="value1" key2="value1" format.
- // @param {Map} map Map to convert.
- // @returns {String} Converted map.
- function mapToTags( map ) {
- let string = '';
- for ( const entry of map ) {
- string += ` ${ entry[ 0 ] }=${ JSON.stringify( entry[ 1 ] ) }`;
- }
- return string;
- }
|