1 /* Title: Pure/PIDE/document.scala
4 Document as collection of named nodes, each consisting of an editable
11 import scala.collection.mutable
12 import scala.annotation.tailrec
17 /* formal identifiers */
26 def parse_id(s: String): ID = java.lang.Long.parseLong(s)
27 def print_id(id: ID): String = id.toString
31 /** named document nodes **/
35 val empty: Node = new Node(Linear_Set())
37 def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
40 for (command <- commands) yield {
48 class Node(val commands: Linear_Set[Command])
52 def command_starts: Iterator[(Command, Int)] =
53 Node.command_starts(commands.iterator)
55 def command_start(cmd: Command): Option[Int] =
56 command_starts.find(_._1 == cmd).map(_._2)
58 def command_range(i: Int): Iterator[(Command, Int)] =
59 command_starts dropWhile { case (cmd, start) => start + cmd.length <= i }
61 def command_range(i: Int, j: Int): Iterator[(Command, Int)] =
62 command_range(i) takeWhile { case (_, start) => start < j }
64 def command_at(i: Int): Option[(Command, Int)] =
66 val range = command_range(i)
67 if (range.hasNext) Some(range.next) else None
70 def proper_command_at(i: Int): Option[Command] =
72 case Some((command, _)) =>
73 commands.reverse_iterator(command).find(cmd => !cmd.is_ignored)
79 /* initial document */
81 val init: Document = new Document(NO_ID, Map().withDefaultValue(Node.empty))
85 /** changes of plain text, eventually resulting in document edits **/
87 type Node_Text_Edit = (String, List[Text_Edit]) // FIXME None: remove
91 Option[List[(Option[C], Option[C])]]) // None: remove, Some: insert/remove commands
93 abstract class Snapshot
95 val document: Document
96 val node: Document.Node
97 val is_outdated: Boolean
98 def convert(offset: Int): Int
99 def revert(offset: Int): Int
100 def lookup_command(id: Command_ID): Option[Command]
101 def state(command: Command): Command.State
106 val init = new Change(Future.value(Document.init), Nil, Future.value(Nil, Document.init))
110 val prev: Future[Document],
111 val edits: List[Node_Text_Edit],
112 val result: Future[(List[Edit[Command]], Document)])
114 val document: Future[Document] = result.map(_._2)
115 def is_finished: Boolean = prev.is_finished && document.is_finished
122 def text_edits(session: Session, old_doc: Document, edits: List[Node_Text_Edit])
123 : (List[Edit[Command]], Document) =
125 /* phase 1: edit individual command source */
127 @tailrec def edit_text(eds: List[Text_Edit],
128 commands: Linear_Set[Command]): Linear_Set[Command] =
132 Node.command_starts(commands.iterator).find {
133 case (cmd, cmd_start) =>
134 e.can_edit(cmd.source, cmd_start) ||
135 e.is_insert && e.start == cmd_start + cmd.length
137 case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
138 val (rest, text) = e.edit(cmd.source, cmd_start)
139 val new_commands = commands.insert_after(Some(cmd), Command.unparsed(text)) - cmd
140 edit_text(rest.toList ::: es, new_commands)
142 case Some((cmd, cmd_start)) =>
143 edit_text(es, commands.insert_after(Some(cmd), Command.unparsed(e.text)))
146 require(e.is_insert && e.start == 0)
147 edit_text(es, commands.insert_after(None, Command.unparsed(e.text)))
154 /* phase 2: recover command spans */
156 @tailrec def parse_spans(commands: Linear_Set[Command]): Linear_Set[Command] =
158 commands.iterator.find(_.is_unparsed) match {
159 case Some(first_unparsed) =>
161 commands.reverse_iterator(first_unparsed).find(_.is_command) getOrElse commands.head
163 commands.iterator(first_unparsed).find(_.is_command) getOrElse commands.last
165 commands.iterator(first).takeWhile(_ != last).toList ::: List(last)
167 val sources = range.flatMap(_.span.map(_.source))
168 val spans0 = Thy_Syntax.parse_spans(session.current_syntax.scan(sources.mkString))
170 val (before_edit, spans1) =
171 if (!spans0.isEmpty && first.is_command && first.span == spans0.head)
172 (Some(first), spans0.tail)
173 else (commands.prev(first), spans0)
175 val (after_edit, spans2) =
176 if (!spans1.isEmpty && last.is_command && last.span == spans1.last)
177 (Some(last), spans1.take(spans1.length - 1))
178 else (commands.next(last), spans1)
180 val inserted = spans2.map(span => new Command(session.create_id(), span))
182 commands.delete_between(before_edit, after_edit).append_after(before_edit, inserted)
183 parse_spans(new_commands)
185 case None => commands
190 /* resulting document edits */
193 val doc_edits = new mutable.ListBuffer[Edit[Command]]
194 var nodes = old_doc.nodes
196 for ((name, text_edits) <- edits) {
197 val commands0 = nodes(name).commands
198 val commands1 = edit_text(text_edits, commands0)
199 val commands2 = parse_spans(commands1) // FIXME somewhat slow
201 val removed_commands = commands0.iterator.filter(!commands2.contains(_)).toList
202 val inserted_commands = commands2.iterator.filter(!commands0.contains(_)).toList
205 removed_commands.reverse.map(cmd => (commands0.prev(cmd), None)) :::
206 inserted_commands.map(cmd => (commands2.prev(cmd), Some(cmd)))
208 doc_edits += (name -> Some(cmd_edits))
209 nodes += (name -> new Node(commands2))
211 (doc_edits.toList, new Document(session.create_id(), nodes))
217 /** global state -- accumulated prover results **/
221 class Fail(state: State) extends Exception
223 val init = State().define_document(Document.init, Map()).assign(Document.init.id, Nil)
225 class Assignment(former_assignment: Map[Command, Exec_ID])
227 @volatile private var tmp_assignment = former_assignment
228 private val promise = Future.promise[Map[Command, Exec_ID]]
229 def is_finished: Boolean = promise.is_finished
230 def join: Map[Command, Exec_ID] = promise.join
231 def assign(command_execs: List[(Command, Exec_ID)])
233 promise.fulfill(tmp_assignment ++ command_execs)
234 tmp_assignment = Map()
240 val documents: Map[Version_ID, Document] = Map(),
241 val commands: Map[Command_ID, Command.State] = Map(),
242 val execs: Map[Exec_ID, (Command.State, Set[Document])] = Map(),
243 val assignments: Map[Document, State.Assignment] = Map(),
244 val disposed: Set[ID] = Set()) // FIXME unused!?
246 private def fail[A]: A = throw new State.Fail(this)
248 def define_document(document: Document, former_assignment: Map[Command, Exec_ID]): State =
251 if (documents.isDefinedAt(id) || disposed(id)) fail
252 copy(documents = documents + (id -> document),
253 assignments = assignments + (document -> new State.Assignment(former_assignment)))
256 def define_command(command: Command): State =
259 if (commands.isDefinedAt(id) || disposed(id)) fail
260 copy(commands = commands + (id -> command.empty_state))
263 def lookup_command(id: Command_ID): Option[Command] = commands.get(id).map(_.command)
265 def the_document(id: Version_ID): Document = documents.getOrElse(id, fail)
266 def the_command(id: Command_ID): Command.State = commands.getOrElse(id, fail)
267 def the_exec_state(id: Exec_ID): Command.State = execs.getOrElse(id, fail)._1
268 def the_assignment(document: Document): State.Assignment = assignments.getOrElse(document, fail)
270 def accumulate(id: ID, message: XML.Tree): (Command.State, State) =
271 execs.get(id) match {
272 case Some((st, docs)) =>
273 val new_st = st.accumulate(message)
274 (new_st, copy(execs = execs + (id -> (new_st, docs))))
276 commands.get(id) match {
278 val new_st = st.accumulate(message)
279 (new_st, copy(commands = commands + (id -> new_st)))
284 def assign(id: Version_ID, edits: List[(Command_ID, Exec_ID)]): State =
286 val doc = the_document(id)
287 val docs = Set(doc) // FIXME unused (!?)
289 var new_execs = execs
291 for ((cmd_id, exec_id) <- edits) yield {
292 val st = the_command(cmd_id)
293 if (new_execs.isDefinedAt(exec_id) || disposed(exec_id)) fail
294 new_execs += (exec_id -> (st, docs))
295 (st.command, exec_id)
297 the_assignment(doc).assign(assigned_execs) // FIXME explicit value instead of promise (!?)
298 copy(execs = new_execs)
301 def is_assigned(document: Document): Boolean =
302 assignments.get(document) match {
303 case Some(assgn) => assgn.is_finished
307 def command_state(document: Document, command: Command): Command.State =
309 val assgn = the_assignment(document)
310 require(assgn.is_finished)
311 the_exec_state(assgn.join.getOrElse(command, fail))
318 val id: Document.Version_ID,
319 val nodes: Map[String, Document.Node])