more efficient document model/view -- avoid repeated iteration over commands from start, prefer bulk operations;
authorwenzelm
Sat, 03 Jul 2010 20:20:13 +0200
changeset 37701305c326db33b
parent 37700 d123b1e08856
child 37702 bb27d99a9a69
more efficient document model/view -- avoid repeated iteration over commands from start, prefer bulk operations;
misc tuning;
src/Pure/PIDE/document.scala
src/Tools/jEdit/src/jedit/document_model.scala
src/Tools/jEdit/src/jedit/document_view.scala
     1.1 --- a/src/Pure/PIDE/document.scala	Fri Jul 02 21:48:54 2010 +0200
     1.2 +++ b/src/Pure/PIDE/document.scala	Sat Jul 03 20:20:13 2010 +0200
     1.3 @@ -14,13 +14,13 @@
     1.4  {
     1.5    /* command start positions */
     1.6  
     1.7 -  def command_starts(commands: Linear_Set[Command]): Iterator[(Command, Int)] =
     1.8 +  def command_starts(commands: Iterator[Command], offset: Int = 0): Iterator[(Command, Int)] =
     1.9    {
    1.10 -    var offset = 0
    1.11 -    for (cmd <- commands.iterator) yield {
    1.12 -      val start = offset
    1.13 -      offset += cmd.length
    1.14 -      (cmd, start)
    1.15 +    var i = offset
    1.16 +    for (command <- commands) yield {
    1.17 +      val start = i
    1.18 +      i += command.length
    1.19 +      (command, start)
    1.20      }
    1.21    }
    1.22  
    1.23 @@ -60,9 +60,10 @@
    1.24      {
    1.25        eds match {
    1.26          case e :: es =>
    1.27 -          command_starts(commands).find {   // FIXME relative search!
    1.28 +          command_starts(commands.iterator).find {
    1.29              case (cmd, cmd_start) =>
    1.30 -              e.can_edit(cmd.source, cmd_start) || e.is_insert && e.start == cmd_start + cmd.length
    1.31 +              e.can_edit(cmd.source, cmd_start) ||
    1.32 +                e.is_insert && e.start == cmd_start + cmd.length
    1.33            } match {
    1.34              case Some((cmd, cmd_start)) if e.can_edit(cmd.source, cmd_start) =>
    1.35                val (rest, text) = e.edit(cmd.source, cmd_start)
    1.36 @@ -144,7 +145,7 @@
    1.37  {
    1.38    /* command ranges */
    1.39  
    1.40 -  def command_starts: Iterator[(Command, Int)] = Document.command_starts(commands)
    1.41 +  def command_starts: Iterator[(Command, Int)] = Document.command_starts(commands.iterator)
    1.42  
    1.43    def command_start(cmd: Command): Option[Int] =
    1.44      command_starts.find(_._1 == cmd).map(_._2)
     2.1 --- a/src/Tools/jEdit/src/jedit/document_model.scala	Fri Jul 02 21:48:54 2010 +0200
     2.2 +++ b/src/Tools/jEdit/src/jedit/document_model.scala	Sat Jul 03 20:20:13 2010 +0200
     2.3 @@ -95,14 +95,6 @@
     2.4    def to_current(doc: Document, offset: Int): Int =
     2.5      (offset /: changes_from(doc)) ((i, change) => change after i)
     2.6  
     2.7 -  def lines_of_command(doc: Document, cmd: Command): (Int, Int) =
     2.8 -  {
     2.9 -    val start = doc.command_start(cmd).get  // FIXME total?
    2.10 -    val stop = start + cmd.length
    2.11 -    (buffer.getLineOfOffset(to_current(doc, start)),
    2.12 -     buffer.getLineOfOffset(to_current(doc, stop)))
    2.13 -  }
    2.14 -
    2.15  
    2.16    /* text edits */
    2.17  
     3.1 --- a/src/Tools/jEdit/src/jedit/document_view.scala	Fri Jul 02 21:48:54 2010 +0200
     3.2 +++ b/src/Tools/jEdit/src/jedit/document_view.scala	Sat Jul 03 20:20:13 2010 +0200
     3.3 @@ -104,13 +104,10 @@
     3.4        react {
     3.5          case Command_Set(changed) =>
     3.6            Swing_Thread.now {
     3.7 +            // FIXME cover doc states as well!!?
     3.8              val document = model.recent_document()
     3.9 -            // FIXME cover doc states as well!!?
    3.10 -            for (command <- changed if document.commands.contains(command)) {
    3.11 -              update_syntax(document, command)
    3.12 -              invalidate_line(document, command)
    3.13 -              overview.repaint()
    3.14 -            }
    3.15 +            if (changed.exists(document.commands.contains))
    3.16 +              full_repaint(document, changed)
    3.17            }
    3.18  
    3.19          case bad => System.err.println("command_change_actor: ignoring bad message " + bad)
    3.20 @@ -118,34 +115,82 @@
    3.21      }
    3.22    }
    3.23  
    3.24 +  def full_repaint(document: Document, changed: Set[Command])
    3.25 +  {
    3.26 +    Swing_Thread.assert()
    3.27 +
    3.28 +    val buffer = model.buffer
    3.29 +    var visible_change = false
    3.30 +
    3.31 +    for ((command, start) <- document.command_starts) {
    3.32 +      if (changed(command)) {
    3.33 +        val stop = start + command.length
    3.34 +        val line1 = buffer.getLineOfOffset(model.to_current(document, start))
    3.35 +        val line2 = buffer.getLineOfOffset(model.to_current(document, stop))
    3.36 +        if (line2 >= text_area.getFirstLine &&
    3.37 +            line1 <= text_area.getFirstLine + text_area.getVisibleLines)
    3.38 +          visible_change = true
    3.39 +        text_area.invalidateLineRange(line1, line2)
    3.40 +      }
    3.41 +    }
    3.42 +    if (visible_change) model.buffer.propertiesChanged()
    3.43 +
    3.44 +    overview.repaint()  // FIXME paint here!?
    3.45 +  }
    3.46 +
    3.47  
    3.48    /* text_area_extension */
    3.49  
    3.50    private val text_area_extension = new TextAreaExtension
    3.51    {
    3.52 -    override def paintValidLine(gfx: Graphics2D,
    3.53 -      screen_line: Int, physical_line: Int, start: Int, end: Int, y: Int)
    3.54 +    override def paintScreenLineRange(gfx: Graphics2D,
    3.55 +      first_line: Int, last_line: Int, physical_lines: Array[Int],
    3.56 +      start: Array[Int], end: Array[Int], y0: Int, line_height: Int)
    3.57      {
    3.58 -      val document = model.recent_document()
    3.59 -      def from_current(pos: Int) = model.from_current(document, pos)
    3.60 -      def to_current(pos: Int) = model.to_current(document, pos)
    3.61 +      Swing_Thread.now {
    3.62 +        val document = model.recent_document()
    3.63 +        def from_current(pos: Int) = model.from_current(document, pos)
    3.64 +        def to_current(pos: Int) = model.to_current(document, pos)
    3.65  
    3.66 -      val line_end = model.visible_line_end(start, end)
    3.67 -      val line_height = text_area.getPainter.getFontMetrics.getHeight
    3.68 +        val command_range: Iterable[(Command, Int)] =
    3.69 +        {
    3.70 +          val range = document.command_range(from_current(start(0)))
    3.71 +          if (range.hasNext) {
    3.72 +            val (cmd0, start0) = range.next
    3.73 +            new Iterable[(Command, Int)] {
    3.74 +              def iterator = Document.command_starts(document.commands.iterator(cmd0), start0)
    3.75 +            }
    3.76 +          }
    3.77 +          else Iterable.empty
    3.78 +        }
    3.79  
    3.80 -      val saved_color = gfx.getColor
    3.81 -      try {
    3.82 -        for ((command, command_start) <-
    3.83 -          document.command_range(from_current(start), from_current(line_end)) if !command.is_ignored)
    3.84 -        {
    3.85 -          val p = text_area.offsetToXY(start max to_current(command_start))
    3.86 -          val q = text_area.offsetToXY(line_end min to_current(command_start + command.length))
    3.87 -          assert(p.y == q.y)
    3.88 -          gfx.setColor(Document_View.choose_color(document, command))
    3.89 -          gfx.fillRect(p.x, y, q.x - p.x, line_height)
    3.90 +        val saved_color = gfx.getColor
    3.91 +        try {
    3.92 +          var y = y0
    3.93 +          for (i <- 0 until physical_lines.length) {
    3.94 +            if (physical_lines(i) != -1) {
    3.95 +              val line_start = start(i)
    3.96 +              val line_end = model.visible_line_end(line_start, end(i))
    3.97 +
    3.98 +              val a = from_current(line_start)
    3.99 +              val b = from_current(line_end)
   3.100 +              val cmds = command_range.iterator.
   3.101 +                dropWhile { case (cmd, c) => c + cmd.length <= a } .
   3.102 +                takeWhile { case (_, c) => c < b }
   3.103 +
   3.104 +              for ((command, command_start) <- cmds if !command.is_ignored) {
   3.105 +                val p = text_area.offsetToXY(line_start max to_current(command_start))
   3.106 +                val q = text_area.offsetToXY(line_end min to_current(command_start + command.length))
   3.107 +                assert(p.y == q.y)
   3.108 +                gfx.setColor(Document_View.choose_color(document, command))
   3.109 +                gfx.fillRect(p.x, y, q.x - p.x, line_height)
   3.110 +              }
   3.111 +            }
   3.112 +            y += line_height
   3.113 +          }
   3.114          }
   3.115 +        finally { gfx.setColor(saved_color) }
   3.116        }
   3.117 -      finally { gfx.setColor(saved_color) }
   3.118      }
   3.119  
   3.120      override def getToolTipText(x: Int, y: Int): String =
   3.121 @@ -186,30 +231,6 @@
   3.122    }
   3.123  
   3.124  
   3.125 -  /* (re)painting */
   3.126 -
   3.127 -  private val update_delay = Swing_Thread.delay_first(500) { model.buffer.propertiesChanged() }
   3.128 -  // FIXME update_delay property
   3.129 -
   3.130 -  private def update_syntax(document: Document, cmd: Command)
   3.131 -  {
   3.132 -    val (line1, line2) = model.lines_of_command(document, cmd)
   3.133 -    if (line2 >= text_area.getFirstLine &&
   3.134 -      line1 <= text_area.getFirstLine + text_area.getVisibleLines)
   3.135 -        update_delay()
   3.136 -  }
   3.137 -
   3.138 -  private def invalidate_line(document: Document, cmd: Command) =
   3.139 -  {
   3.140 -    val (start, stop) = model.lines_of_command(document, cmd)
   3.141 -    text_area.invalidateLineRange(start, stop)
   3.142 -  }
   3.143 -
   3.144 -  private def invalidate_all() =
   3.145 -    text_area.invalidateLineRange(text_area.getFirstPhysicalLine,
   3.146 -      text_area.getLastPhysicalLine)
   3.147 -
   3.148 -
   3.149    /* overview of command status left of scrollbar */
   3.150  
   3.151    private val overview = new JPanel(new BorderLayout)
   3.152 @@ -252,9 +273,9 @@
   3.153        val buffer = model.buffer
   3.154        val document = model.recent_document()
   3.155        def to_current(pos: Int) = model.to_current(document, pos)
   3.156 -      val saved_color = gfx.getColor
   3.157 +      val saved_color = gfx.getColor  // FIXME needed!?
   3.158        try {
   3.159 -        for ((command, start) <- document.command_range(0) if !command.is_ignored) {
   3.160 +        for ((command, start) <- document.command_starts if !command.is_ignored) {
   3.161            val line1 = buffer.getLineOfOffset(to_current(start))
   3.162            val line2 = buffer.getLineOfOffset(to_current(start + command.length)) + 1
   3.163            val y = line_to_y(line1)