-
Notifications
You must be signed in to change notification settings - Fork 309
/
ScalaPlugin.scala
341 lines (291 loc) · 13.4 KB
/
ScalaPlugin.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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
package org.scalaide.core.internal
import org.eclipse.jdt.core.IJavaProject
import scala.collection.mutable
import scala.util.control.ControlThrowable
import org.eclipse.core.resources.IFile
import org.eclipse.core.resources.IProject
import org.eclipse.core.resources.IResourceChangeEvent
import org.eclipse.core.resources.IResourceChangeListener
import org.eclipse.core.resources.ResourcesPlugin
import org.eclipse.core.runtime.CoreException
import org.eclipse.core.runtime.IStatus
import org.eclipse.core.runtime.Platform
import org.eclipse.core.runtime.Status
import org.eclipse.core.runtime.content.IContentTypeSettings
import org.eclipse.jdt.core.ElementChangedEvent
import org.eclipse.jdt.core.IElementChangedListener
import org.eclipse.jdt.core.JavaCore
import org.eclipse.jdt.core.IJavaElement
import org.eclipse.jdt.core.IJavaElementDelta
import org.eclipse.jdt.core.IPackageFragmentRoot
import org.eclipse.jdt.internal.core.JavaModel
import org.eclipse.jdt.internal.core.JavaProject
import org.eclipse.jdt.internal.core.PackageFragment
import org.eclipse.jdt.internal.core.PackageFragmentRoot
import org.eclipse.jdt.internal.core.util.Util
import org.eclipse.jdt.internal.ui.javaeditor.IClassFileEditorInput
import org.eclipse.jface.preference.IPreferenceStore
import org.eclipse.swt.widgets.Shell
import org.eclipse.swt.graphics.Color
import org.eclipse.ui.IEditorInput
import org.eclipse.ui.IFileEditorInput
import org.eclipse.ui.PlatformUI
import org.eclipse.ui.IPartListener
import org.eclipse.ui.IWorkbenchPart
import org.eclipse.ui.IWorkbenchPage
import org.eclipse.ui.IPageListener
import org.eclipse.ui.IEditorPart
import org.eclipse.ui.part.FileEditorInput
import org.eclipse.ui.plugin.AbstractUIPlugin
import org.osgi.framework.BundleContext
import org.scalaide.core.internal.jdt.model.ScalaSourceFile
import org.scalaide.util.internal.eclipse.OSGiUtils
import org.scalaide.ui.internal.templates.ScalaTemplateManager
import org.eclipse.jdt.ui.PreferenceConstants
import org.eclipse.core.resources.IResourceDelta
import org.scalaide.logging.HasLogger
import org.osgi.framework.Bundle
import org.scalaide.util.internal.Utils
import org.eclipse.jdt.core.ICompilationUnit
import scala.tools.nsc.io.AbstractFile
import scala.tools.nsc.settings.ScalaVersion
import scala.tools.nsc.settings.SpecificScalaVersion
import org.scalaide.core.resources.EclipseResource
import org.scalaide.logging.PluginLogConfigurator
import scala.tools.nsc.Settings
import org.scalaide.core.internal.project.ScalaProject
import org.scalaide.ui.internal.diagnostic
import org.scalaide.util.internal.CompilerUtils
import org.scalaide.core.internal.builder.zinc.CompilerInterfaceStore
import org.scalaide.util.internal.eclipse.EclipseUtils
import org.scalaide.util.internal.FixedSizeCache
import org.scalaide.core.IScalaInstallation
import org.scalaide.core.internal.project.ScalaInstallation.platformInstallation
import org.eclipse.core.runtime.content.IContentType
import org.scalaide.core.SdtConstants
import org.scalaide.ui.internal.migration.RegistryExtender
import org.scalaide.core.IScalaPlugin
import org.eclipse.core.resources.IResourceDeltaVisitor
import org.scalaide.util.internal.Utils._
import org.scalaide.core.internal.jdt.model.ScalaCompilationUnit
import org.scalaide.ui.internal.editor.ScalaDocumentProvider
import org.scalaide.core.internal.jdt.model.ScalaClassFile
import org.eclipse.jdt.core.IClassFile
object ScalaPlugin {
@volatile private var plugin: ScalaPlugin = _
def apply(): ScalaPlugin = plugin
}
class ScalaPlugin extends IScalaPlugin with PluginLogConfigurator with IResourceChangeListener with IElementChangedListener with HasLogger {
import CompilerUtils.{ ShortScalaVersion, isBinaryPrevious, isBinarySame }
import org.scalaide.core.SdtConstants._
/** Check if the given version is compatible with the current plug-in version.
* Check on the major/minor number, discard the maintenance number.
*
* For example 2.9.1 and 2.9.2-SNAPSHOT are compatible versions whereas
* 2.8.1 and 2.9.0 aren't.
*/
def isCompatibleVersion(version: ScalaVersion, project: ScalaProject): Boolean = {
if (project.isUsingCompatibilityMode())
isBinaryPrevious(ScalaVersion.current, version)
else
isBinarySame(ScalaVersion.current, version)// don't treat 2 unknown versions as equal
}
private lazy val sdtCoreBundle = getBundle()
lazy val sbtCompilerBundle = Platform.getBundle(SbtPluginId)
lazy val sbtCompilerInterfaceBundle = Platform.getBundle(SbtCompilerInterfacePluginId)
lazy val sbtCompilerInterface = OSGiUtils.pathInBundle(sbtCompilerInterfaceBundle, "/")
lazy val templateManager = new ScalaTemplateManager()
lazy val scalaSourceFileContentType: IContentType =
Platform.getContentTypeManager().getContentType("scala.tools.eclipse.scalaSource")
lazy val scalaClassFileContentType: IContentType =
Platform.getContentTypeManager().getContentType("scala.tools.eclipse.scalaClass")
/**
* The document provider needs to exist only a single time because it caches
* compilation units (their working copies). Each `ScalaSourceFileEditor` is
* associated with this document provider.
*/
private[scalaide] lazy val documentProvider = new ScalaDocumentProvider
override def start(context: BundleContext) = {
ScalaPlugin.plugin = this
super.start(context)
if (!headlessMode) {
PlatformUI.getWorkbench.getEditorRegistry.setDefaultEditor("*.scala", SdtConstants.EditorId)
diagnostic.StartupDiagnostics.run
new RegistryExtender().perform()
}
ResourcesPlugin.getWorkspace.addResourceChangeListener(this, IResourceChangeEvent.PRE_CLOSE | IResourceChangeEvent.POST_CHANGE)
JavaCore.addElementChangedListener(this)
logger.info("Scala compiler bundle: " + platformInstallation.compiler.classJar.toOSString() )
}
override def stop(context: BundleContext) = {
ResourcesPlugin.getWorkspace.removeResourceChangeListener(this)
super.stop(context)
ScalaPlugin.plugin = null
}
/** The compiler-interface store, located in this plugin configuration area (usually inside the metadata directory */
lazy val compilerInterfaceStore: CompilerInterfaceStore = new CompilerInterfaceStore(Platform.getStateLocation(sdtCoreBundle), this)
/** A LRU cache of class loaders for Scala builders */
lazy val classLoaderStore: FixedSizeCache[IScalaInstallation,ClassLoader] = new FixedSizeCache(initSize = 2, maxSize = 3)
// Scala project instances
private val projects = new mutable.HashMap[IProject, ScalaProject]
override def scalaCompilationUnit(input: IEditorInput): Option[ScalaCompilationUnit] = {
def unitOfSourceFile = Option(documentProvider.getWorkingCopy(input).asInstanceOf[ScalaCompilationUnit])
def unitOfClassFile = input.getAdapter(classOf[IClassFile]) match {
case tr: ScalaClassFile => Some(tr)
case _ => None
}
unitOfSourceFile orElse unitOfClassFile
}
def getJavaProject(project: IProject) = JavaCore.create(project)
override def getScalaProject(project: IProject): ScalaProject = projects.synchronized {
projects.get(project) getOrElse {
val scalaProject = ScalaProject(project)
projects(project) = scalaProject
scalaProject
}
}
override def asScalaProject(project: IProject): Option[ScalaProject] = {
if (ScalaProject.isScalaProject(project)) {
Some(getScalaProject(project))
} else {
None
}
}
def disposeProject(project: IProject): Unit = {
projects.synchronized {
projects.get(project) foreach { (scalaProject) =>
projects.remove(project)
scalaProject.dispose()
}
}
}
/** Restart all presentation compilers in the workspace. Need to do it in order
* for them to pick up the new std out/err streams.
*/
def resetAllPresentationCompilers() {
for {
iProject <- ResourcesPlugin.getWorkspace.getRoot.getProjects
if iProject.isOpen
scalaProject <- asScalaProject(iProject)
} scalaProject.presentationCompiler.askRestart()
}
override def resourceChanged(event: IResourceChangeEvent) {
(event.getResource, event.getType) match {
case (project: IProject, IResourceChangeEvent.PRE_CLOSE) =>
disposeProject(project)
case _ =>
}
(Option(event.getDelta()) foreach (_.accept(new IResourceDeltaVisitor() {
override def visit(delta: IResourceDelta): Boolean = {
if (delta.getFlags == IResourceDelta.OPEN){
val resource = delta.getResource().asInstanceOfOpt[IProject]
resource foreach {(r) =>
// that particular classpath check can set the Installation (used, e.g., for sbt-eclipse imports)
// setting the Installation triggers a recursive check
asScalaProject(r) foreach (_.checkClasspath(true))
}
false
} else
true
}
})))
}
override def elementChanged(event: ElementChangedEvent) {
import scala.collection.mutable.ListBuffer
import IJavaElement._
import IJavaElementDelta._
// check if the changes are linked with the build path
val modelDelta = event.getDelta()
// check that the notification is about a change (CHANGE) of some elements (F_CHILDREN) of the java model (JAVA_MODEL)
if (modelDelta.getElement().getElementType() == JAVA_MODEL && modelDelta.getKind() == CHANGED && (modelDelta.getFlags() & F_CHILDREN) != 0) {
for (innerDelta <- modelDelta.getAffectedChildren()) {
// check that the notification no the child is about a change (CHANDED) relative to a resolved classpath change (F_RESOLVED_CLASSPATH_CHANGED)
if (innerDelta.getKind() == CHANGED && (innerDelta.getFlags() & IJavaElementDelta.F_RESOLVED_CLASSPATH_CHANGED) != 0) {
innerDelta.getElement() match {
// classpath change should only impact projects
case javaProject: IJavaProject => {
asScalaProject(javaProject.getProject()).foreach{ (p) => p.classpathHasChanged(false) }
}
case _ =>
}
}
}
}
// process deleted files
val buff = new ListBuffer[ScalaSourceFile]
val changed = new ListBuffer[ICompilationUnit]
val projectsToReset = new mutable.HashSet[ScalaProject]
def findRemovedSources(delta: IJavaElementDelta) {
val isChanged = delta.getKind == CHANGED
val isRemoved = delta.getKind == REMOVED
val isAdded = delta.getKind == ADDED
def hasFlag(flag: Int) = (delta.getFlags & flag) != 0
val elem = delta.getElement
val processChildren: Boolean = elem.getElementType match {
case JAVA_MODEL =>
true
case JAVA_PROJECT if isRemoved =>
disposeProject(elem.getJavaProject.getProject)
false
case JAVA_PROJECT if !hasFlag(F_CLOSED) =>
true
case PACKAGE_FRAGMENT_ROOT =>
val hasContentChanged = isRemoved || hasFlag(F_REMOVED_FROM_CLASSPATH | F_ADDED_TO_CLASSPATH | F_ARCHIVE_CONTENT_CHANGED)
if (hasContentChanged) {
logger.info("package fragment root changed (resetting pres compiler): " + elem.getElementName())
asScalaProject(elem.getJavaProject().getProject).foreach(projectsToReset += _)
}
!hasContentChanged
case PACKAGE_FRAGMENT =>
val hasContentChanged = isAdded || isRemoved
if (hasContentChanged) {
logger.debug("package framgent added or removed" + elem.getElementName())
asScalaProject(elem.getJavaProject().getProject).foreach(projectsToReset += _)
}
// stop recursion here, we need to reset the PC anyway
!hasContentChanged
// TODO: the check should be done with isInstanceOf[ScalaSourceFile] instead of
// endsWith(scalaFileExtn), but it is not working for Play 2.0 because of #1000434
case COMPILATION_UNIT if isChanged && elem.getResource.getName.endsWith(ScalaFileExtn) =>
val hasContentChanged = hasFlag(IJavaElementDelta.F_CONTENT)
if (hasContentChanged)
// mark the changed Scala files to be refreshed in the presentation compiler if needed
changed += elem.asInstanceOf[ICompilationUnit]
false
case COMPILATION_UNIT if elem.isInstanceOf[ScalaSourceFile] && isRemoved =>
buff += elem.asInstanceOf[ScalaSourceFile]
false
case COMPILATION_UNIT if isAdded =>
logger.debug("added compilation unit " + elem.getElementName())
asScalaProject(elem.getJavaProject().getProject).foreach(projectsToReset += _)
false
case _ =>
false
}
if (processChildren)
delta.getAffectedChildren foreach findRemovedSources
}
findRemovedSources(event.getDelta)
// ask for the changed scala files to be refreshed in each project presentation compiler if needed
if (changed.nonEmpty) {
changed.toList groupBy (_.getJavaProject.getProject) foreach {
case (project, units) =>
asScalaProject(project) foreach { p =>
if (project.isOpen && !projectsToReset(p)) {
p.presentationCompiler(_.refreshChangedFiles(units.map(_.getResource.asInstanceOf[IFile])))
}
}
}
}
projectsToReset.foreach(_.presentationCompiler.askRestart())
if (buff.nonEmpty) {
buff.toList groupBy (_.getJavaProject.getProject) foreach {
case (project, srcs) =>
asScalaProject(project) foreach { p =>
if (project.isOpen && !projectsToReset(p))
p.presentationCompiler.internal (_.filesDeleted(srcs))
}
}
}
}
}