diff --git a/lib/sequence-diagram.js b/lib/sequence-diagram.js index 5876e1c..4fc153e 100644 --- a/lib/sequence-diagram.js +++ b/lib/sequence-diagram.js @@ -3539,27 +3539,6 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => { }); } - function measureLine(tester, line) { - if(!line.length) { - return 0; - } - - const labelKey = JSON.stringify(line); - const knownWidth = tester.widths.get(labelKey); - if(knownWidth !== undefined) { - return knownWidth; - } - - // getComputedTextLength forces a reflow, so only call it if nothing - // else can tell us the length - - svg.empty(tester.node); - populateSvgTextLine(tester.node, line); - const width = tester.node.getComputedTextLength(); - tester.widths.set(labelKey, width); - return width; - } - const EMPTY = []; class SVGTextBlock { @@ -3676,35 +3655,68 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => { }); this.container = container; this.cache = new Map(); + this.nodes = null; + } + + _expectMeasure({attrs, formatted}) { + if(!formatted.length) { + return; + } + + const attrKey = JSON.stringify(attrs); + let attrCache = this.cache.get(attrKey); + if(!attrCache) { + attrCache = { + attrs, + lines: new Map(), + }; + this.cache.set(attrKey, attrCache); + } + + formatted.forEach((line) => { + if(!line.length) { + return; + } + + const labelKey = JSON.stringify(line); + if(!attrCache.lines.has(labelKey)) { + attrCache.lines.set(labelKey, { + formatted: line, + width: null, + }); + } + }); + + return attrCache; } _measureHeight({attrs, formatted}) { return formatted.length * fontDetails(attrs).lineHeight; } - _measureWidth({attrs, formatted}) { - if(!formatted.length) { + _measureLine(attrCache, line) { + if(!line.length) { return 0; } - const attrKey = JSON.stringify(attrs); - let tester = this.cache.get(attrKey); - if(!tester) { - const node = svg.make('text', attrs); - this.testers.appendChild(node); - tester = { - node, - widths: new Map(), - }; - this.cache.set(attrKey, tester); + const labelKey = JSON.stringify(line); + const cache = attrCache.lines.get(labelKey); + if(cache.width === null) { + window.console.warn('Performing unexpected measurement', line); + this.performMeasurements(); + } + return cache.width; + } + + _measureWidth(opts) { + if(!opts.formatted.length) { + return 0; } - if(!this.testers.parentNode) { - this.container.appendChild(this.testers); - } + const attrCache = this._expectMeasure(opts); - return (formatted - .map((line) => measureLine(tester, line)) + return (opts.formatted + .map((line) => this._measureLine(attrCache, line)) .reduce((a, b) => Math.max(a, b), 0) ); } @@ -3723,6 +3735,52 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => { return {attrs, formatted}; } + expectMeasure(attrs, formatted) { + const opts = this._getMeasurementOpts(attrs, formatted); + this._expectMeasure(opts); + } + + performMeasurementsPre() { + this.nodes = []; + this.cache.forEach(({attrs, lines}) => { + lines.forEach((cacheLine) => { + if(cacheLine.width === null) { + const node = svg.make('text', attrs); + populateSvgTextLine(node, cacheLine.formatted); + this.testers.appendChild(node); + this.nodes.push({node, cacheLine}); + } + }); + }); + + if(this.nodes.length) { + this.container.appendChild(this.testers); + } + } + + performMeasurementsAct() { + this.nodes.forEach(({node, cacheLine}) => { + cacheLine.width = node.getComputedTextLength(); + }); + } + + performMeasurementsPost() { + if(this.nodes.length) { + this.container.removeChild(this.testers); + svg.empty(this.testers); + } + this.nodes = null; + } + + performMeasurements() { + // getComputedTextLength forces a reflow, so we try to batch as + // many measurements as possible into a single DOM change + + this.performMeasurementsPre(); + this.performMeasurementsAct(); + this.performMeasurementsPost(); + } + measure(attrs, formatted) { const opts = this._getMeasurementOpts(attrs, formatted); return { @@ -3737,15 +3795,8 @@ define('svg/SVGTextBlock',['./SVGUtilities'], (svg) => { } resetCache() { - svg.empty(this.testers); this.cache.clear(); } - - detach() { - if(this.testers.parentNode) { - this.container.removeChild(this.testers); - } - } } SVGTextBlock.SizeTester = SizeTester; @@ -4010,6 +4061,16 @@ define('sequence/components/BaseComponent',[],() => { this.makeState(state); } + prepareMeasurements(/*stage, { + renderer, + theme, + agentInfos, + textSizer, + state, + components, + }*/) { + } + separationPre(/*stage, { renderer, theme, @@ -4128,6 +4189,13 @@ define('sequence/components/Block',[ 'use strict'; class BlockSplit extends BaseComponent { + prepareMeasurements({left, tag, label}, env) { + const blockInfo = env.state.blocks.get(left); + const config = env.theme.getBlock(blockInfo.type).section; + env.textSizer.expectMeasure(config.tag.labelAttrs, tag); + env.textSizer.expectMeasure(config.label.labelAttrs, label); + } + separation({left, right, tag, label}, env) { const blockInfo = env.state.blocks.get(left); const config = env.theme.getBlock(blockInfo.type).section; @@ -4242,8 +4310,14 @@ define('sequence/components/Block',[ return blockInfo; } + prepareMeasurements(stage, env) { + this.storeBlockInfo(stage, env); + super.prepareMeasurements(stage, env); + } + separationPre(stage, env) { this.storeBlockInfo(stage, env); + super.separationPre(stage, env); } separation(stage, env) { @@ -4379,16 +4453,23 @@ define('sequence/components/Parallel',[ } class Parallel extends BaseComponent { - separationPre(stage, env) { - stage.stages.forEach((subStage) => { - env.components.get(subStage.type).separationPre(subStage, env); + invokeChildren(stage, env, methodName) { + return stage.stages.map((subStage) => { + const component = env.components.get(subStage.type); + return component[methodName](subStage, env); }); } + prepareMeasurements(stage, env) { + this.invokeChildren(stage, env, 'prepareMeasurements'); + } + + separationPre(stage, env) { + this.invokeChildren(stage, env, 'separationPre'); + } + separation(stage, env) { - stage.stages.forEach((subStage) => { - env.components.get(subStage.type).separation(subStage, env); - }); + this.invokeChildren(stage, env, 'separation'); } renderPre(stage, env) { @@ -4398,11 +4479,9 @@ define('sequence/components/Parallel',[ asynchronousY: null, }; - return stage.stages.map((subStage) => { - const component = env.components.get(subStage.type); - const subResult = component.renderPre(subStage, env); - return BaseComponent.cleanRenderPreResult(subResult); - }).reduce(mergeResults, baseResults); + return this.invokeChildren(stage, env, 'renderPre') + .map((r) => BaseComponent.cleanRenderPreResult(r)) + .reduce(mergeResults, baseResults); } render(stage, env) { @@ -4424,24 +4503,19 @@ define('sequence/components/Parallel',[ } renderHidden(stage, env) { - stage.stages.forEach((subStage) => { - const component = env.components.get(subStage.type); - component.renderHidden(subStage, env); - }); + this.invokeChildren(stage, env, 'renderHidden'); } shouldHide(stage, env) { - const result = { + const baseResults = { self: false, nest: 0, }; - stage.stages.forEach((subStage) => { - const component = env.components.get(subStage.type); - const hide = component.shouldHide(subStage, env) || {}; - result.self = (result.self || Boolean(hide.self)); - result.nest += (hide.nest || 0); - }); - return result; + return this.invokeChildren(stage, env, 'shouldHide') + .reduce((result, {self = false, nest = 0} = {}) => ({ + self: result.self || Boolean(self), + nest: result.nest + nest, + }), baseResults); } } @@ -4514,6 +4588,11 @@ define('sequence/components/AgentCap',[ return config || env.theme.agentCap.box; } + prepareMeasurements({formattedLabel, options}, env) { + const config = this.getConfig(options, env); + env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); + } + separation({formattedLabel, options}, env) { const config = this.getConfig(options, env); const width = ( @@ -4572,6 +4651,9 @@ define('sequence/components/AgentCap',[ } class CapCross { + prepareMeasurements() { + } + separation(agentInfo, env) { const config = env.theme.agentCap.cross; return { @@ -4616,6 +4698,11 @@ define('sequence/components/AgentCap',[ } class CapBar { + prepareMeasurements({formattedLabel}, env) { + const config = env.theme.agentCap.box; + env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); + } + separation({formattedLabel}, env) { const config = env.theme.agentCap.box; const width = ( @@ -4672,6 +4759,9 @@ define('sequence/components/AgentCap',[ } class CapFade { + prepareMeasurements() { + } + separation({currentRad}) { return { left: currentRad, @@ -4733,6 +4823,9 @@ define('sequence/components/AgentCap',[ } class CapNone { + prepareMeasurements() { + } + separation({currentRad}) { return { left: currentRad, @@ -4781,6 +4874,14 @@ define('sequence/components/AgentCap',[ this.begin = begin; } + prepareMeasurements({mode, agentIDs}, env) { + agentIDs.forEach((id) => { + const agentInfo = env.agentInfos.get(id); + const cap = AGENT_CAPS[mode]; + cap.prepareMeasurements(agentInfo, env, this.begin); + }); + } + separationPre({mode, agentIDs}, env) { agentIDs.forEach((id) => { const agentInfo = env.agentInfos.get(id); @@ -5011,6 +5112,15 @@ define('sequence/components/Connect',[ ]; class Connect extends BaseComponent { + prepareMeasurements({agentIDs, label}, env) { + const config = env.theme.connect; + const loopback = (agentIDs[0] === agentIDs[1]); + const labelAttrs = (loopback ? + config.label.loopbackAttrs : config.label.attrs); + + env.textSizer.expectMeasure(labelAttrs, label); + } + separationPre({agentIDs}, env) { const r = env.theme.connect.source.radius; agentIDs.forEach((id) => { @@ -5029,15 +5139,17 @@ define('sequence/components/Connect',[ const lArrow = ARROWHEADS[options.left]; const rArrow = ARROWHEADS[options.right]; - let labelWidth = ( - env.textSizer.measure(config.label.attrs, label).width - ); + const loopback = (agentIDs[0] === agentIDs[1]); + const labelAttrs = (loopback ? + config.label.loopbackAttrs : config.label.attrs); + + let labelWidth = env.textSizer.measure(labelAttrs, label).width; if(labelWidth > 0) { labelWidth += config.label.padding * 2; } const info1 = env.agentInfos.get(agentIDs[0]); - if(agentIDs[0] === agentIDs[1]) { + if(loopback) { env.addSpacing(agentIDs[0], { left: 0, right: ( @@ -5391,6 +5503,8 @@ define('sequence/components/Connect',[ } class ConnectDelayEnd extends Connect { + prepareMeasurements() {} + separationPre() {} separation() {} @@ -5453,6 +5567,11 @@ define('sequence/components/Note',['./BaseComponent', 'svg/SVGUtilities'], (Base } class NoteComponent extends BaseComponent { + prepareMeasurements({mode, label}, env) { + const config = env.theme.getNote(mode); + env.textSizer.expectMeasure(config.labelAttrs, label); + } + renderPre({agentIDs}) { return {agentIDs}; } @@ -5713,6 +5832,11 @@ define('sequence/components/Divider',[ 'use strict'; class Divider extends BaseComponent { + prepareMeasurements({mode, formattedLabel}, env) { + const config = env.theme.getDivider(mode); + env.textSizer.expectMeasure(config.labelAttrs, formattedLabel); + } + separation({mode, formattedLabel}, env) { const config = env.theme.getDivider(mode); @@ -5905,6 +6029,8 @@ define('sequence/Renderer',[ _bindMethods() { this.separationStage = this.separationStage.bind(this); + this.prepareMeasurementsStage = + this.prepareMeasurementsStage.bind(this); this.renderStage = this.renderStage.bind(this); this.addThemeDef = this.addThemeDef.bind(this); this.addDef = this.addDef.bind(this); @@ -6100,6 +6226,24 @@ define('sequence/Renderer',[ }); } + prepareMeasurementsStage(stage) { + const env = { + renderer: this, + theme: this.theme, + agentInfos: this.agentInfos, + textSizer: this.sizer, + state: this.state, + components: this.components, + }; + + const component = this.components.get(stage.type); + if(!component) { + throw new Error('Unknown component: ' + stage.type); + } + + component.prepareMeasurements(stage, env); + } + checkAgentRange(agentIDs, topY = 0) { if(agentIDs.length === 0) { return topY; @@ -6273,7 +6417,7 @@ define('sequence/Renderer',[ }); } - buildAgentInfos(agents, stages) { + buildAgentInfos(agents) { this.agentInfos = new Map(); agents.forEach((agent, index) => { this.agentInfos.set(agent.id, { @@ -6293,11 +6437,6 @@ define('sequence/Renderer',[ separations: new Map(), }); }); - - this.visibleAgentIDs = ['[', ']']; - stages.forEach(this.separationStage); - - this.positionAgents(); } updateBounds(stagesHeight) { @@ -6429,16 +6568,18 @@ define('sequence/Renderer',[ return this._setCollapsed(line, collapsed); } - render(sequence) { - const prevHighlight = this.currentHighlight; + _switchTheme(name) { const oldTheme = this.theme; + this.theme = this.getThemeNamed(name); + this.theme.reset(); - this.theme = this.getThemeNamed(sequence.meta.theme); + return (this.theme !== oldTheme); + } - const themeChanged = (this.theme !== oldTheme); + optimisedRenderPreReflow(sequence) { + const themeChanged = this._switchTheme(sequence.meta.theme); this._reset(themeChanged); - this.theme.reset(); this.metaCode.nodeValue = sequence.meta.code; this.theme.addDefs(this.addThemeDef); @@ -6446,21 +6587,47 @@ define('sequence/Renderer',[ attrs: this.theme.titleAttrs, formatted: sequence.meta.title, }); + this.sizer.expectMeasure(this.title); this.minX = 0; this.maxX = 0; - this.buildAgentInfos(sequence.agents, sequence.stages); + this.buildAgentInfos(sequence.agents); + + sequence.stages.forEach(this.prepareMeasurementsStage); this._resetState(); + this.sizer.performMeasurementsPre(); + } + + optimisedRenderReflow() { + this.sizer.performMeasurementsAct(); + } + + optimisedRenderPostReflow(sequence) { + this.visibleAgentIDs = ['[', ']']; + sequence.stages.forEach(this.separationStage); + this._resetState(); + + this.positionAgents(); + sequence.stages.forEach(this.renderStage); const bottomY = this.checkAgentRange(['[', ']'], this.currentY); const stagesHeight = Math.max(bottomY - this.theme.actionMargin, 0); this.updateBounds(stagesHeight); - this.sizer.resetCache(); - this.sizer.detach(); + const prevHighlight = this.currentHighlight; + this.currentHighlight = -1; this.setHighlight(prevHighlight); + + this.sizer.performMeasurementsPost(); + this.sizer.resetCache(); + } + + render(sequence) { + this.optimisedRenderPreReflow(sequence); + this.optimisedRenderReflow(); + this.optimisedRenderPostReflow(sequence); } getThemeNames() { @@ -9434,8 +9601,8 @@ define('sequence/themes/Sketch',[ // but this fails when exporting as SVG / PNG (svg tags must // have no external dependencies). // const url = 'https://fonts.googleapis.com/css?family=' + FONT; -// style.innerText = '@import url("' + url + '")'; - style.innerText = ( +// style.textContent = '@import url("' + url + '")'; + style.textContent = ( '@font-face{' + 'font-family:"' + Handlee.name + '";' + 'src:url("data:font/woff2;base64,' + Handlee.woff2 + '");' + @@ -9963,6 +10130,29 @@ define('sequence/SequenceDiagram',[ return meta.textContent; } + function renderAll(diagrams) { + const errors = []; + function storeError(sd, e) { + errors.push(e); + } + + diagrams.forEach((diagram) => { + diagram.addEventListener('error', storeError); + diagram.optimisedRenderPreReflow(); + }); + diagrams.forEach((diagram) => { + diagram.optimisedRenderReflow(); + }); + diagrams.forEach((diagram) => { + diagram.optimisedRenderPostReflow(); + diagram.removeEventListener('error', storeError); + }); + + if(errors.length > 0) { + throw errors; + } + } + class SequenceDiagram extends EventObject { constructor(code = null, options = {}) { super(); @@ -9988,7 +10178,7 @@ define('sequence/SequenceDiagram',[ if(options.interactive) { this.addInteractivity(); } - if(typeof this.code === 'string') { + if(typeof this.code === 'string' && options.render !== false) { this.render(); } } @@ -10005,13 +10195,15 @@ define('sequence/SequenceDiagram',[ }, options)); } - set(code = '') { + set(code = '', {render = true} = {}) { if(this.code === code) { return; } this.code = code; - this.render(); + if(render) { + this.render(); + } } process(code) { @@ -10109,29 +10301,92 @@ define('sequence/SequenceDiagram',[ }; } - render(processed = null) { + _revertParent(state) { const dom = this.renderer.svg(); - const originalParent = dom.parentNode; + if(dom.parentNode !== state.originalParent) { + document.body.removeChild(dom); + if(state.originalParent) { + state.originalParent.appendChild(dom); + } + } + } + + _sendRenderError(e) { + this._revertParent(this.renderState); + this.renderState.error = true; + this.trigger('error', [this, e]); + } + + optimisedRenderPreReflow(processed = null) { + const dom = this.renderer.svg(); + this.renderState = { + originalParent: dom.parentNode, + processed, + error: false, + }; + const state = this.renderState; + if(!document.body.contains(dom)) { - if(originalParent) { - originalParent.removeChild(dom); + if(state.originalParent) { + state.originalParent.removeChild(dom); } document.body.appendChild(dom); } + try { - if(!processed) { - processed = this.process(this.code); + if(!state.processed) { + state.processed = this.process(this.code); } - this.renderer.render(processed); - this.latestProcessed = processed; + this.renderer.optimisedRenderPreReflow(state.processed); + } catch(e) { + this._sendRenderError(e); + } + } + + optimisedRenderReflow() { + try { + if(!this.renderState.error) { + this.renderer.optimisedRenderReflow(); + } + } catch(e) { + this._sendRenderError(e); + } + } + + optimisedRenderPostReflow() { + const state = this.renderState; + + try { + if(!state.error) { + this.renderer.optimisedRenderPostReflow(state.processed); + } + } catch(e) { + this._sendRenderError(e); + } + + this.renderState = null; + + if(!state.error) { + this._revertParent(state); + this.latestProcessed = state.processed; this.trigger('render', [this]); - } finally { - if(dom.parentNode !== originalParent) { - document.body.removeChild(dom); - if(originalParent) { - originalParent.appendChild(dom); - } - } + } + } + + render(processed = null) { + let latestError = null; + function storeError(sd, e) { + latestError = e; + } + this.addEventListener('error', storeError); + + this.optimisedRenderPreReflow(processed); + this.optimisedRenderReflow(); + this.optimisedRenderPostReflow(); + + this.removeEventListener('error', storeError); + if(latestError) { + throw latestError; } } @@ -10160,6 +10415,10 @@ define('sequence/SequenceDiagram',[ return extractCodeFromSVG(svg); } + renderAll(diagrams) { + return renderAll(diagrams); + } + dom() { return this.renderer.svg(); } @@ -10176,16 +10435,13 @@ define('sequence/SequenceDiagram',[ }; } - function convert(element, code = null, options = {}) { + function convertOne(element, code = null, options = {}) { if(element.tagName === 'svg') { return null; } if(code === null) { - code = element.innerText; - } else if(typeof code === 'object') { - options = code; - code = options.code; + code = element.textContent; } const tagOptions = parseTagOptions(element); @@ -10207,6 +10463,24 @@ define('sequence/SequenceDiagram',[ return diagram; } + function convert(elements, code = null, options = {}) { + if(code && typeof code === 'object') { + options = code; + code = options.code; + } + + if(Array.isArray(elements)) { + const opts = Object.assign({}, options, {render: false}); + const diagrams = elements.map((el) => convertOne(el, code, opts)); + if(options.render !== false) { + renderAll(diagrams); + } + return diagrams; + } else { + return convertOne(elements, code, options); + } + } + function convertAll(root = null, className = 'sequence-diagram') { if(typeof root === 'string') { className = root; @@ -10218,13 +10492,15 @@ define('sequence/SequenceDiagram',[ } else { elements = (root || document).getElementsByClassName(className); } + // Convert from "live" collection to static to avoid infinite loops: const els = []; for(let i = 0; i < elements.length; ++ i) { els.push(elements[i]); } + // Convert elements - els.forEach((el) => convert(el)); + convert(els); } return Object.assign(SequenceDiagram, { @@ -10237,6 +10513,7 @@ define('sequence/SequenceDiagram',[ addTheme, registerCodeMirrorMode, extractCodeFromSVG, + renderAll, convert, convertAll, }); diff --git a/lib/sequence-diagram.min.js b/lib/sequence-diagram.min.js index 1fe57b9..020ab07 100644 --- a/lib/sequence-diagram.min.js +++ b/lib/sequence-diagram.min.js @@ -1 +1 @@ -!function(){var e,t,n;!function(r){function i(e,t){return x.call(e,t)}function s(e,t){var n,r,i,s,a,o,l,h,d,g,c,u=t&&t.split("/"),p=b.map,f=p&&p["*"]||{};if(e){for(a=(e=e.split("/")).length-1,b.nodeIdCompat&&w.test(e[a])&&(e[a]=e[a].replace(w,"")),"."===e[0].charAt(0)&&u&&(e=u.slice(0,u.length-1).concat(e)),d=0;d0&&(e.splice(d-1,2),d-=2)}e=e.join("/")}if((u||f)&&p){for(d=(n=e.split("/")).length;d>0;d-=1){if(r=n.slice(0,d).join("/"),u)for(g=u.length;g>0;g-=1)if((i=p[u.slice(0,g).join("/")])&&(i=i[r])){s=i,o=d;break}if(s)break;!l&&f&&f[r]&&(l=f[r],h=d)}!s&&l&&(s=l,o=h),s&&(n.splice(0,o,s),e=n.join("/"))}return e}function a(e,t){return function(){var n=k.call(arguments,0);return"string"!=typeof n[0]&&1===n.length&&n.push(null),c.apply(r,n.concat([e,t]))}}function o(e){return function(t){f[e]=t}}function l(e){if(i(m,e)){var t=m[e];delete m[e],y[e]=!0,g.apply(r,t)}if(!i(f,e)&&!i(y,e))throw new Error("No "+e);return f[e]}function h(e){var t,n=e?e.indexOf("!"):-1;return n>-1&&(t=e.substring(0,n),e=e.substring(n+1,e.length)),[t,e]}function d(e){return e?h(e):[]}var g,c,u,p,f={},m={},b={},y={},x=Object.prototype.hasOwnProperty,k=[].slice,w=/\.js$/;u=function(e,t){var n,r=h(e),i=r[0],a=t[1];return e=r[1],i&&(n=l(i=s(i,a))),i?e=n&&n.normalize?n.normalize(e,function(e){return function(t){return s(t,e)}}(a)):s(e,a):(i=(r=h(e=s(e,a)))[0],e=r[1],i&&(n=l(i))),{f:i?i+"!"+e:e,n:e,pr:i,p:n}},p={require:function(e){return a(e)},exports:function(e){var t=f[e];return void 0!==t?t:f[e]={}},module:function(e){return{id:e,uri:"",exports:f[e],config:function(e){return function(){return b&&b.config&&b.config[e]||{}}}(e)}}},g=function(e,t,n,s){var h,g,c,b,x,k,w,A=[],v=typeof n;if(s=s||e,k=d(s),"undefined"===v||"function"===v){for(t=!t.length&&n.length?["require","exports","module"]:t,x=0;x{"use strict";return class{constructor(){this.listeners=new Map,this.forwards=new Set}addEventListener(e,t){const n=this.listeners.get(e);n?n.push(t):this.listeners.set(e,[t])}removeEventListener(e,t){const n=this.listeners.get(e);if(!n)return;const r=n.indexOf(t);-1!==r&&n.splice(r,1)}countEventListeners(e){return(this.listeners.get(e)||[]).length}removeAllEventListeners(e){e?this.listeners.delete(e):this.listeners.clear()}addEventForwarding(e){this.forwards.add(e)}removeEventForwarding(e){this.forwards.delete(e)}removeAllEventForwardings(){this.forwards.clear()}trigger(e,t=[]){(this.listeners.get(e)||[]).forEach(e=>e.apply(null,t)),this.forwards.forEach(n=>n.trigger(e,t))}}}),n("core/ArrayUtilities",[],()=>{"use strict";function e(e,t,n=null){if(null===n)return e.indexOf(t);for(let r=0;r=e.length)return void i.push(r.slice());const s=e[n];if(!Array.isArray(s))return r.push(s),t(e,n+1,r,i),void r.pop();for(let a=0;a{n.push(...t(e))}),n}}}),n("sequence/CodeMirrorMode",["core/ArrayUtilities"],e=>{"use strict";function t(e,t=!1){return{type:"string",suggest:t,then:Object.assign({"":0},e)}}function n(e,t){return e.v===t.v&&e.prefix===t.prefix&&e.suffix===t.suffix&&e.q===t.q}function r(t,n,r){let i=r.suggest;return Array.isArray(i)||(i=[i]),e.flatMap(i,e=>!1===e?[]:"object"==typeof e?e.known?t["known"+e.known]||[]:[e]:"string"==typeof e&&e?[{v:e,q:""===n}]:[function(e,t){return Object.keys(t.then).length>0?{v:e,suffix:" ",q:!1}:{v:e,suffix:"\n",q:!1}}(n,r)])}function i(t,i){const s=[],a=e.last(i);return Object.keys(a.then).forEach(o=>{let l=a.then[o];"number"==typeof l&&(l=i[i.length-l-1]),e.mergeSets(s,r(t,o,l),n)}),s}function s(t,r,i,{suggest:s,override:a}){let o=null;"object"==typeof s&&s.known&&(o=s.known),r.type&&o!==r.type&&(a&&(r.type=a),e.mergeSets(t["known"+r.type],[{v:r.value,suffix:" ",q:!0}],n),r.type="",r.value=""),o&&(r.type=o,r.value&&(r.value+=i.s),r.value+=i.v)}function a(t,n,r){const a={type:"",value:""};let o=r;const h=[o];return t.line.forEach((n,r)=>{r===t.line.length-1&&(t.completions=i(t,h));const d=n.q?"":n.v;let g=o.then[d];void 0===g?(g=o.then[""],t.isVar=!0):t.isVar=n.q,"number"==typeof g?h.length-=g:h.push(g||l),o=e.last(h),s(t,a,n,o)}),n&&s(t,a,null,{}),t.nextCompletions=i(t,h),t.valid=Boolean(o.then["\n"])||0===Object.keys(o.then).length,o.type}function o(e){const t=e.baseToken||{};return{value:t.v||"",quoted:t.q||!1}}const l={type:"error line-error",suggest:!1,then:{"":0}},h=["database","red"],d=(()=>{function e(e,t=1){return{type:"variable",suggest:{known:"Agent"},then:Object.assign({},e,{"":0,",":{type:"operator",then:{"":t}}})}}function n(e){return{type:"keyword",suggest:[e+" of ",e+": "],then:{of:{type:"keyword",then:{"":g}},":":{type:"operator",then:{"":a}},"":g}}}function r({exit:e,sourceExit:t,blankExit:n}){const r={type:"operator",then:{"+":l,"-":l,"*":l,"!":l,"":e}};return{"+":{type:"operator",then:{"+":l,"-":l,"*":r,"!":l,"":e}},"-":{type:"operator",then:{"+":l,"-":l,"*":r,"!":{type:"operator",then:{"+":l,"-":l,"*":l,"!":l,"":e}},"":e}},"*":{type:"operator",then:Object.assign({"+":r,"-":r,"*":l,"!":l,"":e},t||e)},"!":r,"":n||e}}const i={type:"",suggest:"\n",then:{}},s={type:"",suggest:!1,then:{}},a=t({"\n":i}),o={type:"operator",then:{"":a,"\n":s}},d=e({"\n":i,as:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,",":{type:"operator",then:{"":3}},"\n":i}}}}}),g=e({":":o}),c={type:"variable",suggest:{known:"Agent"},then:{"":0,":":{type:"operator",then:{"":a,"\n":s}},"\n":i}},u={":":{type:"operator",then:{"":t({as:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,"\n":i}}}}})}}},p={type:"keyword",then:Object.assign({over:{type:"keyword",then:{"":e(u)}}},u)},f={"\n":i,":":{type:"operator",then:{"":a,"\n":s}},with:{type:"keyword",suggest:["with height "],then:{height:{type:"keyword",then:{"":{type:"number",suggest:["6 ","30 "],then:{"\n":i,":":{type:"operator",then:{"":a,"\n":s}}}}}}}}},m=function(e,t,n){const r=Object.assign({},n);return t.forEach(t=>{r[t]={type:e,then:n}}),r}("keyword",["a","an"],function(e,t,n){const r={},i=Object.assign({},n);return t.forEach(t=>{r[t]={type:e,then:i},i[t]=0}),r}("keyword",h,{"\n":i})),b={type:"keyword",then:{"":a,":":{type:"operator",then:{"":a}},"\n":i}},y={title:{type:"keyword",then:{"":a}},theme:{type:"keyword",then:{"":{type:"string",suggest:{global:"themes",suffix:"\n"},then:{"":0,"\n":i}}}},headers:{type:"keyword",then:{none:{type:"keyword",then:{}},cross:{type:"keyword",then:{}},box:{type:"keyword",then:{}},fade:{type:"keyword",then:{}},bar:{type:"keyword",then:{}}}},terminators:{type:"keyword",then:{none:{type:"keyword",then:{}},cross:{type:"keyword",then:{}},box:{type:"keyword",then:{}},fade:{type:"keyword",then:{}},bar:{type:"keyword",then:{}}}},divider:{type:"keyword",then:Object.assign({line:{type:"keyword",then:f},space:{type:"keyword",then:f},delay:{type:"keyword",then:f},tear:{type:"keyword",then:f}},f)},define:{type:"keyword",then:{"":d,as:l}},begin:{type:"keyword",then:{"":d,reference:p,as:l}},end:{type:"keyword",then:{"":d,as:l,"\n":i}},if:b,else:{type:"keyword",suggest:["else\n","else if: "],then:{if:{type:"keyword",suggest:"if: ",then:{"":a,":":{type:"operator",then:{"":a}}}},"\n":i}},repeat:b,group:b,note:{type:"keyword",then:{over:{type:"keyword",then:{"":g}},left:n("left"),right:n("right"),between:{type:"keyword",then:{"":e({":":l},g)}}}},state:{type:"keyword",suggest:"state over ",then:{over:{type:"keyword",then:{"":{type:"variable",suggest:{known:"Agent"},then:{"":0,",":l,":":o}}}}}},text:{type:"keyword",then:{left:n("left"),right:n("right")}},autolabel:{type:"keyword",then:{off:{type:"keyword",then:{}},"":t({"\n":i},[{v:"