<commit_before>import {Slice, Fragment} from "../model"

import {Transform} from "./transform"
import {ReplaceStep, ReplaceWrapStep} from "./replace_step"

// :: (Node, number, ?number) → bool
// Tells you whether the range in the given positions' shared
// ancestor, or any of _its_ ancestor nodes, can be lifted out of a
// parent.
export function canLift(doc, from, to) {
  return !!findLiftable(doc.resolve(from), doc.resolve(to == null ? from : to))
}

function rangeDepth($from, $to) {
  let shared = $from.sameDepth($to)
  if ($from.node(shared).isTextblock || $from.pos == $to.pos) --shared
  if (shared < 0 || $from.pos > $to.pos) return null
  return shared
}

function canCut(node, start, end) {
  return (start == 0 || node.canReplace(start, node.childCount)) &&
    (end == node.childCount || node.canReplace(0, start))
}

function findLiftable($from, $to) {
  let shared = rangeDepth($from, $to)
  if (!shared) return null
  let parent = $from.node(shared), content = parent.content.cutByIndex($from.index(shared), $to.indexAfter(shared))
  for (let depth = shared;; --depth) {
    let node = $from.node(depth), index = $from.index(depth)
    if (depth < shared && node.canReplace(index, index + 1, content))
      return {depth, shared, unwrap: false}
    if (depth == 0 || !canCut(node, index, index + 1)) break
  }

  if (parent.isBlock) {
    let joined = Fragment.empty
    content.forEach(node => joined = joined.append(node.content))
    for (let depth = shared;; --depth) {
      let node = $from.node(depth), index = $from.index(depth)
      if (depth < shared && node.canReplace(index, index + 1, joined))
        return {depth, shared, unwrap: true}
      if (depth == 0 || !canCut(node, index, index + 1)) break
    }
  }
}

// :: (number, ?number, ?bool) → Transform
// Lift the nearest liftable ancestor of the [sibling
// range](#Node.siblingRange) of the given positions out of its parent
// (or do nothing if no such node exists). When `silent` is true, this
// won't raise an error when the lift is impossible.
Transform.prototype.lift = function(from, to = from, silent = false) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let liftable = findLiftable($from, $to)
  if (!liftable) {
    if (!silent) throw new RangeError("No valid lift target")
    return this
  }

  let {depth, shared, unwrap} = liftable

  let gapStart = $from.before(shared + 1), gapEnd = $to.after(shared + 1)
  let start = gapStart, end = gapEnd

  let before = Fragment.empty, beforeDepth = 0
  for (let d = shared, splitting = false; d > depth; d--)
    if (splitting || $from.index(d) > 0) {
      splitting = true
      before = Fragment.from($from.node(d).copy(before))
      beforeDepth++
    } else {
      start--
    }
  let after = Fragment.empty, afterDepth = 0
  for (let d = shared, splitting = false; d > depth; d--)
    if (splitting || $to.after(d + 1) < $to.end(d)) {
      splitting = true
      after = Fragment.from($to.node(d).copy(after))
      afterDepth++
    } else {
      end++
    }

  if (unwrap) {
    let joinPos = gapStart, parent = $from.node(shared)
    for (let i = $from.index(shared), e = $to.index(shared) + 1, first = true; i < e; i++, first = false) {
      if (!first) {
        this.join(joinPos)
        end -= 2
        gapEnd -= 2
      }
      joinPos += parent.child(i).nodeSize - (first ? 0 : 2)
    }
    ++gapStart
    --gapEnd
  }

  return this.step(new ReplaceWrapStep(start, end, gapStart, gapEnd,
                                       new Slice(before.append(after), beforeDepth, afterDepth),
                                       before.size - beforeDepth, true))
}

// :: (Node, number, ?number, NodeType, ?Object) → bool
// Determines whether the [sibling range](#Node.siblingRange) of the
// given positions can be wrapped in the given node type.
export function canWrap(doc, from, to, type, attrs) {
  return !!checkWrap(doc.resolve(from), doc.resolve(to == null ? from : to), type, attrs)
}

function checkWrap($from, $to, type, attrs) {
  let shared = rangeDepth($from, $to)
  if (shared == null) return null
  let parent = $from.node(shared)
  let around = parent.findWrappingAt($from.index(shared), type)
  if (!around || !parent.canReplaceWith($from.index(shared), $to.indexAfter(shared), around[0] || type)) return null
  let inside = type.findWrapping(parent.child($from.index(shared)).type, type.contentExpr.start(attrs || type.defaultAttrs))
  if (around && inside) return {shared, around, inside}
}

