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 }