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