204 lines
5.1 KiB
JavaScript
204 lines
5.1 KiB
JavaScript
/**
|
||
* @typedef {import('micromark-util-types').Event} Event
|
||
*/
|
||
|
||
// Port of `edit_map.rs` from `markdown-rs`.
|
||
// This should move to `markdown-js` later.
|
||
|
||
// Deal with several changes in events, batching them together.
|
||
//
|
||
// Preferably, changes should be kept to a minimum.
|
||
// Sometimes, it’s needed to change the list of events, because parsing can be
|
||
// messy, and it helps to expose a cleaner interface of events to the compiler
|
||
// and other users.
|
||
// It can also help to merge many adjacent similar events.
|
||
// And, in other cases, it’s needed to parse subcontent: pass some events
|
||
// through another tokenizer and inject the result.
|
||
|
||
/**
|
||
* @typedef {[number, number, Array<Event>]} Change
|
||
* @typedef {[number, number, number]} Jump
|
||
*/
|
||
|
||
/**
|
||
* Tracks a bunch of edits.
|
||
*/
|
||
export class EditMap {
|
||
/**
|
||
* Create a new edit map.
|
||
*/
|
||
constructor() {
|
||
/**
|
||
* Record of changes.
|
||
*
|
||
* @type {Array<Change>}
|
||
*/
|
||
this.map = []
|
||
}
|
||
|
||
/**
|
||
* Create an edit: a remove and/or add at a certain place.
|
||
*
|
||
* @param {number} index
|
||
* @param {number} remove
|
||
* @param {Array<Event>} add
|
||
* @returns {undefined}
|
||
*/
|
||
add(index, remove, add) {
|
||
addImpl(this, index, remove, add)
|
||
}
|
||
|
||
// To do: add this when moving to `micromark`.
|
||
// /**
|
||
// * Create an edit: but insert `add` before existing additions.
|
||
// *
|
||
// * @param {number} index
|
||
// * @param {number} remove
|
||
// * @param {Array<Event>} add
|
||
// * @returns {undefined}
|
||
// */
|
||
// addBefore(index, remove, add) {
|
||
// addImpl(this, index, remove, add, true)
|
||
// }
|
||
|
||
/**
|
||
* Done, change the events.
|
||
*
|
||
* @param {Array<Event>} events
|
||
* @returns {undefined}
|
||
*/
|
||
consume(events) {
|
||
this.map.sort(function (a, b) {
|
||
return a[0] - b[0]
|
||
})
|
||
|
||
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
|
||
if (this.map.length === 0) {
|
||
return
|
||
}
|
||
|
||
// To do: if links are added in events, like they are in `markdown-rs`,
|
||
// this is needed.
|
||
// // Calculate jumps: where items in the current list move to.
|
||
// /** @type {Array<Jump>} */
|
||
// const jumps = []
|
||
// let index = 0
|
||
// let addAcc = 0
|
||
// let removeAcc = 0
|
||
// while (index < this.map.length) {
|
||
// const [at, remove, add] = this.map[index]
|
||
// removeAcc += remove
|
||
// addAcc += add.length
|
||
// jumps.push([at, removeAcc, addAcc])
|
||
// index += 1
|
||
// }
|
||
//
|
||
// . shiftLinks(events, jumps)
|
||
|
||
let index = this.map.length
|
||
/** @type {Array<Array<Event>>} */
|
||
const vecs = []
|
||
while (index > 0) {
|
||
index -= 1
|
||
vecs.push(
|
||
events.slice(this.map[index][0] + this.map[index][1]),
|
||
this.map[index][2]
|
||
)
|
||
|
||
// Truncate rest.
|
||
events.length = this.map[index][0]
|
||
}
|
||
vecs.push([...events])
|
||
events.length = 0
|
||
let slice = vecs.pop()
|
||
while (slice) {
|
||
events.push(...slice)
|
||
slice = vecs.pop()
|
||
}
|
||
|
||
// Truncate everything.
|
||
this.map.length = 0
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Create an edit.
|
||
*
|
||
* @param {EditMap} editMap
|
||
* @param {number} at
|
||
* @param {number} remove
|
||
* @param {Array<Event>} add
|
||
* @returns {undefined}
|
||
*/
|
||
function addImpl(editMap, at, remove, add) {
|
||
let index = 0
|
||
|
||
/* c8 ignore next 3 -- `resolve` is never called without tables, so without edits. */
|
||
if (remove === 0 && add.length === 0) {
|
||
return
|
||
}
|
||
while (index < editMap.map.length) {
|
||
if (editMap.map[index][0] === at) {
|
||
editMap.map[index][1] += remove
|
||
|
||
// To do: before not used by tables, use when moving to micromark.
|
||
// if (before) {
|
||
// add.push(...editMap.map[index][2])
|
||
// editMap.map[index][2] = add
|
||
// } else {
|
||
editMap.map[index][2].push(...add)
|
||
// }
|
||
|
||
return
|
||
}
|
||
index += 1
|
||
}
|
||
editMap.map.push([at, remove, add])
|
||
}
|
||
|
||
// /**
|
||
// * Shift `previous` and `next` links according to `jumps`.
|
||
// *
|
||
// * This fixes links in case there are events removed or added between them.
|
||
// *
|
||
// * @param {Array<Event>} events
|
||
// * @param {Array<Jump>} jumps
|
||
// */
|
||
// function shiftLinks(events, jumps) {
|
||
// let jumpIndex = 0
|
||
// let index = 0
|
||
// let add = 0
|
||
// let rm = 0
|
||
|
||
// while (index < events.length) {
|
||
// const rmCurr = rm
|
||
|
||
// while (jumpIndex < jumps.length && jumps[jumpIndex][0] <= index) {
|
||
// add = jumps[jumpIndex][2]
|
||
// rm = jumps[jumpIndex][1]
|
||
// jumpIndex += 1
|
||
// }
|
||
|
||
// // Ignore items that will be removed.
|
||
// if (rm > rmCurr) {
|
||
// index += rm - rmCurr
|
||
// } else {
|
||
// // ?
|
||
// // if let Some(link) = &events[index].link {
|
||
// // if let Some(next) = link.next {
|
||
// // events[next].link.as_mut().unwrap().previous = Some(index + add - rm);
|
||
// // while jumpIndex < jumps.len() && jumps[jumpIndex].0 <= next {
|
||
// // add = jumps[jumpIndex].2;
|
||
// // rm = jumps[jumpIndex].1;
|
||
// // jumpIndex += 1;
|
||
// // }
|
||
// // events[index].link.as_mut().unwrap().next = Some(next + add - rm);
|
||
// // index = next;
|
||
// // continue;
|
||
// // }
|
||
// // }
|
||
// index += 1
|
||
// }
|
||
// }
|
||
// }
|