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 }