src/Tools/jEdit/src/document_model.scala
author wenzelm
Wed Jul 23 11:19:24 2014 +0200 (2014-07-23)
changeset 57612 990ffb84489b
parent 57610 518e28a7c74b
child 57615 df1b3452d71c
permissions -rw-r--r--
clarified module name: facilitate alternative GUI frameworks;
wenzelm@43282
     1
/*  Title:      Tools/jEdit/src/document_model.scala
wenzelm@36760
     2
    Author:     Fabian Immler, TU Munich
wenzelm@36760
     3
    Author:     Makarius
wenzelm@36760
     4
wenzelm@55778
     5
Document model connected to jEdit buffer (node in theory graph or
wenzelm@55778
     6
auxiliary file).
wenzelm@36760
     7
*/
wenzelm@34407
     8
wenzelm@34784
     9
package isabelle.jedit
wenzelm@34760
    10
wenzelm@34318
    11
wenzelm@36015
    12
import isabelle._
wenzelm@36015
    13
wenzelm@34693
    14
import scala.collection.mutable
wenzelm@34446
    15
wenzelm@34784
    16
import org.gjt.sp.jedit.Buffer
wenzelm@34783
    17
import org.gjt.sp.jedit.buffer.{BufferAdapter, BufferListener, JEditBuffer}
wenzelm@34318
    18
wenzelm@34760
    19
wenzelm@34784
    20
object Document_Model
wenzelm@34588
    21
{
wenzelm@34784
    22
  /* document model of buffer */
wenzelm@34784
    23
wenzelm@50565
    24
  private val key = "PIDE.document_model"
wenzelm@34784
    25
wenzelm@34788
    26
  def apply(buffer: Buffer): Option[Document_Model] =
wenzelm@34784
    27
  {
wenzelm@57612
    28
    GUI_Thread.require {}
wenzelm@34784
    29
    buffer.getProperty(key) match {
wenzelm@34784
    30
      case model: Document_Model => Some(model)
wenzelm@34784
    31
      case _ => None
wenzelm@34784
    32
    }
wenzelm@34784
    33
  }
wenzelm@34784
    34
wenzelm@34784
    35
  def exit(buffer: Buffer)
wenzelm@34784
    36
  {
wenzelm@57612
    37
    GUI_Thread.require {}
wenzelm@34788
    38
    apply(buffer) match {
wenzelm@39636
    39
      case None =>
wenzelm@34784
    40
      case Some(model) =>
wenzelm@34784
    41
        model.deactivate()
wenzelm@34784
    42
        buffer.unsetProperty(key)
wenzelm@47058
    43
        buffer.propertiesChanged
immler@34653
    44
    }
wenzelm@34318
    45
  }
wenzelm@43397
    46
wenzelm@52973
    47
  def init(session: Session, buffer: Buffer, node_name: Document.Node.Name): Document_Model =
wenzelm@43397
    48
  {
wenzelm@57612
    49
    GUI_Thread.require {}
wenzelm@47058
    50
    apply(buffer).map(_.deactivate)
wenzelm@52973
    51
    val model = new Document_Model(session, buffer, node_name)
wenzelm@43397
    52
    buffer.setProperty(key, model)
wenzelm@43397
    53
    model.activate()
wenzelm@47058
    54
    buffer.propertiesChanged
wenzelm@43397
    55
    model
wenzelm@43397
    56
  }
wenzelm@34318
    57
}
wenzelm@34318
    58
wenzelm@38151
    59
wenzelm@52973
    60
