wenzelm@43282: /* Title: Tools/jEdit/src/plugin.scala wenzelm@36760: Author: Makarius wenzelm@36760: wenzelm@50362: Main plumbing for PIDE infrastructure as jEdit plugin. wenzelm@36760: */ wenzelm@34407: wenzelm@34318: package isabelle.jedit wenzelm@34318: wenzelm@34429: wenzelm@36015: import isabelle._ wenzelm@36015: wenzelm@44573: import javax.swing.JOptionPane wenzelm@34318: wenzelm@64858: import java.io.{File => JFile} wenzelm@64858: wenzelm@49246: import scala.swing.{ListView, ScrollPane} immler@34406: wenzelm@60205: import org.gjt.sp.jedit.{jEdit, EBMessage, EBPlugin, Buffer, View, Debug, PerspectiveManager} wenzelm@53487: import org.gjt.sp.jedit.gui.AboutDialog wenzelm@39043: import org.gjt.sp.jedit.textarea.{JEditTextArea, TextArea} wenzelm@59077: import org.gjt.sp.jedit.buffer.JEditBuffer wenzelm@50362: import org.gjt.sp.jedit.syntax.ModeProvider wenzelm@49100: import org.gjt.sp.jedit.msg.{EditorStarted, BufferUpdate, EditPaneUpdate, PropertiesChanged} wenzelm@43443: import org.gjt.sp.util.SyntaxUtilities wenzelm@62228: import org.gjt.sp.util.Log wenzelm@39241: wenzelm@34318: wenzelm@50205: object PIDE wenzelm@34618: { wenzelm@34784: /* plugin instance */ wenzelm@34784: wenzelm@49245: val options = new JEdit_Options wenzelm@53337: val completion_history = new Completion.History_Variable wenzelm@56558: val spell_checker = new Spell_Checker_Variable wenzelm@49245: wenzelm@49099: @volatile var startup_failure: Option[Throwable] = None wenzelm@49099: @volatile var startup_notified = false wenzelm@34784: wenzelm@49101: @volatile var plugin: Plugin = null wenzelm@60835: @volatile var session: Session = new Session(JEdit_Resources.empty) wenzelm@49101: wenzelm@62068: def options_changed() { if (plugin != null) plugin.options_changed() } wenzelm@62068: def deps_changed() { if (plugin != null) plugin.deps_changed() } wenzelm@52815: wenzelm@56208: def resources(): JEdit_Resources = wenzelm@56208: session.resources.asInstanceOf[JEdit_Resources] wenzelm@44955: wenzelm@64858: def file_watcher(): File_Watcher = wenzelm@64858: if (plugin == null) File_Watcher.none else plugin.file_watcher wenzelm@64858: wenzelm@52971: lazy val editor = new JEdit_Editor wenzelm@52971: wenzelm@34784: wenzelm@53244: /* popups */ wenzelm@53244: wenzelm@53244: def dismissed_popups(view: View): Boolean = wenzelm@53244: { wenzelm@53272: var dismissed = false wenzelm@53272: wenzelm@53274: JEdit_Lib.jedit_text_areas(view).foreach(text_area => wenzelm@53274: if (Completion_Popup.Text_Area.dismissed(text_area)) dismissed = true) wenzelm@53272: wenzelm@53272: if (Pretty_Tooltip.dismissed_all()) dismissed = true wenzelm@53272: wenzelm@53272: dismissed wenzelm@53244: } wenzelm@53244: wenzelm@53244: wenzelm@43449: /* document model and view */ wenzelm@43449: wenzelm@55712: def document_view(text_area: TextArea): Option[Document_View] = Document_View(text_area) wenzelm@43449: wenzelm@44379: def document_views(buffer: Buffer): List[Document_View] = wenzelm@44379: for { wenzelm@49406: text_area <- JEdit_Lib.jedit_text_areas(buffer).toList wenzelm@54529: doc_view <- document_view(text_area) wenzelm@54529: } yield doc_view wenzelm@44379: wenzelm@50344: def exit_models(buffers: List[Buffer]) wenzelm@47058: { wenzelm@57612: GUI_Thread.now { wenzelm@50344: buffers.foreach(buffer => wenzelm@50344: JEdit_Lib.buffer_lock(buffer) { wenzelm@50344: JEdit_Lib.jedit_text_areas(buffer).foreach(Document_View.exit) wenzelm@50344: Document_Model.exit(buffer) wenzelm@50344: }) wenzelm@50344: } wenzelm@47058: } wenzelm@47058: wenzelm@55791: def init_models() wenzelm@43510: { wenzelm@57612: GUI_Thread.now { wenzelm@54461: PIDE.editor.flush() wenzelm@55791: wenzelm@55791: for { wenzelm@55791: buffer <- JEdit_Lib.jedit_buffers() wenzelm@62068: if buffer != null && !buffer.getBooleanProperty(Buffer.GZIPPED) wenzelm@55791: } { wenzelm@62068: if (buffer.isLoaded) { wenzelm@62068: JEdit_Lib.buffer_lock(buffer) { wenzelm@62068: val node_name = resources.node_name(buffer) wenzelm@64817: val model = Document_Model.init(session, node_name, buffer) wenzelm@62068: for { wenzelm@62068: text_area <- JEdit_Lib.jedit_text_areas(buffer) wenzelm@62068: if document_view(text_area).map(_.model) != Some(model) wenzelm@62068: } Document_View.init(model, text_area) wenzelm@62068: } wenzelm@43510: } wenzelm@62068: else if (plugin != null) plugin.delay_init.invoke() wenzelm@55791: } wenzelm@55791: wenzelm@64524: PIDE.editor.invoke_generated() wenzelm@43510: } wenzelm@43510: } wenzelm@43510: wenzelm@56774: def init_view(buffer: Buffer, text_area: JEditTextArea): Unit = wenzelm@57612: GUI_Thread.now { wenzelm@56774: JEdit_Lib.buffer_lock(buffer) { wenzelm@64813: Document_Model.get(buffer) match { wenzelm@56774: case Some(model) => Document_View.init(model, text_area) wenzelm@56774: case None => wenzelm@56774: } wenzelm@43510: } wenzelm@43510: } wenzelm@43510: wenzelm@56774: def exit_view(buffer: Buffer, text_area: JEditTextArea): Unit = wenzelm@57612: GUI_Thread.now { wenzelm@56774: JEdit_Lib.buffer_lock(buffer) { wenzelm@56774: Document_View.exit(text_area) wenzelm@56774: } wenzelm@43510: } wenzelm@55621: wenzelm@55621: wenzelm@55621: /* current document content */ wenzelm@55621: wenzelm@62062: def snapshot(view: View): Document.Snapshot = GUI_Thread.now wenzelm@55621: { wenzelm@64813: Document_Model.get(view.getBuffer) match { wenzelm@55621: case Some(model) => model.snapshot wenzelm@55621: case None => error("No document model for current buffer") wenzelm@55621: } wenzelm@55621: } wenzelm@55621: wenzelm@64621: def rendering(view: View): JEdit_Rendering = GUI_Thread.now wenzelm@55621: { wenzelm@55621: val text_area = view.getTextArea wenzelm@55621: document_view(text_area) match { wenzelm@55621: case Some(doc_view) => doc_view.get_rendering() wenzelm@55621: case None => error("No document view for current text area") wenzelm@55621: } wenzelm@55621: } wenzelm@34318: } wenzelm@34318: wenzelm@34429: wenzelm@34618: class Plugin extends EBPlugin wenzelm@34618: { wenzelm@56316: /* global changes */ wenzelm@55791: wenzelm@56316: def options_changed() wenzelm@56316: { wenzelm@56715: PIDE.session.global_options.post(Session.Global_Options(PIDE.options.value)) wenzelm@56770: delay_load.invoke() wenzelm@55791: } wenzelm@55791: wenzelm@56316: def deps_changed() wenzelm@56316: { wenzelm@56770: delay_load.invoke() wenzelm@56316: } wenzelm@56316: wenzelm@55791: wenzelm@44577: /* theory files */ wenzelm@44574: wenzelm@62068: lazy val delay_init = wenzelm@57612: GUI_Thread.delay_last(PIDE.options.seconds("editor_load_delay")) wenzelm@50344: { wenzelm@55791: PIDE.init_models() wenzelm@50344: } wenzelm@50344: wenzelm@57582: private val delay_load_active = Synchronized(false) wenzelm@57582: private def delay_load_activated(): Boolean = wenzelm@57582: delay_load_active.guarded_access(a => Some((!a, true))) wenzelm@62068: private def delay_load_action() wenzelm@62068: { wenzelm@62068: if (Isabelle.continuous_checking && delay_load_activated() && wenzelm@62068: PerspectiveManager.isPerspectiveEnabled) wenzelm@44574: { wenzelm@64835: if (JEdit_Lib.jedit_buffers().exists(_.isLoading)) delay_load.invoke() wenzelm@64835: else { wenzelm@64835: val required_files = wenzelm@64835: { wenzelm@64835: val models = Document_Model.get_models() wenzelm@44574: wenzelm@62068: val thys = wenzelm@64835: (for ((node_name, model) <- models.iterator if model.is_theory) wenzelm@64835: yield (node_name, Position.none)).toList wenzelm@64839: val thy_files = PIDE.resources.thy_info.dependencies("", thys).deps.map(_.name) wenzelm@46761: wenzelm@64839: val aux_files = wenzelm@62068: if (PIDE.options.bool("jedit_auto_resolve")) { wenzelm@64835: val stable_tip_version = wenzelm@64835: if (models.forall(p => p._2.is_stable)) wenzelm@64835: PIDE.session.current_state().stable_tip_version wenzelm@64835: else None wenzelm@64835: stable_tip_version match { wenzelm@64835: case Some(version) => PIDE.resources.undefined_blobs(version.nodes) wenzelm@64478: case None => delay_load.invoke(); Nil wenzelm@57582: } wenzelm@62068: } wenzelm@62068: else Nil wenzelm@62068: wenzelm@64835: (thy_files ::: aux_files).filterNot(models.isDefinedAt(_)) wenzelm@64835: } wenzelm@64835: if (required_files.nonEmpty) { wenzelm@64835: try { wenzelm@64835: Standard_Thread.fork("resolve_dependencies") { wenzelm@64835: val loaded_files = wenzelm@64835: for { wenzelm@64835: name <- required_files wenzelm@64835: text <- PIDE.resources.read_file_content(name) wenzelm@64835: } yield (name, text) wenzelm@62068: wenzelm@64835: GUI_Thread.later { wenzelm@64835: try { wenzelm@64835: Document_Model.provide_files(PIDE.session, loaded_files) wenzelm@64835: delay_init.invoke() wenzelm@64835: } wenzelm@64835: finally { delay_load_active.change(_ => false) } wenzelm@53715: } wenzelm@53074: } wenzelm@46761: } wenzelm@64835: catch { case _: Throwable => delay_load_active.change(_ => false) } wenzelm@46761: } wenzelm@64835: else delay_load_active.change(_ => false) wenzelm@44606: } wenzelm@44574: } wenzelm@62068: } wenzelm@62068: wenzelm@64478: private lazy val delay_load = wenzelm@62068: GUI_Thread.delay_last(PIDE.options.seconds("editor_load_delay")) { delay_load_action() } wenzelm@44573: wenzelm@64858: private def file_watcher_action(changed: Set[JFile]): Unit = wenzelm@64858: if (Document_Model.sync_files(changed)) PIDE.editor.invoke_generated() wenzelm@64858: wenzelm@64858: lazy val file_watcher: File_Watcher = wenzelm@64858: File_Watcher(file_watcher_action _, PIDE.options.seconds("editor_load_delay")) wenzelm@64858: wenzelm@44574: wenzelm@56715: /* session phase */ wenzelm@44573: wenzelm@56715: private val session_phase = wenzelm@56715: Session.Consumer[Session.Phase](getClass.getName) { wenzelm@56715: case Session.Inactive | Session.Failed => wenzelm@57612: GUI_Thread.later { wenzelm@56715: GUI.error_dialog(jEdit.getActiveView, "Prover process terminated", wenzelm@56775: "Isabelle Syslog", GUI.scrollable_text(PIDE.session.syslog_content())) wenzelm@56715: } wenzelm@39735: wenzelm@56715: case Session.Ready => wenzelm@60910: Debugger.init_session(PIDE.session) wenzelm@60075: PIDE.session.update_options(PIDE.options.value) wenzelm@56715: PIDE.init_models() wenzelm@57640: wenzelm@57640: if (!Isabelle.continuous_checking) { wenzelm@57640: GUI_Thread.later { wenzelm@57640: val answer = wenzelm@57640: GUI.confirm_dialog(jEdit.getActiveView, wenzelm@57640: "Continuous checking of PIDE document", wenzelm@57640: JOptionPane.YES_NO_OPTION, wenzelm@57640: "Continuous checking is presently disabled:", wenzelm@57640: "editor buffers will remain inactive!", wenzelm@57640: "Enable continuous checking now?") wenzelm@57640: if (answer == 0) Isabelle.continuous_checking = true wenzelm@57640: } wenzelm@57640: } wenzelm@57640: wenzelm@56770: delay_load.invoke() wenzelm@44573: wenzelm@56715: case Session.Shutdown => wenzelm@62062: GUI_Thread.later { wenzelm@62062: delay_load.revoke() wenzelm@64838: delay_init.revoke() wenzelm@64838: PIDE.editor.flush() wenzelm@62062: PIDE.exit_models(JEdit_Lib.jedit_buffers().toList) wenzelm@62062: } wenzelm@46740: wenzelm@56715: case _ => wenzelm@39630: } wenzelm@39630: wenzelm@39630: wenzelm@34618: /* main plugin plumbing */ wenzelm@34433: wenzelm@34767: override def handleMessage(message: EBMessage) wenzelm@34618: { wenzelm@57612: GUI_Thread.assert {} wenzelm@39630: wenzelm@50205: if (PIDE.startup_failure.isDefined && !PIDE.startup_notified) { wenzelm@49099: message match { wenzelm@49100: case msg: EditorStarted => wenzelm@51616: GUI.error_dialog(null, "Isabelle plugin startup failure", wenzelm@51616: GUI.scrollable_text(Exn.message(PIDE.startup_failure.get)), wenzelm@49099: "Prover IDE inactive!") wenzelm@50205: PIDE.startup_notified = true wenzelm@49099: case _ => wenzelm@49099: } wenzelm@49099: } wenzelm@49099: wenzelm@50205: if (PIDE.startup_failure.isEmpty) { wenzelm@49099: message match { wenzelm@49099: case msg: EditorStarted => wenzelm@57649: if (Distribution.is_identified && !Distribution.is_official) { wenzelm@57680: GUI.warning_dialog(jEdit.getActiveView, "Isabelle version for testing", wenzelm@60206: "This is " + Distribution.version + ".", wenzelm@57649: "It is for testing only, not for production use.") wenzelm@57649: } wenzelm@57649: wenzelm@64602: val view = jEdit.getActiveView() wenzelm@64602: wenzelm@64602: Session_Build.session_build(view) wenzelm@63747: wenzelm@64602: Keymap_Merge.check_dialog(view) wenzelm@64602: wenzelm@64602: PIDE.editor.hyperlink_position(true, Document.Snapshot.init, wenzelm@64602: JEdit_Sessions.session_info().open_root).foreach(_.follow(view)) wenzelm@61277: wenzelm@49099: case msg: BufferUpdate wenzelm@64818: if msg.getWhat == BufferUpdate.LOAD_STARTED || msg.getWhat == BufferUpdate.CLOSING => wenzelm@64838: if (msg.getBuffer != null) { wenzelm@64838: PIDE.exit_models(List(msg.getBuffer)) wenzelm@64838: PIDE.editor.invoke_generated() wenzelm@64838: } wenzelm@57611: wenzelm@64818: case msg: BufferUpdate wenzelm@64818: if msg.getWhat == BufferUpdate.PROPERTIES_CHANGED || msg.getWhat == BufferUpdate.LOADED => wenzelm@50205: if (PIDE.session.is_ready) { wenzelm@55791: delay_init.invoke() wenzelm@55791: delay_load.invoke() wenzelm@49099: } wenzelm@34784: wenzelm@49099: case msg: EditPaneUpdate wenzelm@60215: if msg.getWhat == EditPaneUpdate.BUFFER_CHANGING || wenzelm@49099: msg.getWhat == EditPaneUpdate.BUFFER_CHANGED || wenzelm@49099: msg.getWhat == EditPaneUpdate.CREATED || wenzelm@60215: msg.getWhat == EditPaneUpdate.DESTROYED => wenzelm@49099: val edit_pane = msg.getEditPane wenzelm@49099: val buffer = edit_pane.getBuffer wenzelm@49099: val text_area = edit_pane.getTextArea wenzelm@49099: wenzelm@49099: if (buffer != null && text_area != null) { wenzelm@49099: if (msg.getWhat == EditPaneUpdate.BUFFER_CHANGED || wenzelm@49099: msg.getWhat == EditPaneUpdate.CREATED) { wenzelm@50205: if (PIDE.session.is_ready) wenzelm@50205: PIDE.init_view(buffer, text_area) wenzelm@49099: } wenzelm@52867: else { wenzelm@53244: PIDE.dismissed_popups(text_area.getView) wenzelm@52867: PIDE.exit_view(buffer, text_area) wenzelm@52867: } wenzelm@53274: wenzelm@53274: if (msg.getWhat == EditPaneUpdate.CREATED) wenzelm@53274: Completion_Popup.Text_Area.init(text_area) wenzelm@53274: wenzelm@53274: if (msg.getWhat == EditPaneUpdate.DESTROYED) wenzelm@53274: Completion_Popup.Text_Area.exit(text_area) wenzelm@43510: } wenzelm@34784: wenzelm@49099: case msg: PropertiesChanged => wenzelm@62264: for { wenzelm@62264: view <- JEdit_Lib.jedit_views wenzelm@62264: edit_pane <- JEdit_Lib.jedit_edit_panes(view) wenzelm@62264: } { wenzelm@62264: val buffer = edit_pane.getBuffer wenzelm@62264: val text_area = edit_pane.getTextArea wenzelm@62264: if (buffer != null && text_area != null) PIDE.init_view(buffer, text_area) wenzelm@62264: } wenzelm@62264: wenzelm@56558: PIDE.spell_checker.update(PIDE.options.value) wenzelm@52084: PIDE.session.update_options(PIDE.options.value) wenzelm@34784: wenzelm@49099: case _ => wenzelm@49099: } wenzelm@34318: } wenzelm@34318: } wenzelm@34318: wenzelm@34618: override def start() wenzelm@49099: { wenzelm@49099: try { wenzelm@50709: Debug.DISABLE_SEARCH_DIALOG_POOL = true wenzelm@50709: wenzelm@50205: PIDE.plugin = this wenzelm@62917: PIDE.options.store(Options.init()) wenzelm@53337: PIDE.completion_history.load() wenzelm@56558: PIDE.spell_checker.update(PIDE.options.value) wenzelm@49245: wenzelm@49099: SyntaxUtilities.setStyleExtender(new Token_Markup.Style_Extender) wenzelm@49099: if (ModeProvider.instance.isInstanceOf[ModeProvider]) wenzelm@49099: ModeProvider.instance = new Token_Markup.Mode_Provider(ModeProvider.instance) wenzelm@48870: wenzelm@53274: JEdit_Lib.jedit_text_areas.foreach(Completion_Popup.Text_Area.init _) wenzelm@53274: wenzelm@64856: val resources = new JEdit_Resources(JEdit_Sessions.session_base(false)) wenzelm@49524: wenzelm@62051: PIDE.session.stop() wenzelm@56208: PIDE.session = new Session(resources) { wenzelm@50207: override def output_delay = PIDE.options.seconds("editor_output_delay") wenzelm@57867: override def prune_delay = PIDE.options.seconds("editor_prune_delay") wenzelm@57974: override def syslog_limit = PIDE.options.int("editor_syslog_limit") wenzelm@50205: override def reparse_limit = PIDE.options.int("editor_reparse_limit") wenzelm@49288: } wenzelm@48870: wenzelm@56715: PIDE.session.phase_changed += session_phase wenzelm@50205: PIDE.startup_failure = None wenzelm@49099: } wenzelm@49099: catch { wenzelm@49099: case exn: Throwable => wenzelm@50205: PIDE.startup_failure = Some(exn) wenzelm@50205: PIDE.startup_notified = false wenzelm@62228: Log.log(Log.ERROR, this, exn) wenzelm@49099: } wenzelm@34318: } wenzelm@34618: wenzelm@39628: override def stop() wenzelm@34618: { wenzelm@53274: JEdit_Lib.jedit_text_areas.foreach(Completion_Popup.Text_Area.exit _) wenzelm@53274: wenzelm@53337: if (PIDE.startup_failure.isEmpty) { wenzelm@50205: PIDE.options.value.save_prefs() wenzelm@53337: PIDE.completion_history.value.save() wenzelm@53337: } wenzelm@49245: wenzelm@56715: PIDE.session.phase_changed -= session_phase wenzelm@50344: PIDE.exit_models(JEdit_Lib.jedit_buffers().toList) wenzelm@50205: PIDE.session.stop() wenzelm@64858: PIDE.file_watcher.shutdown() wenzelm@34318: } wenzelm@34318: }