Skip to content
Browse files

Faster and slicker Semantic Highlighting

The former implementation of semantic highlighting was slow becuase of the
following two reasons:

First, the annotations created by the semantic highlighting component, where
invalidated and recomputed every time the compilation unit was reconciled.

Second, for the current implementation of the AnnotationModel, adding/removing
an annotation is O(n^ 2). (The quadratic behavior is due to the implementation
of ``CompilationUnitDocumentProvider#ReverseMap``, which is used by
``CompilationUnitDocumentProvider#CompilationUnitAnnotationModel#addAnnotation``.
Basically, each time an annotation is added/removed, the ``ReverseMap`` class -
which is backed by a linked list - is accessed to verify if the element exist.)

The proposed solution is to apply semantic colorings directly on the editor's
``TextPresentation`` and hence no longer use the ``AnnotationModel``. This
approach is both lightweight and orders of magnitude more efficient than the
former.

The way semantic highlighting works now is relatively simple. At a high level,
there are only three key elements:

* ``Presenter$SemanticHighlightingJob``: Given a compilation unit, it
  classifyies and collects all positions that need to be colored in the editor.

* ``PositionsTracker``: Keeps track of currently highlighted positions in the
  editor.

* ``TextPresentationEditorHighlighter$ApplyHighlightingTextPresentationChange``:
  Converts them into   ``StyleRange``s, and applies the styles to the editor's
  ``TextPresentation``.