class Document_Model(val session: Session, val buffer: Buffer, val node_name: Document.Node.Name)
wenzelm@34588
    61
{
wenzelm@44385
    62
  /* header */
wenzelm@38222
    63
wenzelm@54509
    64
  def is_theory: Boolean = node_name.is_theory
wenzelm@54509
    65
wenzelm@48707
    66
  def node_header(): Document.Node.Header =
wenzelm@46920
    67
  {
wenzelm@57612
    68
    GUI_Thread.require {}
wenzelm@54509
    69
wenzelm@54509
    70
    if (is_theory) {
wenzelm@54509
    71
      JEdit_Lib.buffer_lock(buffer) {
wenzelm@54509
    72
        Exn.capture {
wenzelm@56823
    73
          PIDE.resources.check_thy_reader("", node_name, JEdit_Lib.buffer_reader(buffer))
wenzelm@54509
    74
        } match {
wenzelm@54509
    75
          case Exn.Res(header) => header
wenzelm@54509
    76
          case Exn.Exn(exn) => Document.Node.bad_header(Exn.message(exn))
wenzelm@54509
    77
        }
wenzelm@46748
    78
      }
wenzelm@46748
    79
    }
wenzelm@54509
    80
    else Document.Node.no_header
wenzelm@46920
    81
  }
wenzelm@44385
    82
wenzelm@44385
    83
wenzelm@44385
    84
  /* perspective */
wenzelm@44385
    85
wenzelm@57612
    86
  // owned by GUI thread
wenzelm@52816
    87
  private var _node_required = false
wenzelm@52816
    88
  def node_required: Boolean = _node_required
wenzelm@52816
    89
  def node_required_=(b: Boolean)
wenzelm@52816
    90
  {
wenzelm@57612
    91
    GUI_Thread.require {}
wenzelm@54531
    92
    if (_node_required != b && is_theory) {
wenzelm@52816
    93
      _node_required = b
wenzelm@52816
    94
      PIDE.options_changed()
wenzelm@52974
    95
      PIDE.editor.flush()
wenzelm@52816
    96
    }
wenzelm@52816
    97
  }
wenzelm@52808
    98
wenzelm@55783
    99
  def node_perspective(doc_blobs: Document.Blobs): (Boolean, Document.Node.Perspective_Text) =
wenzelm@44385
   100
  {
wenzelm@57612
   101
    GUI_Thread.require {}
wenzelm@52759
   102
wenzelm@54509
   103
    if (Isabelle.continuous_checking && is_theory) {
wenzelm@54325
   104
      val snapshot = this.snapshot()
wenzelm@54530
   105
wenzelm@54530
   106
      val document_view_ranges =
wenzelm@55432
   107
        if (is_theory) {
wenzelm@55432
   108
          for {
wenzelm@55432
   109
            doc_view <- PIDE.document_views(buffer)
wenzelm@55432
   110
            range <- doc_view.perspective(snapshot).ranges
wenzelm@55432
   111
          } yield range
wenzelm@55432
   112
        }
wenzelm@55432
   113
        else Nil
wenzelm@54530
   114
wenzelm@56314
   115
      val load_ranges =
wenzelm@54530
   116
        for {
wenzelm@56314
   117
          cmd <- snapshot.node.load_commands
wenzelm@54530
   118
          blob_name <- cmd.blobs_names
wenzelm@56457
   119
          blob_buffer <- JEdit_Lib.jedit_buffer(blob_name)
wenzelm@54530
   120
          if !JEdit_Lib.jedit_text_areas(blob_buffer).isEmpty
wenzelm@54530
   121
          start <- snapshot.node.command_start(cmd)
wenzelm@54530
   122
          range = snapshot.convert(cmd.proper_range + start)
wenzelm@54530
   123
        } yield range
wenzelm@54530
   124
wenzelm@56314
   125
      val reparse = snapshot.node.load_commands.exists(_.blobs_changed(doc_blobs))
wenzelm@55781
   126
wenzelm@55781
   127
      (reparse,
wenzelm@55781
   128
        Document.Node.Perspective(node_required,
wenzelm@56314
   129
          Text.Perspective(document_view_ranges ::: load_ranges),
wenzelm@55781
   130
          PIDE.editor.node_overlays(node_name)))
wenzelm@52849
   131
    }
wenzelm@57610
   132
    else (false, Document.Node.empty_perspective_text)
wenzelm@44385
   133
  }
wenzelm@44385
   134
wenzelm@44385
   135
wenzelm@54509
   136
  /* blob */
wenzelm@54509
   137
wenzelm@57612
   138
  private var _blob: Option[(Bytes, Symbol.Text_Chunk)] = None  // owned by GUI thread
wenzelm@54511
   139
wenzelm@57612
   140
  private def reset_blob(): Unit = GUI_Thread.require { _blob = None }
wenzelm@54511
   141
wenzelm@55783
   142
  def get_blob(): Option[Document.Blob] =
wenzelm@57612
   143
    GUI_Thread.require {
wenzelm@55783
   144
      if (is_theory) None
wenzelm@55783
   145
      else {
wenzelm@56473
   146
        val (bytes, chunk) =
wenzelm@55783
   147
          _blob match {
wenzelm@55783
   148
            case Some(x) => x
wenzelm@55783
   149
            case None =>
wenzelm@56208
   150
              val bytes = PIDE.resources.file_content(buffer)
wenzelm@56746
   151
              val chunk = Symbol.Text_Chunk(buffer.getSegment(0, buffer.getLength))
wenzelm@56473
   152
              _blob = Some((bytes, chunk))
wenzelm@56473
   153
              (bytes, chunk)
wenzelm@55783
   154
          }
wenzelm@55783
   155
        val changed = pending_edits.is_pending()
wenzelm@56473
   156
        Some(Document.Blob(bytes, chunk, changed))
wenzelm@54511
   157
      }
wenzelm@54511
   158
    }
wenzelm@54509
   159
wenzelm@54509
   160
wenzelm@50363
   161
  /* edits */
wenzelm@50344
   162
wenzelm@54461
   163
  def node_edits(
wenzelm@56335
   164
      clear: Boolean,
wenzelm@56335
   165
      text_edits: List[Text.Edit],
wenzelm@56335
   166
      perspective: Document.Node.Perspective_Text): List[Document.Edit_Text] =
wenzelm@56335
   167
    get_blob() match {
wenzelm@56335
   168
      case None =>
wenzelm@56335
   169
        val header_edit = session.header_edit(node_name, node_header())
wenzelm@56335
   170
        if (clear)
wenzelm@56335
   171
          List(header_edit,
wenzelm@56335
   172
            node_name -> Document.Node.Clear(),
wenzelm@56335
   173
            node_name -> Document.Node.Edits(text_edits),
wenzelm@56335
   174
            node_name -> perspective)
wenzelm@56335
   175
        else
wenzelm@56335
   176
          List(header_edit,
wenzelm@56335
   177
            node_name -> Document.Node.Edits(text_edits),
wenzelm@56335
   178
            node_name -> perspective)
wenzelm@56335
   179
      case Some(blob) =>
wenzelm@56335
   180
        List(node_name -> Document.Node.Blob(blob),
wenzelm@56335
   181
          node_name -> Document.Node.Edits(text_edits))
wenzelm@54509
   182
    }
wenzelm@50363
   183
wenzelm@50344
   184
wenzelm@50344
   185
  /* pending edits */
wenzelm@43648
   186
wenzelm@57612
   187
  private object pending_edits  // owned by GUI thread
wenzelm@38224
   188
  {
wenzelm@54461
   189
    private var pending_clear = false
wenzelm@38425
   190
    private val pending = new mutable.ListBuffer[Text.Edit]
wenzelm@57610
   191
    private var last_perspective = Document.Node.empty_perspective_text
wenzelm@44438
   192
wenzelm@55781
   193
    def is_pending(): Boolean = pending_clear || !pending.isEmpty
wenzelm@38425
   194
    def snapshot(): List[Text.Edit] = pending.toList
wenzelm@38224
   195
wenzelm@55783
   196
    def flushed_edits(doc_blobs: Document.Blobs): List[Document.Edit_Text] =
wenzelm@38224
   197
    {
wenzelm@54461
   198
      val clear = pending_clear
wenzelm@47393
   199
      val edits = snapshot()
wenzelm@55783
   200
      val (reparse, perspective) = node_perspective(doc_blobs)
wenzelm@55781
   201
      if (clear || reparse || !edits.isEmpty || last_perspective != perspective) {
wenzelm@54461
   202
        pending_clear = false
wenzelm@47393
   203
        pending.clear
wenzelm@54461
   204
        last_perspective = perspective
wenzelm@54461
   205
        node_edits(clear, edits, perspective)
wenzelm@43648
   206
      }
wenzelm@52759
   207
      else Nil
wenzelm@38224
   208
    }
wenzelm@38224
   209
wenzelm@54461
   210
    def edit(clear: Boolean, e: Text.Edit)
wenzelm@38224
   211
    {
wenzelm@54511
   212
      reset_blob()
wenzelm@54511
   213
wenzelm@54461
   214
      if (clear) {
wenzelm@54461
   215
        pending_clear = true
wenzelm@54461
   216
        pending.clear
wenzelm@54461
   217
      }
wenzelm@54461
   218
      pending += e
wenzelm@54461
   219
      PIDE.editor.invoke()
wenzelm@44436
   220
    }
wenzelm@44436
   221
  }
wenzelm@44436
   222
wenzelm@54464
   223
  def snapshot(): Document.Snapshot =
wenzelm@57612
   224
    GUI_Thread.require { session.snapshot(node_name, pending_edits.snapshot()) }
wenzelm@34731
   225
wenzelm@55783
   226
  def flushed_edits(doc_blobs: Document.Blobs): List[Document.Edit_Text] =
wenzelm@57612
   227
    GUI_Thread.require { pending_edits.flushed_edits(doc_blobs) }
wenzelm@34828
   228
wenzelm@34828
   229
wenzelm@34828
   230
  /* buffer listener */
wenzelm@34828
   231
wenzelm@34828
   232
  private val buffer_listener: BufferListener = new BufferAdapter
wenzelm@34828
   233
  {
wenzelm@40478
   234
    override def bufferLoaded(buffer: JEditBuffer)
wenzelm@40478
   235
    {
wenzelm@55791
   236
      pending_edits.edit(true, Text.Edit.insert(0, JEdit_Lib.buffer_text(buffer)))
wenzelm@40478
   237
    }
wenzelm@40478
   238
wenzelm@34828
   239
    override def contentInserted(buffer: JEditBuffer,
wenzelm@34828
   240
      start_line: Int, offset: Int, num_lines: Int, length: Int)
wenzelm@34828
   241
    {
wenzelm@40478
   242
      if (!buffer.isLoading)
wenzelm@54461
   243
        pending_edits.edit(false, Text.Edit.insert(offset, buffer.getText(offset, length)))
wenzelm@34828
   244
    }
wenzelm@34828
   245
wenzelm@34828
   246
    override def preContentRemoved(buffer: JEditBuffer,
wenzelm@38426
   247
      start_line: Int, offset: Int, num_lines: Int, removed_length: Int)
wenzelm@34828
   248
    {
wenzelm@40478
   249
      if (!buffer.isLoading)
wenzelm@54461
   250
        pending_edits.edit(false, Text.Edit.remove(offset, buffer.getText(offset, removed_length)))
wenzelm@34828
   251
    }
wenzelm@34828
   252
  }
wenzelm@34828
   253
wenzelm@34828
   254
wenzelm@38158
   255
  /* activation */
wenzelm@37557
   256
wenzelm@43512
   257
  private def activate()
wenzelm@34784
   258
  {
wenzelm@55791
   259
    pending_edits.edit(true, Text.Edit.insert(0, JEdit_Lib.buffer_text(buffer)))
wenzelm@34784
   260
    buffer.addBufferListener(buffer_listener)
wenzelm@44358
   261
    Token_Markup.refresh_buffer(buffer)
immler@34680
   262
  }
immler@34680
   263
wenzelm@43512
   264
  private def deactivate()
immler@34680
   265
  {
wenzelm@34784
   266
    buffer.removeBufferListener(buffer_listener)
wenzelm@44358
   267
    Token_Markup.refresh_buffer(buffer)
immler@34680
   268
  }
wenzelm@34447
   269
}