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 }