package com.redhat.ceylon.eclipse.code.style;
import static com.redhat.ceylon.eclipse.code.style.CeylonFormatterConstants.*;
import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.internal.ui.util.SWTUtil;
import org.eclipse.jface.dialogs.ErrorDialog;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.dialogs.StatusDialog;
import org.eclipse.jface.layout.PixelConverter;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import ceylon.formatter.options.loadProfile_;
import ceylon.formatter.options.saveProfile_;
import com.redhat.ceylon.common.Constants;
import com.redhat.ceylon.eclipse.code.style.FormatterProfileManager.Profile;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
/**
* The Ceylon code formatter preference page.
*/
public class FormatterConfigurationBlock extends StyleBlock {
public static final String FORMATTER_PROFILE_SUFFIX = ".format";
private Combo fProfileCombo;
private Button fEditButton;
private Button fDeleteButton;
private Button fNewButton;
private Button fLoadButton;
private Button fExportButton;
private PixelConverter fPixConv;
private FormatterProfileManager fFormatterProfileManager = null;
/**
* Some Ceylon source code used for preview, taken from FormatterTabMisc.
*/
protected final String PREVIEW =
"import ceylon.collection { HashSet, LinkedList }\n"
+ "\n"
+ "\"Tests the Collatz conjecture for all numbers up to the first argument\n"
+ " (defaulting to 100).\n"
+ " \n"
+ " The Collatz conjecture states that if you start at any number,\n"
+ " and repeatedly\n"
+ " \n"
+ " - divide it by 2 if it’s even, or\n"
+ " - multiply it by 3 and add 1 if it’s odd,\n"
+ " \n"
+ " then you’ll always arrive at the cycle 1 4 2.\"\n"
+ "throws (`class AssertionError`, \"If another cycle is found\")\n"
+ "by (\"Lothar Collatz\")\n"
+ "deprecated (\"Very inefficient\")\n"
+ "shared void run() {\n"
+ " value max = parseInteger(process.arguments.first else \"100\") else 100;\n"
+ " HashSet<Integer[]> cycles = HashSet<Integer[]>();\n"
+ " for (start in 1..max) {\n"
+ " variable Integer i = start;\n"
+ " HashSet<Integer> current = HashSet<Integer>();\n"
+ " while (!i in current) {\n"
+ " current.add(i);\n"
+ " if (i % 2 == 0) {\n"
+ " i /= 2;\n"
+ " } else {\n"
+ " i = 3 * i + 1;\n"
+ " }\n"
+ " }\n"
+ " LinkedList<Integer> cycle = LinkedList<Integer>();\n"
+ " while (!i in cycle) {\n"
+ " cycle.add(i);\n"
+ " if (i % 2 == 0) {\n"
+ " i /= 2;\n"
+ " } else {\n"
+ " i = 3 * i + 1;\n"
+ " }\n"
+ " }\n"
+ " assert (exists smallestFromCycle = cycle.sort(byIncreasing(identity<Integer>)).first);\n"
+ " i = smallestFromCycle;\n"
+ " LinkedList<Integer> orderedCycle = LinkedList<Integer>();\n"
+ " while (!i in orderedCycle) {\n"
+ " orderedCycle.add(i);\n"
+ " if (i % 2 == 0) {\n"
+ " i /= 2;\n"
+ " } else {\n"
+ " i = 3 * i + 1;\n"
+ " }\n"
+ " }\n"
+ " cycles.add(orderedCycle.sequence());\n"
+ " }\n"
+ " \"Conjecture\"\n"
+ " assert (cycles.size == 1);\n"
+ "}\n";
private class PreviewController implements Observer {
public PreviewController(FormatterProfileManager profileManager) {
profileManager.addObserver(this);
ceylonPreview.setWorkingValues(new FormatterPreferences(
profileManager.getSelected().getSettings()));
ceylonPreview.update();
}
public void update(Observable o, Object arg) {
final int value = ((Integer) arg).intValue();
switch (value) {
case FormatterProfileManager.PROFILE_CREATED_EVENT:
case FormatterProfileManager.PROFILE_DELETED_EVENT:
case FormatterProfileManager.SELECTION_CHANGED_EVENT:
case FormatterProfileManager.SETTINGS_CHANGED_EVENT:
ceylonPreview.setWorkingValues(new FormatterPreferences(
((FormatterProfileManager) o).getSelected()
.getSettings()));
ceylonPreview.update();
}
}
}
/**
* The CeylonPreview.
*/
private CeylonPreview ceylonPreview;
public FormatterConfigurationBlock(IProject project) {
if (project != null) {
super.project = project;
enableProjectSettings();
}
initialize();
List<Profile> profiles = new ArrayList<Profile>();
File[] profileFiles = null;
if (project.getLocation().toFile().isDirectory()) {
File ceylonConfigDir = new File(project.getLocation().toFile(),
Constants.CEYLON_CONFIG_DIR);
if (ceylonConfigDir.isDirectory()) {
profileFiles = ceylonConfigDir.listFiles(new FileFilter() {
@Override
public boolean accept(File f) {
if (f.isFile()
&& f.getName().endsWith(
FORMATTER_PROFILE_SUFFIX)) {
return true;
} else {
return false;
}
}
});
}
}
if (profileFiles != null) {
for (File pf : profileFiles) {
String profileName = pf.getName().substring(
0,
pf.getName().length()
- FORMATTER_PROFILE_SUFFIX.length());
if (DEFAULT_PROFILE_NAME.equals(profileName)) {
continue; // 'default.format' can be copied manually in the
// OS FS
}
Profile projectProfile = new FormatterProfileManager.Profile(
profileName, loadProfile_.loadProfile(profileName,
false, project.getLocation().toFile()
.getAbsolutePath()), 1, 0,
FormatterProfileManager.CEYLON_FORMATTER_VERSION);
profiles.add(projectProfile);
}
}
// profiles may have been deleted, cannot rely on selected profile in
// .style
String candidateProfile = CeylonStyle.getFormatterProfile(project);
String activeProfile = DEFAULT_PROFILE_NAME;
for (Profile cpf : profiles) {
if (cpf.getName().equals(candidateProfile)) {
activeProfile = candidateProfile;
}
}
fFormatterProfileManager = createFormatterProfileManager(profiles,
activeProfile);
}
protected FormatterProfileManager createFormatterProfileManager(
List<Profile> profiles, String activeProfile) {
return new FormatterProfileManager(profiles, activeProfile);
}
protected void configurePreview(Composite composite, int numColumns,
FormatterProfileManager FormatterProfileManager) {
createLabel(composite, "Preview", numColumns);
CeylonPreview result = new CeylonPreview(new FormatterPreferences(
FormatterProfileManager.getSelected().getSettings()), composite);
result.setPreviewText(PREVIEW);
ceylonPreview = result;
final GridData gd = new GridData(GridData.FILL_VERTICAL
| GridData.HORIZONTAL_ALIGN_FILL);
gd.horizontalSpan = numColumns;
gd.verticalSpan = 7;
gd.widthHint = 0;
gd.heightHint = 0;
ceylonPreview.getControl().setLayoutData(gd);
new PreviewController(FormatterProfileManager);
}
protected static Label createLabel(Composite composite, String text,
int numColumns) {
final GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL);
gd.horizontalSpan = numColumns;
gd.widthHint = 0;
final Label label = new Label(composite, SWT.WRAP);
label.setFont(composite.getFont());
label.setText(text);
label.setLayoutData(gd);
return label;
}
protected FormatterModifyProfileDialog createModifyDialog(Shell shell,
Profile profile, FormatterProfileManager profileManager,
boolean newProfile, boolean projectSpecific) {
return new FormatterModifyProfileDialog(shell, profile, profileManager,
newProfile, projectSpecific);
}
@Override
public void initialize() {
// all in constructor
}
@Override
protected Control createContents(Composite parent) {
setShell(parent.getShell());
final int numColumns = 5;
fPixConv = new PixelConverter(parent);
block = createComposite(parent, numColumns);
Label profileLabel = new Label(block, SWT.NONE);
profileLabel.setText("A&ctive profile:");
GridData data = new GridData(SWT.FILL, SWT.FILL, true, false);
data.horizontalSpan = numColumns;
profileLabel.setLayoutData(data);
fProfileCombo = createProfileCombo(block, 3,
fPixConv.convertWidthInCharsToPixels(20));
fEditButton = createButton(block, "&Edit...",
GridData.HORIZONTAL_ALIGN_BEGINNING);
fDeleteButton = createButton(block, "&Remove",
GridData.HORIZONTAL_ALIGN_BEGINNING);
fNewButton = createButton(block, "Ne&w...",
GridData.HORIZONTAL_ALIGN_BEGINNING);
fLoadButton = createButton(block, "I&mport...",
GridData.HORIZONTAL_ALIGN_END);
fExportButton = createButton(block, "E&xport...",
GridData.HORIZONTAL_ALIGN_BEGINNING);
createLabel(block, "", 3);
configurePreview(block, numColumns, fFormatterProfileManager);
new ButtonController();
new ProfileComboController();
return block;
}
private static Combo createProfileCombo(Composite composite, int span,
int widthHint) {
final GridData gd = new GridData(GridData.FILL_HORIZONTAL);
gd.horizontalSpan = span;
gd.widthHint = widthHint;
final Combo combo = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY);
combo.setFont(composite.getFont());
combo.setLayoutData(gd);
return combo;
}
private static Button createButton(Composite composite, String text,
final int style) {
final Button button = new Button(composite, SWT.PUSH);
button.setFont(composite.getFont());
button.setText(text);
final GridData gd = new GridData(style);
gd.widthHint = SWTUtil.getButtonWidthHint(button);
button.setLayoutData(gd);
return button;
}
class ProfileComboController implements Observer, SelectionListener {
private final List<Profile> fSortedProfiles;
public ProfileComboController() {
fSortedProfiles = fFormatterProfileManager.getSortedProfiles();
fProfileCombo.addSelectionListener(this);
fFormatterProfileManager.addObserver(this);
updateProfiles();
updateSelection();
}
public void widgetSelected(SelectionEvent e) {
final int index = fProfileCombo.getSelectionIndex();
fFormatterProfileManager.setSelected((Profile) fSortedProfiles
.get(index));
}
public void widgetDefaultSelected(SelectionEvent e) {
}
public void update(Observable o, Object arg) {
if (arg == null)
return;
final int value = ((Integer) arg).intValue();
switch (value) {
case FormatterProfileManager.PROFILE_CREATED_EVENT:
case FormatterProfileManager.PROFILE_DELETED_EVENT:
case FormatterProfileManager.PROFILE_RENAMED_EVENT:
updateProfiles();
updateSelection();
break;
case FormatterProfileManager.SELECTION_CHANGED_EVENT:
updateSelection();
break;
}
}
private void updateProfiles() {
fProfileCombo.setItems(fFormatterProfileManager
.getSortedDisplayNames());
}
private void updateSelection() {
fProfileCombo.setText(fFormatterProfileManager.getSelected()
.getName());
}
}
class ButtonController implements Observer, SelectionListener {
public ButtonController() {
fFormatterProfileManager.addObserver(this);
fNewButton.addSelectionListener(this);
fEditButton.addSelectionListener(this);
fDeleteButton.addSelectionListener(this);
fLoadButton.addSelectionListener(this);
fExportButton.addSelectionListener(this);
update(fFormatterProfileManager, null);
}
public void update(Observable o, Object arg) {
Profile selected = ((FormatterProfileManager) o).getSelected();
final boolean notBuiltIn = !selected.isBuiltInProfile();
fDeleteButton.setEnabled(notBuiltIn);
fEditButton.setEnabled(notBuiltIn);
fNewButton.setEnabled(true);
fLoadButton.setEnabled(true);
fExportButton.setEnabled(true);
}
public void widgetSelected(SelectionEvent e) {
final Button button = (Button) e.widget;
if (button == fEditButton)
modifyButtonPressed();
else if (button == fDeleteButton)
deleteButtonPressed();
else if (button == fNewButton)
newButtonPressed();
else if (button == fLoadButton)
loadButtonPressed();
else if (button == fExportButton)
exportButtonPressed();
}
private void exportButtonPressed() {
final FileDialog dialog = new FileDialog(block.getShell(), SWT.SAVE);
dialog.setText("Export Ceylon Formatter profile");
dialog.setFilterExtensions(new String[] { "*"
+ FORMATTER_PROFILE_SUFFIX });
String lastPath = project.getLocation().toFile().getAbsolutePath();
if (lastPath != null) {
dialog.setFilterPath(lastPath);
}
final String path = dialog.open();
if (path == null)
return;
final File file = new File(path);
if (file.exists()
&& !MessageDialog.openQuestion(block.getShell(),
"Overwrite Profile?",
"Overwrite Profile " + file.getPath() + "?")) {
return;
}
try {
saveProfile_.saveProfile(fFormatterProfileManager.getSelected()
.getSettings(), fFormatterProfileManager.getSelected()
.getName(), file.isDirectory() ? file.getAbsolutePath()
: file.getParent(), file.getName());
} catch (Exception e) {
final String title = "Error exporting profile";
final String message = "There was an error exporting the profile: "
+ e.getMessage();
handleException(
new Status(IStatus.ERROR, CeylonPlugin.PLUGIN_ID, message),
block.getShell(), title, message);
}
}
public void widgetDefaultSelected(SelectionEvent e) {
}
private void modifyButtonPressed() {
final StatusDialog modifyDialog = createModifyDialog(
block.getShell(), fFormatterProfileManager.getSelected(),
fFormatterProfileManager, false, projectSettings);
modifyDialog.open();
}
private void deleteButtonPressed() {
if (MessageDialog.openQuestion(block.getShell(), "Confirm Remove",
"Are you sure you want to remove profile "
+ fFormatterProfileManager.getSelected().getName()
+ "?")) {
if (deleteProfile(project, fFormatterProfileManager
.getSelected().getName())) {
fFormatterProfileManager.deleteSelected();
}
}
}
private boolean deleteProfile(IProject project, String profileName) {
if (project.getLocation().toFile().isDirectory()) {
File ceylonConfigDir = new File(project.getLocation().toFile(),
Constants.CEYLON_CONFIG_DIR);
if (ceylonConfigDir.isDirectory()) {
try {
File toBeDeleted = new File(ceylonConfigDir,
profileName + FORMATTER_PROFILE_SUFFIX);
toBeDeleted.delete();
return true;
} catch (Exception e) {
final String title = "Error deleting profile";
final String message = "There was an error deleting profile "
+ profileName + " : " + e.getMessage();
handleException(new Status(IStatus.ERROR, CeylonPlugin.PLUGIN_ID, message),
block.getShell(), title, message);
return false;
}
}
}
return false;
}
private void newButtonPressed() {
final FormatterCreateProfileDialog p = new FormatterCreateProfileDialog(
block.getShell(), fFormatterProfileManager, false);
if (p.open() != Window.OK)
return;
if (!p.openEditDialog())
return;
final StatusDialog modifyDialog = createModifyDialog(
block.getShell(), p.getCreatedProfile(),
fFormatterProfileManager, true, projectSettings);
modifyDialog.open();
}
private void loadButtonPressed() {
final FileDialog dialog = new FileDialog(block.getShell(), SWT.OPEN);
dialog.setText("Load Ceylon Formatter profile");
dialog.setFilterExtensions(new String[] { "*"
+ FORMATTER_PROFILE_SUFFIX });
// TODO find last path
String lastPath = project.getLocation().toFile().getAbsolutePath();
if (lastPath != null) {
dialog.setFilterPath(lastPath);
}
final String path = dialog.open();
if (path == null)
return;
final File file = new File(path);
Profile profile = null;
String profileName = UNNAMED_PROFILE_NAME;
String fileName = file.getName();
if (fileName.length() > FORMATTER_PROFILE_SUFFIX.length()
&& fileName.endsWith(FORMATTER_PROFILE_SUFFIX)) {
profileName = fileName.substring(0, fileName.length()
- FORMATTER_PROFILE_SUFFIX.length());
}
try {
profile = new Profile(profileName, loadProfile_.loadProfile(
profileName, false, file.getParent()), 1, 0,
FormatterProfileManager.CEYLON_FORMATTER_VERSION);
} catch (Exception e) {
final String title = "Error importing profile";
final String message = "There was an error importing the profile from file "
+ file.getName();
handleException(new Status(
IStatus.ERROR, CeylonPlugin.PLUGIN_ID, message),
block.getShell(), title, e.getMessage());
}
if (profile == null) {
return;
}
if (fFormatterProfileManager.containsName(profile.getName())
|| UNNAMED_PROFILE_NAME.equals(profile.getName())) {
final FormatterProfileAlreadyExistsDialog aeDialog = new FormatterProfileAlreadyExistsDialog(
block.getShell(), profile, fFormatterProfileManager);
if (aeDialog.open() != Window.OK)
return;
}
try {
CeylonStyle.writeProfileToFile(profile, project.getLocation()
.toFile());
fFormatterProfileManager.addProfile(profile);
} catch (CoreException ce) {
handleException(ce.getStatus(), block.getShell(),
"Error importing into prject",
"There was an error importing profile to project : "
+ project.getName());
}
}
}
@Override
public boolean performApply() {
try {
// the two possible values other than a real profile
Profile profile = fFormatterProfileManager.getSelected();
if (profile != null) {
if (profile.getName() != DEFAULT_PROFILE_NAME
&& profile.getName() != UNNAMED_PROFILE_NAME) {
CeylonStyle.writeProfileToFile(fFormatterProfileManager
.getSelected(), project.getLocation().toFile());
}
// save the chosen profile name to Ceylon config
// separate step for clarity
if (profile.getName() != UNNAMED_PROFILE_NAME) {
CeylonStyle.setFormatterProfile(project,
fFormatterProfileManager.getSelected()
.getName());
}
}
} catch (CoreException ce) {
handleException(ce.getStatus(), block.getShell(),
"Error applying changes",
"There was an error applying changes to project : "
+ project.getName());
return false;
}
return true;
}
@Override
public void dispose() {
if (block != null) {
block.dispose();
block = null;
}
}
@Override
protected void performDefaults() {
// revert to default
this.fFormatterProfileManager.setSelected(fFormatterProfileManager.getDefaultProfile());
CeylonStyle.setFormatterProfile(project, this.fFormatterProfileManager.getSelected().getName());
}
private void handleException(IStatus status, Shell shell, String title, String extMsg) {
if (status != null) {
ErrorDialog.openError(shell, title, extMsg, status);
} else {
MessageDialog.openError(shell, title, extMsg);
}
}
}