/*******************************************************************************
* Copyright (c) 2012-2017 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.ide.console;
import com.google.common.base.Strings;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.Node;
import com.google.gwt.dom.client.NodeList;
import com.google.gwt.dom.client.PreElement;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.regexp.shared.MatchResult;
import com.google.gwt.regexp.shared.RegExp;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DockLayoutPanel;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import org.eclipse.che.ide.CoreLocalizationConstant;
import org.eclipse.che.ide.FontAwesome;
import org.eclipse.che.ide.machine.MachineResources;
import org.eclipse.che.ide.ui.Tooltip;
import org.eclipse.che.ide.util.Pair;
import org.vectomatic.dom.svg.ui.SVGImage;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.gwt.regexp.shared.RegExp.compile;
import static org.eclipse.che.ide.ui.menu.PositionController.HorizontalAlign.MIDDLE;
import static org.eclipse.che.ide.ui.menu.PositionController.VerticalAlign.BOTTOM;
/**
* View representation of output console.
*
* @author Artem Zatsarynnyi
* @author Vitaliy Guliy
*/
public class OutputConsoleViewImpl extends Composite implements OutputConsoleView, ScrollHandler {
private final List<Pair<RegExp, String>> output2Color = newArrayList(new Pair<>(compile("\\[\\s*(DOCKER)\\s*\\]"), "#4EABFF"),
new Pair<>(compile("\\[\\s*(ERROR)\\s*\\]"), "#FF2727"),
new Pair<>(compile("\\[\\s*(WARN)\\s*\\]"), "#F5A623"),
new Pair<>(compile("\\[\\s*(STDOUT)\\s*\\]"), "#8ED72B"),
new Pair<>(compile("\\[\\s*(STDERR)\\s*\\]"), "#FF4343"));
interface OutputConsoleViewUiBinder extends UiBinder<Widget, OutputConsoleViewImpl> {
}
private static final OutputConsoleViewUiBinder UI_BINDER = GWT.create(OutputConsoleViewUiBinder.class);
private ActionDelegate delegate;
@UiField
DockLayoutPanel consolePanel;
@UiField
FlowPanel commandPanel;
@UiField
FlowPanel previewPanel;
@UiField
Label commandTitle;
@UiField
Label commandLabel;
@UiField
ScrollPanel scrollPanel;
@UiField
FlowPanel consoleLines;
@UiField
Anchor previewUrlLabel;
@UiField
FlowPanel reRunProcessButton;
@UiField
FlowPanel stopProcessButton;
@UiField
FlowPanel clearOutputsButton;
@UiField
FlowPanel downloadOutputsButton;
@UiField
FlowPanel wrapTextButton;
@UiField
FlowPanel scrollToBottomButton;
/** If true - next printed line should replace the previous one. */
private boolean carriageReturn;
/** Follow the output. Scroll to the bottom automatically when <b>true</b>. */
private boolean followOutput = true;
/** Scroll to the bottom immediately when view become visible. */
private boolean followScheduled = false;
@Inject
public OutputConsoleViewImpl(MachineResources resources,
CoreLocalizationConstant localization) {
initWidget(UI_BINDER.createAndBindUi(this));
reRunProcessButton.add(new SVGImage(resources.reRunIcon()));
stopProcessButton.add(new SVGImage(resources.stopIcon()));
clearOutputsButton.add(new SVGImage(resources.clearOutputsIcon()));
downloadOutputsButton.getElement().setInnerHTML(FontAwesome.DOWNLOAD);
wrapTextButton.add(new SVGImage(resources.lineWrapIcon()));
scrollToBottomButton.add(new SVGImage(resources.scrollToBottomIcon()));
scrollPanel.addDomHandler(this, ScrollEvent.getType());
reRunProcessButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!reRunProcessButton.getElement().hasAttribute("disabled") && delegate != null) {
delegate.reRunProcessButtonClicked();
}
}
}, ClickEvent.getType());
stopProcessButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!stopProcessButton.getElement().hasAttribute("disabled") && delegate != null) {
delegate.stopProcessButtonClicked();
}
}
}, ClickEvent.getType());
clearOutputsButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!clearOutputsButton.getElement().hasAttribute("disabled") && delegate != null) {
delegate.clearOutputsButtonClicked();
}
}
}, ClickEvent.getType());
downloadOutputsButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (delegate != null) {
delegate.downloadOutputsButtonClicked();
}
}
}, ClickEvent.getType());
wrapTextButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent clickEvent) {
if (!wrapTextButton.getElement().hasAttribute("disabled") && delegate != null) {
delegate.wrapTextButtonClicked();
}
}
}, ClickEvent.getType());
scrollToBottomButton.addDomHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
if (!scrollToBottomButton.getElement().hasAttribute("disabled") && delegate != null) {
delegate.scrollToBottomButtonClicked();
}
}
}, ClickEvent.getType());
Tooltip.create((elemental.dom.Element)reRunProcessButton.getElement(),
BOTTOM,
MIDDLE,
localization.consolesReRunButtonTooltip());
Tooltip.create((elemental.dom.Element)stopProcessButton.getElement(),
BOTTOM,
MIDDLE,
localization.consolesStopButtonTooltip());
Tooltip.create((elemental.dom.Element)clearOutputsButton.getElement(),
BOTTOM,
MIDDLE,
localization.consolesClearOutputsButtonTooltip());
Tooltip.create((elemental.dom.Element)wrapTextButton.getElement(),
BOTTOM,
MIDDLE,
localization.consolesWrapTextButtonTooltip());
Tooltip.create((elemental.dom.Element)scrollToBottomButton.getElement(),
BOTTOM,
MIDDLE,
localization.consolesAutoScrollButtonTooltip());
}
@Override
public void setDelegate(ActionDelegate delegate) {
this.delegate = delegate;
}
@Override
public void hideCommand() {
consolePanel.setWidgetHidden(commandPanel, true);
}
@Override
public void hidePreview() {
consolePanel.setWidgetHidden(previewPanel, true);
}
@Override
public void wrapText(boolean wrap) {
if (wrap) {
consoleLines.getElement().setAttribute("wrap", "");
} else {
consoleLines.getElement().removeAttribute("wrap");
}
}
@Override
public void enableAutoScroll(boolean enable) {
followOutput = enable;
followOutput();
}
@Override
public void clearConsole() {
consoleLines.getElement().setInnerHTML("");
}
@Override
public void toggleWrapTextButton(boolean toggle) {
if (toggle) {
wrapTextButton.getElement().setAttribute("toggled", "");
} else {
wrapTextButton.getElement().removeAttribute("toggled");
}
}
@Override
public void toggleScrollToEndButton(boolean toggle) {
if (toggle) {
scrollToBottomButton.getElement().setAttribute("toggled", "");
} else {
scrollToBottomButton.getElement().removeAttribute("toggled");
}
}
@Override
public void setReRunButtonVisible(boolean visible) {
reRunProcessButton.setVisible(visible);
}
@Override
public void setStopButtonVisible(boolean visible) {
stopProcessButton.setVisible(visible);
}
@Override
public void enableStopButton(boolean enable) {
if (enable) {
stopProcessButton.getElement().removeAttribute("disabled");
} else {
stopProcessButton.getElement().setAttribute("disabled", "");
}
}
@Override
public void showCommandLine(String commandLine) {
commandLabel.setText(commandLine);
Tooltip.create((elemental.dom.Element)commandLabel.getElement(),
BOTTOM,
MIDDLE,
commandLine);
}
@Override
public void showPreviewUrl(String previewUrl) {
if (Strings.isNullOrEmpty(previewUrl)) {
hidePreview();
} else {
previewUrlLabel.setText(previewUrl);
previewUrlLabel.setHref(previewUrl);
Tooltip.create((elemental.dom.Element)previewUrlLabel.getElement(),
BOTTOM,
MIDDLE,
previewUrl);
}
}
@Override
public void print(String text, boolean carriageReturn) {
print(text, carriageReturn, null);
}
@Override
public void print(final String text, boolean carriageReturn, String color) {
if (this.carriageReturn) {
Node lastChild = consoleLines.getElement().getLastChild();
if (lastChild != null) {
lastChild.removeFromParent();
}
}
this.carriageReturn = carriageReturn;
final SafeHtml colorOutput = new SafeHtml() {
@Override
public String asString() {
if (Strings.isNullOrEmpty(text)) {
return " ";
}
for (final Pair<RegExp, String> pair : output2Color) {
final MatchResult matcher = pair.first.exec(text);
if (matcher != null) {
return text.replaceAll(matcher.getGroup(1),
"<span style=\"color: " + pair.second + "\">" + matcher.getGroup(1) + "</span>");
}
}
return text;
}
};
PreElement pre = DOM.createElement("pre").cast();
pre.setInnerSafeHtml(colorOutput);
if (color != null) {
pre.getStyle().setColor(color);
}
consoleLines.getElement().appendChild(pre);
followOutput();
}
@Override
public String getText() {
String text = "";
NodeList<Node> nodes = consoleLines.getElement().getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.getItem(i);
Element element = node.cast();
text += element.getInnerText() + "\r\n";
}
return text;
}
@Override
public void onScroll(ScrollEvent event) {
// Do nothing if content height less scroll area height
if (scrollPanel.getElement().getScrollHeight() < scrollPanel.getElement().getOffsetHeight()) {
followOutput = true;
if (delegate != null) {
delegate.onOutputScrolled(followOutput);
}
return;
}
// Follow output if scroll area is scrolled to the end
if (scrollPanel.getElement().getScrollTop() + scrollPanel.getElement().getOffsetHeight() >
scrollPanel.getElement().getScrollHeight()) {
followOutput = true;
} else {
followOutput = false;
}
if (delegate != null) {
delegate.onOutputScrolled(followOutput);
}
}
/**
* Scrolls to the bottom if following the output is enabled.
*/
private void followOutput() {
if (!followOutput) {
return;
}
/** Scroll bottom immediately if view is visible */
if (scrollPanel.getElement().getOffsetParent() != null) {
scrollPanel.scrollToBottom();
scrollPanel.scrollToLeft();
return;
}
/** Otherwise, check the visibility periodically and scroll the view when it's visible */
if (!followScheduled) {
followScheduled = true;
Scheduler.get().scheduleFixedPeriod(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
if (!followOutput) {
followScheduled = false;
return false;
}
if (scrollPanel.getElement().getOffsetParent() != null) {
scrollPanel.scrollToBottom();
scrollPanel.scrollToLeft();
followScheduled = false;
return false;
}
return true;
}
}, 500);
}
}
}