-
Notifications
You must be signed in to change notification settings - Fork 309
/
Presenter.scala
187 lines (168 loc) · 7.7 KB
/
Presenter.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
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
/* 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) {
runPositionsUpdateInUiThread(sortedPositions, damagedRegion)
Job.ASYNC_FINISH
} else Status.OK_STATUS
}
else Status.OK_STATUS
}(Status.OK_STATUS)
}
private def runPositionsUpdateInUiThread(newPositions: Array[Position], damagedRegion: IRegion): Unit =
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)
}
}