/**
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the Eclipse Public License (EPL).
* Please see the license.txt included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
/*
* Created on Feb 22, 2005
*
* @author Fabio Zadrozny
*/
package org.python.pydev.plugin.preferences;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.preference.BooleanFieldEditor;
import org.eclipse.jface.preference.StringFieldEditor;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
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.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.preferences.IWorkbenchPreferenceContainer;
import org.python.pydev.editor.StyledTextForShowingCodeFactory;
import org.python.pydev.editor.actions.PyFormatStd.FormatStd;
import org.python.pydev.editor.preferences.PyScopedPreferences;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.structure.Tuple;
import org.python.pydev.shared_ui.field_editors.BooleanFieldEditorCustom;
import org.python.pydev.shared_ui.field_editors.ComboFieldEditor;
import org.python.pydev.shared_ui.field_editors.LinkFieldEditor;
import org.python.pydev.shared_ui.field_editors.ScopedFieldEditorPreferencePage;
import org.python.pydev.shared_ui.field_editors.ScopedPreferencesFieldEditor;
/**
* @author Fabio Zadrozny
*/
public class PyCodeFormatterPage extends ScopedFieldEditorPreferencePage implements IWorkbenchPreferencePage {
public static final String FORMAT_WITH_AUTOPEP8 = "FORMAT_WITH_AUTOPEP8";
public static final boolean DEFAULT_FORMAT_WITH_AUTOPEP8 = false;
public static final String AUTOPEP8_PARAMETERS = "AUTOPEP8_PARAMETERS";
public static final String FORMAT_ONLY_CHANGED_LINES = "FORMAT_ONLY_CHANGED_LINES";
public static final boolean DEFAULT_FORMAT_ONLY_CHANGED_LINES = false;
public static final String TRIM_LINES = "TRIM_EMPTY_LINES";
public static final boolean DEFAULT_TRIM_LINES = false;
public static final String TRIM_MULTILINE_LITERALS = "TRIM_MULTILINE_LITERALS";
public static final boolean DEFAULT_TRIM_MULTILINE_LITERALS = false;
public static final String ADD_NEW_LINE_AT_END_OF_FILE = "ADD_NEW_LINE_AT_END_OF_FILE";
public static final boolean DEFAULT_ADD_NEW_LINE_AT_END_OF_FILE = true;
//a, b, c
public static final String USE_SPACE_AFTER_COMMA = "USE_SPACE_AFTER_COMMA";
public static final boolean DEFAULT_USE_SPACE_AFTER_COMMA = true;
//call( a )
public static final String USE_SPACE_FOR_PARENTESIS = "USE_SPACE_FOR_PARENTESIS";
public static final boolean DEFAULT_USE_SPACE_FOR_PARENTESIS = false;
//call(a = 1)
public static final String USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS = "USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS";
public static final boolean DEFAULT_USE_ASSIGN_WITH_PACES_INSIDE_PARENTESIS = false;
//operators =, !=, <, >, //, etc.
public static final String USE_OPERATORS_WITH_SPACE = "USE_OPERATORS_WITH_SPACE";
public static final boolean DEFAULT_USE_OPERATORS_WITH_SPACE = true;
//Spaces before '#'.
public static final String SPACES_BEFORE_COMMENT = "SPACES_BEFORE_COMMENT";
public static final int DEFAULT_SPACES_BEFORE_COMMENT = 2; //pep-8 says 2 spaces before inline comment.
//Spaces after '#'.
public static final String SPACES_IN_START_COMMENT = "SPACES_IN_START_COMMENT";
public static final int DEFAULT_SPACES_IN_START_COMMENT = 1; //pep-8 says 1 space after '#'
private StyledText labelExample;
private BooleanFieldEditorCustom formatWithAutoPep8;
private BooleanFieldEditorCustom spaceAfterComma;
private BooleanFieldEditorCustom onlyChangedLines;
private BooleanFieldEditorCustom spaceForParentesis;
private BooleanFieldEditorCustom assignWithSpaceInsideParentesis;
private BooleanFieldEditorCustom operatorsWithSpace;
private BooleanFieldEditorCustom rightTrimLines;
private BooleanFieldEditorCustom rightTrimMultilineLiterals;
private BooleanFieldEditorCustom addNewLineAtEndOfFile;
private StyledTextForShowingCodeFactory formatAndStyleRangeHelper;
private ComboFieldEditor spacesBeforeComment;
private ComboFieldEditor spacesInStartComment;
private Composite fieldParent;
private StringFieldEditor autopep8Parameters;
private LinkFieldEditor autopep8Link;
private boolean disposed = false;
public PyCodeFormatterPage() {
super(GRID);
setPreferenceStore(PydevPlugin.getDefault().getPreferenceStore());
}
private static final String[][] ENTRIES_AND_VALUES_FOR_SPACES = new String[][] {
{ "Don't change manual formatting", Integer.toString(FormatStd.DONT_HANDLE_SPACES) },
{ "No spaces", "0" },
{ "1 space", "1" },
{ "2 spaces", "2" },
{ "3 spaces", "3" },
{ "4 spaces", "4" },
};
private static final String[][] ENTRIES_AND_VALUES_FOR_SPACES2 = new String[][] {
{ "Don't change manual formatting", Integer.toString(FormatStd.DONT_HANDLE_SPACES) }, //0 and -1 means the same thing here.
{ "At least 1 space", "1" },
{ "At least 2 spaces", "2" },
{ "At least 3 spaces", "3" },
{ "At least 4 spaces", "4" },
};
/**
* @see org.eclipse.jface.preference.FieldEditorPreferencePage#createFieldEditors()
*/
@Override
public void createFieldEditors() {
Composite p = getFieldEditorParent();
this.fieldParent = p;
addField(new LinkFieldEditor("link_saveactions", "Note: view <a>save actions</a> to auto-format on save.", p,
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
String id = "org.python.pydev.editor.saveactions.PydevSaveActionsPrefPage";
IWorkbenchPreferenceContainer workbenchPreferenceContainer = ((IWorkbenchPreferenceContainer) getContainer());
workbenchPreferenceContainer.openPage(id, null);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
}));
formatWithAutoPep8 = createBooleanFieldEditorCustom(FORMAT_WITH_AUTOPEP8,
"Use autopep8.py for code formatting?", p);
addField(formatWithAutoPep8);
autopep8Link = new LinkFieldEditor("link_autopep8_interpreter",
"Note: the default configured <a>Python Interpreter</a> will be used to execute autopep8.py", p,
new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
String id = "org.python.pydev.ui.pythonpathconf.interpreterPreferencesPagePython";
IWorkbenchPreferenceContainer workbenchPreferenceContainer = ((IWorkbenchPreferenceContainer) getContainer());
workbenchPreferenceContainer.openPage(id, null);
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
addField(autopep8Link);
autopep8Parameters = new StringFieldEditor(AUTOPEP8_PARAMETERS,
"Parameters for autopep8 (i.e.: -a for aggressive, --ignore E24)", p);
addField(autopep8Parameters);
onlyChangedLines = createBooleanFieldEditorCustom(FORMAT_ONLY_CHANGED_LINES,
"On save, only apply formatting in changed lines?", p);
addField(onlyChangedLines);
spaceAfterComma = createBooleanFieldEditorCustom(USE_SPACE_AFTER_COMMA, "Use space after commas?", p);
addField(spaceAfterComma);
spaceForParentesis = createBooleanFieldEditorCustom(USE_SPACE_FOR_PARENTESIS,
"Use space before and after parenthesis?", p);
addField(spaceForParentesis);
assignWithSpaceInsideParentesis = createBooleanFieldEditorCustom(USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS,
"Use space before and after assign for keyword arguments?", p);
addField(assignWithSpaceInsideParentesis);
operatorsWithSpace = createBooleanFieldEditorCustom(USE_OPERATORS_WITH_SPACE,
"Use space before and after operators? (+, -, /, *, //, **, etc.)", p);
addField(operatorsWithSpace);
rightTrimLines = createBooleanFieldEditorCustom(TRIM_LINES, "Right trim lines?", p);
addField(rightTrimLines);
rightTrimMultilineLiterals = createBooleanFieldEditorCustom(TRIM_MULTILINE_LITERALS,
"Right trim multi-line string literals?", p);
addField(rightTrimMultilineLiterals);
addNewLineAtEndOfFile = createBooleanFieldEditorCustom(ADD_NEW_LINE_AT_END_OF_FILE,
"Add new line at end of file?", p);
addField(addNewLineAtEndOfFile);
spacesBeforeComment = new ComboFieldEditor(SPACES_BEFORE_COMMENT, "Spaces before a comment?",
ENTRIES_AND_VALUES_FOR_SPACES, p);
addField(spacesBeforeComment);
spacesInStartComment = new ComboFieldEditor(SPACES_IN_START_COMMENT, "Spaces in comment start?",
ENTRIES_AND_VALUES_FOR_SPACES2, p);
addField(spacesInStartComment);
formatAndStyleRangeHelper = new StyledTextForShowingCodeFactory();
labelExample = formatAndStyleRangeHelper.createStyledTextForCodePresentation(p);
GridData layoutData = new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1);
labelExample.setLayoutData(layoutData);
addField(new ScopedPreferencesFieldEditor(p, PydevPlugin.DEFAULT_PYDEV_SCOPE, this));
}
@Override
protected void initialize() {
super.initialize();
//After initializing, let's check the proper state based on pep8.
Button checkBox = formatWithAutoPep8.getCheckBox(fieldParent);
checkBox.addSelectionListener(new SelectionListener() {
@Override
public void widgetSelected(SelectionEvent e) {
updateState();
}
@Override
public void widgetDefaultSelected(SelectionEvent e) {
}
});
updateState();
// And update the example when it's already there
updateLabelExampleNow(this.getFormatFromEditorContents());
}
private void updateState() {
if (formatWithAutoPep8.getBooleanValue()) {
assignWithSpaceInsideParentesis.setEnabled(false, fieldParent);
operatorsWithSpace.setEnabled(false, fieldParent);
spaceForParentesis.setEnabled(false, fieldParent);
spaceAfterComma.setEnabled(false, fieldParent);
addNewLineAtEndOfFile.setEnabled(false, fieldParent);
rightTrimLines.setEnabled(false, fieldParent);
rightTrimMultilineLiterals.setEnabled(false, fieldParent);
spacesBeforeComment.setEnabled(false, fieldParent);
spacesInStartComment.setEnabled(false, fieldParent);
onlyChangedLines.setEnabled(false, fieldParent);
autopep8Parameters.setEnabled(true, fieldParent);
autopep8Link.setEnabled(true, fieldParent);
} else {
assignWithSpaceInsideParentesis.setEnabled(true, fieldParent);
operatorsWithSpace.setEnabled(true, fieldParent);
spaceForParentesis.setEnabled(true, fieldParent);
spaceAfterComma.setEnabled(true, fieldParent);
addNewLineAtEndOfFile.setEnabled(true, fieldParent);
rightTrimLines.setEnabled(true, fieldParent);
rightTrimMultilineLiterals.setEnabled(true, fieldParent);
spacesBeforeComment.setEnabled(true, fieldParent);
spacesInStartComment.setEnabled(true, fieldParent);
onlyChangedLines.setEnabled(true, fieldParent);
autopep8Parameters.setEnabled(false, fieldParent);
autopep8Link.setEnabled(false, fieldParent);
}
}
private BooleanFieldEditorCustom createBooleanFieldEditorCustom(String name, String label, Composite parent) {
return new BooleanFieldEditorCustom(name, label, BooleanFieldEditor.SEPARATE_LABEL, parent);
}
// Note: no locking is needed since we're doing everything in the UI thread.
private Runnable currentRunnable;
private void updateLabelExample(final FormatStd formatStd) {
if (!disposed) {
Runnable runnable = new Runnable() {
@Override
public void run() {
if (disposed) {
currentRunnable = null;
return;
}
if (currentRunnable == this) {
updateLabelExampleNow(formatStd);
currentRunnable = null;
}
}
};
currentRunnable = runnable;
// Give a timeout before updating (otherwise when changing the text for the autopep8 integration
// it becomes slow).
Display.getCurrent().timerExec(400, runnable);
}
}
private void updateLabelExampleNow(FormatStd formatStd) {
String str = "class Example(object): \n" +
" \n" +
" def Call(self, param1=None): \n" +
" '''docstring''' \n" +
" return param1 + 10 * 10 \n" +
" \n" +
" def Call2(self): #Comment \n" +
" #Comment \n" +
" return self.Call(param1=10)" +
"";
Tuple<String, StyleRange[]> result = formatAndStyleRangeHelper.formatAndGetStyleRanges(formatStd, str,
PydevPrefs.getChainedPrefStore(), true);
labelExample.setText(result.o1);
labelExample.setStyleRanges(result.o2);
}
@Override
public void propertyChange(PropertyChangeEvent event) {
super.propertyChange(event);
updateLabelExample(getFormatFromEditorContents());
}
@Override
protected void performDefaults() {
super.performDefaults();
updateLabelExample(getFormatFromEditorContents());
updateState();
}
private FormatStd getFormatFromEditorContents() {
FormatStd formatStd = new FormatStd();
formatStd.assignWithSpaceInsideParens = this.assignWithSpaceInsideParentesis.getBooleanValue();
formatStd.operatorsWithSpace = operatorsWithSpace.getBooleanValue();
formatStd.parametersWithSpace = spaceForParentesis.getBooleanValue();
formatStd.spaceAfterComma = spaceAfterComma.getBooleanValue();
formatStd.addNewLineAtEndOfFile = addNewLineAtEndOfFile.getBooleanValue();
formatStd.trimLines = rightTrimLines.getBooleanValue();
formatStd.trimMultilineLiterals = rightTrimMultilineLiterals.getBooleanValue();
formatStd.spacesBeforeComment = Integer.parseInt(spacesBeforeComment.getComboValue());
formatStd.spacesInStartComment = Integer.parseInt(spacesInStartComment.getComboValue());
formatStd.formatWithAutopep8 = this.formatWithAutoPep8.getBooleanValue();
formatStd.autopep8Parameters = this.autopep8Parameters.getStringValue();
formatStd.updateAutopep8();
return formatStd;
}
/**
* @see org.eclipse.ui.IWorkbenchPreferencePage#init(org.eclipse.ui.IWorkbench)
*/
@Override
public void init(IWorkbench workbench) {
}
public static boolean getFormatWithAutopep8(IAdaptable projectAdaptable) {
return getBoolean(FORMAT_WITH_AUTOPEP8, projectAdaptable);
}
public static boolean getBoolean(String setting, IAdaptable projectAdaptable) {
return PyScopedPreferences.getBoolean(setting, projectAdaptable);
}
protected static String getString(String setting, IAdaptable projectAdaptable) {
return PyScopedPreferences.getString(setting, projectAdaptable);
}
public static String getAutopep8Parameters(IAdaptable projectAdaptable) {
return getString(AUTOPEP8_PARAMETERS, projectAdaptable);
}
public static boolean getFormatOnlyChangedLines(IAdaptable projectAdaptable) {
if (getFormatWithAutopep8(projectAdaptable)) {
return false; //i.e.: not available with autopep8.
}
return getBoolean(FORMAT_ONLY_CHANGED_LINES, projectAdaptable);
}
public static boolean getAddNewLineAtEndOfFile(IAdaptable projectAdaptable) {
return getBoolean(ADD_NEW_LINE_AT_END_OF_FILE, projectAdaptable);
}
public static boolean getTrimLines(IAdaptable projectAdaptable) {
return getBoolean(TRIM_LINES, projectAdaptable);
}
public static boolean getTrimMultilineLiterals(IAdaptable projectAdaptable) {
return getBoolean(TRIM_MULTILINE_LITERALS, projectAdaptable);
}
public static boolean useSpaceAfterComma(IAdaptable projectAdaptable) {
return getBoolean(USE_SPACE_AFTER_COMMA, projectAdaptable);
}
public static boolean useSpaceForParentesis(IAdaptable projectAdaptable) {
return getBoolean(USE_SPACE_FOR_PARENTESIS, projectAdaptable);
}
public static boolean useAssignWithSpacesInsideParenthesis(IAdaptable projectAdaptable) {
return getBoolean(USE_ASSIGN_WITH_PACES_INSIDER_PARENTESIS, projectAdaptable);
}
public static boolean useOperatorsWithSpace(IAdaptable projectAdaptable) {
return getBoolean(USE_OPERATORS_WITH_SPACE, projectAdaptable);
}
public static int getSpacesBeforeComment(IAdaptable projectAdaptable) {
return PyScopedPreferences.getInt(SPACES_BEFORE_COMMENT, projectAdaptable, FormatStd.DONT_HANDLE_SPACES);
}
public static int getSpacesInStartComment(IAdaptable projectAdaptable) {
return PyScopedPreferences.getInt(SPACES_IN_START_COMMENT, projectAdaptable, FormatStd.DONT_HANDLE_SPACES);
}
@Override
public void dispose() {
disposed = true;
super.dispose();
formatAndStyleRangeHelper.dispose();
}
}