use of org.rstudio.core.client.Mutable in project rstudio by rstudio.
the class RMarkdownChunkHeaderParser method parse.
public static final void parse(String line, Map<String, String> options) {
// set up state
Mutable<String> key = new Mutable<String>();
Consumer keyConsumer = new MutableConsumer(key);
Mutable<String> val = new Mutable<String>();
Consumer valConsumer = new MutableConsumer(val);
line = trimBoundary(line);
TextCursor cursor = new TextCursor(line);
// force default R engine
options.put("engine", ensureQuoted("r"));
// consume engine
if (!consumeEngine(cursor, options))
return;
// consume whitespace and commas
if (!cursor.consumeUntilRegex("[^\\s,]"))
return;
// a label soon after
if (!consumeKey(cursor, keyConsumer))
return;
// found, this must have been a label
if (!cursor.consumeUntilRegex("[,=]")) {
options.put("label", ensureQuoted(key.get().trim()));
return;
}
char ch = cursor.peek();
if (ch == ',') {
// found a comma -- this must have been a label
options.put("label", ensureQuoted(key.get().trim()));
} else {
// found an '=' -- this was a key for a chunk option
if (!cursor.consume('='))
return;
// eat whitespace
if (!cursor.consumeUntilRegex("\\S"))
return;
// consume value
if (!consumeValue(cursor, valConsumer))
return;
// set option
options.put(key.get(), val.get());
// move to next comma
if (!cursor.fwdToCharacter(',', false))
return;
}
while (cursor.peek() == ',') {
// eat whitespace and commas
if (!cursor.consumeUntilRegex("[^\\s,]"))
return;
// consume key
if (!consumeKey(cursor, keyConsumer))
return;
// eat whitespace
if (!cursor.consumeUntilRegex("\\S"))
return;
// check '='
if (!cursor.consume('='))
return;
// eat whitespace
if (!cursor.consumeUntilRegex("\\S"))
return;
// consume value
if (!consumeValue(cursor, valConsumer))
return;
// update options
options.put(StringUtil.stringValue(key.get().trim()), val.get().trim());
// find next comma
if (!cursor.consumeUntil(','))
return;
}
return;
}
use of org.rstudio.core.client.Mutable in project rstudio by rstudio.
the class EditingTargetInlineChunkExecution method execute.
public void execute(Range range) {
// synthesize an identifier for this chunk execution
final String chunkId = "i" + StringUtil.makeRandomId(12);
// 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 {
// 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);
}
use of org.rstudio.core.client.Mutable in project rstudio by rstudio.
the class ImagePreviewer method onPreviewImageLineWidget.
private static void onPreviewImageLineWidget(final DocDisplay display, final DocUpdateSentinel sentinel, final String href, final String attributes, final Position position, final Range tokenRange) {
// if we already have a line widget for this row, bail
LineWidget lineWidget = display.getLineWidgetForRow(position.getRow());
if (lineWidget != null)
return;
// shared mutable state that we hide in this closure
final Mutable<PinnedLineWidget> plw = new Mutable<PinnedLineWidget>();
final Mutable<ChunkOutputWidget> cow = new Mutable<ChunkOutputWidget>();
final Mutable<HandlerRegistration> docChangedHandler = new Mutable<HandlerRegistration>();
final Mutable<HandlerRegistration> renderHandler = new Mutable<HandlerRegistration>();
// command that ensures state is cleaned up when widget hidden
final Command onDetach = new Command() {
private void detach() {
// detach chunk output widget
cow.set(null);
// detach pinned line widget
if (plw.get() != null)
plw.get().detach();
plw.set(null);
// detach render handler
if (renderHandler.get() != null)
renderHandler.get().removeHandler();
renderHandler.set(null);
// detach doc changed handler
if (docChangedHandler.get() != null)
docChangedHandler.get().removeHandler();
docChangedHandler.set(null);
}
@Override
public void execute() {
// if the associated chunk output widget has been cleaned up,
// make a last-ditch detach effort anyhow
ChunkOutputWidget widget = cow.get();
if (widget == null) {
detach();
return;
}
// fade out and then detach
FadeOutAnimation anim = new FadeOutAnimation(widget, new Command() {
@Override
public void execute() {
detach();
}
});
anim.run(400);
}
};
// construct placeholder for image
final SimplePanel container = new SimplePanel();
container.addStyleName(RES.styles().container());
final Label noImageLabel = new Label("(No image at path " + href + ")");
// resize command (used by various routines that need to respond
// to width / height change events)
final CommandWithArg<Integer> onResize = new CommandWithArg<Integer>() {
private int state_ = -1;
@Override
public void execute(Integer height) {
// defend against missing chunk output widget (can happen if a widget
// is closed / dismissed before image finishes loading)
ChunkOutputWidget widget = cow.get();
if (widget == null)
return;
// don't resize if the chunk widget if we were already collapsed
int state = widget.getExpansionState();
if (state == state_ && state == ChunkOutputWidget.COLLAPSED)
return;
state_ = state;
widget.getFrame().setHeight(height + "px");
LineWidget lw = plw.get().getLineWidget();
lw.setPixelHeight(height);
display.onLineWidgetChanged(lw);
}
};
// construct our image
String srcPath = imgSrcPathFromHref(sentinel, href);
final Image image = new Image(srcPath);
image.addStyleName(RES.styles().image());
// parse and inject attributes
Map<String, String> parsedAttributes = HTMLAttributesParser.parseAttributes(attributes);
final Element imgEl = image.getElement();
for (Map.Entry<String, String> entry : parsedAttributes.entrySet()) {
String key = entry.getKey();
String val = entry.getValue();
if (StringUtil.isNullOrEmpty(key) || StringUtil.isNullOrEmpty(val))
continue;
imgEl.setAttribute(key, val);
}
// add load handlers to image
DOM.sinkEvents(imgEl, Event.ONLOAD | Event.ONERROR);
DOM.setEventListener(imgEl, new EventListener() {
@Override
public void onBrowserEvent(Event event) {
if (DOM.eventGetType(event) == Event.ONLOAD) {
final ImageElementEx imgEl = image.getElement().cast();
int minWidth = Math.min(imgEl.naturalWidth(), 100);
int maxWidth = Math.min(imgEl.naturalWidth(), 650);
Style style = imgEl.getStyle();
boolean hasWidth = imgEl.hasAttribute("width") || style.getProperty("width") != null;
if (!hasWidth) {
style.setProperty("width", "100%");
style.setProperty("minWidth", minWidth + "px");
style.setProperty("maxWidth", maxWidth + "px");
}
// attach to container
container.setWidget(image);
// update widget
int height = image.getOffsetHeight() + 10;
onResize.execute(height);
} else if (DOM.eventGetType(event) == Event.ONERROR) {
container.setWidget(noImageLabel);
onResize.execute(50);
}
}
});
// handle editor resize events
final Timer renderTimer = new Timer() {
@Override
public void run() {
int height = image.getOffsetHeight() + 30;
onResize.execute(height);
}
};
// initialize render handler
renderHandler.set(display.addRenderFinishedHandler(new RenderFinishedEvent.Handler() {
private int width_;
@Override
public void onRenderFinished(RenderFinishedEvent event) {
int width = display.getBounds().getWidth();
if (width == width_)
return;
width_ = width;
renderTimer.schedule(100);
}
}));
// initialize doc changed handler
docChangedHandler.set(display.addDocumentChangedHandler(new DocumentChangedEvent.Handler() {
private String href_ = href;
private String attributes_ = StringUtil.notNull(attributes);
private final Timer refreshImageTimer = new Timer() {
@Override
public void run() {
// if the discovered href isn't an image link, just bail
if (!ImagePreviewer.isImageHref(href_))
return;
// set new src location (load handler will replace label as needed)
container.setWidget(new SimplePanel());
noImageLabel.setText("(No image at path " + href_ + ")");
image.getElement().setAttribute("src", imgSrcPathFromHref(sentinel, href_));
// parse and inject attributes
Map<String, String> parsedAttributes = HTMLAttributesParser.parseAttributes(attributes_);
final Element imgEl = image.getElement();
for (Map.Entry<String, String> entry : parsedAttributes.entrySet()) {
String key = entry.getKey();
String val = entry.getValue();
if (StringUtil.isNullOrEmpty(key) || StringUtil.isNullOrEmpty(val))
continue;
imgEl.setAttribute(key, val);
}
}
};
private void onDocumentChangedImpl(DocumentChangedEvent event) {
int row = plw.get().getRow();
Range range = event.getEvent().getRange();
if (range.getStart().getRow() <= row && row <= range.getEnd().getRow()) {
String line = display.getLine(row);
if (ImagePreviewer.isStandaloneMarkdownLink(line)) {
// check to see if the URL text has been updated
Token hrefToken = null;
JsArray<Token> tokens = display.getTokens(row);
for (Token token : JsUtil.asIterable(tokens)) {
if (token.hasType("href")) {
hrefToken = token;
break;
}
}
if (hrefToken == null)
return;
String attributes = "";
int startBraceIdx = line.indexOf("){");
int endBraceIdx = line.lastIndexOf("}");
if (startBraceIdx != -1 && endBraceIdx != -1 && endBraceIdx > startBraceIdx) {
attributes = line.substring(startBraceIdx + 2, endBraceIdx).trim();
}
// (avoid flickering + re-requests of same URL)
if (hrefToken.getValue().equals(href_) && attributes.equals(attributes_))
return;
// cache href and schedule refresh of image
href_ = hrefToken.getValue();
attributes_ = attributes;
refreshImageTimer.schedule(700);
} else {
onDetach.execute();
}
}
}
@Override
public void onDocumentChanged(final DocumentChangedEvent event) {
// ignore 'removeLines' events as they won't mutate the actual
// line containing the markdown link
String action = event.getEvent().getAction();
if (action.equals("removeLines"))
return;
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
@Override
public void execute() {
onDocumentChangedImpl(event);
}
});
}
}));
ChunkOutputHost host = new ChunkOutputHost() {
@Override
public void onOutputRemoved(final ChunkOutputWidget widget) {
onDetach.execute();
}
@Override
public void onOutputHeightChanged(ChunkOutputWidget widget, int height, boolean ensureVisible) {
onResize.execute(height);
}
};
cow.set(new ChunkOutputWidget(sentinel.getId(), "md-image-preview-" + StringUtil.makeRandomId(8), RmdChunkOptions.create(), ChunkOutputWidget.EXPANDED, // can close
false, host, ChunkOutputSize.Bare));
ChunkOutputWidget outputWidget = cow.get();
outputWidget.setRootWidget(container);
outputWidget.hideSatellitePopup();
outputWidget.getElement().getStyle().setMarginTop(4, Unit.PX);
plw.set(new PinnedLineWidget(LINE_WIDGET_TYPE, display, outputWidget, position.getRow(), null, null));
}
Aggregations