1 module dlangide.ui.outputpanel;
2 
3 import dlangui.all;
4 import dlangide.workspace.workspace;
5 import dlangide.workspace.project;
6 
7 import std.utf;
8 import std.regex;
9 import std.algorithm : startsWith;
10 
11 /// event listener to navigate by error/warning position
12 interface CompilerLogIssueClickHandler {
13 	bool onCompilerLogIssueClick(dstring filename, int line, int column);
14 }
15 
16 /// Log widget with parsing of compiler output
17 class CompilerLogWidget : LogWidget {
18 
19 	Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
20 
21 	auto ctr = ctRegex!(r"(.+)\((\d+)\): (Error|Warning|Deprecation): (.+)"d);
22 
23 	/// forward to super c'tor
24 	this(string ID) {
25 		super(ID);
26 	}
27 
28     /** 
29     Custom text color and style highlight (using text highlight) support.
30 
31     Return null if no syntax highlight required for line.
32     */
33     override protected CustomCharProps[] handleCustomLineHighlight(int line, dstring txt, ref CustomCharProps[] buf) {
34         auto match = matchFirst(txt, ctr);
35         uint defColor = textColor;
36         const uint filenameColor = 0x0000C0;
37         const uint errorColor = 0xFF0000;
38         const uint warningColor = 0x606000;
39         const uint deprecationColor = 0x802040;
40         uint flags = 0;
41         if(!match.empty) {
42             if (buf.length < txt.length)
43                 buf.length = txt.length;
44             CustomCharProps[] colors = buf[0..txt.length];
45             uint cl = filenameColor;
46             flags = TextFlag.Underline;
47             for (int i = 0; i < txt.length; i++) {
48                 dstring rest = txt[i..$];
49                 if (rest.startsWith(" Error"d)) {
50                     cl = errorColor;
51                     flags = 0;
52                 } else if (rest.startsWith(" Warning"d)) {
53                     cl = warningColor;
54                     flags = 0;
55                 } else if (rest.startsWith(" Deprecation"d)) {
56                     cl = deprecationColor;
57                     flags = 0;
58                 }
59                 colors[i].color = cl;
60                 colors[i].textFlags = flags;
61             }
62             return colors;
63         } else if (txt.startsWith("Building ")) {
64             CustomCharProps[] colors = new CustomCharProps[txt.length];
65             uint cl = defColor;
66             for (int i = 0; i < txt.length; i++) {
67                 dstring rest = txt[i..$];
68                 if (i == 9) {
69                     cl = filenameColor;
70                     flags = TextFlag.Underline;
71                 } else if (rest.startsWith(" configuration"d)) {
72                     cl = defColor;
73                     flags = 0;
74                 }
75                 colors[i].color = cl;
76                 colors[i].textFlags = flags;
77             }
78             return colors;
79         }
80         return null;
81     }
82 
83 	///
84 	override bool onMouseEvent(MouseEvent event) {
85 
86 		if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
87 			super.onMouseEvent(event);
88 
89 			auto logLine = this.content.line(this._caretPos.line);
90 
91 			//src\tetris.d(49): Error: found 'return' when expecting ';' following statement
92 
93 			auto match = matchFirst(logLine, ctr);
94 
95 			if(!match.empty) {
96 				if (compilerLogIssueClickHandler.assigned) {
97 					import std.conv:to;
98 					compilerLogIssueClickHandler(match[1], to!int(match[2]), 0);
99 				}
100 			}
101 
102 			return true;
103 		}
104 
105 		return super.onMouseEvent(event);
106 	}
107 }
108 
109 ///
110 class OutputPanel : DockWindow {
111 
112 	Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
113 
114 	protected CompilerLogWidget _logWidget;
115 
116     this(string id) {
117         super(id);
118         _caption.text = "Output"d;
119         dockAlignment = DockAlignment.Bottom;
120     }
121 
122     override protected Widget createBodyWidget() {
123 		_logWidget = new CompilerLogWidget("logwidget");
124         _logWidget.readOnly = true;
125         _logWidget.layoutHeight(FILL_PARENT).layoutHeight(FILL_PARENT);
126 		_logWidget.compilerLogIssueClickHandler = &onIssueClick;
127         return _logWidget;
128     }
129 
130     void appendText(string category, dstring msg) {
131         _logWidget.appendText(msg);
132     }
133 
134     void logLine(string category, dstring msg) {
135         appendText(category, msg ~ "\n");
136     }
137 
138     void logLine(dstring msg) {
139         logLine(null, msg);
140     }
141 
142     void logLine(string category, string msg) {
143         appendText(category, toUTF32(msg ~ "\n"));
144     }
145 
146     void logLine(string msg) {
147         logLine(null, msg);
148     }
149 
150 	void clear(string category = null) {
151 		_logWidget.text = ""d;
152 	}
153 
154 	private bool onIssueClick(dstring fn, int line, int column)
155 	{
156 		if (compilerLogIssueClickHandler.assigned) {
157 			compilerLogIssueClickHandler(fn, line, column);
158 		}
159 
160 		return true;
161 	}
162 }