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 
8 import std.utf;
9 import std.regex;
10 import std.algorithm : startsWith;
11 import std.string;
12 
13 /// event listener to navigate by error/warning position
14 interface CompilerLogIssueClickHandler {
15 	bool onCompilerLogIssueClick(dstring filename, int line, int column);
16 }
17 
18 
19 /// Log widget with parsing of compiler output
20 class CompilerLogWidget : LogWidget {
21 
22 	Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
23 
24 	auto ctr = ctRegex!(r"(.+)\((\d+)\): (Error|Warning|Deprecation): (.+)"d);
25 
26 	/// forward to super c'tor
27 	this(string ID) {
28 		super(ID);
29 	}
30 
31     protected uint _filenameColor = 0x0000C0;
32     protected uint _errorColor = 0xFF0000;
33     protected uint _warningColor = 0x606000;
34     protected uint _deprecationColor = 0x802040;
35 
36     /// handle theme change: e.g. reload some themed resources
37     override void onThemeChanged() {
38         _filenameColor = style.customColor("build_log_filename_color", 0x0000C0);
39         _errorColor = style.customColor("build_log_error_color", 0xFF0000);
40         _warningColor = style.customColor("build_log_warning_color", 0x606000);
41         _deprecationColor = style.customColor("build_log_deprecation_color", 0x802040);
42         super.onThemeChanged();
43     }
44 
45     /** 
46     Custom text color and style highlight (using text highlight) support.
47 
48     Return null if no syntax highlight required for line.
49     */
50     override protected CustomCharProps[] handleCustomLineHighlight(int line, dstring txt, ref CustomCharProps[] buf) {
51         auto match = matchFirst(txt, ctr);
52         uint defColor = textColor;
53         uint flags = 0;
54         if(!match.empty) {
55             if (buf.length < txt.length)
56                 buf.length = txt.length;
57             CustomCharProps[] colors = buf[0..txt.length];
58             uint cl = _filenameColor;
59             flags = TextFlag.Underline;
60             for (int i = 0; i < txt.length; i++) {
61                 dstring rest = txt[i..$];
62                 if (rest.startsWith(" Error"d)) {
63                     cl = _errorColor;
64                     flags = 0;
65                 } else if (rest.startsWith(" Warning"d)) {
66                     cl = _warningColor;
67                     flags = 0;
68                 } else if (rest.startsWith(" Deprecation"d)) {
69                     cl = _deprecationColor;
70                     flags = 0;
71                 }
72                 colors[i].color = cl;
73                 colors[i].textFlags = flags;
74             }
75             return colors;
76         } else if (txt.startsWith("Building ")) {
77             CustomCharProps[] colors = new CustomCharProps[txt.length];
78             uint cl = defColor;
79             for (int i = 0; i < txt.length; i++) {
80                 dstring rest = txt[i..$];
81                 if (i == 9) {
82                     cl = _filenameColor;
83                     flags = TextFlag.Underline;
84                 } else if (rest.startsWith(" configuration"d)) {
85                     cl = defColor;
86                     flags = 0;
87                 }
88                 colors[i].color = cl;
89                 colors[i].textFlags = flags;
90             }
91             return colors;
92         }
93         return null;
94     }
95 
96 	///
97 	override bool onMouseEvent(MouseEvent event) {
98 
99 		if (event.action == MouseAction.ButtonDown && event.button == MouseButton.Left) {
100 			super.onMouseEvent(event);
101 
102 			auto logLine = this.content.line(this._caretPos.line);
103 
104 			//src\tetris.d(49): Error: found 'return' when expecting ';' following statement
105 
106 			auto match = matchFirst(logLine, ctr);
107 
108 			if(!match.empty) {
109 				if (compilerLogIssueClickHandler.assigned) {
110 					import std.conv:to;
111 					compilerLogIssueClickHandler(match[1], to!int(match[2]), 0);
112 				}
113 			}
114 
115 			return true;
116 		}
117 
118 		return super.onMouseEvent(event);
119 	}
120 }
121 
122 ///
123 class OutputPanel : DockWindow {
124 
125 	Signal!CompilerLogIssueClickHandler compilerLogIssueClickHandler;
126 
127 	protected CompilerLogWidget _logWidget;
128 
129     TabWidget _tabs;
130 
131 	@property TabWidget getTabs() { return _tabs;}
132 
133     this(string id) {
134 		_showCloseButton = false;
135 		dockAlignment = DockAlignment.Bottom;
136         super(id);
137 	}
138 
139     override protected Widget createBodyWidget() {
140         layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
141         _tabs = new TabWidget("OutputPanelTabs", Align.Bottom);
142         //_tabs.setStyles(STYLE_DOCK_HOST_BODY, STYLE_TAB_UP_DARK, STYLE_TAB_UP_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
143         _tabs.setStyles(null, STYLE_TAB_DOWN_DARK, STYLE_TAB_DOWN_BUTTON_DARK, STYLE_TAB_UP_BUTTON_DARK_TEXT);
144         _tabs.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
145         _tabs.tabHost.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
146 
147 		_logWidget = new CompilerLogWidget("logwidget");
148         _logWidget.readOnly = true;
149         _logWidget.layoutWidth(FILL_PARENT).layoutHeight(FILL_PARENT);
150 		_logWidget.compilerLogIssueClickHandler = &onIssueClick;
151 
152         //_tabs.tabHost.styleId = STYLE_DOCK_WINDOW_BODY;
153         _tabs.addTab(_logWidget, "Compiler Log"d);
154 		_tabs.selectTab("logwidget");
155 
156         return _tabs;
157     }
158 
159 	override protected void init() {
160 		
161 		//styleId = STYLE_DOCK_WINDOW;
162         styleId = null;
163 		_bodyWidget = createBodyWidget();
164 		//_bodyWidget.styleId = STYLE_DOCK_WINDOW_BODY;
165 		addChild(_bodyWidget);
166 	}
167 
168     //TODO: Refactor OutputPanel to expose CompilerLogWidget
169 
170     void appendText(string category, dstring msg) {
171         _logWidget.appendText(msg);
172     }
173 
174     void logLine(string category, dstring msg) {
175         appendText(category, msg ~ "\n");
176     }
177 
178     void logLine(dstring msg) {
179         logLine(null, msg);
180     }
181 
182     void logLine(string category, string msg) {
183         appendText(category, toUTF32(msg ~ "\n"));
184     }
185 
186     void logLine(string msg) {
187         logLine(null, msg);
188     }
189 
190 	void clear(string category = null) {
191 		_logWidget.text = ""d;
192 	}
193 
194 	private bool onIssueClick(dstring fn, int line, int column)
195 	{
196 		if (compilerLogIssueClickHandler.assigned) {
197 			compilerLogIssueClickHandler(fn, line, column);
198 		}
199 
200 		return true;
201 	}
202 }