wenzelm@38425: /* Title: Pure/PIDE/text.scala wenzelm@34276: Author: Fabian Immler, TU Munich wenzelm@34276: Author: Makarius wenzelm@34276: wenzelm@38425: Basic operations on plain text. wenzelm@34276: */ wenzelm@34276: wenzelm@34276: package isabelle wenzelm@34276: wenzelm@34276: wenzelm@44379: import scala.collection.mutable wenzelm@44379: import scala.math.Ordering wenzelm@44379: import scala.util.Sorting wenzelm@44379: wenzelm@44379: wenzelm@38425: object Text wenzelm@34276: { wenzelm@38477: /* offset */ wenzelm@38426: wenzelm@38426: type Offset = Int wenzelm@38426: wenzelm@38477: wenzelm@38565: /* range -- with total quasi-ordering */ wenzelm@38477: wenzelm@38568: object Range wenzelm@38568: { wenzelm@38568: def apply(start: Offset): Range = Range(start, start) wenzelm@44379: wenzelm@44379: object Ordering extends scala.math.Ordering[Text.Range] wenzelm@44379: { wenzelm@44379: def compare(r1: Text.Range, r2: Text.Range): Int = r1 compare r2 wenzelm@44379: } wenzelm@38568: } wenzelm@38568: wenzelm@38426: sealed case class Range(val start: Offset, val stop: Offset) wenzelm@38427: { wenzelm@38565: // denotation: {start} Un {i. start < i & i < stop} wenzelm@43425: if (start > stop) wenzelm@43425: error("Bad range: [" + start.toString + ":" + stop.toString + "]") wenzelm@38477: wenzelm@38563: override def toString = "[" + start.toString + ":" + stop.toString + "]" wenzelm@38563: wenzelm@38427: def map(f: Offset => Offset): Range = Range(f(start), f(stop)) wenzelm@38427: def +(i: Offset): Range = map(_ + i) wenzelm@38570: def -(i: Offset): Range = map(_ - i) wenzelm@38662: wenzelm@38725: def is_singularity: Boolean = start == stop wenzelm@38662: wenzelm@38565: def contains(i: Offset): Boolean = start == i || start < i && i < stop wenzelm@38565: def contains(that: Range): Boolean = this.contains(that.start) && that.stop <= this.stop wenzelm@38565: def overlaps(that: Range): Boolean = this.contains(that.start) || that.contains(this.start) wenzelm@38565: def compare(that: Range): Int = if (overlaps(that)) 0 else this.start compare that.start wenzelm@38485: wenzelm@45240: def apart(that: Range): Boolean = wenzelm@45240: (this.start max that.start) > (this.stop min that.stop) wenzelm@45240: wenzelm@38564: def restrict(that: Range): Range = wenzelm@38485: Range(this.start max that.start, this.stop min that.stop) wenzelm@43428: wenzelm@43428: def try_restrict(that: Range): Option[Range] = wenzelm@45240: if (this apart that) None wenzelm@45240: else Some(restrict(that)) wenzelm@45240: wenzelm@45240: def try_join(that: Range): Option[Range] = wenzelm@45240: if (this apart that) None wenzelm@45240: else Some(Range(this.start min that.start, this.stop max that.stop)) wenzelm@38427: } wenzelm@38426: wenzelm@38426: wenzelm@44379: /* perspective */ wenzelm@44379: wenzelm@44473: object Perspective wenzelm@44379: { wenzelm@44474: val empty: Perspective = Perspective(Nil) wenzelm@44379: wenzelm@44473: def apply(ranges: Seq[Range]): Perspective = wenzelm@44379: { wenzelm@44473: val result = new mutable.ListBuffer[Text.Range] wenzelm@44473: var last: Option[Text.Range] = None wenzelm@45240: def ship(next: Option[Range]) { result ++= last; last = next } wenzelm@45240: wenzelm@45240: for (range <- ranges.sortBy(_.start)) wenzelm@44473: { wenzelm@44473: last match { wenzelm@45240: case None => ship(Some(range)) wenzelm@45240: case Some(last_range) => wenzelm@45240: last_range.try_join(range) match { wenzelm@45240: case None => ship(Some(range)) wenzelm@45240: case joined => last = joined wenzelm@45240: } wenzelm@44473: } wenzelm@44379: } wenzelm@45240: ship(None) wenzelm@44473: new Perspective(result.toList) wenzelm@44379: } wenzelm@44473: } wenzelm@44473: wenzelm@45240: class Perspective private(val ranges: List[Range]) // visible text partitioning in canonical order wenzelm@44473: { wenzelm@44473: def is_empty: Boolean = ranges.isEmpty wenzelm@44473: def range: Range = wenzelm@44473: if (is_empty) Range(0) wenzelm@44473: else Range(ranges.head.start, ranges.last.stop) wenzelm@45631: wenzelm@45631: override def hashCode: Int = ranges.hashCode wenzelm@45631: override def equals(that: Any): Boolean = wenzelm@45631: that match { wenzelm@45631: case other: Perspective => ranges == other.ranges wenzelm@45631: case _ => false wenzelm@45631: } wenzelm@45240: override def toString = ranges.toString wenzelm@44379: } wenzelm@44379: wenzelm@44379: wenzelm@38577: /* information associated with text range */ wenzelm@38577: wenzelm@43714: sealed case class Info[A](val range: Text.Range, val info: A) wenzelm@38577: { wenzelm@38577: def restrict(r: Text.Range): Info[A] = Info(range.restrict(r), info) wenzelm@43428: def try_restrict(r: Text.Range): Option[Info[A]] = wenzelm@43428: try { Some(Info(range.restrict(r), info)) } wenzelm@43650: catch { case ERROR(_) => None } wenzelm@38577: } wenzelm@38577: wenzelm@45470: type Markup = Info[XML.Elem] wenzelm@45455: wenzelm@38577: wenzelm@38426: /* editing */ wenzelm@34286: wenzelm@38425: object Edit wenzelm@38425: { wenzelm@38426: def insert(start: Offset, text: String): Edit = new Edit(true, start, text) wenzelm@38426: def remove(start: Offset, text: String): Edit = new Edit(false, start, text) wenzelm@38425: } wenzelm@34286: wenzelm@45250: class Edit private(val is_insert: Boolean, val start: Offset, val text: String) wenzelm@38425: { wenzelm@38425: override def toString = wenzelm@38425: (if (is_insert) "Insert(" else "Remove(") + (start, text).toString + ")" wenzelm@34286: wenzelm@34286: wenzelm@38425: /* transform offsets */ wenzelm@34286: wenzelm@38426: private def transform(do_insert: Boolean, i: Offset): Offset = wenzelm@38426: if (i < start) i wenzelm@43425: else if (do_insert) i + text.length wenzelm@38426: else (i - text.length) max start wenzelm@34286: wenzelm@43425: def convert(i: Offset): Offset = transform(is_insert, i) wenzelm@43425: def revert(i: Offset): Offset = transform(!is_insert, i) wenzelm@43425: def convert(range: Range): Range = range.map(convert) wenzelm@43425: def revert(range: Range): Range = range.map(revert) wenzelm@38425: wenzelm@34286: wenzelm@38425: /* edit strings */ wenzelm@38425: wenzelm@38426: private def insert(i: Offset, string: String): String = wenzelm@38426: string.substring(0, i) + text + string.substring(i) wenzelm@34276: wenzelm@38426: private def remove(i: Offset, count: Int, string: String): String = wenzelm@38426: string.substring(0, i) + string.substring(i + count) wenzelm@38425: wenzelm@38425: def can_edit(string: String, shift: Int): Boolean = wenzelm@38425: shift <= start && start < shift + string.length wenzelm@38425: wenzelm@38425: def edit(string: String, shift: Int): (Option[Edit], String) = wenzelm@38425: if (!can_edit(string, shift)) (Some(this), string) wenzelm@38425: else if (is_insert) (None, insert(start - shift, string)) wenzelm@38425: else { wenzelm@38426: val i = start - shift wenzelm@38426: val count = text.length min (string.length - i) wenzelm@38425: val rest = wenzelm@38425: if (count == text.length) None wenzelm@38425: else Some(Edit.remove(start, text.substring(count))) wenzelm@38426: (rest, remove(i, count, string)) wenzelm@38425: } wenzelm@38425: } wenzelm@34276: }