// :: (number, ?number, NodeType, ?Object) → Transform
// Wrap the [sibling range](#Node.siblingRange) of the given positions
// in a node of the given type, with the given attributes (if
// possible).
Transform.prototype.wrap = function(from, to = from, type, wrapAttrs) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let check = checkWrap($from, $to, type, wrapAttrs)
  if (!check) throw new RangeError("Wrap not possible")
  let {shared, around, inside} = check

  let content = Fragment.empty, open = inside.length + 1 + around.length
  for (let i = inside.length - 1; i >= 0; i--) content = Fragment.from(inside[i].create(null, content))
  content = Fragment.from(type.create(wrapAttrs, content))
  for (let i = around.length - 1; i >= 0; i--) content = Fragment.from(around[i].create(null, content))

  let start = $from.before(shared + 1), end = $to.after(shared + 1)
  this.step(new ReplaceWrapStep(start, end, start, end, new Slice(content, 0, 0), open, true))

  if (inside.length) {
    let splitPos = start + open, parent = $from.node(shared)
    for (let i = $from.index(shared), e = $to.index(shared) + 1, first = true; i < e; i++, first = false) {
      if (!first) this.split(splitPos, inside.length)
      splitPos += parent.child(i).nodeSize + (first ? 0 : 2 * inside.length)
    }
  }
  return this
}

// :: (number, ?number, NodeType, ?Object) → Transform
// Set the type of all textblocks (partly) between `from` and `to` to
// the given node type with the given attributes.
Transform.prototype.setBlockType = function(from, to = from, type, attrs) {
  if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
  this.doc.nodesBetween(from, to, (node, pos) => {
    if (node.isTextblock && !node.hasMarkup(type, attrs)) {
      // Ensure all markup that isn't allowed in the new node type is cleared
      this.clearMarkupFor(this.map(pos), type, attrs)
      let startM = this.map(pos), endM = this.map(pos + node.nodeSize)
      this.step(new ReplaceWrapStep(startM, endM, startM + 1, endM - 1,
                                    new Slice(Fragment.from(type.create(attrs)), 0, 0), 1, true))
      return false
    }
  })
  return this
}

// :: (number, ?NodeType, ?Object) → Transform
// Change the type and attributes of the node after `pos`.
Transform.prototype.setNodeType = function(pos, type, attrs) {
  let node = this.doc.nodeAt(pos)
  if (!node) throw new RangeError("No node at given position")
  if (!type) type = node.type
  if (node.type.isLeaf)
    return this.replaceWith(pos, pos + node.nodeSize, type.create(attrs, null, node.marks))
<commit_msg>Re-fix issue #338<commit_after>import {Slice, Fragment} from "../model"

import {Transform} from "./transform"
import {ReplaceStep, ReplaceWrapStep} from "./replace_step"

// :: (Node, number, ?number) → bool
// Tells you whether the range in the given positions' shared
// ancestor, or any of _its_ ancestor nodes, can be lifted out of a
// parent.
export function canLift(doc, from, to) {
  return !!findLiftable(doc.resolve(from), doc.resolve(to == null ? from : to))
}

function rangeDepth($from, $to) {
  let shared = $from.sameDepth($to)
  if ($from.node(shared).isTextblock || $from.pos == $to.pos) --shared
  if (shared < 0 || $from.pos > $to.pos) return null
  return shared
}

function canCut(node, start, end) {
  return (start == 0 || node.canReplace(start, node.childCount)) &&
    (end == node.childCount || node.canReplace(0, start))
}

function findLiftable($from, $to) {
  let shared = rangeDepth($from, $to)
  if (!shared) return null
  let parent = $from.node(shared), content = parent.content.cutByIndex($from.index(shared), $to.indexAfter(shared))
  for (let depth = shared;; --depth) {
    let node = $from.node(depth), index = $from.index(depth)
    if (depth < shared && node.canReplace(index, index + 1, content))
      return {depth, shared, unwrap: false}
    if (depth == 0 || !canCut(node, index, index + 1)) break
  }

  if (parent.isBlock) {
    let joined = Fragment.empty
    content.forEach(node => joined = joined.append(node.content))
    for (let depth = shared;; --depth) {
      let node = $from.node(depth), index = $from.index(depth)
      if (depth < shared && node.canReplace(index, index + 1, joined))
        return {depth, shared, unwrap: true}
      if (depth == 0 || !canCut(node, index, index + 1)) break
    }
  }
}

// :: (number, ?number, ?bool) → Transform
// Lift the nearest liftable ancestor of the [sibling
// range](#Node.siblingRange) of the given positions out of its parent
// (or do nothing if no such node exists). When `silent` is true, this
// won't raise an error when the lift is impossible.
Transform.prototype.lift = function(from, to = from, silent = false) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let liftable = findLiftable($from, $to)
  if (!liftable) {
    if (!silent) throw new RangeError("No valid lift target")
    return this
  }

  let {depth, shared, unwrap} = liftable

  let gapStart = $from.before(shared + 1), gapEnd = $to.after(shared + 1)
  let start = gapStart, end = gapEnd

  let before = Fragment.empty, beforeDepth = 0
  for (let d = shared, splitting = false; d > depth; d--)
    if (splitting || $from.index(d) > 0) {
      splitting = true
      before = Fragment.from($from.node(d).copy(before))
      beforeDepth++
    } else {
      start--
    }
  let after = Fragment.empty, afterDepth = 0
  for (let d = shared, splitting = false; d > depth; d--)
    if (splitting || $to.after(d + 1) < $to.end(d)) {
      splitting = true
      after = Fragment.from($to.node(d).copy(after))
      afterDepth++
    } else {
      end++
    }

  if (unwrap) {
    let joinPos = gapStart, parent = $from.node(shared)
    for (let i = $from.index(shared), e = $to.index(shared) + 1, first = true; i < e; i++, first = false) {
      if (!first) {
        this.join(joinPos)
        end -= 2
        gapEnd -= 2
      }
      joinPos += parent.child(i).nodeSize - (first ? 0 : 2)
    }
    ++gapStart
    --gapEnd
  }

  return this.step(new ReplaceWrapStep(start, end, gapStart, gapEnd,
                                       new Slice(before.append(after), beforeDepth, afterDepth),
                                       before.size - beforeDepth, true))
}

