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