/*******************************************************************************
* 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.editor.orion.client.signature;
import elemental.dom.Element;
import elemental.dom.Node;
import elemental.events.EventListener;
import elemental.html.SpanElement;
import com.google.common.base.Optional;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.inject.Inject;
import org.eclipse.che.api.promises.client.Operation;
import org.eclipse.che.api.promises.client.OperationException;
import org.eclipse.che.api.promises.client.Promise;
import org.eclipse.che.ide.api.editor.signature.ParameterInfo;
import org.eclipse.che.ide.api.editor.signature.SignatureHelp;
import org.eclipse.che.ide.api.editor.signature.SignatureInfo;
import org.eclipse.che.ide.util.Pair;
import org.eclipse.che.ide.util.dom.Elements;
import java.util.ArrayList;
import java.util.List;
/**
* Widget for showing signature information
*
* @author Evgen Vidolob
*/
public class SignatureHelpView extends PopupPanel {
private final SignatureHelpResources resources;
private final Element rootElement;
private final Element signatures;
private final Element overloads;
private SignatureHelp signatureHelp;
private int activeSignature;
private List<Pair<Integer, Integer>> signatureViews;
private int x;
private int y;
@Inject
public SignatureHelpView(SignatureHelpResources resources) {
super(true);
this.resources = resources;
rootElement = Elements.createDivElement(resources.css().main(), resources.css().parameterHintsWidget());
Element wrapper = Elements.createDivElement(resources.css().wrapper());
rootElement.appendChild(wrapper);
Element buttons = Elements.createDivElement(resources.css().buttons());
wrapper.appendChild(buttons);
Element previous = Elements.createDivElement(resources.css().button(), resources.css().previous());
previous.appendChild((Node)resources.arrow().getSvg().getElement());
buttons.appendChild(previous);
previous.addEventListener(elemental.events.Event.CLICK, new EventListener() {
@Override
public void handleEvent(elemental.events.Event evt) {
previous();
}
}, true);
Element next = Elements.createDivElement(resources.css().button(), resources.css().next());
next.appendChild((Node)resources.arrow().getSvg().getElement());
next.addEventListener(elemental.events.Event.CLICK, new EventListener() {
@Override
public void handleEvent(elemental.events.Event evt) {
next();
}
}, true);
buttons.appendChild(next);
overloads = Elements.createDivElement(resources.css().overloads());
wrapper.appendChild(overloads);
signatures = Elements.createDivElement(resources.css().signatures());
wrapper.appendChild(signatures);
Widget widget = new ElementWidget((com.google.gwt.dom.client.Element)rootElement);
setWidget(widget);
}
private void previous() {
if (signatureViews.size() < 2) {
return;
}
activeSignature--;
if (activeSignature < 0) {
activeSignature = signatureViews.size() - 1;
}
this.select(activeSignature);
}
private void next() {
if (signatureViews.size() < 2) {
return;
}
activeSignature = (activeSignature + 1) % signatureViews.size();
select(activeSignature);
}
public void showSignature(Promise<Optional<SignatureHelp>> promise, final int x, final int y) {
this.x = x;
this.y = y;
promise.then(new Operation<Optional<SignatureHelp>>() {
@Override
public void apply(Optional<SignatureHelp> arg) throws OperationException {
if (arg.isPresent() && !arg.get().getSignatures().isEmpty()) {
activeSignature = 0;
signatureHelp = arg.get();
if (signatureHelp.getActiveSignature().isPresent()) {
activeSignature = signatureHelp.getActiveSignature().get();
}
show();
render();
select(activeSignature);
}
}
});
}
private void select(int position) {
Pair<Integer, Integer> signaturePosition = signatureViews.get(position);
if (signaturePosition == null) {
return;
}
signatures.getStyle().setHeight(signaturePosition.second + "px");
signatures.setScrollTop(signaturePosition.first);
String overloads = "" + (position + 1);
if (signatureViews.size() < 10) {
overloads += ("/" + signatureViews.size());
}
this.overloads.setInnerText(overloads);
setPopupPosition(x, y - getElement().getOffsetHeight());
}
private void render() {
if (signatureHelp.getSignatures().size() > 1) {
Elements.addClassName(resources.css().multiple(), rootElement);
overloads.getStyle().setDisplay("block");
} else {
Elements.removeClassName(resources.css().multiple(), rootElement);
overloads.getStyle().setDisplay("none");
}
signatures.setInnerHTML("");
signatureViews = new ArrayList<>();
int height = 0;
for (SignatureInfo signatureInfo : signatureHelp.getSignatures()) {
Element signatureElement = renderSignature(signatures, signatureInfo, signatureHelp.getActiveParameter());
renderDocumentation(signatureElement, signatureInfo, signatureHelp.getActiveParameter());
int signatureHeight = signatureElement.getOffsetHeight();
signatureViews.add(Pair.of(height, signatureHeight));
height += signatureHeight;
}
}
private void renderDocumentation(Element element, SignatureInfo signatureInfo, Optional<Integer> activeParameter) {
if (signatureInfo.getDocumentation().isPresent()) {
elemental.html.DivElement documentation = Elements.createDivElement(resources.css().documentation());
documentation.setTextContent(signatureInfo.getDocumentation().get());
element.appendChild(documentation);
}
if (signatureInfo.getParameters().isPresent() && activeParameter.isPresent() &&
signatureInfo.getParameters().get().size() > activeParameter.get()) {
ParameterInfo parameterInfo = signatureInfo.getParameters().get().get(activeParameter.get());
if (parameterInfo.getDocumentation().isPresent()) {
elemental.html.DivElement parameter = Elements.createDivElement(resources.css().documentationParameter());
SpanElement label = Elements.createSpanElement(resources.css().documentationParameter());
label.setTextContent(parameterInfo.getLabel());
SpanElement documentation = Elements.createSpanElement(resources.css().documentation());
documentation.setTextContent(parameterInfo.getDocumentation().get());
parameter.appendChild(label);
parameter.appendChild(documentation);
element.appendChild(parameter);
}
}
}
private Element renderSignature(Element signatures, SignatureInfo signatureInfo, Optional<Integer> activeParameter) {
Element signatureElement = Elements.createDivElement();
signatures.appendChild(signatureElement);
Element code = Elements.createDivElement();
signatureElement.appendChild(code);
boolean hasParameters = signatureInfo.getParameters().isPresent() && !signatureInfo.getParameters().get().isEmpty();
if (hasParameters) {
renderParameters(code, signatureInfo, activeParameter);
} else {
Node label = code.appendChild(Elements.createSpanElement());
label.setTextContent(signatureInfo.getLabel());
}
return signatureElement;
}
private void renderParameters(Element parent, SignatureInfo signatureInfo, Optional<Integer> activeParameter) {
int end = signatureInfo.getLabel().length();
int idx;
Element element;
for (int i = signatureInfo.getParameters().get().size() - 1; i >= 0; i--) {
ParameterInfo parameterInfo = signatureInfo.getParameters().get().get(i);
idx = signatureInfo.getLabel().lastIndexOf(parameterInfo.getLabel(), end);
int signatureLabelOffset = 0;
int signatureLabelEnd = 0;
if (idx >= 0) {
signatureLabelOffset = idx;
signatureLabelEnd = idx + parameterInfo.getLabel().length();
}
element = Elements.createSpanElement();
element.setTextContent(signatureInfo.getLabel().substring(signatureLabelEnd, end));
parent.insertBefore(element, parent.getFirstElementChild());
element = Elements.createSpanElement(resources.css().parameter());
if (activeParameter.isPresent() && i == activeParameter.get()) {
Elements.addClassName(resources.css().active(), element);
}
element.setTextContent(signatureInfo.getLabel().substring(signatureLabelOffset, signatureLabelEnd));
parent.insertBefore(element, parent.getFirstElementChild());
end = signatureLabelOffset;
}
element = Elements.createSpanElement();
element.setTextContent(signatureInfo.getLabel().substring(0, end));
parent.insertBefore(element, parent.getFirstElementChild());
}
/** The method used to hide popup with parameters when user press 'Escape' button. */
@Override
protected void onPreviewNativeEvent(Event.NativePreviewEvent event) {
super.onPreviewNativeEvent(event);
switch (event.getTypeInt()) {
case Event.ONKEYDOWN:
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ESCAPE) {
hide();
}
break;
}
}
}