package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.appendTypeName; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getImageForDeclaration; import static com.redhat.ceylon.eclipse.util.Nodes.getReferencedNode; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isConstructor; import static org.eclipse.jface.viewers.ArrayContentProvider.getInstance; import static org.eclipse.swt.layout.GridData.FILL_BOTH; import static org.eclipse.swt.layout.GridData.FILL_HORIZONTAL; import static org.eclipse.swt.layout.GridData.HORIZONTAL_ALIGN_FILL; import static org.eclipse.swt.layout.GridData.VERTICAL_ALIGN_BEGINNING; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.antlr.runtime.ANTLRStringStream; import org.antlr.runtime.CommonToken; import org.antlr.runtime.CommonTokenStream; import org.eclipse.core.resources.IProject; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.viewers.CellEditor; import org.eclipse.jface.viewers.ColumnLabelProvider; import org.eclipse.jface.viewers.ComboBoxCellEditor; import org.eclipse.jface.viewers.EditingSupport; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.jface.viewers.TextCellEditor; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.jface.window.Window; import org.eclipse.ltk.ui.refactoring.UserInputWizardPage; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.Image; 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.widgets.Table; import org.eclipse.text.edits.ReplaceEdit; import com.redhat.ceylon.compiler.typechecker.analyzer.ExpressionVisitor; import com.redhat.ceylon.compiler.typechecker.analyzer.TypeVisitor; import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer; import com.redhat.ceylon.compiler.typechecker.parser.CeylonParser; import com.redhat.ceylon.compiler.typechecker.parser.LexError; import com.redhat.ceylon.compiler.typechecker.parser.ParseError; import com.redhat.ceylon.compiler.typechecker.tree.Message; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.util.ErrorVisitor; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.eclipse.util.Nodes; import com.redhat.ceylon.ide.common.model.CeylonUnit; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.Cancellable; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.FunctionOrValue; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.Reference; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.Value; public class ChangeParametersInputPage extends UserInputWizardPage { private StyledText signatureText; public ChangeParametersInputPage(String name) { super(name); } private boolean isDefaulted( List<Parameter> parameterModels, Parameter parameter) { return getChangeParametersRefactoring() .getDefaulted() .get(parameterModels.indexOf(parameter)); } public void createControl(Composite parent) { final ChangeParametersRefactoring refactoring = getChangeParametersRefactoring(); Composite result = new Composite(parent, SWT.NONE); setControl(result); GridLayout layout = new GridLayout(); layout.numColumns = 2; result.setLayout(layout); Label title = new Label(result, SWT.LEFT); Declaration dec = refactoring.getDeclaration(); String name = dec.getName(); if (name == null && isConstructor(dec)) { Scope container = dec.getContainer(); if (container instanceof Declaration) { Declaration cd = (Declaration) container; name = cd.getName(); } } title.setText("Change parameters in " + refactoring.getCount() + " occurrences of '" + name + "'."); GridData gd = new GridData(); gd.horizontalSpan=2; title.setLayoutData(gd); GridData gd2 = new GridData(FILL_HORIZONTAL); gd2.horizontalSpan=2; new Label(result, SWT.SEPARATOR | SWT.HORIZONTAL) .setLayoutData(gd2); Composite composite = new Composite(result, SWT.NONE); GridData cgd = new GridData(FILL_BOTH); cgd.grabExcessHorizontalSpace = true; cgd.grabExcessVerticalSpace = true; composite.setLayoutData(cgd); GridLayout tableLayout = new GridLayout(4, true); tableLayout.marginWidth=0; composite.setLayout(tableLayout); final List<Parameter> parameterModels = new ArrayList<Parameter>( refactoring.getParameters()); final TableViewer viewer = new TableViewer(composite, SWT.SINGLE | SWT.V_SCROLL | SWT.BORDER); viewer.setContentProvider(getInstance()); viewer.getTable().setHeaderVisible(true); viewer.getTable().setLinesVisible(true); GridData tgd = new GridData(FILL_BOTH); tgd.horizontalSpan = 3; tgd.verticalSpan = 6; tgd.grabExcessHorizontalSpace = true; // gd.grabExcessVerticalSpace = true; tgd.heightHint = 100; tgd.widthHint = 425; viewer.getTable().setLayoutData(tgd); TableViewerColumn orderCol = new TableViewerColumn(viewer, SWT.LEFT); orderCol.getColumn().setText(""); orderCol.getColumn().setWidth(10); orderCol.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { int originalIndex = refactoring.getParameters() .indexOf(element); int currentIndex = parameterModels.indexOf(element); if (originalIndex==currentIndex) { return ""; } else { return originalIndex>currentIndex ? "\u2191" : "\u2193"; } } @Override public Image getImage(Object element) { return null; } }); TableViewerColumn sigCol = new TableViewerColumn(viewer, SWT.LEFT); sigCol.getColumn().setText("Type"); sigCol.getColumn().setWidth(140); sigCol.setLabelProvider( new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { Parameter p = (Parameter) cell.getElement(); FunctionOrValue model = p.getModel(); StyledString styledString = new StyledString(); Reference ref = model.appliedReference(null, Collections.<Type>emptyList()); Type fullType = ref.getFullType(); appendTypeName(styledString, fullType, model.getUnit()); cell.setImage(getImageForDeclaration( fullType.getDeclaration())); cell.setText(styledString.toString()); cell.setStyleRanges(styledString.getStyleRanges()); super.update(cell); } }); TableViewerColumn nameCol = new TableViewerColumn(viewer, SWT.LEFT); nameCol.getColumn().setText("Name"); nameCol.getColumn().setWidth(100); nameCol.setLabelProvider( new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { Parameter p = (Parameter) cell.getElement(); String name = getChangeParametersRefactoring() .getNames() .get(parameterModels.indexOf(p)); StyledString styledString = new StyledString(); styledString.append(name, Highlights.ID_STYLER); cell.setText(styledString.toString()); cell.setStyleRanges(styledString.getStyleRanges()); super.update(cell); } }); nameCol.setEditingSupport(new EditingSupport(viewer) { @Override protected CellEditor getCellEditor(Object element) { Table table = viewer.getTable(); return new TextCellEditor(table, SWT.FLAT); } @Override protected boolean canEdit(Object element) { return true; } @Override protected Object getValue(Object element) { Parameter p = (Parameter) element; int index = parameterModels.indexOf(p); return getChangeParametersRefactoring() .getNames() .get(index); } @Override protected void setValue(Object element, Object value) { Parameter p = (Parameter) element; int index = parameterModels.indexOf(p); getChangeParametersRefactoring() .getNames() .set(index, value.toString()); viewer.update(element, null); drawSignature(); } }); TableViewerColumn optCol = new TableViewerColumn(viewer, SWT.LEFT); optCol.getColumn().setText("Optionality"); optCol.getColumn().setWidth(70); optCol.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { Parameter p = (Parameter) element; return isDefaulted(parameterModels, p) ? "defaulted" : "required"; } @Override public Image getImage(Object element) { return null; } }); final String[] options = new String[] {"required", "defaulted"}; optCol.setEditingSupport(new EditingSupport(viewer) { @Override protected CellEditor getCellEditor(Object element) { Table table = viewer.getTable(); return new ComboBoxCellEditor( table, options, SWT.FLAT); } @Override protected boolean canEdit(Object element) { return true; } @Override protected Object getValue(Object element) { Parameter p = (Parameter) element; return isDefaulted(parameterModels, p) ? 1 : 0; } @Override protected void setValue(Object element, Object value) { int index = parameterModels.indexOf(element); refactoring.getDefaulted() .set(index, value.equals(1)); viewer.update(element, null); drawSignature(); } }); TableViewerColumn diffCol = new TableViewerColumn(viewer, SWT.LEFT); diffCol.getColumn().setText(""); diffCol.getColumn().setWidth(15); diffCol.setLabelProvider( new ColumnLabelProvider() { @Override public String getText(Object element) { Parameter p = (Parameter) element; boolean def = isDefaulted(parameterModels, p); if (!def && p.isDefaulted()) { return "\u2296"; } if (def && !p.isDefaulted()) { return "\u2295"; } if (def && refactoring.defaultHasChanged(p)) { return ">"; } return ""; } @Override public Image getImage(Object element) { /*Parameter p = (Parameter) element; boolean def = isDefaulted(parameterModels, p); if (p.isDefaulted()) { return def ? null : REMOVE_CORR; } else { return def ? ADD_CORR : null; }*/ return null; } }); TableViewerColumn argCol = new TableViewerColumn(viewer, SWT.LEFT); argCol.getColumn().setText("Default Argument"); argCol.getColumn().setWidth(100); argCol.setLabelProvider( new StyledCellLabelProvider() { @Override public void update(ViewerCell cell) { StyledString styledString = new StyledString(); String text = getText(cell.getElement()); if (text!=null) { Highlights.styleFragment( styledString, text, false, null, CeylonPlugin.getOutlineFont()); } cell.setText(styledString.toString()); cell.setStyleRanges(styledString.getStyleRanges()); super.update(cell); } public String getText(Object element) { Parameter p = (Parameter) element; FunctionOrValue model = p.getModel(); if (isDefaulted(parameterModels, p)) { return refactoring.getDefaultArgs() .get(model); } else { return null; } } }); argCol.setEditingSupport(new EditingSupport(viewer) { @Override protected CellEditor getCellEditor(Object element) { Table table = viewer.getTable(); return new TextCellEditor(table, SWT.FLAT); } @Override protected boolean canEdit(Object element) { Parameter p = (Parameter) element; return isDefaulted(parameterModels, p); } @Override protected Object getValue(Object element) { Parameter p = (Parameter) element; FunctionOrValue model = p.getModel(); if (isDefaulted(parameterModels, p)) { String arg = refactoring.getDefaultArgs() .get(model); return arg==null ? "" : arg; } else { return ""; } } @Override protected void setValue(Object element, Object value) { Parameter p = (Parameter) element; String str = (String) value; updateArgument(p, str, refactoring); FunctionOrValue model = p.getModel(); refactoring.getDefaultArgs().put(model, str); viewer.update(element, null); drawSignature(); } }); viewer.setInput(parameterModels); final Button upButton = new Button(composite, SWT.PUSH); upButton.setText("Up"); int alignment = VERTICAL_ALIGN_BEGINNING | HORIZONTAL_ALIGN_FILL; GridData bgd = new GridData(alignment); bgd.grabExcessHorizontalSpace=false; bgd.widthHint = 50; upButton.setLayoutData(bgd); upButton.addSelectionListener( new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { Table table = viewer.getTable(); int[] indices = table.getSelectionIndices(); if (indices.length>0 && indices[0]>0) { int index = indices[0]; List<Integer> order = refactoring.getOrder(); List<Boolean> defaulted = refactoring.getDefaulted(); List<String> names = refactoring.getNames(); Parameter p = parameterModels.remove(index); parameterModels.add(index-1, p); order.add(index-1, order.remove(index)); defaulted.add(index-1, defaulted.remove(index)); names.add(index-1, names.remove(index)); viewer.refresh(); table.select(index-1); } drawSignature(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); final Button downButton = new Button(composite, SWT.PUSH); downButton.setText("Down"); downButton.setLayoutData(bgd); downButton.addSelectionListener( new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { Table table = viewer.getTable(); int[] indices = table.getSelectionIndices(); int lastIndex = table.getItemCount()-1; if (indices.length>0 && indices[0]<lastIndex) { int index = indices[0]; List<Integer> order = refactoring.getOrder(); List<Boolean> defaulted = refactoring.getDefaulted(); List<String> names = refactoring.getNames(); Parameter p = parameterModels.remove(index); parameterModels.add(index+1, p); order.add(index+1, order.remove(index)); defaulted.add(index+1, defaulted.remove(index)); names.add(index+1, names.remove(index)); viewer.refresh(); table.select(index+1); } drawSignature(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); final Button addButton = new Button(composite, SWT.PUSH); addButton.setText("Add..."); addButton.setLayoutData(bgd); final Button removeButton = new Button(composite, SWT.PUSH); removeButton.setText("Remove"); removeButton.setLayoutData(bgd); new Label(composite, SWT.NONE); final Button toggleButton = new Button(composite, SWT.PUSH); toggleButton.setText("Toggle Optionality"); toggleButton.setLayoutData(bgd); toggleButton.addSelectionListener( new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { Table table = viewer.getTable(); int[] indices = table.getSelectionIndices(); if (indices.length>0) { int index = indices[0]; List<Boolean> defaulted = refactoring.getDefaulted(); defaulted.set(index, !defaulted.get(index)); viewer.refresh(); } drawSignature(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); removeButton.addSelectionListener( new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { Table table = viewer.getTable(); int[] indices = table.getSelectionIndices(); int lastIndex = table.getItemCount()-1; if (indices.length>0 && indices[0]<=lastIndex) { int index = indices[0]; Parameter p = parameterModels.remove(index); refactoring.getDefaulted().remove(index); refactoring.getOrder().remove(index); refactoring.getNames().remove(index); viewer.remove(p); viewer.refresh(); if (parameterModels.isEmpty()) { toggleButton.setEnabled(false); removeButton.setEnabled(false); } if (parameterModels.size()<2) { upButton.setEnabled(false); downButton.setEnabled(false); } } drawSignature(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); addButton.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { Set<String> names = new HashSet<String>(); for (Parameter p: parameterModels) { names.add(p.getName()); } Node node = refactoring.node; IProject project = refactoring.project; AddParameterDialog dialog = new AddParameterDialog(getShell(), node, project, names); if (dialog.open()==Window.OK) { String name = dialog.getName(); Type type = dialog.getType(); String arg = dialog.getArgument(); Value model = new Value(); model.setType(type); model.setName(name); Scope scope = node.getScope(); model.setContainer(scope); model.setScope(scope); Parameter p = new Parameter(); p.setModel(model); p.setName(name); p.setDefaulted(false); p.setDeclaration((Declaration) scope); model.setInitializerParameter(p); int index = parameterModels.size(); int order = refactoring.getParameters() .size(); parameterModels.add(p); refactoring.getParameters() .add(p); refactoring.getDefaulted() .add(false); refactoring.getNames() .add(name); refactoring.getArguments() .put(p.getModel(), arg); refactoring.getDefaultArgs() .put(model, arg); refactoring.getOrder() .add(index, order); viewer.add(p); viewer.refresh(); viewer.getTable().select(index); if (index==1) { upButton.setEnabled(true); downButton.setEnabled(true); } if (index==0) { toggleButton.setEnabled(true); removeButton.setEnabled(true); } } drawSignature(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); if (parameterModels.isEmpty()) { toggleButton.setEnabled(false); removeButton.setEnabled(false); } else { viewer.getTable().setSelection(0); } if (parameterModels.size()<2) { upButton.setEnabled(false); downButton.setEnabled(false); } new Label(composite, SWT.SEPARATOR | SWT.HORIZONTAL) .setLayoutData(GridDataFactory.fillDefaults() .span(4, 1).create()); Label l = new Label(composite, SWT.NONE); l.setLayoutData(GridDataFactory.fillDefaults() .span(4, 1).create()); l.setText("Refactored signature preview:"); signatureText = new StyledText(composite, SWT.FLAT | SWT.READ_ONLY | SWT.WRAP); signatureText.setLayoutData(GridDataFactory.fillDefaults() .hint(300, 50).grab(true, true) .span(4, 3).create()); signatureText.setBackground(composite.getBackground()); drawSignature(); } private void drawSignature() { ChangeParametersRefactoring ref = getChangeParametersRefactoring(); Declaration declaration = ref.getDeclaration(); Node decNode = getReferencedNode(declaration); Tree.ParameterList pl; int startIndex; if (decNode instanceof Tree.AnyMethod) { Tree.AnyMethod m = (Tree.AnyMethod) decNode; pl = m.getParameterLists().get(0); startIndex = m.getType().getStartIndex(); } else if (decNode instanceof Tree.AnyClass) { Tree.AnyClass c = (Tree.AnyClass) decNode; pl = c.getParameterList(); startIndex = ((CommonToken) c.getMainToken()) .getStartIndex(); } else if (decNode instanceof Tree.Constructor) { Tree.Constructor c = (Tree.Constructor) decNode; pl = c.getParameterList(); startIndex = ((CommonToken) c.getMainToken()) .getStartIndex(); } else { return; } CeylonUnit ceylonUnit = (CeylonUnit) declaration.getUnit(); List<CommonToken> tokens = ceylonUnit.getPhasedUnit().getTokens(); Tree.Parameter[] reorderedParameters = ref.reorderedParameters(pl.getParameters()); ReplaceEdit edit = ref.reorderParamsEdit(pl, reorderedParameters, false, tokens); int start = startIndex - decNode.getStartIndex(); int end = pl.getStartIndex() - decNode.getStartIndex(); String text = Nodes.text(decNode, tokens) .substring(start,end) + edit.getText(); StyledString styledString = new StyledString(); Highlights.styleFragment(styledString, text, false, null, CeylonPlugin.getEditorFont()); signatureText.setText(styledString.getString()); signatureText.setStyleRanges(styledString.getStyleRanges()); signatureText.setFont(CeylonPlugin.getEditorFont()); } private ChangeParametersRefactoring getChangeParametersRefactoring() { return (ChangeParametersRefactoring) getRefactoring(); } private boolean updateArgument(final Parameter parameter, final String text, final ChangeParametersRefactoring refactoring) { try { if (text.isEmpty()) { return true; // setErrorMessage("Missing argument expression"); // return false; } String typeExpression = parameter.getType().asString(); String parameterName = parameter.getName(); String paramDeclaration = "(" + typeExpression + " " + parameterName + " = " + text + ")"; ANTLRStringStream stream = new ANTLRStringStream(paramDeclaration); CeylonLexer lexer = new CeylonLexer(stream); CommonTokenStream ts = new CommonTokenStream(lexer); ts.fill(); List<LexError> lexErrors = lexer.getErrors(); if (!lexErrors.isEmpty()) { LexError lexError = lexErrors.get(0); setErrorMessage(lexError.getMessage()); return false; } CeylonParser parser = new CeylonParser(ts); Tree.ParameterList parameters = parser.parameters(); if (ts.index()<ts.size()-1) { setErrorMessage( "extra tokens in argument expression"); return false; } List<ParseError> parseErrors = parser.getErrors(); if (!parseErrors.isEmpty()) { ParseError parseError = parseErrors.get(0); setErrorMessage(parseError.getMessage()); return false; } Node node = refactoring.node; final Unit unit = node.getUnit(); final Scope scope = node.getScope(); final Value value = new Value(); value.setName(parameterName); final Parameter param = new Parameter(); param.setName(parameterName); param.setModel(value); parameters.visit(new Visitor() { @Override public void visitAny(Node that) { that.setUnit(unit); that.setScope(scope); super.visitAny(that); } @Override public void visit( Tree.AttributeDeclaration that) { that.setDeclarationModel(value); super.visit(that); } @Override public void visit( Tree.ParameterDeclaration that) { that.setParameterModel(param); super.visit(that); } }); parameters.visit(new TypeVisitor(unit, Cancellable.ALWAYS_CANCELLED)); parameters.visit(new ExpressionVisitor(unit, Cancellable.ALWAYS_CANCELLED)); setErrorMessage(null); new ErrorVisitor() { @Override protected void handleMessage( int startOffset, int endOffset, int startCol, int startLine, Message error) { setErrorMessage(error.getMessage()); } }.visit(parameters); if (getErrorMessage()!=null) { return false; } else { return true; } } catch (Exception e) { setErrorMessage("Could not parse argument expression"); return false; } } }