/*
* EditingTargetInlineChunkExecution.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.studio.client.workbench.views.source.editors;
import java.util.HashMap;
import org.rstudio.core.client.Mutable;
import org.rstudio.core.client.StringUtil;
import org.rstudio.studio.client.RStudioGinjector;
import org.rstudio.studio.client.application.events.EventBus;
import org.rstudio.studio.client.rmarkdown.events.ChunkExecStateChangedEvent;
import org.rstudio.studio.client.rmarkdown.events.SendToChunkConsoleEvent;
import org.rstudio.studio.client.rmarkdown.model.NotebookDocQueue;
import org.rstudio.studio.client.rmarkdown.model.NotebookQueueUnit;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteErrorEvent;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteErrorHandler;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteOutputEvent;
import org.rstudio.studio.client.workbench.views.console.events.ConsoleWriteOutputHandler;
import org.rstudio.studio.client.workbench.views.source.editors.text.ChunkInlineOutput;
import org.rstudio.studio.client.workbench.views.source.editors.text.DocDisplay;
import org.rstudio.studio.client.workbench.views.source.editors.text.Scope;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Position;
import org.rstudio.studio.client.workbench.views.source.editors.text.ace.Range;
import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedEvent;
import org.rstudio.studio.client.workbench.views.source.editors.text.events.CursorChangedHandler;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.inject.Inject;
public class EditingTargetInlineChunkExecution
implements ConsoleWriteOutputHandler,
ConsoleWriteErrorHandler,
ChunkExecStateChangedEvent.Handler
{
@Inject
private void initialize(EventBus events)
{
events_ = events;
events_.addHandler(ConsoleWriteOutputEvent.TYPE, this);
events_.addHandler(ConsoleWriteErrorEvent.TYPE, this);
events_.addHandler(ChunkExecStateChangedEvent.TYPE, this);
}
public EditingTargetInlineChunkExecution(DocDisplay display, String docId)
{
RStudioGinjector.INSTANCE.injectMembers(this);
display_ = display;
docId_ = docId;
outputs_ = new HashMap<String, ChunkInlineOutput>();
}
public void execute(Range range)
{
// synthesize an identifier for this chunk execution
final String chunkId = "i" + StringUtil.makeRandomId(12);
// check to see if we're already showing a panel for this range; if we
// are, remove it to make way for the new one
for (ChunkInlineOutput output: outputs_.values())
{
if (output.range().isEqualTo(range))
{
if (output.state() == ChunkInlineOutput.State.Finished)
{
// remove old, completed output for this input
output.hide();
outputs_.remove(output.chunkId());
}
else
{
// we already have an output panel for this input, and it's
// either waiting to execute or currently executing. ignore this
// request to re-execute the range, as it's likely to be an
// unintended duplicate.
return;
}
}
}
// create dummy scope for execution
Scope scope = Scope.createRScopeNode(
chunkId,
range.getStart(),
range.getEnd(),
Scope.SCOPE_TYPE_CHUNK);
// create popup panel to host output
final ChunkInlineOutput output = new ChunkInlineOutput(chunkId,
display_.createAnchoredSelection(range.getStart(), range.getEnd()));
// auto dismiss the panel when the cursor leaves the inline chunk
final Mutable<HandlerRegistration> cursorHandler =
new Mutable<HandlerRegistration>();
cursorHandler.set(display_.addCursorChangedHandler(
new CursorChangedHandler()
{
@Override
public void onCursorChanged(CursorChangedEvent event)
{
Position position = event.getPosition();
if (!output.range().contains(position))
{
output.hide();
}
}
}));
// when the popup is dismissed, clean up local state
output.addCloseHandler(new CloseHandler<PopupPanel>()
{
@Override
public void onClose(CloseEvent<PopupPanel> event)
{
outputs_.remove(chunkId);
cursorHandler.get().removeHandler();
}
});
// render offscreen until complete
output.setPopupPosition(-100000, -100000);
output.show();
outputs_.put(chunkId, output);
SendToChunkConsoleEvent event =
new SendToChunkConsoleEvent(docId_, scope, range,
NotebookQueueUnit.EXEC_SCOPE_INLINE);
events_.fireEvent(event);
}
// Handlers ----
@Override
public void onConsoleWriteError(ConsoleWriteErrorEvent event)
{
if (outputs_.containsKey(event.getConsole()))
outputs_.get(event.getConsole()).onConsoleWriteError(event);
}
@Override
public void onConsoleWriteOutput(ConsoleWriteOutputEvent event)
{
if (outputs_.containsKey(event.getConsole()))
outputs_.get(event.getConsole()).onConsoleWriteOutput(event);
}
@Override
public void onChunkExecStateChanged(ChunkExecStateChangedEvent event)
{
// ignore if not targeted at one of our chunks
if (event.getDocId() != docId_ ||
!outputs_.containsKey(event.getChunkId()))
return;
ChunkInlineOutput output = outputs_.get(event.getChunkId());
if (event.getExecState() == NotebookDocQueue.CHUNK_EXEC_STARTED)
{
output.setState(ChunkInlineOutput.State.Started);
}
else if (event.getExecState() == NotebookDocQueue.CHUNK_EXEC_FINISHED)
{
output.setState(ChunkInlineOutput.State.Finished);
output.positionNearRange(display_, output.range());
output.show();
}
}
// Private Members ----
private final String docId_;
private final DocDisplay display_;
private final HashMap<String, ChunkInlineOutput> outputs_;
// Injected ----
private EventBus events_;
}