added concrete implementations of putc(), getc(), getchar() and gets()
[tangerine.git] / workbench / tools / Edit / Jed.c
blob2d65c697ac605393f632b3ff2c3b26007b0bc78f
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_NUMBERS /* We will need the string id */
26 #include "strings.h"
28 /* Linked list of opened project */
29 Project edit = NULL;
30 ULONG sigbits=0, sigrcvd, sigport, err_time;
31 UBYTE clear=TRUE, BufTxt[256];
32 pfnSelectFunc move_selection;
34 /* Static string for version command */
35 const char Version[]=SVER;
37 /* Shared libraires we'll need to open */
38 struct IntuitionBase *IntuitionBase = NULL;
39 struct GfxBase *GfxBase = NULL;
40 struct Library *KeymapBase = NULL;
41 struct Library *GadToolsBase = NULL;
42 struct Library *AslBase = NULL;
43 struct LocaleBase *LocaleBase = NULL;
44 struct Library *DiskfontBase = NULL;
45 struct UtilityBase *UtilityBase = NULL;
46 struct Library *IFFParseBase = NULL;
48 struct IntuiMessage *msg, msgbuf; /* Used to collect events */
50 StartUpArgs args;
52 #if DEBUG
53 ULONG bmem, amem;
54 #endif
56 /*** MAIN LOOP ***/
57 int main(int argc, char *argv[])
59 #if DEBUG
60 bmem = AvailMem( MEMF_PUBLIC );
61 #endif
63 ParseArgs(&args, argc, argv);
65 /* Look first if Jano isn't already running */
66 if( find_janoed( &args ) ) cleanup(0, RETURN_OK);
68 /* Some global initialization */
69 init_searchtable();
71 /* Optionnal libraries */
72 AslBase = (struct Library *) OpenLibrary("asl.library", 36);
73 LocaleBase = (struct LocaleBase *) OpenLibrary("locale.library", 38);
74 DiskfontBase = (struct Library *) OpenLibrary("diskfont.library", 0);
75 IFFParseBase = (struct Library *) OpenLibrary("iffparse.library",36);
77 if(LocaleBase) InitLocale(); /* Localize the prog */
79 /* Open the required ROM libraries */
80 if( (IntuitionBase = (struct IntuitionBase *) OpenLibrary("intuition.library", 36)) &&
81 (GfxBase = (struct GfxBase *) OpenLibrary("graphics.library", 36)) &&
82 (GadToolsBase = (struct Library *) OpenLibrary("gadtools.library", 36)) &&
83 (KeymapBase = (struct Library *) OpenLibrary("keymap.library", 36)) &&
84 (UtilityBase = (struct UtilityBase *) OpenLibrary("utility.library", 36)) )
86 init_macros();
87 set_default_prefs(&prefs, IntuitionBase->ActiveScreen);
89 load_prefs(&prefs, NULL); /* See if it exists a config file */
90 sigport = create_port();
92 /* Create whether an empty project or an existing one */
93 if( ( edit = create_projects(NULL, args.sa_ArgLst, args.sa_NbArgs) ) )
95 /* Open the main interface */
96 if(setup() == 0)
98 /* Makes edit project visible */
99 reshape_panel(edit);
100 active_project(edit,TRUE);
101 clear_brcorner();
103 dispatch_events();
105 } else cleanup(ErrMsg(ERR_NOGUI), RETURN_FAIL);
107 } else cleanup(ErrMsg(ERR_BADOS), RETURN_FAIL);
109 /* Hope that all were well... */
110 cleanup(0, RETURN_OK);
112 return 0;
115 /*** Deallocate ressources properly ***/
116 void cleanup(UBYTE *msg, int errcode)
118 CBClose();
119 close_port();
120 CloseMainWnd(1);
121 CleanupLocale();
122 free_macros();
123 free_diskio_alloc(); /* ASL */
124 if(args.sa_Free) FreeVec(args.sa_ArgLst);
125 if(DiskfontBase) CloseLibrary(DiskfontBase);
126 if(LocaleBase) CloseLibrary((struct Library *)LocaleBase);
127 if(AslBase) CloseLibrary(AslBase);
128 if(IFFParseBase) CloseLibrary(IFFParseBase);
129 if(UtilityBase) CloseLibrary((struct Library *)UtilityBase);
130 if(KeymapBase) CloseLibrary(KeymapBase);
131 if(GadToolsBase) CloseLibrary(GadToolsBase);
132 if(GfxBase) CloseLibrary((struct Library *)GfxBase);
133 if(IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
134 if(msg) puts(msg);
136 #if DEBUG
137 /* Here can be compared good programs with the others :-) */
138 amem = AvailMem( MEMF_PUBLIC );
139 if(amem < bmem) printf("Possible memory lost of %d bytes\n", bmem-amem);
140 #endif
141 exit(errcode);
144 /*** Handle events coming from main window: ***/
145 void dispatch_events()
147 extern ULONG sigmainwnd, swinsig;
148 extern UBYTE record;
149 BYTE scrolldisp=0, state=0, cnt=0, mark=0, quit = 0;
151 while( quit == 0 )
153 /* Active collect, when pressing arrow gadgets */
154 sigrcvd = (state==0 ? Wait(sigbits) : sigmainwnd);
156 /* if(sigrcvd & SIGBREAKF_CTRL_C) break;
158 else */ if(sigrcvd & sigport) { handle_port(); continue; }
160 else if(sigrcvd & swinsig) { handle_search(); continue; }
162 /* Collect messages posted to the window port */
163 while( ( msg = (struct IntuiMessage *) GetMsg(Wnd->UserPort) ) )
165 /* Copy the entire message into the buffer */
166 CopyMemQuick(msg, &msgbuf, sizeof(msgbuf));
167 ReplyMsg( (struct Message *) msg );
169 switch( msgbuf.Class )
171 case IDCMP_CLOSEWINDOW: handle_menu(112); break;
172 case IDCMP_RAWKEY:
173 handle_kbd(edit);
174 if(record) {
175 if(record == 1) reg_act_com(MAC_ACT_SHORTCUT, msgbuf.Code, msgbuf.Qualifier);
176 else record &= 0x7f;
178 break;
179 case IDCMP_INTUITICKS:
180 /* An error message which needs to be removed? */
181 if(err_time == 0) err_time = msgbuf.Seconds;
182 if(err_time && msgbuf.Seconds-err_time>4) StopError(Wnd);
183 break;
184 case IDCMP_MOUSEBUTTONS:
185 /* Click somewhere in the text */
186 switch( msgbuf.Code )
188 case SELECTDOWN:
189 /* Click over the project bar ? */
190 if(msgbuf.MouseY < gui.top)
192 edit = select_panel(edit, msgbuf.MouseX);
193 break;
196 click(edit, msgbuf.MouseX, msgbuf.MouseY, FALSE);
198 /* Shift-click to use columnar selection */
199 if( ( move_selection = SwitchSelect(edit, msgbuf.Qualifier & SHIFTKEYS ? 1:0, 1) ) )
200 mark=TRUE;
201 break;
202 case SELECTUP:
203 if(mark) unclick(edit);
204 mark=FALSE; scrolldisp=0; break;
206 break;
207 case IDCMP_NEWSIZE:
208 new_size(EDIT_ALL);
209 break;
210 case IDCMP_GADGETDOWN: /* Left scroll bar */
211 if(msgbuf.IAddress == (APTR) &Prop->down) state=1;
212 if(msgbuf.IAddress == (APTR) &Prop->up) state=2;
213 break;
214 case IDCMP_GADGETUP: /* Arrows or prop gadget */
215 state=0;
216 if(msgbuf.IAddress == (APTR) Prop)
217 scroll_disp(edit, FALSE), scrolldisp=0;
218 break;
219 case IDCMP_MOUSEMOVE:
220 if(mark) scrolldisp=2;
221 else
222 if(Prop->scroller.Flags & GFLG_SELECTED) scrolldisp=1;
223 break;
224 case IDCMP_MENUPICK:
225 { struct MenuItem * Item;
226 ULONG MenuId;
228 /* Multi-selection of menu entries */
229 while(msgbuf.Code != MENUNULL)
230 if( (Item = ItemAddress( Menu, msgbuf.Code )) )
232 MenuId = (ULONG)GTMENUITEM_USERDATA( Item );
233 handle_menu( MenuId );
235 if(record) reg_act_com(MAC_ACT_COM_MENU, MenuId, msgbuf.Qualifier);
236 else record &= 0x7f;
238 msgbuf.Code = Item->NextSelect;
243 /* Reduces the number of IDCMP mousemove messages to process */
244 if(scrolldisp==1) scroll_disp(edit, FALSE), scrolldisp=0;
245 if(scrolldisp==2) { scrolldisp=0; goto moveit; }
247 /* User may want to auto-scroll the display using arrow gadgets */
248 if(state && (mark || (((struct Gadget *)Prop)[state].Flags & GFLG_SELECTED))) {
249 /* Slow down animation: */
250 WaitTOF(); cnt++;
251 if(cnt>1) {
252 cnt=0;
253 if(autoscroll(edit,state==1 ? 1:-1)==0) state=0;
254 else if(mark) {
255 LONG x , y; moveit:
256 /* Adjust mouse position */
257 x = (msgbuf.MouseX-gui.left) / XSIZE;
258 y = (msgbuf.MouseY-gui.top) / YSIZE;
259 if(x < 0) x = 0; if(x >= gui.nbcol) x = gui.nbcol-1;
260 if(y < 0) y = -1; if(y > gui.nbline) y = gui.nbline;
261 edit->nbrwc = (x += edit->left_pos);
262 y += (LONG)edit->top_line;
263 if( x != edit->ccp.xc || y != edit->ccp.yc )
264 /* Move the selected stream */
265 if( !(state = move_selection(edit,x,y)) )
266 set_cursor_line(edit, y, edit->top_line),
267 inv_curs(edit,TRUE);
270 } /* endif: arrow gadget pressed or autoscroll */
274 /*** Refresh display, according to new window size ***/
275 void new_size(UBYTE Flags)
277 inv_curs(edit, FALSE);
278 adjust_win(Wnd,NbProject>1); /* Adjust internal variables */
279 SetABPenDrMd(RP, pen.fg, pen.bg, JAM2);
280 clear_brcorner();
281 prop_adj(edit);
282 edit->left_pos = curs_visible(edit, edit->top_line);
283 edit->xcurs = (edit->nbrc - edit->left_pos)*XSIZE + gui.left;
284 if(Flags & EDIT_GUI) reshape_panel(edit);
285 if(Flags & EDIT_AREA) redraw_content(edit,edit->show,gui.topcurs,gui.nbline);
286 inv_curs(edit,TRUE);
289 /*** Scroll display according to right prop gadget ***/
290 void scroll_disp(Project p, BOOL adjust)
292 ULONG pos = ((struct PropInfo *)((struct Gadget*)Prop)->SpecialInfo)->VertPot *
293 (p->max_lines - gui.nbline) / MAXPOT;
295 if(p->max_lines>gui.nbline && pos!=p->top_line)
297 if(p->ccp.select)
298 /* If selection mode is on, don't move cursor */
299 p->ycurs-=(pos-p->top_line)*YSIZE,
300 scroll_xy(p, p->left_pos, pos, adjust);
301 else
302 /* Be sure cursor is always in the edit area */
303 inv_curs(p,FALSE),
304 scroll_xy(p, curs_visible(p,pos), pos, adjust),
305 inv_curs(p,TRUE);
309 /*** Be sure that cursor is always visible ***/
310 LONG curs_visible(Project p, LONG newtop)
312 if(p->nbl < newtop) {
313 /* The cursor is above the top line */
314 p->ycurs = gui.topcurs;
315 p->nbl = newtop;
316 goto adj_edited;
317 } else if(p->nbl >= newtop+gui.nbline) {
318 register LONG nb;
319 register LINE *ln;
321 /* The cursor is below the bottom line */
322 p->nbl = newtop+gui.nbline-1;
323 p->ycurs = gui.botcurs;
325 adj_edited:
326 for(ln=p->the_line, nb=p->nbl; nb--; ln=ln->next);
327 if(ln) p->edited=ln;
328 draw_info(p);
330 } else
331 /* Is between the top and bottom line */
332 p->ycurs = (p->nbl-newtop) * YSIZE+ gui.topcurs;
334 /* Jump too far to the right? */
335 newtop=p->left_pos + gui.nbcol - 1;
336 if(p->nbrc>newtop) p->nbrwc=newtop;
337 /* Too far on the left */
338 if((newtop = p->nbrc<p->left_pos)) p->nbrwc=p->left_pos;
340 /* Adjust cursor pos */
341 newtop = adjust_rc(p->edited, p->nbrwc, &p->nbc, newtop);
342 if(p->nbrc != newtop)
343 p->nbrc = newtop, draw_info(p);
345 p->xcurs = (p->nbrc-p->left_pos)*XSIZE + gui.left;
346 return center_horiz(p);
349 /*** Center the display horizontally to show the cursor ***/
350 LONG center_horiz(Project p)
352 /* Return the new left position */
353 if(p->nbrc < p->left_pos)
354 if(p->nbrc > gui.xstep) return (LONG)(p->nbrc - gui.xstep);
355 else return 0;
356 else
357 if(p->nbrc >= p->left_pos+gui.nbcol)
358 return (LONG)(p->nbrc + gui.xstep - gui.nbcol + 1);
359 else return (LONG)p->left_pos;
362 /*** Center the display vertically to show the cursor ***/
363 LONG center_vert(Project p)
365 /* Return the new top position */
366 if(p->nbl < p->top_line || p->nbl >= p->top_line+gui.nbline)
368 LONG newtop=p->nbl - (gui.nbline>>1) + 4;
369 return newtop<0 ? 0:newtop;
371 return (LONG)p->top_line;
374 /*** Low-level text rendering at current rastport position ***/
375 void write_text(Project p, LINE *ln)
377 static UBYTE ts;
378 register UBYTE *str,*buf;
379 register LONG nb, nbc, size = ln->size;
380 static LONG stsel, ensel;
382 /* Look if line is partially selected */
383 if(ln->flags)
384 stsel = (ln->flags & FIRSTSEL ? p->ccp.startsel:0),
385 ensel = (ln->flags & LASTSEL ? p->ccp.endsel:0x7FFFFFFF);
386 else stsel=ensel=0x7FFFFFFF;
388 /* Find the first char to display */
389 for(str=ln->stream, nb=p->left_pos, nbc=0; nbc<nb; str++, size--)
390 if(*str=='\t') nbc+=(ts=tabstop(nbc));
391 else nbc++;
393 nbc-=nb; stsel-=nb; ensel-=nb;
394 if(nbc>=0 && size>=0)
396 /* Tricky case: line begins with an overlapping tabstop */
397 if(nbc>0) {
398 memset(BufTxt,' ',nbc);
399 if(stsel<nbc) stsel=(stsel<=nbc-ts ? 0:nbc);
400 if(ensel<nbc) ensel=nbc;
403 /* Copy the string to a temp buffer */
404 for(nb=size, buf=BufTxt+nbc; nbc<gui.nbcol && nb; str++, nb--)
405 if(*str=='\t') {
406 register UBYTE ts = tabstop(nbc+p->left_pos);
407 memset(buf,' ',ts);
408 nbc+=ts; buf+=ts;
409 if(stsel<nbc && stsel>nbc-ts) stsel=nbc;
410 if(ensel<nbc && ensel>nbc-ts) ensel=nbc;
411 } else *buf++ = *str, nbc++;
413 /* Overlapping tabulation ? */
414 if(nbc>=gui.nbcol) nbc=gui.nbcol;
415 else *buf=' ',nbc++;
417 /* Optimize rendering of unselected line */
418 if(ensel==stsel || stsel>nbc || ensel<0) Text(RP,BufTxt,nbc);
419 else {
420 buf=BufTxt; nb=stsel;
421 if(nb > 0) Text(RP,BufTxt,nb); else nb=0;
422 if(ensel>nbc) ensel=nbc;
423 SetABPenDrMd(RP,pen.fgfill,pen.bgfill,JAM2);
424 Text(RP,buf+nb,ensel-nb);
425 SetABPenDrMd(RP,pen.fg,pen.bg,JAM2);
426 if(ensel!=nbc) Text(RP,buf+ensel,nbc-ensel);
429 if(clear && gui.right>RP->cp_x) {
430 /* Clear the end of line, like ClearEOL(rp), but without overlapping borders :-( */
431 SetAPen(RP,pen.bg); nb=RP->cp_y-BASEL;
432 RectFill(RP,RP->cp_x,nb,gui.right,nb+YSIZE-1);
433 SetAPen(RP,pen.fg);
437 /*** Delta horizontal scroll, with boundary check ***/
438 void scroll_xdelta(Project p, LONG x)
440 if(x<0)
442 x=-x;
443 if(x>p->left_pos) x=0; else x=p->left_pos-x;
444 } else x+=p->left_pos;
445 /* Refresh only if different pos */
446 if(x!=p->left_pos)
448 if(!p->ccp.select) inv_curs(p,FALSE);
449 scroll_xy(p, x, p->top_line, FALSE);
450 /* Is cursor always visible? */
451 x=p->xcurs;
452 curs_visible(p, p->top_line);
453 if(x!=p->xcurs && p->ccp.select)
454 move_selection(p, p->nbrwc, p->nbl);
456 SetAPen(RP,pen.fg); inv_curs(p,TRUE);
460 /*** Delta vertical scroll, with boundary check ***/
461 void scroll_ydelta(Project p, LONG y)
463 LONG pos=p->top_line+y;
464 /* Clamp values to the boundary */
466 if(pos<0) pos=0;
467 #if 0
468 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
469 #else
470 if(pos>p->max_lines-1) pos=p->max_lines-1;
471 #endif
473 if(pos!=p->top_line)
475 if(!p->ccp.select) inv_curs(p,FALSE);
476 scroll_xy(p, curs_visible(p,pos), pos, TRUE);
477 if(p->ccp.select) move_selection(p, p->nbrwc, p->nbl);
478 inv_curs(p,TRUE);
482 /*** Like previous, but with required simplifications ***/
483 BOOL autoscroll(Project p, WORD y)
485 LONG pos=p->top_line+y;
486 /* Clamp values to the boundary */
487 #if 1
488 if(pos>(LONG)p->max_lines-(LONG)gui.nbline) pos=p->max_lines-gui.nbline;
489 #else
490 if(pos>p->max_lines-1) pos=p->max_lines-1;
491 #endif
492 if(pos<0) pos=0;
494 if(pos!=p->top_line)
496 if(!p->ccp.select) inv_curs(p,FALSE);
497 scroll_xy(p, p->left_pos, pos, TRUE);
498 if(!p->ccp.select)
499 curs_visible(p, p->top_line),
500 inv_curs(p,TRUE);
502 return TRUE;
503 } else return FALSE;
506 /*** Redraw part of a display ***/
507 void redraw_content(Project p, LINE *ln, WORD startpos, WORD nb)
509 for(SetAPen(RP,pen.fg); nb-- && ln; startpos += YSIZE,ln = ln->next)
510 Move(RP, gui.left, startpos),
511 write_text(p, ln);
513 /* Empty lines visible? */
514 if(nb >= 0)
515 SetAPen(RP,pen.bg),
516 RectFill(RP,gui.left, startpos-BASEL, gui.right, gui.bottom),
517 SetAPen(RP,pen.fg);
520 /*** Scroll to a determinate absolute position ***/
521 void scroll_xy(Project p, LONG xp, LONG yp, BYTE adj)
523 LONG skipy = yp-p->top_line,
524 skipx = xp-p->left_pos, svg;
526 if(skipy<0) skipy = -skipy;
527 if(skipx<0) skipx = -skipx;
528 /* Can the process be optimized? */
529 if(skipy < gui.nbline && skipx < gui.nbcol)
531 /* Yes, don't redraw whole display */
532 WORD ystart,xstart; LINE *disp = p->show;
534 xstart = skipx * XSIZE;
535 ystart = skipy * YSIZE;
537 /* 1. Scroll the display */
538 ScrollRaster(RP, xp<p->left_pos? -xstart:xstart, yp<p->top_line? -ystart:ystart,
539 gui.left, gui.top, gui.rcurs-1, gui.bottom);
541 /* Only performs changes if required */
542 if(skipy)
544 /* 2. Update internal variables if vertical scroll */
545 if(yp < p->top_line)
547 /* Scroll display down */
548 register LONG nb=skipy;
549 for(; nb--; disp=disp->prev);
550 p->top_line -= skipy;
551 ystart = gui.topcurs;
552 p->show = disp;
553 } else {
554 /* Scroll display up */
555 register LONG nb=skipy;
556 p->top_line += skipy;
557 ystart = gui.botcurs - ystart + YSIZE;
558 for(; nb--; disp=disp->next);
559 p->show = disp;
561 for(nb=gui.nbline - skipy; disp && nb--; disp=disp->next);
565 /* Same fight */
566 if(skipx)
568 /* 3. Update variables if horizontal scroll */
569 svg = gui.nbcol;
570 gui.nbcol = skipx;
572 if(xp < p->left_pos)
574 /* Scroll display right */
575 p->xcurs += xstart;
576 clear = FALSE;
577 p->left_pos = xp;
578 redraw_content(p, p->show, gui.topcurs, gui.nbline);
579 clear = TRUE;
580 } else {
581 /* Scroll display left */
582 register LONG left=gui.left;
583 p->xcurs -= xstart;
584 gui.left = gui.rcurs-xstart;
585 p->left_pos += svg;
586 redraw_content(p, p->show, gui.topcurs, gui.nbline);
587 gui.left = left;
588 p->left_pos = xp;
590 gui.nbcol = svg;
592 /* 4. Redraw display (needs previous changes before) */
593 if(skipy) redraw_content(p, disp, ystart, skipy);
595 } else {
596 /* We've jump too far, whole redraw */
597 register LONG nb = yp-p->top_line;
598 register LINE *disp = p->show;
599 p->top_line += nb;
600 if(nb < 0) for(; nb++; disp=disp->prev);
601 else for(; nb--; disp=disp->next);
602 p->show = disp;
603 p->left_pos = xp;
604 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;
605 redraw_content(p, disp, gui.topcurs, gui.nbline);
607 /* Adjust position of the prop gadget */
608 if(adj) prop_adj(p);
611 /*** Scroll lines up, at a specified position (used for deleting) ***/
612 void scroll_up(Project p, LINE *ln, WORD ystart, LONG leftpos)
614 register WORD nbl;
615 /* If some of redrawn lines are still visible */
616 if( leftpos == p->left_pos )
617 /* Optimize lines to refresh */
618 nbl = p->top_line + gui.nbline - p->nbl - 1,
619 ystart += YSIZE;
620 else
621 /* Redraw whole content */
622 nbl = gui.nbline, ystart = gui.topcurs, ln=p->show,
623 p->left_pos = leftpos;
625 redraw_content(p, ln, ystart, nbl);
626 /* Adjust cursor position */
627 p->xcurs = (p->nbrc-p->left_pos) * XSIZE + gui.left;