diff --git a/ivette/src/dome/src/renderer/text/buffers.js b/ivette/src/dome/src/renderer/text/buffers.js index ebab6e603aba83c5d8f79094c61861da1bc8cfec..bf6a2f190b7cef9f13fcb453838b51250dcfaed0 100644 --- a/ivette/src/dome/src/renderer/text/buffers.js +++ b/ivette/src/dome/src/renderer/text/buffers.js @@ -82,6 +82,8 @@ export class RichTextBuffer extends Emitter { super(); const { mode , maxlines } = props ; this._doc = new CodeMirror.Doc('',mode); + this._operations = 0 ; + this._editors = [] ; this._stacked = [] ; this._edited = false ; this._focused = false ; @@ -439,18 +441,87 @@ is blocked. // -------------------------------------------------------------------------- /** - @summary Linked CodeMirror document. + @summary Bind this buffer to a CodeMirror instance. + @param {CodeMirror} cm - code mirror instance to link this document in. @description - Returns a CodeMirror document linked to this buffer (with shared history). + Uses CodeMirror linked documents to allow several CodeMirror instances to be linked + to the same buffer. */ - linkedDoc() { return this._doc.linkedDoc( { sharedHist: true } ); } + link(cm) { + const newDoc = this._doc.linkedDoc( { sharedHist: true } ); + cm.swapDoc( newDoc ); + this._editors.push(cm); + if (this._operations > 0) cm.startOperation(); + } /** @summary Release a linked CodeMirror document. + @param {CodeMirror} cm - the code mirror instance to unlink + @param {Document} previous document of the instance. + @description + Unlinks a CodeMirror document previously linked by `link(cm)`. + */ + unlink(cm) { + const oldDoc = cm.getDoc(); + this._doc.unlinkDoc( oldDoc ); + this._editors = this._editors.filter((cm0) => cm0 !== cm); + if (this._operations > 0) cm.endOperation(); + } + + /** + @summary Iterates over each linked CodeMirror instances + @description + The operation `fn` is performed on each code mirror + instance currently linked to this buffer. + */ + forEach(fn) { + this._editors.forEach(fn); + }; + + // -------------------------------------------------------------------------- + // --- Stacked Operations + // -------------------------------------------------------------------------- + + /** + @summary Batch heavy operations on editors + @param {function} job - a function performing the operations (can return a promise) + @return {promise} the promised job @description - Unlinks a CodeMirror document obtained by `linkedDoc()`. + Uses code mirror `cm.startOperation()` and `cm.sendOperation()` on all + linked editors to batch the updating operations performed on the + buffer. The batched updates can run asynchronously. */ - unlinkDoc(doc) { this._doc.unlinkDoc( doc ); } + operation(job) { + + // Protect each start/end call against error + const forEachEditor = (fn) => { + this._editors.forEach((cm) => { + try { fn(cm); } catch(e) { console.err('[Dome.text.buffers]',e); } + }); + }; + + // Invariant: this._operations is the number of batched job still running + // Invariant: when pending job are running, all linked this._editors are started + // Second invariant is also maintained by link and unlink methods + + const startOperation = () => { + this._operations ++; + if (this._operations == 1) + forEachEditor((cm) => cm.startOperation()); + }; + + const endOperation = () => { + this._operations --; + if (this._operations == 0) { + forEachEditor((cm) => cm.endOperation()); + } + }; + + return Promise.resolve() + .then(startOperation) + .then(job) + .finally(endOperation); + } } diff --git a/ivette/src/dome/src/renderer/text/editors.js b/ivette/src/dome/src/renderer/text/editors.js index 6040d63c4eb90b0fade6c5516bd388f2497bd4de..db98d1a8fb8a88164d73a7ddc00bea35230313b5 100644 --- a/ivette/src/dome/src/renderer/text/editors.js +++ b/ivette/src/dome/src/renderer/text/editors.js @@ -112,8 +112,8 @@ export class Text extends React.Component { className, /* ignored */ style, /* ignored */ ...config } = this.props ; - const value = buffer ? buffer.linkedDoc() : "" ; - const cm = this.codeMirror = new CodeMirror(elt, { value }); + const cm = this.codeMirror = new CodeMirror(elt, { value: "" }); + if (buffer) buffer.link(cm); // Passing all options to constructor does not work (Cf. CodeMirror's BTS) for (var opt in config) cm.setOption( opt , config[opt] ); cm.on('update',this.handleUpdate); @@ -134,7 +134,7 @@ export class Text extends React.Component { Dome.off('dome.update',this.refresh); const { buffer } = this.props ; if (cm && buffer) { - buffer.unlinkDoc(cm.getDoc()); + buffer.unlink(cm); buffer.off('decorated',this.handleUpdate); buffer.off('scroll',this.handleScrollTo); } @@ -343,9 +343,9 @@ export class Text extends React.Component { selection:newSelect, ...newConfig } = newProps ; if (oldBuffer !== newBuffer) { - const newDoc = newBuffer.linkedDoc(); - const oldDoc = cm.swapDoc( newDoc ); - oldBuffer.unlinkDoc( oldDoc ); + if (oldBuffer) oldBuffer.unlink(cm); + if (newBuffer) newBuffer.link(cm); + else cm.clear(); } // Incremental update options var opt ; diff --git a/ivette/src/renderer/ASTview.tsx b/ivette/src/renderer/ASTview.tsx index 78c071618b827b2b70fe1f4b9d7cb3b51ec9be76..2399b2410f5bbafc71e72290935fa1a529daa215 100644 --- a/ivette/src/renderer/ASTview.tsx +++ b/ivette/src/renderer/ASTview.tsx @@ -42,12 +42,14 @@ async function loadAST(buffer: any, theFunction?: string, theMarker?: string) { endpoint: 'kernel.ast.printFunction', params: theFunction, }); - buffer.clear(); - if (!data) - buffer.log('// No code for function ', theFunction); - printAST(buffer, data); - if (theMarker) - buffer.scroll(theMarker, undefined); + buffer.operation(() => { + buffer.clear(); + if (!data) + buffer.log('// No code for function ', theFunction); + printAST(buffer, data); + if (theMarker) + buffer.scroll(theMarker, undefined); + }); return; })(); }