Update Scintilla to 4.0.4
[TortoiseGit.git] / ext / scintilla / lexers / LexErrorList.cxx
blob84f6adf285e93b746642961a53a33151ae0e210b
1 // Scintilla source code edit control
2 /** @file LexErrorList.cxx
3 ** Lexer for error lists. Used for the output pane in SciTE.
4 **/
5 // Copyright 1998-2001 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdio.h>
11 #include <stdarg.h>
12 #include <assert.h>
13 #include <ctype.h>
15 #include "ILexer.h"
16 #include "Scintilla.h"
17 #include "SciLexer.h"
19 #include "WordList.h"
20 #include "LexAccessor.h"
21 #include "Accessor.h"
22 #include "StyleContext.h"
23 #include "CharacterSet.h"
24 #include "LexerModule.h"
26 using namespace Scintilla;
28 static bool strstart(const char *haystack, const char *needle) {
29 return strncmp(haystack, needle, strlen(needle)) == 0;
32 static bool Is0To9(char ch) {
33 return (ch >= '0') && (ch <= '9');
36 static bool Is1To9(char ch) {
37 return (ch >= '1') && (ch <= '9');
40 static bool IsAlphabetic(int ch) {
41 return IsASCII(ch) && isalpha(ch);
44 static inline bool AtEOL(Accessor &styler, Sci_PositionU i) {
45 return (styler[i] == '\n') ||
46 ((styler[i] == '\r') && (styler.SafeGetCharAt(i + 1) != '\n'));
49 static int RecogniseErrorListLine(const char *lineBuffer, Sci_PositionU lengthLine, Sci_Position &startValue) {
50 if (lineBuffer[0] == '>') {
51 // Command or return status
52 return SCE_ERR_CMD;
53 } else if (lineBuffer[0] == '<') {
54 // Diff removal.
55 return SCE_ERR_DIFF_DELETION;
56 } else if (lineBuffer[0] == '!') {
57 return SCE_ERR_DIFF_CHANGED;
58 } else if (lineBuffer[0] == '+') {
59 if (strstart(lineBuffer, "+++ ")) {
60 return SCE_ERR_DIFF_MESSAGE;
61 } else {
62 return SCE_ERR_DIFF_ADDITION;
64 } else if (lineBuffer[0] == '-') {
65 if (strstart(lineBuffer, "--- ")) {
66 return SCE_ERR_DIFF_MESSAGE;
67 } else {
68 return SCE_ERR_DIFF_DELETION;
70 } else if (strstart(lineBuffer, "cf90-")) {
71 // Absoft Pro Fortran 90/95 v8.2 error and/or warning message
72 return SCE_ERR_ABSF;
73 } else if (strstart(lineBuffer, "fortcom:")) {
74 // Intel Fortran Compiler v8.0 error/warning message
75 return SCE_ERR_IFORT;
76 } else if (strstr(lineBuffer, "File \"") && strstr(lineBuffer, ", line ")) {
77 return SCE_ERR_PYTHON;
78 } else if (strstr(lineBuffer, " in ") && strstr(lineBuffer, " on line ")) {
79 return SCE_ERR_PHP;
80 } else if ((strstart(lineBuffer, "Error ") ||
81 strstart(lineBuffer, "Warning ")) &&
82 strstr(lineBuffer, " at (") &&
83 strstr(lineBuffer, ") : ") &&
84 (strstr(lineBuffer, " at (") < strstr(lineBuffer, ") : "))) {
85 // Intel Fortran Compiler error/warning message
86 return SCE_ERR_IFC;
87 } else if (strstart(lineBuffer, "Error ")) {
88 // Borland error message
89 return SCE_ERR_BORLAND;
90 } else if (strstart(lineBuffer, "Warning ")) {
91 // Borland warning message
92 return SCE_ERR_BORLAND;
93 } else if (strstr(lineBuffer, "at line ") &&
94 (strstr(lineBuffer, "at line ") < (lineBuffer + lengthLine)) &&
95 strstr(lineBuffer, "file ") &&
96 (strstr(lineBuffer, "file ") < (lineBuffer + lengthLine))) {
97 // Lua 4 error message
98 return SCE_ERR_LUA;
99 } else if (strstr(lineBuffer, " at ") &&
100 (strstr(lineBuffer, " at ") < (lineBuffer + lengthLine)) &&
101 strstr(lineBuffer, " line ") &&
102 (strstr(lineBuffer, " line ") < (lineBuffer + lengthLine)) &&
103 (strstr(lineBuffer, " at ") + 4 < (strstr(lineBuffer, " line ")))) {
104 // perl error message:
105 // <message> at <file> line <line>
106 return SCE_ERR_PERL;
107 } else if ((lengthLine >= 6) &&
108 (memcmp(lineBuffer, " at ", 6) == 0) &&
109 strstr(lineBuffer, ":line ")) {
110 // A .NET traceback
111 return SCE_ERR_NET;
112 } else if (strstart(lineBuffer, "Line ") &&
113 strstr(lineBuffer, ", file ")) {
114 // Essential Lahey Fortran error message
115 return SCE_ERR_ELF;
116 } else if (strstart(lineBuffer, "line ") &&
117 strstr(lineBuffer, " column ")) {
118 // HTML tidy style: line 42 column 1
119 return SCE_ERR_TIDY;
120 } else if (strstart(lineBuffer, "\tat ") &&
121 strstr(lineBuffer, "(") &&
122 strstr(lineBuffer, ".java:")) {
123 // Java stack back trace
124 return SCE_ERR_JAVA_STACK;
125 } else if (strstart(lineBuffer, "In file included from ") ||
126 strstart(lineBuffer, " from ")) {
127 // GCC showing include path to following error
128 return SCE_ERR_GCC_INCLUDED_FROM;
129 } else if (strstr(lineBuffer, "warning LNK")) {
130 // Microsoft linker warning:
131 // {<object> : } warning LNK9999
132 return SCE_ERR_MS;
133 } else {
134 // Look for one of the following formats:
135 // GCC: <filename>:<line>:<message>
136 // Microsoft: <filename>(<line>) :<message>
137 // Common: <filename>(<line>): warning|error|note|remark|catastrophic|fatal
138 // Common: <filename>(<line>) warning|error|note|remark|catastrophic|fatal
139 // Microsoft: <filename>(<line>,<column>)<message>
140 // CTags: <identifier>\t<filename>\t<message>
141 // Lua 5 traceback: \t<filename>:<line>:<message>
142 // Lua 5.1: <exe>: <filename>:<line>:<message>
143 const bool initialTab = (lineBuffer[0] == '\t');
144 bool initialColonPart = false;
145 bool canBeCtags = !initialTab; // For ctags must have an identifier with no spaces then a tab
146 enum { stInitial,
147 stGccStart, stGccDigit, stGccColumn, stGcc,
148 stMsStart, stMsDigit, stMsBracket, stMsVc, stMsDigitComma, stMsDotNet,
149 stCtagsStart, stCtagsFile, stCtagsStartString, stCtagsStringDollar, stCtags,
150 stUnrecognized
151 } state = stInitial;
152 for (Sci_PositionU i = 0; i < lengthLine; i++) {
153 const char ch = lineBuffer[i];
154 char chNext = ' ';
155 if ((i + 1) < lengthLine)
156 chNext = lineBuffer[i + 1];
157 if (state == stInitial) {
158 if (ch == ':') {
159 // May be GCC, or might be Lua 5 (Lua traceback same but with tab prefix)
160 if ((chNext != '\\') && (chNext != '/') && (chNext != ' ')) {
161 // This check is not completely accurate as may be on
162 // GTK+ with a file name that includes ':'.
163 state = stGccStart;
164 } else if (chNext == ' ') { // indicates a Lua 5.1 error message
165 initialColonPart = true;
167 } else if ((ch == '(') && Is1To9(chNext) && (!initialTab)) {
168 // May be Microsoft
169 // Check against '0' often removes phone numbers
170 state = stMsStart;
171 } else if ((ch == '\t') && canBeCtags) {
172 // May be CTags
173 state = stCtagsStart;
174 } else if (ch == ' ') {
175 canBeCtags = false;
177 } else if (state == stGccStart) { // <filename>:
178 state = Is0To9(ch) ? stGccDigit : stUnrecognized;
179 } else if (state == stGccDigit) { // <filename>:<line>
180 if (ch == ':') {
181 state = stGccColumn; // :9.*: is GCC
182 startValue = i + 1;
183 } else if (!Is0To9(ch)) {
184 state = stUnrecognized;
186 } else if (state == stGccColumn) { // <filename>:<line>:<column>
187 if (!Is0To9(ch)) {
188 state = stGcc;
189 if (ch == ':')
190 startValue = i + 1;
191 break;
193 } else if (state == stMsStart) { // <filename>(
194 state = Is0To9(ch) ? stMsDigit : stUnrecognized;
195 } else if (state == stMsDigit) { // <filename>(<line>
196 if (ch == ',') {
197 state = stMsDigitComma;
198 } else if (ch == ')') {
199 state = stMsBracket;
200 } else if ((ch != ' ') && !Is0To9(ch)) {
201 state = stUnrecognized;
203 } else if (state == stMsBracket) { // <filename>(<line>)
204 if ((ch == ' ') && (chNext == ':')) {
205 state = stMsVc;
206 } else if ((ch == ':' && chNext == ' ') || (ch == ' ')) {
207 // Possibly Delphi.. don't test against chNext as it's one of the strings below.
208 char word[512];
209 Sci_PositionU j, chPos;
210 unsigned numstep;
211 chPos = 0;
212 if (ch == ' ')
213 numstep = 1; // ch was ' ', handle as if it's a delphi errorline, only add 1 to i.
214 else
215 numstep = 2; // otherwise add 2.
216 for (j = i + numstep; j < lengthLine && IsAlphabetic(lineBuffer[j]) && chPos < sizeof(word) - 1; j++)
217 word[chPos++] = lineBuffer[j];
218 word[chPos] = 0;
219 if (!CompareCaseInsensitive(word, "error") || !CompareCaseInsensitive(word, "warning") ||
220 !CompareCaseInsensitive(word, "fatal") || !CompareCaseInsensitive(word, "catastrophic") ||
221 !CompareCaseInsensitive(word, "note") || !CompareCaseInsensitive(word, "remark")) {
222 state = stMsVc;
223 } else {
224 state = stUnrecognized;
226 } else {
227 state = stUnrecognized;
229 } else if (state == stMsDigitComma) { // <filename>(<line>,
230 if (ch == ')') {
231 state = stMsDotNet;
232 break;
233 } else if ((ch != ' ') && !Is0To9(ch)) {
234 state = stUnrecognized;
236 } else if (state == stCtagsStart) {
237 if (ch == '\t') {
238 state = stCtagsFile;
240 } else if (state == stCtagsFile) {
241 if ((lineBuffer[i - 1] == '\t') &&
242 ((ch == '/' && chNext == '^') || Is0To9(ch))) {
243 state = stCtags;
244 break;
245 } else if ((ch == '/') && (chNext == '^')) {
246 state = stCtagsStartString;
248 } else if ((state == stCtagsStartString) && ((lineBuffer[i] == '$') && (lineBuffer[i + 1] == '/'))) {
249 state = stCtagsStringDollar;
250 break;
253 if (state == stGcc) {
254 return initialColonPart ? SCE_ERR_LUA : SCE_ERR_GCC;
255 } else if ((state == stMsVc) || (state == stMsDotNet)) {
256 return SCE_ERR_MS;
257 } else if ((state == stCtagsStringDollar) || (state == stCtags)) {
258 return SCE_ERR_CTAG;
259 } else if (initialColonPart && strstr(lineBuffer, ": warning C")) {
260 // Microsoft warning without line number
261 // <filename>: warning C9999
262 return SCE_ERR_MS;
263 } else {
264 return SCE_ERR_DEFAULT;
269 #define CSI "\033["
271 namespace {
273 bool SequenceEnd(int ch) {
274 return (ch == 0) || ((ch >= '@') && (ch <= '~'));
277 int StyleFromSequence(const char *seq) {
278 int bold = 0;
279 int colour = 0;
280 while (!SequenceEnd(*seq)) {
281 if (Is0To9(*seq)) {
282 int base = *seq - '0';
283 if (Is0To9(seq[1])) {
284 base = base * 10;
285 base += seq[1] - '0';
286 seq++;
288 if (base == 0) {
289 colour = 0;
290 bold = 0;
292 else if (base == 1) {
293 bold = 1;
295 else if (base >= 30 && base <= 37) {
296 colour = base - 30;
299 seq++;
301 return SCE_ERR_ES_BLACK + bold * 8 + colour;
306 static void ColouriseErrorListLine(
307 char *lineBuffer,
308 Sci_PositionU lengthLine,
309 Sci_PositionU endPos,
310 Accessor &styler,
311 bool valueSeparate,
312 bool escapeSequences) {
313 Sci_Position startValue = -1;
314 int style = RecogniseErrorListLine(lineBuffer, lengthLine, startValue);
315 if (escapeSequences && strstr(lineBuffer, CSI)) {
316 const Sci_Position startPos = endPos - lengthLine;
317 const char *linePortion = lineBuffer;
318 Sci_Position startPortion = startPos;
319 int portionStyle = style;
320 while (const char *startSeq = strstr(linePortion, CSI)) {
321 if (startSeq > linePortion) {
322 styler.ColourTo(startPortion + static_cast<int>(startSeq - linePortion), portionStyle);
324 const char *endSeq = startSeq + 2;
325 while (!SequenceEnd(*endSeq))
326 endSeq++;
327 const Sci_Position endSeqPosition = startPortion + static_cast<Sci_Position>(endSeq - linePortion) + 1;
328 switch (*endSeq) {
329 case 0:
330 styler.ColourTo(endPos, SCE_ERR_ESCSEQ_UNKNOWN);
331 return;
332 case 'm': // Colour command
333 styler.ColourTo(endSeqPosition, SCE_ERR_ESCSEQ);
334 portionStyle = StyleFromSequence(startSeq+2);
335 break;
336 case 'K': // Erase to end of line -> ignore
337 styler.ColourTo(endSeqPosition, SCE_ERR_ESCSEQ);
338 break;
339 default:
340 styler.ColourTo(endSeqPosition, SCE_ERR_ESCSEQ_UNKNOWN);
341 portionStyle = style;
343 startPortion = endSeqPosition;
344 linePortion = endSeq + 1;
346 styler.ColourTo(endPos, portionStyle);
347 } else {
348 if (valueSeparate && (startValue >= 0)) {
349 styler.ColourTo(endPos - (lengthLine - startValue), style);
350 styler.ColourTo(endPos, SCE_ERR_VALUE);
351 } else {
352 styler.ColourTo(endPos, style);
357 static void ColouriseErrorListDoc(Sci_PositionU startPos, Sci_Position length, int, WordList *[], Accessor &styler) {
358 char lineBuffer[10000];
359 styler.StartAt(startPos);
360 styler.StartSegment(startPos);
361 Sci_PositionU linePos = 0;
363 // property lexer.errorlist.value.separate
364 // For lines in the output pane that are matches from Find in Files or GCC-style
365 // diagnostics, style the path and line number separately from the rest of the
366 // line with style 21 used for the rest of the line.
367 // This allows matched text to be more easily distinguished from its location.
368 const bool valueSeparate = styler.GetPropertyInt("lexer.errorlist.value.separate", 0) != 0;
370 // property lexer.errorlist.escape.sequences
371 // Set to 1 to interpret escape sequences.
372 const bool escapeSequences = styler.GetPropertyInt("lexer.errorlist.escape.sequences") != 0;
374 for (Sci_PositionU i = startPos; i < startPos + length; i++) {
375 lineBuffer[linePos++] = styler[i];
376 if (AtEOL(styler, i) || (linePos >= sizeof(lineBuffer) - 1)) {
377 // End of line (or of line buffer) met, colourise it
378 lineBuffer[linePos] = '\0';
379 ColouriseErrorListLine(lineBuffer, linePos, i, styler, valueSeparate, escapeSequences);
380 linePos = 0;
383 if (linePos > 0) { // Last line does not have ending characters
384 lineBuffer[linePos] = '\0';
385 ColouriseErrorListLine(lineBuffer, linePos, startPos + length - 1, styler, valueSeparate, escapeSequences);
389 static const char *const emptyWordListDesc[] = {
393 LexerModule lmErrorList(SCLEX_ERRORLIST, ColouriseErrorListDoc, "errorlist", 0, emptyWordListDesc);