Skip to content

Commit

Permalink
Merge pull request #5565 from rstudio/feature/auto-save-option
Browse files Browse the repository at this point in the history
Add auto-save options and finer control of backup
  • Loading branch information
jmcphers committed Oct 17, 2019
2 parents f4e807f + 4a89a4d commit 394689b
Show file tree
Hide file tree
Showing 12 changed files with 417 additions and 26 deletions.
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@
* Customizable dictionaries and word ignore lists preloaded with common R terms
* Inline correction suggestions

### Auto Save

* Changes automatically (and optionally) saved to disk after a few seconds or when editor loses focus (#5263)
* Option to disable real-time backup of unsaved changes to avoid conflicts with Google Drive, Dropbox, etc. (#3837)
* Option to adjust idle interval for backup or saving changes

### Preferences and Configuration

* All user preferences and settings can now be set using a plain JSON file
Expand Down
24 changes: 24 additions & 0 deletions src/cpp/session/include/session/prefs/UserPrefValues.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,12 @@ namespace prefs {
#define kTypingStatusDelayMs "typing_status_delay_ms"
#define kAriaApplicationRole "aria_application_role"
#define kReducedMotion "reduced_motion"
#define kAutoSaveOnIdle "auto_save_on_idle"
#define kAutoSaveOnIdleCommit "commit"
#define kAutoSaveOnIdleBackup "backup"
#define kAutoSaveOnIdleNone "none"
#define kAutoSaveIdleMs "auto_save_idle_ms"
#define kAutoSaveOnBlur "auto_save_on_blur"

class UserPrefValues: public Preferences
{
Expand Down Expand Up @@ -1308,6 +1314,24 @@ class UserPrefValues: public Preferences
bool reducedMotion();
core::Error setReducedMotion(bool val);

/**
* How to deal with changes to documents on idle.
*/
std::string autoSaveOnIdle();
core::Error setAutoSaveOnIdle(std::string val);

/**
* The idle period, in milliseconds, after which documents should be auto-saved.
*/
int autoSaveIdleMs();
core::Error setAutoSaveIdleMs(int val);

/**
* Whether to automatically save when the editor loses focus.
*/
bool autoSaveOnBlur();
core::Error setAutoSaveOnBlur(bool val);

};


Expand Down
42 changes: 42 additions & 0 deletions src/cpp/session/prefs/UserPrefValues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2207,6 +2207,45 @@ core::Error UserPrefValues::setReducedMotion(bool val)
return writePref("reduced_motion", val);
}

/**
* How to deal with changes to documents on idle.
*/
std::string UserPrefValues::autoSaveOnIdle()
{
return readPref<std::string>("auto_save_on_idle");
}

core::Error UserPrefValues::setAutoSaveOnIdle(std::string val)
{
return writePref("auto_save_on_idle", val);
}

/**
* The idle period, in milliseconds, after which documents should be auto-saved.
*/
int UserPrefValues::autoSaveIdleMs()
{
return readPref<int>("auto_save_idle_ms");
}

core::Error UserPrefValues::setAutoSaveIdleMs(int val)
{
return writePref("auto_save_idle_ms", val);
}

/**
* Whether to automatically save when the editor loses focus.
*/
bool UserPrefValues::autoSaveOnBlur()
{
return readPref<bool>("auto_save_on_blur");
}

core::Error UserPrefValues::setAutoSaveOnBlur(bool val)
{
return writePref("auto_save_on_blur", val);
}

std::vector<std::string> UserPrefValues::allKeys()
{
return std::vector<std::string>({
Expand Down Expand Up @@ -2378,6 +2417,9 @@ std::vector<std::string> UserPrefValues::allKeys()
kTypingStatusDelayMs,
kAriaApplicationRole,
kReducedMotion,
kAutoSaveOnIdle,
kAutoSaveIdleMs,
kAutoSaveOnBlur,
});
}

Expand Down
16 changes: 16 additions & 0 deletions src/cpp/session/resources/schema/user-prefs-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -956,6 +956,22 @@
"type": "boolean",
"default": false,
"description": "Reduce use of animations in the user interface."
},
"auto_save_on_idle": {
"type": "string",
"enum": ["commit", "backup", "none"],
"default": "backup",
"description": "How to deal with changes to documents on idle."
},
"auto_save_idle_ms": {
"type": "integer",
"default": 1000,
"description": "The idle period, in milliseconds, after which documents should be auto-saved."
},
"auto_save_on_blur": {
"type": "boolean",
"default": false,
"description": "Whether to automatically save when the editor loses focus."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,32 @@ public void onError(ServerError error)
});
}

