Browse files

Extending the refactoring support by integrating new refactorings fro…

…m the refactoring library.

The new integrated refactorings are:

- Method signature refactorings:
  - Change parameter order (within parameter lists)
  - Split parameter lists
  - Merge parameter lists

- Extract trait
  Extracts class members (val/var/def) to a new trait

- Move constructor to companion object
  Redirects calls constructor calls to a newly generated apply-method of the companion object.
  If necessary, the companion object is generated as well.

- Source generators:
  - Generate hashCode and equals
  - Introduce ProductN trait
    Generates methods to implement ProductN for selected class parameters.
    This includes generation of hashCode and equals.
  • Loading branch information...
1 parent d3545a8 commit 0f918497f83de1d0939303b25fbe01cc0914b84b @michih57 michih57 committed Mar 9, 2012
Showing with 1,436 additions and 6 deletions.
  1. +95 −1 org.scala-ide.sdt.core/plugin.xml
  2. +81 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/ExtractTraitAction.scala
  3. +61 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/IndexedRefactorings.scala
  4. +27 −0 ...ala-ide.sdt.core/src/scala/tools/eclipse/refactoring/MoveConstructorToCompanionObjectAction.scala
  5. +20 −5 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/RefactoringMenu.scala
  6. +28 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/ChangeParameterOrderAction.scala
  7. +27 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/MergeParameterListsAction.scala
  8. +40 −0 ...scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/MethodSignatureIdeRefactoring.scala
  9. +27 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/SplitParameterListsAction.scala
  10. +108 −0 ...rc/scala/tools/eclipse/refactoring/method/ui/ChangeParameterOrderConfigurationPageGenerator.scala
  11. +77 −0 ...src/scala/tools/eclipse/refactoring/method/ui/MergeParameterListsConfigurationPageGenerator.scala
  12. +406 −0 ...la/tools/eclipse/refactoring/method/ui/MethodSignatureRefactoringConfigurationPageGenerator.scala
  13. +79 −0 ...src/scala/tools/eclipse/refactoring/method/ui/SplitParameterListsConfigurationPageGenerator.scala
  14. +41 −0 ...-ide.sdt.core/src/scala/tools/eclipse/refactoring/source/ClassParameterDrivenIdeRefactoring.scala
  15. +33 −0 ...ala-ide.sdt.core/src/scala/tools/eclipse/refactoring/source/GenerateHashcodeAndEqualsAction.scala
  16. +34 −0 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/source/IntroduceProductNTraitAction.scala
  17. +98 −0 ...re/src/scala/tools/eclipse/refactoring/source/ui/GenerateHashcodeAndEqualsConfigurationPage.scala
  18. +14 −0 ....core/src/scala/tools/eclipse/refactoring/source/ui/IntroduceProductNTraitConfigurationPage.scala
  19. +140 −0 ...-ide.sdt.core/src/scala/tools/eclipse/refactoring/ui/ExtractTraitConfigurationPageGenerator.scala
