diff --git a/src/cpp/core/include/core/json/JsonRpc.hpp b/src/cpp/core/include/core/json/JsonRpc.hpp index f5365cb485e..e3e1de60da5 100644 --- a/src/cpp/core/include/core/json/JsonRpc.hpp +++ b/src/cpp/core/include/core/json/JsonRpc.hpp @@ -312,7 +312,28 @@ core::Error readObject(const json::Object& object, return Success() ; } - + +template +core::Error readObject(const json::Object& object, + const std::string& name, + const T& defaultValue, + T* pValue) +{ + json::Object::const_iterator it = object.find(name) ; + if (it == object.end()) + { + *pValue = defaultValue; + return Success(); + } + + if (!isType(it->second)) + return Error(errc::ParamTypeMismatch, ERROR_LOCATION) ; + + *pValue = it->second.get_value() ; + + return Success() ; +} + template core::Error readObjectParam(const json::Array& params, unsigned int index, @@ -419,7 +440,147 @@ core::Error readObjectParam(const json::Array& params, name4, pValue4); } - + +template +core::Error readObject(const json::Object& object, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5) +{ + Error error = readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4); + if (error) + return error; + + return readObject(object, name5, pValue5); +} + +template +core::Error readObjectParam(const json::Array& params, + unsigned int index, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5) +{ + json::Object object; + Error error = json::readParam(params, index, &object); + if (error) + return error; + + return readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4, + name5, pValue5); +} + + +template +core::Error readObject(const json::Object& object, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5, + const std::string& name6, T6* pValue6) +{ + Error error = readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4, + name5, pValue5); + if (error) + return error; + + return readObject(object, name6, pValue6); +} + + +template +core::Error readObjectParam(const json::Array& params, + unsigned int index, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5, + const std::string& name6, T6* pValue6) +{ + json::Object object; + Error error = json::readParam(params, index, &object); + if (error) + return error; + + return readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4, + name5, pValue5, + name6, pValue6); +} + + +template +core::Error readObject(const json::Object& object, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5, + const std::string& name6, T6* pValue6, + const std::string& name7, T7* pValue7) +{ + Error error = readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4, + name5, pValue5, + name6, pValue6); + if (error) + return error; + + return readObject(object, name7, pValue7); +} + +template +core::Error readObjectParam(const json::Array& params, + unsigned int index, + const std::string& name1, T1* pValue1, + const std::string& name2, T2* pValue2, + const std::string& name3, T3* pValue3, + const std::string& name4, T4* pValue4, + const std::string& name5, T5* pValue5, + const std::string& name6, T6* pValue6, + const std::string& name7, T7* pValue7) +{ + json::Object object; + Error error = json::readParam(params, index, &object); + if (error) + return error; + + return readObject(object, + name1, pValue1, + name2, pValue2, + name3, pValue3, + name4, pValue4, + name5, pValue5, + name6, pValue6, + name7, pValue7); +} + + + // json rpc response class JsonRpcResponse diff --git a/src/cpp/core/include/core/r_util/RProjectFile.hpp b/src/cpp/core/include/core/r_util/RProjectFile.hpp index 485eac179de..4f50ddf000a 100644 --- a/src/cpp/core/include/core/r_util/RProjectFile.hpp +++ b/src/cpp/core/include/core/r_util/RProjectFile.hpp @@ -40,7 +40,10 @@ struct RProjectConfig : version(1.0), saveWorkspace(DefaultValue), restoreWorkspace(DefaultValue), - alwaysSaveHistory(DefaultValue) + alwaysSaveHistory(DefaultValue), + useSpacesForTab(true), + numSpacesForTab(2), + encoding() { } @@ -48,11 +51,16 @@ struct RProjectConfig int saveWorkspace; int restoreWorkspace; int alwaysSaveHistory; + bool useSpacesForTab; + int numSpacesForTab; + std::string encoding; }; Error readProjectFile(const FilePath& projectFilePath, + const RProjectConfig& defaultConfig, RProjectConfig* pConfig, + bool* pProvidedDefaults, std::string* pUserErrMsg); Error writeProjectFile(const FilePath& projectFilePath, diff --git a/src/cpp/core/r_util/RProjectFile.cpp b/src/cpp/core/r_util/RProjectFile.cpp index f0745c0b810..a877818be5e 100644 --- a/src/cpp/core/r_util/RProjectFile.cpp +++ b/src/cpp/core/r_util/RProjectFile.cpp @@ -76,6 +76,48 @@ bool interpretYesNoAskValue(const std::string& value, } } +std::string boolValueToString(bool value) +{ + if (value) + return "Yes"; + else + return "No"; +} + +bool interpretBoolValue(const std::string& value, bool* pValue) +{ + std::string valueLower = string_utils::toLower(value); + boost::algorithm::trim(valueLower); + if (valueLower == "yes") + { + *pValue = true; + return true; + } + else if (valueLower == "no") + { + *pValue = false; + return true; + } + else + { + return false; + } +} + + +bool interpretIntValue(const std::string& value, int* pValue) +{ + try + { + *pValue = boost::lexical_cast(value); + return true; + } + catch(const boost::bad_lexical_cast& e) + { + return false; + } +} + } // anonymous namespace std::ostream& operator << (std::ostream& stream, const YesNoAskValue& val) @@ -102,9 +144,14 @@ std::ostream& operator << (std::ostream& stream, const YesNoAskValue& val) Error readProjectFile(const FilePath& projectFilePath, + const RProjectConfig& defaultConfig, RProjectConfig* pConfig, + bool* pProvidedDefaults, std::string* pUserErrMsg) { + // default to not providing defaults + *pProvidedDefaults = false; + // first read the project DCF file typedef std::map Fields; Fields dcfFields; @@ -144,7 +191,7 @@ Error readProjectFile(const FilePath& projectFilePath, ERROR_LOCATION); } - // extract restore workspace (optional) + // extract restore workspace it = dcfFields.find("RestoreWorkspace"); if (it != dcfFields.end()) { @@ -153,10 +200,11 @@ Error readProjectFile(const FilePath& projectFilePath, } else { - pConfig->restoreWorkspace = DefaultValue; + pConfig->restoreWorkspace = defaultConfig.restoreWorkspace; + *pProvidedDefaults = true; } - // extract save workspace (optional) + // extract save workspace it = dcfFields.find("SaveWorkspace"); if (it != dcfFields.end()) { @@ -165,10 +213,11 @@ Error readProjectFile(const FilePath& projectFilePath, } else { - pConfig->saveWorkspace = DefaultValue; + pConfig->saveWorkspace = defaultConfig.saveWorkspace; + *pProvidedDefaults = true; } - // extract always save history (optional) + // extract always save history it = dcfFields.find("AlwaysSaveHistory"); if (it != dcfFields.end()) { @@ -177,7 +226,46 @@ Error readProjectFile(const FilePath& projectFilePath, } else { - pConfig->alwaysSaveHistory = DefaultValue; + pConfig->alwaysSaveHistory = defaultConfig.alwaysSaveHistory; + *pProvidedDefaults = true; + } + + // extract spaces for tab + it = dcfFields.find("UseSpacesForTab"); + if (it != dcfFields.end()) + { + if (!interpretBoolValue(it->second, &(pConfig->useSpacesForTab))) + return requiredFieldError("UseSpacesForTab", pUserErrMsg); + } + else + { + pConfig->useSpacesForTab = defaultConfig.useSpacesForTab; + *pProvidedDefaults = true; + } + + // extract num spaces for tab + it = dcfFields.find("NumSpacesForTab"); + if (it != dcfFields.end()) + { + if (!interpretIntValue(it->second, &(pConfig->numSpacesForTab))) + return requiredFieldError("NumSpacesForTab", pUserErrMsg); + } + else + { + pConfig->numSpacesForTab = defaultConfig.numSpacesForTab; + *pProvidedDefaults = true; + } + + // extract encoding + it = dcfFields.find("Encoding"); + if (it != dcfFields.end()) + { + pConfig->encoding = it->second; + } + else + { + pConfig->encoding = defaultConfig.encoding; + *pProvidedDefaults = true; } return Success(); @@ -193,13 +281,21 @@ Error writeProjectFile(const FilePath& projectFilePath, "\n" "RestoreWorkspace: %2%\n" "SaveWorkspace: %3%\n" - "AlwaysSaveHistory: %4%\n"); + "AlwaysSaveHistory: %4%\n" + "\n" + "UseSpacesForTab: %5%\n" + "NumSpacesForTab: %6%\n" + "\n" + "Encoding: %7%\n"); std::string contents = boost::str(fmt % boost::io::group(std::fixed, std::setprecision(1), config.version) % yesNoAskValueToString(config.restoreWorkspace) % yesNoAskValueToString(config.saveWorkspace) % - yesNoAskValueToString(config.alwaysSaveHistory)); + yesNoAskValueToString(config.alwaysSaveHistory) % + boolValueToString(config.useSpacesForTab) % + config.numSpacesForTab % + config.encoding); // write it return writeStringToFile(projectFilePath, diff --git a/src/cpp/session/SessionUserSettings.cpp b/src/cpp/session/SessionUserSettings.cpp index 0c4b8beb1e0..262f2c72fae 100644 --- a/src/cpp/session/SessionUserSettings.cpp +++ b/src/cpp/session/SessionUserSettings.cpp @@ -149,26 +149,58 @@ void UserSettings::setUiPrefs(const core::json::Object& prefsObject) updatePrefsCache(prefsObject); } -void UserSettings::updatePrefsCache(const json::Object& prefs) const +namespace { + +template +T readPref(const json::Object& prefs, + const std::string& name, + const T& defaultValue) { - // read fields - std::string defaultEncoding; - Error error = json::readObject(prefs,"default_encoding", &defaultEncoding); + T value; + Error error = json::readObject(prefs, + name, + defaultValue, + &value); if (error) + { + value = defaultValue; + error.addProperty("pref", name); LOG_ERROR(error); + } + + return value; +} + +} // anonymous namespace + +void UserSettings::updatePrefsCache(const json::Object& prefs) const +{ + bool useSpacesForTab = readPref(prefs, "use_spaces_for_tab", true); + pUseSpacesForTab_.reset(new bool(useSpacesForTab)); + + int numSpacesForTab = readPref(prefs, "num_spaces_for_tab", 2); + pNumSpacesForTab_.reset(new int(numSpacesForTab)); - // set pref - pDefaultEncoding_.reset(new std::string(defaultEncoding)); + std::string enc = readPref(prefs, "default_encoding", ""); + pDefaultEncoding_.reset(new std::string(enc)); } // readers for ui prefs -std::string UserSettings::defaultEncoding() const + +bool UserSettings::useSpacesForTab() const +{ + return readUiPref(pUseSpacesForTab_); +} + +int UserSettings::numSpacesForTab() const { - if (!pDefaultEncoding_) - updatePrefsCache(uiPrefs()); + return readUiPref(pNumSpacesForTab_); +} - return *pDefaultEncoding_; +std::string UserSettings::defaultEncoding() const +{ + return readUiPref(pDefaultEncoding_); } bool UserSettings::alwaysRestoreLastProject() const diff --git a/src/cpp/session/include/session/SessionUserSettings.hpp b/src/cpp/session/include/session/SessionUserSettings.hpp index f5f6e4f9c25..f0143bfc2e3 100644 --- a/src/cpp/session/include/session/SessionUserSettings.hpp +++ b/src/cpp/session/include/session/SessionUserSettings.hpp @@ -76,6 +76,8 @@ class UserSettings : boost::noncopyable void setUiPrefs(const core::json::Object& prefsObject); // readers for ui prefs + bool useSpacesForTab() const; + int numSpacesForTab() const; std::string defaultEncoding() const; bool alwaysRestoreLastProject() const; @@ -115,11 +117,23 @@ class UserSettings : boost::noncopyable void updatePrefsCache() const; void updatePrefsCache(const core::json::Object& uiPrefs) const; + template + T readUiPref(const boost::scoped_ptr& pPref) const + { + if (!pPref) + updatePrefsCache(uiPrefs()); + + return *pPref; + } + private: core::Settings settings_; // cached prefs values + mutable boost::scoped_ptr pUseSpacesForTab_; + mutable boost::scoped_ptr pNumSpacesForTab_; mutable boost::scoped_ptr pDefaultEncoding_; + }; } // namespace session diff --git a/src/cpp/session/modules/SessionCodeSearch.cpp b/src/cpp/session/modules/SessionCodeSearch.cpp index f0df128ac52..5644e2a6585 100644 --- a/src/cpp/session/modules/SessionCodeSearch.cpp +++ b/src/cpp/session/modules/SessionCodeSearch.cpp @@ -52,8 +52,8 @@ std::vector > s_sourceIndexes; class SourceFileIndexer : boost::noncopyable { public: - SourceFileIndexer(const std::string& encoding) - : encoding_(encoding), + SourceFileIndexer() + : encoding_(projects::projectContext().config().encoding), projectRootDir_(projects::projectContext().directory()), dirIter_(projectRootDir_) { @@ -127,14 +127,8 @@ class SourceFileIndexer : boost::noncopyable void indexProjectFiles() { - // determine encoding - std::string encoding = userSettings().defaultEncoding(); - if (encoding.empty()) - encoding = ::locale2charset(NULL); - // create indexer - boost::shared_ptr pIndexer( - new SourceFileIndexer(encoding)); + boost::shared_ptr pIndexer(new SourceFileIndexer()); // schedule indexing to occur up front + at idle time module_context::scheduleIncrementalWork( diff --git a/src/cpp/session/projects/SessionProjectContext.cpp b/src/cpp/session/projects/SessionProjectContext.cpp index 525a11c9adc..d3c597519dd 100644 --- a/src/cpp/session/projects/SessionProjectContext.cpp +++ b/src/cpp/session/projects/SessionProjectContext.cpp @@ -117,12 +117,36 @@ Error ProjectContext::initialize(const FilePath& projectFile, return error; } + // setup defaults for project file + r_util::RProjectConfig defaultConfig; + defaultConfig.useSpacesForTab = userSettings().useSpacesForTab(); + defaultConfig.numSpacesForTab = userSettings().numSpacesForTab(); + if (!userSettings().defaultEncoding().empty()) + defaultConfig.encoding = userSettings().defaultEncoding(); + else + defaultConfig.encoding = "UTF-8"; + + // read project file config + bool providedDefaults; r_util::RProjectConfig config; - error = r_util::readProjectFile(projectFile, &config, pUserErrMsg); + error = r_util::readProjectFile(projectFile, + defaultConfig, + &config, + &providedDefaults, + pUserErrMsg); if (error) return error; + // if we provided defaults then re-write the project file + // with the defaults + if (providedDefaults) + { + error = r_util::writeProjectFile(projectFile, config); + if (error) + LOG_ERROR(error); + } + // initialize members file_ = projectFile; directory_ = file_.parent(); diff --git a/src/cpp/session/projects/SessionProjects.cpp b/src/cpp/session/projects/SessionProjects.cpp index dff61d689d0..0864bcb1ed0 100644 --- a/src/cpp/session/projects/SessionProjects.cpp +++ b/src/cpp/session/projects/SessionProjects.cpp @@ -73,6 +73,9 @@ json::Object projectConfigJson(const r_util::RProjectConfig& config) configJson["restore_workspace"] = config.restoreWorkspace; configJson["save_workspace"] = config.saveWorkspace; configJson["always_save_history"] = config.alwaysSaveHistory; + configJson["use_spaces_for_tab"] = config.useSpacesForTab; + configJson["num_spaces_for_tab"] = config.numSpacesForTab; + configJson["encoding"] = config.encoding; return configJson; } @@ -93,7 +96,10 @@ Error writeProjectConfig(const json::JsonRpcRequest& request, "version", &(config.version), "restore_workspace", &(config.restoreWorkspace), "save_workspace", &(config.saveWorkspace), - "always_save_history", &(config.alwaysSaveHistory)); + "always_save_history", &(config.alwaysSaveHistory), + "use_spaces_for_tab", &(config.useSpacesForTab), + "num_spaces_for_tab", &(config.numSpacesForTab), + "encoding", &(config.encoding)); if (error) return error; diff --git a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java index 0d2033e79af..b13d87df767 100644 --- a/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java +++ b/src/gwt/src/org/rstudio/studio/client/projects/model/RProjectConfig.java @@ -27,12 +27,18 @@ protected RProjectConfig() public native static final RProjectConfig create(int restoreWorkspace, int saveWorkspace, - int alwaysSaveHistory) /*-{ + int alwaysSaveHistory, + boolean useSpacesForTab, + int numSpacesForTab, + String encoding) /*-{ var config = new Object(); config.version = 1.0; config.restore_workspace = restoreWorkspace; config.save_workspace = saveWorkspace; config.always_save_history = alwaysSaveHistory; + config.use_spaces_for_tab = useSpacesForTab; + config.num_spaces_for_tab = numSpacesForTab; + config.encoding = encoding; return config; }-*/; @@ -51,4 +57,16 @@ public native final int getSaveWorkspace() /*-{ public native final int getAlwaysSaveHistory() /*-{ return this.always_save_history; }-*/; + + public native final boolean getUseSpacesForTab() /*-{ + return this.use_spaces_for_tab; + }-*/; + + public native final int getNumSpacesForTab() /*-{ + return this.num_spaces_for_tab; + }-*/; + + public native final String getEncoding() /*-{ + return this.encoding; + }-*/; } diff --git a/src/gwt/src/org/rstudio/studio/client/projects/ui/ProjectOptionsDialog.java b/src/gwt/src/org/rstudio/studio/client/projects/ui/ProjectOptionsDialog.java index 4504b353414..485fbac5144 100644 --- a/src/gwt/src/org/rstudio/studio/client/projects/ui/ProjectOptionsDialog.java +++ b/src/gwt/src/org/rstudio/studio/client/projects/ui/ProjectOptionsDialog.java @@ -41,7 +41,10 @@ protected RProjectConfig collectInput() { return RProjectConfig.create(restoreWorkspace_.getSelectedValue(), saveWorkspace_.getSelectedValue(), - alwaysSaveHistory_.getSelectedValue()); + alwaysSaveHistory_.getSelectedValue(), + initialSettings_.getUseSpacesForTab(), + initialSettings_.getNumSpacesForTab(), + initialSettings_.getEncoding()); } @Override diff --git a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java index 26412488b24..6a939e125f4 100644 --- a/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java +++ b/src/gwt/src/org/rstudio/studio/client/workbench/prefs/model/UIPrefs.java @@ -48,11 +48,13 @@ public PrefValue paneConfig() return object("pane_config"); } + // NOTE: UserSettings.cpp depends on the name of this value public PrefValue useSpacesForTab() { return bool("use_spaces_for_tab", true); } + // NOTE: UserSettings.cpp depends on the name of this value public PrefValue numSpacesForTab() { return integer("num_spaces_for_tab", 2); @@ -93,6 +95,7 @@ public PrefValue theme() return string("theme", null); } + // NOTE: UserSettings.cpp depends on the name of this value public PrefValue defaultEncoding() { return string("default_encoding", "");