10 |
10 |
11 import scala.collection.mutable |
11 import scala.collection.mutable |
12 import scala.util.Sorting |
12 import scala.util.Sorting |
13 |
13 |
14 |
14 |
15 object Text |
15 object Text { |
16 { |
|
17 /* offset */ |
16 /* offset */ |
18 |
17 |
19 type Offset = Int |
18 type Offset = Int |
20 |
19 |
21 |
20 |
22 /* range -- with total quasi-ordering */ |
21 /* range -- with total quasi-ordering */ |
23 |
22 |
24 object Range |
23 object Range { |
25 { |
|
26 def apply(start: Offset): Range = Range(start, start) |
24 def apply(start: Offset): Range = Range(start, start) |
27 |
25 |
28 val full: Range = apply(0, Integer.MAX_VALUE / 2) |
26 val full: Range = apply(0, Integer.MAX_VALUE / 2) |
29 val offside: Range = apply(-1) |
27 val offside: Range = apply(-1) |
30 |
28 |
31 object Ordering extends scala.math.Ordering[Range] |
29 object Ordering extends scala.math.Ordering[Range] { |
32 { |
|
33 def compare(r1: Range, r2: Range): Int = r1 compare r2 |
30 def compare(r1: Range, r2: Range): Int = r1 compare r2 |
34 } |
31 } |
35 } |
32 } |
36 |
33 |
37 sealed case class Range(start: Offset, stop: Offset) |
34 sealed case class Range(start: Offset, stop: Offset) { |
38 { |
|
39 // denotation: {start} Un {i. start < i & i < stop} |
35 // denotation: {start} Un {i. start < i & i < stop} |
40 if (start > stop) |
36 if (start > stop) |
41 error("Bad range: [" + start.toString + ".." + stop.toString + "]") |
37 error("Bad range: [" + start.toString + ".." + stop.toString + "]") |
42 |
38 |
43 override def toString: String = "[" + start.toString + ".." + stop.toString + "]" |
39 override def toString: String = "[" + start.toString + ".." + stop.toString + "]" |
80 } |
76 } |
81 |
77 |
82 |
78 |
83 /* perspective */ |
79 /* perspective */ |
84 |
80 |
85 object Perspective |
81 object Perspective { |
86 { |
|
87 val empty: Perspective = Perspective(Nil) |
82 val empty: Perspective = Perspective(Nil) |
88 |
83 |
89 def full: Perspective = Perspective(List(Range.full)) |
84 def full: Perspective = Perspective(List(Range.full)) |
90 |
85 |
91 def apply(ranges: List[Range]): Perspective = |
86 def apply(ranges: List[Range]): Perspective = { |
92 { |
|
93 val result = new mutable.ListBuffer[Range] |
87 val result = new mutable.ListBuffer[Range] |
94 var last: Option[Range] = None |
88 var last: Option[Range] = None |
95 def ship(next: Option[Range]): Unit = { result ++= last; last = next } |
89 def ship(next: Option[Range]): Unit = { result ++= last; last = next } |
96 |
90 |
97 for (range <- ranges.sortBy(_.start)) |
91 for (range <- ranges.sortBy(_.start)) { |
98 { |
|
99 last match { |
92 last match { |
100 case None => ship(Some(range)) |
93 case None => ship(Some(range)) |
101 case Some(last_range) => |
94 case Some(last_range) => |
102 last_range.try_join(range) match { |
95 last_range.try_join(range) match { |
103 case None => ship(Some(range)) |
96 case None => ship(Some(range)) |
109 new Perspective(result.toList) |
102 new Perspective(result.toList) |
110 } |
103 } |
111 } |
104 } |
112 |
105 |
113 final class Perspective private( |
106 final class Perspective private( |
114 val ranges: List[Range]) // visible text partitioning in canonical order |
107 val ranges: List[Range] // visible text partitioning in canonical order |
115 { |
108 ) { |
116 def is_empty: Boolean = ranges.isEmpty |
109 def is_empty: Boolean = ranges.isEmpty |
117 def range: Range = |
110 def range: Range = |
118 if (is_empty) Range(0) |
111 if (is_empty) Range(0) |
119 else Range(ranges.head.start, ranges.last.stop) |
112 else Range(ranges.head.start, ranges.last.stop) |
120 |
113 |
128 } |
121 } |
129 |
122 |
130 |
123 |
131 /* information associated with text range */ |
124 /* information associated with text range */ |
132 |
125 |
133 sealed case class Info[A](range: Range, info: A) |
126 sealed case class Info[A](range: Range, info: A) { |
134 { |
|
135 def restrict(r: Range): Info[A] = Info(range.restrict(r), info) |
127 def restrict(r: Range): Info[A] = Info(range.restrict(r), info) |
136 def try_restrict(r: Range): Option[Info[A]] = range.try_restrict(r).map(Info(_, info)) |
128 def try_restrict(r: Range): Option[Info[A]] = range.try_restrict(r).map(Info(_, info)) |
137 |
129 |
138 def map[B](f: A => B): Info[B] = Info(range, f(info)) |
130 def map[B](f: A => B): Info[B] = Info(range, f(info)) |
139 } |
131 } |
141 type Markup = Info[XML.Elem] |
133 type Markup = Info[XML.Elem] |
142 |
134 |
143 |
135 |
144 /* editing */ |
136 /* editing */ |
145 |
137 |
146 object Edit |
138 object Edit { |
147 { |
|
148 def insert(start: Offset, text: String): Edit = new Edit(true, start, text) |
139 def insert(start: Offset, text: String): Edit = new Edit(true, start, text) |
149 def remove(start: Offset, text: String): Edit = new Edit(false, start, text) |
140 def remove(start: Offset, text: String): Edit = new Edit(false, start, text) |
150 def inserts(start: Offset, text: String): List[Edit] = |
141 def inserts(start: Offset, text: String): List[Edit] = |
151 if (text == "") Nil else List(insert(start, text)) |
142 if (text == "") Nil else List(insert(start, text)) |
152 def removes(start: Offset, text: String): List[Edit] = |
143 def removes(start: Offset, text: String): List[Edit] = |
154 def replace(start: Offset, old_text: String, new_text: String): List[Edit] = |
145 def replace(start: Offset, old_text: String, new_text: String): List[Edit] = |
155 if (old_text == new_text) Nil |
146 if (old_text == new_text) Nil |
156 else removes(start, old_text) ::: inserts(start, new_text) |
147 else removes(start, old_text) ::: inserts(start, new_text) |
157 } |
148 } |
158 |
149 |
159 final class Edit private(val is_insert: Boolean, val start: Offset, val text: String) |
150 final class Edit private(val is_insert: Boolean, val start: Offset, val text: String) { |
160 { |
|
161 override def toString: String = |
151 override def toString: String = |
162 (if (is_insert) "Insert(" else "Remove(") + (start, text).toString + ")" |
152 (if (is_insert) "Insert(" else "Remove(") + (start, text).toString + ")" |
163 |
153 |
164 |
154 |
165 /* transform offsets */ |
155 /* transform offsets */ |