tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

replace_step.ts (7204B)


      1 import {Slice, Node, Schema} from "prosemirror-model"
      2 
      3 import {Step, StepResult} from "./step"
      4 import {StepMap, Mappable} from "./map"
      5 
      6 /// Replace a part of the document with a slice of new content.
      7 export class ReplaceStep extends Step {
      8  /// The given `slice` should fit the 'gap' between `from` and
      9  /// `to`—the depths must line up, and the surrounding nodes must be
     10  /// able to be joined with the open sides of the slice. When
     11  /// `structure` is true, the step will fail if the content between
     12  /// from and to is not just a sequence of closing and then opening
     13  /// tokens (this is to guard against rebased replace steps
     14  /// overwriting something they weren't supposed to).
     15  constructor(
     16    /// The start position of the replaced range.
     17    readonly from: number,
     18    /// The end position of the replaced range.
     19    readonly to: number,
     20    /// The slice to insert.
     21    readonly slice: Slice,
     22    /// @internal
     23    readonly structure = false
     24  ) {
     25    super()
     26  }
     27 
     28  apply(doc: Node) {
     29    if (this.structure && contentBetween(doc, this.from, this.to))
     30      return StepResult.fail("Structure replace would overwrite content")
     31    return StepResult.fromReplace(doc, this.from, this.to, this.slice)
     32  }
     33 
     34  getMap() {
     35    return new StepMap([this.from, this.to - this.from, this.slice.size])
     36  }
     37 
     38  invert(doc: Node) {
     39    return new ReplaceStep(this.from, this.from + this.slice.size, doc.slice(this.from, this.to))
     40  }
     41 
     42  map(mapping: Mappable) {
     43    let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
     44    if (from.deletedAcross && to.deletedAcross) return null
     45    return new ReplaceStep(from.pos, Math.max(from.pos, to.pos), this.slice, this.structure)
     46  }
     47 
     48  merge(other: Step) {
     49    if (!(other instanceof ReplaceStep) || other.structure || this.structure) return null
     50 
     51    if (this.from + this.slice.size == other.from && !this.slice.openEnd && !other.slice.openStart) {
     52      let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
     53          : new Slice(this.slice.content.append(other.slice.content), this.slice.openStart, other.slice.openEnd)
     54      return new ReplaceStep(this.from, this.to + (other.to - other.from), slice, this.structure)
     55    } else if (other.to == this.from && !this.slice.openStart && !other.slice.openEnd) {
     56      let slice = this.slice.size + other.slice.size == 0 ? Slice.empty
     57          : new Slice(other.slice.content.append(this.slice.content), other.slice.openStart, this.slice.openEnd)
     58      return new ReplaceStep(other.from, this.to, slice, this.structure)
     59    } else {
     60      return null
     61    }
     62  }
     63 
     64  toJSON(): any {
     65    let json: any = {stepType: "replace", from: this.from, to: this.to}
     66    if (this.slice.size) json.slice = this.slice.toJSON()
     67    if (this.structure) json.structure = true
     68    return json
     69  }
     70 
     71  /// @internal
     72  static fromJSON(schema: Schema, json: any) {
     73    if (typeof json.from != "number" || typeof json.to != "number")
     74      throw new RangeError("Invalid input for ReplaceStep.fromJSON")
     75    return new ReplaceStep(json.from, json.to, Slice.fromJSON(schema, json.slice), !!json.structure)
     76  }
     77 }
     78 
     79 Step.jsonID("replace", ReplaceStep)
     80 
     81 /// Replace a part of the document with a slice of content, but
     82 /// preserve a range of the replaced content by moving it into the
     83 /// slice.
     84 export class ReplaceAroundStep extends Step {
     85  /// Create a replace-around step with the given range and gap.
     86  /// `insert` should be the point in the slice into which the content
     87  /// of the gap should be moved. `structure` has the same meaning as
     88  /// it has in the [`ReplaceStep`](#transform.ReplaceStep) class.
     89  constructor(
     90    /// The start position of the replaced range.
     91    readonly from: number,
     92    /// The end position of the replaced range.
     93    readonly to: number,
     94    /// The start of preserved range.
     95    readonly gapFrom: number,
     96    /// The end of preserved range.
     97    readonly gapTo: number,
     98    /// The slice to insert.
     99    readonly slice: Slice,
    100    /// The position in the slice where the preserved range should be
    101    /// inserted.
    102    readonly insert: number,
    103    /// @internal
    104    readonly structure = false
    105  ) {
    106    super()
    107  }
    108 
    109  apply(doc: Node) {
    110    if (this.structure && (contentBetween(doc, this.from, this.gapFrom) ||
    111                           contentBetween(doc, this.gapTo, this.to)))
    112      return StepResult.fail("Structure gap-replace would overwrite content")
    113 
    114    let gap = doc.slice(this.gapFrom, this.gapTo)
    115    if (gap.openStart || gap.openEnd)
    116      return StepResult.fail("Gap is not a flat range")
    117    let inserted = this.slice.insertAt(this.insert, gap.content)
    118    if (!inserted) return StepResult.fail("Content does not fit in gap")
    119    return StepResult.fromReplace(doc, this.from, this.to, inserted)
    120  }
    121 
    122  getMap() {
    123    return new StepMap([this.from, this.gapFrom - this.from, this.insert,
    124                        this.gapTo, this.to - this.gapTo, this.slice.size - this.insert])
    125  }
    126 
    127  invert(doc: Node) {
    128    let gap = this.gapTo - this.gapFrom
    129    return new ReplaceAroundStep(this.from, this.from + this.slice.size + gap,
    130                                 this.from + this.insert, this.from + this.insert + gap,
    131                                 doc.slice(this.from, this.to).removeBetween(this.gapFrom - this.from, this.gapTo - this.from),
    132                                 this.gapFrom - this.from, this.structure)
    133  }
    134 
    135  map(mapping: Mappable) {
    136    let from = mapping.mapResult(this.from, 1), to = mapping.mapResult(this.to, -1)
    137    let gapFrom = this.from == this.gapFrom ? from.pos : mapping.map(this.gapFrom, -1)
    138    let gapTo = this.to == this.gapTo ? to.pos : mapping.map(this.gapTo, 1)
    139    if ((from.deletedAcross && to.deletedAcross) || gapFrom < from.pos || gapTo > to.pos) return null
    140    return new ReplaceAroundStep(from.pos, to.pos, gapFrom, gapTo, this.slice, this.insert, this.structure)
    141  }
    142 
    143  toJSON(): any {
    144    let json: any = {stepType: "replaceAround", from: this.from, to: this.to,
    145                     gapFrom: this.gapFrom, gapTo: this.gapTo, insert: this.insert}
    146    if (this.slice.size) json.slice = this.slice.toJSON()
    147    if (this.structure) json.structure = true
    148    return json
    149  }
    150 
    151  /// @internal
    152  static fromJSON(schema: Schema, json: any) {
    153    if (typeof json.from != "number" || typeof json.to != "number" ||
    154        typeof json.gapFrom != "number" || typeof json.gapTo != "number" || typeof json.insert != "number")
    155      throw new RangeError("Invalid input for ReplaceAroundStep.fromJSON")
    156    return new ReplaceAroundStep(json.from, json.to, json.gapFrom, json.gapTo,
    157                                 Slice.fromJSON(schema, json.slice), json.insert, !!json.structure)
    158  }
    159 }
    160 
    161 Step.jsonID("replaceAround", ReplaceAroundStep)
    162 
    163 function contentBetween(doc: Node, from: number, to: number) {
    164  let $from = doc.resolve(from), dist = to - from, depth = $from.depth
    165  while (dist > 0 && depth > 0 && $from.indexAfter(depth) == $from.node(depth).childCount) {
    166    depth--
    167    dist--
    168  }
    169  if (dist > 0) {
    170    let next = $from.node(depth).maybeChild($from.indexAfter(depth))
    171    while (dist > 0) {
    172      if (!next || next.isLeaf) return true
    173      next = next.firstChild
    174      dist--
    175    }
    176  }
    177  return false
    178 }