1 module util.terminal;
2 
3 import std.stdio;
4 
5 import source.location;
6 
7 version (Windows) {
8 	import core.sys.windows.windows;
9 }
10 
11 void outputCaretDiagnostics(FullLocation loc, string fixHint) {
12 	uint offset = loc.getStartOffset();
13 	uint start = offset;
14 
15 	auto source = loc.getSource();
16 	auto content = source.getContent();
17 
18 	// This is unexpected end of input.
19 	if (start == content.length) {
20 		// Find first non white char.
21 		import std.ascii;
22 		while (start > 0 && isWhite(content[--start])) {}
23 	}
24 
25 	// XXX: We could probably use infos from source manager here.
26 	FindStart: while (start > 0) {
27 		switch (content[start]) {
28 			case '\n':
29 			case '\r':
30 				start++;
31 				break FindStart;
32 
33 			default:
34 				start--;
35 		}
36 	}
37 
38 	uint length = loc.length;
39 	uint end = offset + loc.length;
40 
41 	// This is unexpected end of input.
42 	if (end > content.length) {
43 		end = cast(uint) content.length;
44 	}
45 
46 	FindEnd: while (end < content.length) {
47 		switch (content[end]) {
48 			case '\n':
49 			case '\r':
50 				break FindEnd;
51 
52 			default:
53 				end++;
54 		}
55 	}
56 
57 	auto line = content[start .. end];
58 	uint index = offset - start;
59 
60 	// Multi line location
61 	if (index < line.length && index + length > line.length) {
62 		length = cast(uint) line.length - index;
63 	}
64 
65 	char[] underline;
66 	underline.length = index + length;
67 	foreach (i; 0 .. index) {
68 		underline[i] = (line[i] == '\t') ? '\t' : ' ';
69 	}
70 
71 	underline[index] = '^';
72 	foreach (i; index + 1 .. index + length) {
73 		underline[i] = '~';
74 	}
75 
76 	assert(index == loc.getStartColumn());
77 
78 	stderr.write(loc.isMixin() ? "mixin" : source.getFileName().toString(), ":",
79 	             loc.getStartLineNumber(), ":", index, ":");
80 
81 	stderr.writeColouredText(ConsoleColour.Red, " error: ");
82 	stderr.writeColouredText(ConsoleColour.White, fixHint, "\n");
83 
84 	stderr.writeln(line);
85 	stderr.writeColouredText(ConsoleColour.Green, underline, "\n");
86 
87 	if (loc.isMixin()) {
88 		outputCaretDiagnostics(source.getImportLocation(), "mixed in at");
89 	}
90 }
91 
92 /*
93  * ANSI colour codes per ECMA-48 (minus 30).
94  * e.g., Yellow = 3 + 30 = 33.
95  */
96 enum ConsoleColour {
97 	Black = 0,
98 	Red = 1,
99 	Green = 2,
100 	Yellow = 3,
101 	Blue = 4,
102 	Magenta = 5,
103 	Cyan = 6,
104 	White = 7,
105 }
106 
107 void writeColouredText(T...)(File pipe, ConsoleColour colour, T t) {
108 	bool coloursEnabled = true; // XXX: Fix me!
109 
110 	if (!coloursEnabled) {
111 		pipe.write(t);
112 	}
113 
114 	char[5] ansiSequence = [0x1b, '[', '3', '0', 'm'];
115 	ansiSequence[3] = cast(char) (colour + '0');
116 
117 	// XXX: use \e]11;?\a to get the color to restore
118 	pipe.write(ansiSequence, t, "\x1b[0m");
119 }