1 /*
2   FXiTe - The Free eXtensIble Text Editor
3   Copyright (c) 2009-2013 Jeffrey Pohlmeyer <yetanothergeek@gmail.com>
4 
5   This program is free software; you can redistribute it and/or modify it
6   under the terms of the GNU General Public License version 3 as
7   published by the Free Software Foundation.
8 
9   This software is distributed in the hope that it will be useful,
10   but WITHOUT ANY WARRANTY; without even the implied warranty of
11   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12   GNU General Public License for more details.
13 
14   You should have received a copy of the GNU General Public License
15   along with this program; if not, write to the Free Software
16   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17 */
18 
19 
20 #include <cctype>
21 #include <fxkeys.h>
22 #include <fx.h>
23 
24 #include "compat.h"
25 #include "scidoc.h"
26 #include "readtags.h"
27 
28 #include "intl.h"
29 #include "tagread.h"
30 
31 extern "C" {
32   // Let the "readtags.c" file use fxwarning instead of perror...
33   void tag_warning(const char*msg);
34 }
35 
36 
tag_warning(const char * msg)37 void tag_warning(const char*msg)
38 {
39   fxwarning("%s\n", msg);
40 }
41 
42 typedef struct {
43   char *file;
44   char *pattern;
45   unsigned long lineNumber;
46   char *kind;
47   short fileScope;
48 } MytagEntry;
49 
50 
51 
CopyTag(tagEntry * te)52 static MytagEntry*CopyTag(tagEntry*te)
53 {
54   MytagEntry*rv=(MytagEntry*)calloc(1,sizeof(MytagEntry));
55   rv->file=te->file?strdup(te->file):NULL;
56   rv->pattern=te->address.pattern?strdup(te->address.pattern):NULL;
57   rv->kind=te->kind?strdup(te->kind):NULL;
58   rv->lineNumber=te->address.lineNumber;
59   rv->fileScope=te->fileScope;
60   return rv;
61 }
62 
63 
64 
FreeTag(MytagEntry * t)65 static void FreeTag(MytagEntry*t)
66 {
67   if (t) {
68     if (t->pattern) { free(t->pattern); }
69     if (t->file) { free(t->file); }
70     if (t->kind) { free(t->kind); }
71     free(t);
72   }
73 }
74 
75 
76 class TagDialog: public FXDialogBox {
77   FXDECLARE(TagDialog);
78 protected:
TagDialog()79   TagDialog() {}
80 private:
81   FXList* taglist;
82   FXString tagname;
83   FXLabel *desc;
84   void SetLabel();
85 public:
86   long onListClick(FXObject*o, FXSelector sel, void*p);
87   enum {
88     ID_LIST_CLICK=FXDialogBox::ID_LAST,
89     ID_LAST
90   };
91   TagDialog(FXWindow*p, const FXString &tn);
92   ~TagDialog();
list()93   FXList*list() { return taglist; }
appendItem(FXListItem * item)94   void appendItem(FXListItem*item) { taglist->appendItem(item); }
95   virtual FXuint execute(FXuint placement=PLACEMENT_OWNER);
96 };
97 
98 
99 
execute(FXuint placement)100 FXuint TagDialog::execute(FXuint placement)
101 {
102   SetLabel();
103   return FXDialogBox::execute(placement);
104 }
105 
106 
107 
108 FXDEFMAP(TagDialog) TagDialogMap[]={
109   FXMAPFUNC(SEL_DOUBLECLICKED,TagDialog::ID_LIST_CLICK, TagDialog::onListClick),
110   FXMAPFUNC(SEL_CLICKED,TagDialog::ID_LIST_CLICK, TagDialog::onListClick),
111   FXMAPFUNC(SEL_KEYPRESS,TagDialog::ID_LIST_CLICK, TagDialog::onListClick)
112 };
113 
114 
115 FXIMPLEMENT(TagDialog,FXDialogBox,TagDialogMap,ARRAYNUMBER(TagDialogMap));
116 
117 #define PACK_UNIFORM PACK_UNIFORM_WIDTH|PACK_UNIFORM_HEIGHT
118 #define BTN_OPTS FRAME_RAISED|FRAME_THICK|LAYOUT_CENTER_Y|LAYOUT_RIGHT
119 
TagDialog(FXWindow * p,const FXString & tn)120 TagDialog::TagDialog(FXWindow*p,
121   const FXString &tn):FXDialogBox(p,_("Choose tag"),DECOR_TITLE|DECOR_BORDER,0,0,480,320)
122 {
123   tagname=tn;
124   FXHorizontalFrame* buttons=new FXHorizontalFrame(this,LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X|PACK_UNIFORM_WIDTH);
125   buttons->setPadRight(8);
126   buttons->setPadLeft(8);
127   buttons->setHSpacing(12);
128   new FXButton(buttons,_("&OK"),         NULL,this,ID_ACCEPT,BTN_OPTS);
129   new FXButton(buttons,_("  &Cancel  "), NULL,this,ID_CANCEL,BTN_OPTS);
130   new FXHorizontalSeparator(this,SEPARATOR_GROOVE|LAYOUT_SIDE_BOTTOM|LAYOUT_FILL_X);
131 
132   FXVerticalFrame*vbox=new FXVerticalFrame(this,FRAME_RAISED|FRAME_THICK|LAYOUT_SIDE_TOP|LAYOUT_FILL);
133   taglist=new FXList(vbox,this,ID_LIST_CLICK, LIST_BROWSESELECT|LAYOUT_SIDE_TOP|LAYOUT_FILL);
134   desc=new FXLabel(vbox, " \n ",
135     NULL,LABEL_NORMAL|LAYOUT_SIDE_BOTTOM|LAYOUT_SIDE_RIGHT|LAYOUT_FILL_X);
136   changeFocus(taglist);
137 }
138 
139 
~TagDialog()140 TagDialog::~TagDialog()
141 {
142   FXint i;
143   for (i=0; i<taglist->getNumItems(); i++) {
144     FreeTag((MytagEntry*)(taglist->getItemData(i)));
145     taglist->setItemData(i,NULL);
146   }
147 }
148 
149 
150 
onListClick(FXObject * o,FXSelector sel,void * p)151 long TagDialog::onListClick(FXObject*o, FXSelector sel, void*p)
152 {
153   switch ( FXSELTYPE(sel) ) {
154     case SEL_DOUBLECLICKED: {
155       getApp()->stopModal(this,true);
156       return 1;
157     }
158     case SEL_KEYPRESS: {
159       FXint code=((FXEvent*)p)->code;
160       if ((code==KEY_Return)||(code==KEY_KP_Enter)) {
161         getApp()->stopModal(this,true);
162         return 1;
163       }
164     }
165   }
166   SetLabel();
167   return 0;
168 }
169 
170 
171 
kind(char * c)172 static inline const char*kind(char *c)
173 {
174   if (c) {
175     switch (*c) {
176       case 'c': return "class";
177       case 'd': return "define";
178       case 'e': return "enum value";
179       case 'f': return "function";
180       case 'g': return "enum";
181       case 'm': return "member";
182       case 'n': return "namespace";
183       case 's': return "struct";
184       case 't': return "typedef";
185       case 'u': return "union";
186       case 'v': return "variable";
187       case 'x': return "external";
188     default:return "";
189     }
190   } else {
191     return "";
192   }
193 }
194 
195 
196 
SetLabel()197 void TagDialog::SetLabel()
198 {
199   MytagEntry*mt=(MytagEntry*)(taglist->getItemData(taglist->getCurrentItem()));
200   FXString txt;
201   txt.format("%s %s %s\n%s", kind(mt->kind), tagname.text(), mt->fileScope?"local ":"",mt->file);
202   desc->setText(txt);
203 }
204 
205 
206 
IsCallTipFile(const char * filename)207 static bool IsCallTipFile(const char*filename)
208 {
209   FXFile fh(filename,FXIO::Reading);
210   if (fh.isOpen()) {
211     char buf[64];
212     memset(buf,0,sizeof(buf));
213     fh.readBlock(buf,sizeof(buf)-1);
214     fh.close();
215     if (strstr(buf,"!_TAG_FILE_FORMAT\t")==buf) {
216       return false;
217     }
218   }
219   return true;
220 }
221 
222 
223 
NewListItem(tagEntry * te)224 static FXListItem *NewListItem(tagEntry *te)
225 {
226   char *txt;
227   if (te->address.pattern) {
228     // Strip the pattern-matching meta-chars from the start and end of the label.
229     int p=0;
230     if (strncmp(te->address.pattern,"/^", 2)==0) { p+=2; }
231     while (isspace(te->address.pattern[p])) {p++;}
232     txt=strdup(te->address.pattern+p);
233     int n=strlen(txt);
234     if ((n>2)&&(txt[n-1]=='/')&&(txt[n-2]=='$')) { txt[n-2]='\0'; }
235   } else {
236     txt=strdup("");
237   }
238   FXListItem *rv=new FXListItem(txt,NULL,CopyTag(te));
239   free(txt);
240   return rv;
241 }
242 
243 
244 
FindTag(FXWindow * shell,const FXString & tagname,FXMenuCaption * tagfiles,FXString & outfile,FXString & outcoords,FXString & outpat)245 static bool FindTag(FXWindow*shell,
246   const FXString &tagname, FXMenuCaption*tagfiles, FXString &outfile, FXString &outcoords, FXString &outpat)
247 {
248   outfile="";
249   outcoords="";
250   outpat="";
251   if (tagfiles && !tagname.empty()) {
252     FXWindow*w;
253     MytagEntry*mt=NULL;
254     TagDialog dlg(shell,tagname);
255     tagEntry te;
256     for (w=tagfiles; w; w=w->getNext()) {
257       const char* filename=((FXMenuCommand*)w)->getText().text();
258       if (IsCallTipFile(filename)) { continue; }
259       tagFileInfo ti;
260       memset(&ti,0,sizeof(ti));
261       tagFile *tf=tagsOpen(filename, &ti);
262       int tagflags=(TAG_FULLMATCH|TAG_OBSERVECASE);
263       if (tf) {
264         if (ti.status.opened) {
265           if (tagsFind(tf, &te, tagname.text(), tagflags)==TagSuccess) {
266             dlg.appendItem(NewListItem(&te));
267             while (tagsFindNext(tf,&te)==TagSuccess) {
268               dlg.appendItem(NewListItem(&te));
269             }
270           }
271         }
272         tagsClose(tf);
273       }
274     }
275     switch (dlg.list()->getNumItems()) {
276       case 0:{
277         FXMessageBox::information(shell, MBOX_OK, _("Tag not found."),_("Tag \"%s\" not found\n"), tagname.text());
278         break;
279       }
280       case 1: {
281         mt=(MytagEntry*)(dlg.list()->getItem(0)->getData());
282         break;
283       }
284       default:{
285         dlg.list()->selectItem(0);
286         if (dlg.execute(PLACEMENT_OWNER)) {
287           mt=(MytagEntry*)(dlg.list()->getItemData(dlg.list()->getCurrentItem()));
288         }
289       }
290     }
291     if (mt) {
292       outfile=mt->file;
293       if (mt->lineNumber) { outcoords.format("%ld", mt->lineNumber); } else { outcoords=""; }
294       outpat=mt->pattern;
295       return true;
296     }
297   }
298   return false;
299 }
300 
301 
302 
FindTag(SciDoc * sci,FXMenuCaption * tagfiles,FXString & outfile,FXString & outcoords,FXString & outpat)303 bool TagHandler::FindTag(SciDoc*sci, FXMenuCaption* tagfiles, FXString &outfile, FXString &outcoords, FXString &outpat)
304 {
305   FXString s;
306   sci->GetSelText(s);
307   if (s.empty()) { sci->WordAtPos(s); }
308   return ::FindTag(sci->getShell(),s,tagfiles,outfile,outcoords,outpat);
309 }
310 
311 
312 
GoToTag(SciDoc * sci,FXString & pattern)313 void TagHandler::GoToTag(SciDoc*sci, FXString &pattern)
314 {
315   if (pattern.empty()) { return; }
316   sci->sendMessage(SCI_SETTARGETSTART,0,0);
317   sci->sendMessage(SCI_SETTARGETEND,sci->sendMessage(SCI_GETTEXTLENGTH,0,0),0);
318   long oldflags=sci->sendMessage(SCI_GETSEARCHFLAGS,0,0);
319   sci->sendMessage(SCI_SETSEARCHFLAGS, SCFIND_REGEXP|SCFIND_POSIX|SCFIND_MATCHCASE, 0);
320   pattern.erase(0,1);
321   pattern.trunc(pattern.length()-1);
322   const char *esc_chars="*()";
323   const char *c;
324   for (c=esc_chars; *c; c++) {
325     char esc[3]="\\ ";
326     char orig[2]=" ";
327     esc[1]=*c;
328     orig[0]=*c;
329     if ( (pattern.find(*c)>=0) && (pattern.find(esc)==-1)) {
330       pattern.substitute(orig,esc,true);
331     }
332   }
333   long found=sci->sendString(SCI_SEARCHINTARGET,pattern.length(),pattern.text());
334   if (found>=0) { sci->GoToPos(found); }
335   sci->sendMessage(SCI_SETSEARCHFLAGS,oldflags,0);
336 }
337 
338 
339 
TagToTip(tagEntry * te,FXString & calltip)340 static void TagToTip(tagEntry *te, FXString&calltip)
341 {
342   const char*sig=tagsField(te,"signature");
343   if (sig && te->name) {
344     const char*acc=tagsField(te,"access");
345     if (acc && (strcmp(acc, "public")!=0)) { return; }
346     FXString tip=te->name;
347     tip.append(sig);
348     if (calltip.contains(tip)) { return; }
349     if (!calltip.empty()) { calltip.append("\n"); }
350     calltip.append(tip);
351   }
352 }
353 
354 
355 /*
356   Check to see if the file's contents begin with a valid "exuberant C tags" signature,
357   if so, return true. Otherwise assume the file is in our own "calltips" format,
358   and parse it for the tagname. If found, append its tip text to the calltip string.
359 */
360 
IsTagFile(const char * filename,const FXString & tagname,FXString & calltip)361 static bool IsTagFile(const char*filename,const FXString &tagname, FXString &calltip) {
362   FXint size=FXStat::size(filename);
363   if (size>0) {
364     FXFile fh(filename,FXIO::Reading);
365     if (fh.isOpen()) {
366       char buf[64];
367       memset(buf,0,sizeof(buf));
368       fh.readBlock(buf,sizeof(buf)-1);
369       if (strstr(buf,"!_TAG_FILE_FORMAT\t")==buf) {
370         fh.close();
371         return true;
372       } else {
373         FXString all;
374         all.length(size+1);
375         fh.position(0);
376         fh.readBlock(&all[0],size);
377         FXint pos;
378         if ( strncmp(all.text(), (tagname+"\n").text(), tagname.length()+1) == 0 )
379         {
380           pos=0;
381         } else {
382           pos=all.find("\n"+tagname+"\n");
383         }
384         if (pos>=0) {
385           FXint start=pos+1+tagname.length()+1;
386           if (pos==0) { start--; } // account for no newline before first file entry.
387           FXint stop=all.find("\n\n",start);
388 
389           calltip.append("\n"+all.mid(start,stop-start));
390         }
391       }
392       fh.close();
393     }
394   }
395   return false;
396 }
397 
398 
399 
ShowCallTip(SciDoc * sci,FXMenuCaption * tagfiles)400 void TagHandler::ShowCallTip(SciDoc*sci, FXMenuCaption* tagfiles)
401 {
402   FXString tagname;
403   FXString calltip="";
404   long pos=sci->GetCaretPos();
405   sci->GetSelText(tagname);
406   if (tagname.empty()) {
407     if (sci->sendMessage(SCI_GETCHARAT,pos-1, 0)=='(') {
408       long p=pos-2;
409       while ((p>0) && isspace(sci->sendMessage(SCI_GETCHARAT,p, 0))) {p--;}
410       sci->WordAtPos(tagname, p-1);
411     } else {
412       sci->WordAtPos(tagname);
413     }
414   }
415   if (!tagname.empty()) {
416     FXWindow*w;
417     tagEntry te;
418     for (w=tagfiles; w; w=w->getNext()) {
419       const char*filename=((FXMenuCommand*)w)->getText().text();
420       if (IsTagFile(filename,tagname,calltip)) {
421         tagFileInfo ti;
422         memset(&ti,0,sizeof(ti));
423         tagFile *tf=tagsOpen(filename, &ti);
424         int tagflags=(TAG_FULLMATCH|TAG_OBSERVECASE);
425         if (tf) {
426           if (ti.status.opened) {
427             if (tagsFind(tf, &te, tagname.text(), tagflags)==TagSuccess) {
428               TagToTip(&te,calltip);
429               while (tagsFindNext(tf,&te)==TagSuccess) {
430                 TagToTip(&te,calltip);
431               }
432             }
433           }
434           tagsClose(tf);
435         }
436       }
437 
438     }
439     if (!calltip.empty()) {
440       FXString wrapped="";
441       calltip.append('\n');
442       FXint nlines=calltip.contains('\n');
443       for (FXint iline=0; iline<nlines; iline++) {
444         FXString line=calltip.section('\n', iline);
445         if (line.length()>80) {
446           char sep=line.contains(',')?',':line.contains(';')?';':line.contains(' ')?' ':'\0';
447           FXint nseps=sep?line.contains(sep):0;
448           if (nseps) {
449             FXString str="";
450             FXString substr="";
451             for (FXint isep=0; isep<=nseps; isep++) {
452               FXString sect=line.section(sep,isep);
453               if (isep<nseps) {
454                 sect.append(sep);
455                 sect.append(' ');
456               }
457               if ( (substr.length()+sect.length()) < 80 ) {
458                 substr.append(sect);
459               } else {
460                 if (!str.empty()) { str.append("\n    "); }
461                 str.append(substr);
462                 substr=sect;
463               }
464             }
465             line=str+"\n    "+substr;
466           }
467         }
468         if (!wrapped.empty())wrapped.append('\n');
469         wrapped.append(line);
470       }
471       sci->sendString(SCI_CALLTIPSHOW, pos, wrapped.text());
472     }
473   }
474 }
475 
476 
477 
Parse(char startchar,const char * filename)478 void AutoCompleter::Parse(char startchar, const char*filename)
479 {
480   FXFile file(filename,FXIO::Reading);
481   if (file.isOpen()) {
482     char*lines=(char*)malloc(file.size()+1);
483     lines[file.size()]='\0';
484     if (file.readBlock(lines,file.size())==file.size()) {
485       if (strncmp(lines,"!_TAG_FILE_FORMAT\t",18)==0) {
486         char*p1=lines;
487         do {
488           char*p3=strchr(p1,'\n');
489           if (!p3) { p3=strchr(p1,'\0'); }
490           if (*p1==startchar) {
491             char*p2=strchr(p1,'\t');
492             if (p2&&p3&&(p3>p2)) {
493               *p2='\0';
494               insert(p1,NULL);
495             } else { break; }
496           }
497           if (*p3) { p1=p3+1; } else { break; }
498         } while (1);
499       } else {
500         char*p1=lines;
501         char*p3=strchr(p1,'\0');
502         while (Ascii::isSpace(*p1)) { p1++; }
503         while ((p3>p1)&&Ascii::isSpace(*(p3-1))) {
504           p3--;
505           *p3='\0';
506         }
507         if (strstr(p1,"\n\n")||strstr(p1,"\r\n\r\n")) {
508           // Blank lines are not allowed, it might be a calltips file, so skip it for now.
509         } else {
510           do {
511             char*p2=strchr(p1,'\n');
512             if (!p2) { p2=p3; }
513             if (*p1==startchar) {
514               *p2='\0';
515               insert(p1,NULL);
516             }
517             if (p2<p3) { p1=p2+1; } else { break; }
518           } while (1);
519         }
520       }
521     }
522     file.close();
523     ::free(lines);
524   }
525 }
526 
527 
528 
Show(SciDoc * sci)529 void AutoCompleter::Show(SciDoc*sci)
530 {
531   FXString part=FXString::null;
532   if (no()&&sci->PrefixAtPos(part)) {
533     FXint partlen=part.length();
534     FXint len=0; // save lots of reallocs by calculating overall length first
535     for (FXint i=0; i<TotalSlotsInDict(this); ++i) {
536       FXString ctag=key(i);
537       int taglen=ctag.length();
538       if ((taglen>partlen)&&(compare(part,ctag,partlen)==0)) {
539         ReplaceInDict(this,ctag.text(),(void*)((FXival)1)); // flag it for inclusion
540         len+=taglen+1; // count its length
541       }
542     }
543     if (len) {
544       FXString list=FXString::null;
545       list.length(len);
546       list.trunc(0);
547       for (FXint i=0; i<TotalSlotsInDict(this); ++i) {
548         if (data(i)) {
549           FXString ctag=key(i);
550           ReplaceInDict(this,ctag.text(),NULL); // reset our flag
551           list.append(ctag);
552           list.append(' ');
553         }
554       }
555       if (list.text()[list.length()-1]==' ') { list.trunc(list.length()-1); }
556       sci->sendString(SCI_AUTOCSHOW,part.length(),list.text());
557     }
558   }
559 }
560 
561 
562 
Start(SciDoc * sci,FXMenuCaption * tagfiles)563 void AutoCompleter::Start(SciDoc*sci, FXMenuCaption*tagfiles)
564 {
565   FXString part=FXString::null;
566   clear();
567   if (sci->PrefixAtPos(part)) {
568     for (FXWindow *w=tagfiles; w; w=w->getNext()) {
569       Parse(part[0],((FXMenuCommand*)w)->getText().text());
570     }
571     Show(sci);
572   }
573 }
574 
575 
576 
Continue(SciDoc * sci)577 bool AutoCompleter::Continue(SciDoc*sci)
578 {
579   if (sci->sendMessage(SCI_AUTOCACTIVE,0,0)) {
580     sci->sendMessage(SCI_AUTOCCANCEL,0,0);
581     Show(sci);
582     return true;
583   } else { return false; }
584 }
585 
586 
587 
~AutoCompleter()588 AutoCompleter::~AutoCompleter()
589 {
590   clear();
591 }
592 
593