001 /* 002 * Copyright 2010-2016 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.text.StringsKt; 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 public static final boolean COLOR_ENABLED; 034 035 static { 036 boolean colorEnabled = false; 037 // TODO: investigate why ANSI escape codes on Windows only work in REPL for some reason 038 if (!SystemInfo.isWindows && !"false".equals(System.getProperty("kotlin.colors.enabled"))) { 039 try { 040 // AnsiConsole doesn't check isatty() for stderr (see https://github.com/fusesource/jansi/pull/35). 041 colorEnabled = CLibrary.isatty(CLibrary.STDERR_FILENO) != 0; 042 } 043 catch (UnsatisfiedLinkError e) { 044 colorEnabled = false; 045 } 046 } 047 COLOR_ENABLED = colorEnabled; 048 } 049 050 private static final String LINE_SEPARATOR = LineSeparator.getSystemLineSeparator().getSeparatorString(); 051 052 private static final Set<CompilerMessageSeverity> IMPORTANT_MESSAGE_SEVERITIES = EnumSet.of(EXCEPTION, ERROR, WARNING); 053 054 @Override 055 public String renderPreamble() { 056 return ""; 057 } 058 059 @Override 060 public String render( 061 @NotNull CompilerMessageSeverity severity, @NotNull String message, @NotNull CompilerMessageLocation location 062 ) { 063 StringBuilder result = new StringBuilder(); 064 065 int line = location.getLine(); 066 int column = location.getColumn(); 067 String lineContent = location.getLineContent(); 068 069 String path = getPath(location); 070 if (path != null) { 071 result.append(path); 072 result.append(":"); 073 if (line > 0) { 074 result.append(line).append(":"); 075 if (column > 0) { 076 result.append(column).append(":"); 077 } 078 } 079 result.append(" "); 080 } 081 082 if (COLOR_ENABLED) { 083 Ansi ansi = Ansi.ansi() 084 .bold() 085 .fg(severityColor(severity)) 086 .a(severity.name().toLowerCase()) 087 .a(": ") 088 .reset(); 089 090 if (IMPORTANT_MESSAGE_SEVERITIES.contains(severity)) { 091 ansi.bold(); 092 } 093 094 // Only make the first line of the message bold. Otherwise long overload ambiguity errors or exceptions are hard to read 095 String decapitalized = decapitalizeIfNeeded(message); 096 int firstNewline = decapitalized.indexOf(LINE_SEPARATOR); 097 if (firstNewline < 0) { 098 result.append(ansi.a(decapitalized).reset()); 099 } 100 else { 101 result.append(ansi.a(decapitalized.substring(0, firstNewline)).reset().a(decapitalized.substring(firstNewline))); 102 } 103 } 104 else { 105 result.append(severity.name().toLowerCase()); 106 result.append(": "); 107 result.append(decapitalizeIfNeeded(message)); 108 } 109 110 if (lineContent != null && 1 <= column && column <= lineContent.length() + 1) { 111 result.append(LINE_SEPARATOR); 112 result.append(lineContent); 113 result.append(LINE_SEPARATOR); 114 result.append(StringsKt.repeat(" ", column - 1)); 115 result.append("^"); 116 } 117 118 return result.toString(); 119 } 120 121 @NotNull 122 private static String decapitalizeIfNeeded(@NotNull String message) { 123 // TODO: invent something more clever 124 // An ad-hoc heuristic to prevent decapitalization of some names 125 if (message.startsWith("Java") || message.startsWith("Kotlin")) { 126 return message; 127 } 128 129 // For abbreviations and capitalized text 130 if (message.length() >= 2 && Character.isUpperCase(message.charAt(0)) && Character.isUpperCase(message.charAt(1))) { 131 return message; 132 } 133 134 return StringsKt.decapitalize(message); 135 } 136 137 @NotNull 138 private static Ansi.Color severityColor(@NotNull CompilerMessageSeverity severity) { 139 switch (severity) { 140 case EXCEPTION: 141 return Ansi.Color.RED; 142 case ERROR: 143 return Ansi.Color.RED; 144 case WARNING: 145 return Ansi.Color.YELLOW; 146 case INFO: 147 return Ansi.Color.BLUE; 148 case LOGGING: 149 return Ansi.Color.BLUE; 150 case OUTPUT: 151 return Ansi.Color.BLUE; 152 default: 153 throw new UnsupportedOperationException("Unknown severity: " + severity); 154 } 155 } 156 157 @Nullable 158 protected abstract String getPath(@NotNull CompilerMessageLocation location); 159 160 @Override 161 public String renderConclusion() { 162 return ""; 163 } 164 }