// :: (Node, number, ?number, NodeType, ?Object) → bool
// Determines whether the [sibling range](#Node.siblingRange) of the
// given positions can be wrapped in the given node type.
export function canWrap(doc, from, to, type, attrs) {
  return !!checkWrap(doc.resolve(from), doc.resolve(to == null ? from : to), type, attrs)
}

function checkWrap($from, $to, type, attrs) {
  let shared = rangeDepth($from, $to)
  if (shared == null) return null
  let parent = $from.node(shared)
  let around = parent.findWrappingAt($from.index(shared), type)
  if (!around || !parent.canReplaceWith($from.index(shared), $to.indexAfter(shared), around[0] || type)) return null
  let inside = type.findWrapping(parent.child($from.index(shared)).type, type.contentExpr.start(attrs || type.defaultAttrs))
  if (around && inside) return {shared, around, inside}
}

// :: (number, ?number, NodeType, ?Object) → Transform
// Wrap the [sibling range](#Node.siblingRange) of the given positions
// in a node of the given type, with the given attributes (if
// possible).
Transform.prototype.wrap = function(from, to = from, type, wrapAttrs) {
  let $from = this.doc.resolve(from), $to = this.doc.resolve(to)
  let check = checkWrap($from, $to, type, wrapAttrs)
  if (!check) throw new RangeError("Wrap not possible")
  let {shared, around, inside} = check

  let content = Fragment.empty, open = inside.length + 1 + around.length
  for (let i = inside.length - 1; i >= 0; i--) content = Fragment.from(inside[i].create(null, content))
  content = Fragment.from(type.create(wrapAttrs, content))
  for (let i = around.length - 1; i >= 0; i--) content = Fragment.from(around[i].create(null, content))

  let start = $from.before(shared + 1), end = $to.after(shared + 1)
  this.step(new ReplaceWrapStep(start, end, start, end, new Slice(content, 0, 0), open, true))

  if (inside.length) {
    let splitPos = start + open, parent = $from.node(shared)
    for (let i = $from.index(shared), e = $to.index(shared) + 1, first = true; i < e; i++, first = false) {
      if (!first) this.split(splitPos, inside.length)
      splitPos += parent.child(i).nodeSize + (first ? 0 : 2 * inside.length)
    }
  }
  return this
}

// :: (number, ?number, NodeType, ?Object) → Transform
// Set the type of all textblocks (partly) between `from` and `to` to
// the given node type with the given attributes.
Transform.prototype.setBlockType = function(from, to = from, type, attrs) {
  if (!type.isTextblock) throw new RangeError("Type given to setBlockType should be a textblock")
  let mapFrom = this.steps.length
  this.doc.nodesBetween(from, to, (node, pos) => {
    if (node.isTextblock && !node.hasMarkup(type, attrs)) {
      // Ensure all markup that isn't allowed in the new node type is cleared
      this.clearMarkupFor(this.map(pos, 1, mapFrom), type, attrs)
      let startM = this.map(pos, 1, mapFrom), endM = this.map(pos + node.nodeSize, 1, mapFrom)
      this.step(new ReplaceWrapStep(startM, endM, startM + 1, endM - 1,
                                    new Slice(Fragment.from(type.create(attrs)), 0, 0), 1, true))
      return false
    }
  })
  return this
}

// :: (number, ?NodeType, ?Object) → Transform
// Change the type and attributes of the node after `pos`.
Transform.prototype.setNodeType = function(pos, type, attrs) {
  let node = this.doc.nodeAt(pos)
  if (!node) throw new RangeError("No node at given position")
  if (!type) type = node.type
  if (node.type.isLeaf)
    return this.replaceWith(pos, pos + node.nodeSize, type.create(attrs, null, node.marks))

{'fraction_non_alphanumeric': 0.15022265516281658, 'fraction_numerical': 0.005705538547175063, 'mean_word_length': 3.282777115613826, 'pattern_counts': {'":': 0, '<': 15, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 8, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '51079', 'n_tokens_mistral': 4757, 'n_tokens_neox': 4627, 'n_words': 1439}