001 /*
002 * Copyright 2010-2015 JetBrains s.r.o.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017 package org.jetbrains.kotlin.cli.common.messages;
018
019 import com.intellij.openapi.util.SystemInfo;
020 import com.intellij.util.LineSeparator;
021 import kotlin.KotlinPackage;
022 import org.fusesource.jansi.Ansi;
023 import org.fusesource.jansi.internal.CLibrary;
024 import org.jetbrains.annotations.NotNull;
025 import org.jetbrains.annotations.Nullable;
026
027 import java.util.EnumSet;
028 import java.util.Set;
029
030 import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.*;
031
032 public abstract class PlainTextMessageRenderer implements MessageRenderer {
033 // AnsiConsole doesn't check isatty() for stderr (see https://github.com/fusesource/jansi/pull/35).
034 // TODO: investigate why ANSI escape codes on Windows only work in REPL for some reason
035 private static final boolean COLOR_ENABLED =
036 !SystemInfo.isWindows &&
037 CLibrary.isatty(CLibrary.STDERR_FILENO) != 0;
038
039 private static final String LINE_SEPARATOR = LineSeparator.getSystemLineSeparator().getSeparatorString();
040
041 private static final Set<CompilerMessageSeverity> IMPORTANT_MESSAGE_SEVERITIES = EnumSet.of(EXCEPTION, ERROR, WARNING);
042
043 @Override
044 public String renderPreamble() {
045 return "";
046 }
047
048 @Override
049 public String render(
050 @NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location
051 ) {
052 StringBuilder result = new StringBuilder();
053
054 int line = location.getLine();
055 int column = location.getColumn();
056 String lineContent = location.getLineContent();
057
058 String path = getPath(location);
059 if (path != null) {
060 result.append(path);
061 result.append(":");
062 if (line > 0) {
063 result.append(line).append(":");
064 if (column > 0) {
065 result.append(column).append(":");
066 }
067 }
068 result.append(" ");
069 }
070
071 if (COLOR_ENABLED) {
072 Ansi ansi = Ansi.ansi()
073 .bold()
074 .fg(severityColor(severity))
075 .a(severity.name().toLowerCase())
076 .a(": ")
077 .reset();
078
079 if (IMPORTANT_MESSAGE_SEVERITIES.contains(severity)) {
080 ansi.bold();
081 }
082
083 // Only make the first line of the message bold. Otherwise long overload ambiguity errors or exceptions are hard to read
084 String decapitalized = decapitalizeIfNeeded(message);
085 int firstNewline = decapitalized.indexOf(LINE_SEPARATOR);
086 if (firstNewline < 0) {
087 result.append(ansi.a(decapitalized).reset());
088 }
089 else {
090 result.append(ansi.a(decapitalized.substring(0, firstNewline)).reset().a(decapitalized.substring(firstNewline)));
091 }
092 }
093 else {
094 result.append(severity.name().toLowerCase());
095 result.append(": ");
096 result.append(decapitalizeIfNeeded(message));
097 }
098
099 if (lineContent != null && 1 <= column && column <= lineContent.length() + 1) {
100 result.append(LINE_SEPARATOR);
101 result.append(lineContent);
102 result.append(LINE_SEPARATOR);
103 result.append(KotlinPackage.repeat(" ", column - 1));
104 result.append("^");
105 }
106
107 return result.toString();
108 }
109
110 @NotNull
111 private static String decapitalizeIfNeeded(@NotNull String message) {
112 // TODO: invent something more clever
113 // An ad-hoc heuristic to prevent decapitalization of some names
114 if (message.startsWith("Java") || message.startsWith("Kotlin")) {
115 return message;
116 }
117
118 // For abbreviations and capitalized text
119 if (message.length() >= 2 && Character.isUpperCase(message.charAt(0)) && Character.isUpperCase(message.charAt(1))) {
120 return message;
121 }
122
123 return KotlinPackage.decapitalize(message);
124 }
125
126 @NotNull
127 private static Ansi.Color severityColor(@NotNull CompilerMessageSeverity severity) {
128 switch (severity) {
129 case EXCEPTION:
130 return Ansi.Color.RED;
131 case ERROR:
132 return Ansi.Color.RED;
133 case WARNING:
134 return Ansi.Color.YELLOW;
135 case INFO:
136 return Ansi.Color.BLUE;
137 case LOGGING:
138 return Ansi.Color.BLUE;
139 case OUTPUT:
140 return Ansi.Color.BLUE;
141 default:
142 throw new UnsupportedOperationException("Unknown severity: " + severity);
143 }
144 }
145
146 @Nullable
147 protected abstract String getPath(@NotNull CompilerMessageLocation location);
148
149 @Override
150 public String renderConclusion() {
151 return "";
152 }
153 }