/**
* Indicates whether autosave is enabled, via any pref that turns it on.
*
* @return Whether auto save is enabled.
*/
public boolean autoSaveEnabled()
{
return autoSaveOnBlur().getValue() ||
autoSaveOnIdle().getValue() == AUTO_SAVE_ON_IDLE_COMMIT;
}

/**
* Indicates the number of milliseconds after which to autosave. Won't return
* a value less than 500 since autosaves typically require a network call and
* other synchronization.
*
* @return The number of milliseconds.
*/
public int autoSaveMs()
{
Integer ms = autoSaveIdleMs().getValue();
if (ms < 500)
return 500;
return ms;
}

@Override
public void onUserPrefsChanged(UserPrefsChangedEvent e)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1573,6 +1573,34 @@ public PrefValue<Boolean> reducedMotion()
return bool("reduced_motion", false);
}

/**
* How to deal with changes to documents on idle.
*/
public PrefValue<String> autoSaveOnIdle()
{
return string("auto_save_on_idle", "backup");
}

public final static String AUTO_SAVE_ON_IDLE_COMMIT = "commit";
public final static String AUTO_SAVE_ON_IDLE_BACKUP = "backup";
public final static String AUTO_SAVE_ON_IDLE_NONE = "none";

/**
* The idle period, in milliseconds, after which documents should be auto-saved.
*/
public PrefValue<Integer> autoSaveIdleMs()
{
return integer("auto_save_idle_ms", 1000);
}

/**
* Whether to automatically save when the editor loses focus.
*/
public PrefValue<Boolean> autoSaveOnBlur()
{
return bool("auto_save_on_blur", false);
}

public void syncPrefs(String layer, JsObject source)
{
if (source.hasKey("run_rprofile_on_resume"))
Expand Down Expand Up @@ -1911,6 +1939,12 @@ public void syncPrefs(String layer, JsObject source)
ariaApplicationRole().setValue(layer, source.getBool("aria_application_role"));
if (source.hasKey("reduced_motion"))
reducedMotion().setValue(layer, source.getBool("reduced_motion"));
if (source.hasKey("auto_save_on_idle"))
autoSaveOnIdle().setValue(layer, source.getString("auto_save_on_idle"));
if (source.hasKey("auto_save_idle_ms"))
autoSaveIdleMs().setValue(layer, source.getInteger("auto_save_idle_ms"));
if (source.hasKey("auto_save_on_blur"))
autoSaveOnBlur().setValue(layer, source.getBool("auto_save_on_blur"));
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
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.gwt.user.client.ui.Widget;
import com.google.inject.Inject;

import org.rstudio.core.client.ElementIds;
Expand Down Expand Up @@ -303,6 +304,51 @@ public void execute(String encoding)
spaced(encoding_);
setEncoding(prefs.defaultEncoding().getGlobalValue());

savePanel.add(spacedBefore(headerLabel("Auto-save")));
savePanel.add(checkboxPref("Automatically save when editor loses focus", prefs_.autoSaveOnBlur()));
autoSaveOnIdle_ = new SelectWidget(
"When editor is idle: ",
new String[] {
"Backup unsaved changes",
"Save and write changes",
"Do nothing"
},
new String[] {
UserPrefs.AUTO_SAVE_ON_IDLE_BACKUP,
UserPrefs.AUTO_SAVE_ON_IDLE_COMMIT,
UserPrefs.AUTO_SAVE_ON_IDLE_NONE
},
false,
true,
false);
savePanel.add(autoSaveOnIdle_);
autoSaveIdleMs_ = new SelectWidget(
"Idle period: ",
new String[] {
"500ms",
"1000ms",
"1500ms",
"2000ms",
"3000ms",
"4000ms",
"5000ms",
"10000ms",
},
new String[] {
"500",
"1000",
"1500",
"2000",
"3000",
"4000",
"5000",
"10000"
},
false,
true,
false);
savePanel.add(autoSaveIdleMs_);

VerticalTabPanel completionPanel = new VerticalTabPanel(ElementIds.EDIT_COMPLETION_PREFS);

completionPanel.add(headerLabel("R and C/C++"));
Expand Down Expand Up @@ -501,6 +547,15 @@ protected void initialize(UserPrefs prefs)
foldMode_.setValue(prefs_.foldStyle().getValue());
delimiterSurroundWidget_.setValue(prefs_.surroundSelection().getValue());
executionBehavior_.setValue(prefs_.executionBehavior().getValue());
autoSaveOnIdle_.setValue(prefs_.autoSaveOnIdle().getValue());

// To prevent users from choosing nonsensical or pathological values for
// the sensitive autosave idle option, act like they selected 1000ms (the
// default) if they've managed to load something invalid.
if (!autoSaveIdleMs_.setValue(prefs_.autoSaveIdleMs().getValue().toString()))
{
autoSaveIdleMs_.setValue("1000");
}
}

