1 /* Title: Pure/PIDE/document.scala
4 Document as collection of named nodes, each consisting of an editable
5 list of commands, associated with asynchronous execution process.
11 import scala.collection.mutable
16 /* formal identifiers */
21 def apply(id: ID): String = Markup.Long.apply(id)
22 def unapply(s: String): Option[ID] = Markup.Long.unapply(s)
33 /** named document nodes **/
37 val empty: Node = new Node(Linear_Set())
39 def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
42 for (command <- commands) yield {
50 class Node(val commands: Linear_Set[Command])
54 def command_starts: Iterator[(Command, Int)] =
55 Node.command_starts(commands.iterator)
57 def command_start(cmd: Command): Option[Int] =
58 command_starts.find(_._1 == cmd).map(_._2)
60 def command_range(i: Int): Iterator[(Command, Int)] =
61 command_starts dropWhile { case (cmd, start) => start + cmd.length <= i }
63 def command_range(i: Int, j: Int): Iterator[(Command, Int)] =
64 command_range(i) takeWhile { case (_, start) => start < j }
66 def command_at(i: Int): Option[(Command, Int)] =
68 val range = command_range(i)
69 if (range.hasNext) Some(range.next) else None
72 def proper_command_at(i: Int): Option[Command] =
74 case Some((command, _)) =>
75 commands.reverse_iterator(command).find(cmd => !cmd.is_ignored)
81 /* document versions */
85 val init: Version = new Version(NO_ID, Map().withDefaultValue(Node.empty))
88 class Version(val id: Version_ID, val nodes: Map[String, Node])
92 /** changes of plain text, eventually resulting in document edits **/
94 type Node_Text_Edit = (String, List[Text_Edit]) // FIXME None: remove
98 Option[List[(Option[C], Option[C])]]) // None: remove, Some: insert/remove commands
100 abstract class Snapshot
104 val is_outdated: Boolean
105 def convert(offset: Int): Int
106 def revert(offset: Int): Int
107 def lookup_command(id: Command_ID): Option[Command]
108 def state(command: Command): Command.State
113 val init = new Change(Future.value(Version.init), Nil, Future.value(Nil, Version.init))
117 val previous: Future[Version],
118 val edits: List[Node_Text_Edit],
119 val result: Future[(List[Edit[Command]], Version)])
121 val current: Future[Version] = result.map(_._2)
122 def is_finished: Boolean = previous.is_finished && current.is_finished
127 /** global state -- document structure and execution process **/
131 class Fail(state: State) extends Exception
133 val init = State().define_version(Version.init, Map()).assign(Version.init.id, Nil)
135 class Assignment(former_assignment: Map[Command, Exec_ID])
137 @volatile private var tmp_assignment = former_assignment
138 private val promise = Future.promise[Map[Command, Exec_ID]]
139 def is_finished: Boolean = promise.is_finished
140 def join: Map[Command, Exec_ID] = promise.join
141 def assign(command_execs: List[(Command, Exec_ID)])
143 promise.fulfill(tmp_assignment ++ command_execs)
144 tmp_assignment = Map()
150 val versions: Map[Version_ID, Version] = Map(),
151 val commands: Map[Command_ID, Command.State] = Map(),
152 val execs: Map[Exec_ID, (Command.State, Set[Version])] = Map(),
153 val assignments: Map[Version, State.Assignment] = Map(),
154 val disposed: Set[ID] = Set()) // FIXME unused!?
156 private def fail[A]: A = throw new State.Fail(this)
158 def define_version(version: Version, former_assignment: Map[Command, Exec_ID]): State =
161 if (versions.isDefinedAt(id) || disposed(id)) fail
162 copy(versions = versions + (id -> version),
163 assignments = assignments + (version -> new State.Assignment(former_assignment)))
166 def define_command(command: Command): State =
169 if (commands.isDefinedAt(id) || disposed(id)) fail
170 copy(commands = commands + (id -> command.empty_state))
173 def lookup_command(id: Command_ID): Option[Command] = commands.get(id).map(_.command)
175 def the_version(id: Version_ID): Version = versions.getOrElse(id, fail)
176 def the_command(id: Command_ID): Command.State = commands.getOrElse(id, fail)
177 def the_exec_state(id: Exec_ID): Command.State = execs.getOrElse(id, fail)._1
178 def the_assignment(version: Version): State.Assignment = assignments.getOrElse(version, fail)
180 def accumulate(id: ID, message: XML.Tree): (Command.State, State) =
181 execs.get(id) match {
182 case Some((st, occs)) =>
183 val new_st = st.accumulate(message)
184 (new_st, copy(execs = execs + (id -> (new_st, occs))))
186 commands.get(id) match {
188 val new_st = st.accumulate(message)
189 (new_st, copy(commands = commands + (id -> new_st)))
194 def assign(id: Version_ID, edits: List[(Command_ID, Exec_ID)]): State =
196 val version = the_version(id)
197 val occs = Set(version) // FIXME unused (!?)
199 var new_execs = execs
201 for ((cmd_id, exec_id) <- edits) yield {
202 val st = the_command(cmd_id)
203 if (new_execs.isDefinedAt(exec_id) || disposed(exec_id)) fail
204 new_execs += (exec_id -> (st, occs))
205 (st.command, exec_id)
207 the_assignment(version).assign(assigned_execs) // FIXME explicit value instead of promise (!?)
208 copy(execs = new_execs)
211 def is_assigned(version: Version): Boolean =
212 assignments.get(version) match {
213 case Some(assgn) => assgn.is_finished
217 def command_state(version: Version, command: Command): Command.State =
219 val assgn = the_assignment(version)
220 require(assgn.is_finished)
221 the_exec_state(assgn.join.getOrElse(command, fail))