1 /********************************************************************************
2 *                                                                               *
3 *                     T h e   A d i e   T e x t   E d i t o r                   *
4 *                                                                               *
5 *********************************************************************************
6 * Copyright (C) 1998,2021 by Jeroen van der Zijp.   All Rights Reserved.        *
7 *********************************************************************************
8 * This program is free software: you can redistribute it and/or modify          *
9 * it under the terms of the GNU General Public License as published by          *
10 * the Free Software Foundation, either version 3 of the License, or             *
11 * (at your option) any later version.                                           *
12 *                                                                               *
13 * This program is distributed in the hope that it will be useful,               *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of                *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                 *
16 * GNU General Public License for more details.                                  *
17 *                                                                               *
18 * You should have received a copy of the GNU General Public License             *
19 * along with this program.  If not, see <http://www.gnu.org/licenses/>.         *
20 ********************************************************************************/
21 #include "fx.h"
22 #include "fxkeys.h"
23 #include <signal.h>
24 #ifndef WIN32
25 #include <sys/wait.h>
26 #endif
27 #include "HelpWindow.h"
28 #include "Preferences.h"
29 #include "Commands.h"
30 #include "Modeline.h"
31 #include "Syntax.h"
32 #include "SyntaxParser.h"
33 #include "TextWindow.h"
34 #include "FindInFiles.h"
35 #include "Adie.h"
36 #include "icons.h"
37 
38 /*
39   Notes:
40   - One single collection of icons.
41   - Manage list of open windows.
42 */
43 
44 /*******************************************************************************/
45 
46 
47 // Map
48 FXDEFMAP(Adie) AdieMap[]={
49   FXMAPFUNC(SEL_SIGNAL,Adie::ID_HARVEST,Adie::onSigHarvest),
50   FXMAPFUNC(SEL_SIGNAL,Adie::ID_CLOSEALL,Adie::onCmdCloseAll),
51   FXMAPFUNC(SEL_COMMAND,Adie::ID_CLOSEALL,Adie::onCmdCloseAll),
52   FXMAPFUNC(SEL_COMMAND,Adie::ID_SYNTAXPATHS,Adie::onCmdSyntaxPaths),
53   FXMAPFUNC(SEL_UPDATE,Adie::ID_SYNTAXPATHS,Adie::onUpdSyntaxPaths),
54   };
55 
56 
57 // Object implementation
FXIMPLEMENT(Adie,FXApp,AdieMap,ARRAYNUMBER (AdieMap))58 FXIMPLEMENT(Adie,FXApp,AdieMap,ARRAYNUMBER(AdieMap))
59 
60 
61 // Make some windows
62 Adie::Adie(const FXString& name):FXApp(name){
63 
64   // Make some icons; these are shared between all text windows
65   bigicon=new FXGIFIcon(this,big_gif);
66   smallicon=new FXGIFIcon(this,small_gif);
67   newicon=new FXGIFIcon(this,new_gif,0,IMAGE_ALPHAGUESS);
68   reloadicon=new FXGIFIcon(this,reload_gif);
69   openicon=new FXGIFIcon(this,open_gif);
70   saveicon=new FXGIFIcon(this,save_gif);
71   saveasicon=new FXGIFIcon(this,saveas_gif,0,IMAGE_ALPHAGUESS);
72   savetoicon=new FXGIFIcon(this,saveto_gif,0,IMAGE_ALPHAGUESS);
73   printicon=new FXGIFIcon(this,print_gif);
74   cuticon=new FXGIFIcon(this,cut_gif);
75   copyicon=new FXGIFIcon(this,copy_gif);
76   pasteicon=new FXGIFIcon(this,paste_gif);
77   deleteicon=new FXGIFIcon(this,delete_gif);
78   undoicon=new FXGIFIcon(this,undo_gif);
79   redoicon=new FXGIFIcon(this,redo_gif);
80   fontsicon=new FXGIFIcon(this,fonts_gif);
81   helpicon=new FXGIFIcon(this,help_gif);
82   quiticon=new FXGIFIcon(this,quit_gif);
83   searchicon=new FXGIFIcon(this,search_gif,0,IMAGE_ALPHAGUESS);
84   replaceicon=new FXGIFIcon(this,replace_gif,0,IMAGE_ALPHAGUESS);
85   searchnexticon=new FXGIFIcon(this,searchnext_gif,0,IMAGE_ALPHAGUESS);
86   searchprevicon=new FXGIFIcon(this,searchprev_gif,0,IMAGE_ALPHAGUESS);
87   bookseticon=new FXGIFIcon(this,bookset_gif);
88   booknexticon=new FXGIFIcon(this,booknext_gif);
89   bookprevicon=new FXGIFIcon(this,bookprev_gif);
90   bookdelicon=new FXGIFIcon(this,bookdel_gif);
91   shiftlefticon=new FXGIFIcon(this,shiftleft_gif);
92   shiftrighticon=new FXGIFIcon(this,shiftright_gif);
93   configicon=new FXGIFIcon(this,config_gif);
94   browsericon=new FXGIFIcon(this,browser);
95   nobrowsericon=new FXGIFIcon(this,nobrowser);
96   loggericon=new FXGIFIcon(this,logger);
97   nologgericon=new FXGIFIcon(this,nologger);
98   uppercaseicon=new FXGIFIcon(this,uppercase);
99   lowercaseicon=new FXGIFIcon(this,lowercase);
100   backwardicon=new FXGIFIcon(this,backward_gif);
101   forwardicon=new FXGIFIcon(this,forward_gif);
102   shownicon=new FXGIFIcon(this,fileshown);
103   hiddenicon=new FXGIFIcon(this,filehidden);
104 
105 #ifndef DEBUG
106   // If interrupt happens, quit gracefully; we may want to
107   // save edit buffer contents w/o asking if display gets
108   // disconnected or if hangup signal is received.
109   addSignal(SIGINT,this,ID_CLOSEALL);
110 #ifndef WIN32
111   addSignal(SIGQUIT,this,ID_CLOSEALL);
112   addSignal(SIGHUP,this,ID_CLOSEALL);
113   addSignal(SIGPIPE,this,ID_CLOSEALL);
114 #endif
115 #endif
116 
117 #ifndef WIN32
118   // On unix, we need to catch SIGCHLD to harvest zombie child processes.
119   //addSignal(SIGCHLD,this,ID_HARVEST,true);
120 #endif
121 
122   // File associations, shared between all windows
123   associations=new FXFileAssociations(this);
124   }
125 
126 
127 // Close all windows
onCmdCloseAll(FXObject *,FXSelector,void *)128 long Adie::onCmdCloseAll(FXObject*,FXSelector,void*){
129   while(0<windowlist.no() && windowlist[0]->close(true)){}
130   return 1;
131   }
132 
133 
134 // Change syntax paths
onCmdSyntaxPaths(FXObject * sender,FXSelector,void *)135 long Adie::onCmdSyntaxPaths(FXObject* sender,FXSelector,void*){
136   sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_GETSTRINGVALUE),(void*)&syntaxpaths);
137   reg().writeStringEntry("SETTINGS","syntaxpaths",syntaxpaths.text());
138   return 1;
139   }
140 
141 
142 // Update syntax paths
onUpdSyntaxPaths(FXObject * sender,FXSelector,void *)143 long Adie::onUpdSyntaxPaths(FXObject* sender,FXSelector,void*){
144   sender->handle(this,FXSEL(SEL_COMMAND,FXWindow::ID_SETSTRINGVALUE),(void*)&syntaxpaths);
145   return 1;
146   }
147 
148 
149 // Harvest the zombies :-)
onSigHarvest(FXObject *,FXSelector,void *)150 long Adie::onSigHarvest(FXObject*,FXSelector,void*){
151   fxmessage("Harvesting...\n");
152 #ifndef WIN32
153   while(waitpid(-1,NULL,WNOHANG)>0){ }
154 #endif
155   return 1;
156   }
157 
158 
159 /*******************************************************************************/
160 
161 // Print command line help
printusage()162 static void printusage(){
163   fxmessage("Usage: adie [options] files...\n");
164   fxmessage("  options:\n");
165   fxmessage("  -?, -h, --help                      Print help.\n");
166   fxmessage("  -V, --version                       Print version number.\n");
167   fxmessage("  -v, --view                          Start in view-only mode.\n");
168   fxmessage("  -e, --edit                          Start in edit-mode.\n");
169   fxmessage("  -l NUM, --line NUM                  Jump cursor position to line number.\n");
170   fxmessage("  -c NUM, --col NUM                   Jump cursor position to column.\n");
171   fxmessage("  -S SYNTAXFILE, --syntax SYNTAXFILE  Load given syntax file.\n");
172   fxmessage("  -L LANGUAGE, --lang LANGUAGE        Force language mode.\n");
173   }
174 
175 
176 // Print verson info
printversion()177 static void printversion(){
178   fxmessage("A.d.i.e. - ADvanced Interactive Editor %d.%d.%d.\n",VERSION_MAJOR,VERSION_MINOR,VERSION_PATCH);
179   fxmessage("Copyright (C) 2000,2020 Jeroen van der Zijp.  All Rights Reserved.\n\n");
180   fxmessage("Please visit: http://www.fox-toolkit.org for further information.\n");
181   fxmessage("\n");
182   fxmessage("This program is free software: you can redistribute it and/or modify\n");
183   fxmessage("it under the terms of the GNU General Public License as published by\n");
184   fxmessage("the Free Software Foundation, either version 3 of the License, or\n");
185   fxmessage("(at your option) any later version.\n");
186   fxmessage("\n");
187   fxmessage("This program is distributed in the hope that it will be useful,\n");
188   fxmessage("but WITHOUT ANY WARRANTY; without even the implied warranty of\n");
189   fxmessage("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n");
190   fxmessage("GNU General Public License for more details.\n");
191   fxmessage("\n");
192   fxmessage("You should have received a copy of the GNU General Public License\n");
193   fxmessage("along with this program.  If not, see <http://www.gnu.org/licenses/>.\n");
194   }
195 
196 
197 // Start the application
start(int argc,char ** argv)198 FXint Adie::start(int argc,char** argv){
199   FXString    file,lang,execpath,iconpath,syntaxfile;
200   TextWindow *window=NULL;
201   Syntax     *syntax=NULL;
202   FXbool      edit=true;
203   FXint       line=0;
204   FXint       col=0;
205   FXint       arg=1;
206 
207   // The registry has been loaded after this
208   init(argc,argv);
209 
210   // Make a tool tip
211   new FXToolTip(this,0);
212 
213   // Create it
214   create();
215 
216   // Exec path is default for syntax path
217   execpath=FXSystem::getExecPath();
218 
219   FXTRACE((10,"execpath=%s\n",execpath.text()));
220 
221   // See if override paths are provided in the registry
222   syntaxpaths=reg().readStringEntry("SETTINGS","syntaxpaths",execpath.text());
223 
224   FXTRACE((10,"syntaxpaths=%s\n",syntaxpaths.text()));
225 
226   // Look for syntax file in the syntax paths
227   syntaxfile=FXPath::search(syntaxpaths,"Adie.stx");
228 
229   FXTRACE((10,"syntaxfile=%s\n",syntaxfile.text()));
230 
231   // Get icon search path
232   iconpath=reg().readStringEntry("SETTINGS","iconpath",FXIconCache::defaultIconPath);
233 
234   FXTRACE((10,"iconpath=%s\n",iconpath.text()));
235 
236   // Change icon search path
237   associations->setIconPath(iconpath);
238 
239   // Parse options first
240   while(arg<argc && argv[arg][0]=='-'){
241     if(compare(argv[arg],"-v")==0 || compare(argv[arg],"--view")==0){
242       edit=false;
243       }
244     else if(compare(argv[arg],"-e")==0 || compare(argv[arg],"--edit")==0){
245       edit=true;
246       }
247     else if(compare(argv[arg],"-?")==0 || compare(argv[arg],"-h")==0 || compare(argv[arg],"--help")==0){
248       printusage();
249       return 0;
250       }
251     else if(compare(argv[arg],"-V")==0 || compare(argv[arg],"--version")==0){
252       printversion();
253       return 0;
254       }
255     else if(compare(argv[arg],"-l")==0 || compare(argv[arg],"--line")==0){
256       if(++arg>=argc){ fxwarning("Adie: missing line number.\n"); return 1; }
257       sscanf(argv[arg],"%d",&line);
258       }
259     else if(compare(argv[arg],"-c")==0 || compare(argv[arg],"--col")==0){
260       if(++arg>=argc){ fxwarning("Adie: missing column number.\n"); return 1; }
261       sscanf(argv[arg],"%d",&col);
262       }
263     else if(compare(argv[arg],"-S")==0 || compare(argv[arg],"--syntax")==0){
264       if(++arg>=argc){ fxwarning("Adie: missing syntax file.\n"); return 1; }
265       syntaxfile=argv[arg];
266       }
267     else if(compare(argv[arg],"-L")==0 || compare(argv[arg],"--lang")==0){
268       if(++arg>=argc){ fxwarning("Adie: missing language mode.\n"); return 1; }
269       lang=argv[arg];
270       }
271     else{
272       fxwarning("Adie: unknown command line argument.\n");
273       return 1;
274       }
275     arg++;
276     }
277 
278   // Load syntax file
279   if(!syntaxfile.empty()){
280     if(!SyntaxParser::parseFile(syntaxes,syntaxfile)){
281       fxwarning("Adie: unable to parse syntax file: %s.\n",syntaxfile.text());
282       }
283     }
284 
285   // Get syntax
286   syntax=getSyntaxByName(lang);
287 
288   // Parse filenames
289   while(arg<argc){
290 
291     // Make new window
292     window=new TextWindow(this);
293     window->create();
294 
295     // Compute absolute path
296     file=FXPath::absolute(argv[arg]);
297 
298     // Start in directory with empty untitled file
299     if(FXStat::isDirectory(file)){
300       file=unique(file);
301       window->setFilename(file);
302       window->setFilenameSet(false);
303       window->setBrowserCurrentFile(file);
304       }
305 
306     // Start in directory with existing, accessible file
307     else if(FXStat::isFile(file) && window->loadFile(file)){
308       window->readBookmarks(file);
309       window->readView(file);
310       window->setEditable(edit);
311       window->determineSyntax();
312       window->parseModeline();
313       if(line) window->visitLine(line,col);
314       }
315 
316     // Start in directory with empty or inaccessible file
317     else{
318       window->setFilename(file);
319       window->setFilenameSet(false);    // Prompt for name when saving
320       window->determineSyntax();
321       window->setBrowserCurrentFile(file);
322       }
323 
324     // Override language mode?
325     if(syntax){
326       window->setSyntax(syntax);
327       }
328     arg++;
329     }
330 
331   // Start in current directory with empty untitled file
332   if(!window){
333 
334     // New window
335     window=new TextWindow(this);
336     window->create();
337 
338     // Compute absolute path
339     file=FXPath::absolute("untitled");
340     window->setFilename(file);
341     window->setFilenameSet(false);
342     window->setBrowserCurrentFile(file);
343 
344     // Override language mode?
345     if(syntax){
346       window->setSyntax(syntax);
347       }
348     }
349 
350   // Now run
351   return run();
352   }
353 
354 
355 // Generate unique name from given path
unique(const FXString & path) const356 FXString Adie::unique(const FXString& path) const {
357   FXString name="untitled";
358   FXString file;
359   for(FXint i=1; i<2147483647; i++){
360     file=FXPath::absolute(path,name);
361     if(!findWindow(file)) break;
362     name.format("untitled%d",i);
363     }
364   return file;
365   }
366 
367 
368 // Find an as yet untitled, unedited window
findUnused() const369 TextWindow *Adie::findUnused() const {
370   for(FXint w=0; w<windowlist.no(); w++){
371     if(!windowlist[w]->isFilenameSet() && !windowlist[w]->isModified()){
372       return windowlist[w];
373       }
374     }
375   return NULL;
376   }
377 
378 
379 // Find window, if any, currently editing the given file
findWindow(const FXString & file) const380 TextWindow* Adie::findWindow(const FXString& file) const {
381   for(FXint w=0; w<windowlist.no(); w++){
382     if(windowlist[w]->getFilename()==file){
383       return windowlist[w];
384       }
385     }
386   return NULL;
387   }
388 
389 
390 // Open file and jump to line, or just jump to line if already open
openFileWindow(const FXString & file,FXint lineno,FXint column)391 TextWindow* Adie::openFileWindow(const FXString& file,FXint lineno,FXint column){
392   TextWindow *window=NULL;
393 
394   FXTRACE((1,"Adie::openFileWindow(%s,%d,%d)\n",file.text(),lineno,column));
395 
396   // See if we already have this file
397   window=findWindow(file);
398   if(!window){
399 
400     // Create new one if no unused windows
401     window=findUnused();
402     if(!window){
403       window=new TextWindow(this);
404       window->create();
405       }
406 
407     // Load the file
408     if(window->loadFile(file)){
409       window->readBookmarks(file);
410       window->readView(file);
411       window->determineSyntax();
412       window->parseModeline();
413       }
414     }
415 
416   // Switch line number only
417   if(lineno){
418     window->visitLine(lineno,column);
419     }
420 
421   // Bring up the window
422   window->raise();
423   window->setFocus();
424   return window;
425   }
426 
427 
428 // Get syntax for language name
getSyntaxByName(const FXString & lang)429 Syntax* Adie::getSyntaxByName(const FXString& lang){
430   FXTRACE((10,"Adie::getSyntaxByName(%s)\n",lang.text()));
431   if(!lang.empty()){
432     for(FXint syn=0; syn<syntaxes.no(); syn++){
433       if(syntaxes[syn]->getName()==lang){
434         FXTRACE((10,"syntaxes[%d]: language: %s matched name: %s!\n",syn,syntaxes[syn]->getName().text(),lang.text()));
435         return syntaxes[syn];
436         }
437       }
438     }
439   return NULL;
440   }
441 
442 
443 // Get syntax by consulting registry
getSyntaxByRegistry(const FXString & file)444 Syntax* Adie::getSyntaxByRegistry(const FXString& file){
445   FXTRACE((10,"Adie::getSyntaxByRegistry(%s)\n",file.text()));
446   if(!file.empty()){
447     FXString name=FXPath::name(file);
448     FXString lang=reg().readStringEntry("SYNTAX",name);
449     return getSyntaxByName(lang);
450     }
451   return NULL;
452   }
453 
454 
455 // Get syntax by matching file patterns
getSyntaxByPattern(const FXString & file)456 Syntax* Adie::getSyntaxByPattern(const FXString& file){
457   FXTRACE((10,"Adie::getSyntaxByPattern(%s)\n",file.text()));
458   if(!file.empty()){
459     for(FXint syn=0; syn<syntaxes.no(); syn++){
460       if(syntaxes[syn]->matchFilename(file)){
461         FXTRACE((10,"syntaxes[%d]: language: %s matched file: %s!\n",syn,syntaxes[syn]->getName().text(),file.text()));
462         return syntaxes[syn];
463         }
464       }
465     }
466   return NULL;
467   }
468 
469 
470 // Get syntax by matching file contents
getSyntaxByContents(const FXString & contents)471 Syntax* Adie::getSyntaxByContents(const FXString& contents){
472   FXTRACE((10,"Adie::getSyntaxByContents(%s)\n",contents.text()));
473   if(!contents.empty()){
474     for(FXint syn=0; syn<syntaxes.no(); syn++){
475       if(syntaxes[syn]->matchContents(contents)){
476         FXTRACE((10,"syntaxes[%d]: language: %s matched contents: %s!\n",syn,syntaxes[syn]->getName().text(),contents.text()));
477         return syntaxes[syn];
478         }
479       }
480     }
481   return NULL;
482   }
483 
484 
485 /*******************************************************************************/
486 
487 // Clean up the mess
~Adie()488 Adie::~Adie(){
489   for(int i=0; i<syntaxes.no(); i++) delete syntaxes[i];
490   FXASSERT(windowlist.no()==0);
491   delete associations;
492   delete bigicon;
493   delete smallicon;
494   delete newicon;
495   delete reloadicon;
496   delete openicon;
497   delete saveicon;
498   delete saveasicon;
499   delete savetoicon;
500   delete printicon;
501   delete cuticon;
502   delete copyicon;
503   delete pasteicon;
504   delete deleteicon;
505   delete undoicon;
506   delete redoicon;
507   delete fontsicon;
508   delete helpicon;
509   delete quiticon;
510   delete searchicon;
511   delete replaceicon;
512   delete searchnexticon;
513   delete searchprevicon;
514   delete bookseticon;
515   delete booknexticon;
516   delete bookprevicon;
517   delete bookdelicon;
518   delete shiftlefticon;
519   delete shiftrighticon;
520   delete configicon;
521   delete browsericon;
522   delete nobrowsericon;
523   delete loggericon;
524   delete nologgericon;
525   delete uppercaseicon;
526   delete lowercaseicon;
527   delete backwardicon;
528   delete forwardicon;
529   delete shownicon;
530   delete hiddenicon;
531   }
532 
533