/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.idea.highlighter;
import kotlin.Unit;
import kotlin.jvm.functions.Function1;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
import org.jetbrains.kotlin.diagnostics.rendering.DiagnosticParameterRenderer;
import org.jetbrains.kotlin.diagnostics.rendering.RenderingContext;
import org.jetbrains.kotlin.diagnostics.rendering.SmartDescriptorRenderer;
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer;
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.TableRenderer.DescriptorRow;
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.TableRenderer.FunctionArgumentsRow;
import org.jetbrains.kotlin.diagnostics.rendering.TabledDescriptorRenderer.TableRenderer.TableRow;
import org.jetbrains.kotlin.idea.highlighter.renderersUtil.RenderersUtilKt;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier;
import org.jetbrains.kotlin.renderer.DescriptorRendererOptions;
import org.jetbrains.kotlin.renderer.RenderingFormat;
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPosition;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
import static org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.RECEIVER_POSITION;
import static org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.VALUE_PARAMETER_POSITION;
public class HtmlTabledDescriptorRenderer extends TabledDescriptorRenderer {
@NotNull
@Override
public DiagnosticParameterRenderer<KotlinType> getTypeRenderer() {
return IdeRenderers.HTML_RENDER_TYPE;
}
@Override
protected void renderText(TextRenderer textRenderer, StringBuilder result) {
for (TextRenderer.TextElement element : textRenderer.elements) {
renderText(result, element.type, element.text);
}
}
private static void renderText(StringBuilder result, TextElementType elementType, String text) {
if (elementType == TextElementType.DEFAULT) {
result.append(text);
}
else if (elementType == TextElementType.ERROR) {
result.append(RenderersUtilKt.renderError(text));
}
else if (elementType == TextElementType.STRONG) {
result.append(RenderersUtilKt.renderStrong(text));
}
}
private static int countColumnNumber(TableRenderer table) {
int argumentsNumber = 0;
for (TableRow row : table.rows) {
if (row instanceof DescriptorRow) {
int valueParametersNumber = ((DescriptorRow) row).descriptor.getValueParameters().size();
if (valueParametersNumber > argumentsNumber) {
argumentsNumber = valueParametersNumber;
}
}
else if (row instanceof FunctionArgumentsRow) {
int argumentTypesNumber = ((FunctionArgumentsRow) row).argumentTypes.size();
if (argumentTypesNumber > argumentsNumber) {
argumentsNumber = argumentTypesNumber;
}
}
}
//magical number 6:
// <td> white-space </td> <td> receiver: ___ </td> <td> arguments: </td> <td> ( </td> arguments <td> ) </td> <td> : return_type </td>
return argumentsNumber + 6;
}
@Override
protected void renderTable(TableRenderer table, StringBuilder result) {
if (table.rows.isEmpty()) return;
RenderingContext context = computeRenderingContext(table);
int rowsNumber = countColumnNumber(table);
result.append("<table>");
for (TableRow row : table.rows) {
result.append("<tr>");
if (row instanceof TextRenderer) {
StringBuilder rowText = new StringBuilder();
renderText((TextRenderer) row, rowText);
tdColspan(result, rowText.toString(), rowsNumber);
}
if (row instanceof DescriptorRow) {
tdSpace(result);
tdRightBoldColspan(result, 2, DESCRIPTOR_IN_TABLE.render(((DescriptorRow) row).descriptor, context));
}
if (row instanceof FunctionArgumentsRow) {
FunctionArgumentsRow functionArgumentsRow = (FunctionArgumentsRow) row;
renderFunctionArguments(functionArgumentsRow.receiverType, functionArgumentsRow.argumentTypes, functionArgumentsRow.isErrorPosition, result, context);
}
result.append("</tr>");
}
result.append("</table>");
}
private void renderFunctionArguments(
@Nullable KotlinType receiverType,
@NotNull List<KotlinType> argumentTypes,
Predicate<ConstraintPosition> isErrorPosition,
StringBuilder result,
@NotNull RenderingContext context
) {
boolean hasReceiver = receiverType != null;
tdSpace(result);
String receiver = "";
if (hasReceiver) {
boolean error = false;
if (isErrorPosition.test(RECEIVER_POSITION.position())) {
error = true;
}
receiver = "receiver: " + RenderersUtilKt.renderStrong(getTypeRenderer().render(receiverType, context), error);
}
td(result, receiver);
td(result, hasReceiver ? "arguments: " : "");
if (argumentTypes.isEmpty()) {
tdBold(result, "( )");
return;
}
td(result, RenderersUtilKt.renderStrong("("));
int i = 0;
for (Iterator<KotlinType> iterator = argumentTypes.iterator(); iterator.hasNext(); ) {
KotlinType argumentType = iterator.next();
boolean error = false;
if (isErrorPosition.test(VALUE_PARAMETER_POSITION.position(i))) {
error = true;
}
String renderedArgument = argumentType == null ? "unknown" : getTypeRenderer().render(argumentType, context);
tdRight(result, RenderersUtilKt.renderStrong(renderedArgument, error) + (iterator.hasNext() ? RenderersUtilKt.renderStrong(",") : ""));
i++;
}
td(result, RenderersUtilKt.renderStrong(")"));
}
public static HtmlTabledDescriptorRenderer create() {
return new HtmlTabledDescriptorRenderer();
}
protected HtmlTabledDescriptorRenderer() {
super();
}
private static final DescriptorRenderer.ValueParametersHandler VALUE_PARAMETERS_HANDLER = new DescriptorRenderer.ValueParametersHandler() {
@Override
public void appendBeforeValueParameter(
@NotNull ValueParameterDescriptor parameter, int parameterIndex, int parameterCount, @NotNull StringBuilder builder
) {
builder.append("<td align=\"right\" style=\"white-space:nowrap;font-weight:bold;\">");
}
@Override
public void appendAfterValueParameter(
@NotNull ValueParameterDescriptor parameter, int parameterIndex, int parameterCount, @NotNull StringBuilder builder
) {
boolean last = parameterIndex == parameterCount - 1;
if (!last) {
builder.append(",");
}
builder.append("</td>");
}
@Override
public void appendBeforeValueParameters(int parameterCount, @NotNull StringBuilder builder) {
builder.append("</td>");
if (parameterCount == 0) {
tdBold(builder, "( )");
}
else {
tdBold(builder, "(");
}
}
@Override
public void appendAfterValueParameters(int parameterCount, @NotNull StringBuilder builder) {
if (parameterCount != 0) {
tdBold(builder, ")");
}
builder.append("<td style=\"white-space:nowrap;font-weight:bold;\">");
}
};
private static final DiagnosticParameterRenderer<DeclarationDescriptor>
DESCRIPTOR_IN_TABLE = new SmartDescriptorRenderer(DescriptorRenderer.Companion.withOptions(
new Function1<DescriptorRendererOptions, Unit>() {
@Override
public Unit invoke(DescriptorRendererOptions options) {
options.setWithDefinedIn(false);
options.setModifiers(Collections.<DescriptorRendererModifier>emptySet());
options.setValueParametersHandler(VALUE_PARAMETERS_HANDLER);
options.setTextFormat(RenderingFormat.HTML);
return Unit.INSTANCE;
}
}));
private static void td(StringBuilder builder, String text) {
builder.append("<td style=\"white-space:nowrap;\">").append(text).append("</td>");
}
private static void tdSpace(StringBuilder builder) {
builder.append("<td width=\"10%\"></td>");
}
private static void tdColspan(StringBuilder builder, String text, int colspan) {
builder.append("<td colspan=\"").append(colspan).append("\" style=\"white-space:nowrap;\">").append(text).append("</td>");
}
private static void tdBold(StringBuilder builder, String text) {
builder.append("<td style=\"white-space:nowrap;font-weight:bold;\">").append(text).append("</td>");
}
private static void tdRight(StringBuilder builder, String text) {
builder.append("<td align=\"right\" style=\"white-space:nowrap;\">").append(text).append("</td>");
}
private static void tdRightBoldColspan(StringBuilder builder, int colspan, String text) {
builder.append("<td align=\"right\" colspan=\"").append(colspan).append("\" style=\"white-space:nowrap;font-weight:bold;\">").append(text).append("</td>");
}
public static String tableForTypes(String message, String firstDescription, TextElementType firstType, String secondDescription, TextElementType secondType) {
StringBuilder result = new StringBuilder();
result.append("<html>").append(message);
result.append("<table><tr><td>").append(firstDescription).append("</td><td>");
renderText(result, firstType, "{0}");
result.append("</td></tr><tr><td>").append(secondDescription).append("</td><td>");
renderText(result, secondType, "{1}");
result.append("</td></tr></table></html>");
return result.toString();
}
}