28 |
28 |
29 val NO_ID: ID = 0 |
29 val NO_ID: ID = 0 |
30 |
30 |
31 |
31 |
32 |
32 |
33 /** named document nodes **/ |
33 /** document structure **/ |
|
34 |
|
35 /* named nodes -- development graph */ |
|
36 |
|
37 type Node_Text_Edit = (String, List[Text_Edit]) // FIXME None: remove |
|
38 |
|
39 type Edit[C] = |
|
40 (String, // node name |
|
41 Option[List[(Option[C], Option[C])]]) // None: remove, Some: insert/remove commands |
34 |
42 |
35 object Node |
43 object Node |
36 { |
44 { |
37 val empty: Node = new Node(Linear_Set()) |
45 val empty: Node = new Node(Linear_Set()) |
38 |
46 |
|
47 // FIXME not scalable |
39 def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] = |
48 def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] = |
40 { |
49 { |
41 var i = offset |
50 var i = offset |
42 for (command <- commands) yield { |
51 for (command <- commands) yield { |
43 val start = i |
52 val start = i |
47 } |
56 } |
48 } |
57 } |
49 |
58 |
50 class Node(val commands: Linear_Set[Command]) |
59 class Node(val commands: Linear_Set[Command]) |
51 { |
60 { |
52 /* command ranges */ |
|
53 |
|
54 def command_starts: Iterator[(Command, Int)] = |
61 def command_starts: Iterator[(Command, Int)] = |
55 Node.command_starts(commands.iterator) |
62 Node.command_starts(commands.iterator) |
56 |
63 |
57 def command_start(cmd: Command): Option[Int] = |
64 def command_start(cmd: Command): Option[Int] = |
58 command_starts.find(_._1 == cmd).map(_._2) |
65 command_starts.find(_._1 == cmd).map(_._2) |
76 case None => None |
83 case None => None |
77 } |
84 } |
78 } |
85 } |
79 |
86 |
80 |
87 |
81 /* document versions */ |
88 |
|
89 /** versioning **/ |
|
90 |
|
91 /* particular document versions */ |
82 |
92 |
83 object Version |
93 object Version |
84 { |
94 { |
85 val init: Version = new Version(NO_ID, Map().withDefaultValue(Node.empty)) |
95 val init: Version = new Version(NO_ID, Map().withDefaultValue(Node.empty)) |
86 } |
96 } |
87 |
97 |
88 class Version(val id: Version_ID, val nodes: Map[String, Node]) |
98 class Version(val id: Version_ID, val nodes: Map[String, Node]) |
89 |
99 |
90 |
100 |
91 |
101 /* changes of plain text, eventually resulting in document edits */ |
92 /** changes of plain text, eventually resulting in document edits **/ |
102 |
93 |
103 object Change |
94 type Node_Text_Edit = (String, List[Text_Edit]) // FIXME None: remove |
104 { |
95 |
105 val init = new Change(Future.value(Version.init), Nil, Future.value(Nil, Version.init)) |
96 type Edit[C] = |
106 } |
97 (String, // node name |
107 |
98 Option[List[(Option[C], Option[C])]]) // None: remove, Some: insert/remove commands |
108 class Change( |
|
109 val previous: Future[Version], |
|
110 val edits: List[Node_Text_Edit], |
|
111 val result: Future[(List[Edit[Command]], Version)]) |
|
112 { |
|
113 val current: Future[Version] = result.map(_._2) |
|
114 def is_finished: Boolean = previous.is_finished && current.is_finished |
|
115 } |
|
116 |
|
117 |
|
118 /* history navigation and persistent snapshots */ |
99 |
119 |
100 abstract class Snapshot |
120 abstract class Snapshot |
101 { |
121 { |
102 val version: Version |
122 val version: Version |
103 val node: Node |
123 val node: Node |
106 def revert(offset: Int): Int |
126 def revert(offset: Int): Int |
107 def lookup_command(id: Command_ID): Option[Command] |
127 def lookup_command(id: Command_ID): Option[Command] |
108 def state(command: Command): Command.State |
128 def state(command: Command): Command.State |
109 } |
129 } |
110 |
130 |
111 object Change |
131 object History |
112 { |
132 { |
113 val init = new Change(Future.value(Version.init), Nil, Future.value(Nil, Version.init)) |
133 val init = new History(List(Change.init)) |
114 } |
134 } |
115 |
135 |
116 class Change( |
136 // FIXME pruning, purging of state |
117 val previous: Future[Version], |
137 class History(undo_list: List[Change]) |
118 val edits: List[Node_Text_Edit], |
138 { |
119 val result: Future[(List[Edit[Command]], Version)]) |
139 require(!undo_list.isEmpty) |
120 { |
140 |
121 val current: Future[Version] = result.map(_._2) |
141 def tip: Change = undo_list.head |
122 def is_finished: Boolean = previous.is_finished && current.is_finished |
142 def +(ch: Change): History = new History(ch :: undo_list) |
|
143 |
|
144 def snapshot(name: String, pending_edits: List[Text_Edit], state_snapshot: State): Snapshot = |
|
145 { |
|
146 val found_stable = undo_list.find(change => |
|
147 change.is_finished && state_snapshot.is_assigned(change.current.join)) |
|
148 require(found_stable.isDefined) |
|
149 val stable = found_stable.get |
|
150 val latest = undo_list.head |
|
151 |
|
152 val edits = |
|
153 (pending_edits /: undo_list.takeWhile(_ != stable))((edits, change) => |
|
154 (for ((a, eds) <- change.edits if a == name) yield eds).flatten ::: edits) |
|
155 lazy val reverse_edits = edits.reverse |
|
156 |
|
157 new Snapshot |
|
158 { |
|
159 val version = stable.current.join |
|
160 val node = version.nodes(name) |
|
161 val is_outdated = !(pending_edits.isEmpty && latest == stable) |
|
162 def convert(offset: Int): Int = (offset /: edits)((i, edit) => edit.convert(i)) |
|
163 def revert(offset: Int): Int = (offset /: reverse_edits)((i, edit) => edit.revert(i)) |
|
164 def lookup_command(id: Command_ID): Option[Command] = state_snapshot.lookup_command(id) |
|
165 def state(command: Command): Command.State = |
|
166 try { state_snapshot.command_state(version, command) } |
|
167 catch { case _: State.Fail => command.empty_state } |
|
168 } |
|
169 } |
123 } |
170 } |
124 |
171 |
125 |
172 |
126 |
173 |
127 /** global state -- document structure and execution process **/ |
174 /** global state -- document structure and execution process **/ |