Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for knitting in alternate directories #904

Merged
merged 8 commits into from Nov 22, 2016
1 change: 1 addition & 0 deletions NEWS.md
Expand Up @@ -7,4 +7,5 @@
### Miscellaneous

* Implement support for changing editor tabs with the mouse wheel
* Add option to knit in current working directory or project directory

3 changes: 3 additions & 0 deletions src/cpp/session/SessionMain.cpp
Expand Up @@ -686,6 +686,9 @@ void handleClientInit(const boost::function<void()>& initFunction,
sessionInfo["knit_params_available"] =
modules::rmarkdown::knitParamsAvailable();

sessionInfo["knit_working_dir_available"] =
modules::rmarkdown::knitWorkingDirAvailable();

sessionInfo["clang_available"] = modules::clang::isAvailable();

// don't show help home until we figure out a sensible heuristic
Expand Down
26 changes: 19 additions & 7 deletions src/cpp/session/modules/rmarkdown/NotebookDocQueue.cpp
Expand Up @@ -31,6 +31,7 @@ using namespace rstudio::core;
#define kDocQueueMaxUnits "max_units"
#define kDocQueueCommitMode "commit_mode"
#define kDocQueueCompletedUnits "completed_units"
#define kDocQueueWorkingDir "working_dir"

namespace rstudio {
namespace session {
Expand All @@ -39,8 +40,8 @@ namespace rmarkdown {
namespace notebook {

NotebookDocQueue::NotebookDocQueue(const std::string& docId,
const std::string& jobDesc, CommitMode commitMode,
int pixelWidth, int charWidth, int maxUnits) :
const std::string& jobDesc, const std::string& workingDir,
CommitMode commitMode, int pixelWidth, int charWidth, int maxUnits) :
docId_(docId),
jobDesc_(jobDesc),
commitMode_(commitMode),
Expand All @@ -61,10 +62,19 @@ NotebookDocQueue::NotebookDocQueue(const std::string& docId,

// read the default working dir; if it specifies a valid directory, use it
// as the working directory for executing chunks in this document
std::string workingDir;
json::readObject(vals, kChunkWorkingDir, &workingDir);
if (!workingDir.empty())
std::string docWorkingDir;
json::readObject(vals, kChunkWorkingDir, &docWorkingDir);
if (!docWorkingDir.empty())
{
// working directory set in setup chunk (i.e. knitr root.dir) takes
// precedence
setWorkingDir(docWorkingDir);
}
else if (!workingDir.empty())
{
// working directory given by IDE (current dir or project dir)
setWorkingDir(workingDir);
}

// read external code chunk contents
json::readObject(vals, kChunkExternals, &externalChunks_);
Expand All @@ -90,6 +100,7 @@ json::Object NotebookDocQueue::toJson() const
json::Object queue;
queue[kDocQueueId] = docId_;
queue[kDocQueueJobDesc] = jobDesc_;
queue[kDocQueueWorkingDir] = workingDir_.absolutePath();
queue[kDocQueueCommitMode] = commitMode_;
queue[kDocQueuePixelWidth] = pixelWidth_;
queue[kDocQueueCharWidth] = charWidth_;
Expand All @@ -106,10 +117,11 @@ core::Error NotebookDocQueue::fromJson(const core::json::Object& source,
// extract contained unit for manipulation
json::Array units;
int commitMode = 0, pixelWidth = 0, charWidth = 0, maxUnits = 0;
std::string docId, jobDesc;
std::string docId, jobDesc, workingDir;
Error error = json::readObject(source,
kDocQueueId, &docId,
kDocQueueJobDesc, &jobDesc,
kDocQueueWorkingDir, &workingDir,
kDocQueueCommitMode, &commitMode,
kDocQueuePixelWidth, &pixelWidth,
kDocQueueCharWidth, &charWidth,
Expand All @@ -118,7 +130,7 @@ core::Error NotebookDocQueue::fromJson(const core::json::Object& source,
if (error)
return error;

*pQueue = boost::make_shared<NotebookDocQueue>(docId, jobDesc,
*pQueue = boost::make_shared<NotebookDocQueue>(docId, jobDesc, workingDir,
static_cast<CommitMode>(commitMode), pixelWidth, charWidth,
maxUnits);

Expand Down
3 changes: 2 additions & 1 deletion src/cpp/session/modules/rmarkdown/NotebookDocQueue.hpp
Expand Up @@ -40,7 +40,8 @@ class NotebookDocQueue : boost::noncopyable
{
public:
NotebookDocQueue(const std::string& docId, const std::string& jobDesc,
CommitMode commitMode, int pixelWith, int charWidth, int maxUnits);
const std::string& workingDir, CommitMode commitMode, int pixelWith,
int charWidth, int maxUnits);

static core::Error fromJson(const core::json::Object& source,
boost::shared_ptr<NotebookDocQueue>* pQueue);
Expand Down
36 changes: 27 additions & 9 deletions src/cpp/session/modules/rmarkdown/SessionRMarkdown.cpp
Expand Up @@ -234,13 +234,15 @@ class RenderRmd : public async_r::AsyncRProcess
bool sourceNavigation,
bool asTempfile,
bool asShiny,
std::string existingOutputFile)
const std::string& existingOutputFile,
const std::string& workingDir)
{
boost::shared_ptr<RenderRmd> pRender(new RenderRmd(targetFile,
sourceLine,
sourceNavigation,
asShiny));
pRender->start(format, encoding, paramsFile, asTempfile, existingOutputFile);
pRender->start(format, encoding, paramsFile, asTempfile,
existingOutputFile, workingDir);
return pRender;
}

Expand Down Expand Up @@ -301,7 +303,8 @@ class RenderRmd : public async_r::AsyncRProcess
const std::string& encoding,
const std::string& paramsFile,
bool asTempfile,
std::string existingOutputFile)
const std::string& existingOutputFile,
const std::string& workingDir)
{
Error error;
json::Object dataJson;
Expand Down Expand Up @@ -355,6 +358,13 @@ class RenderRmd : public async_r::AsyncRProcess
renderOptions += ", params = readRDS('" + paramsFile + "')";
}

// use the stated working directory if specified
if (!workingDir.empty())
{
renderOptions += ", knit_root_dir = '" +
string_utils::utf8ToSystem(workingDir) + "'";
}

// output to a temporary directory if specified (no need to do this
// for Shiny since it already renders to a temporary dir)
if (asTempfile && !isShiny_)
Expand Down Expand Up @@ -897,7 +907,8 @@ void doRenderRmd(const std::string& file,
bool sourceNavigation,
bool asTempfile,
bool asShiny,
std::string existingOutputFile,
const std::string& existingOutputFile,
const std::string& workingDir,
json::JsonRpcResponse* pResponse)
{
if (s_pCurrentRender_ &&
Expand All @@ -916,7 +927,8 @@ void doRenderRmd(const std::string& file,
sourceNavigation,
asTempfile,
asShiny,
existingOutputFile);
existingOutputFile,
workingDir);
pResponse->setResult(true);
}
}
Expand All @@ -925,7 +937,8 @@ Error renderRmd(const json::JsonRpcRequest& request,
json::JsonRpcResponse* pResponse)
{
int line = -1, type = kRenderTypeStatic;
std::string file, format, encoding, paramsFile, existingOutputFile;
std::string file, format, encoding, paramsFile, existingOutputFile,
workingDir;
bool asTempfile = false;
Error error = json::readParams(request.params,
&file,
Expand All @@ -935,7 +948,8 @@ Error renderRmd(const json::JsonRpcRequest& request,
&paramsFile,
&asTempfile,
&type,
&existingOutputFile);
&existingOutputFile,
&workingDir);
if (error)
return error;

Expand Down Expand Up @@ -978,7 +992,7 @@ Error renderRmd(const json::JsonRpcRequest& request,
// not a notebook, do render work
doRenderRmd(file, line, format, encoding, paramsFile,
true, asTempfile, type == kRenderTypeShiny, existingOutputFile,
pResponse);
workingDir, pResponse);
}

return Success();
Expand All @@ -999,7 +1013,7 @@ Error renderRmdSource(const json::JsonRpcRequest& request,
return error;

doRenderRmd(rmdTempFile.absolutePath(), -1, "", "UTF-8", "",
false, false, false, "", pResponse);
false, false, false, "", "", pResponse);

return Success();
}
Expand Down Expand Up @@ -1356,6 +1370,10 @@ bool knitParamsAvailable()
module_context::isPackageVersionInstalled("knitr", "1.10.18");
}

bool knitWorkingDirAvailable()
{
return module_context::isPackageVersionInstalled("rmarkdown", "1.1.9017");
}

bool rmarkdownPackageAvailable()
{
Expand Down
2 changes: 2 additions & 0 deletions src/cpp/session/modules/rmarkdown/SessionRMarkdown.hpp
Expand Up @@ -37,6 +37,8 @@ bool rmarkdownPackageAvailable();

bool knitParamsAvailable();

bool knitWorkingDirAvailable();

core::Error evaluateRmdParams(const std::string& docId);

core::Error initialize();
Expand Down
@@ -0,0 +1,79 @@
/*
* DocShadowPropMenuItem.java
*
* Copyright (C) 2009-16 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.core.client.widget;

import org.rstudio.studio.client.workbench.prefs.model.Prefs.PrefValue;
import org.rstudio.studio.client.workbench.views.source.model.DocUpdateSentinel;

import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;

/**
* DocShadowPropMenuItem is a menu item representing a local document property
* that shadows (overrides) a global property.
*/
public class DocShadowPropMenuItem extends DocPropMenuItem
{
/**
* Creates a new shadowed document property menu item.
*
* @param label The label for the menu item
* @param docUpdate The update sentinel
* @param pref The global preference to track
* @param propName The name of the document property to track
* @param targetValue The value of the document property when the menu item
* is checked.
*/
public DocShadowPropMenuItem(String label, DocUpdateSentinel docUpdate,
PrefValue<String> pref, String propName, String targetValue)
{
super(label,
docUpdate,
docUpdate.getProperty(propName, pref.getValue()) == targetValue,
propName,
targetValue);

propName_ = propName;
sentinel_ = docUpdate;
pref_ = pref;
target_ = targetValue;

pref_.addValueChangeHandler(new ValueChangeHandler<String>()
{
@Override
public void onValueChange(ValueChangeEvent<String> event)
{
onStateChanged();
}
});
onStateChanged();
}

@Override
public boolean isChecked()
{
if (sentinel_ == null)
return false;
else if (sentinel_.hasProperty(propName_))
return super.isChecked();
else
return pref_.getValue() == target_;
}

private final String propName_;
private final String target_;
private final DocUpdateSentinel sentinel_;
private final PrefValue<String> pref_;
}
@@ -1,7 +1,7 @@
/*
* ToolbarPopupMenu.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
* Copyright (C) 2009-16 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
Expand All @@ -22,9 +22,11 @@
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Event.NativePreviewEvent;
import com.google.gwt.user.client.Event.NativePreviewHandler;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;
import com.google.gwt.user.client.ui.MenuItemSeparator;
import com.google.gwt.user.client.ui.Widget;
Expand Down Expand Up @@ -81,6 +83,11 @@ public void addItem(MenuItem menuItem)
menuBar_.addItem(menuItem);
}

public void addItem(SafeHtml html, MenuBar popup)
{
menuBar_.addItem(html, popup);
}

public void insertItem(MenuItem menuItem, int beforeIndex)
{
ScheduledCommand command = menuItem.getScheduledCommand() ;
Expand Down
@@ -1,7 +1,7 @@
/*
* ToolbarPopupMenuButton.java
*
* Copyright (C) 2009-12 by RStudio, Inc.
* Copyright (C) 2009-16 by RStudio, Inc.
*
* Unless you have received this program directly from RStudio pursuant
* to the terms of a commercial license agreement with RStudio, then
Expand All @@ -21,7 +21,9 @@
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
import com.google.gwt.user.client.Command;
import com.google.gwt.user.client.ui.MenuBar;
import com.google.gwt.user.client.ui.MenuItem;

public class ToolbarPopupMenuButton extends ToolbarButton
Expand Down Expand Up @@ -74,6 +76,15 @@ public void execute()
getMenu().addItem(item);
}

public void addMenuItem(MenuBar subMenu, String label)
{
SafeHtmlBuilder html = new SafeHtmlBuilder();
html.appendHtmlConstant("<span style=\"margin-left: 25px;\">");
html.appendEscaped(label);
html.appendHtmlConstant("</span>");
getMenu().addItem(html.toSafeHtml(), subMenu);
}

public void addSeparator()
{
getMenu().addSeparator();
Expand Down