/*
* EditingPreferencesPane.java
*
* Copyright (C) 2009-17 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
* this program is licensed to you under the terms of version 3 of the
* GNU Affero General Public License. This program is distributed WITHOUT
* ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
* MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
* AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
*
*/
package org.rstudio.studio.client.workbench.prefs.views;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.resources.client.ImageResource;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.inject.Inject;
import org.rstudio.core.client.StringUtil;
import org.rstudio.core.client.VirtualConsole;
import org.rstudio.core.client.command.KeyboardShortcut;
import org.rstudio.core.client.command.ShortcutManager;
import org.rstudio.core.client.prefs.PreferencesDialogBaseResources;
import org.rstudio.core.client.resources.ImageResource2x;
import org.rstudio.core.client.theme.DialogTabLayoutPanel;
import org.rstudio.core.client.widget.HelpButton;
import org.rstudio.core.client.widget.ModifyKeyboardShortcutsWidget;
import org.rstudio.core.client.widget.NumericValueWidget;
import org.rstudio.core.client.widget.OperationWithInput;
import org.rstudio.core.client.widget.SelectWidget;
import org.rstudio.core.client.widget.SmallButton;
import org.rstudio.core.client.widget.TextBoxWithButton;
import org.rstudio.studio.client.common.DiagnosticsHelpLink;
import org.rstudio.studio.client.common.HelpLink;
import org.rstudio.studio.client.common.SimpleRequestCallback;
import org.rstudio.studio.client.workbench.prefs.model.EditingPrefs;
import org.rstudio.studio.client.workbench.prefs.model.RPrefs;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefs;
import org.rstudio.studio.client.workbench.prefs.model.UIPrefsAccessor;
import org.rstudio.studio.client.workbench.snippets.ui.EditSnippetsDialog;
import org.rstudio.studio.client.workbench.views.source.editors.text.FoldStyle;
import org.rstudio.studio.client.workbench.views.source.editors.text.IconvListResult;
import org.rstudio.studio.client.workbench.views.source.editors.text.ui.ChooseEncodingDialog;
import org.rstudio.studio.client.workbench.views.source.model.SourceServerOperations;
public class EditingPreferencesPane extends PreferencesPane
{
@Inject
public EditingPreferencesPane(UIPrefs prefs,
SourceServerOperations server,
PreferencesDialogResources res)
{
prefs_ = prefs;
server_ = server;
PreferencesDialogBaseResources baseRes = PreferencesDialogBaseResources.INSTANCE;
VerticalPanel editingPanel = new VerticalPanel();
editingPanel.add(headerLabel("General"));
editingPanel.add(tight(spacesForTab_ = checkboxPref("Insert spaces for tab", prefs.useSpacesForTab(),
false /*defaultSpace*/)));
editingPanel.add(indent(tabWidth_ = numericPref("Tab width", prefs.numSpacesForTab())));
editingPanel.add(checkboxPref("Insert matching parens/quotes", prefs_.insertMatching()));
editingPanel.add(checkboxPref("Auto-indent code after paste", prefs_.reindentOnPaste()));
editingPanel.add(checkboxPref("Vertically align arguments in auto-indent", prefs_.verticallyAlignArgumentIndent()));
editingPanel.add(checkboxPref("Soft-wrap R source files", prefs_.softWrapRFiles()));
editingPanel.add(checkboxPref(
"Continue comment when inserting new line",
prefs_.continueCommentsOnNewline(),
"When enabled, pressing enter will continue comments on new lines. Press Shift + Enter to exit a comment."));
delimiterSurroundWidget_ = new SelectWidget(
"Surround selection on text insertion:",
new String[] {
"Never",
"Quotes",
"Quotes & Brackets"
},
new String[] {
UIPrefsAccessor.EDITOR_SURROUND_SELECTION_NEVER,
UIPrefsAccessor.EDITOR_SURROUND_SELECTION_QUOTES,
UIPrefsAccessor.EDITOR_SURROUND_SELECTION_QUOTES_AND_BRACKETS
},
false,
true,
false);
editingPanel.add(delimiterSurroundWidget_);
HorizontalPanel keyboardPanel = new HorizontalPanel();
editorMode_ = new SelectWidget(
"Keybindings:",
new String[] {
"Default",
"Vim",
"Emacs"
},
new String[] {
UIPrefsAccessor.EDITOR_KEYBINDINGS_DEFAULT,
UIPrefsAccessor.EDITOR_KEYBINDINGS_VIM,
UIPrefsAccessor.EDITOR_KEYBINDINGS_EMACS
},
false,
true,
false);
keyboardPanel.add(editorMode_);
SmallButton editShortcuts = new SmallButton("Modify Keyboard Shortcuts...");
editShortcuts.getElement().getStyle().setMarginLeft(15, Unit.PX);
editShortcuts.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event)
{
new ModifyKeyboardShortcutsWidget().showModal();
}
});
keyboardPanel.add(editShortcuts);
lessSpaced(keyboardPanel);
editingPanel.add(keyboardPanel);
Label executionLabel = headerLabel("Execution");
editingPanel.add(executionLabel);
executionLabel.getElement().getStyle().setMarginTop(8, Unit.PX);
editingPanel.add(checkboxPref("Always save R scripts before sourcing", prefs.saveBeforeSourcing()));
editingPanel.add(checkboxPref("Focus console after executing from source", prefs_.focusConsoleAfterExec()));
executionBehavior_ = new SelectWidget(
"Ctrl+Enter executes: ",
new String[] {
"Current line",
"Multi-line R statement",
"Multiple consecutive R lines"
},
new String[] {
UIPrefsAccessor.EXECUTE_LINE,
UIPrefsAccessor.EXECUTE_STATEMENT,
UIPrefsAccessor.EXECUTE_PARAGRAPH
},
false,
true,
false);
editingPanel.add(executionBehavior_);
Label snippetsLabel = headerLabel("Snippets");
snippetsLabel.getElement().getStyle().setMarginTop(8, Unit.PX);
editingPanel.add(snippetsLabel);
HorizontalPanel panel = new HorizontalPanel();
CheckBox enableSnippets = checkboxPref("Enable code snippets", prefs_.enableSnippets());
panel.add(enableSnippets);
SmallButton editSnippets = new SmallButton("Edit Snippets...");
editSnippets.getElement().getStyle().setMarginTop(1, Unit.PX);
editSnippets.getElement().getStyle().setMarginLeft(5, Unit.PX);
editSnippets.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event)
{
new EditSnippetsDialog().showModal();
}
});
panel.add(editSnippets);
HelpButton snippetHelp = new HelpButton("code_snippets");
snippetHelp.getElement().getStyle().setMarginTop(2, Unit.PX);
snippetHelp.getElement().getStyle().setMarginLeft(6, Unit.PX);
panel.add(snippetHelp);
editingPanel.add(panel);
VerticalPanel displayPanel = new VerticalPanel();
displayPanel.add(headerLabel("General"));
displayPanel.add(checkboxPref("Highlight selected word", prefs.highlightSelectedWord()));
displayPanel.add(checkboxPref("Highlight selected line", prefs.highlightSelectedLine()));
displayPanel.add(checkboxPref("Show line numbers", prefs.showLineNumbers()));
displayPanel.add(tight(showMargin_ = checkboxPref("Show margin",
prefs.showMargin(), false /*defaultSpace*/)));
displayPanel.add(indent(marginCol_ = numericPref("Margin column", prefs.printMarginColumn())));
displayPanel.add(checkboxPref("Show whitespace characters", prefs_.showInvisibles()));
displayPanel.add(checkboxPref("Show indent guides", prefs_.showIndentGuides()));
displayPanel.add(checkboxPref("Blinking cursor", prefs_.blinkingCursor()));
displayPanel.add(checkboxPref("Show syntax highlighting in console input", prefs_.syntaxColorConsole()));
displayPanel.add(checkboxPref("Allow scroll past end of document", prefs_.scrollPastEndOfDocument()));
displayPanel.add(extraSpaced(checkboxPref("Highlight R function calls",
prefs_.highlightRFunctionCalls(), false /*defaultSpace*/)));
foldMode_ = new SelectWidget(
"Fold Style:",
new String[] {
"Start Only",
"Start and End"
},
new String[] {
FoldStyle.FOLD_MARK_BEGIN_ONLY,
FoldStyle.FOLD_MARK_BEGIN_AND_END
},
false,
true,
false);
displayPanel.add(foldMode_);
displayPanel.add(headerLabel("Console"));
NumericValueWidget limitLengthPref =
numericPref("Limit length of lines displayed in console to:", prefs_.truncateLongLinesInConsoleHistory());
limitLengthPref.setWidth("36px");
displayPanel.add(nudgeRightPlus(limitLengthPref));
consoleColorMode_ = new SelectWidget(
"ANSI Escape Codes:",
new String[] {
"Show ANSI colors",
"Remove ANSI codes",
"Ignore ANSI codes (1.0 behavior)"
},
new String[] {
Integer.toString(VirtualConsole.ANSI_COLOR_ON),
Integer.toString(VirtualConsole.ANSI_COLOR_STRIP),
Integer.toString(VirtualConsole.ANSI_COLOR_OFF)
},
false,
true,
false);
displayPanel.add(consoleColorMode_);
VerticalPanel savePanel = new VerticalPanel();
savePanel.add(headerLabel("General"));
savePanel.add(checkboxPref("Ensure that source files end with newline", prefs_.autoAppendNewline()));
savePanel.add(checkboxPref("Strip trailing horizontal whitespace when saving", prefs_.stripTrailingWhitespace()));
Label serializationLabel = headerLabel("Serialization");
serializationLabel.getElement().getStyle().setPaddingTop(14, Unit.PX);
savePanel.add(serializationLabel);
lineEndings_ = new LineEndingsSelectWidget();
spaced(lineEndings_);
savePanel.add(lineEndings_);
encodingValue_ = prefs_.defaultEncoding().getGlobalValue();
savePanel.add(lessSpaced(encoding_ = new TextBoxWithButton(
"Default text encoding:",
"Change...",
new ClickHandler()
{
public void onClick(ClickEvent event)
{
server_.iconvlist(new SimpleRequestCallback<IconvListResult>()
{
@Override
public void onResponseReceived(IconvListResult response)
{
new ChooseEncodingDialog(
response.getCommon(),
response.getAll(),
encodingValue_,
true,
false,
new OperationWithInput<String>()
{
public void execute(String encoding)
{
if (encoding == null)
return;
setEncoding(encoding);
}
}).showModal();
}
});
}
})));
nudgeRight(encoding_);
textBoxWithChooser(encoding_);
spaced(encoding_);
setEncoding(prefs.defaultEncoding().getGlobalValue());
VerticalPanel completionPanel = new VerticalPanel();
completionPanel.add(headerLabel("R and C/C++"));
showCompletions_ = new SelectWidget(
"Show code completions:",
new String[] {
"Automatically",
"When Triggered ($, ::)",
"Manually (Tab)"
},
new String[] {
UIPrefsAccessor.COMPLETION_ALWAYS,
UIPrefsAccessor.COMPLETION_WHEN_TRIGGERED,
UIPrefsAccessor.COMPLETION_MANUAL
},
false,
true,
false);
spaced(showCompletions_);
completionPanel.add(showCompletions_);
final CheckBox alwaysCompleteInConsole = checkboxPref(
"Allow automatic completions in console",
prefs.alwaysCompleteInConsole());
completionPanel.add(alwaysCompleteInConsole);
showCompletions_.addChangeHandler(new ChangeHandler()
{
@Override
public void onChange(ChangeEvent event)
{
alwaysCompleteInConsole.setVisible(
showCompletions_.getValue().equals(
UIPrefsAccessor.COMPLETION_ALWAYS));
}
});
final CheckBox insertParensAfterFunctionCompletionsCheckbox =
checkboxPref("Insert parentheses after function completions",
prefs.insertParensAfterFunctionCompletion());
final CheckBox showSignatureTooltipsCheckbox =
checkboxPref("Show help tooltip after function completions",
prefs.showSignatureTooltips());
addEnabledDependency(
insertParensAfterFunctionCompletionsCheckbox,
showSignatureTooltipsCheckbox);
completionPanel.add(insertParensAfterFunctionCompletionsCheckbox);
completionPanel.add(showSignatureTooltipsCheckbox);
completionPanel.add(checkboxPref("Show help tooltip on cursor idle", prefs.showFunctionTooltipOnIdle()));
completionPanel.add(checkboxPref("Insert spaces around equals for argument completions", prefs.insertSpacesAroundEquals()));
completionPanel.add(checkboxPref("Use tab for multiline autocompletions", prefs.allowTabMultilineCompletion()));
Label otherLabel = headerLabel("Other Languages");
otherLabel.getElement().getStyle().setMarginTop(8, Unit.PX);
completionPanel.add(otherLabel);
showCompletionsOther_ = new SelectWidget(
"Show code completions:",
new String[] {
"Automatically",
"Manually (Ctrl+Space) "
},
new String[] {
UIPrefsAccessor.COMPLETION_ALWAYS,
UIPrefsAccessor.COMPLETION_MANUAL
},
false,
true,
false);
completionPanel.add(showCompletionsOther_);
Label otherTip = new Label(
"Keyword and text-based completions are supported for several other " +
"languages including JavaScript, HTML, CSS, Python, and SQL.");
otherTip.addStyleName(baseRes.styles().infoLabel());
completionPanel.add(nudgeRightPlus(otherTip));
Label delayLabel = headerLabel("Completion Delay");
delayLabel.getElement().getStyle().setMarginTop(14, Unit.PX);
completionPanel.add(delayLabel);
completionPanel.add(nudgeRightPlus(alwaysCompleteChars_ =
numericPref("Show completions after characters entered:",
prefs.alwaysCompleteCharacters())));
completionPanel.add(nudgeRightPlus(alwaysCompleteDelayMs_ =
numericPref("Show completions after keyboard idle (ms):",
prefs.alwaysCompleteDelayMs())));
VerticalPanel diagnosticsPanel = new VerticalPanel();
diagnosticsPanel.add(headerLabel("R Diagnostics"));
final CheckBox chkShowRDiagnostics = checkboxPref("Show diagnostics for R", prefs.showDiagnosticsR());
diagnosticsPanel.add(chkShowRDiagnostics);
final VerticalPanel rOptionsPanel = new VerticalPanel();
rOptionsPanel.add(checkboxPref("Enable diagnostics within R function calls", prefs.diagnosticsInRFunctionCalls()));
rOptionsPanel.add(checkboxPref("Check arguments to R function calls", prefs.checkArgumentsToRFunctionCalls()));
rOptionsPanel.add(checkboxPref("Warn if variable used has no definition in scope", prefs.warnIfNoSuchVariableInScope()));
rOptionsPanel.add(checkboxPref("Warn if variable is defined but not used", prefs.warnIfVariableDefinedButNotUsed()));
rOptionsPanel.add(checkboxPref("Provide R style diagnostics (e.g. whitespace)", prefs.enableStyleDiagnostics()));
rOptionsPanel.setVisible(prefs.showDiagnosticsR().getValue());
chkShowRDiagnostics.addValueChangeHandler(new ValueChangeHandler<Boolean>() {
@Override
public void onValueChange(ValueChangeEvent<Boolean> event)
{
rOptionsPanel.setVisible(event.getValue());
}
});
diagnosticsPanel.add(rOptionsPanel);
Label diagOtherLabel = headerLabel("Other Languages");
diagnosticsPanel.add(spacedBefore(diagOtherLabel));
diagnosticsPanel.add(checkboxPref("Show diagnostics for C/C++", prefs.showDiagnosticsCpp()));
diagnosticsPanel.add(checkboxPref("Show diagnostics for JavaScript, HTML, and CSS", prefs.showDiagnosticsOther()));
Label diagShowLabel = headerLabel("Show Diagnostics");
diagnosticsPanel.add(spacedBefore(diagShowLabel));
diagnosticsPanel.add(checkboxPref("Show diagnostics whenever source files are saved", prefs.diagnosticsOnSave()));
diagnosticsPanel.add(tight(checkboxPref("Show diagnostics after keyboard is idle for a period of time",
prefs.enableBackgroundDiagnostics(), false /*defaultSpace*/)));
diagnosticsPanel.add(indent(backgroundDiagnosticsDelayMs_ =
numericPref("Keyboard idle time (ms):", prefs.backgroundDiagnosticsDelayMs())));
HelpLink diagnosticsHelpLink = new DiagnosticsHelpLink();
diagnosticsHelpLink.getElement().getStyle().setMarginTop(12, Unit.PX);
nudgeRight(diagnosticsHelpLink);
diagnosticsPanel.add(diagnosticsHelpLink);
DialogTabLayoutPanel tabPanel = new DialogTabLayoutPanel();
tabPanel.setSize("435px", "498px");
tabPanel.add(editingPanel, "Editing");
tabPanel.add(displayPanel, "Display");
tabPanel.add(savePanel, "Saving");
tabPanel.add(completionPanel, "Completion");
tabPanel.add(diagnosticsPanel, "Diagnostics");
tabPanel.selectTab(0);
add(tabPanel);
}
private void disable(CheckBox checkBox)
{
checkBox.setValue(false);
checkBox.setEnabled(false);
checkBox.setVisible(false);
}
private void enable(CheckBox checkBox)
{
checkBox.setValue(true);
checkBox.setEnabled(true);
checkBox.setVisible(true);
}
private void addEnabledDependency(final CheckBox speaker,
final CheckBox listener)
{
if (speaker.getValue() == false)
disable(listener);
speaker.addValueChangeHandler(
new ValueChangeHandler<Boolean>()
{
@Override
public void onValueChange(ValueChangeEvent<Boolean> event)
{
if (event.getValue() == false)
disable(listener);
else
enable(listener);
}
});
}
@Override
protected void initialize(RPrefs prefs)
{
// editing prefs
EditingPrefs editingPrefs = prefs.getEditingPrefs();
lineEndings_.setIntValue(editingPrefs.getLineEndings());
consoleColorMode_.setValue(Integer.toString(prefs_.consoleAnsiMode().getValue()));
showCompletions_.setValue(prefs_.codeComplete().getValue());
showCompletionsOther_.setValue(prefs_.codeCompleteOther().getValue());
if (prefs_.useVimMode().getValue())
editorMode_.setValue(UIPrefsAccessor.EDITOR_KEYBINDINGS_VIM);
else if (prefs_.enableEmacsKeybindings().getValue())
editorMode_.setValue(UIPrefsAccessor.EDITOR_KEYBINDINGS_EMACS);
else
editorMode_.setValue(UIPrefsAccessor.EDITOR_KEYBINDINGS_DEFAULT);
foldMode_.setValue(prefs_.foldStyle().getValue());
delimiterSurroundWidget_.setValue(prefs_.surroundSelection().getValue());
executionBehavior_.setValue(prefs_.executionBehavior().getValue());
}
@Override
public boolean onApply(RPrefs prefs)
{
boolean reload = super.onApply(prefs);
// editing prefs
prefs.setEditingPrefs(EditingPrefs.create(lineEndings_.getIntValue()));
prefs_.consoleAnsiMode().setGlobalValue(StringUtil.parseInt(
consoleColorMode_.getValue(), VirtualConsole.ANSI_COLOR_ON));
prefs_.defaultEncoding().setGlobalValue(encodingValue_);
prefs_.codeComplete().setGlobalValue(showCompletions_.getValue());
prefs_.codeCompleteOther().setGlobalValue(showCompletionsOther_.getValue());
String editorMode = editorMode_.getValue();
boolean isVim = editorMode.equals(UIPrefsAccessor.EDITOR_KEYBINDINGS_VIM);
boolean isEmacs = editorMode.equals(UIPrefsAccessor.EDITOR_KEYBINDINGS_EMACS);
prefs_.useVimMode().setGlobalValue(isVim);
prefs_.enableEmacsKeybindings().setGlobalValue(isEmacs);
if (isVim)
ShortcutManager.INSTANCE.setEditorMode(KeyboardShortcut.MODE_VIM);
else if (isEmacs)
ShortcutManager.INSTANCE.setEditorMode(KeyboardShortcut.MODE_EMACS);
else
ShortcutManager.INSTANCE.setEditorMode(KeyboardShortcut.MODE_DEFAULT);
prefs_.foldStyle().setGlobalValue(foldMode_.getValue());
prefs_.surroundSelection().setGlobalValue(delimiterSurroundWidget_.getValue());
prefs_.executionBehavior().setGlobalValue(executionBehavior_.getValue());
return reload;
}
@Override
public ImageResource getIcon()
{
return new ImageResource2x(PreferencesDialogBaseResources.INSTANCE.iconCodeEditing2x());
}
@Override
public boolean validate()
{
return (!spacesForTab_.getValue() || tabWidth_.validatePositive("Tab width")) &&
(!showMargin_.getValue() || marginCol_.validate("Margin column")) &&
alwaysCompleteChars_.validateRange("Characters entered", 1, 100) &&
alwaysCompleteDelayMs_.validateRange("Completion keyboard idle (ms)", 0, 10000) &&
backgroundDiagnosticsDelayMs_.validateRange("Diagnostics keyboard idle (ms):", 0, 10000);
}
@Override
public String getName()
{
return "Code";
}
private void setEncoding(String encoding)
{
encodingValue_ = encoding;
if (StringUtil.isNullOrEmpty(encoding))
encoding_.setText(ChooseEncodingDialog.ASK_LABEL);
else
encoding_.setText(encoding);
}
private final UIPrefs prefs_;
private final SourceServerOperations server_;
private final NumericValueWidget tabWidth_;
private final NumericValueWidget marginCol_;
private final LineEndingsSelectWidget lineEndings_;
private final NumericValueWidget alwaysCompleteChars_;
private final NumericValueWidget alwaysCompleteDelayMs_;
private final NumericValueWidget backgroundDiagnosticsDelayMs_;
private final CheckBox spacesForTab_;
private final CheckBox showMargin_;
private final SelectWidget showCompletions_;
private final SelectWidget showCompletionsOther_;
private final SelectWidget editorMode_;
private final SelectWidget foldMode_;
private final SelectWidget consoleColorMode_;
private final SelectWidget delimiterSurroundWidget_;
private final SelectWidget executionBehavior_;
private final TextBoxWithButton encoding_;
private String encodingValue_;
}