Skip to content

Commit

Permalink
Wip - template imports based on the Play version
Browse files Browse the repository at this point in the history
This commit outlines how I believe we should attempt to fix issue scala-ide#249.
  • Loading branch information
dotta committed Jun 14, 2015
1 parent 34cba49 commit 6b7addb
Show file tree
Hide file tree
Showing 18 changed files with 200 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import org.junit.Assert.assertEquals
import org.scalaide.play2.PlayPlugin

abstract class AbstractRouteScannerTest {
protected val prefStore = PlayPlugin.preferenceStore
protected val prefStore = PlayPlugin.instance().getPreferenceStore
protected val scanner : AbstractRouteScanner
protected val defaultToken = scanner.getDefaultReturnToken
protected val wsToken = Token.WHITESPACE
Expand Down
64 changes: 64 additions & 0 deletions org.scala-ide.play2/src/org/scalaide/play2/Play.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.scalaide.play2

import scala.collection.immutable.Seq

trait Play {
def templateImports: TemplateImports
}

trait TemplateImports {
def defaultScalaTemplateImports: Seq[String]
def defaultJavaTemplateImports: Seq[String]
def templateResultType: String
def templateFormatterType: String
}

object Play {
final val SupportedVersion = List("2.4", "2.3", "2.2")
final val DefaultVersion = SupportedVersion.head

def apply(version: String): Play = version match {
case "2.4" | "2.3" => new Play24
case _ => ???
}

private final class Play24 extends Play {
lazy val templateImports: TemplateImports = new Play24.Play24TemplateImports
}

private object Play24 {
private final class Play24TemplateImports extends TemplateImports {
// all imports have been copied from play.TemplateImports (see the playframework codebase)

private def defaultTemplateImports: Seq[String] = List(
"models._",
"controllers._",
"play.api.i18n._",
"play.api.mvc._",
"views.%format%._",
"play.api.templates.PlayMagic._"
)

lazy val defaultJavaTemplateImports: Seq[String] = defaultTemplateImports ++ List(
"java.lang._",
"java.util._",
"scala.collection.JavaConversions._",
"scala.collection.JavaConverters._",
"play.core.j.PlayMagicForJava._",
"play.mvc._",
"play.data._",
"play.api.data.Field",
"play.mvc.Http.Context.Implicit._"
)

lazy val defaultScalaTemplateImports: Seq[String] = defaultTemplateImports ++ List(
"play.api.mvc._",
"play.api.data._"
)

def templateResultType: String = templateFormatterType + ".Appendable"

def templateFormatterType: String = "play.twirl.api.HtmlFormat"
}
}
}
44 changes: 23 additions & 21 deletions org.scala-ide.play2/src/org/scalaide/play2/PlayPlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,43 +23,45 @@ object PlayPlugin {
/** Return the current plugin instace */
def instance(): PlayPlugin = plugin

/** Return the plugin-wide preference store */
def preferenceStore: IPreferenceStore = plugin.getPreferenceStore
def getImageDescriptor(path: String): ImageDescriptor =
AbstractUIPlugin.imageDescriptorFromPlugin(PluginId, path)

def getImageDescriptor(path: String): ImageDescriptor = {
AbstractUIPlugin.imageDescriptorFromPlugin(PluginId, path);
}

def log(status: Int, msg: String, ex: Throwable = null): Unit = {
def log(status: Int, msg: String, ex: Throwable = null): Unit =
plugin.getLog.log(new Status(status, plugin.getBundle().getSymbolicName(), msg, ex))

def isPlayProject(project: IProject): Boolean = asPlayProject(project).isDefined

def asPlayProject(project: IProject): Option[PlayProject] = {
for {
scalaPlugin <- Option(IScalaPlugin())
scalaProject <- scalaPlugin.asScalaProject(project)
} yield PlayProject(scalaProject)
}
}

class PlayPlugin extends AbstractUIPlugin {
override def start(context: BundleContext): Unit = {
super.start(context)
PlayPlugin.plugin = this
super.start(context)
initializeProjects()
}

private def initializeProjects(): Unit = {
// FIXME: All Scala projects are paying a penalty if the Play2 support is installed.
// I don't like it, but I'm not sure how to fix this. Maybe we should add a
// Play Nature?
// Also, how is the `Play2PropertyTester` used/useful?
for {
project <- ResourcesPlugin.getWorkspace.getRoot.getProjects
if project.isOpen
} PlayPlugin.asPlayProject(project)
}

override def stop(context: BundleContext): Unit = {
PlayPlugin.plugin = null
super.stop(context)
}

def asPlayProject(project: IProject): Option[PlayProject] = {
val scalaProject = IScalaPlugin().asScalaProject(project)
scalaProject map (PlayProject(_))
}

private def initializeProjects(): Unit = {
for {
iProject <- ResourcesPlugin.getWorkspace.getRoot.getProjects
if iProject.isOpen
playProject <- asPlayProject(iProject)
} playProject.initialize()
}

override def initializeImageRegistry(reg: ImageRegistry) {
reg.put(Images.ROUTES_ICON, Images.ROUTES_ICON_DESCRIPTOR)
reg.put(Images.HTTP_METHODS_ICON, Images.HTTP_METHODS_ICON_DESCRIPTOR)
Expand Down
70 changes: 48 additions & 22 deletions org.scala-ide.play2/src/org/scalaide/play2/PlayProject.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,48 +7,74 @@ import org.eclipse.core.resources.IFile
import org.scalaide.play2.templateeditor.TemplateCompilationUnit
import org.eclipse.ui.preferences.ScopedPreferenceStore
import org.eclipse.core.resources.ProjectScope
import org.scalaide.play2.util.SyncedScopedPreferenceStore
import org.eclipse.jface.preference.IPreferenceStore
import org.scalaide.play2.properties.PlayPreferences

import scala.collection.mutable
import org.eclipse.jface.util.PropertyChangeEvent

class PlayProject private (val scalaProject: IScalaProject) {
val cachedPreferenceStore = new SyncedScopedPreferenceStore(scalaProject.underlying, PlayPlugin.PluginId)
class PlayProject private (scalaProject: IScalaProject) {
@volatile private var _playSupport: Play = Play(Play.DefaultVersion)

/** Return additional imports that are automatically added to template files.
*
* @return The additional imports, or the empty string if none defined.
*/
def additionalTemplateImports(extension: String): String = {
cachedPreferenceStore.getString(PlayPreferences.TemplateImports).replace("%format%", extension);
}
def playSupport: Play = _playSupport

lazy val projectPrefStore = generateScopedPreferenceStore

/** Return a new project-scoped preference store for this project. */
def generateScopedPreferenceStore: IPreferenceStore = new ScopedPreferenceStore(new ProjectScope(scalaProject.underlying), PlayPlugin.PluginId)

/** Tries to load the scala template files
*/
def initialize() {
val templateCompilationUnits = for (
r <- scalaProject.underlying.members() if r.isInstanceOf[IFile] && r.getFullPath().toString().endsWith("." + PlayPlugin.TemplateExtension)
) yield TemplateCompilationUnit(r.asInstanceOf[IFile], false)
templateCompilationUnits foreach (_.initialReconcile())
// TODO: Why was there a second round of ask reload here?
// templateCompilationUnits.reverse foreach (_.askReload())
/** Tries to load the scala template files */
private def initialize() {
initializePreferences()
initializeTemplates()
}

private def initializePreferences(): Unit = {
import org.scalaide.util.eclipse.SWTUtils.fnToPropertyChangeListener
projectPrefStore.addPropertyChangeListener((event: PropertyChangeEvent) => event.getProperty() match {
case PlayPreferences.PlayVersion =>
val value = event.getNewValue().toString()
_playSupport = Play(value)
import org.eclipse.core.runtime.IStatus
PlayPlugin.log(IStatus.OK, s"Value of ${PlayPreferences.PlayVersion} is ${value}")
// do something with the new value
case PlayPreferences.TemplateImports =>
val value = event.getNewValue().toString()
import org.eclipse.core.runtime.IStatus
PlayPlugin.log(IStatus.OK, s"Value of ${PlayPreferences.TemplateImports} is ${value}")
case other =>
import org.eclipse.core.runtime.IStatus
PlayPlugin.log(IStatus.OK, s"Not interested on $other")
});
}

private def initializeTemplates(): Unit = {
for {
r <- scalaProject.underlying.members()
if r.isInstanceOf[IFile] && r.getFullPath().toString().endsWith("." + PlayPlugin.TemplateExtension)
} {
// FIXME: I wonder what happens is the presentation compiler is restarted.
// Will the template compilation units still be loaded?
// If not, then why do we actually need to load them in the first place?
val unit = TemplateCompilationUnit(r.asInstanceOf[IFile], false)
unit.initialReconcile()
}
}

/** FIXME: This method should probably not exist.
* Template files can be anywhere
*
* @return the absolute location of the project directory
*/
lazy val sourceDir = scalaProject.underlying.getLocation().toFile()
private[play2] lazy val sourceDir = scalaProject.underlying.getLocation().toFile()
}

object PlayProject {
// FIXME: This is a source of memory leaks. We should remove the project from the map if it's deleted.
private val projects = (new mutable.HashMap) withDefault {(scalaProject: IScalaProject) => new PlayProject(scalaProject)}

def apply(scalaProject: IScalaProject): PlayProject = {
projects(scalaProject)
val project = projects(scalaProject)
project.initialize()
project
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ object PlayPreferences {
*/
final val TemplateImports = "templateImports"

final val PlayVersion = "playVersion"

// Regex used for the operations on the templateImports preference.
private val importsRegex = "import ([^\n]+)\n".r

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@ import org.eclipse.core.runtime.preferences.AbstractPreferenceInitializer
import org.scalaide.play2.PlayPlugin

class PreferenceInitializer extends AbstractPreferenceInitializer {

override def initializeDefaultPreferences() {
PlayPlugin.preferenceStore.setDefault(PlayPreferences.TemplateImports, "import play.api.templates._\nimport play.api.templates.PlayMagic._\n")
override def initializeDefaultPreferences(): Unit = {
PlayPlugin.instance().getPreferenceStore().setDefault(PlayPreferences.TemplateImports, "")
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,48 @@ import org.eclipse.swt.widgets.Composite
import org.eclipse.swt.widgets.Display
import org.eclipse.ui.IWorkbenchPropertyPage
import org.scalaide.play2.PlayPlugin
import org.scalaide.play2.Play
import org.eclipse.ui.IWorkbench
import org.eclipse.ui.IWorkbenchPreferencePage

/** Preference page displayed in the property dialog of (play) projects.
* Used from the UI thread.
*/
class ProjectPropertyPage extends FieldEditorPreferencePage(FieldEditorPreferencePage.GRID) with IWorkbenchPropertyPage {

/** Preference field to display the list of extra imports.
*/
private var prefStore: IPreferenceStore = _

override protected def createFieldEditors() {
import ProjectPropertyPage._
addField(new PlayProjectVersion(getFieldEditorParent()))
addField(new ImportsFieldEditor(PlayPreferences.TemplateImports, "Template default imports", getFieldEditorParent()))
}

override protected def doGetPreferenceStore(): IPreferenceStore = prefStore

// Members declared in org.eclipse.ui.IWorkbenchPropertyPage

// doesn't seem to be a real function for this method.
// It looks like it leaked from the implementation of PropertyPage.
override def getElement(): IAdaptable = null

override def setElement(element: IAdaptable): Unit = {
prefStore = element match {
case project: IProject =>
PlayPlugin.asPlayProject(project).get.generateScopedPreferenceStore
case project: IJavaProject =>
PlayPlugin.asPlayProject(project.getProject()).get.generateScopedPreferenceStore
case _ => null
}
}
}

object ProjectPropertyPage {
import org.eclipse.jface.preference.ComboFieldEditor
private def supportedVersions = Play.SupportedVersion.toArray.map(version => Array(version, version))
class PlayProjectVersion(parent:Composite) extends ComboFieldEditor(PlayPreferences.PlayVersion, "Play version", supportedVersions, parent)

/** Preference field to display the list of extra imports. */
private class ImportsFieldEditor(name: String, labelText: String, parent: Composite) extends ListEditor(name, labelText, parent) {

override protected def createList(entries: Array[String]): String =
Expand All @@ -30,7 +64,6 @@ class ProjectPropertyPage extends FieldEditorPreferencePage(FieldEditorPreferenc
PlayPreferences.deserializeImports(s)

override protected def getNewInputObject(): String = {

val dlg = new InputDialog(
Display.getCurrent().getActiveShell(),
"Play template import",
Expand All @@ -46,38 +79,5 @@ class ProjectPropertyPage extends FieldEditorPreferencePage(FieldEditorPreferenc
null
}
}

}

// The preference store being edited.
// The data require to get the store is provided by the workbench during the page lifecycle.
private var prefStore: IPreferenceStore = _

// Members declared in org.eclipse.jface.preference.FieldEditorPreferencePage

override def createFieldEditors() {
addField(new ImportsFieldEditor(PlayPreferences.TemplateImports, "Template default imports", getFieldEditorParent()))
}

// Members declared in org.eclipse.ui.IWorkbenchPropertyPage

// doesn't seem to be a real function for this method.
// It looks like it leaked from the implementation of PropertyPage.
override def getElement(): IAdaptable = null

override def setElement(element: IAdaptable) {
prefStore = element match {
case project: IProject =>
PlayPlugin.instance().asPlayProject(project).get.generateScopedPreferenceStore
case project: IJavaProject =>
PlayPlugin.instance().asPlayProject(project.getProject()).get.generateScopedPreferenceStore
}
}

// ----

override def doGetPreferenceStore(): IPreferenceStore = {
prefStore
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.eclipse.ui.texteditor.TextOperationAction
import org.scalaide.play2.PlayPlugin

class RouteEditor extends TextEditor with ISourceViewerEditor with HasLogger with HasScalaProject { self =>
private lazy val preferenceStore = new ChainedPreferenceStore(Array(PlayPlugin.preferenceStore, EditorsUI.getPreferenceStore))
private lazy val preferenceStore = new ChainedPreferenceStore(Array(PlayPlugin.instance().getPreferenceStore(), EditorsUI.getPreferenceStore))
private val config = new RouteConfiguration(preferenceStore, this)

this.setPreferenceStore(preferenceStore)
Expand Down Expand Up @@ -57,7 +57,7 @@ class RouteEditor extends TextEditor with ISourceViewerEditor with HasLogger wit
HandlerUtil.getActiveEditor(event) match {
case routeEditor: RouteEditor if routeEditor eq self => {
val isSaveAction = commandid == "org.eclipse.ui.file.save"
val shouldFormatOnSave = PlayPlugin.preferenceStore.getBoolean(PlayPlugin.RouteFormatterFormatOnSaveId)
val shouldFormatOnSave = PlayPlugin.instance().getPreferenceStore().getBoolean(PlayPlugin.RouteFormatterFormatOnSaveId)
if (isSaveAction && shouldFormatOnSave) {
val document = routeEditor.getSourceViewer.getDocument
routeEditor.config.getContentFormatter(routeEditor.getSourceViewer).format(document, new Region(0, document.getLength))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ class RouteFormattingStrategy(val editor: ITextEditor) extends IFormattingStrate
import RouteFormattingStrategy._

val lines = getLines(document)
val margin = PlayPlugin.preferenceStore.getInt(PlayPlugin.RouteFormatterMarginId)
val margin = PlayPlugin.instance().getPreferenceStore().getInt(PlayPlugin.RouteFormatterMarginId)
val maxHttpVerbLength = getMaxHttpVerbLength(lines) + margin
val maxUriLength = getMaxUriLength(lines) + margin
val eclipseEdits = lines flatMap { route =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class RouteColorPreferenceInitializer extends AbstractPreferenceInitializer {
}

private def doInitializeDefaultPreferences() {
val prefStore = PlayPlugin.preferenceStore
val prefStore = PlayPlugin.instance().getPreferenceStore()
setDefaultsForSyntaxClasses(prefStore)
prefStore.setDefault(PlayPlugin.RouteFormatterMarginId, 3) // for formatter
}
Expand Down
Loading

0 comments on commit 6b7addb

Please sign in to comment.