Of course the actual implementation contains a bit more than just the above
mentioned classes, but most of it is needed to correctly handle platform's
events such as the swapping of the document input (see
``Presenter$DocumentSwapListener`).

There is however one important optimization that is worth mentioning, which is
done to reduce to a minimum the editor's region whose styles need to be
refreshed. Basically, whenever the user is typing in the editor, all positions
that are **after** the cursor should be invalidated. If you are editing the
beginning of a big file, almost the whole editor's text presentation would need
to be refreshed. Furthermore, the mentioned approach would cause colors to
flicker because all highlighted positions **after** the edit would be deleted
(and hence the colors would be removed), and the new positions would get
colored only **after** the semantic highlighting job completes. This is clearly
not ideal, and it turns out it can be easily avoided by registering a
``IPositionUpdater`` on the editor's document. The concrete implementation of
the listener (i.e., ``Presenter$PositionUpdater``) takes care of shifting -
through side-effect - all currently highlighted positions that are **after**
the edited region. The net effect is that colors never flicker.

Bottom line, with the current implementation semantic highlighting is:

* Considerably faster.
* Never flickers.
* Testable.

If you wonder why the semantically highlighted positions are not stored in the
document's model, the answer is that I simply could not see any benefit of
doing so (why? because the document synchronizes access to the underlying
position's map when adding/removing positions).  Furthermore, it is really easy
to add the missing logic for storing the positions in the document's model, if
in the future it turns out we need/want to do so.

Fix #1001156

Other facts about this commit:

* Added the needed infrastructure for registering reconciling listeners (i.e.,
  ``IJavaReconcilingListener``) to the editor.

* Created a ``UiThread`` trait needed for testing code that needs to be run
  within the UI Thread.

Other tickets fixed by this commit:

* Disabling semantic highlighting in the preferences now refreshes currently
  opened editors. Fixes #1001507

* Changing semantic highlighting style's preferences now refreshes the
  currently opened editors. Fixes #1001508

* Cache highlighting styles instead of creating the during
  ``TextPresentationEditorHighlighter.ApplyHighlightingTextPresentationChanges.applyTextPresentation``.
  This optimization has major consequence on the user's experience. I should also
  point out that ``ScalaSyntaxClass`` and ``SymbolTypes``  are quite messy and
  should be improved. However, I think this should be tackled in a different PR.
  Fixes #1001493, #1001489.
  • Loading branch information...
1 parent e94f727 commit 9bfeb02fb7dda1be1fc526f014a7695fa29c988e @dotta dotta committed
Showing with 1,389 additions and 519 deletions.
  1. +2 −0 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java
  2. +282 −0 ...t.core.tests/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingPositionsTest.scala
  3. +22 −8 ....tests/src/scala/tools/eclipse/semantichighlighting/classifier/AbstractSymbolClassifierTest.scala
  4. +6 −3 ...ala-ide.sdt.core.tests/src/scala/tools/eclipse/semantichighlighting/classifier/RegionParser.scala
  5. +11 −0 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/util/CurrentThread.scala
  6. +2 −0 org.scala-ide.sdt.core/META-INF/MANIFEST.MF
  7. +2 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala
  8. +32 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationReconciler.scala
  9. +82 −33 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaSourceFileEditor.scala
  10. +11 −10 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaSourceViewerConfiguration.scala
  11. +2 −4 org.scala-ide.sdt.core/src/scala/tools/eclipse/diagnostic/ReportBugDialog.scala
  12. +8 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/jface/text/EmptyRegion.scala
  13. +16 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/jface/text/Region.scala
  14. +20 −61 ...ide.sdt.core/src/scala/tools/eclipse/properties/syntaxcolouring/ColourPreferenceInitializer.scala
  15. +14 −5 org.scala-ide.sdt.core/src/scala/tools/eclipse/properties/syntaxcolouring/ScalaSyntaxClass.scala
  16. +0 −8 org.scala-ide.sdt.core/src/scala/tools/eclipse/properties/syntaxcolouring/ScalaSyntaxClasses.scala
  17. +0 −13 ...e/src/scala/tools/eclipse/semantichighlighting/AnnotationPreferenceWithForegroundColourStyle.java
  18. +63 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Position.scala
  19. +39 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/PositionsChange.scala
  20. +182 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/PositionsTracker.scala
  21. +20 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Preferences.scala
  22. +185 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Presenter.scala
  23. +0 −9 ...la-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotation.scala
  24. +0 −88 ...a-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotations.scala
  25. +0 −114 ...dt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotationsManager.scala
  26. +1 −4 ...de.sdt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingReconciliation.scala
  27. +0 −18 ...sdt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingTextStyleStrategy.scala
  28. +20 −0 ...scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/TextPresentationHighlighter.scala
  29. +1 −1 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/Debugger.scala
  30. +0 −12 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/Region.scala
  31. +1 −1 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SafeSymbol.scala
  32. +53 −39 ...a-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SymbolClassification.scala
  33. +0 −11 ...scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SymbolClassifier.scala
  34. +8 −21 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SymbolInfo.scala
  35. +25 −18 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SymbolTests.scala
  36. +26 −21 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SyntacticInfo.scala
  37. +1 −1 ...cala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/TypeTreeTraverser.scala
  38. +3 −5 ...scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/{ → implicits}/ColorManager.scala
  39. +0 −1 ...t.core/src/scala/tools/eclipse/semantichighlighting/implicits/ImplicitHighlightingPresenter.scala
  40. +56 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/ui/HighlightingStyle.scala
  41. +138 −0 ....sdt.core/src/scala/tools/eclipse/semantichighlighting/ui/TextPresentationEditorHighlighter.scala
  42. +22 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/ui/DisplayThread.scala
  43. +12 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/ui/UIThread.scala
  44. +1 −2 org.scala-ide.sdt.core/src/scala/tools/eclipse/util/EditorUtils.scala
  45. +5 −8 org.scala-ide.sdt.core/src/scala/tools/eclipse/util/SWTUtils.scala
  46. +15 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/util/withDocument.scala
View
2 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/TestsSuite.java
@@ -26,6 +26,7 @@
import scala.tools.eclipse.sbtbuilder.ScalaJavaDepTest;
import scala.tools.eclipse.sbtbuilder.TodoBuilderTest;
import scala.tools.eclipse.semantic.ImplicitsHighlightingTest;
+import scala.tools.eclipse.semantichighlighting.SemanticHighlightingPositionsTest;
import scala.tools.eclipse.semantichighlighting.classifier.SymbolClassifierTestSuite;
import scala.tools.eclipse.structurebuilder.StructureBuilderTest;
import scala.tools.eclipse.ui.UITestSuite;
@@ -66,6 +67,7 @@
ScalaJavaDepTest.class,
TodoBuilderTest.class,
ImplicitsHighlightingTest.class,
+ SemanticHighlightingPositionsTest.class,
SymbolClassifierTestSuite.class,
StructureBuilderTest.class,
UITestSuite.class,
View
282 ...ests/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingPositionsTest.scala
@@ -0,0 +1,282 @@
+package scala.tools.eclipse.semantichighlighting
+
+import scala.tools.eclipse.EclipseUserSimulator
+import scala.tools.eclipse.ScalaPlugin
+import scala.tools.eclipse.ScalaProject
+import scala.tools.eclipse.javaelements.ScalaCompilationUnit
+import scala.tools.eclipse.jface.text.EmptyRegion
+import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes
+import scala.tools.eclipse.ui.InteractiveCompilationUnitEditor
+import scala.tools.eclipse.util.CurrentThread
+import scala.tools.eclipse.util.EclipseUtils
+import scala.util.matching.Regex
+import scala.util.matching.Regex.Match
+
+import org.eclipse.core.internal.filebuffers.SynchronizableDocument
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.core.runtime.IStatus
+import org.eclipse.core.runtime.NullProgressMonitor
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.jface.preference.IPreferenceStore
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.Region
+import org.eclipse.jface.text.source.ISourceViewer
+import org.junit.{ Before, Test }
+import org.junit.After
+import org.junit.Assert
+import org.mockito.Mockito._
+
+class SemanticHighlightingPositionsTest {
+ import SemanticHighlightingPositionsTest._
+
+ private val MarkerRegex: Regex = """/\*\^\*/""".r
+ private val Marker = "/*^*/"
+
+ protected val simulator = new EclipseUserSimulator
+ private var project: ScalaProject = _
+
+ private var sourceView: ISourceViewer = _
+ private var document: IDocument = _
+
+ private val preferences: Preferences = {
+ val store = mock(classOf[IPreferenceStore])
+ when(store.getBoolean(ScalaSyntaxClasses.USE_SYNTACTIC_HINTS)).thenReturn(true)
+ new Preferences(store)
+ }
+
+ private var editor: TextPresentationStub = _
+ private var presenter: Presenter = _
+
+ private var testCode: String = _
+ private var unit: ScalaCompilationUnit = _
+ private var compilationUnitEditor: InteractiveCompilationUnitEditor = mock(classOf[InteractiveCompilationUnitEditor])
+
+ @Before
+ def createProject() {
+ project = simulator.createProjectInWorkspace("semantic-highlighting-positions-update", true)
+ }
+
+ @After
+ def deleteProject() {
+ EclipseUtils.workspaceRunnableIn(ScalaPlugin.plugin.workspaceRoot.getWorkspace) { _ =>
+ project.underlying.delete(true, null)
+ }
+ }
+
+ @Before
+ def setupMocks() {
+ sourceView = mock(classOf[ISourceViewer])
+ document = new SynchronizableDocument
+ when(sourceView.getDocument()).thenReturn(document)
+ }
+
+ trait Edit {
+ def newText: String = ""
+ def newPositions: List[Position] = Nil
+ }
+ case class AddText(override val newText: String, override val newPositions: List[Position] = Nil) extends Edit
+ case object RemoveText extends Edit
+
+ private def setTestCode(code: String): Unit = {
+ testCode = code.stripMargin
+ val emptyPkg = simulator.createPackage("")
+ unit = simulator.createCompilationUnit(emptyPkg, "A.scala", testCode).asInstanceOf[ScalaCompilationUnit]
+ when(compilationUnitEditor.getInteractiveCompilationUnit).thenReturn(unit)
+ document.set(testCode)
+ }
+
+ private def createSemanticHighlightingPresenter(): Unit = {
+ editor = TextPresentationStub(sourceView)
+ presenter = new Presenter(compilationUnitEditor, editor, preferences, CurrentThread)
+ presenter.initialize(forceRefresh = false)
+ }
+
+ private def positionsInRegion(region: Region): List[Position] = {
+ val positions = editor.positionsTracker.positionsInRegion(region).filterNot(_.isDeleted())
+ // We clone the `positions` because the semantic highlighting component works by performing side-effects
+ // on the existing positions. And, the `runTest`, expects the returned positions to not change.
+ positions.map(pos => new Position(pos.getOffset, pos.getLength, pos.kind, pos.deprecated)).toList
+ }
+
+ private def findAllMarkersIn(code: String): List[Match] =
+ MarkerRegex.findAllIn(code).matchData.toList
+
+ /** Test that semantic highlighting positions are correctly created for the passed `code` before
+ * and after performing the `edit`. It also checks that the damaged region caused by the `edit`
+ * is the smallest contiguous region that includes all positions affected by the `edit`.
+ *
+ * The passed `code` should contain at least one, and at most two, `Marker`.
+ *
+ * - One `Marker`: the new text carried by the `edit` should replace the `Marker` in
+ * the passed `code`.
+ *
+ * - Two `Marker`'s: the new text carried by the `edit` should replace the whole text contained
+ * within the two `Marker`s in the passed `code`.
+ *
+ * @param edit The edit action to perform on the passed `code`.
+ * @param code The test code.
+ */
+ private def runTest(edit: Edit)(code: String): Unit = {
+ setTestCode(code)
+
+ createSemanticHighlightingPresenter()
+
+ val highlightedBeforeEdition = positionsInRegion(new Region(0, code.length))
+
+ val markers = findAllMarkersIn(testCode)
+
+ val (start, end) = markers.size match {
+ case 1 => (markers.head.start, markers.head.end)
+ case 2 => (markers.head.start, markers.last.end)
+ case size => throw new AssertionError("Unsupported test definition. Found %d occurrences of %s in test code:\n%s".format(size, Marker, testCode))
+ }
+ val length = end - start
+
+ val positionShift = edit.newText.length - length
+ val expectedHighlightedPositionsAfterEdition: List[Position] = {
+ // collect all positions in the document that are expected to still be highlighted after the edition
+ val positions = highlightedBeforeEdition.filterNot { _.overlapsWith(start, length) }
+ // of the above positions, shift the ones that are affected by the edition
+ val shiftedPositions = positions.map { pos =>
+ if (pos.getOffset() >= start) pos.setOffset(pos.getOffset() + positionShift)
+ pos
+ }
+ // merge together the shifted positions and any additional position that is expected to be created by the edit
+ val merged = (edit.newPositions ++ positions)
+ // Sort the position. This is needed because position's added to the document are expected to be sorted.
+ val sorted = merged.sorted
+ sorted
+ }
+
+ def editTestCode(offset: Int, length: Int, newText: String): Unit = {
+ document.replace(start, length, edit.newText) // triggers the IUpdatePosition listener
+ unit.getBuffer().replace(start, length, edit.newText) // needed by the semantic highlighting reconciler
+ }
+
+ // perform edit
+ editTestCode(start, length, edit.newText)
+
+ // checks edit's postcondition
+ val currentTestCode = unit.getContents.mkString
+ if (findAllMarkersIn(currentTestCode).nonEmpty)
+ throw new AssertionError("After edition, no marker `%s` should be present in test code:\n%s".format(Marker, currentTestCode))
+
+ // This will trigger semantic highlighting computation, which in turn will update the document's positions (sequential execution!)
+ editor.reconcileNow()
+
+ val newPositions = positionsInRegion(new Region(start, edit.newText.length()))
+ val affectedRegion = PositionsChange(newPositions, Nil).affectedRegion()
+
+ val highlightedAfterEdition = positionsInRegion(new Region(0, currentTestCode.length))
+
+ Assert.assertEquals("Wrong start of damaged region", affectedRegion.getOffset(), editor.damagedRegion.getOffset())
+ Assert.assertEquals("Wrong length of damaged region", affectedRegion.getLength(), editor.damagedRegion.getLength())
+ Assert.assertEquals(expectedHighlightedPositionsAfterEdition.size, highlightedAfterEdition.size)
+ Assert.assertEquals(expectedHighlightedPositionsAfterEdition, highlightedAfterEdition)
+ }
+
+ @Test
+ def highlighted_positions_not_affected_by_edition() {
+ runTest(AddText("\n\n")) {
+ """
+ |class A {
+ | def foo(): Unit = {}
+ | /*^*/
+ |}
+ """
+ }
+ }
+
+ @Test
+ def existing_highlighted_positions_are_shifted() {
+ runTest(AddText("\n\n")) {
+ """
+ |class A {
+ | /*^*/
+ | def foo(): Unit = {}
+ |}
+ """
+ }
+ }
+
+ @Test
+ def new_highlighted_positions_are_reported() {
+ runTest(AddText("val bar = 2", List(new Position(17, 3, SymbolTypes.TemplateVal, deprecated = false)))) {
+ """
+ |class A {
+ | /*^*/
+ | def foo(): Unit = {}
+ |}
+ """
+ }
+ }
+
+ @Test
+ def highlighted_positions_in_the_document_are_removed_on_deletion() {
+ runTest(RemoveText) {
+ """
+ |class A {
+ | /*^*/
+ | def foo(): Unit = {}
+ | /*^*/
+ |}
+ """
+ }
+ }
+
+ @Test
+ def highlighted_positions_around_deletion_action_are_correct() {
+ runTest(RemoveText) {
+ """
+ |class A {
+ |/*^*/
+ | if (true)
+ |/*^*/ println("abc")
+ |}
+ """
+ }
+ }
+
+ @Test
+ def correctly_compute_damagedRegion_whenDeletingText() {
+ runTest(RemoveText) {
+ """
+ |object A {
+ | /*^*/def f = 0
+ | /*^*/class C
+ |}
+ """
+ }
+ }
+}
+
+object SemanticHighlightingPositionsTest {
+
+ class TextPresentationStub(override val sourceViewer: ISourceViewer) extends TextPresentationHighlighter {
+ @volatile private var reconciler: Job = _
+ @volatile var positionsTracker: PositionsTracker = _
+ @volatile var damagedRegion: IRegion = _
+
+ override def initialize(reconciler: Job, positionsTracker: PositionsTracker): Unit = {
+ this.reconciler = reconciler
+ this.positionsTracker = positionsTracker
+ reconcileNow()
+ }
+
+ override def dispose(): Unit = ()
+
+ def reconcileNow(): Unit = {
+ damagedRegion = EmptyRegion
+ // `Job.run` is protected, but when we subclass it in `Presenter$Reconciler` we make the `run` method public, which is really useful for running the reconciler within the same thread of the test.
+ reconciler.asInstanceOf[{ def run(monitor: IProgressMonitor): IStatus }].run(new NullProgressMonitor)
+ }
+
+ override def updateTextPresentation(damage: IRegion): Unit = damagedRegion = damage
+ }
+
+ object TextPresentationStub {
+ def apply(sourceViewer: ISourceViewer): TextPresentationStub = new TextPresentationStub(sourceViewer)
+ }
+}
View
30 ...rc/scala/tools/eclipse/semantichighlighting/classifier/AbstractSymbolClassifierTest.scala
@@ -8,9 +8,13 @@ import scala.tools.eclipse.ScalaProject
import scala.tools.eclipse.util.EclipseUtils
import scala.tools.eclipse.ScalaPlugin
import org.junit.After
+import org.eclipse.core.runtime.NullProgressMonitor
+import org.eclipse.jface.text.IRegion
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes.SymbolType
class AbstractSymbolClassifierTest {
-
+ import AbstractSymbolClassifierTest._
+
protected val simulator = new EclipseUserSimulator
private var project: ScalaProject = _
@@ -28,15 +32,15 @@ class AbstractSymbolClassifierTest {
}
protected def checkSymbolClassification(source: String, locationTemplate: String, regionTagToSymbolType: Map[String, SymbolType]) {
- val expectedRegionToSymbolNameMap: Map[Region, String] = RegionParser.getRegions(locationTemplate)
- val expectedRegionsAndSymbols: List[(Region, SymbolType)] =
+ val expectedRegionToSymbolNameMap: Map[IRegion, String] = RegionParser.getRegions(locationTemplate)
+ val expectedRegionsAndSymbols: List[(IRegion, SymbolType)] =
expectedRegionToSymbolNameMap.mapValues(regionTagToSymbolType).toList sortBy regionOffset
- val actualRegionsAndSymbols: List[(Region, SymbolType)] =
+ val actualRegionsAndSymbols: List[(IRegion, SymbolType)] =
classifySymbols(source, expectedRegionToSymbolNameMap.keySet) sortBy regionOffset
if (expectedRegionsAndSymbols != actualRegionsAndSymbols) {
val sb = new StringBuffer
- def displayRegions(regionToSymbolInfoMap: List[(Region, SymbolType)]) = {
+ def displayRegions(regionToSymbolInfoMap: List[(IRegion, SymbolType)]) = {
regionToSymbolInfoMap.toList.sortBy(regionOffset) map {
case (region, symbolType) =>
" " + region + " '" + region.of(source) + "' " + symbolType
@@ -51,7 +55,7 @@ class AbstractSymbolClassifierTest {
}
}
- private def classifySymbols(source: String, restrictToRegions: Set[Region]): List[(Region, SymbolType)] = {
+ private def classifySymbols(source: String, restrictToRegions: Set[IRegion]): List[(IRegion, SymbolType)] = {
val sourceFile = new BatchSourceFile("", source)
project.withPresentationCompiler { compiler =>
// first load the source
@@ -60,7 +64,7 @@ class AbstractSymbolClassifierTest {
dummy.get
// then run classification
- val symbolInfos: List[SymbolInfo] = SymbolClassifier.classifySymbols(sourceFile, compiler, useSyntacticHints = true)
+ val symbolInfos: List[SymbolInfo] = new SymbolClassification(sourceFile, compiler, useSyntacticHints = true).classifySymbols(new NullProgressMonitor)
for {
SymbolInfo(symbolType, regions, deprecated) <- symbolInfos
region <- regions
@@ -69,6 +73,16 @@ class AbstractSymbolClassifierTest {
}(orElse = Nil)
}.distinct sortBy regionOffset
- private def regionOffset(regionAndSymbolType: (Region, SymbolType)) = regionAndSymbolType._1.offset
+ private def regionOffset(regionAndSymbolType: (IRegion, SymbolType)) = regionAndSymbolType._1.getOffset
+}
+object AbstractSymbolClassifierTest {
+ private class RegionOps(region: IRegion) {
+ def intersects(other: IRegion): Boolean =
+ !(other.getOffset >= region.getOffset + region.getLength || other.getOffset + other.getLength - 1 < region.getOffset)
+
+ def of(s: String): String = s.slice(region.getOffset, region.getOffset + region.getLength)
+ }
+
+ private implicit def region2regionOps(region: IRegion): RegionOps = new RegionOps(region)
}
View
9 ...sdt.core.tests/src/scala/tools/eclipse/semantichighlighting/classifier/RegionParser.scala
@@ -1,5 +1,8 @@
package scala.tools.eclipse.semantichighlighting.classifier
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.Region
+
object RegionParser {
/**
@@ -23,11 +26,11 @@ object RegionParser {
* are handled as there were no escape sign. This means that the String `$a\$b$`
* is treated as `$a$b$`.
*/
- def getRegions(text: String, delimiter: Char = '$'): Map[Region, String] = {
+ def getRegions(text: String, delimiter: Char = '$'): Map[IRegion, String] = {
val sb = new StringBuilder
var curPos = 0
var offset = 0
- var regions = Map.empty[Region, String]
+ var regions = Map.empty[IRegion, String]
while (curPos < text.length) {
text.charAt(curPos) match {
@@ -43,7 +46,7 @@ object RegionParser {
val start = curPos-sb.length
val label = sb.substring(1, sb.length).trim
sb.clear()
- regions += (Region(start-offset, curPos-start+1) -> label)
+ regions += (new Region(start-offset, curPos-start+1) -> label)
}
case _ =>
if (!sb.isEmpty)
View
11 org.scala-ide.sdt.core.tests/src/scala/tools/eclipse/util/CurrentThread.scala
@@ -0,0 +1,11 @@
+package scala.tools.eclipse.util
+
+import scala.tools.eclipse.ui.UIThread
+
+object CurrentThread extends UIThread {
+ override def asyncExec(f: => Unit): Unit = syncExec(f)
+
+ override def syncExec(f: => Unit): Unit = f
+
+ override def get: Thread = Thread.currentThread()
+}
View
2 org.scala-ide.sdt.core/META-INF/MANIFEST.MF
@@ -79,6 +79,7 @@ Export-Package:
scala.tools.eclipse.interpreter,
scala.tools.eclipse.javaelements,
scala.tools.eclipse.jcompiler,
+ scala.tools.eclipse.jface.text,
scala.tools.eclipse.launching,
scala.tools.eclipse.lexical,
scala.tools.eclipse.logging,
@@ -102,6 +103,7 @@ Export-Package:
scala.tools.eclipse.semantichighlighting,
scala.tools.eclipse.semantichighlighting.classifier,
scala.tools.eclipse.semantichighlighting.implicits,
+ scala.tools.eclipse.semantichighlighting.ui,
scala.tools.eclipse.semicolon,
scala.tools.eclipse.sourcefileprovider,
scala.tools.eclipse.templates,
View
2 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPlugin.scala
@@ -40,6 +40,8 @@ import scala.tools.nsc.Settings
import scala.tools.eclipse.ui.PartAdapter
object ScalaPlugin {
+ final val IssueTracker = "https://www.assembla.com/spaces/scala-ide/support/tickets"
+
private final val HeadlessTest = "sdtcore.headless"
private final val NoTimeouts = "sdtcore.notimeouts"
View
32 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaPresentationReconciler.scala
@@ -0,0 +1,32 @@
+package scala.tools.eclipse
+
+import org.eclipse.jdt.internal.ui.text.JavaPresentationReconciler
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.TextPresentation
+
+/** It is really sad that we need to inherit from `JavaPresentationReconciler` (and not simply `PresentationReconciler`).
+ * The reason is that the JDT `ClassFileEditor` (which is re-used by the Scala IDE, for instance when opening a type
+ * that comes from a Scala jar), during initialization makes a call to `JavaEditor.installSemanticHighlighting`, which
+ * expects that the presentation reconciler returned by `ScalaSourceViewerConfiguration.getPresentationReconciler` is
+ * a subtype of `JavaPresentationReconciler` (the assumption is made real via an unsafe cast).
+ *
+ * There is an additional interesting fact. To prevent the `JavaEditor` to install semantic highlighting on Scala sources
+ * an aspect pointcut was defined in `ScalaEditorPreferencesAspect`. It turns out this is not needed, because in the
+ * `ScalaSourceFileEditor` (which extends the `JavaEditor`) we can noop the call to `JavaEditor.installSemanticHighlighting`.
+ * Unfortunately, the same cannot be done for the `ClassFileEditor, because we don't currently provide a custom implementation.
+ */
+class ScalaPresentationReconciler extends JavaPresentationReconciler {
+
+ @volatile private var lastDocument: IDocument = null
+
+ override def createRepairDescription(damage: IRegion, document: IDocument): TextPresentation = {
+ if (document != lastDocument) {
+ setDocumentToDamagers(document)
+ setDocumentToRepairers(document)
+ lastDocument = document
+ }
+ createPresentation(damage, document)
+ }
+}
View
115 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaSourceFileEditor.scala
@@ -7,19 +7,27 @@
package scala.tools.eclipse
import java.util.ResourceBundle
+
import scala.Option.option2Iterable
+import scala.collection.mutable.ArrayBuffer
+import scala.collection.mutable.SynchronizedBuffer
import scala.tools.eclipse.javaelements.ScalaCompilationUnit
import scala.tools.eclipse.markoccurrences.Occurrences
import scala.tools.eclipse.markoccurrences.ScalaOccurrencesFinder
-import scala.tools.eclipse.semantichighlighting.SemanticHighlightingAnnotations
+import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
+import scala.tools.eclipse.semantichighlighting.Presenter
+import scala.tools.eclipse.semantichighlighting.ui.TextPresentationEditorHighlighter
import scala.tools.eclipse.semicolon.ShowInferredSemicolonsAction
import scala.tools.eclipse.semicolon.ShowInferredSemicolonsBundle
+import scala.tools.eclipse.ui.DisplayThread
import scala.tools.eclipse.ui.SurroundSelectionStrategy
+import scala.tools.eclipse.util.EclipseUtils
import scala.tools.eclipse.util.EditorUtils
import scala.tools.eclipse.util.RichAnnotationModel.annotationModel2RichAnnotationModel
import scala.tools.eclipse.util.SWTUtils
import scala.tools.eclipse.util.SWTUtils.fnToPropertyChangeListener
import scala.tools.eclipse.util.Utils
+
import org.eclipse.core.runtime.IProgressMonitor
import org.eclipse.core.runtime.Status
import org.eclipse.core.runtime.jobs.Job
@@ -29,8 +37,9 @@ import org.eclipse.jdt.internal.ui.javaeditor.JavaSourceViewer
import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.SelectionHistory
import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectHistoryAction
import org.eclipse.jdt.internal.ui.javaeditor.selectionactions.StructureSelectionAction
+import org.eclipse.jdt.internal.ui.text.java.IJavaReconcilingListener
+import org.eclipse.jdt.ui.PreferenceConstants
import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds
-import org.eclipse.jdt.ui.text.JavaSourceViewerConfiguration
import org.eclipse.jface.action.Action
import org.eclipse.jface.action.IContributionItem
import org.eclipse.jface.action.MenuManager
@@ -45,12 +54,7 @@ import org.eclipse.jface.text.ITextViewerExtension
import org.eclipse.jface.text.Position
import org.eclipse.jface.text.information.InformationPresenter
import org.eclipse.jface.text.source.Annotation
-import org.eclipse.jface.text.source.AnnotationPainter
-import org.eclipse.jface.text.source.IAnnotationAccess
import org.eclipse.jface.text.source.IAnnotationModel
-import org.eclipse.jface.text.source.IOverviewRuler
-import org.eclipse.jface.text.source.ISharedTextColors
-import org.eclipse.jface.text.source.ISourceViewer
import org.eclipse.jface.text.source.SourceViewerConfiguration
import org.eclipse.jface.util.IPropertyChangeListener
import org.eclipse.jface.util.PropertyChangeEvent
@@ -62,10 +66,8 @@ import org.eclipse.ui.texteditor.IAbstractTextEditorHelpContextIds
import org.eclipse.ui.texteditor.ITextEditorActionConstants
import org.eclipse.ui.texteditor.IUpdate
import org.eclipse.ui.texteditor.IWorkbenchActionDefinitionIds
-import org.eclipse.ui.texteditor.SourceViewerDecorationSupport
import org.eclipse.ui.texteditor.TextOperationAction
-import scala.tools.eclipse.util.EclipseUtils
-import org.eclipse.jdt.ui.PreferenceConstants
+
import scala.tools.eclipse.properties.EditorPreferencePage
@@ -76,6 +78,12 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
private var occurrencesFinder: ScalaOccurrencesFinder = _
private var occurencesFinderInstalled = false
private val preferenceListener: IPropertyChangeListener = handlePreferenceStoreChanged _
+ private val reconcilingListeners: ReconcilingListeners = new ScalaSourceFileEditor.ReconcilingListeners
+
+ /**@note Current implementation assumes that all accesses to this member should be confined to the UI Thread */
+ private var semanticHighlightingPresenter: semantichighlighting.Presenter = _
+ private def semanticHighlightingPreferences = semantichighlighting.Preferences(scalaPrefStore)
+
private lazy val selectionListener = new ISelectionListener() {
def selectionChanged(part: IWorkbenchPart, selection: ISelection) {
selection match {
@@ -144,11 +152,29 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
setAction("OpenEditor", openAction)
}
+
+ private def isScalaSemanticHighlightingEnabled(): Boolean = semanticHighlightingPreferences.isEnabled()
+
+ protected def installScalaSemanticHighlighting(forceRefresh: Boolean): Unit = {
+ if(semanticHighlightingPresenter == null) {
+ val presentationHighlighter = TextPresentationEditorHighlighter(this, semanticHighlightingPreferences)
+ semanticHighlightingPresenter = new Presenter(this, presentationHighlighter, semanticHighlightingPreferences, DisplayThread)
+ semanticHighlightingPresenter.initialize(forceRefresh)
+ }
+ }
+
+ protected def uninstallScalaSemanticHighlighting(removesHighlights: Boolean): Unit = {
+ if(semanticHighlightingPresenter != null) {
+ semanticHighlightingPresenter.dispose(removesHighlights)
+ semanticHighlightingPresenter = null
+ }
+ }
+
override protected def initializeKeyBindingScopes() {
setKeyBindingScopes(Array(SCALA_EDITOR_SCOPE))
}
- override def createJavaSourceViewerConfiguration: JavaSourceViewerConfiguration =
+ override def createJavaSourceViewerConfiguration: ScalaSourceViewerConfiguration =
new ScalaSourceViewerConfiguration(javaPrefStore, scalaPrefStore, this)
override def setSourceViewerConfiguration(configuration: SourceViewerConfiguration) {
@@ -158,7 +184,8 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
case _ => new ScalaSourceViewerConfiguration(javaPrefStore, scalaPrefStore, this)
})
}
- private[eclipse] def sourceViewer = getSourceViewer.asInstanceOf[JavaSourceViewer]
+
+ private[eclipse] def sourceViewer: JavaSourceViewer = getSourceViewer.asInstanceOf[JavaSourceViewer]
override def updateOccurrenceAnnotations(selection: ITextSelection, astRoot: CompilationUnit): Unit = {
askForOccurrencesUpdate(selection)
@@ -252,6 +279,7 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
override def dispose() {
super.dispose()
scalaPrefStore.removePropertyChangeListener(preferenceListener)
+ uninstallScalaSemanticHighlighting(removesHighlights = false)
}
/** Return the `InformationPresenter` used to display the type of the selected expression.*/
@@ -311,6 +339,10 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
super.createPartControl(parent)
occurrencesFinder = new ScalaOccurrencesFinder(getInteractiveCompilationUnit)
refactoring.RefactoringMenu.fillQuickMenu(this)
+
+ if(isScalaSemanticHighlightingEnabled())
+ installScalaSemanticHighlighting(forceRefresh = false) // relies on the Java reconciler to refresh the highlights
+
getSourceViewer match {
case sourceViewer: ITextViewerExtension =>
sourceViewer.prependVerifyKeyListener(new SurroundSelectionStrategy(getSourceViewer))
@@ -318,10 +350,11 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
}
}
- override def handlePreferenceStoreChanged(event: PropertyChangeEvent) =
+ override def handlePreferenceStoreChanged(event: PropertyChangeEvent) = {
event.getProperty match {
case ShowInferredSemicolonsAction.PREFERENCE_KEY =>
getAction(ShowInferredSemicolonsAction.ACTION_ID).asInstanceOf[IUpdate].update()
+
case PreferenceConstants.EDITOR_MARK_OCCURRENCES =>
// swallow the event. We don't want 'mark occurrences' to be linked to the Java editor preference
case EditorPreferencePage.P_ENABLE_MARK_OCCURRENCES =>
@@ -331,6 +364,13 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
case _ =>
uninstallOccurrencesFinder()
}
+
+ case ScalaSyntaxClasses.ENABLE_SEMANTIC_HIGHLIGHTING =>
+ // This preference can be changed only via the preference dialog, hence the below block
+ // is ensured to be always run within the UI Thread. Check the JavaDoc of `handlePreferenceStoreChanged`
+ if(isScalaSemanticHighlightingEnabled) installScalaSemanticHighlighting(forceRefresh = true)
+ else uninstallScalaSemanticHighlighting(removesHighlights = true)
+
case _ =>
if (affectsTextPresentation(event)) {
// those events will trigger a UI change
@@ -339,28 +379,29 @@ class ScalaSourceFileEditor extends CompilationUnitEditor with ScalaEditor { sel
super.handlePreferenceStoreChanged(event)
}
}
+ }
override def isMarkingOccurrences =
scalaPrefStore.getBoolean(EditorPreferencePage.P_ENABLE_MARK_OCCURRENCES)
- override def configureSourceViewerDecorationSupport(support: SourceViewerDecorationSupport) {
- super.configureSourceViewerDecorationSupport(support)
- SemanticHighlightingAnnotations.addAnnotationPreferences(support)
+ override def getInteractiveCompilationUnit(): InteractiveCompilationUnit = {
+ // getInputJavaElement always returns the right value
+ getInputJavaElement().asInstanceOf[InteractiveCompilationUnit]
}
- override protected def getSourceViewerDecorationSupport(viewer: ISourceViewer): SourceViewerDecorationSupport = {
- if (fSourceViewerDecorationSupport == null) {
- fSourceViewerDecorationSupport = new ScalaSourceViewerDecorationSupport(viewer, getOverviewRuler, getAnnotationAccess, getSharedColors)
- configureSourceViewerDecorationSupport(fSourceViewerDecorationSupport)
- }
- fSourceViewerDecorationSupport
+ override def aboutToBeReconciled(): Unit = {
+ super.aboutToBeReconciled()
+ reconcilingListeners.aboutToBeReconciled()
}
-
- override def getInteractiveCompilationUnit(): InteractiveCompilationUnit = {
- // getInputJavaElement always returns the right value
- getInputJavaElement().asInstanceOf[InteractiveCompilationUnit]
+ override def reconciled(ast: CompilationUnit, forced: Boolean, progressMonitor: IProgressMonitor): Unit = {
+ super.reconciled(ast, forced, progressMonitor)
+ reconcilingListeners.reconciled(ast, forced, progressMonitor)
}
+
+ def addReconcilingListener(listener: IJavaReconcilingListener): Unit = reconcilingListeners.addReconcileListener(listener)
+
+ def removeReconcilingListener(listener: IJavaReconcilingListener): Unit = reconcilingListeners.removeReconcileListener(listener)
}
object ScalaSourceFileEditor {
@@ -375,14 +416,22 @@ object ScalaSourceFileEditor {
override def doCreateInformationControl(shell: Shell) =
new DefaultInformationControl(shell, true)
}
+
+ /** A thread-safe object for keeping track of Java reconciling listeners.*/
+ private class ReconcilingListeners extends IJavaReconcilingListener {
+ private val reconcilingListeners = new ArrayBuffer[IJavaReconcilingListener] with SynchronizedBuffer[IJavaReconcilingListener]
+
+ /** Return a snapshot of the currently registered `reconcilingListeners`. This is useful to avoid concurrency hazards when iterating on the `reconcilingListeners`. */
+ private def currentReconcilingListeners: List[IJavaReconcilingListener] = reconcilingListeners.toList
- private class ScalaSourceViewerDecorationSupport(viewer: ISourceViewer, overviewRuler: IOverviewRuler, annotationAccess: IAnnotationAccess, sharedTextColors: ISharedTextColors)
- extends SourceViewerDecorationSupport(viewer, overviewRuler, annotationAccess, sharedTextColors) {
+ override def aboutToBeReconciled(): Unit =
+ for(listener <- currentReconcilingListeners) listener.aboutToBeReconciled()
- override protected def createAnnotationPainter(): AnnotationPainter = {
- val annotationPainter = super.createAnnotationPainter
- SemanticHighlightingAnnotations.addTextStyleStrategies(annotationPainter)
- annotationPainter
- }
+ override def reconciled(ast: CompilationUnit, forced: Boolean, progressMonitor: IProgressMonitor): Unit =
+ for(listener <- currentReconcilingListeners) listener.reconciled(ast, forced, progressMonitor)
+
+ def addReconcileListener(listener: IJavaReconcilingListener): Unit = reconcilingListeners += listener
+
+ def removeReconcileListener(listener: IJavaReconcilingListener): Unit = reconcilingListeners -= listener
}
}
View
21 org.scala-ide.sdt.core/src/scala/tools/eclipse/ScalaSourceViewerConfiguration.scala
@@ -77,16 +77,17 @@ class ScalaSourceViewerConfiguration(store: IPreferenceStore, scalaPreferenceSto
)
}
- override def getPresentationReconciler(sv: ISourceViewer) = {
- val reconciler = super.getPresentationReconciler(sv).asInstanceOf[PresentationReconciler]
-
- for ((partitionType, tokenScanner) <- codeHighlightingScanners) {
- val dr = new DefaultDamagerRepairer(tokenScanner)
- reconciler.setDamager(dr, partitionType)
- reconciler.setRepairer(dr, partitionType)
- }
- reconciler
- }
+ override def getPresentationReconciler(sourceViewer: ISourceViewer): ScalaPresentationReconciler = {
+ val reconciler = new ScalaPresentationReconciler()
+ reconciler.setDocumentPartitioning(getConfiguredDocumentPartitioning(sourceViewer))
+
+ for ((partitionType, tokenScanner) <- codeHighlightingScanners) {
+ val dr = new DefaultDamagerRepairer(tokenScanner)
+ reconciler.setDamager(dr, partitionType)
+ reconciler.setRepairer(dr, partitionType)
+ }
+ reconciler
+ }
override def getTextHover(sv: ISourceViewer, contentType: String, stateMask: Int) = {
// new ScalaHover(getCodeAssist _)
View
6 org.scala-ide.sdt.core/src/scala/tools/eclipse/diagnostic/ReportBugDialog.scala
@@ -15,9 +15,7 @@ import scala.tools.eclipse.ui.OpenExternalFile
class ReportBugDialog(shell: Shell) extends Dialog(shell) {
-
- val SDT_TRACKER_URL = "https://www.assembla.com/spaces/scala-ide/support/tickets"
-
+
protected override def isResizable = true
protected override def createDialogArea(parent: Composite): Control = {
@@ -50,7 +48,7 @@ class ReportBugDialog(shell: Shell) extends Dialog(shell) {
logFileLink.addListener(SWT.Selection, OpenExternalFile(LogManager.logFile))
val reportBugLink = new Link(group2, SWT.NONE)
- reportBugLink.setText("and <a href=\"" + SDT_TRACKER_URL + "\">report a bug</a>.")
+ reportBugLink.setText("and <a href=\"" + ScalaPlugin.IssueTracker + "\">report a bug</a>.")
reportBugLink.addListener(SWT.Selection, DiagnosticDialog.linkListener)
control
View
8 org.scala-ide.sdt.core/src/scala/tools/eclipse/jface/text/EmptyRegion.scala
@@ -0,0 +1,8 @@
+package scala.tools.eclipse.jface.text
+
+import org.eclipse.jface.text.IRegion
+
+object EmptyRegion extends IRegion {
+ override def getOffset: Int = 0
+ override def getLength: Int = 0
+}
View
16 org.scala-ide.sdt.core/src/scala/tools/eclipse/jface/text/Region.scala
@@ -0,0 +1,16 @@
+package scala.tools.eclipse.jface.text
+
+import org.eclipse.jface.text.IRegion
+
+//TODO: Transform this into an implicit value class as soon as we drop 2.9 support!
+class RegionOps(region: IRegion) {
+ def of(s: Array[Char]): String = s.slice(region.getOffset, region.getOffset + region.getLength).mkString
+}
+
+object RegionOps {
+ implicit def region2regionOps(region: IRegion): RegionOps = new RegionOps(region)
+
+ // TODO: Remove this method as soon as `RegionOps` is transformed in an implicit value class.
+ // https://github.com/scala-ide/scala-ide/pull/292/files#r2873974 gives some context on why we do it this way for the moment.
+ def of(region: IRegion, s: Array[Char]): String = s.slice(region.getOffset, region.getOffset + region.getLength).mkString
+}
View
81 ...core/src/scala/tools/eclipse/properties/syntaxcolouring/ColourPreferenceInitializer.scala
@@ -1,7 +1,6 @@
package scala.tools.eclipse.properties.syntaxcolouring
import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses._
-import scala.tools.eclipse.semantichighlighting.SemanticHighlightingAnnotations
import scala.tools.eclipse.util.SWTUtils.fnToPropertyChangeListener
import scala.tools.eclipse.ScalaPlugin
@@ -28,55 +27,8 @@ class ColourPreferenceInitializer extends AbstractPreferenceInitializer {
scalaPrefStore.setDefault(STRIKETHROUGH_DEPRECATED, true)
setDefaultsForSyntaxClasses(scalaPrefStore)
- initializePreferencesIndirectly(scalaPrefStore)
}
- /** This closure is needed in order to hide the reference to UI code behind an
- * anonymous class. In headless tests, when there is no Xserver, it is imperative
- * that no UI code is initialized. Without this indirection, the JVM would attempt
- * to load `AbstractUIPlugin`, which is the owner of `getPreferenceStore` when
- * preparing this class (even when this code is not executed). The reference exists
- * in bytecode:
- *
- * invokevirtual #78; //Method org/eclipse/ui/plugin/AbstractUIPlugin.getPreferenceStore:
- *
- * Contrary to what we believe, this is enough to trigger initialization of
- * `AbstractUIPlugin`, regardless whether this is ever executed.
- *
- * The JVM will not attempt to load classes indirectly, until they are needed
- * for execution, so the solution was to hide the call behind a closure.
- *
- * TODO: Remove once we have a UI bundle, and a clear separation between UI and core.
- *
- */
- lazy val initializePreferencesIndirectly = (scalaPrefStore: IPreferenceStore) => {
- val javaPrefStore = JavaPlugin.getDefault.getPreferenceStore
- SemanticHighlightingAnnotations.initAnnotationPreferences(javaPrefStore)
-
- mirrorColourPreferencesIntoJavaPreferenceStore(scalaPrefStore, javaPrefStore)
- }
-
- private def setDefaultsForSyntaxClass(
- syntaxClass: ScalaSyntaxClass,
- foregroundRGB: RGB,
- enabled: Boolean = true,
- backgroundRGBOpt: Option[RGB] = None,
- bold: Boolean = false,
- italic: Boolean = false,
- strikethrough: Boolean = false,
- underline: Boolean = false)(implicit scalaPrefStore: IPreferenceStore) =
- {
- lazy val WHITE = new RGB(255, 255, 255)
- scalaPrefStore.setDefault(syntaxClass.enabledKey, enabled)
- scalaPrefStore.setDefault(syntaxClass.foregroundColourKey, StringConverter.asString(foregroundRGB))
- val defaultBackgroundColour = StringConverter.asString(backgroundRGBOpt getOrElse WHITE)
- scalaPrefStore.setDefault(syntaxClass.backgroundColourKey, defaultBackgroundColour)
- scalaPrefStore.setDefault(syntaxClass.backgroundColourEnabledKey, backgroundRGBOpt.isDefined)
- scalaPrefStore.setDefault(syntaxClass.boldKey, bold)
- scalaPrefStore.setDefault(syntaxClass.italicKey, italic)
- scalaPrefStore.setDefault(syntaxClass.underlineKey, underline)
- }
-
private def setDefaultsForSyntaxClasses(implicit scalaPrefStore: IPreferenceStore) {
// Scala syntactic
setDefaultsForSyntaxClass(SINGLE_LINE_COMMENT, new RGB(63, 127, 95))
@@ -126,20 +78,27 @@ class ColourPreferenceInitializer extends AbstractPreferenceInitializer {
setDefaultsForSyntaxClass(PACKAGE, new RGB(0, 110, 4), enabled = false)
setDefaultsForSyntaxClass(TYPE, new RGB(50, 147, 153), italic = true, enabled = false)
setDefaultsForSyntaxClass(TYPE_PARAMETER, new RGB(23, 0, 129), underline = true, enabled = false)
- }
-
- // Mirror across the colour preferences into the Java preference store so that they can be read by the annotation
- // mechanism.
- private def mirrorColourPreferencesIntoJavaPreferenceStore(scalaPrefStore: IPreferenceStore, javaPrefStore: IPreferenceStore) {
- for (key <- ALL_KEYS)
- javaPrefStore.setDefault(key, scalaPrefStore getDefaultString key)
+ }
- scalaPrefStore.addPropertyChangeListener { event: PropertyChangeEvent =>
- val key = event.getProperty
- if (ALL_KEYS contains key)
- javaPrefStore.setValue(key, event.getNewValue.toString)
+ private def setDefaultsForSyntaxClass(
+ syntaxClass: ScalaSyntaxClass,
+ foregroundRGB: RGB,
+ enabled: Boolean = true,
+ backgroundRGBOpt: Option[RGB] = None,
+ bold: Boolean = false,
+ italic: Boolean = false,
+ strikethrough: Boolean = false,
+ underline: Boolean = false)(implicit scalaPrefStore: IPreferenceStore) =
+ {
+ lazy val WHITE = new RGB(255, 255, 255)
+ scalaPrefStore.setDefault(syntaxClass.enabledKey, enabled)
+ scalaPrefStore.setDefault(syntaxClass.foregroundColourKey, StringConverter.asString(foregroundRGB))
+ val defaultBackgroundColour = StringConverter.asString(backgroundRGBOpt getOrElse WHITE)
+ scalaPrefStore.setDefault(syntaxClass.backgroundColourKey, defaultBackgroundColour)
+ scalaPrefStore.setDefault(syntaxClass.backgroundColourEnabledKey, backgroundRGBOpt.isDefined)
+ scalaPrefStore.setDefault(syntaxClass.boldKey, bold)
+ scalaPrefStore.setDefault(syntaxClass.italicKey, italic)
+ scalaPrefStore.setDefault(syntaxClass.underlineKey, underline)
}
- }
-
}
View
19 ...la-ide.sdt.core/src/scala/tools/eclipse/properties/syntaxcolouring/ScalaSyntaxClass.scala
@@ -12,6 +12,7 @@ import org.eclipse.jface.text._
import org.eclipse.jface.preference.PreferenceConverter
import org.eclipse.jface.preference.IPreferenceStore
import org.eclipse.jdt.internal.ui.JavaPlugin
+import scala.tools.eclipse.util.SWTUtils
case class ScalaSyntaxClass(displayName: String, baseName: String, canBeDisabled: Boolean = false) {
@@ -53,14 +54,22 @@ case class ScalaSyntaxClass(displayName: String, baseName: String, canBeDisabled
def getStyleInfo(preferenceStore: IPreferenceStore): StyleInfo = {
val colourManager = JavaPlugin.getDefault.getJavaTextTools.getColorManager
- val backgroundOpt =
+ val foregroundColorPref = preferenceStore getColor foregroundColourKey
+ var foregroundColor: Color = null
+ var backgroundOpt: Option[Color] = None
+
+ // FIXME: Blocking on the UI thread is bad. I'm pretty sure we can avoid this, but some refactoring is in needed. Basically, the
+ // different SyntaxClasses should be created by the editor right after checking if semantic highlighting is enabled, that
+ // way you know you are running inside the UI Thread. Re #1001489.
+ SWTUtils.syncExec {
+ foregroundColor = colourManager.getColor(foregroundColorPref)
if (preferenceStore getBoolean backgroundColourEnabledKey)
- Some(colourManager.getColor(preferenceStore getColor backgroundColourKey))
- else
- None
+ backgroundOpt = Option(colourManager.getColor(preferenceStore getColor backgroundColourKey))
+ }
+
StyleInfo(
preferenceStore getBoolean enabledKey,
- colourManager.getColor(preferenceStore getColor foregroundColourKey),
+ foregroundColor,
backgroundOpt,
preferenceStore getBoolean boldKey,
preferenceStore getBoolean italicKey,
View
8 ...-ide.sdt.core/src/scala/tools/eclipse/properties/syntaxcolouring/ScalaSyntaxClasses.scala
@@ -79,14 +79,6 @@ object ScalaSyntaxClasses {
val ITALIC_SUFFIX = ".italic"
val UNDERLINE_SUFFIX = ".underline"
- val ALL_SUFFIXES = List(ENABLED_SUFFIX, FOREGROUND_COLOUR_SUFFIX, BACKGROUND_COLOUR_SUFFIX,
- BACKGROUND_COLOUR_ENABLED_SUFFIX, BOLD_SUFFIX, ITALIC_SUFFIX, UNDERLINE_SUFFIX)
-
- val ALL_KEYS = (for {
- syntaxClass <- ALL_SYNTAX_CLASSES
- suffix <- ALL_SUFFIXES
- } yield syntaxClass.baseName + suffix).toSet
-
val ENABLE_SEMANTIC_HIGHLIGHTING = "syntaxColouring.semantic.enabled"
val USE_SYNTACTIC_HINTS = "syntaxColouring.semantic.useSyntacticHints"
View
13 ...ala/tools/eclipse/semantichighlighting/AnnotationPreferenceWithForegroundColourStyle.java
@@ -1,13 +0,0 @@
-package scala.tools.eclipse.semantichighlighting;
-
-import org.eclipse.ui.texteditor.AnnotationPreference;
-
-// Written in Java so we can access the protected static field TEXT_STYLE_PREFERENCE_KEY
-public class AnnotationPreferenceWithForegroundColourStyle extends AnnotationPreference {
-
- public AnnotationPreferenceWithForegroundColourStyle(Object annotationType, String textKey, String styleKey) {
- super(annotationType, "not-used", textKey, "not-used", 0);
- setValue(TEXT_STYLE_PREFERENCE_KEY, styleKey);
- }
-
-}
View
63 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Position.scala
@@ -0,0 +1,63 @@
+package scala.tools.eclipse.semantichighlighting
+
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolInfo
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes
+
+import org.eclipse.jface.text.{ Position => TextPosition }
+
+/** Represents a semantically colored position in the editor.
+ *
+ * @note This class is thread-safe.
+ */
+class Position (
+ offset: Int,
+ length: Int,
+ val kind: SymbolTypes.SymbolType,
+ val deprecated: Boolean) extends TextPosition(offset, length) {
+
+ /** Lock used to protect concurrent access to `this` instance.*/
+ private val lock: AnyRef = new Object
+
+ override def hashCode(): Int = lock.synchronized { super.hashCode() }
+
+ override def delete(): Unit = lock.synchronized { super.delete() }
+
+ override def undelete(): Unit = lock.synchronized { super.undelete() }
+
+ override def equals(that: Any): Boolean = that match {
+ // This implementation of `equals` is NOT symmetric.
+ case that: Position =>
+ lock.synchronized { super.equals(that) && kind == that.kind && deprecated == that.deprecated && isDeleted() == that.isDeleted() }
+ case _ => false
+ }
+
+ override def getLength(): Int = lock.synchronized { super.getLength() }
+
+ override def getOffset(): Int = lock.synchronized { super.getOffset() }
+
+ override def includes(index: Int): Boolean = lock.synchronized { super.includes(index) }
+
+ override def overlapsWith(rangeOffset: Int, rangeLength: Int): Boolean = lock.synchronized { super.overlapsWith(rangeOffset, rangeLength) }
+
+ override def isDeleted(): Boolean = lock.synchronized { super.isDeleted() }
+
+ override def setLength(length: Int): Unit = lock.synchronized { super.setLength(length) }
+
+ override def setOffset(offset: Int): Unit = lock.synchronized { super.setOffset(offset) }
+
+ override def toString(): String = lock.synchronized { super.toString() }
+}
+
+object Position {
+ implicit object ByOffset extends Ordering[Position] {
+ override def compare(x: Position, y: Position): Int = x.getOffset() - y.getOffset()
+ }
+
+ def from(symbolInfos: List[SymbolInfo]): List[Position] = {
+ (for {
+ SymbolInfo(symbolType, regions, isDeprecated) <- symbolInfos
+ region <- regions
+ if region.getLength > 0
+ } yield new Position(region.getOffset, region.getLength, symbolType, isDeprecated))
+ }
+}
View
39 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/PositionsChange.scala
@@ -0,0 +1,39 @@
+package scala.tools.eclipse.semantichighlighting
+
+import scala.annotation.tailrec
+import scala.tools.eclipse.jface.text.EmptyRegion
+
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.Region
+
+case class PositionsChange(toAdd: List[Position], toRemove: List[Position]) {
+
+ private def findMinMaxOffsets(positions: List[Position]): (Int, Int) = {
+ @tailrec
+ def find(positions: List[Position], min: Int, max: Int): (Int, Int) = positions match {
+ case Nil => (min, max)
+ case pos :: rest =>
+ val offset = pos.getOffset
+ find(rest, Math.min(min, offset), Math.max(max, offset + pos.getLength))
+ }
+ find(positions, Int.MaxValue, Int.MinValue)
+ }
+
+ /** Computes the smallest contiguous region that includes all changed positions.
+ * @note Because the held ``Position`` are mutable, the computed affected region
+ * could be different for successive calls.
+ * Hence, don't turn this into a lazy value.
+ *
+ * @return The smallest contiguous region that includes all changed positions.
+ */
+ def affectedRegion(): IRegion = {
+ val (min1, max1) = findMinMaxOffsets(toAdd)
+ val (min2, max2) = findMinMaxOffsets(toRemove)
+
+ val minStart = Math.min(min1, min2)
+ val maxEnd = Math.max(max1, max2)
+
+ if (minStart < maxEnd) new Region(minStart, maxEnd - minStart)
+ else EmptyRegion
+ }
+}
View
182 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/PositionsTracker.scala
@@ -0,0 +1,182 @@
+package scala.tools.eclipse.semantichighlighting
+
+import java.util.Arrays.{ binarySearch, copyOfRange }
+
+import scala.collection.mutable
+import scala.tools.eclipse.ScalaPlugin
+import scala.tools.eclipse.logging.HasLogger
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes
+
+import org.eclipse.jface.text.DocumentEvent
+import org.eclipse.jface.text.IRegion
+
+/** This class keeps track of semantic positions and it's used by the text's presentation to apply semantic highlighting
+ * styles in the editor.
+ *
+ * There are two important facts to understand about this implementation:
+ *
+ * 1) All accesses that mutate the positions held by `this` instance are run inside the UI Thread. The natural consequence
+ * of this is that to get a consistent view of the tracked position your code has to run within the UI Thread.
+ *
+ * 2) Computing the new positions is an expensive operation, particularly for big files. Furthermore, it's not acceptable
+ * to compute the new positions in the UI Thread. The solution is to adopt an ''optimistic concurrency control'' strategy
+ * (@see http://en.wikipedia.org/wiki/Optimistic_concurrency_control).
+ * Basically, the new positions are computed outside of the UI Thread and the new positions can replace the old ones only
+ * and only if no change happen while the new positions where being computed. If a change is detected, the positions are
+ * discarded (this is ok because a new semantic highlighting job will start as soon as the user pause, and the java
+ * reconciler kicks-in).
+ */
+private[semantichighlighting] class PositionsTracker extends HasLogger {
+
+ @volatile private var positions: Array[Position] = Array.empty
+
+ @volatile private var trackedPositionsChanged = false
+
+ def startComputingNewPositions(): Unit = { trackedPositionsChanged = false }
+
+ def isDirty: Boolean = trackedPositionsChanged
+
+ /** Compares the `newPositions` with the current `positions` and return the sequence of positions
+ * that have been added and removed since last reconciliation.
+ *
+ * @param `newPositions` The freshly computed positions.
+ * @return A container holding the added and removed positions since last reconciliation.
+ */
+ def createPositionsChange(newPositions: List[Position]): PositionsChange = {
+ /* Filtering out deleted positions here is important, failing to do so can cause half-colored identifiers.
+ * The reason is that deleted positions should not be considered when computing the damaged region that is
+ * used to invalidate the text presentation. Failing to do so can result in the computed damaged region to
+ * partially remove a keyword's coloring style.
+ */
+ val existingPositions = positions.filterNot(_.isDeleted())
+ val existingPositionsByOffset = existingPositions.groupBy(_.getOffset)
+
+ val positionsToAdd = mutable.ListBuffer.empty[Position]
+ val positionsToRemove = mutable.ListBuffer.empty[Position] ++ existingPositions
+
+ for {
+ newPos <- newPositions
+ offset = newPos.getOffset()
+ } {
+ // sanity check
+ if (newPos.isDeleted()) {
+ logger.error("Encountered position deleted during semantic highlighting. Please report a bug at " + ScalaPlugin.IssueTracker)
+ }
+ else {
+ existingPositionsByOffset.get(offset) match {
+ case None =>
+ // No positions existed at the given offset, hence it's a new position.
+ positionsToAdd += newPos
+
+ case Some(existingPositions) =>
+ for (oldPos <- existingPositions) {
+ if (newPos == oldPos) {
+ // Old position is the same as new one, so no need to remove it.
+ positionsToRemove -= newPos
+ }
+ else positionsToAdd += newPos
+ }
+ }
+ }
+ }
+
+ PositionsChange(positionsToAdd.toList, positionsToRemove.toList)
+ }
+
+ /** @note This method must always be called within the UI Thread. */
+ def reset(): Unit = {
+ trackedPositionsChanged = true
+ positions = Array.empty
+ }
+
+ /** Replace the currently held `positions` with the passed `newPositions`.
+ *
+ * @note `newPositions` are expected to be sorted.
+ * @note This method must always be called within the UI Thread.
+ *
+ * @precondition `positionsChanged` is `false`
+ * @param newPositions The new semantic positions (that will be colored in the editor)
+ */
+ def swapPositions(newPositions: Array[Position]): Unit = {
+ if (isDirty)
+ logger.error("Error while performing semantic highlighting. Attempting to swap posions on a " +
+ "not up-to-date state. Please report a bug at " + ScalaPlugin.IssueTracker)
+ else positions = newPositions
+ }
+
+ /** Return all currently tracked `positions` in the passed `region`.
+ *
+ * @note For performance reasons, deleted positions are not filtered out. Clients of this method are
+ * expected to filter the deleted positions themselves
+ *
+ * @note This method must always be called within the UI Thread.
+ *
+ * @return The sequence of positions that are included in the passed `region`
+ */
+ def positionsInRegion(region: IRegion): Array[Position] = {
+ if (region.getLength() == 0 || positions.length == 0) Array.empty[Position]
+ else {
+ // `positions` are sorted so here we first find the lower and upper index.
+ // This matters for large files, i.e., when `positions` is > 10K
+ def findIndex(position: Position): Int = {
+ /* @see java.util.Arrays.binarySearch documentation.*/
+ val indexOrMirroredInsertionPoint = binarySearch(positions, position, Position.ByOffset)
+ val index = Math.max(-1 - indexOrMirroredInsertionPoint, indexOrMirroredInsertionPoint)
+ Math.min(index, positions.length)
+ }
+
+ val dummyLowerPosition = new Position(region.getOffset, 0, null, false)
+ val lowerIndex = findIndex(dummyLowerPosition)
+
+ val dummyUpperPosition = new Position(region.getOffset + region.getLength, 0, null, false)
+ val upperIndex = findIndex(dummyUpperPosition)
+
+ copyOfRange(positions, lowerIndex, upperIndex)
+ }
+ }
+
+ /** Deletes all positions included in the `event`'s region. Positions that are after the `event`'s
+ * region are shifted.
+ *
+ * @note This method must always be called within the UI Thread.
+ */
+ def updatePositions(event: DocumentEvent): Unit = {
+ val editionOffset = event.getOffset
+ val editionLength = event.getLength
+ val editionEnd = editionOffset + editionLength
+ val newText = event.getText()
+ val newLength = Option(newText) map (_.length) getOrElse 0
+
+ for (position <- positions) {
+ val posOffset = position.getOffset
+ val posEnd = posOffset + position.getLength
+
+ if (editionOffset > posEnd) { /* nothing to do because the current `position` is not affected by the triggered edition change */ }
+ else {
+ trackedPositionsChanged = true
+ if (editionEnd < posOffset) {
+ // edition change occurred *before* the current `position`, which implies that the `position`'s offset needs to be shifted
+ val delta = newLength - editionLength
+ position.setOffset(posOffset + delta)
+ }
+ else {
+ // The edit affected the current `position`, hence let's delete it
+ position.delete()
+ }
+ }
+ }
+ }
+
+ /** Deletes all positions of the passed `kind`.
+ *
+ * @note This method must always be called within the UI Thread.
+ */
+ def deletesPositionsOfType(kind: SymbolTypes.SymbolType): Unit = {
+ for (position <- positions) {
+ if (position.kind == kind) {
+ trackedPositionsChanged = true
+ position.delete()
+ }
+ }
+ }
+}
View
20 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Preferences.scala
@@ -0,0 +1,20 @@
+package scala.tools.eclipse.semantichighlighting
+
+import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
+
+import org.eclipse.jface.preference.IPreferenceStore
+
+class Preferences(val store: IPreferenceStore) {
+ def isEnabled(): Boolean =
+ store.getBoolean(ScalaSyntaxClasses.ENABLE_SEMANTIC_HIGHLIGHTING)
+
+ def isStrikethroughDeprecatedDecorationEnabled(): Boolean =
+ store.getBoolean(ScalaSyntaxClasses.STRIKETHROUGH_DEPRECATED)
+
+ def isUseSyntacticHintsEnabled(): Boolean =
+ store.getBoolean(ScalaSyntaxClasses.USE_SYNTACTIC_HINTS)
+}
+
+object Preferences {
+ def apply(store: IPreferenceStore): Preferences = new Preferences(store)
+}
View
185 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/Presenter.scala
@@ -0,0 +1,185 @@
+package scala.tools.eclipse.semantichighlighting
+
+import scala.tools.eclipse.InteractiveCompilationUnit
+import scala.tools.eclipse.logging.HasLogger
+import scala.tools.eclipse.semantichighlighting.classifier.SymbolClassification
+import scala.tools.eclipse.ui.UIThread
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.core.runtime.IStatus
+import org.eclipse.core.runtime.Status
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.jface.text.DocumentEvent
+import org.eclipse.jface.text.IDocument
+import org.eclipse.jface.text.IDocumentListener
+import org.eclipse.jface.text.IPositionUpdater
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.ITextInputListener
+import scala.tools.eclipse.ui.InteractiveCompilationUnitEditor
+
+/** This class is responsible of coordinating the correct initialization of the different components
+ * needed to perform semantic highlighting in an editor.
+ *
+ * @note This class is thread-safe.
+ *
+ * @param editor The editor holding the unit on which semantic highlighting is performed.
+ * @param presentationHighlighter Responsible of updating the editor's text presentation.
+ * @param preferences Semantic Highlighting user's preferences.
+ * @param uiThread Allows to run code in the UI Thread.
+ */
+class Presenter(
+ editor: InteractiveCompilationUnitEditor,
+ presentationHighlighter: TextPresentationHighlighter,
+ preferences: Preferences,
+ uiThread: UIThread) extends HasLogger { self =>
+ import Presenter._
+
+ private val job = {
+ val job = new SemanticHighlightingJob(editor)
+ job.setSystem(true)
+ job.setPriority(Job.DECORATE)
+ job
+ }
+
+ /** Keep tracks of all semantically highlighted positions in the editor.*/
+ private val positionsTracker = new PositionsTracker
+
+ private val documentSwapListener = new DocumentSwapListener(self, job)
+ private val documentContentListener = new DocumentContentListener(job)
+ private val positionUpdater = new PositionUpdater(positionsTracker)
+
+ /** Should be called right after creating an instance of `this` class.
+ *
+ * @note Must be called from within the UI Thread
+ *
+ * @param forceRefresh Force a semantic reconciler run during initialization.
+ */
+ def initialize(forceRefresh: Boolean): Unit = {
+ presentationHighlighter.initialize(job, positionsTracker)
+ Option(presentationHighlighter.sourceViewer) foreach { sv =>
+ sv.addTextInputListener(documentSwapListener)
+ manageDocument(sv.getDocument)
+ }
+ if (forceRefresh) refresh()
+ }
+
+ private def manageDocument(document: IDocument): Unit = {
+ if (document != null) {
+ document.addPositionUpdater(positionUpdater)
+ document.addDocumentListener(documentContentListener)
+ }
+ }
+
+ private def releaseDocument(document: IDocument): Unit = {
+ if (document != null) {
+ document.removePositionUpdater(positionUpdater)
+ document.removeDocumentListener(documentContentListener)
+ }
+ }
+
+ /** Stop the ongoing semantic reconciling job and unregister all editor/document's listeners.
+ *
+ * @note Must be called from within the UI Thread
+ *
+ * @param removesHighlights Force removal of all semantic highlighting styles from the editor.
+ */
+ def dispose(removesHighlights: Boolean): Unit = {
+ job.cancel()
+ /* invalidate the text presentation before disposing `presentationHighlighter`
+ * (because `presentationHighlighter` contains the logic for applying the styles). */
+ if (removesHighlights) removesAllHighlightings()
+ presentationHighlighter.dispose()
+ Option(presentationHighlighter.sourceViewer) foreach { sv => releaseDocument(sv.getDocument) }
+ }
+
+ /** Asynchronously refresh all semantic highlighting styles in the editor. */
+ private def refresh(): Unit = { job.schedule() }
+
+ /** Removes all highlighting styles from the editor.
+ *
+ * @note Must be called from within the UI Thread
+ */
+ private def removesAllHighlightings(): Unit = {
+ positionsTracker.reset()
+ Option(presentationHighlighter.sourceViewer) foreach (_.invalidateTextPresentation())
+ }
+
+ /** A background job that performs semantic highlighting.
+ *
+ * @note This class is thread-safe.
+ */
+ private class SemanticHighlightingJob(editor: InteractiveCompilationUnitEditor) extends Job("semantic highlighting") with HasLogger {
+
+ override def run(monitor: IProgressMonitor): IStatus = {
+ if (monitor.isCanceled()) Status.CANCEL_STATUS
+ else performSemanticHighlighting(monitor)
+ }
+
+ private def performSemanticHighlighting(monitor: IProgressMonitor): IStatus = {
+ editor.getInteractiveCompilationUnit.withSourceFile { (sourceFile, compiler) =>
+ logger.debug("performing semantic highlighting on " + sourceFile.file.name)
+ positionsTracker.startComputingNewPositions()
+ val symbolInfos =
+ try new SymbolClassification(sourceFile, compiler, preferences.isUseSyntacticHintsEnabled()).classifySymbols(monitor)
+ catch {
+ case e: Exception =>
+ logger.error("Error while performing semantic highlighting", e)
+ Nil
+ }
+ val newPositions = Position.from(symbolInfos)
+ val positionsChange = positionsTracker.createPositionsChange(newPositions)
+ val damagedRegion = positionsChange.affectedRegion()
+
+ if (damagedRegion.getLength > 0) {
+ val sortedPositions = newPositions.sorted.toArray
+ runPositionsUpdateInUiThread(sortedPositions, damagedRegion)
+ Job.ASYNC_FINISH
+ }
+ else Status.OK_STATUS
+ }(Status.OK_STATUS)
+ }
+
+ private def runPositionsUpdateInUiThread(newPositions: Array[Position], damagedRegion: IRegion): Unit =
+ /* if the positions held by the `positionsTracker` have changed, then
+ * it's useless to proceed because the `newPositions` have computed on a
+ * not up-to-date compilation unit. Let the next reconciler run take care
+ * of re-computing the correct positions with the up-to-date content.
+ */
+ if (!positionsTracker.isDirty) uiThread.asyncExec {
+ try {
+ setThread(uiThread.get)
+ if (!positionsTracker.isDirty) {
+ positionsTracker.swapPositions(newPositions)
+ presentationHighlighter.updateTextPresentation(damagedRegion)
+ }
+ }
+ catch { case e: Exception => () }
+ finally done(Status.OK_STATUS)
+ }
+ }
+}
+
+private object Presenter {
+ class DocumentSwapListener(presenter: Presenter, semanticHighlightingJob: Job) extends ITextInputListener with HasLogger {
+ override def inputDocumentAboutToBeChanged(oldInput: IDocument, newInput: IDocument): Unit = {
+ semanticHighlightingJob.cancel()
+ /* deletes all highlighted positions to avoid wrong colorings in the about to be displayed `newInput` document (the wrong
+ * colors would be displayed only until the semantic reconciler has a chance to reconcile the swapped compilation unit.
+ * Though, it makes sense to avoid the colors flickering and that's why `positionsTracker`'s state is reset.)
+ */
+ presenter.positionsTracker.reset()
+ presenter.releaseDocument(oldInput)
+ }
+ override def inputDocumentChanged(oldInput: IDocument, newInput: IDocument): Unit =
+ presenter.manageDocument(newInput)
+ }
+
+ class DocumentContentListener(reconciler: Job) extends IDocumentListener {
+ override def documentAboutToBeChanged(event: DocumentEvent): Unit = reconciler.cancel()
+ override def documentChanged(event: DocumentEvent): Unit = ()
+ }
+
+ class PositionUpdater(positionsTracker: PositionsTracker) extends IPositionUpdater with HasLogger {
+ override def update(event: DocumentEvent): Unit =
+ positionsTracker.updatePositions(event)
+ }
+}
View
9 ...dt.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotation.scala
@@ -1,9 +0,0 @@
-package scala.tools.eclipse.semantichighlighting
-
-import org.eclipse.jface.text.source.Annotation
-
-class SemanticHighlightingAnnotation(annotationType: String) extends Annotation(annotationType, false, null) {
-
- override lazy val toString = getClass.getSimpleName + "(" + annotationType + ")"
-
-}
View
88 ...t.core/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotations.scala
@@ -1,88 +0,0 @@
-package scala.tools.eclipse.semantichighlighting
-
-import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClass
-import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
-import scala.tools.eclipse.semantichighlighting.classifier.SymbolType
-import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes
-import org.eclipse.jface.preference.IPreferenceStore
-import org.eclipse.jface.text.source.AnnotationPainter
-import org.eclipse.ui.texteditor.SourceViewerDecorationSupport
-
-/**
- * Misc wiring for semantic highlighting annotations
- */
-object SemanticHighlightingAnnotations {
-
- private val TEXT_PREFERENCE_KEY = "scala.tools.eclipse.semantichighlighting.text"
-
- private def annotationType(syntaxClass: ScalaSyntaxClass, deprecated: Boolean) =
- syntaxClass.baseName + (if (deprecated) ".deprecated" else "") + ".annotationType"
-
- private def paintingStrategyId(syntaxClass: ScalaSyntaxClass, deprecated: Boolean) =
- syntaxClass.baseName + (if (deprecated) ".deprecated" else "") + ".paintingStrategyId"
-
- // Used to look up the paintingStrategyId in a pref store
- private def stylePreferenceKey(syntaxClass: ScalaSyntaxClass, deprecated: Boolean) =
- syntaxClass.baseName + (if (deprecated) ".deprecated" else "") + ".stylePreferenceKey"
-
- def initAnnotationPreferences(javaPrefStore: IPreferenceStore) {
- javaPrefStore.setDefault(TEXT_PREFERENCE_KEY, true)
- for {
- syntaxClass <- ScalaSyntaxClasses.scalaSemanticCategory.children
- deprecated <- List(false, true)
- paintingId = paintingStrategyId(syntaxClass, deprecated)
- preferenceKey = stylePreferenceKey(syntaxClass, deprecated)
- } javaPrefStore.setDefault(preferenceKey, paintingId)
- }
-
- def symbolAnnotation(symbolType: SymbolType, deprecated: Boolean) = {
- val syntaxClass = symbolTypeToSyntaxClass(symbolType)
- new SemanticHighlightingAnnotation(annotationType(syntaxClass, deprecated))
- }
-
- def addAnnotationPreferences(support: SourceViewerDecorationSupport) =
- for {
- syntaxClass <- ScalaSyntaxClasses.scalaSemanticCategory.children
- deprecated <- List(false, true)
- } support.setAnnotationPreference(annotationPreference(syntaxClass, deprecated))
-
- private def annotationPreference(syntaxClass: ScalaSyntaxClass, deprecated: Boolean) =
- new AnnotationPreferenceWithForegroundColourStyle(
- annotationType(syntaxClass, deprecated),
- TEXT_PREFERENCE_KEY,
- stylePreferenceKey(syntaxClass, deprecated))
-
- def addTextStyleStrategies(annotationPainter: AnnotationPainter) {
- for {
- syntaxClass <- ScalaSyntaxClasses.scalaSemanticCategory.children
- deprecated <- List(false, true)
- paintStrategyId = paintingStrategyId(syntaxClass, deprecated)
- textStyleStrategy = new SemanticHighlightingTextStyleStrategy(syntaxClass, deprecated)
- } annotationPainter.addTextStyleStrategy(paintStrategyId, textStyleStrategy)
- }
-
- private def symbolTypeToSyntaxClass(symbolType: SymbolType) = {
- import SymbolTypes._
- import ScalaSyntaxClasses._
- symbolType match {
- case Annotation => ANNOTATION
- case CaseClass => CASE_CLASS
- case CaseObject => CASE_OBJECT
- case Class => CLASS
- case LazyLocalVal => LAZY_LOCAL_VAL
- case LazyTemplateVal => LAZY_TEMPLATE_VAL
- case LocalVal => LOCAL_VAL
- case LocalVar => LOCAL_VAR
- case Method => METHOD
- case Param => PARAM
- case Object => OBJECT
- case Package => PACKAGE
- case TemplateVar => TEMPLATE_VAR
- case TemplateVal => TEMPLATE_VAL
- case Trait => TRAIT
- case Type => TYPE
- case TypeParameter => TYPE_PARAMETER
- }
- }
-
-}
View
114 ...src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingAnnotationsManager.scala
@@ -1,114 +0,0 @@
-package scala.tools.eclipse.semantichighlighting
-
-import scala.collection.JavaConversions._
-import scala.collection.Set
-import scala.tools.eclipse.javaelements.ScalaCompilationUnit
-import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClasses
-import scala.tools.eclipse.semantichighlighting.classifier._
-import scala.tools.eclipse.ScalaPlugin
-import scala.tools.eclipse.util.RichAnnotationModel._
-import scala.tools.eclipse.logging.HasLogger
-import org.eclipse.jface.text.source._
-import org.eclipse.jface.text.Position
-import scala.tools.eclipse.semantic.SemanticAction
-import scala.tools.eclipse.util.Utils.debugTimed
-import org.eclipse.core.runtime._
-import org.eclipse.core.runtime.jobs.Job
-import scala.tools.eclipse.javaelements.ScalaCompilationUnit
-
-class SemanticHighlightingAnnotationsManager(sourceViewer: ISourceViewer) extends SemanticAction with HasLogger {
-
- private var annotations: Set[Annotation] = Set()
-
- /** One job family per annotation manager, so we can cancel existing jobs before scheduling another job */
- private val jobFamily = new Object
-
- override def apply(scu: ScalaCompilationUnit) {
- if (semanticHighlightingRequired) {
- // cancel any in-progress or not-yet-scheduled jobs for this family
- Job.getJobManager.cancel(jobFamily)
-
- val job = semanticHighlightingJob(scu)
-
- job.setPriority(Job.DECORATE)
- job.schedule()
- } else
- removeAllAnnotations()
- }
-
- private def semanticHighlightingJob(scu: ScalaCompilationUnit): Job =
- new SemanticHighlightingJob(scu)
-
- private def semanticHighlightingRequired: Boolean =
- isSemanticHighlightingEnabled &&
- (ScalaSyntaxClasses.scalaSemanticCategory.children.map(_.enabledKey) exists prefStore.getBoolean)
-
- @inline private def prefStore = ScalaPlugin.prefStore
-
- private def isSemanticHighlightingEnabled: Boolean = prefStore.getBoolean(ScalaSyntaxClasses.ENABLE_SEMANTIC_HIGHLIGHTING)
- private def isUseSyntacticHintsEnabled: Boolean = prefStore.getBoolean(ScalaSyntaxClasses.USE_SYNTACTIC_HINTS)
-
- private def makeAnnotations(symbolInfos: List[SymbolInfo]): Map[Annotation, Position] = {
- val strikethroughDeprecatedSymbols = isStrikethroughDeprecatedDecorationEnabled
- for {
- SymbolInfo(symbolType, regions, isDeprecated) <- symbolInfos
- region <- regions
- deprecated = isDeprecated && strikethroughDeprecatedSymbols
- annotation = SemanticHighlightingAnnotations.symbolAnnotation(symbolType, deprecated)
- } yield (annotation -> asPosition(region))
- }.toMap
-
- private def isStrikethroughDeprecatedDecorationEnabled: Boolean =
- prefStore.getBoolean(ScalaSyntaxClasses.STRIKETHROUGH_DEPRECATED)
-
- private def setAnnotations(symbolInfos: List[SymbolInfo]) {
- val annotationsToPositions: Map[Annotation, Position] = debugTimed("makeAnnotations")(makeAnnotations(symbolInfos))
- for (annotationModel <- annotationModelOpt) {
- annotationModel.withLock {
- debugTimed("replaceAnnotations")(annotationModel.replaceAnnotations(annotations, annotationsToPositions))
- annotations = annotationsToPositions.keySet
- }
- }
- }
-
- private def asPosition(region: Region) = new Position(region.offset, region.length)
-
- private def removeAllAnnotations() =
- if (annotations.nonEmpty)
- for (annotationModel <- annotationModelOpt)
- annotationModel.withLock {
- annotationModel.deleteAnnotations(annotations)
- annotations = Set()
- }
-
- private def annotationModelOpt = Option(sourceViewer.getAnnotationModel)
-
- /** A background job that performs semantic highlighting. */
- private class SemanticHighlightingJob(scu: ScalaCompilationUnit) extends Job("semantic highlighting") {
- @volatile private var cancelled = false
-
- def run(monitor: IProgressMonitor): IStatus = {
- scu.doWithSourceFile { (sourceFile, compiler) =>
- val useSyntacticHints = isUseSyntacticHintsEnabled
- logger.info("Semantic highlighting " + scu.getResource.getName)
- val symbolInfos =
- try SymbolClassifier.classifySymbols(sourceFile, compiler, useSyntacticHints)
- catch {
- case e =>
- logger.error("Error performing semantic highlighting", e)
- Nil
- }
- if (!cancelled) setAnnotations(symbolInfos)
- }
- Status.OK_STATUS
- }
-
- override def canceling() {
- cancelled = true
- }
-
- /** It belongs to the semantic highlighting family. */
- override def belongsTo(family: Object) =
- jobFamily eq family
- }
-}
View
5 ...ore/src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingReconciliation.scala
@@ -72,10 +72,7 @@ class SemanticHighlightingReconciliation extends HasLogger {
if scu == compilationUnit
} yield {
page.addPartListener(new UnregisteringPartListener(scu))
- val semanticActions = List(
- new ImplicitHighlightingPresenter(scalaEditor.sourceViewer),
- new SemanticHighlightingAnnotationsManager(scalaEditor.sourceViewer)
- )
+ val semanticActions = List(new ImplicitHighlightingPresenter(scalaEditor.sourceViewer))
SemanticDecorationManagers(semanticActions)
}
presenters.headOption
View
18 .../src/scala/tools/eclipse/semantichighlighting/SemanticHighlightingTextStyleStrategy.scala
@@ -1,18 +0,0 @@
-package scala.tools.eclipse.semantichighlighting
-
-import scala.tools.eclipse.properties.syntaxcolouring.ScalaSyntaxClass
-import scala.tools.eclipse.ScalaPlugin
-
-import org.eclipse.jface.text.source.AnnotationPainter
-import org.eclipse.swt.custom.StyleRange
-import org.eclipse.swt.graphics.Color
-
-class SemanticHighlightingTextStyleStrategy(syntaxClass: ScalaSyntaxClass, deprecated: Boolean)
- extends AnnotationPainter.ITextStyleStrategy {
-
- def applyTextStyle(styleRange: StyleRange, annotationColor: Color) {
- syntaxClass.populateStyleRange(styleRange, ScalaPlugin.prefStore)
- styleRange.strikeout = deprecated
- }
-
-}
View
20 ...e.sdt.core/src/scala/tools/eclipse/semantichighlighting/TextPresentationHighlighter.scala
@@ -0,0 +1,20 @@
+package scala.tools.eclipse.semantichighlighting
+
+import org.eclipse.core.runtime.jobs.Job
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.source.ISourceViewer
+
+/** This interface expose the minimal amount of functionality needed by the semantic highlighting
+ * component to apply the presentation styles in a text editor.
+ *
+ * @note This trait is needed for running tests in a headless environment.
+ */
+trait TextPresentationHighlighter {
+ def sourceViewer: ISourceViewer
+
+ def initialize(semanticHighlightingJob: Job, positionsTracker: PositionsTracker): Unit
+ def dispose(): Unit
+
+ /** Triggers an update of the editor's `TextPresentation` based on the passed `damage` region.*/
+ def updateTextPresentation(damage: IRegion): Unit
+}
View
2 ...scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/Debugger.scala
@@ -3,7 +3,7 @@ package scala.tools.eclipse.semantichighlighting.classifier
/**
* Debugging info about the symbols
*/
-trait SymbolClassificationDebugger { self: SymbolClassification =>
+private[classifier] trait SymbolClassificationDebugger { self: SymbolClassification =>
import global._
View
12 org.scala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/Region.scala
@@ -1,12 +0,0 @@
-package scala.tools.eclipse.semantichighlighting.classifier
-
-case class Region(offset: Int, length: Int) {
-
- def intersects(other: Region): Boolean =
- !(other.offset >= offset + length || other.offset + other.length - 1 < offset)
-
- def of(s: Array[Char]): String = s.slice(offset, offset + length).mkString
-
- def of(s: String): String = s.slice(offset, offset + length)
-
-}
View
2 ...ala-ide.sdt.core/src/scala/tools/eclipse/semantichighlighting/classifier/SafeSymbol.scala
@@ -27,7 +27,7 @@ import scala.tools.nsc.util.SourceFile
* name and there are no trees for the getter yet (added by phase lazyvals). We need
* to return the accessor, who can later be classified as `lazy`.
*/
-trait SafeSymbol extends CompilerAccess with PimpedTrees {
+private[classifier] trait SafeSymbol extends CompilerAccess with PimpedTrees {
val global: ScalaPresentationCompiler
View
92 ...t.core/src/scala/tools/eclipse/semantichighlighting/classifier/SymbolClassification.scala
@@ -1,24 +1,26 @@
package scala.tools.eclipse.semantichighlighting.classifier
-
import scala.PartialFunction.condOpt
import scala.collection.mutable
+import scala.tools.eclipse.ScalaPresentationCompiler
import scala.tools.eclipse.logging.HasLogger
import scala.tools.eclipse.semantichighlighting.classifier.SymbolTypes._
-import scala.tools.eclipse.ScalaPresentationCompiler
+import scala.tools.eclipse.util.Utils._
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.util.RangePosition
import scala.tools.nsc.util.SourceFile
-import scala.tools.refactoring.common.CompilerAccess
-import scala.tools.refactoring.common.PimpedTrees
-import scala.tools.eclipse.util.Utils._
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.jface.text.IRegion
+import org.eclipse.jface.text.Region
+import scala.tools.eclipse.jface.text.RegionOps
-object SymbolClassification {
+private object SymbolClassification {
+ private val debug = false
/**
* If a symbol gets classified as more than one type, we give certain types precedence.
* Preference is given to map values over the corresponding key.
*/
- val pruneTable: Map[SymbolType, Set[SymbolType]] = Map(
+ private val pruneTable: Map[SymbolType, Set[SymbolType]] = Map(
LocalVar -> Set(LazyLocalVal),
LocalVal -> Set(LocalVar, TemplateVal, TemplateVar, LazyTemplateVal, LazyLocalVal),
Method -> Set(TemplateVal, TemplateVar, LazyTemplateVal),
@@ -26,9 +28,10 @@ object SymbolClassification {
CaseClass -> Set(CaseObject),
Param -> Set(TemplateVal, TemplateVar, LazyTemplateVal),
TemplateVal -> Set(Type),
- Object -> Set(CaseClass))
-
- val debug = false
+ Object -> Set(CaseClass)
+ )
+
+
}
class SymbolClassification(protected val sourceFile: SourceFile, val global: ScalaPresentationCompiler, useSyntacticHints: Boolean)
@@ -52,40 +55,55 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
!isSyntheticMethodParam(sym)
}
- def classifySymbols: List[SymbolInfo] = {
- val allSymbols: List[(Symbol, Position)] = debugTimed("allSymbols") {
+ def classifySymbols(progressMonitor: IProgressMonitor): List[SymbolInfo] = {
+ if(progressMonitor.isCanceled()) return Nil
+
+ val tree = unitTree
+ if(progressMonitor.isCanceled()) return Nil
+
+ val allSymbols: List[(Symbol, Position)] = {
for {
t <- unitTree
- if (t.hasSymbol || t.isType) && isSourceTree(t)
+ if !progressMonitor.isCanceled() && (t.hasSymbol || t.isType) && isSourceTree(t)
(sym, pos) <- safeSymbol(t)
if canSymbolBeReferencedInSource(sym)
} yield (sym, pos)
}
+ if(progressMonitor.isCanceled()) return Nil
+
if (debug) printSymbolInfo()
- val rawSymbolInfos: Seq[SymbolInfo] = debugTimed("rawSymbolInfos") {
+ val rawSymbolInfos: Seq[SymbolInfo] = {
val symAndPos = mutable.HashMap[Symbol, List[Position]]()
for {
(sym, pos) <- allSymbols
if sym != NoSymbol
} symAndPos(sym) = pos :: symAndPos.getOrElse(sym, Nil)
-
- (for {
- (sym, poss) <- symAndPos
- } yield getSymbolInfo(sym, poss)).toList
+ if (progressMonitor.isCanceled()) Nil
+ else {
+ (for {
+ (sym, poss) <- symAndPos
+ } yield getSymbolInfo(sym, poss)).toList
+ }
}
+
+ if (progressMonitor.isCanceled()) return Nil
+
+ val prunedSymbolInfos = prune(rawSymbolInfos)
+ if (progressMonitor.isCanceled()) return Nil
- val prunedSymbolInfos = debugTimed("pruned")(prune(rawSymbolInfos))
- val all: Set[Region] = rawSymbolInfos flatMap (_.regions) toSet
- val localVars: Set[Region] = rawSymbolInfos.collect { case SymbolInfo(LocalVar, regions, _) => regions }.flatten.toSet
- val symbolInfosFromSyntax = debugTimed("symbolInfosFromSyntax")(getSymbolInfosFromSyntax(syntacticInfo, localVars, all))
+ val all: Set[IRegion] = rawSymbolInfos.flatMap(_.regions).toSet
+ if (progressMonitor.isCanceled()) return Nil
- val res = debugTimed("res")((symbolInfosFromSyntax ++ prunedSymbolInfos) filter { _.regions.nonEmpty } distinct)
+ val localVars: Set[IRegion] = rawSymbolInfos.collect { case SymbolInfo(LocalVar, regions, _) => regions }.flatten.toSet
+ if (progressMonitor.isCanceled()) return Nil
- logger.debug("raw symbols: %d, pruned symbols: %d".format(rawSymbolInfos.size, prunedSymbolInfos.size))
- res
+ val symbolInfosFromSyntax = getSymbolInfosFromSyntax(syntacticInfo, localVars, all)
+ if (progressMonitor.isCanceled()) return Nil
+
+ (symbolInfosFromSyntax ++ prunedSymbolInfos) filter { _.regions.nonEmpty } distinct
}
private def getSymbolInfo(sym: Symbol, poss: List[Position]): SymbolInfo = {
@@ -95,28 +113,24 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
SymbolInfo(getSymbolType(sym), regions, deprecated)
}
- private def getOccurrenceRegion(sym: Symbol)(pos: Position): Option[Region] =
+ private def getOccurrenceRegion(sym: Symbol)(pos: Position): Option[IRegion] =
getNameRegion(pos) flatMap { region =>
- val text = region of sourceFile.content
+ val text = RegionOps.of(region, sourceFile.content)
val symName = sym.nameString
- if (symName.startsWith(text) || text == "`" + symName + "`")
- Some(region)
- else {
- logger.debug("couldn't find region for: " + sym + " at: " + pos.line)
- None
- }
+ if (symName.startsWith(text) || text == "`" + symName + "`") Some(region)
+ else None
}
- private def getNameRegion(pos: Position): Option[Region] =
+ private def getNameRegion(pos: Position): Option[IRegion] =
try
condOpt(pos) {
- case rangePosition: RangePosition => Region(rangePosition.start, rangePosition.end - rangePosition.start)
+ case rangePosition: RangePosition => new Region(rangePosition.start, rangePosition.end - rangePosition.start)
}
catch {
case e: Exception => None
}
- private def getSymbolInfosFromSyntax(syntacticInfo: SyntacticInfo, localVars: Set[Region], all: Set[Region]): List[SymbolInfo] = {
+ private def getSymbolInfosFromSyntax(syntacticInfo: SyntacticInfo, localVars: Set[IRegion], all: Set[IRegion]): List[SymbolInfo] = {
val SyntacticInfo(namedArgs, forVals, maybeSelfRefs, maybeClassOfs, annotations, packages) = syntacticInfo
List(
SymbolInfo(LocalVal, forVals toList, deprecated = false),
@@ -128,10 +142,10 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
}
private def prune(rawSymbolInfos: Seq[SymbolInfo]): Seq[SymbolInfo] = {
- def findRegionsWithSymbolType(symbolType: SymbolType): Set[Region] =
+ def findRegionsWithSymbolType(symbolType: SymbolType): Set[IRegion] =
rawSymbolInfos.collect { case SymbolInfo(`symbolType`, regions, _) => regions }.flatten.toSet
- val symbolTypeToRegion: Map[SymbolType, Set[Region]] = debugTimed("symbolTypeToRegion") {
+ val symbolTypeToRegion: Map[SymbolType, Set[IRegion]] = {
// we use `map' instead of the more elegant `mapValue(f)` because the latter is
// a `view': it applies `f' for each retrieved key, wihtout any caching. This
// causes quadratic behavior: `findRegionsWithSymbolType` is linear in rawSymbolInfos,
@@ -148,7 +162,7 @@ class SymbolClassification(protected val sourceFile: SourceFile, val global: Sca
case None => symbolInfo
}
- debugTimed("pruneMisidentifiedSymbols")(rawSymbolInfos.map(pruneMisidentifiedSymbols))
+ rawSymbolInfos.map(pruneMisidentifiedSymbols)
}
}