1 module dlangide.ui.outputpanel;
2 
3 import dlangui;
4 import dlangide.workspace.workspace;
5 import dlangide.workspace.project;
6 import dlangide.ui.frame;
7 import dlangide.ui.terminal;
8 
9 import std.utf;
10 import std.regex;
11 import std.algorithm : startsWith;
12 import std.string;
13 
14 //static if (BACKEND_CONSOLE) {
15 //    enum ENABLE_INTERNAL_TERMINAL = true;
16 //} else {
17     version (Windows) {
18         enum ENABLE_INTERNAL_TERMINAL = false;
19     } else {
20         enum ENABLE_INTERNAL_TERMINAL = true;
21     }
22 //}
23 
24 enum ENABLE_INTERNAL_TERMINAL_TEST = false;
25 
26 /// event listener to navigate by error/warning position
27 interface CompilerLogIssueClickHandler {
28     bool onCompilerLogIssueClick(dstring filename, int line, int column);
29 }
30 
31 
32 /// Log widget with parsing of compiler output
33 class CompilerLogWidget : LogWidget {
34 
35     Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
36 
37     //auto ctr = ctRegex!(r"(.+)\((\d+)\): (Error|Warning|Deprecation): (.+)"d);
38     auto ctr = ctRegex!(r"(.+)\((\d+)(?:,(\d+))?\): (Error|Warning|Deprecation): (.+)"d);
39 
40     /// forward to super c'tor
41     this(string ID) {
42         super(ID);
43         //auto match2 = matchFirst("file.d(123,234): Error: bla bla"d, ctr2);
44         //if (!match2.empty) {
45         //    Log.d("found");
46         //}
47     }
48 
49     protected uint _filenameColor = 0x0000C0;
50     protected uint _errorColor = 0xFF0000;
51     protected uint _warningColor = 0x606000;
52     protected uint _deprecationColor = 0x802040;
53 
54     /// handle theme change: e.g. reload some themed resources
55     override void onThemeChanged() {
56         super.onThemeChanged();
57         _filenameColor = style.customColor("build_log_filename_color", 0x0000C0);
58         _errorColor = style.customColor("build_log_error_color", 0xFF0000);
59         _warningColor = style.customColor("build_log_warning_color", 0x606000);
60         _deprecationColor = style.customColor("build_log_deprecation_color", 0x802040);
61     }
62 
63     /** 
64     Custom text color and style highlight (using text highlight) support.
65 
66     Return null if no syntax highlight required for line.
67     */
68     override protected CustomCharProps[] handleCustomLineHighlight(int line, dstring txt, ref CustomCharProps[] buf) {
69         auto match = matchFirst(txt, ctr);
70         uint defColor = textColor;
71         uint flags = 0;
72         if(!match.empty) {
73             if (buf.length < txt.length)
74                 buf.length = txt.length;
75             CustomCharProps[] colors = buf[0..txt.length];
76             uint cl = _filenameColor;
77             flags = TextFlag.Underline;
78             for (int i = 0; i < txt.length; i++) {
79                 dstring rest = txt[i..$];
80                 if (rest.startsWith(" Error"d)) {
81                     cl = _errorColor;
82                     flags = 0;
83                 } else if (rest.startsWith(" Warning"d)) {
84                     cl = _warningColor;
85                     flags = 0;
86                 } else if (rest.startsWith(" Deprecation"d)) {
87                     cl = _deprecationColor;
88                     flags = 0;
89                 }
90                 colors[i].color = cl;
91                 colors[i].textFlags = flags;
92             }
93             return colors;
94         } else if (txt.startsWith("Building ")) {
95             CustomCharProps[] colors = new CustomCharProps[txt.length];
96             uint cl = defColor;
97             for (int i = 0; i < txt.length; i++) {
98                 dstring rest = txt[i..$];
99                 if (i == 9) {
100                     cl = _filenameColor;
101                     flags = TextFlag.Underline;
102                 } else if (rest.startsWith(" configuration"d)) {
103                     cl = defColor;
104                     flags = 0;
105                 }
106                 colors[i].color = cl;
107                 colors[i].textFlags = flags;
108             }
109             return colors;
110         } else if ((txt.startsWith("Performing ") && txt.indexOf(" build using ") > 0)
111                    || txt.startsWith("Upgrading project in ")
112                    ) {
113             CustomCharProps[] colors = new CustomCharProps[txt.length];
114             uint cl = defColor;
115             flags |= TextFlag.Underline;
116             for (int i = 0; i < txt.length; i++) {
117                 colors[i].color = cl;
118                 colors[i].textFlags = flags;
119             }
120             return colors;
121         } else if (txt.indexOf(": building configuration ") > 0) {
122             CustomCharProps[] colors = new CustomCharProps[txt.length];
123             uint cl = _filenameColor;
124             flags |= TextFlag.Underline;
125             for (int i = 0; i < txt.length; i++) {
126                 dstring rest = txt[i..$];
127                 if (rest.startsWith(": building configuration "d)) {
128                     //cl = defColor;
129                     flags &= ~TextFlag.Underline;
130                 }
131                 colors[i].color = cl;
132                 colors[i].textFlags = flags;
133             }
134             return colors;
135         }
136         return null;
137     }
138 
139     ///
140     override bool onMouseEvent(MouseEvent event) {
141 
142         if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
143             super.onMouseEvent(event);
144 
145             auto logLine = this.content.line(this._caretPos.line);
146 
147             //src\tetris.d(49): Error: found 'return' when expecting ';' following statement
148 
149             auto match = matchFirst(logLine, ctr);
150 
151             if(!match.empty) {
152                 if (compilerLogIssueClickHandler.assigned) {
153                     import std.conv:to;
154                     int row = to!int(match[2]) - 1;
155                     if (row < 0)
156                         row = 0;
157                     int col = 0;
158                     if (match[3]) {
159                         col = to!int(match[3]) - 1;
160                         if (col < 0)
161                             col = 0;
162                     }
163 
164                     compilerLogIssueClickHandler(match[1], row, col);
165                 }
166             }
167 
168             return true;
169         }
170 
171         return super.onMouseEvent(event);
172     }
173 }
174 
175 ///
176 class OutputPanel : DockWindow {
177 
178     Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
179 
180     protected CompilerLogWidget _logWidget;
181     protected TerminalWidget _terminalWidget;
182 
183     TabWidget _tabs;
184 
185     @property TabWidget getTabs() { return _tabs;}
186 
187     void activateLogTab() {
188         _tabs.selectTab("logwidget");
189     }
190 
191     void activateTerminalTab(bool clear = false) {
192         static if (ENABLE_INTERNAL_TERMINAL) {
193             _tabs.selectTab("TERMINAL");
194             if (clear)
195                 _terminalWidget.resetTerminal();
196         }
197     }
198 
199     this(string id) {
200         _showCloseButton = false;
201         dockAlignment = DockAlignment.Bottom;
202         super(id);
203     }
204 
205     /// terminal device for Console tab
206     @property string terminalDeviceName() {
207         static if (ENABLE_INTERNAL_TERMINAL) {
208             if (_terminalWidget)
209                 return _terminalWidget.deviceName;
210         }
211         return null;
212     }
213 
214     override protected Widget createBodyWidget() {
215         layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
216         _tabs = new TabWidget("OutputPanelTabs", Align.Bottom);
217         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
218         _tabs.setStyles(STYLE_DOCK_WINDOW, STYLE_TAB_DOWN_DARK, STYLE_TAB_DOWN_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT, STYLE_DOCK_HOST_BODY);
219         _tabs.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
220         _tabs.tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
221 
222         _logWidget = new CompilerLogWidget("logwidget");
223         _logWidget.readOnly = true;
224         _logWidget.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
225         _logWidget.compilerLogIssueClickHandler = &onIssueClick;
226         _logWidget.styleId = "EDIT_BOX_NO_FRAME";
227 
228         //_tabs.tabHost.styleId = STYLE_DOCK_WINDOW_BODY;
229         _tabs.addTab(_logWidget, "Compiler Log"d);
230         _tabs.selectTab("logwidget");
231 
232         static if (ENABLE_INTERNAL_TERMINAL) {
233             _terminalWidget = new TerminalWidget("TERMINAL");
234             _terminalWidget.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
235             _tabs.addTab(_terminalWidget, "Output"d);
236             _terminalWidget.write("Hello\nSecond line\nTest\n"d);
237         }
238         static if (ENABLE_INTERNAL_TERMINAL_TEST) {
239             _terminalWidget.write("Hello\nSecond line\nTest\n"d);
240             _terminalWidget.write("SomeString 123456789\rAwesomeString\n"d); // test \r
241             // testing tabs
242             _terminalWidget.write("id\tname\tdescription\n"d);
243             _terminalWidget.write("1\tFoo\tFoo line\n"d);
244             _terminalWidget.write("2\tBar\tBar line\n"d);
245             _terminalWidget.write("3\tFoobar\tFoo bar line\n"d);
246             _terminalWidget.write("\n\n\n"d);
247             // testing line wrapping
248             _terminalWidget.write("Testing very long line. Юникод. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n"c);
249             // testing cursor position changes
250             _terminalWidget.write("\x1b[4;4HCURSOR(4,4)\x1b[HHOME\x1b[B*A\x1b[B*B\x1b[5C\x1b[D***\x1b[A*UP\x1b[3B*DOWN"d);
251             //_terminalWidget.write("\x1b[Jerased down"d);
252             //_terminalWidget.write("\x1b[1Jerased up"d);
253             //_terminalWidget.write("\x1b[2Jerased screen"d);
254             //_terminalWidget.write("\x1b[Kerased eol"d);
255             //_terminalWidget.write("\x1b[1Kerased bol"d);
256             //_terminalWidget.write("\x1b[2Kerased line"d);
257             //_terminalWidget.write("Юникод Unicode"d);
258             _terminalWidget.write("\x1b[34;45m blue on magenta "d);
259             _terminalWidget.write("\x1b[31;46m red on cyan "d);
260             //_terminalWidget.write("\x1b[2Jerased screen"d);
261             //TerminalDevice term = new TerminalDevice();
262             //if (!term.create()) {
263             //    Log.e("Cannot create terminal device");
264             //}
265             _terminalWidget.write("\n\n\n\nDevice: "d ~ toUTF32(_terminalWidget.deviceName));
266             _terminalWidget.write("\x1b[0m\nnormal text\n"d);
267         }
268         return _tabs;
269     }
270 
271     override protected void initialize() {
272         
273         //styleId = STYLE_DOCK_WINDOW;
274         styleId = null;
275         _bodyWidget = createBodyWidget();
276         //_bodyWidget.styleId = STYLE_DOCK_WINDOW_BODY;
277         addChild(_bodyWidget);
278     }
279 
280     //TODO: Refactor OutputPanel to expose CompilerLogWidget
281 
282     void appendText(string category, dstring msg) {
283         _logWidget.appendText(msg);
284     }
285 
286     void logLine(string category, dstring msg) {
287         appendText(category, msg ~ "\n");
288     }
289 
290     void logLine(dstring msg) {
291         logLine(null, msg);
292     }
293 
294     void logLine(string category, string msg) {
295         appendText(category, toUTF32(msg ~ "\n"));
296     }
297 
298     void logLine(string msg) {
299         logLine(null, msg);
300     }
301 
302     void clear(string category = null) {
303         _logWidget.text = ""d;
304     }
305 
306     private bool onIssueClick(dstring fn, int line, int column)
307     {
308         if (compilerLogIssueClickHandler.assigned) {
309             compilerLogIssueClickHandler(fn, line, column);
310         }
311 
312         return true;
313     }
314 }