View
96 org.scala-ide.sdt.core/plugin.xml
@@ -965,8 +965,66 @@
path="edit">
<separator name="scala.tools.eclipse.refactoring.refactoringGroup"/>
<separator name="scala.tools.eclipse.refactoring.quickfixGroup"/>
+ <separator name="scala.tools.eclipse.refactoring.generatorOnlyGroup"/>
+ <separator name="scala.tools.eclipse.refactoring.methodSignatureGroup"/>
</menu>
<action
+ class="scala.tools.eclipse.refactoring.source.GenerateHashcodeAndEqualsAction"
+ definitionId="scala.tools.eclipse.refactoring.source.command.GenerateHashcodeAndEquals"
+ id="scala.tools.eclipse.refactoring.source.generateHashcodeAndEqualsAction"
+ label="Generate hashCode and equals"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.generatorOnlyGroup"
+ tooltip="Generate hashCode and equals">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.source.IntroduceProductNTraitAction"
+ definitionId="scala.tools.eclipse.refactoring.source.command.IntroduceProductNTrait"
+ id="scala.tools.eclipse.refactoring.source.introduceProductNTraitAction"
+ label="Introduce ProductN trait"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.generatorOnlyGroup"
+ tooltip="Introduce ProductN trait">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.method.SplitParameterListsAction"
+ definitionId="scala.tools.eclipse.refactoring.method.command.SplitParameterLists"
+ id="scala.tools.eclipse.refactoring.method.splitParameterListsAction"
+ label="Split parameter lists"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.methodSignatureGroup"
+ tooltip="Split parameter lists">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.method.MergeParameterListsAction"
+ definitionId="scala.tools.eclipse.refactoring.method.command.MergeParameterLists"
+ id="scala.tools.eclipse.refactoring.method.mergeParameterListsAction"
+ label="Merge parameter lists"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.methodSignatureGroup"
+ tooltip="Merge parameter lists">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.method.ChangeParameterOrderAction"
+ definitionId="scala.tools.eclipse.refactoring.method.command.ChangeParameterOrder"
+ id="scala.tools.eclipse.refactoring.method.changeParameterOrderAction"
+ label="Change parameter order"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.methodSignatureGroup"
+ tooltip="Change parameter order">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.MoveConstructorToCompanionObjectAction"
+ definitionId="scala.tools.eclipse.refactoring.command.MoveConstructorToCompanionObject"
+ id="scala.tools.eclipse.refactoring.moveConstructorToCompanionObjectAction"
+ label="Move constructor to companion object"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.sourceGeneratorGroup"
+ tooltip="Move constructor to companion object">
+ </action>
+ <action
+ class="scala.tools.eclipse.refactoring.ExtractTraitAction"
+ definitionId="scala.tools.eclipse.refactoring.command.ExtractTrait"
+ id="scala.tools.eclipse.refactoring.extractTraitAction"
+ label="Extract trait"
+ menubarPath="scala.tools.eclipse.refactoring.refactoringMenu/scala.tools.eclipse.refactoring.sourceGeneratorGroup"
+ tooltip="Extract trait">
+ </action>
+ <action
class="scala.tools.eclipse.refactoring.move.MoveClassAction"
definitionId="scala.tools.eclipse.refactoring.command.MoveClass"
id="scala.tools.eclipse.refactoring.moveClassAction"
@@ -1066,18 +1124,54 @@
</actionSet>
</extension>
- <extension
+ <extension
point="org.eclipse.ui.commands">
<category
description="Refactorings"
id="scala.tools.eclipse.refactoring.commands.refactoring"
name="Refactor - Scala"/>
+ <category
+ description="Method Signature Refactorings"
+ id="scala.tools.eclipse.refactoring.commands.methodsignature"
+ name="Method Signature Refactoring - Scala"/>
+ <category
+ description="Source Generation Refactorings"
+ id="scala.tools.eclipse.refactoring.commands.sourcegeneration"
+ name="Source - Scala"/>
<command
name="Refactor Quick Menu"
description="Shows the refactor quick menu"
categoryId="scala.tools.eclipse.refactoring.commands.refactoring"
id="scala.tools.eclipse.refactoring.commands.quickMenu"/>
<command
+ categoryId="scala.tools.eclipse.refactoring.commands.sourcegeneration"
+ id="scala.tools.eclipse.refactoring.source.command.GenerateHashcodeAndEquals"
+ name="Generate hashCode and equals" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.sourcegeneration"
+ id="scala.tools.eclipse.refactoring.source.command.IntroduceProductNTrait"
+ name="Introduce ProductN trait" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.methodsignature"
+ id="scala.tools.eclipse.refactoring.method.command.SplitParameterLists"
+ name="Split parameter lists" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.methodsignature"
+ id="scala.tools.eclipse.refactoring.method.command.MergeParameterLists"
+ name="Merge parameter lists" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.methodsignature"
+ id="scala.tools.eclipse.refactoring.method.command.ChangeParameterOrder"
+ name="Change parameter order" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.refactoring"
+ id="scala.tools.eclipse.refactoring.command.MoveConstructorToCompanionObject"
+ name="Move constructor to companion object" />
+ <command
+ categoryId="scala.tools.eclipse.refactoring.commands.refactoring"
+ id="scala.tools.eclipse.refactoring.command.ExtractTrait"
+ name="Extract Trait" />
+ <command
categoryId="scala.tools.eclipse.refactoring.commands.refactoring"
id="scala.tools.eclipse.refactoring.command.MoveClass"
name="Move Class/Object/Trait"/>
View
81 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/ExtractTraitAction.scala
@@ -0,0 +1,81 @@
+package scala.tools.eclipse
+package refactoring
+
+import scala.tools.eclipse.refactoring.ui.ExtractTraitConfigurationPageGenerator
+import scala.tools.refactoring.analysis.GlobalIndexes
+import scala.tools.refactoring.common.NewFileChange
+import scala.tools.refactoring.common.TextChange
+import scala.tools.refactoring.implementations.ExtractTrait
+
+import org.eclipse.core.resources.IFile
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.jdt.internal.corext.refactoring.nls.changes.CreateFileChange
+import org.eclipse.ltk.core.refactoring.{Change => EclipseChange}
+import org.eclipse.ltk.core.refactoring.CompositeChange
+
+import javaelements.ScalaSourceFile
+
+/**
+ * The ExtractTrait refactoring extracts members (vals, vars and defs) of a class
+ * or trait to a new trait.
+ * The original class/trait will automatically extend the extracted trait and the
+ * extracted trait will have a self-type annotation for the original class/trait.
+ */
+class ExtractTraitAction extends RefactoringAction {
+
+ def createRefactoring(selectionStart: Int, selectionEnd: Int, file: ScalaSourceFile) = new ExtractTraitScalaIdeRefactoring(selectionStart, selectionEnd, file)
+
+ class ExtractTraitScalaIdeRefactoring(start: Int, end: Int, file: ScalaSourceFile)
+ extends ScalaIdeRefactoring("Extract trait", file, start, end) with ExtractTraitConfigurationPageGenerator {
+
+ val refactoring = withCompiler { c =>
+ new ExtractTrait with GlobalIndexes {
+ val global = c
+ var index = GlobalIndex(Nil)
+ }
+ }
+
+ def refactoringParameters = refactoring.RefactoringParameters(traitName, name => selectedMembers contains name)
+
+ // The preparation result provides a list of members that can be extracted
+ val extractableMembers = preparationResult.right.get.extractableMembers
+
+ // The members selected for extraction in the wizard
+ var selectedMembers: List[refactoring.global.ValOrDefDef] = Nil
+ // The name of the extracted trait, will be set in the wizard
+ var traitName = "ExtractedTrait"
+
+ val configPage = mkConfigPage(
+ extractableMembers,
+ members => selectedMembers = members,
+ name => traitName = name)
+
+ override def getPages = configPage::Nil
+
+ override def createChange(pm: IProgressMonitor): CompositeChange = {
+ val (textChanges, newFileChanges) = {
+ performRefactoring().foldRight((List[TextChange](), List[NewFileChange]())) {
+ case (change: TextChange, (textChanges, newFiles)) =>
+ (change :: textChanges, newFiles)
+ case (change: NewFileChange, (textChanges, newFilesChanges)) =>
+ (textChanges, change :: newFilesChanges)
+ }
+ }
+
+ // Create a new file for the extracted trait
+ val fileChange: Option[EclipseChange] = newFileChanges.headOption.map(newFile => {
+ val pkg = file.getCompilationUnit.getParent
+ val path = pkg.getPath.append(traitName + ".scala")
+ new CreateFileChange(path, newFile.text, file.getResource.asInstanceOf[IFile].getCharset)
+ })
+
+ val changes = fileChange.toList ::: scalaChangesToEclipseChanges(textChanges).toList
+ val compositeChange = new CompositeChange(getName)
+ changes.foreach(compositeChange.add)
+
+ compositeChange
+ }
+
+ }
+
+}
View
61 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/IndexedRefactorings.scala
@@ -0,0 +1,61 @@
+package scala.tools.eclipse.refactoring
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.refactoring.analysis.GlobalIndexes
+import scala.tools.refactoring.MultiStageRefactoring
+
+import org.eclipse.core.runtime.IProgressMonitor
+import org.eclipse.ltk.core.refactoring.RefactoringStatus
+
+/**
+ * Helper trait that adds an index variable to a refactoring.
+ * Needed to be able to factor out common functionality of refactorings
+ * that need an index of the full project.
+ * @see IndexedIdeRefactoring
+ */
+trait Indexed {
+ this: GlobalIndexes =>
+ var index = GlobalIndex(Nil)
+}
+
+/**
+ * Abstract ScalaIdeRefactoring for refactorings that need an index of the full project.
+ */
+abstract class IndexedIdeRefactoring(refactoringName: String, start: Int, end: Int, sourcefile: ScalaSourceFile)
+ extends ScalaIdeRefactoring(refactoringName, sourcefile, start, end) with FullProjectIndex {
+
+ val project = sourcefile.project
+
+ val refactoring: MultiStageRefactoring with GlobalIndexes with Indexed
+
+ /**
+ * A cleanup handler, will later be set by the refactoring
+ * to remove all loaded compilation units from the compiler.
+ */
+ var cleanup = () => ()
+
+ override def checkInitialConditions(pm: IProgressMonitor): RefactoringStatus = {
+ val status = super.checkInitialConditions(pm)
+
+ if (!status.hasError) {
+ // the hints parameter is not used, this can slow down the refactoring considerably!
+ val (index, cleanupIndex) = buildFullProjectIndex(pm, Nil)
+ refactoring.index = index
+ // will be called after the refactoring has finished
+ cleanup = cleanupIndex
+ }
+
+ if (pm.isCanceled) {
+ status.addWarning("Indexing was cancelled, aborting refactoring.")
+ }
+
+ status
+ }
+
+ override def createChange(pm: IProgressMonitor) = {
+ val change = super.createChange(pm)
+ cleanup()
+ change
+ }
+
+}
View
27 ...sdt.core/src/scala/tools/eclipse/refactoring/MoveConstructorToCompanionObjectAction.scala
@@ -0,0 +1,27 @@
+package scala.tools.eclipse.refactoring
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.refactoring.analysis.GlobalIndexes
+import scala.tools.refactoring.implementations.MoveConstructorToCompanionObject
+
+/**
+ * This refactoring redirects calls to the primary constructor to the
+ * apply method of the companion object.
+ * The apply method and if necessary the companion object will be generated.
+ */
+class MoveConstructorToCompanionObjectAction extends RefactoringAction {
+
+ def createRefactoring(start: Int, end: Int, file: ScalaSourceFile) = new MoveConstructorToCompanionObjectIdeRefactoring(start, end, file)
+
+ class MoveConstructorToCompanionObjectIdeRefactoring(start: Int, end: Int, file: ScalaSourceFile)
+ extends IndexedIdeRefactoring("Move constructor to companion object", start, end, file) {
+
+ val refactoring = withCompiler { compiler =>
+ new MoveConstructorToCompanionObject with GlobalIndexes with Indexed {
+ val global = compiler
+ }
+ }
+
+ val refactoringParameters = new refactoring.RefactoringParameters
+ }
+}
View
25 org.scala-ide.sdt.core/src/scala/tools/eclipse/refactoring/RefactoringMenu.scala
@@ -12,6 +12,7 @@ import org.eclipse.core.commands.Command
import org.eclipse.jface.action.{ Action, IAction, IMenuManager, IMenuListener, ActionContributionItem }
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor
import org.eclipse.jdt.internal.ui.actions.JDTQuickMenuCreator
+import org.eclipse.jface.action.Separator
/**
* Unfortunately, it seems we cannot simply define a context menu on the Scala source editor via
@@ -28,6 +29,8 @@ protected[eclipse] object RefactoringMenu {
val QuickMenu = Value("scala.tools.eclipse.refactoring.commands.quickMenu")
val ContextMenu = Value("org.eclipse.jdt.ui.refactoring.menu")
val CommandsCategory = Value("scala.tools.eclipse.refactoring.commands.refactoring")
+ val CommandsSourceCategory = Value("scala.tools.eclipse.refactoring.commands.sourcegeneration")
+ val CommandsMethodSignatureCategory = Value("scala.tools.eclipse.refactoring.commands.methodsignature")
type Id = Value; implicit def toString(id: Id.Value) = id.toString
}
@@ -55,13 +58,25 @@ protected[eclipse] object RefactoringMenu {
private def fillFromPluginXml(menu: IMenuManager, editor: EditorPart, all: Boolean): Unit = {
menu.removeAll
- for (command <- refactoringCommandsDefinedInPluginXml) menu.add(wrapped(command))
- def refactoringCommandsDefinedInPluginXml = {
- val service = commandService(editor)
+ val service = commandService(editor)
+ val categories = {
val refactoringCategory = service.getCategory(Id.CommandsCategory)
- service.getDefinedCommands.filter((command: Command) =>
- command.getCategory == refactoringCategory && (all || command.getId != Id.QuickMenu.toString))
+ val sourceCategory = service.getCategory(Id.CommandsSourceCategory)
+ val methodSignatureCategory = service.getCategory(Id.CommandsMethodSignatureCategory)
+ List(refactoringCategory, sourceCategory, methodSignatureCategory)
+ }
+
+ for(category <- categories)
+ menu.add(new Separator(category.getId))
+
+ for (command <- refactoringCommandsDefinedInPluginXml)
+ menu.appendToGroup(command.getCategory.getId, wrapped(command))
+
+ def refactoringCommandsDefinedInPluginXml = {
+ val refactoringCommands = service.getDefinedCommands.filter((command: Command) =>
+ (categories contains command.getCategory) && (all || command.getId != Id.QuickMenu.toString))
+ refactoringCommands
}
def wrapped(command: Command) = new Action {
View
28 ...-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/ChangeParameterOrderAction.scala
@@ -0,0 +1,28 @@
+package scala.tools.eclipse.refactoring.method
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.eclipse.refactoring.RefactoringAction
+import scala.tools.refactoring.implementations.ChangeParamOrder
+import scala.tools.eclipse.refactoring.method.ui.ChangeParameterOrderConfigurationPageGenerator
+
+/**
+ * A method signature refactoring that changes the order of parameters within
+ * their parameter lists.
+ * The refactoring is applied to the definition of the method as well as to all its usages.
+ * This also extends to overridden and partially applied versions of the method.
+ */
+class ChangeParameterOrderAction extends RefactoringAction {
+ def createRefactoring(start: Int, end: Int, file: ScalaSourceFile) = new ChangeParameterOrderIdeRefactoring(start, end, file)
+
+ class ChangeParameterOrderIdeRefactoring(start: Int, end: Int, f: ScalaSourceFile)
+ extends MethodSignatureScalaIdeRefactoring("Change parameter order", start, end, f) with ChangeParameterOrderConfigurationPageGenerator {
+
+ val refactoring = withCompiler { compiler =>
+ new ChangeParamOrder with IndexedMethodSignatureRefactoring {
+ val global = compiler
+ }
+ }
+
+ override var refactoringParameters = List[List[Int]]()
+ }
+}
View
27 ...a-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/MergeParameterListsAction.scala
@@ -0,0 +1,27 @@
+package scala.tools.eclipse.refactoring.method
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.eclipse.refactoring.method.ui.MergeParameterListsConfigurationPageGenerator
+import scala.tools.eclipse.refactoring.RefactoringAction
+import scala.tools.refactoring.implementations.MergeParameterLists
+
+/**
+ * A method signature refactoring that merges selected parameter lists of a method.
+ * The refactoring is applied to the definition of the method as well as to all its usages.
+ * This also extends to overridden and partially applied versions of the method.
+ */
+class MergeParameterListsAction extends RefactoringAction {
+ def createRefactoring(start: Int, end: Int, file: ScalaSourceFile) = new MergeParameterListsIdeRefactoring(start, end, file)
+
+ class MergeParameterListsIdeRefactoring(start: Int, end: Int, f: ScalaSourceFile)
+ extends MethodSignatureScalaIdeRefactoring("Merge parameter lists", start, end, f) with MergeParameterListsConfigurationPageGenerator {
+
+ override val refactoring = withCompiler { compiler =>
+ new MergeParameterLists with IndexedMethodSignatureRefactoring {
+ val global = compiler
+ }
+ }
+
+ override var refactoringParameters = List[Int]()
+ }
+}
View
40 ...e.sdt.core/src/scala/tools/eclipse/refactoring/method/MethodSignatureIdeRefactoring.scala
@@ -0,0 +1,40 @@
+package scala.tools.eclipse.refactoring.method
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.eclipse.refactoring.method.ui.MethodSignatureRefactoringConfigurationPageGenerator
+import scala.tools.eclipse.refactoring.Indexed
+import scala.tools.eclipse.refactoring.IndexedIdeRefactoring
+import scala.tools.refactoring.analysis.GlobalIndexes
+import scala.tools.refactoring.implementations.MethodSignatureRefactoring
+
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage
+
+/**
+ * Abstract refactoring that contains common functionality of method signature
+ * refactorings.
+ */
+abstract class MethodSignatureScalaIdeRefactoring(refactoringName: String, start: Int, end: Int, file: ScalaSourceFile)
+ extends IndexedIdeRefactoring(refactoringName, start, end, file) {
+
+ // Provides the wizard page
+ this: MethodSignatureRefactoringConfigurationPageGenerator =>
+
+ trait IndexedMethodSignatureRefactoring extends MethodSignatureRefactoring with GlobalIndexes with Indexed
+
+ val refactoring: IndexedMethodSignatureRefactoring
+
+ type MSRefactoringParameters = refactoring.RefactoringParameters
+
+ // Parameters will be set from the wizard page
+ var refactoringParameters: MSRefactoringParameters
+
+ // The selected DefDef that will be refactored
+ def defdef: refactoring.global.DefDef = preparationResult.right.get.defdef
+
+ // Generates the wizard page
+ def configPage = mkConfigPage(defdef, refactoringParameters_=)
+
+ def mkConfigPage(defdef: refactoring.global.DefDef, paramsObs: refactoring.RefactoringParameters => Unit): UserInputWizardPage
+
+ override def getPages = configPage :: Nil
+}
View
27 ...a-ide.sdt.core/src/scala/tools/eclipse/refactoring/method/SplitParameterListsAction.scala
@@ -0,0 +1,27 @@
+package scala.tools.eclipse.refactoring.method
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.eclipse.refactoring.method.ui.SplitParameterListsConfigurationPageGenerator
+import scala.tools.eclipse.refactoring.RefactoringAction
+import scala.tools.refactoring.implementations.SplitParameterLists
+
+/**
+ * A method signature refactoring that splits parameter lists of a method.
+ * The refactoring is applied to the definition of the method as well as to all its usages.
+ * This also extends to overridden and partially applied versions of the method.
+ */
+class SplitParameterListsAction extends RefactoringAction {
+ def createRefactoring(start: Int, end: Int, file: ScalaSourceFile) = new SplitParameterListsIdeRefactoring(start, end, file)
+
+ class SplitParameterListsIdeRefactoring(start: Int, end: Int, f: ScalaSourceFile)
+ extends MethodSignatureScalaIdeRefactoring("Split parameter lists", start, end, f) with SplitParameterListsConfigurationPageGenerator {
+
+ override val refactoring = withCompiler { compiler =>
+ new SplitParameterLists with IndexedMethodSignatureRefactoring {
+ val global = compiler
+ }
+ }
+
+ override var refactoringParameters = List[List[Int]]()
+ }
+}
View
108 .../tools/eclipse/refactoring/method/ui/ChangeParameterOrderConfigurationPageGenerator.scala
@@ -0,0 +1,108 @@
+package scala.tools.eclipse.refactoring.method.ui
+
+import org.eclipse.swt.widgets.Button
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+import scala.tools.refactoring.Refactoring
+
+
+/**
+ * Generates the wizard page for a ChangeParameterOrder refactoring.
+ */
+trait ChangeParameterOrderConfigurationPageGenerator extends MethodSignatureRefactoringConfigurationPageGenerator {
+
+ this: ScalaIdeRefactoring =>
+
+ import refactoring.global._
+
+ override type MSRefactoringParameters = List[List[Int]]
+
+ override val refactoringCaption = "Change parameter order"
+
+ override def mkConfigPage(method: DefDef, paramsObs: MSRefactoringParameters => Unit) = new ChangeParameterOrderConfigurationPage(method, paramsObs)
+
+ class ChangeParameterOrderConfigurationPage(
+ method: DefDef,
+ paramsObs: MSRefactoringParameters => Unit) extends MethodSignatureRefactoringConfigurationPage(method, paramsObs) {
+
+ override val headerLabelText = "Change the order of parameters inside a parameter list"
+ // We use the first button to move a parameter up...
+ override val firstBtnText = "Up"
+ // ...and the second button to move it down.
+ override val secondBtnText = "Down"
+
+ // If a parameter is selected we
+ // - enable the up button when the selected parameter is not
+ // the first one in its parameter list and disable it otherwise
+ // - enable the down button when the selected parameter is not
+ // the last one in its parameter list and disable it otherwise
+ override def setBtnStatesForParameter(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ upBtn: Button,
+ downBtn: Button) {
+ val isLast = isLastInParamList(param, paramsWithSeparators)
+ val isFirst = isFirstInParamList(param, paramsWithSeparators)
+
+ downBtn.setEnabled(!(isLast && isFirst) && !isLast)
+ upBtn.setEnabled(!(isLast && isFirst) && !isFirst)
+ }
+
+ // If a parameter is selected we disable both buttons.
+ override def setBtnStatesForSeparator(
+ separator: ParamListSeparator,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ upBtn: Button,
+ downBtn: Button) {
+ downBtn.setEnabled(false)
+ upBtn.setEnabled(false)
+ }
+
+ override def computeParameters(paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ def computePermutation(paramLists: (List[ValDef], List[ValDef])) = {
+ val original = paramLists._1
+ val permuted = paramLists._2
+ permuted.map(p => original.indexOf(p))
+ }
+
+ val permutedParamLists = extractParamLists(paramsWithSeparators)
+ val originalParamLists = method.vparamss
+ val permutations = originalParamLists.zip(permutedParamLists).map(computePermutation)
+ println(permutations)
+ permutations
+ }
+
+ // Handles a click on the up button; moves the selected parameter up
+ override def handleFirstBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Left(param) => moveParamUp(param, paramsWithSeparators)
+ case _ => paramsWithSeparators
+ }
+
+ private def moveParamUp(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]):
+ List[Either[ValDef, ParamListSeparator]] = paramsWithSeparators match {
+ case Nil => Nil
+ case p::Nil => paramsWithSeparators
+ case Left(first)::Left(second)::rest if second == param => Left(second)::Left(first)::rest
+ case p::ps => p::moveParamUp(param, ps)
+ }
+
+ // Handles a click on the down button; moves the selected parameter down
+ override def handleSecondBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Left(param) => moveParamDown(param, paramsWithSeparators)
+ case _ => paramsWithSeparators
+ }
+
+ private def moveParamDown(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]):
+ List[Either[ValDef, ParamListSeparator]] = paramsWithSeparators match {
+ case Nil => Nil
+ case p::Nil => paramsWithSeparators
+ case Left(first)::Left(second)::rest if first == param => Left(second)::Left(first)::rest
+ case p::ps => p::moveParamDown(param, ps)
+ }
+
+ }
+
+}
View
77 ...a/tools/eclipse/refactoring/method/ui/MergeParameterListsConfigurationPageGenerator.scala
@@ -0,0 +1,77 @@
+package scala.tools.eclipse.refactoring.method.ui
+
+import org.eclipse.swt.widgets.Button
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+import scala.tools.refactoring.Refactoring
+
+/**
+ * Generates the wizard page for a MergeParameterLists refactoring.
+ */
+trait MergeParameterListsConfigurationPageGenerator extends MethodSignatureRefactoringConfigurationPageGenerator {
+
+ this: ScalaIdeRefactoring =>
+
+ import refactoring.global._
+
+ override type MSRefactoringParameters = List[Int]
+
+ override val refactoringCaption = "Merge parameter lists"
+
+ override def mkConfigPage(method: DefDef, paramsObs: MSRefactoringParameters => Unit) = new MergeParameterListsConfigurationPage(method, paramsObs)
+
+ class MergeParameterListsConfigurationPage(
+ method: DefDef,
+ paramsObs: MSRefactoringParameters => Unit) extends MethodSignatureRefactoringConfigurationPage(method, paramsObs) {
+
+ override val headerLabelText = "Merge parameter lists"
+
+ // We need to remember the original parameter lists
+ val paramsWithOriginalSeparators = intersperse(method.vparamss, nr => OriginalSeparator(nr))
+
+ // If a parameter is selected we disable the merge button and enable
+ // the split button if the selected parameter was originally before
+ // a separator to make it possible to revert a previously triggered merge.
+ override def setBtnStatesForParameter(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ splitBtn: Button,
+ mergeBtn: Button) {
+ mergeBtn.setEnabled(false)
+ val isBeforeSeparatorCurrently = isBeforeSeparator(param, paramsWithSeparators)
+ val isBeforeSeparatorOriginally = isBeforeSeparator(param, paramsWithOriginalSeparators)
+ splitBtn.setEnabled(!isBeforeSeparatorCurrently && isBeforeSeparatorOriginally)
+ }
+
+ // If a separator is selected we enable the merge button and disable the split button.
+ override def setBtnStatesForSeparator(
+ separator: ParamListSeparator,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ splitBtn: Button,
+ mergeBtn: Button) {
+ mergeBtn.setEnabled(true)
+ splitBtn.setEnabled(false)
+ }
+
+ override def computeParameters(paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ val currentSeparatorPositions = paramsWithSeparators.collect{case Right(OriginalSeparator(nr)) => nr}
+ val originalSeparatorPositions = paramsWithOriginalSeparators.collect{case Right(OriginalSeparator(nr)) => nr}
+ originalSeparatorPositions diff currentSeparatorPositions
+ }
+
+ // Handles a click of the split button; reinserts an original separator.
+ override def handleFirstBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Left(param) => {
+ val originalFollowingSeparator = followingSeparator(param, paramsWithOriginalSeparators)
+ originalFollowingSeparator.map(insertSeparatorAfter(param, _, paramsWithSeparators)).getOrElse(paramsWithSeparators)
+ }
+ case _ => paramsWithSeparators
+ }
+
+ // Handles a click of the merge button; removes the selected separator.
+ override def handleSecondBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Right(sep @ OriginalSeparator(_)) => removeSeparator(sep, paramsWithSeparators)
+ case _ => paramsWithSeparators
+ }
+
+ }
+}
View
406 .../eclipse/refactoring/method/ui/MethodSignatureRefactoringConfigurationPageGenerator.scala
@@ -0,0 +1,406 @@
+package scala.tools.eclipse.refactoring.method.ui
+
+import org.eclipse.jface.viewers.ColumnLabelProvider
+import org.eclipse.jface.viewers.ISelectionChangedListener
+import org.eclipse.jface.viewers.IStructuredContentProvider
+import org.eclipse.jface.viewers.IStructuredSelection
+import org.eclipse.jface.viewers.SelectionChangedEvent
+import org.eclipse.jface.viewers.TableViewer
+import org.eclipse.jface.viewers.TableViewerColumn
+import org.eclipse.jface.viewers.Viewer
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage
+import org.eclipse.swt.events.MouseAdapter
+import org.eclipse.swt.events.MouseEvent
+import org.eclipse.swt.events.MouseListener
+import org.eclipse.swt.layout.GridData
+import org.eclipse.swt.layout.GridLayout
+import org.eclipse.swt.widgets.Button
+import org.eclipse.swt.widgets.Composite
+import org.eclipse.swt.widgets.Label
+import org.eclipse.swt.SWT
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+import scala.tools.eclipse.ScalaPlugin
+import scala.tools.eclipse.ScalaPreviewerFactory
+import scala.tools.eclipse.refactoring.method.MethodSignatureScalaIdeRefactoring
+
+/**
+ * Generates the generic wizard page for method signature refactorings.
+ * Subtraits only have to fill in the specific details for actual refactorings.
+ */
+trait MethodSignatureRefactoringConfigurationPageGenerator {
+
+ // Using this self type we get access to refactoring.global
+ this: ScalaIdeRefactoring =>
+
+ import refactoring.global.{DefDef, ValDef}
+
+ /**
+ * Represents a separator of two parameter lists of in a method signature.
+ * The parameter lists will be represented by List[Either[ValDef, ParamListSeparator]]
+ */
+ sealed trait ParamListSeparator
+ /**
+ * Represents a separator that existed before the refactoring.
+ */
+ case class OriginalSeparator(number: Int) extends ParamListSeparator
+ /**
+ * Represents a separator that was inserted by the refactoring.
+ * @param paramListIndex Indicates in which parameter list the separater is inserted
+ * @param splitPosition Indicates the position within the parameter list where the
+ * separator is inserted
+ */
+ case class InsertedSeparator(paramListIndex: Int, splitPosition: Int) extends ParamListSeparator
+
+ type MSRefactoringParameters
+
+ val refactoringCaption: String
+
+ // Generates the wizard.
+ def mkConfigPage(method: DefDef, paramsObs: MSRefactoringParameters => Unit): UserInputWizardPage
+
+ /**
+ * Generic wizard page for method signature refactorings.
+ * Consists of:
+ * - a descriptive header label
+ * - a table displaying the parameters of all parameter lists
+ * - two buttons to operate on the parameter table
+ * - a preview of the refactored method signature
+ */
+ abstract class MethodSignatureRefactoringConfigurationPage(
+ method: DefDef,
+ paramsObs: MSRefactoringParameters => Unit) extends UserInputWizardPage(refactoringCaption) {
+
+ // Descriptive header label text
+ val headerLabelText: String
+
+ // Captions of the buttons that operate on the parameter table
+ val firstBtnText = "Split"
+ val secondBtnText = "Merge"
+
+ def createControl(parent: Composite) {
+ initializeDialogUnits(parent)
+
+ // this represents all parameter lists of the method
+ var paramsWithSeparators: List[Either[ValDef, ParamListSeparator]] = intersperse(method.vparamss, nr => OriginalSeparator(nr))
+ // function that provides the parameters to the parameter table
+ val methodProvider: () => List[Either[ValDef, ParamListSeparator]] = () => paramsWithSeparators
+ // the currently selected item in the parameter table
+ var selection: Either[ValDef, ParamListSeparator] = Right(OriginalSeparator(0))
+
+ type SelectedParamHandler = Either[ValDef, ParamListSeparator] => Unit
+
+ // Convenience implicit conversion of SelectedParamHandlers to MouseListeners
+ implicit def partial2MouseUpListener(f: SelectedParamHandler): MouseListener = new MouseAdapter {
+ override def mouseUp(me: MouseEvent) {
+ f(selection)
+ }
+ }
+
+ val composite = new Composite(parent, SWT.NONE)
+
+ val layout = new GridLayout(2, false)
+
+ composite.setLayout(layout)
+
+ val headerLabel = new Label(composite, SWT.WRAP)
+ headerLabel.setText(headerLabelText)
+ headerLabel.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1))
+
+ val paramsTable = new ParamsTable(composite, methodProvider)
+ paramsTable.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 3))
+
+ val firstBtn = new Button(composite, SWT.NONE)
+ firstBtn.setText(firstBtnText)
+ firstBtn.setEnabled(false)
+ firstBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1))
+
+ val secondBtn = new Button(composite, SWT.NONE)
+ secondBtn.setText(secondBtnText)
+ secondBtn.setEnabled(false)
+ secondBtn.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1))
+
+ val spacingLabel = new Label(composite, SWT.NONE)
+ spacingLabel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1))
+
+ def setBtnStatesForSelection(sel: Either[ValDef, ParamListSeparator]): Unit = sel match {
+ case Left(param) => setBtnStatesForParameter(param, paramsWithSeparators, firstBtn, secondBtn)
+ case Right(separator) => setBtnStatesForSeparator(separator, paramsWithSeparators, firstBtn, secondBtn)
+ }
+
+ // Observer for selection changed events from the parameter table.
+ // Calls setBtnStatesForParameter or setBtnStatesForSeparator to update
+ // the button states when a parameter or a separator is selected, respectively.
+ def selectionObs(structuredSel: IStructuredSelection) {
+ if(structuredSel.size > 0) {
+ structuredSel.getFirstElement match {
+ case Left(param: ValDef) => {
+ setBtnStatesForParameter(param, paramsWithSeparators, firstBtn, secondBtn)
+ selection = Left(param)
+ }
+ case Right(separator: ParamListSeparator) => {
+ setBtnStatesForSeparator(separator, paramsWithSeparators, firstBtn, secondBtn)
+ selection = Right(separator)
+ }
+ case _ => // unknown item selected
+ }
+ }
+ }
+ paramsTable.selectionObs = selectionObs
+
+ val methodPreview = ScalaPreviewerFactory.createPreviewer(
+ composite,
+ ScalaPlugin.plugin.getPreferenceStore,
+ previewString(method, paramsWithSeparators))
+ methodPreview.getControl.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1))
+
+ // triggers an update of the parameter table and everything that depends on it
+ def updateParamsAndSeparators() {
+ paramsTable.updateTable()
+ methodPreview.getDocument.set(previewString(method, paramsWithSeparators))
+
+ val parameters = computeParameters(paramsWithSeparators)
+ paramsObs(parameters)
+ }
+
+ // listener for the first button, delegates to handleFirstBtn
+ val firstBtnListener: SelectedParamHandler = selection => {
+ paramsWithSeparators = handleFirstBtn(selection, paramsWithSeparators)
+ updateParamsAndSeparators
+ setBtnStatesForSelection(selection)
+ }
+ firstBtn.addMouseListener(firstBtnListener)
+
+ // listener for the second button, delegates to handleSecondBtn
+ val secondBtnListener: SelectedParamHandler = selection => {
+ paramsWithSeparators = handleSecondBtn(selection, paramsWithSeparators)
+ updateParamsAndSeparators
+ setBtnStatesForSelection(selection)
+ }
+ secondBtn.addMouseListener(secondBtnListener)
+
+ setControl(composite)
+ }
+
+ // responsible for setting the states of the two buttons when a parameter is selected in the parameter table
+ def setBtnStatesForParameter(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ firstBtn: Button,
+ secondBtn: Button): Unit
+
+ // responsible for setting the states of the two buttons when a ParamListSeparator is selected in the parameter table
+ def setBtnStatesForSeparator(
+ separator: ParamListSeparator,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ firstBtn: Button,
+ seecondBtn: Button): Unit
+
+ // responsible to compute the refactoring parameters that correspond to the current state of this wizard
+ def computeParameters(paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]): MSRefactoringParameters
+
+ def handleFirstBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]): List[Either[ValDef, ParamListSeparator]]
+
+ def handleSecondBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]): List[Either[ValDef, ParamListSeparator]]
+ }
+
+ /**
+ * Flattens a List[List[A]] to List[Either[A, B]]. The original lists are separated
+ * by elements of Right() and flattened to Left(A).
+ */
+ protected def intersperse[A, B](lists: List[List[A]], insertProvider: (Int) => B, nrInserted: Int = 1): List[Either[A, B]] = lists match {
+ case Nil => Nil
+ case ls::Nil => ls.map(Left(_))
+ case ls::lss => ls.map(Left(_)) ::: Right(insertProvider(nrInserted))::intersperse(lss, insertProvider, nrInserted + 1)
+ }
+
+ /**
+ * Reconstructs the list of parameter lists from the flattened withSeparators list.
+ * Inverse operation to intersperse.
+ */
+ protected def extractParamLists(withSeparators: List[Either[ValDef, ParamListSeparator]]): List[List[ValDef]] = withSeparators match {
+ case Nil => Nil
+ case _ => {
+ val vals = withSeparators.takeWhile(_.isLeft) collect { case Left(v) => v}
+ val tail = withSeparators.dropWhile(_.isLeft).dropWhile(_.isRight)
+ vals::extractParamLists(tail)
+ }
+ }
+
+ /**
+ * Finds the separator of type S that follows the given item of type A in the given list.
+ */
+ protected def followingSeparator[A, S](item: A, separatedItems: List[Either[A, S]]) = {
+ separatedItems.sliding(2).collect({ case Left(p)::Right(sep)::Nil if p == item => sep}).toList.headOption
+ }
+
+ /**
+ * Finds the separator of type S that precedes the given item of type A in the given list.
+ */
+ protected def precedingSeparator[A, S](item: A, separatedItems: List[Either[A, S]]) = {
+ separatedItems.sliding(2).collect({case Right(sep)::Left(p)::Nil if p == item => sep}).toList.headOption
+ }
+
+ /**
+ * Checks whether the given element is followed by a separator in the given list.
+ */
+ protected def isBeforeSeparator[A, S](item: A, separatedItems: List[Either[A, S]]) = {
+ followingSeparator(item, separatedItems).isDefined
+ }
+
+ /**
+ * Checks whether the given element is preceded by a separator in the given list.
+ */
+ protected def isAfterSeparator[A, S](item: A, separatedItems: List[Either[A, S]]) = {
+ precedingSeparator(item, separatedItems).isDefined
+ }
+
+ /**
+ * Checks whether the given parameter is the first one in its parameter list.
+ */
+ protected def isFirstInParamList(param: ValDef, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ isAfterSeparator(param, Right(OriginalSeparator(0))::paramsWithSeparators)
+ }
+
+ /**
+ * Checks whether the given parameter is the last one in its parameter list.
+ */
+ protected def isLastInParamList(param: ValDef, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ isBeforeSeparator(param, paramsWithSeparators ::: List(Right(OriginalSeparator(0))))
+ }
+
+ /**
+ * Checks if the parameter list can be splitted after the given parameter.
+ */
+ protected def isInSplitPosition(param: ValDef, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) =
+ !isLastInParamList(param, paramsWithSeparators)
+
+ /**
+ * Inserts the given separator after the given parameter in paramsWithSeparators.
+ */
+ protected def insertSeparatorAfter(param: ValDef, separator: ParamListSeparator, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ paramsWithSeparators.foldRight(Nil: List[Either[ValDef, ParamListSeparator]])((el, acc) => el match {
+ case Left(p) if p == param => el::Right(separator)::acc
+ case _ => el::acc
+ })
+ }
+
+ /**
+ * Inserts a new separator after the given parameter in paramsWithSeparators.
+ */
+ protected def addSplitPositionAfter(param: ValDef, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ def computePos(paramListIndex: Int, posCounter: Int, m: List[Either[ValDef, ParamListSeparator]]): Option[(Int, Int)] = m match {
+ case Nil => None
+ case Left(p)::ms if p == param => Some(paramListIndex, posCounter + 1)
+ case Left(_)::ms => computePos(paramListIndex, posCounter + 1, ms)
+ case Right(OriginalSeparator(_))::ms => computePos(paramListIndex + 1, 0, ms)
+ case Right(InsertedSeparator(_, _))::ms => computePos(paramListIndex, posCounter, ms)
+ }
+
+ val posOpt = computePos(0, 0, paramsWithSeparators)
+ posOpt.map(pos => insertSeparatorAfter(param, InsertedSeparator(pos._1, pos._2), paramsWithSeparators)) getOrElse paramsWithSeparators
+ }
+
+ /**
+ * Removes the given separator from paramsWithSeparators
+ */
+ protected def removeSeparator(separator: ParamListSeparator, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ paramsWithSeparators.filter(_ != Right(separator))
+ }
+
+ /**
+ * Generates the preview string for the refactored method signature
+ */
+ protected def previewString(method: DefDef, paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ val paramLists = extractParamLists(paramsWithSeparators)
+ val paramListStrings = paramLists.map(params => params.map(p => p.name + ": " + p.tpt.symbol.name)).map(_.mkString(", "))
+ "def " + method.name + "(" + paramListStrings.mkString(")(") + "): " + method.tpt.symbol.name
+ }
+
+ /**
+ * The parameter table.
+ */
+ class ParamsTable(
+ parent: Composite,
+ methodProvider: () => List[Either[ValDef, ParamListSeparator]]) extends Composite(parent, SWT.NONE) {
+
+ var selectionObs: IStructuredSelection => Unit = _
+
+ private val viewer = new TableViewer(this, SWT.SINGLE | SWT.BORDER)
+
+ setup()
+
+ private def setup() {
+
+ def mkTableViewerColumn(title: String) = {
+ val viewerColumn = new TableViewerColumn(viewer, SWT.NONE)
+ val column = viewerColumn.getColumn
+ column.setText(title)
+ column.setWidth(100)
+ column.setResizable(true)
+ column.setMoveable(true)
+ viewerColumn
+ }
+
+ object ParamsContentProvider extends IStructuredContentProvider {
+ override def getElements(paramsWithSeparators: AnyRef): Array[AnyRef] = {
+ val elems = paramsWithSeparators.asInstanceOf[List[Either[ValDef, ParamListSeparator]]]
+ Array(elems: _*)
+ }
+ override def inputChanged(viewer: Viewer, oldInput: Any, newInput: Any) {}
+ override def dispose {}
+ }
+
+ val gridLayout = new GridLayout
+ setLayout(gridLayout)
+
+ val nameColumn = mkTableViewerColumn("Name")
+ val typeColumn = mkTableViewerColumn("Type")
+
+ nameColumn.setLabelProvider(new ColumnLabelProvider {
+ override def getText(element: Any): String = {
+ val row = element.asInstanceOf[Either[ValDef, ParamListSeparator]]
+ row match {
+ case Left(param) => param.symbol.nameString
+ case Right(separator) => "-"
+ }
+ }
+ })
+
+ typeColumn.setLabelProvider(new ColumnLabelProvider {
+ override def getText(element: Any): String = {
+ val row = element.asInstanceOf[Either[ValDef, ParamListSeparator]]
+ row match {
+ case Left(param) => param.tpt.symbol.nameString
+ // maybe display original/inserted separators differently
+ case Right(separator) => separator match {
+ case OriginalSeparator(_) => "-"
+ case InsertedSeparator(_, _) => "-"
+ }
+ }
+ }
+ })
+
+ val table = viewer.getTable
+ table.setHeaderVisible(true)
+ table.setLinesVisible(true)
+
+ viewer.setContentProvider(ParamsContentProvider)
+ viewer.getControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true))
+ updateTable()
+
+ viewer.addSelectionChangedListener(new ISelectionChangedListener {
+ override def selectionChanged(event: SelectionChangedEvent) {
+ event.getSelection match {
+ case structuredSel: IStructuredSelection => selectionObs(structuredSel)
+ case _ => // can't handle unstructured selections
+ }
+ }
+ })
+ }
+
+ def updateTable() {
+ viewer.setInput(methodProvider.apply)
+ viewer.refresh()
+ }
+ }
+}
View
79 ...a/tools/eclipse/refactoring/method/ui/SplitParameterListsConfigurationPageGenerator.scala
@@ -0,0 +1,79 @@
+package scala.tools.eclipse
+package refactoring.method.ui
+
+import org.eclipse.swt.widgets.Button
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+import scala.tools.refactoring.Refactoring
+
+/**
+ * Generates the wizard page for a SplitParameterLists refactoring.
+ */
+trait SplitParameterListsConfigurationPageGenerator extends MethodSignatureRefactoringConfigurationPageGenerator {
+
+ this: ScalaIdeRefactoring =>
+
+ import refactoring.global._
+
+ override type MSRefactoringParameters = List[List[Int]]
+
+ override val refactoringCaption = "Split parameter lists"
+
+ override def mkConfigPage(method: DefDef, paramsObs: MSRefactoringParameters => Unit) = new SplitParameterListsConfigurationPage(method, paramsObs)
+
+ class SplitParameterListsConfigurationPage(
+ method: DefDef,
+ paramsObs: MSRefactoringParameters => Unit) extends MethodSignatureRefactoringConfigurationPage(method, paramsObs) {
+
+ override val headerLabelText = "Split parameter lists after the marked parameter"
+
+ // If a parameter is selected in the parameter table we disable the
+ // merge button and enable the split button if the selected parameter
+ // is in a splittable position (not the last parameter in its list).
+ override def setBtnStatesForParameter(
+ param: ValDef,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ splitBtn: Button,
+ mergeBtn: Button) {
+ mergeBtn.setEnabled(false)
+ splitBtn.setEnabled(isInSplitPosition(param, paramsWithSeparators))
+ }
+
+ // If a separator is selected we disable the split button and
+ // enable the merge button if the separator was previously inserted
+ // to make it possible to revert this decision.
+ override def setBtnStatesForSeparator(
+ separator: ParamListSeparator,
+ paramsWithSeparators: List[Either[ValDef, ParamListSeparator]],
+ splitBtn: Button,
+ mergeBtn: Button) {
+ separator match {
+ case OriginalSeparator(_) => mergeBtn.setEnabled(false)
+ case InsertedSeparator(_, _) => mergeBtn.setEnabled(true)
+ }
+ splitBtn.setEnabled(false)
+ }
+
+ override def computeParameters(paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = {
+ val splitters = paramsWithSeparators collect { case Right(sep @ InsertedSeparator(_, _)) => sep }
+ val grouped = splitters.groupBy(sep => sep.paramListIndex).withDefaultValue(Nil)
+ val splitterLists =
+ for (i <- 0 until method.vparamss.size)
+ yield grouped(i)
+ val splitPositions = splitterLists.map(splitters => splitters.map(_.splitPosition).sortWith(_ < _)).toList
+ splitPositions
+ }
+
+ // Handles a click of the split button; inserts a separator after the selected parameter.
+ override def handleFirstBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Left(param) if isInSplitPosition(param, paramsWithSeparators) => addSplitPositionAfter(param, paramsWithSeparators)
+ case _ => paramsWithSeparators
+ }
+
+ // Handles a click of the merge button; removes the selected inserted separator.
+ override def handleSecondBtn(selection: Either[ValDef, ParamListSeparator], paramsWithSeparators: List[Either[ValDef, ParamListSeparator]]) = selection match {
+ case Right(sep: InsertedSeparator) => removeSeparator(sep, paramsWithSeparators)
+ case _ => paramsWithSeparators
+ }
+
+ }
+}
View
41 ....core/src/scala/tools/eclipse/refactoring/source/ClassParameterDrivenIdeRefactoring.scala
@@ -0,0 +1,41 @@
+package scala.tools.eclipse.refactoring.source
+
+import scala.tools.eclipse.javaelements.ScalaSourceFile
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+import scala.tools.refactoring.implementations.ClassParameterDrivenSourceGeneration
+
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardPage
+
+/**
+ * Abstract refactoring for common functionality of refactorings that
+ * generate code driven by class parameters.
+ * @see GenerateHashcodeAndEqualsAction
+ * @see IntroduceProductNTraitAction
+ */
+abstract class ClassParameterDrivenIdeRefactoring(name: String, start: Int, end: Int, sourcefile: ScalaSourceFile)
+ extends ScalaIdeRefactoring(name, sourcefile, start, end) {
+
+ val project = file.project
+
+ val refactoring: ClassParameterDrivenSourceGeneration
+
+ var selectedClassParamNames: List[String] = Nil
+ var callSuper = false
+ var prime = None
+
+ def refactoringParameters = refactoring.RefactoringParameters(callSuper, selectByNames(selectedClassParamNames))
+
+ // The preparation result contains the list of class parameters of the primary constructor
+ lazy val classParams = preparationResult.right.get.classParams.map(t => t._1)
+
+ val configPage: RefactoringWizardPage
+
+ override def getPages = configPage :: Nil
+
+ import refactoring.global.ValDef
+ def selectByNames(names: List[String]): Option[ValDef => Boolean] = names match {
+ case Nil => None
+ case _ => Some((param: ValDef) => names.contains(param.name.toString))
+ }
+
+}
View
33 ...sdt.core/src/scala/tools/eclipse/refactoring/source/GenerateHashcodeAndEqualsAction.scala
@@ -0,0 +1,33 @@
+package scala.tools.eclipse
+package refactoring.source
+
+import scala.tools.eclipse.refactoring.RefactoringAction
+import scala.tools.refactoring.implementations.GenerateHashcodeAndEquals
+
+import javaelements.ScalaSourceFile
+import ui.GenerateHashcodeAndEqualsConfigurationPage
+
+/**
+ * This refactoring that generates hashCode and equals implementations
+ * following the recommendations given in chapter 28 of
+ * Programming in Scala.
+ */
+class GenerateHashcodeAndEqualsAction extends RefactoringAction {
+
+ def createRefactoring(selectionStart: Int, selectionEnd: Int, file: ScalaSourceFile) = new GenerateHashcodeAndEqualsScalaIdeRefactoring(selectionStart, selectionEnd, file)
+
+ class GenerateHashcodeAndEqualsScalaIdeRefactoring(start: Int, end: Int, file: ScalaSourceFile)
+ extends ClassParameterDrivenIdeRefactoring("Generate hashCode and equals", start, end, file) {
+
+ val refactoring = withCompiler { c =>
+ new GenerateHashcodeAndEquals {
+ val global = c
+ }
+ }
+
+ val configPage = new GenerateHashcodeAndEqualsConfigurationPage(
+ classParams.map(_.name.toString),
+ selectedClassParamNames_=,
+ callSuper_=)
+ }
+}
View
34 ...de.sdt.core/src/scala/tools/eclipse/refactoring/source/IntroduceProductNTraitAction.scala
@@ -0,0 +1,34 @@
+package scala.tools.eclipse
+package refactoring.source
+
+import scala.tools.eclipse.refactoring.RefactoringAction
+import scala.tools.refactoring.implementations.IntroduceProductNTrait
+
+import javaelements.ScalaSourceFile
+import ui.IntroduceProductNTraitConfigurationPage
+/**
+ * This refactoring implements the ProductN trait for a class.
+ * Given N selected class parameters this refactoring generates
+ * the methods needed to implement the ProductN trait. This includes
+ * implementations for hashCode and equals.
+ * @see GenerateHashcodeAndEqualsAction
+ */
+class IntroduceProductNTraitAction extends RefactoringAction {
+
+ def createRefactoring(selectionStart: Int, selectionEnd: Int, file: ScalaSourceFile) = new GenerateHashcodeAndEqualsScalaIdeRefactoring(selectionStart, selectionEnd, file)
+
+ class GenerateHashcodeAndEqualsScalaIdeRefactoring(start: Int, end: Int, file: ScalaSourceFile)
+ extends ClassParameterDrivenIdeRefactoring("Generate hashCode and equals", start, end, file) {
+
+ val refactoring = withCompiler { c =>
+ new IntroduceProductNTrait{
+ val global = c
+ }
+ }
+
+ val configPage = new IntroduceProductNTraitConfigurationPage(
+ classParams.map(_.name.toString),
+ selectedNames => selectedClassParamNames = selectedNames,
+ callSuperNew => callSuper = callSuperNew)
+ }
+}
View
98 ...cala/tools/eclipse/refactoring/source/ui/GenerateHashcodeAndEqualsConfigurationPage.scala
@@ -0,0 +1,98 @@
+package scala.tools.eclipse
+package refactoring.source.ui
+
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage
+import org.eclipse.swt.events.MouseAdapter
+import org.eclipse.swt.events.MouseEvent
+import org.eclipse.swt.layout.GridData
+import org.eclipse.swt.layout.GridLayout
+import org.eclipse.swt.widgets.Button
+import org.eclipse.swt.widgets.Composite
+import org.eclipse.swt.widgets.Event
+import org.eclipse.swt.widgets.Label
+import org.eclipse.swt.widgets.Listener
+import org.eclipse.swt.widgets.Table
+import org.eclipse.swt.widgets.TableItem
+import org.eclipse.swt.SWT
+
+/**
+ * Wizard page for the GenerateHashcodeAndEquals refactoring.
+ */
+class GenerateHashcodeAndEqualsConfigurationPage(
+ classParamNames: List[String],
+ selectedParamsObs: List[String] => Unit,
+ callSuperObs: Boolean => Unit) extends UserInputWizardPage("Generate hashCode and equals") {
+
+ val headerLabelText = "Select the class parameters to include in the hashCode() and equals() methods"
+
+ def createControl(parent: Composite) {
+ initializeDialogUnits(parent)
+
+ val composite = new Composite(parent, SWT.NONE)
+ composite.setLayout(new GridLayout(2, false))
+
+ val paramSelectionLabel = new Label(composite, SWT.WRAP)
+ paramSelectionLabel.setText(headerLabelText)
+
+ val paramSelectLabelGridData = new GridData(SWT.LEFT, SWT.CENTER, false, false, 2, 1)
+ paramSelectionLabel.setLayoutData(paramSelectLabelGridData)
+
+
+ val paramTable = new Table(composite, SWT.CHECK | SWT.BORDER)
+ val paramTableGridData = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 2)
+ paramTable.setLayoutData(paramTableGridData)
+
+ val tableItems = classParamNames.map { param =>
+ val tableItem = new TableItem(paramTable, SWT.NONE)
+ tableItem.setText(param)
+ tableItem
+ }
+
+ def updateSelectedParams() {
+ val checkedParams = tableItems.filter(_.getChecked).map(_.getText)
+ selectedParamsObs(checkedParams)
+ }
+
+ paramTable.addListener(SWT.Selection, new Listener {
+ def handleEvent(event: Event) {
+ updateSelectedParams()
+ }
+ })
+
+ val selectAllButton = new Button(composite, SWT.NONE)
+ selectAllButton.setText("Select all")
+ val selectAllButtonGridData = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1)
+ selectAllButton.setLayoutData(selectAllButtonGridData)
+ selectAllButton.addMouseListener(new MouseAdapter {
+ override def mouseUp(me: MouseEvent) = {
+ tableItems.foreach(_.setChecked(true))
+ updateSelectedParams()
+ }
+ })
+
+ val deselectAllButton = new Button(composite, SWT.NONE)
+ deselectAllButton.setText("Deselect all")
+ val deselectAllButtonGridData = new GridData(SWT.CENTER, SWT.CENTER, false, false, 1, 1)
+ deselectAllButton setLayoutData deselectAllButtonGridData
+ deselectAllButton.addMouseListener(new MouseAdapter {
+ override def mouseUp(me: MouseEvent) = {
+ tableItems.foreach(_.setChecked(false))
+ updateSelectedParams()
+ }
+ })
+
+ val superCallButton = new Button(composite, SWT.CHECK)
+ superCallButton.setText("Insert calls to super")
+ superCallButton.addMouseListener(new MouseAdapter() {
+ override def mouseUp(event: MouseEvent) {
+ callSuperObs(superCallButton.getSelection)
+ }
+ })
+
+ val superCallButtonGridData = new GridData(SWT.BEGINNING, SWT.CENTER, false, false, 1, 1)
+ superCallButton.setLayoutData(superCallButtonGridData)
+
+ setControl(composite)
+ }
+
+}
View
14 ...c/scala/tools/eclipse/refactoring/source/ui/IntroduceProductNTraitConfigurationPage.scala
@@ -0,0 +1,14 @@
+package scala.tools.eclipse.refactoring.source.ui
+
+/**
+ * Wizard page for the IntroduceProductNTrait refactoring.
+ */
+class IntroduceProductNTraitConfigurationPage(
+ classParamNames: List[String],
+ selectedParamsObs: List[String] => Unit,
+ callSuperObs: Boolean => Unit)
+ extends GenerateHashcodeAndEqualsConfigurationPage(classParamNames, selectedParamsObs, callSuperObs) {
+
+ override val headerLabelText = "Select the class parameters for the ProductN trait"
+
+}
View
140 ....core/src/scala/tools/eclipse/refactoring/ui/ExtractTraitConfigurationPageGenerator.scala
@@ -0,0 +1,140 @@
+package scala.tools.eclipse.refactoring.ui
+
+import org.eclipse.ltk.ui.refactoring.UserInputWizardPage
+import org.eclipse.swt.widgets.Composite
+import org.eclipse.swt.SWT
+import org.eclipse.swt.widgets.Label
+import org.eclipse.swt.layout.GridLayout
+import org.eclipse.swt.layout.GridData
+import org.eclipse.jface.viewers.IStructuredContentProvider
+import org.eclipse.jface.viewers.Viewer
+import org.eclipse.jface.viewers.TableViewer
+import org.eclipse.jface.viewers.TableViewerColumn
+import org.eclipse.jface.viewers.ColumnLabelProvider
+import org.eclipse.jface.viewers.ISelectionChangedListener
+import org.eclipse.jface.viewers.SelectionChangedEvent
+import org.eclipse.jface.viewers.IStructuredSelection
+import scala.tools.eclipse.refactoring.ScalaIdeRefactoring
+
+/**
+ * Generates the wizard page for a ExtractTrait refactoring.
+ */
+trait ExtractTraitConfigurationPageGenerator {
+
+ // This gives us access to refactoring.global
+ this: ScalaIdeRefactoring =>
+
+ import refactoring.global._
+
+ def mkConfigPage(
+ extractableMembers: List[ValOrDefDef],
+ selectedMembersObs: List[ValOrDefDef] => Unit,
+ extractedNameObs: String => Unit) = {
+ new ExtractTraitConfigurationPage(extractableMembers, selectedMembersObs, extractedNameObs)
+ }
+
+ /**
+ * The wizard page for ExtractTrait
+ * @param extractableMembers The members that can be extracted.
+ * @param selectedMembersObs Observer for the members currently selected for extraction.
+ * @param extractedNameObs Observer for the currently chosen name of the extracted trait.
+ */
+ class ExtractTraitConfigurationPage(
+ extractableMembers: List[ValOrDefDef],
+ selectedMembersObs: List[ValOrDefDef] => Unit,
+ extractedNameObs: String => Unit) extends UserInputWizardPage("Extract trait") {
+
+ def createControl(parent: Composite) {
+ initializeDialogUnits(parent)
+
+ val composite = new Composite(parent, SWT.NONE)
+ composite.setLayout(new GridLayout(1, false))
+
+ // Gets the name of the extracted trait.
+ val traitNamePart = new LabeledTextField(composite, extractedNameObs, "Trait name: ", "Extracted")
+ val traitNamePartLayoutData = new GridData(SWT.FILL, SWT.CENTER, true, false)
+ traitNamePart.setLayoutData(traitNamePartLayoutData)
+
+ val selectMembersLbl = new Label(composite, SWT.NONE)
+ selectMembersLbl.setText("Select members for extraction: ")
+ selectMembersLbl.setLayoutData(new GridData(SWT.BEGINNING, SWT.CENTER, false, false))
+
+ // Table presenting the extractable members for selection.
+ val membersTable = new MembersTable(composite, extractableMembers, selectedMembersObs)
+ membersTable.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true))
+
+ setControl(composite)
+ }
+
+ // Presents the extractable members along with checkboxes for selection.
+ private class MembersTable(
+ parent: Composite,
+ members: List[ValOrDefDef],
+ selectedMembersObs: List[ValOrDefDef] => Unit) extends Composite(parent, SWT.NONE) {
+
+ object ListContentProvider extends IStructuredContentProvider {
+ override def getElements(members: AnyRef): Array[AnyRef] = {
+ val elems = members.asInstanceOf[List[ValOrDefDef]]
+ Array(elems: _*)
+ }
+ override def inputChanged(viewer: Viewer, oldInput: Any, newInput: Any) {}
+ override def dispose {}
+ }
+
+ def mkTableViewerColumn(title: String) = {
+ val viewerColumn = new TableViewerColumn(viewer, SWT.NONE)
+ val column = viewerColumn.getColumn
+ column setText title
+ column setWidth 100
+ column setResizable true
+ column setMoveable true
+ viewerColumn
+ }
+
+ val viewer = new TableViewer(this, SWT.CHECK | SWT.BORDER)
+
+ val gridLayout = new GridLayout
+ setLayout(gridLayout)
+
+ val nameColumn = mkTableViewerColumn("Member")
+ nameColumn.setLabelProvider(new ColumnLabelProvider() {
+ override def getText(element: Any): String = element match {
+ case v @ ValDef(_, name, tpt, _) => {
+ val varType = if(v.symbol.isMutable) {"var "} else {"val "}
+ varType + name.toString.trim + ": " + tpt.symbol.nameString
+ }
+ case DefDef(_, name, tparams, vparamss, tpt, _) =>
+ val tparamsStr = tparams match {
+ case Nil => ""
+ case _ => "[" + tparams.map(_.symbol.nameString).mkString(", ") + "]"
+ }
+ val vparamssStr = vparamss.map( vparams =>
+ "(" + vparams.map(_.tpt.symbol.nameString).mkString(", ") + ")"
+ ).mkString("")
+ "def " + name + tparamsStr + vparamssStr + ": " + tpt.symbol.nameString
+ case _ => ""
+ }
+ })
+
+ val table = viewer.getTable
+ table setLinesVisible true
+
+ viewer setContentProvider ListContentProvider
+ viewer.getControl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true))
+ viewer.setInput(members)
+
+ val rows = List(table.getItems: _*)
+
+ viewer.addSelectionChangedListener(new ISelectionChangedListener() {
+ override def selectionChanged(event: SelectionChangedEvent) {
+ val selectedMembersIndices = rows.map(_.getChecked).zipWithIndex.collect { case (true, index) => index }
+ val selectedMembers = selectedMembersIndices.map(members)
+ selectedMembersObs(selectedMembers)
+ }
+ })
+
+ }
+
+ }
+}
+

0 comments on commit 0f91849

Please sign in to comment.