1 // Scintilla source code edit control
2 /** @file LexErrorList.cxx
3 ** Lexer for error lists. Used for the output pane in SciTE.
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.
16 #include "Scintilla.h"
20 #include "LexAccessor.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
53 } else if (lineBuffer
[0] == '<') {
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
;
62 return SCE_ERR_DIFF_ADDITION
;
64 } else if (lineBuffer
[0] == '-') {
65 if (strstart(lineBuffer
, "--- ")) {
66 return SCE_ERR_DIFF_MESSAGE
;
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
73 } else if (strstart(lineBuffer
, "fortcom:")) {
74 // Intel Fortran Compiler v8.0 error/warning message
76 } else if (strstr(lineBuffer
, "File \"") && strstr(lineBuffer
, ", line ")) {
77 return SCE_ERR_PYTHON
;
78 } else if (strstr(lineBuffer
, " in ") && strstr(lineBuffer
, " on line ")) {
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
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
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>
107 } else if ((lengthLine
>= 6) &&
108 (memcmp(lineBuffer
, " at ", 6) == 0) &&
109 strstr(lineBuffer
, ":line ")) {
112 } else if (strstart(lineBuffer
, "Line ") &&
113 strstr(lineBuffer
, ", file ")) {
114 // Essential Lahey Fortran error message
116 } else if (strstart(lineBuffer
, "line ") &&
117 strstr(lineBuffer
, " column ")) {
118 // HTML tidy style: line 42 column 1
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
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
147 stGccStart
, stGccDigit
, stGccColumn
, stGcc
,
148 stMsStart
, stMsDigit
, stMsBracket
, stMsVc
, stMsDigitComma
, stMsDotNet
,
149 stCtagsStart
, stCtagsFile
, stCtagsStartString
, stCtagsStringDollar
, stCtags
,
152 for (Sci_PositionU i
= 0; i
< lengthLine
; i
++) {
153 const char ch
= lineBuffer
[i
];
155 if ((i
+ 1) < lengthLine
)
156 chNext
= lineBuffer
[i
+ 1];
157 if (state
== stInitial
) {
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 ':'.
164 } else if (chNext
== ' ') { // indicates a Lua 5.1 error message
165 initialColonPart
= true;
167 } else if ((ch
== '(') && Is1To9(chNext
) && (!initialTab
)) {
169 // Check against '0' often removes phone numbers
171 } else if ((ch
== '\t') && canBeCtags
) {
173 state
= stCtagsStart
;
174 } else if (ch
== ' ') {
177 } else if (state
== stGccStart
) { // <filename>:
178 state
= Is0To9(ch
) ? stGccDigit
: stUnrecognized
;
179 } else if (state
== stGccDigit
) { // <filename>:<line>
181 state
= stGccColumn
; // :9.*: is GCC
183 } else if (!Is0To9(ch
)) {
184 state
= stUnrecognized
;
186 } else if (state
== stGccColumn
) { // <filename>:<line>:<column>
193 } else if (state
== stMsStart
) { // <filename>(
194 state
= Is0To9(ch
) ? stMsDigit
: stUnrecognized
;
195 } else if (state
== stMsDigit
) { // <filename>(<line>
197 state
= stMsDigitComma
;
198 } else if (ch
== ')') {
200 } else if ((ch
!= ' ') && !Is0To9(ch
)) {
201 state
= stUnrecognized
;
203 } else if (state
== stMsBracket
) { // <filename>(<line>)
204 if ((ch
== ' ') && (chNext
== ':')) {
206 } else if ((ch
== ':' && chNext
== ' ') || (ch
== ' ')) {
207 // Possibly Delphi.. don't test against chNext as it's one of the strings below.
209 Sci_PositionU j
, chPos
;
213 numstep
= 1; // ch was ' ', handle as if it's a delphi errorline, only add 1 to i.
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
];
219 if (!CompareCaseInsensitive(word
, "error") || !CompareCaseInsensitive(word
, "warning") ||
220 !CompareCaseInsensitive(word
, "fatal") || !CompareCaseInsensitive(word
, "catastrophic") ||
221 !CompareCaseInsensitive(word
, "note") || !CompareCaseInsensitive(word
, "remark")) {
224 state
= stUnrecognized
;
227 state
= stUnrecognized
;
229 } else if (state
== stMsDigitComma
) { // <filename>(<line>,
233 } else if ((ch
!= ' ') && !Is0To9(ch
)) {
234 state
= stUnrecognized
;
236 } else if (state
== stCtagsStart
) {
240 } else if (state
== stCtagsFile
) {
241 if ((lineBuffer
[i
- 1] == '\t') &&
242 ((ch
== '/' && chNext
== '^') || Is0To9(ch
))) {
245 } else if ((ch
== '/') && (chNext
== '^')) {
246 state
= stCtagsStartString
;
248 } else if ((state
== stCtagsStartString
) && ((lineBuffer
[i
] == '$') && (lineBuffer
[i
+ 1] == '/'))) {
249 state
= stCtagsStringDollar
;
253 if (state
== stGcc
) {
254 return initialColonPart
? SCE_ERR_LUA
: SCE_ERR_GCC
;
255 } else if ((state
== stMsVc
) || (state
== stMsDotNet
)) {
257 } else if ((state
== stCtagsStringDollar
) || (state
== stCtags
)) {
259 } else if (initialColonPart
&& strstr(lineBuffer
, ": warning C")) {
260 // Microsoft warning without line number
261 // <filename>: warning C9999
264 return SCE_ERR_DEFAULT
;
273 bool SequenceEnd(int ch
) {
274 return (ch
== 0) || ((ch
>= '@') && (ch
<= '~'));
277 int StyleFromSequence(const char *seq
) {
280 while (!SequenceEnd(*seq
)) {
282 int base
= *seq
- '0';
283 if (Is0To9(seq
[1])) {
285 base
+= seq
[1] - '0';
292 else if (base
== 1) {
295 else if (base
>= 30 && base
<= 37) {
301 return SCE_ERR_ES_BLACK
+ bold
* 8 + colour
;
306 static void ColouriseErrorListLine(
308 Sci_PositionU lengthLine
,
309 Sci_PositionU endPos
,
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
))
327 const Sci_Position endSeqPosition
= startPortion
+ static_cast<Sci_Position
>(endSeq
- linePortion
) + 1;
330 styler
.ColourTo(endPos
, SCE_ERR_ESCSEQ_UNKNOWN
);
332 case 'm': // Colour command
333 styler
.ColourTo(endSeqPosition
, SCE_ERR_ESCSEQ
);
334 portionStyle
= StyleFromSequence(startSeq
+2);
336 case 'K': // Erase to end of line -> ignore
337 styler
.ColourTo(endSeqPosition
, SCE_ERR_ESCSEQ
);
340 styler
.ColourTo(endSeqPosition
, SCE_ERR_ESCSEQ_UNKNOWN
);
341 portionStyle
= style
;
343 startPortion
= endSeqPosition
;
344 linePortion
= endSeq
+ 1;
346 styler
.ColourTo(endPos
, portionStyle
);
348 if (valueSeparate
&& (startValue
>= 0)) {
349 styler
.ColourTo(endPos
- (lengthLine
- startValue
), style
);
350 styler
.ColourTo(endPos
, SCE_ERR_VALUE
);
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
);
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
);