19 import org.gjt.sp.jedit.View |
19 import org.gjt.sp.jedit.View |
20 import org.gjt.sp.jedit.Buffer |
20 import org.gjt.sp.jedit.Buffer |
21 import org.gjt.sp.jedit.buffer.{BufferAdapter, BufferListener, JEditBuffer} |
21 import org.gjt.sp.jedit.buffer.{BufferAdapter, BufferListener, JEditBuffer} |
22 |
22 |
23 |
23 |
24 object Document_Model |
24 object Document_Model { |
25 { |
|
26 /* document models */ |
25 /* document models */ |
27 |
26 |
28 sealed case class State( |
27 sealed case class State( |
29 models: Map[Document.Node.Name, Document_Model] = Map.empty, |
28 models: Map[Document.Node.Name, Document_Model] = Map.empty, |
30 buffer_models: Map[JEditBuffer, Buffer_Model] = Map.empty, |
29 buffer_models: Map[JEditBuffer, Buffer_Model] = Map.empty, |
31 overlays: Document.Overlays = Document.Overlays.empty) |
30 overlays: Document.Overlays = Document.Overlays.empty |
32 { |
31 ) { |
33 def file_models_iterator: Iterator[(Document.Node.Name, File_Model)] = |
32 def file_models_iterator: Iterator[(Document.Node.Name, File_Model)] = |
34 for { |
33 for { |
35 (node_name, model) <- models.iterator |
34 (node_name, model) <- models.iterator |
36 if model.isInstanceOf[File_Model] |
35 if model.isInstanceOf[File_Model] |
37 } yield (node_name, model.asInstanceOf[File_Model]) |
36 } yield (node_name, model.asInstanceOf[File_Model]) |
41 (for { |
40 (for { |
42 (node_name, model) <- models.iterator |
41 (node_name, model) <- models.iterator |
43 blob <- model.get_blob |
42 blob <- model.get_blob |
44 } yield (node_name -> blob)).toMap) |
43 } yield (node_name -> blob)).toMap) |
45 |
44 |
46 def open_buffer(session: Session, node_name: Document.Node.Name, buffer: Buffer) |
45 def open_buffer( |
47 : (Buffer_Model, State) = |
46 session: Session, |
48 { |
47 node_name: Document.Node.Name, |
|
48 buffer: Buffer |
|
49 ) : (Buffer_Model, State) = { |
49 val old_model = |
50 val old_model = |
50 models.get(node_name) match { |
51 models.get(node_name) match { |
51 case Some(file_model: File_Model) => Some(file_model) |
52 case Some(file_model: File_Model) => Some(file_model) |
52 case Some(buffer_model: Buffer_Model) => Some(buffer_model.exit()) |
53 case Some(buffer_model: Buffer_Model) => Some(buffer_model.exit()) |
53 case _ => None |
54 case _ => None |
111 state.change(st => st.copy(overlays = st.overlays.remove(command, fn, args))) |
111 state.change(st => st.copy(overlays = st.overlays.remove(command, fn, args))) |
112 |
112 |
113 |
113 |
114 /* sync external files */ |
114 /* sync external files */ |
115 |
115 |
116 def sync_files(changed_files: Set[JFile]): Boolean = |
116 def sync_files(changed_files: Set[JFile]): Boolean = { |
117 { |
117 state.change_result(st => { |
118 state.change_result(st => |
118 val changed_models = |
119 { |
119 (for { |
120 val changed_models = |
120 (node_name, model) <- st.file_models_iterator |
121 (for { |
121 file <- model.file if changed_files(file) |
122 (node_name, model) <- st.file_models_iterator |
122 text <- PIDE.resources.read_file_content(node_name) |
123 file <- model.file if changed_files(file) |
123 if model.content.text != text |
124 text <- PIDE.resources.read_file_content(node_name) |
124 } yield { |
125 if model.content.text != text |
125 val content = Document_Model.File_Content(text) |
126 } yield { |
126 val edits = Text.Edit.replace(0, model.content.text, text) |
127 val content = Document_Model.File_Content(text) |
127 (node_name, model.copy(content = content, pending_edits = model.pending_edits ::: edits)) |
128 val edits = Text.Edit.replace(0, model.content.text, text) |
128 }).toList |
129 (node_name, model.copy(content = content, pending_edits = model.pending_edits ::: edits)) |
129 if (changed_models.isEmpty) (false, st) |
130 }).toList |
130 else (true, st.copy(models = changed_models.foldLeft(st.models)(_ + _))) |
131 if (changed_models.isEmpty) (false, st) |
131 }) |
132 else (true, st.copy(models = changed_models.foldLeft(st.models)(_ + _))) |
|
133 }) |
|
134 } |
132 } |
135 |
133 |
136 |
134 |
137 /* syntax */ |
135 /* syntax */ |
138 |
136 |
139 def syntax_changed(names: List[Document.Node.Name]): Unit = |
137 def syntax_changed(names: List[Document.Node.Name]): Unit = { |
140 { |
|
141 GUI_Thread.require {} |
138 GUI_Thread.require {} |
142 |
139 |
143 val models = state.value.models |
140 val models = state.value.models |
144 for (name <- names.iterator; model <- models.get(name)) { |
141 for (name <- names.iterator; model <- models.get(name)) { |
145 model match { case buffer_model: Buffer_Model => buffer_model.syntax_changed() case _ => } |
142 model match { case buffer_model: Buffer_Model => buffer_model.syntax_changed() case _ => } |
162 buffer.propertiesChanged() |
158 buffer.propertiesChanged() |
163 res |
159 res |
164 }) |
160 }) |
165 } |
161 } |
166 |
162 |
167 def exit(buffer: Buffer): Unit = |
163 def exit(buffer: Buffer): Unit = { |
168 { |
|
169 GUI_Thread.require {} |
164 GUI_Thread.require {} |
170 state.change(st => |
165 state.change(st => |
171 if (st.buffer_models.isDefinedAt(buffer)) { |
166 if (st.buffer_models.isDefinedAt(buffer)) { |
172 val res = st.close_buffer(buffer) |
167 val res = st.close_buffer(buffer) |
173 buffer.propertiesChanged() |
168 buffer.propertiesChanged() |
174 res |
169 res |
175 } |
170 } |
176 else st) |
171 else st) |
177 } |
172 } |
178 |
173 |
179 def provide_files(session: Session, files: List[(Document.Node.Name, String)]): Unit = |
174 def provide_files(session: Session, files: List[(Document.Node.Name, String)]): Unit = { |
180 { |
|
181 GUI_Thread.require {} |
175 GUI_Thread.require {} |
182 state.change(st => |
176 state.change(st => |
183 files.foldLeft(st) { |
177 files.foldLeft(st) { |
184 case (st1, (node_name, text)) => st1.provide_file(session, node_name, text) |
178 case (st1, (node_name, text)) => st1.provide_file(session, node_name, text) |
185 }) |
179 }) |
224 node_required(model.node_name, toggle = toggle, set = set)) |
220 node_required(model.node_name, toggle = toggle, set = set)) |
225 |
221 |
226 |
222 |
227 /* flushed edits */ |
223 /* flushed edits */ |
228 |
224 |
229 def flush_edits(hidden: Boolean, purge: Boolean): (Document.Blobs, List[Document.Edit_Text]) = |
225 def flush_edits(hidden: Boolean, purge: Boolean): (Document.Blobs, List[Document.Edit_Text]) = { |
230 { |
226 GUI_Thread.require {} |
231 GUI_Thread.require {} |
227 |
232 |
228 state.change_result(st => { |
233 state.change_result(st => |
229 val doc_blobs = st.document_blobs |
234 { |
230 |
235 val doc_blobs = st.document_blobs |
231 val buffer_edits = |
236 |
232 (for { |
237 val buffer_edits = |
233 (_, model) <- st.buffer_models.iterator |
238 (for { |
234 edit <- model.flush_edits(doc_blobs, hidden).iterator |
239 (_, model) <- st.buffer_models.iterator |
235 } yield edit).toList |
240 edit <- model.flush_edits(doc_blobs, hidden).iterator |
236 |
241 } yield edit).toList |
237 val file_edits = |
242 |
238 (for { |
243 val file_edits = |
239 (node_name, model) <- st.file_models_iterator |
244 (for { |
240 (edits, model1) <- model.flush_edits(doc_blobs, hidden) |
245 (node_name, model) <- st.file_models_iterator |
241 } yield (edits, node_name -> model1)).toList |
246 (edits, model1) <- model.flush_edits(doc_blobs, hidden) |
242 |
247 } yield (edits, node_name -> model1)).toList |
243 val model_edits = buffer_edits ::: file_edits.flatMap(_._1) |
248 |
244 |
249 val model_edits = buffer_edits ::: file_edits.flatMap(_._1) |
245 val purge_edits = |
250 |
246 if (purge) { |
251 val purge_edits = |
247 val purged = |
252 if (purge) { |
248 (for ((node_name, model) <- st.file_models_iterator) |
253 val purged = |
249 yield (node_name -> model.purge_edits(doc_blobs))).toList |
254 (for ((node_name, model) <- st.file_models_iterator) |
250 |
255 yield (node_name -> model.purge_edits(doc_blobs))).toList |
251 val imports = { |
256 |
252 val open_nodes = |
257 val imports = |
253 (for ((_, model) <- st.buffer_models.iterator) yield model.node_name).toList |
258 { |
254 val touched_nodes = model_edits.map(_._1) |
259 val open_nodes = |
255 val pending_nodes = for ((node_name, None) <- purged) yield node_name |
260 (for ((_, model) <- st.buffer_models.iterator) yield model.node_name).toList |
256 (open_nodes ::: touched_nodes ::: pending_nodes).map((_, Position.none)) |
261 val touched_nodes = model_edits.map(_._1) |
|
262 val pending_nodes = for ((node_name, None) <- purged) yield node_name |
|
263 (open_nodes ::: touched_nodes ::: pending_nodes).map((_, Position.none)) |
|
264 } |
|
265 val retain = PIDE.resources.dependencies(imports).theories.toSet |
|
266 |
|
267 for ((node_name, Some(edits)) <- purged if !retain(node_name); edit <- edits) |
|
268 yield edit |
|
269 } |
257 } |
270 else Nil |
258 val retain = PIDE.resources.dependencies(imports).theories.toSet |
271 |
259 |
272 val st1 = st.copy(models = st.models ++ file_edits.map(_._2) -- purge_edits.map(_._1)) |
260 for ((node_name, Some(edits)) <- purged if !retain(node_name); edit <- edits) |
273 PIDE.plugin.file_watcher.purge( |
261 yield edit |
274 (for { |
262 } |
275 (_, model) <- st1.file_models_iterator |
263 else Nil |
276 file <- model.file |
264 |
277 } yield file.getParentFile).toSet) |
265 val st1 = st.copy(models = st.models ++ file_edits.map(_._2) -- purge_edits.map(_._1)) |
278 |
266 PIDE.plugin.file_watcher.purge( |
279 ((doc_blobs, model_edits ::: purge_edits), st1) |
267 (for { |
280 }) |
268 (_, model) <- st1.file_models_iterator |
|
269 file <- model.file |
|
270 } yield file.getParentFile).toSet) |
|
271 |
|
272 ((doc_blobs, model_edits ::: purge_edits), st1) |
|
273 }) |
281 } |
274 } |
282 |
275 |
283 |
276 |
284 /* file content */ |
277 /* file content */ |
285 |
278 |
286 sealed case class File_Content(text: String) |
279 sealed case class File_Content(text: String) { |
287 { |
|
288 lazy val bytes: Bytes = Bytes(Symbol.encode(text)) |
280 lazy val bytes: Bytes = Bytes(Symbol.encode(text)) |
289 lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text) |
281 lazy val chunk: Symbol.Text_Chunk = Symbol.Text_Chunk(text) |
290 lazy val bibtex_entries: List[Text.Info[String]] = |
282 lazy val bibtex_entries: List[Text.Info[String]] = |
291 try { Bibtex.entries(text) } |
283 try { Bibtex.entries(text) } |
292 catch { case ERROR(_) => Nil } |
284 catch { case ERROR(_) => Nil } |
293 } |
285 } |
294 |
286 |
295 |
287 |
296 /* HTTP preview */ |
288 /* HTTP preview */ |
297 |
289 |
298 def open_preview(view: View, plain_text: Boolean): Unit = |
290 def open_preview(view: View, plain_text: Boolean): Unit = { |
299 { |
|
300 Document_Model.get(view.getBuffer) match { |
291 Document_Model.get(view.getBuffer) match { |
301 case Some(model) => |
292 case Some(model) => |
302 val url = Preview_Service.server_url(plain_text, model.node_name) |
293 val url = Preview_Service.server_url(plain_text, model.node_name) |
303 PIDE.editor.hyperlink_url(url).follow(view) |
294 PIDE.editor.hyperlink_url(url).follow(view) |
304 case _ => |
295 case _ => |
305 } |
296 } |
306 } |
297 } |
307 |
298 |
308 object Preview_Service extends HTTP.Service("preview") |
299 object Preview_Service extends HTTP.Service("preview") { |
309 { |
|
310 service => |
300 service => |
311 |
301 |
312 private val plain_text_prefix = "plain_text=" |
302 private val plain_text_prefix = "plain_text=" |
313 |
303 |
314 def server_url(plain_text: Boolean, node_name: Document.Node.Name): String = |
304 def server_url(plain_text: Boolean, node_name: Document.Node.Name): String = |
371 } |
361 } |
372 |
362 |
373 |
363 |
374 /* snapshot */ |
364 /* snapshot */ |
375 |
365 |
376 @tailrec final def await_stable_snapshot(): Document.Snapshot = |
366 @tailrec final def await_stable_snapshot(): Document.Snapshot = { |
377 { |
|
378 val snapshot = this.snapshot() |
367 val snapshot = this.snapshot() |
379 if (snapshot.is_outdated) { |
368 if (snapshot.is_outdated) { |
380 PIDE.options.seconds("editor_output_delay").sleep() |
369 PIDE.options.seconds("editor_output_delay").sleep() |
381 await_stable_snapshot() |
370 await_stable_snapshot() |
382 } |
371 } |
383 else snapshot |
372 else snapshot |
384 } |
373 } |
385 } |
374 } |
386 |
375 |
387 object File_Model |
376 object File_Model { |
388 { |
|
389 def empty(session: Session): File_Model = |
377 def empty(session: Session): File_Model = |
390 File_Model(session, Document.Node.Name.empty, None, Document_Model.File_Content(""), |
378 File_Model(session, Document.Node.Name.empty, None, Document_Model.File_Content(""), |
391 false, Document.Node.no_perspective_text, Nil) |
379 false, Document.Node.no_perspective_text, Nil) |
392 |
380 |
393 def init(session: Session, |
381 def init(session: Session, |
394 node_name: Document.Node.Name, |
382 node_name: Document.Node.Name, |
395 text: String, |
383 text: String, |
396 node_required: Boolean = false, |
384 node_required: Boolean = false, |
397 last_perspective: Document.Node.Perspective_Text = Document.Node.no_perspective_text, |
385 last_perspective: Document.Node.Perspective_Text = Document.Node.no_perspective_text, |
398 pending_edits: List[Text.Edit] = Nil): File_Model = |
386 pending_edits: List[Text.Edit] = Nil |
399 { |
387 ): File_Model = { |
400 val file = JEdit_Lib.check_file(node_name.node) |
388 val file = JEdit_Lib.check_file(node_name.node) |
401 file.foreach(PIDE.plugin.file_watcher.register_parent(_)) |
389 file.foreach(PIDE.plugin.file_watcher.register_parent(_)) |
402 |
390 |
403 val content = Document_Model.File_Content(text) |
391 val content = Document_Model.File_Content(text) |
404 val node_required1 = node_required || File_Format.registry.is_theory(node_name) |
392 val node_required1 = node_required || File_Format.registry.is_theory(node_name) |
451 val content1 = Document_Model.File_Content(text) |
439 val content1 = Document_Model.File_Content(text) |
452 val pending_edits1 = pending_edits ::: edits |
440 val pending_edits1 = pending_edits ::: edits |
453 Some(copy(content = content1, pending_edits = pending_edits1)) |
441 Some(copy(content = content1, pending_edits = pending_edits1)) |
454 } |
442 } |
455 |
443 |
456 def flush_edits(doc_blobs: Document.Blobs, hidden: Boolean) |
444 def flush_edits( |
457 : Option[(List[Document.Edit_Text], File_Model)] = |
445 doc_blobs: Document.Blobs, |
458 { |
446 hidden: Boolean |
|
447 ) : Option[(List[Document.Edit_Text], File_Model)] = { |
459 val (reparse, perspective) = node_perspective(doc_blobs, hidden) |
448 val (reparse, perspective) = node_perspective(doc_blobs, hidden) |
460 if (reparse || pending_edits.nonEmpty || last_perspective != perspective) { |
449 if (reparse || pending_edits.nonEmpty || last_perspective != perspective) { |
461 val edits = node_edits(node_header, pending_edits, perspective) |
450 val edits = node_edits(node_header, pending_edits, perspective) |
462 Some((edits, copy(last_perspective = perspective, pending_edits = Nil))) |
451 Some((edits, copy(last_perspective = perspective, pending_edits = Nil))) |
463 } |
452 } |
479 def is_stable: Boolean = pending_edits.isEmpty |
468 def is_stable: Boolean = pending_edits.isEmpty |
480 def snapshot(): Document.Snapshot = session.snapshot(node_name, pending_edits) |
469 def snapshot(): Document.Snapshot = session.snapshot(node_name, pending_edits) |
481 } |
470 } |
482 |
471 |
483 case class Buffer_Model(session: Session, node_name: Document.Node.Name, buffer: Buffer) |
472 case class Buffer_Model(session: Session, node_name: Document.Node.Name, buffer: Buffer) |
484 extends Document_Model |
473 extends Document_Model { |
485 { |
|
486 /* text */ |
474 /* text */ |
487 |
475 |
488 def get_text(range: Text.Range): Option[String] = |
476 def get_text(range: Text.Range): Option[String] = |
489 JEdit_Lib.get_text(buffer, range) |
477 JEdit_Lib.get_text(buffer, range) |
490 |
478 |
491 |
479 |
492 /* header */ |
480 /* header */ |
493 |
481 |
494 def node_header(): Document.Node.Header = |
482 def node_header(): Document.Node.Header = { |
495 { |
|
496 GUI_Thread.require {} |
483 GUI_Thread.require {} |
497 |
484 |
498 PIDE.resources.special_header(node_name) getOrElse |
485 PIDE.resources.special_header(node_name) getOrElse |
499 JEdit_Lib.buffer_lock(buffer) { |
486 JEdit_Lib.buffer_lock(buffer) { |
500 PIDE.resources.check_thy(node_name, JEdit_Lib.buffer_reader(buffer), strict = false) |
487 PIDE.resources.check_thy(node_name, JEdit_Lib.buffer_reader(buffer), strict = false) |
627 pending_edits.flush_edits(doc_blobs, hidden) |
612 pending_edits.flush_edits(doc_blobs, hidden) |
628 |
613 |
629 |
614 |
630 /* buffer listener */ |
615 /* buffer listener */ |
631 |
616 |
632 private val buffer_listener: BufferListener = new BufferAdapter |
617 private val buffer_listener: BufferListener = new BufferAdapter { |
633 { |
618 override def contentInserted( |
634 override def contentInserted(buffer: JEditBuffer, |
619 buffer: JEditBuffer, |
635 start_line: Int, offset: Int, num_lines: Int, length: Int): Unit = |
620 start_line: Int, |
636 { |
621 offset: Int, |
|
622 num_lines: Int, |
|
623 length: Int |
|
624 ): Unit = { |
637 pending_edits.edit(List(Text.Edit.insert(offset, buffer.getText(offset, length)))) |
625 pending_edits.edit(List(Text.Edit.insert(offset, buffer.getText(offset, length)))) |
638 } |
626 } |
639 |
627 |
640 override def preContentRemoved(buffer: JEditBuffer, |
628 override def preContentRemoved( |
641 start_line: Int, offset: Int, num_lines: Int, removed_length: Int): Unit = |
629 buffer: JEditBuffer, |
642 { |
630 start_line: Int, |
|
631 offset: Int, |
|
632 num_lines: Int, |
|
633 removed_length: Int |
|
634 ): Unit = { |
643 pending_edits.edit(List(Text.Edit.remove(offset, buffer.getText(offset, removed_length)))) |
635 pending_edits.edit(List(Text.Edit.remove(offset, buffer.getText(offset, removed_length)))) |
644 } |
636 } |
645 } |
637 } |
646 |
638 |
647 |
639 |
648 /* syntax */ |
640 /* syntax */ |
649 |
641 |
650 def syntax_changed(): Unit = |
642 def syntax_changed(): Unit = { |
651 { |
|
652 JEdit_Lib.buffer_line_manager(buffer).setFirstInvalidLineContext(0) |
643 JEdit_Lib.buffer_line_manager(buffer).setFirstInvalidLineContext(0) |
653 for (text_area <- JEdit_Lib.jedit_text_areas(buffer)) |
644 for (text_area <- JEdit_Lib.jedit_text_areas(buffer)) |
654 Untyped.method(Class.forName("org.gjt.sp.jedit.textarea.TextArea"), "foldStructureChanged"). |
645 Untyped.method(Class.forName("org.gjt.sp.jedit.textarea.TextArea"), "foldStructureChanged"). |
655 invoke(text_area) |
646 invoke(text_area) |
656 buffer.invalidateCachedFoldLevels() |
647 buffer.invalidateCachedFoldLevels() |
657 } |
648 } |
658 |
649 |
659 def init_token_marker(): Unit = |
650 def init_token_marker(): Unit = { |
660 { |
|
661 Isabelle.buffer_token_marker(buffer) match { |
651 Isabelle.buffer_token_marker(buffer) match { |
662 case Some(marker) if marker != buffer.getTokenMarker => |
652 case Some(marker) if marker != buffer.getTokenMarker => |
663 buffer.setTokenMarker(marker) |
653 buffer.setTokenMarker(marker) |
664 syntax_changed() |
654 syntax_changed() |
665 case _ => |
655 case _ => |