Permalink
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...
dotta committed Feb 1, 2013
1 parent e94f727 commit 9bfeb02fb7dda1be1fc526f014a7695fa29c988e
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
@@ -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,
@@ -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)
+ }
+}
@@ -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)
}
Oops, something went wrong.

0 comments on commit 9bfeb02

Please sign in to comment.