@Override
Expand Down Expand Up @@ -536,6 +591,8 @@ else if (isSublime)
prefs_.foldStyle().setGlobalValue(foldMode_.getValue());
prefs_.surroundSelection().setGlobalValue(delimiterSurroundWidget_.getValue());
prefs_.executionBehavior().setGlobalValue(executionBehavior_.getValue());
prefs_.autoSaveOnIdle().setGlobalValue(autoSaveOnIdle_.getValue());
prefs_.autoSaveIdleMs().setGlobalValue(StringUtil.parseInt(autoSaveIdleMs_.getValue(), 1000));

return restartRequirement;
}
Expand Down Expand Up @@ -588,6 +645,8 @@ private void setEncoding(String encoding)
private final SelectWidget consoleColorMode_;
private final SelectWidget delimiterSurroundWidget_;
private final SelectWidget executionBehavior_;
private final SelectWidget autoSaveOnIdle_;
private final SelectWidget autoSaveIdleMs_;
private final TextBoxWithButton encoding_;
private String encodingValue_;
}
Original file line number Diff line number Diff line change
Expand Up @@ -2232,11 +2232,16 @@ public HandlerRegistration addAttachHandler(AttachEvent.Handler handler)
{
return widget_.addAttachHandler(handler);
}

public HandlerRegistration addEditorFocusHandler(FocusHandler handler)
{
return widget_.addFocusHandler(handler);
}

public HandlerRegistration addEditorBlurHandler(BlurHandler handler)
{
return widget_.addBlurHandler(handler);
}

public HandlerRegistration addContextMenuHandler(ContextMenuHandler handler)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.FocusHandler;
import com.google.gwt.event.dom.client.HasFocusHandlers;
import com.google.gwt.event.dom.client.HasKeyDownHandlers;
Expand Down Expand Up @@ -226,6 +227,7 @@ DocDisplay.AnchoredSelection createAnchoredSelection(Position start,

HandlerRegistration addAttachHandler(AttachEvent.Handler handler);
HandlerRegistration addEditorFocusHandler(FocusHandler handler);
HandlerRegistration addEditorBlurHandler(BlurHandler handler);
HandlerRegistration addCommandClickHandler(CommandClickEvent.Handler handler);
HandlerRegistration addFindRequestedHandler(FindRequestedEvent.Handler handler);
HandlerRegistration addCursorChangedHandler(CursorChangedHandler handler);
Expand Down
Loading

0 comments on commit 394689b

Please sign in to comment.