1 /*    o_messages.cpp
2  *
3  *    Copyright (c) 1994-1996, Marko Macek
4  *
5  *    You may distribute under the terms of either the GNU General Public
6  *    License or the Artistic License, as specified in the README file.
7  *
8  */
9 
10 #include "o_messages.h"
11 
12 #ifdef CONFIG_OBJ_MESSAGES
13 
14 #include "c_commands.h"
15 #include "c_config.h"
16 #include "i_view.h"
17 #include "o_buflist.h"
18 #include "s_files.h"
19 #include "s_string.h"
20 #include "s_util.h"
21 
22 #include <stdio.h>
23 
24 #define MAXREGEXP  32
25 
26 EMessages *CompilerMsgs = 0;
27 
28 struct Error {
29     EBuffer *Buf;
30     StlString file;
31     StlString msg;
32     StlString text;
33     int line;
34     int hilit;
35 
ErrorError36     Error(const char *fn, int ln, const char *mesg, const char *txt, int hlt) :
37 	Buf(0), file(fn), msg(mesg), text(txt), line(ln), hilit(hlt)
38     {}
39 };
40 
41 static int NCRegexp = 0;
42 static struct {
43     int RefFile;
44     int RefLine;
45     int RefMsg;
46     RxNode *rx;
47 } CRegexp[MAXREGEXP];
48 
AddCRegexp(int file,int line,int msg,const char * regexp)49 int AddCRegexp(int file, int line, int msg, const char *regexp) {
50     //fprintf(stderr, "ADD EXP %d: %s   %d %d %d\n", NCRegexp, regexp, file, line, msg);
51     if (NCRegexp >= MAXREGEXP) return 0;
52     CRegexp[NCRegexp].RefFile = file;
53     CRegexp[NCRegexp].RefLine = line;
54     CRegexp[NCRegexp].RefMsg = msg;
55     if ((CRegexp[NCRegexp].rx = RxCompile(regexp)) == NULL) {
56         return 0;
57     }
58     NCRegexp++;
59     return 1;
60 }
61 
FreeCRegexp()62 void FreeCRegexp()
63 {
64     while (NCRegexp--)
65         RxFree(CRegexp[NCRegexp].rx);
66 }
67 
EMessages(int createFlags,EModel ** ARoot,const char * ADir,const char * ACommand)68 EMessages::EMessages(int createFlags, EModel **ARoot, const char *ADir, const char *ACommand) :
69     EList(createFlags, ARoot, "Messages"),
70     Running(1),
71     BufLen(0),
72     BufPos(0),
73     ReturnCode(-1),
74     MatchCount(0)
75 {
76     CompilerMsgs = this;
77     RunPipe(ADir, ACommand);
78 }
79 
~EMessages()80 EMessages::~EMessages() {
81     gui->ClosePipe(PipeId);
82     FreeErrors();
83     CompilerMsgs = 0;
84 }
85 
NotifyDelete(EModel * Deleting)86 void EMessages::NotifyDelete(EModel *Deleting) {
87     vector_iterate(Error*, ErrList, it) {
88         if ((*it)->Buf == Deleting) {
89             /* NOT NEEDED!
90              char bk[16];
91              sprintf(bk, "_MSG.%d", i);
92              ((EBuffer *)Deleting)->RemoveBookmark(bk);
93              */
94             (*it)->Buf = 0;
95         }
96     }
97 }
98 
FindErrorFiles()99 void EMessages::FindErrorFiles() {
100     unsigned i = 0;
101     vector_iterate(Error*, ErrList, it) {
102         if ((*it)->Buf == 0 && (*it)->file.empty())
103 	    FindErrorFile(i);
104         ++i;
105     }
106 }
107 
FindErrorFile(unsigned err)108 void EMessages::FindErrorFile(unsigned err) {
109     assert(err < ErrList.size());
110     if (ErrList[err]->file.empty())
111         return;
112 
113     ErrList[err]->Buf = 0;
114 
115     EBuffer *B = FindFile(ErrList[err]->file.c_str());
116     if (B == 0)
117         return;
118 
119     if (B->Loaded == 0)
120         return;
121 
122     AddFileError(B, err);
123 }
124 
AddFileError(EBuffer * B,unsigned err)125 void EMessages::AddFileError(EBuffer *B, unsigned err) {
126 
127     assert(err < ErrList.size());
128 
129     char bk[16];
130     sprintf(bk, "_MSG.%d", err);
131     EPoint P(ErrList[err]->line - 1, 0);
132 
133     if (P.Row >= B->RCount)
134         P.Row = B->RCount - 1;
135     if (P.Row < 0)
136         P.Row = 0;
137 
138     if (B->PlaceBookmark(bk, P) == 1)
139         ErrList[err]->Buf = B;
140 }
141 
FindFileErrors(EBuffer * B)142 void EMessages::FindFileErrors(EBuffer *B) {
143     unsigned i = 0;
144     vector_iterate(Error*, ErrList, it) {
145         if ((*it)->Buf == 0 && !(*it)->file.empty()) {
146             if (filecmp(B->FileName, (*it)->file.c_str()) == 0)
147                 AddFileError(B, i);
148 	}
149 	++i;
150     }
151 }
152 
RunPipe(const char * ADir,const char * ACommand)153 int EMessages::RunPipe(const char *ADir, const char *ACommand) {
154     if (!KeepMessages)
155         FreeErrors();
156 
157     Command = ACommand;
158     Directory = ADir;
159 
160     MatchCount = 0;
161     ReturnCode = -1;
162     Running = 1;
163     BufLen = BufPos = 0;
164     Row = (int)ErrList.size() - 1;
165 
166     char s[2 * MAXPATH * 4];
167 
168     sprintf(s, "[running '%s' in '%s']", ACommand, ADir);
169     AddError(0, -1, 0, s);
170 
171     sprintf(s, "Messages [%s]: %s", ADir, ACommand);
172     SetTitle(s);
173 
174     ChangeDir(ADir);
175     PipeId = gui->OpenPipe(ACommand, this);
176     return 0;
177 }
178 
GetEventMap()179 EEventMap *EMessages::GetEventMap() {
180     return FindEventMap("MESSAGES");
181 }
182 
ExecCommand(ExCommands Command,ExState & State)183 int EMessages::ExecCommand(ExCommands Command, ExState &State) {
184     switch (Command) {
185     case ExChildClose:
186         if (Running == 0 || PipeId == -1)
187             break;
188         ReturnCode = gui->ClosePipe(PipeId);
189         PipeId = -1;
190         Running = 0;
191         char s[30];
192         sprintf(s, "[aborted, status=%d]", ReturnCode);
193         AddError(0, -1, 0, s);
194         return 1;
195     case ExActivateInOtherWindow:
196         ShowError(View->Next, Row);
197         return 1;
198     case ExFind:
199         fprintf(stderr, "FIND\n");
200         return 1;
201     default:
202         ;
203     }
204     return EList::ExecCommand(Command, State);
205 }
206 
AddError(const char * file,int line,const char * msg,const char * text,int hilit)207 void EMessages::AddError(const char *file, int line, const char *msg, const char *text, int hilit) {
208     ErrList.push_back(new Error(file, line, msg, text, hilit));
209     //fprintf(stderr, "Error %s %d %s %s\n", file, line, msg, text);
210     FindErrorFile((unsigned)ErrList.size() - 1);
211 
212     if ((int)ErrList.size() > Count)
213         if (Row >= Count - 1)
214 	    Row = (int)ErrList.size() - 1;
215 
216     UpdateList();
217 }
218 
FreeErrors()219 void EMessages::FreeErrors() {
220     unsigned i = 0;
221     vector_iterate(Error*, ErrList, it) {
222 	if ((*it)->Buf != 0) {
223 	    char bk[16];
224 	    sprintf(bk, "_MSG.%d", i);
225 	    (*it)->Buf->RemoveBookmark(bk);
226 	}
227 	delete *it;
228         i++;
229     }
230     ErrList.clear();
231     BufLen = BufPos = 0;
232 }
233 
GetLine(char * Line,size_t maxim)234 int EMessages::GetLine(char *Line, size_t maxim) {
235     ssize_t rc;
236     char *p;
237     int l;
238 
239     //fprintf(stderr, "GetLine: %d\n", Running);
240 
241     *Line = 0;
242     if (Running && PipeId != -1) {
243         rc = gui->ReadPipe(PipeId, MsgBuf + BufLen, sizeof(MsgBuf) - BufLen);
244         //fprintf(stderr, "GetLine: ReadPipe rc = %d\n", rc);
245         if (rc == -1) {
246             ReturnCode = gui->ClosePipe(PipeId);
247             PipeId = -1;
248             Running = 0;
249         }
250         if (rc > 0)
251             BufLen += rc;
252     }
253     l = maxim - 1;
254     if (BufLen - BufPos < l)
255         l = BufLen - BufPos;
256     //fprintf(stderr, "GetLine: Data %d\n", l);
257     p = (char *)memchr(MsgBuf + BufPos, '\n', l);
258     if (p) {
259         *p = 0;
260         UnEscStr(Line, maxim, MsgBuf + BufPos, p - (MsgBuf + BufPos));
261         //strcpy(Line, MsgBuf + BufPos);
262         l = strlen(Line);
263         if (l > 0 && Line[l - 1] == '\r')
264             Line[l - 1] = 0;
265         BufPos = p + 1 - MsgBuf;
266         //fprintf(stderr, "GetLine: Line %d\n", strlen(Line));
267     } else if (Running && sizeof(MsgBuf) != BufLen) {
268         memmove(MsgBuf, MsgBuf + BufPos, BufLen - BufPos);
269         BufLen -= BufPos;
270         BufPos = 0;
271         //fprintf(stderr, "GetLine: Line Incomplete\n");
272         return 0;
273     } else {
274         if (l == 0)
275             return 0;
276         UnEscStr(Line, maxim, MsgBuf + BufPos, l);
277         //memcpy(Line, MsgBuf + BufPos, l);
278         Line[l] = 0;
279         if (l > 0 && Line[l - 1] == '\r')
280             Line[l - 1] = 0;
281         BufPos += l;
282         //fprintf(stderr, "GetLine: Line Last %d\n", l);
283     }
284     memmove(MsgBuf, MsgBuf + BufPos, BufLen - BufPos);
285     BufLen -= BufPos;
286     BufPos = 0;
287     //fprintf(stderr, "GetLine: Got Line\n");
288     return 1;
289 }
290 
291 
getWord(char * dest,const char * pin)292 static void getWord(char* dest, const char* pin)
293 {
294     char *pout, *pend;
295     char ch, ec;
296 
297     while (*pin == ' ' || *pin == '\t')
298         pin++;
299 
300     pout = dest;
301     pend = dest + 256 - 1;
302     if (*pin == '\'' || *pin == '"' || *pin == '`') {
303         ec = *pin++;
304         if (ec == '`')
305             ec = '\'';
306         for (;;) {
307             ch  = *pin++;
308             if (ch == '`')
309                 ch = '\'';
310             if (ch == ec || ch == 0)
311                 break;
312 
313             if (pout < pend)
314                 *pout++ = ch;
315         }
316         if (ch == 0)
317             pin--;
318     } else {
319         for(;;) {
320             ch  = *pin++;
321             if (ch == ' ' || ch == '\t' || ch == 0)
322                 break;
323             if (pout < pend) *pout++ = ch;
324         }
325     }
326     *pout = 0;
327 }
328 
329 
GetErrors()330 void EMessages::GetErrors() {
331     char line[4096];
332     RxMatchRes RM;
333     //int retc;
334     int i, n;
335     int didmatch = 0;
336     int WasRunning = Running;
337     char fn[256];
338 
339     //fprintf(stderr, "Reading pipe\n");
340     while (GetLine(line, sizeof(line))) {
341         size_t len = strlen(line);
342         if (len > 0 && line[len - 1] == '\n')
343 	    line[--len] = 0;
344         didmatch = 0;
345         for (i = 0; i < NCRegexp; i++) {
346             if (RxExec(CRegexp[i].rx, line, len, line, &RM) == 1) {
347 		char ln[256];
348 		char msg[256];
349 		char fn1[256];
350 		char fn2[256];
351 		char *file;
352 
353 		n = CRegexp[i].RefFile;
354 		unsigned s = RM.Close[n] - RM.Open[n];
355 		if (s < sizeof(fn))
356 		    memcpy(fn, line + RM.Open[n], s);
357 		else
358 		    s = 0;
359 		fn[s] = 0;
360 
361 		n = CRegexp[i].RefLine;
362 		s = RM.Close[n] - RM.Open[n];
363 		if (s < sizeof(ln))
364 		    memcpy(ln, line + RM.Open[n], s);
365 		else
366 		    s = 0;
367 		ln[s] = 0;
368 
369 		n = CRegexp[i].RefMsg;
370 		s = RM.Close[n] - RM.Open[n];
371 		if (s < sizeof(msg))
372 		    memcpy(msg, line + RM.Open[n], s);
373 		else
374 		    s = 0;
375 		msg[s] = 0;
376 		//fprintf(stderr, "File:%s msg:%s rex:%d c:%d o:%d>%s<8\nTXT:%s\n", fn, ln, i, RM.Close[n], RM.Open[n], msg, line);
377 		if (IsFullPath(fn))
378 		    file = fn;
379 		else {
380 		    /*
381 		     * for now - try only with top most dir
382 		     * later we might try to find the file in all stacked dirs
383 		     * as with parallel makes it's hard to guess the right directory path
384                      */
385 		    strlcpy(fn1, DirLevel.size() ? DirLevel.back().c_str() : Directory.c_str(),
386 			    sizeof(fn1));
387                     Slash(fn1, 1);
388                     strlcat(fn1, fn, sizeof(fn1));
389                     if (ExpandPath(fn1, fn2, sizeof(fn2)) == 0)
390                         file = fn2;
391                     else
392                         file = fn1;
393                 }
394                 AddError(file, atoi(ln), msg, line, 1);
395                 didmatch = 1;
396                 MatchCount++;
397                 break;
398             }
399         }
400         if (!didmatch)
401         {
402             AddError(0, -1, 0, line);
403             //** Quicky: check for gnumake 'entering directory'
404             //** make[x]: entering directory `xxx'
405             //** make[x]: leaving...
406             static const char t1[] = "entering directory";
407             static const char t2[] = "leaving directory";
408             const char *pin;
409 
410             if ( (pin = strstr(line, "]:")) != 0)
411             {
412                 //** It *is* some make line.. Check for 'entering'..
413                 pin += 2;
414                 while (*pin == ' ')
415                     pin++;
416                 if (strnicmp(pin, t1, sizeof(t1)-1) == 0) {  // Entering?
417                     //** Get the directory name from the line,
418                     pin += sizeof(t1)-1;
419                     getWord(fn, pin);
420                     //dbg("entering %s", fn);
421 
422 		    if (*fn)
423                         //** Indeed entering directory! Link in list,
424 			DirLevel.push_back(fn);
425                 } else if (strnicmp(pin, t2, sizeof(t2)-1) == 0) {  // Leaving?
426                     pin += sizeof(t2)-1;
427                     getWord(fn, pin);                   // Get dirname,
428                     //dbg("leaving %s", fn);
429 		    int found = 0;
430 		    for (unsigned i = DirLevel.size(); i-- > 0;) {
431 			/*
432 			 * remove leaved director from our list of Dirs
433 			 * as many users runs make in parallel mode
434 			 * we might get pretty mangled order of dirs
435                          * so remove the last added with the same name
436                          */
437 			if (stricmp(DirLevel[i].c_str(), fn) == 0) {
438 			    DirLevel.erase(DirLevel.begin() + i);
439                             found++;
440 			    break;
441 			}
442 		    }
443 		    if (!found) {
444                         //** Mismatch filenames -> error, and revoke stack.
445                         //dbg("mismatch on %s", fn);
446                         AddError(0, -1, 0, "fte: mismatch in directory stack!?");
447 			//** In this case we totally die the stack..
448                         DirLevel.clear();
449                     }
450                 }
451             }
452         }
453     }
454     //fprintf(stderr, "Reading Stopped\n");
455     if (!Running && WasRunning) {
456         char s[30];
457 
458         sprintf(s, "[done, status=%d]", ReturnCode);
459         AddError(0, -1, 0, s);
460     }
461     //UpdateList();
462     //NeedsUpdate = 1;
463 }
464 
CompilePrevError(EView * V)465 int EMessages::CompilePrevError(EView *V) {
466     if (!ErrList.size()) {
467         V->Msg(S_INFO, "No errors.");
468         return 0;
469     }
470 
471     while (Row > 0) {
472         Row--;
473         if (ErrList[Row]->line != -1 && !ErrList[Row]->file.empty()) {
474             ShowError(V, Row);
475             return 1;
476         }
477     }
478 
479     V->Msg(S_INFO, "No previous error.");
480     return 0;
481 }
482 
CompileNextError(EView * V)483 int EMessages::CompileNextError(EView *V) {
484     if (!ErrList.size()) {
485         V->Msg(S_INFO, (Running) ? "No errors (yet)." : "No errors.");
486         return 0;
487     }
488 
489     while ((Row + 1) < (int)ErrList.size()) {
490         Row++;
491         if (ErrList[Row]->line != -1 && !ErrList[Row]->file.empty()) {
492             ShowError(V, Row);
493             return 1;
494         }
495     }
496 
497     V->Msg(S_INFO, (Running) ? "No more errors (yet)." : "No more errors.");
498     return 0;
499 }
500 
Compile(char *)501 int EMessages::Compile(char * /*Command*/) {
502     return 0;
503 }
504 
ShowError(EView * V,unsigned err)505 void EMessages::ShowError(EView *V, unsigned err) {
506     if (err < ErrList.size()) {
507         if (!ErrList[err]->file.empty()) {
508             // should check if relative path
509             // possibly scan for (gnumake) directory info in output
510             if (ErrList[err]->Buf) {
511                 char bk[16];
512 
513                 V->SwitchToModel(ErrList[err]->Buf);
514 
515                 sprintf(bk, "_MSG.%d", err);
516                 ErrList[err]->Buf->GotoBookmark(bk);
517             } else {
518                 if (FileLoad(0, ErrList[err]->file.c_str(), 0, V) == 1) {
519                     V->SwitchToModel(ActiveModel);
520                     ((EBuffer *)ActiveModel)->CenterNearPosR(0, ErrList[err]->line - 1);
521                 }
522             }
523             if (!ErrList[err]->msg.empty())
524                 V->Msg(S_INFO, "%s", ErrList[err]->msg.c_str());
525             else
526                 V->Msg(S_INFO, "%s", ErrList[err]->text.c_str());
527         }
528     }
529 }
530 
DrawLine(PCell B,int Line,int Col,ChColor color,int Width)531 void EMessages::DrawLine(PCell B, int Line, int Col, ChColor color, int Width) {
532     if (Line < (int)ErrList.size())
533         if (Col < int(ErrList[Line]->text.size())) {
534             char str[1024];
535             size_t len;
536 
537             len = UnTabStr(str, sizeof(str),
538                            ErrList[Line]->text.c_str(),
539                            ErrList[Line]->text.size());
540 
541             if ((int)len > Col)
542                 MoveStr(B, 0, Width, str + Col, color, Width);
543         }
544 }
545 
FormatLine(int Line)546 char* EMessages::FormatLine(int Line) {
547     if (Line < (int)ErrList.size())
548         return strdup(ErrList[Line]->text.c_str());
549 
550     return 0;
551 }
552 
IsHilited(int Line)553 int EMessages::IsHilited(int Line) {
554     return (Line >= 0 && Line < (int)ErrList.size()) ? ErrList[Line]->hilit : 0;
555 }
556 
UpdateList()557 void EMessages::UpdateList() {
558     Count = (int)ErrList.size();
559     EList::UpdateList();
560 }
561 
Activate(int)562 int EMessages::Activate(int /*No*/) {
563     //assert(No == Row);
564     //Row = No;
565     ShowError(View, Row);
566     return 1;
567 }
568 
CanActivate(int Line)569 int EMessages::CanActivate(int Line) {
570     //return (Line < (int)ErrList.size());
571     return (Line < (int)ErrList.size()
572             && (!ErrList[Line]->file.empty()
573                 || ErrList[Line]->line != -1)) ? 1 : 0;
574 }
575 
NotifyPipe(int APipeId)576 void EMessages::NotifyPipe(int APipeId) {
577     //fprintf(stderr, "Got notified");
578     if (APipeId == PipeId)
579         GetErrors();
580 }
581 
GetName(char * AName,size_t MaxLen)582 void EMessages::GetName(char *AName, size_t MaxLen) {
583     strlcpy(AName, "Messages", MaxLen);
584 }
585 
GetInfo(char * AInfo,size_t)586 void EMessages::GetInfo(char *AInfo, size_t /*MaxLen*/) {
587     sprintf(AInfo, "%2d %04d/%03d Messages: %d (%s)",
588             ModelNo,Row, Count, MatchCount, Command.c_str());
589 }
590 
GetPath(char * APath,size_t MaxLen)591 void EMessages::GetPath(char *APath, size_t MaxLen) {
592     strlcpy(APath, Directory.c_str(), MaxLen);
593     Slash(APath, 0);
594 }
595 
GetTitle(char * ATitle,size_t MaxLen,char * ASTitle,size_t SMaxLen)596 void EMessages::GetTitle(char *ATitle, size_t MaxLen, char *ASTitle, size_t SMaxLen) {
597     snprintf(ATitle, MaxLen, "Messages: %s", Command.c_str());
598     strlcpy(ASTitle, "Messages", SMaxLen);
599 }
600 
601 // get row length for specified row, used in MoveLineEnd to get actual row length
GetRowLength(int ARow)602 size_t EMessages::GetRowLength(int ARow)
603 {
604     if ((ARow >= 0) && (ARow < (int)ErrList.size()))
605         return ErrList[ARow]->text.size();
606 
607     return 0;
608 }
609 
610 #endif // CONFIG_OBJ_MESSAGES
611