revert between 56095 -> 55830 in arch
[AROS.git] / workbench / tools / Edit / Jed.c
blob867955c70c1dd06b641af27d2dd1e7d46c024cf8
1 /**********************************************************
2 ** jed.c : An simple, fast and efficient text editor **
3 ** Written by T.Pierron and C.Guillaume. **
4 ** Started on august 1998. **
5 **-------------------------------------------------------**
6 ** Special requirements: WorkBench 2.0, v36 or above **
7 **********************************************************/
9 #include <intuition/intuition.h> /* Std types */
10 #include <libraries/gadtools.h> /* Menu events */
11 #include <dos/dos.h> /* Standard error codes */
12 #include <exec/memory.h> /* Memory allocation */
13 #include "Version.h"
14 #include "Jed.h"
15 #include "DiskIO.h"
16 #include "Events.h"
17 #include "Utility.h"
18 #include "Macros.h"
19 #include "Search.h"
20 #include "ProtoTypes.h"
22 #define DEBUG_STUFF /* Only activated if DEBUG macro is defined */
23 #include "Debug.h"
25 #define CATCOMP_ARRAY
26 #define CATCOMP_NUMBERS /* We will need the string id */
27 #include "strings.h"
29 /* Linked list of opened project */
30 Project edit = NULL;
31 ULONG sigbits=0, sigrcvd, sigport, err_time;
32 UBYTE clear=TRUE, BufTxt[256];
33 pfnSelectFunc move_selection;
35 /* Static string for version command */
36 const char Version[]=SVER;
38 /* Shared libraires we'll need to open */
39 struct IntuitionBase *IntuitionBase = NULL;
40 struct GfxBase *GfxBase = NULL;
41 struct Library *KeymapBase = NULL;
42 struct Library *GadToolsBase = NULL;
43 struct Library *AslBase = NULL;
44 struct LocaleBase *LocaleBase = NULL;
45 struct Library *DiskfontBase = NULL;
46 struct UtilityBase *UtilityBase = NULL;
47 struct Library *IFFParseBase = NULL;
49 struct IntuiMessage *msg, msgbuf; /* Used to collect events */
51 StartUpArgs args;
53 #if DEBUG
54 ULONG bmem, amem;
55 #endif
57 /*** MAIN LOOP ***/
58 int main(int argc, char *argv[])
60 #if DEBUG
61 bmem = AvailMem( MEMF_PUBLIC );
62 #endif
64 ParseArgs(&args, argc, argv);
66 /* Look first if Jano isn't already running */
67 if( find_janoed( &args ) ) cleanup(0, RETURN_OK);
69 /* Some global initialization */
70 init_searchtable();
72 /* Optionnal libraries */
73 AslBase = (struct Library *) OpenLibrary("asl.library", 36);
74 LocaleBase = (struct LocaleBase *) OpenLibrary("locale.library", 38);
75 DiskfontBase = (struct Library *) OpenLibrary("diskfont.library", 0);
76 IFFParseBase = (struct Library *) OpenLibrary("iffparse.library",36);
78 if(LocaleBase) InitLocale(); /* Localize the prog */
80 /* Open the required ROM libraries */
81 if( (IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 36)) &&
82 (GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 36)) &&
83 (GadToolsBase = (struct Library *) OpenLibrary("gadtools.library", 36)) &&
84 (KeymapBase = (struct Library *) OpenLibrary("keymap.library", 36)) &&
85 (UtilityBase = (struct UtilityBase *) OpenLibrary("utility.library", 36)) )
87 int lock;
88 init_macros();
89 lock = LockIBase(0);
90 set_default_prefs(&prefs, IntuitionBase->ActiveScreen);
91 UnlockIBase(lock);
93 load_prefs(&prefs, NULL); /* See if it exists a config file */
94 sigport = create_port();
96 /* Create whether an empty project or an existing one */
97 if( ( edit = create_projects(NULL, args.sa_ArgLst, args.sa_NbArgs) ) )
99 /* Open the main interface */
100 if(setup() == 0)
102 /* Makes edit project visible */
103 reshape_panel(edit);
104 active_project(edit,TRUE);
105 clear_brcorner();
107 dispatch_events();
109 } else cleanup(ErrMsg(ERR_NOGUI), RETURN_FAIL);
111 } else cleanup(ErrMsg(ERR_BADOS), RETURN_FAIL);
113 /* Hope that all were well... */
114 cleanup(0, RETURN_OK);
116 return 0;
119 /*** Deallocate ressources properly ***/
120 void cleanup(UBYTE *msg, int errcode)
122 CBClose();
123 close_port();
124 CloseMainWnd(1);
125 CleanupLocale();
126 free_macros();
127 free_diskio_alloc(); /* ASL */
128 if(args.sa_Free) FreeVec(args.sa_ArgLst);
129 if(DiskfontBase) CloseLibrary(DiskfontBase);
130 if(LocaleBase) CloseLibrary((struct Library *)LocaleBase);
131 if(AslBase) CloseLibrary(AslBase);
132 if(IFFParseBase) CloseLibrary(IFFParseBase);
133 if(UtilityBase) CloseLibrary((struct Library *)UtilityBase);
134 if(KeymapBase) CloseLibrary(KeymapBase);
135 if(GadToolsBase) CloseLibrary(GadToolsBase);
136 if(GfxBase) CloseLibrary((struct Library *)GfxBase);
137 if(IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
138 if(msg) puts(msg);
140 #if DEBUG
141 /* Here can be compared good programs with the others :-) */
142 amem = AvailMem( MEMF_PUBLIC );
143 if(amem < bmem) printf("Possible memory lost of %d bytes\n", bmem-amem);
144 #endif
145 exit(errcode);
148 /*** Handle events coming from main window: ***/
149 void dispatch_events()
151 extern ULONG sigmainwnd, swinsig;
152 extern UBYTE record;
153 BYTE scrolldisp=0, state=0, cnt=0, mark=0, quit = 0;
155 while( quit == 0 )
157 /* Active collect, when pressing arrow gadgets */
158 sigrcvd = (state==0 ? Wait(sigbits) : sigmainwnd);
160 /* if(sigrcvd & SIGBREAKF_CTRL_C) break;
162 else */ if(sigrcvd & sigport) { handle_port(); continue; }
164 else if(sigrcvd & swinsig) { handle_search(); continue; }
166 /* Collect messages posted to the window port */
167 while( ( msg = (struct IntuiMessage *) GetMsg(Wnd->UserPort) ) )
169 /* Copy the entire message into the buffer */
170 CopyMemQuick(msg, &msgbuf, sizeof(msgbuf));
171 ReplyMsg( (struct Message *) msg );
173 switch( msgbuf.Class )
175 case IDCMP_CLOSEWINDOW: handle_menu(112); break;
176 case IDCMP_RAWKEY:
177 handle_kbd(edit);
178 if(record) {
179 if(record == 1) reg_act_com(MAC_ACT_SHORTCUT, msgbuf.Code, msgbuf.Qualifier);
180 else record &= 0x7f;
182 break;
183 case IDCMP_INTUITICKS:
184 /* An error message which needs to be removed? */
185 if(err_time == 0) err_time = msgbuf.Seconds;
186 if(err_time && msgbuf.Seconds-err_time>4) StopError(Wnd);
187 break;
188 case IDCMP_MOUSEBUTTONS:
189 /* Click somewhere in the text */
190 switch( msgbuf.Code )
192 case SELECTDOWN:
193 /* Click over the project bar ? */
194 if(msgbuf.MouseY < gui.top)
196 edit = select_panel(edit, msgbuf.MouseX);
197 break;
200 click(edit, msgbuf.MouseX, msgbuf.MouseY, FALSE);
202 /* Shift-click to use columnar selection */
203 if( ( move_selection = SwitchSelect(edit, msgbuf.Qualifier & SHIFTKEYS ? 1:0, 1) ) )
204 mark=TRUE;
205 break;
206 case SELECTUP:
207 if(mark) unclick(edit);
208 mark=FALSE; scrolldisp=0; break;
210 break;
211 case IDCMP_NEWSIZE:
212 new_size(EDIT_ALL);
213 break;
214 case IDCMP_GADGETDOWN: /* Left scroll bar */
215 if(msgbuf.IAddress == (APTR) &Prop->down) state=1;
216 if(msgbuf.IAddress == (APTR) &Prop->up) state=2;
217 break;
218 case IDCMP_GADGETUP: /* Arrows or prop gadget */
219 state=0;
220 if(msgbuf.IAddress == (APTR) Prop)
221 scroll_disp(edit, FALSE), scrolldisp=0;
222 break;
223 case IDCMP_MOUSEMOVE:
224 if(mark) scrolldisp=2;
225 else
226 if(Prop->scroller.Flags & GFLG_SELECTED) scrolldisp=1;
227 break;
228 case IDCMP_MENUPICK:
229 { struct MenuItem * Item;
230 ULONG MenuId;
232 /* Multi-selection of menu entries */
233 while(msgbuf.Code != MENUNULL)
234 if( (Item = ItemAddress( Menu, msgbuf.Code )) )
236 /* stegerg: get NextSelect here in case menu action causes screen
237 to be closed/reopened in which case item becomes invalid.
238 Also assuming here that user in such case will not use
239 multiselection, ie. that nextselect will be MENUNULL.
241 If that's not the case it would mean more trouble and to protect
242 against that one would need to check if during handle_menu() the
243 screen has been closed/reopened and in that case break out of
244 the menu multiselection loop here. */
246 UWORD nextselect = Item->NextSelect;
248 MenuId = (IPTR)GTMENUITEM_USERDATA( Item );
249 handle_menu( MenuId );
251 if(record) reg_act_com(MAC_ACT_COM_MENU, MenuId, msgbuf.Qualifier);
252 else record &= 0x7f;
254 msgbuf.Code = nextselect;
259 /* Reduces the number of IDCMP mousemove messages to process */
260 if(scrolldisp==1) scroll_disp(edit, FALSE), scrolldisp=0;
261 if(scrolldisp==2) { scrolldisp=0; goto moveit; }
263 /* User may want to auto-scroll the display using arrow gadgets */
264 if(state && (mark || (((struct Gadget *)Prop)[state].Flags & GFLG_SELECTED))) {
265 /* Slow down animation: */
266 WaitTOF(); cnt++;
267 if(cnt>1) {
268 cnt=0;
269 if(autoscroll(edit,state==1 ? 1:-1)==0) state=0;
270 else if(mark) {
271 LONG x , y; moveit:
272 /* Adjust mouse position */
273 x = (msgbuf.MouseX-gui.left) / XSIZE;
274 y = (msgbuf.MouseY-gui.top) / YSIZE;
275 if(x < 0)
276 x = 0;
277 if(x >= gui.nbcol)
278 x = gui.nbcol-1;
279 if(y < 0)
280 y = -1;
281 if(y > gui.nbline)
282 y = gui.nbline;
283 edit->nbrwc = (x += edit->left_pos);
284 y += (LONG)edit->top_line;
285 if( x != edit->ccp.xc || y != edit->ccp.yc )
286 /* Move the selected stream */
287 if( !(state = move_selection(edit,x,y)) )
288 set_cursor_line(edit, y, edit->top_line),
289 inv_curs(edit,TRUE);
292 } /* endif: arrow gadget pressed or autoscroll */
296 /*** Refresh display, according to new window size ***/
297 void new_size(UBYTE Flags)
299 inv_curs(edit, FALSE);
300 adjust_win(Wnd,NbProject>1); /* Adjust internal variables */
301 SetABPenDrMd(RP, pen.fg, pen.bg, JAM2);
302 clear_brcorner();
303 prop_adj(edit);
304 edit->left_pos = curs_visible(edit, edit->top_line);
305 edit->xcurs = (edit->nbrc - edit->left_pos)*XSIZE + gui.left;
306 if(Flags & EDIT_GUI) reshape_panel(edit);
307 if(Flags & EDIT_AREA) redraw_content(edit,edit->show,gui.topcurs,gui.nbline);
308 inv_curs(edit,TRUE);
311 /*** Scroll display according to right prop gadget ***/
312 void scroll_disp(Project p, BOOL adjust)
314 ULONG pos = ((struct PropInfo *)((struct Gadget*)Prop)->SpecialInfo)->VertPot *
315 (p->max_lines - gui.nbline) / MAXPOT;
317 if(p->max_lines>gui.nbline && pos!=p->top_line)
319 if(p->ccp.select)
320 /* If selection mode is on, don't move cursor */
321 p->ycurs-=(pos-p->top_line)*YSIZE,
322 scroll_xy(p, p->left_pos, pos, adjust);
323 else
324 /* Be sure cursor is always in the edit area */
325 inv_curs(p,FALSE),
326 scroll_xy(p, curs_visible(p,pos), pos, adjust),
327 inv_curs(p,TRUE);
331 /*** Be sure that cursor is always visible ***/
332 LONG curs_visible(Project p, LONG newtop)
334 if(p->nbl < newtop) {
335 /* The cursor is above the top line */
336 p->ycurs = gui.topcurs;
337 p->nbl = newtop;
338 goto adj_edited;
339 } else if(p->nbl >= newtop+gui.nbline) {
340 register LONG nb;
341 register LINE *ln;
343 /* The cursor is below the bottom line */
344 p->nbl = newtop+gui.nbline-1;
345 p->ycurs = gui.botcurs;
347 adj_edited:
348 for(ln=p->the_line, nb=p->nbl; nb--; ln=ln->next);
349 if(ln) p->edited=ln;
350 draw_info(p);
352 } else
353 /* Is between the top and bottom line */
354 p->ycurs = (p->nbl-newtop) * YSIZE+ gui.topcurs;
356 /* Jump too far to the right? */
357 newtop=p->left_pos + gui.nbcol - 1;
358 if(p->nbrc>newtop) p->nbrwc=newtop;
359 /* Too far on the left */
360 if((newtop = p->nbrc<p->left_pos)) p->nbrwc=p->left_pos;
362 /* Adjust cursor pos */
363 newtop = adjust_rc(p->edited, p->nbrwc, &p->nbc, newtop);
364 if(p->nbrc != newtop)
365 p->nbrc = newtop, draw_info(p);
367 p->xcurs = (p->nbrc-p->left_pos)*XSIZE + gui.left;
368 return center_horiz(p);
371 /*** Center the display horizontally to show the cursor ***/
372 LONG center_horiz(Project p)
374 /* Return the new left position */
375 if(p->nbrc < p->left_pos)
376 if(p->nbrc > gui.xstep) return (LONG)(p->nbrc - gui.xstep);
377 else return 0;
378 else
379 if(p->nbrc >= p->left_pos+gui.nbcol)
380 return (LONG)(p->nbrc + gui.xstep - gui.nbcol + 1);
381 else return (LONG)p->left_pos;
384 /*** Center the display vertically to show the cursor ***/
385 LONG center_vert(Project p)
387 /* Return the new top position */
388 if(p->nbl < p->top_line || p->nbl >= p->top_line+gui.nbline)
390 LONG newtop=p->nbl - (gui.nbline>>1) + 4;
391 return newtop<0 ? 0:newtop;
393 return (LONG)p->top_line;
396 /*** Low-level text rendering at current rastport position ***/
397 void write_text(Project p, LINE *ln)
399 static UBYTE ts;
400 register UBYTE *str,*buf;
401 register LONG nb, nbc, size = ln->size;
402 static LONG stsel, ensel;
404 /* Look if line is partially selected */
405 if(ln->flags)
406 stsel = (ln->flags & FIRSTSEL ? p->ccp.startsel:0),
407 ensel = (ln->flags & LASTSEL ? p->ccp.endsel:0x7FFFFFFF);
408 else stsel=ensel=0x7FFFFFFF;
410 /* Find the first char to display */
411 for(str=ln->stream, nb=p->left_pos, nbc=0; nbc<nb; str++, size--)
412 if(*str=='\t') nbc+=(ts=tabstop(nbc));
413 else nbc++;
415 nbc-=nb; stsel-=nb; ensel-=nb;
416 if(nbc>=0 && size>=0)
418 /* Tricky case: line begins with an overlapping tabstop */
419 if(nbc>0) {
420 memset(BufTxt,' ',nbc);
421 if(stsel<nbc) stsel=(stsel<=nbc-ts ? 0:nbc);
422 if(ensel<nbc) ensel=nbc;
425 /* Copy the string to a temp buffer */
426 for(nb=size, buf=BufTxt+nbc; nbc<gui.nbcol && nb; str++, nb--)
427 if(*str=='\t') {
428 register UBYTE ts = tabstop(nbc+p->left_pos);
429 memset(buf,' ',ts);
430 nbc+=ts; buf+=ts;
431 if(stsel<nbc && stsel>nbc-ts) stsel=nbc;
432 if(ensel<nbc && ensel>nbc-ts) ensel=nbc;
433 } else *buf++ = *str, nbc++;
435 /* Overlapping tabulation ? */
436 if(nbc>=gui.nbcol) nbc=gui.nbcol;
437 else *buf=' ',nbc++;
439 /* Optimize rendering of unselected line */
440 if(ensel==stsel || stsel>nbc || ensel<0) Text(RP,BufTxt,nbc);
441 else {
442 buf=BufTxt; nb=stsel;
443 if(nb > 0) Text(RP,BufTxt,nb); else nb=0;
444 if(ensel>nbc) ensel=nbc;
445 SetABPenDrMd(RP,pen.fgfill,pen.bgfill,JAM2);
446 Text(RP,buf+nb,ensel-nb);
447 SetABPenDrMd(RP,pen.fg,pen.bg,JAM2);
448 if(ensel!=nbc) Text(RP,buf+ensel,nbc-ensel);
451 if(clear && gui.right>RP->cp_x) {
452 /* Clear the end of line, like ClearEOL(rp), but without overlapping borders :-( */
453 SetAPen(RP,pen.bg); nb=RP->cp_y-BASEL;
454 RectFill(RP,RP->cp_x,nb,gui.right,nb+YSIZE-1);
455 SetAPen(RP,pen.fg);
459 /*** Delta horizontal scroll, with boundary check ***/
460 void scroll_xdelta(Project p, LONG x)
462 if(x<0)
464 x=-x;
465 if(x>p->left_pos) x=0; else x=p->left_pos-x;
466 } else x+=p->left_pos;
467 /* Refresh only if different pos */
468 if(x!=p->left_pos)
470 if(!p->ccp.select) inv_curs(p,FALSE);
471 scroll_xy(p, x, p->top_line, FALSE);
472 /* Is cursor always visible? */
473 x=p->xcurs;
474 curs_visible(p, p->top_line);
475 if(x!=p->xcurs && p->ccp.select)
476 move_selection(p, p->nbrwc, p->nbl);
478 SetAPen(RP,pen.fg); inv_curs(p,TRUE);
482 /*** Delta vertical scroll, with boundary check ***/
483 void scroll_ydelta(Project p, LONG y)
485 LONG pos=p->top_line+y;
486 /* Clamp values to the boundary */
488 if(pos<0) pos=0;
489 #if 0
490 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
491 #else
492 if(pos>p->max_lines-1) pos=p->max_lines-1;
493 #endif
495 if(pos!=p->top_line)
497 if(!p->ccp.select) inv_curs(p,FALSE);
498 scroll_xy(p, curs_visible(p,pos), pos, TRUE);
499 if(p->ccp.select) move_selection(p, p->nbrwc, p->nbl);
500 inv_curs(p,TRUE);
504 /*** Like previous, but with required simplifications ***/
505 BOOL autoscroll(Project p, WORD y)
507 LONG pos=p->top_line+y;
508 /* Clamp values to the boundary */
509 #if 1
510 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
511 #else
512 if(pos>p->max_lines-1) pos=p->max_lines-1;
513 #endif
514 if(pos<0) pos=0;
516 if(pos!=p->top_line)
518 if(!p->ccp.select) inv_curs(p,FALSE);
519 scroll_xy(p, p->left_pos, pos, TRUE);
520 if(!p->ccp.select)
521 curs_visible(p, p->top_line),
522 inv_curs(p,TRUE);
524 return TRUE;
525 } else return FALSE;
528 /*** Redraw part of a display ***/
529 void redraw_content(Project p, LINE *ln, WORD startpos, WORD nb)
531 for(SetAPen(RP,pen.fg); nb-- && ln; startpos += YSIZE,ln = ln->next)
532 Move(RP, gui.left, startpos),
533 write_text(p, ln);
535 /* Empty lines visible? */
536 if(nb >= 0)
537 SetAPen(RP,pen.bg),
538 RectFill(RP,gui.left, startpos-BASEL, gui.right, gui.bottom),
539 SetAPen(RP,pen.fg);
542 /*** Scroll to a determinate absolute position ***/
543 void scroll_xy(Project p, LONG xp, LONG yp, BYTE adj)
545 LONG skipy = yp-p->top_line,
546 skipx = xp-p->left_pos, svg;
548 if(skipy<0) skipy = -skipy;
549 if(skipx<0) skipx = -skipx;
550 /* Can the process be optimized? */
551 if(skipy < gui.nbline && skipx < gui.nbcol)
553 /* Yes, don't redraw whole display */
554 WORD ystart,xstart; LINE *disp = p->show;
556 xstart = skipx * XSIZE;
557 ystart = skipy * YSIZE;
559 /* 1. Scroll the display */
560 ScrollRaster(RP, xp<p->left_pos? -xstart:xstart, yp<p->top_line? -ystart:ystart,
561 gui.left, gui.top, gui.rcurs-1, gui.bottom);
563 /* Only performs changes if required */
564 if(skipy)
566 /* 2. Update internal variables if vertical scroll */
567 if(yp < p->top_line)
569 /* Scroll display down */
570 register LONG nb=skipy;
571 for(; nb--; disp=disp->prev);
572 p->top_line -= skipy;
573 ystart = gui.topcurs;
574 p->show = disp;
575 } else {
576 /* Scroll display up */
577 register LONG nb=skipy;
578 p->top_line += skipy;
579 ystart = gui.botcurs - ystart + YSIZE;
580 for(; nb--; disp=disp->next);
581 p->show = disp;
583 for(nb=gui.nbline - skipy; disp && nb--; disp=disp->next);
587 /* Same fight */
588 if(skipx)
590 /* 3. Update variables if horizontal scroll */
591 svg = gui.nbcol;
592 gui.nbcol = skipx;
594 if(xp < p->left_pos)
596 /* Scroll display right */
597 p->xcurs += xstart;
598 clear = FALSE;
599 p->left_pos = xp;
600 redraw_content(p, p->show, gui.topcurs, gui.nbline);
601 clear = TRUE;
602 } else {
603 /* Scroll display left */
604 register LONG left=gui.left;
605 p->xcurs -= xstart;
606 gui.left = gui.rcurs-xstart;
607 p->left_pos += svg;
608 redraw_content(p, p->show, gui.topcurs, gui.nbline);
609 gui.left = left;
610 p->left_pos = xp;
612 gui.nbcol = svg;
614 /* 4. Redraw display (needs previous changes before) */
615 if(skipy) redraw_content(p, disp, ystart, skipy);
617 } else {
618 /* We've jump too far, whole redraw */
619 register LONG nb = yp-p->top_line;
620 register LINE *disp = p->show;
621 p->top_line += nb;
622 if(nb < 0) for(; nb++; disp=disp->prev);
623 else for(; nb--; disp=disp->next);
624 p->show = disp;
625 p->left_pos = xp;
626 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;
627 redraw_content(p, disp, gui.topcurs, gui.nbline);
629 /* Adjust position of the prop gadget */
630 if(adj) prop_adj(p);
633 /*** Scroll lines up, at a specified position (used for deleting) ***/
634 void scroll_up(Project p, LINE *ln, WORD ystart, LONG leftpos)
636 register WORD nbl;
637 /* If some of redrawn lines are still visible */
638 if( leftpos == p->left_pos )
639 /* Optimize lines to refresh */
640 nbl = p->top_line + gui.nbline - p->nbl - 1,
641 ystart += YSIZE;
642 else
643 /* Redraw whole content */
644 nbl = gui.nbline, ystart = gui.topcurs, ln=p->show,
645 p->left_pos = leftpos;
647 redraw_content(p, ln, ystart, nbl);
648 /* Adjust cursor position */
649 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;