Indentation fix, cleanup.
[AROS.git] / rom / filesys / console_handler / completion.c
blob108dfafa36099a9fd95b1b346a54cf19a371aeb1
1 /*
2 Copyright © 1995-2011, The AROS Development Team. All rights reserved.
3 $Id$
5 Desc:
6 Lang: english
7 */
9 #include <exec/memory.h>
10 #include <dos/dos.h>
11 #include <utility/utility.h>
12 #include <intuition/intuition.h>
13 #include <graphics/rastport.h>
14 #include <libraries/gadtools.h>
15 #include <libraries/asl.h>
16 #include <devices/rawkeycodes.h>
18 #include <proto/exec.h>
19 #include <proto/dos.h>
20 #include <proto/intuition.h>
21 #include <proto/graphics.h>
22 #include <proto/utility.h>
23 #include <proto/alib.h>
24 #include <proto/gadtools.h>
25 #include <proto/asl.h>
26 #include "con_handler_intern.h"
27 #include "support.h"
28 #include "completion.h"
30 #include <string.h>
31 #include <stdlib.h>
33 #define DEBUG 0
34 #include <aros/debug.h>
36 /****************************************************************************************/
38 struct matchnode
40 struct Node node;
41 UBYTE name[1];
44 /****************************************************************************************/
46 struct completioninfo
48 struct filehandle *fh;
49 struct List matchlist;
50 APTR pool;
51 WORD nummatchnodes;
52 WORD wordstart;
53 BOOL wordquoted;
54 UBYTE filepart[256];
55 UBYTE dirpart[256];
56 UBYTE pattern[256 * 2 + 3];
57 UBYTE match[256];
58 BOOL withinfo;
61 /****************************************************************************************/
63 /* Delay opening of the gadtools.library to the first time InitCompletion is called */
65 static struct completioninfo *InitCompletion(struct filehandle *fh, BOOL withinfo)
67 struct completioninfo *ci = NULL;
68 APTR pool;
70 if (fh->gtbase == NULL)
71 fh->gtbase = OpenLibrary("gadtools.library", 39);
73 if (fh->gfxbase == NULL)
74 fh->gfxbase = (APTR) OpenLibrary("graphics.library", 39);
76 if (fh->lastwritetask && GadToolsBase)
77 if (fh->lastwritetask->tc_Node.ln_Type == NT_PROCESS)
79 if ((pool = CreatePool(MEMF_CLEAR, 1024, 1024)))
81 BOOL ok = FALSE;
83 if ((ci = (struct completioninfo *) AllocPooled(pool, sizeof(*ci))))
85 ci->pool = pool;
86 ci->fh = fh;
87 ci->withinfo = withinfo;
88 NewList(&ci->matchlist);
90 ok = TRUE;
93 if (!ok)
95 DeletePool(pool);
96 ci = NULL;
101 return ci;
104 /****************************************************************************************/
106 static void CleanupCompletion(struct completioninfo *ci)
108 DeletePool(ci->pool);
111 /****************************************************************************************/
113 static void PrepareCompletion(struct filehandle *fh, struct completioninfo *ci)
115 WORD i;
116 BOOL in_quotes = FALSE;
118 /* Find word start */
120 ci->wordstart = i = ci->fh->inputstart;
122 while (i != ci->fh->inputpos)
124 switch (ci->fh->inputbuffer[i++])
126 case '"':
127 in_quotes = !in_quotes;
128 if (in_quotes)
129 ci->wordstart = i;
130 break;
132 case ' ':
133 case '>':
134 case '<':
135 if (!in_quotes)
136 ci->wordstart = i;
137 break;
141 strncpy(ci->dirpart, &ci->fh->inputbuffer[ci->wordstart], ci->fh->inputpos - ci->wordstart);
142 strcpy(ci->filepart, FilePart(ci->dirpart));
144 *(PathPart(ci->dirpart)) = '\0';
146 ci->wordquoted = in_quotes;
148 D(bug("PrepareCompletion: dirpart = \"%s\" filepart = \"%s\"\n", ci->dirpart, ci->filepart));
151 /****************************************************************************************/
153 static void AddQuotes(struct completioninfo *ci, STRPTR s, LONG s_size)
155 LONG len = strlen(s);
157 if (!ci->wordquoted)
159 if (len < s_size - 1)
160 memmove(s + 1, s, len + 1);
161 s[0] = '"';
163 else
165 len--;
168 if (len < s_size - 3)
170 if ((s[len] != '/') && (s[len] != ':'))
172 s[len + 1] = '"';
173 s[len + 2] = '\0';
178 /****************************************************************************************/
180 static void InsertIntoConBuffer(struct completioninfo *ci, STRPTR s)
182 WORD i = ci->fh->conbufferpos;
184 /* Lame code, but speed is no issue here */
185 while ((ci->fh->conbuffersize < CONSOLEBUFFER_SIZE) && *s)
187 memmove(&ci->fh->consolebuffer[i + 1], &ci->fh->consolebuffer[i], ci->fh->conbuffersize - i);
189 ci->fh->consolebuffer[i++] = *s++;
190 ci->fh->conbuffersize++;
194 /****************************************************************************************/
196 static void DoFileReq(struct filehandle *fh, struct completioninfo *ci)
198 struct Library *AslBase;
199 BPTR lock, olddir;
201 if ((lock = DupLock(((struct Process *) ci->fh->lastwritetask)->pr_CurrentDir)))
203 olddir = CurrentDir(lock);
205 if ((AslBase = OpenLibrary("asl.library", 36)))
207 struct FileRequester *fr;
208 struct TagItem tags[] =
210 { ASLFR_Window, (IPTR) ci->fh->window },
211 { ASLFR_DoPatterns, (IPTR) TRUE },
212 { ASLFR_InitialPattern, 0 },
213 { TAG_DONE } };
215 tags[2].ti_Data = (IPTR) (ci->withinfo ? "#?" : "~(#?.info)");
217 if ((fr = AllocAslRequest(ASL_FileRequest, tags)))
219 if (AslRequest(fr, NULL))
221 UBYTE c;
223 strcpy(ci->match, fr->fr_Drawer);
224 AddPart(ci->match, fr->fr_File, sizeof(ci->match));
226 if (ci->match[0])
228 if (strchr(ci->match, ' '))
229 AddQuotes(ci, ci->match, sizeof(ci->match));
231 c = ci->match[strlen(ci->match) - 1];
232 if ((c != '/') && (c != ':'))
234 strncat(ci->match, " ", sizeof(ci->match));
237 InsertIntoConBuffer(ci, ci->match);
241 FreeAslRequest(fr);
244 CloseLibrary(AslBase);
246 } /* if ((AslBase = OpenLibrary("asl.library", 36))) */
248 CurrentDir(olddir);
249 UnLock(lock);
253 /****************************************************************************************/
255 static BOOL PreparePattern(struct filehandle *fh, struct completioninfo *ci)
257 WORD parsecode;
259 parsecode = ParsePatternNoCase(ci->filepart, ci->pattern, sizeof(ci->pattern));
260 if (parsecode != -1)
262 if (parsecode == 0)
264 if (ci->withinfo)
266 strncat(ci->filepart, "#?", sizeof(ci->filepart));
268 else
270 strncat(ci->filepart, "~(#?.info)", sizeof(ci->filepart));
272 parsecode = ParsePatternNoCase(ci->filepart, ci->pattern, sizeof(ci->pattern));
276 return (parsecode == 1);
280 /****************************************************************************************/
282 static void AddMatchNode(struct filehandle *fh, struct completioninfo *ci, STRPTR name, WORD type)
284 struct matchnode *mn;
285 struct Node *prev, *check;
286 WORD size;
287 BOOL exists = FALSE;
289 size = strlen(name) + 1 + sizeof(struct matchnode) + ((type > 0) ? 1 : 0);
291 if ((mn = AllocPooled(ci->pool, size)))
293 strcpy(mn->name, name);
294 mn->node.ln_Name = mn->name;
296 if (type == 1)
297 strcat(mn->name, "/");
298 if (type == 2)
299 strcat(mn->name, ":");
301 /* Sort into matchlist */
303 prev = NULL;
304 ForeachNode(&ci->matchlist, check)
306 WORD match;
308 match = Stricmp(mn->name, ((struct matchnode *) check)->name);
309 if (match < 0)
310 break;
311 if (match == 0)
313 exists = TRUE;
314 break;
317 prev = check;
320 if (!exists)
322 Insert(&ci->matchlist, (struct Node *) mn, prev);
323 ci->nummatchnodes++;
325 else
327 FreePooled(ci->pool, mn, size);
332 /****************************************************************************************/
334 static void ScanDir(struct filehandle *fh, struct completioninfo *ci)
336 struct FileInfoBlock *fib;
337 BPTR lock;
339 if ((fib = AllocDosObject(DOS_FIB, 0)))
341 if ((lock = Lock(ci->dirpart, SHARED_LOCK)))
343 if (Examine(lock, fib))
345 while (ExNext(lock, fib))
347 if (MatchPatternNoCase(ci->pattern, fib->fib_FileName))
349 BOOL isdir = (fib->fib_DirEntryType > 0);
351 AddMatchNode(fh, ci, fib->fib_FileName, (isdir ? 1 : 0));
356 UnLock(lock);
359 FreeDosObject(DOS_FIB, fib);
363 /****************************************************************************************/
365 static void ScanVol(struct filehandle *fh, struct completioninfo *ci)
367 struct DosList *dlist;
369 dlist = LockDosList(LDF_READ | LDF_VOLUMES | LDF_DEVICES | LDF_ASSIGNS);
371 while ((dlist = NextDosEntry(dlist, LDF_VOLUMES | LDF_ASSIGNS | LDF_DEVICES)) != NULL)
373 STRPTR devname = AROS_BSTR_ADDR(dlist->dol_Name);
375 if (MatchPatternNoCase(ci->pattern, devname))
377 AddMatchNode(fh, ci, devname, 2);
381 UnLockDosList(LDF_READ | LDF_VOLUMES | LDF_DEVICES | LDF_ASSIGNS);
384 static void DoScan(struct filehandle *fh, struct completioninfo *ci)
386 BPTR lock, olddir;
388 if ((lock = DupLock(((struct Process *) ci->fh->lastwritetask)->pr_CurrentDir)))
390 olddir = CurrentDir(lock);
392 if (ci->dirpart[0] == 0)
393 ScanVol(fh, ci);
394 ScanDir(fh, ci);
396 CurrentDir(olddir);
397 UnLock(lock);
401 /****************************************************************************************/
403 #define BUTTON_EXTRA_WIDTH 16
404 #define BUTTON_EXTRA_HEIGHT 6
405 #define BORDER_X 4
406 #define BORDER_Y 4
407 #define BUTTON_SPACING_X 8
408 #define LV_BUTTON_SPACING_Y 4
409 #define LV_EXTRA_HEIGHT 4
411 #define ID_LISTVIEW 1
412 #define ID_OK 2
413 #define ID_CANCEL 3
415 /****************************************************************************************/
417 static BOOL DoChooseReq(struct filehandle *fh, struct completioninfo *ci)
419 static const char oktext[] = "Ok";
420 static const char canceltext[] = "Cancel";
421 static const char titletext[] = "Select filename";
423 struct RastPort temprp;
424 struct DrawInfo *dri;
425 struct Window *win;
426 struct Gadget *gadlist, *gad, *lvgad;
427 struct NewGadget ng;
428 APTR vi;
429 WORD i, buttonwidth, buttonheight, visible_lv_lines;
430 WORD winwidth, winheight, winleft, wintop;
431 LONG sec, micro, secold, microold;
432 BOOL retval = FALSE;
434 if ((dri = GetScreenDrawInfo(ci->fh->window->WScreen)))
436 if ((vi = GetVisualInfoA(ci->fh->window->WScreen, NULL)))
438 InitRastPort(&temprp);
439 SetFont(&temprp, dri->dri_Font);
441 buttonwidth = TextLength(&temprp, oktext, strlen(oktext));
442 i = TextLength(&temprp, canceltext, strlen(canceltext));
443 buttonwidth = (buttonwidth > i) ? buttonwidth : i;
444 buttonwidth += BUTTON_EXTRA_WIDTH;
446 buttonheight = dri->dri_Font->tf_YSize + BUTTON_EXTRA_HEIGHT;
448 i = ci->nummatchnodes > 15 ? 15 : ci->nummatchnodes;
449 if (i < 4)
450 i = 4;
452 winheight = i * (dri->dri_Font->tf_YSize + 1) + LV_EXTRA_HEIGHT + LV_BUTTON_SPACING_Y + buttonheight
453 + BORDER_Y * 2;
455 winwidth = buttonwidth * 2 + BUTTON_SPACING_X + BORDER_X * 2;
456 i = ci->fh->window->WScreen->Width * 1 / 3;
457 if (i > winwidth)
458 winwidth = i;
460 winleft = ci->fh->window->WScreen->MouseX
461 - (winwidth + ci->fh->window->WScreen->WBorLeft + ci->fh->window->WScreen->WBorRight) / 2;
462 wintop = ci->fh->window->WScreen->MouseY
463 - (winheight + ci->fh->window->WScreen->WBorTop + dri->dri_Font->tf_YSize + 1
464 + ci->fh->window->WScreen->WBorBottom) / 2;
466 gad = CreateContext(&gadlist);
468 ng.ng_LeftEdge = ci->fh->window->WScreen->WBorLeft + BORDER_X;
469 ng.ng_TopEdge = ci->fh->window->WScreen->WBorTop + dri->dri_Font->tf_YSize + 1 + BORDER_Y;
470 ng.ng_Width = winwidth - BORDER_X * 2;
471 ng.ng_Height = winheight - BORDER_Y * 2 - buttonheight - LV_BUTTON_SPACING_Y;
472 ng.ng_GadgetText = NULL;
473 ng.ng_TextAttr = NULL;
474 ng.ng_GadgetID = ID_LISTVIEW;
475 ng.ng_Flags = 0;
476 ng.ng_VisualInfo = vi;
477 ng.ng_UserData = 0;
480 struct TagItem lvtags[] =
482 { GTLV_Labels, (IPTR) &ci->matchlist },
483 { GTLV_ShowSelected, 0 },
484 { GTLV_Selected, 0 },
485 { TAG_DONE } };
487 gad = lvgad = CreateGadgetA(LISTVIEW_KIND, gad, &ng, lvtags);
489 visible_lv_lines = (ng.ng_Height - LV_EXTRA_HEIGHT) / (dri->dri_Font->tf_YSize + 1);
492 ng.ng_TopEdge += ng.ng_Height + LV_BUTTON_SPACING_Y;
493 ng.ng_Width = buttonwidth;
494 ng.ng_Height = buttonheight;
495 ng.ng_GadgetText = oktext;
496 ng.ng_GadgetID = ID_OK;
498 gad = CreateGadgetA(BUTTON_KIND, gad, &ng, NULL);
500 ng.ng_LeftEdge += winwidth - buttonwidth - BORDER_X * 2;
501 ng.ng_GadgetText = canceltext;
502 ng.ng_GadgetID = ID_CANCEL;
504 gad = CreateGadgetA(BUTTON_KIND, gad, &ng, NULL);
506 if (gad)
508 struct TagItem wintags[] =
510 { WA_CustomScreen, (IPTR) ci->fh->window->WScreen },
511 { WA_Title, (IPTR) titletext },
512 { WA_CloseGadget, TRUE },
513 { WA_DragBar, TRUE },
514 { WA_DepthGadget, TRUE },
515 { WA_Activate, TRUE },
516 { WA_AutoAdjust, TRUE },
517 { WA_Left, winleft },
518 { WA_Top, wintop },
519 { WA_InnerWidth, winwidth },
520 { WA_InnerHeight, winheight },
521 { WA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_RAWKEY | IDCMP_VANILLAKEY | LISTVIEWIDCMP | BUTTONIDCMP },
522 { WA_Gadgets, (IPTR) gadlist },
523 { TAG_DONE }
526 if ((win = OpenWindowTagList(NULL, wintags)))
528 BOOL done = FALSE;
529 BOOL doit = FALSE;
531 CurrentTime(&secold, &microold);
533 while (!done && !doit)
535 struct IntuiMessage *msg;
537 WaitPort(win->UserPort);
539 while ((msg = GT_GetIMsg(win->UserPort)))
541 switch (msg->Class)
543 case IDCMP_CLOSEWINDOW:
544 done = TRUE;
545 break;
547 case IDCMP_VANILLAKEY:
548 switch (msg->Code)
550 case 27:
551 done = TRUE;
552 break;
554 case 13:
555 doit = TRUE;
556 break;
558 break;
560 case IDCMP_RAWKEY:
562 WORD scroll = 0;
563 BOOL page = FALSE;
564 BOOL extreme = FALSE;
566 switch (msg->Code)
568 case RAWKEY_UP:
569 scroll = -1;
570 break;
572 case RAWKEY_DOWN:
573 scroll = 1;
574 break;
576 case RAWKEY_PAGEUP:
577 scroll = -1;
578 page = TRUE;
579 break;
581 case RAWKEY_PAGEDOWN:
582 scroll = 1;
583 page = TRUE;
584 break;
586 case RAWKEY_HOME:
587 scroll = -1;
588 extreme = TRUE;
589 break;
591 case RAWKEY_END:
592 scroll = 1;
593 extreme = TRUE;
594 break;
596 case RAWKEY_NM_WHEEL_UP:
597 scroll = -1;
598 break;
600 case RAWKEY_NM_WHEEL_DOWN:
601 scroll = 1;
602 break;
605 if (msg->Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
607 page = TRUE;
610 if (msg->Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT | IEQUALIFIER_CONTROL))
612 extreme = TRUE;
615 if (scroll)
617 IPTR getsel;
618 struct TagItem tags[] =
620 { GTLV_Selected, (IPTR) &getsel },
621 { TAG_IGNORE, TRUE },
622 { TAG_DONE } };
623 WORD sel;
625 GT_GetGadgetAttrsA(lvgad, win, NULL, tags);
626 sel = (WORD) getsel;
628 if (extreme)
630 scroll *= ci->nummatchnodes;
632 else if (page)
634 scroll *= visible_lv_lines;
637 sel += scroll;
638 if (sel < 0)
639 sel = ci->nummatchnodes - 1;
640 if (sel >= ci->nummatchnodes)
641 sel = 0;
643 tags[0].ti_Data = (IPTR) sel;
644 tags[1].ti_Tag = GTLV_MakeVisible;
645 tags[1].ti_Data = (IPTR) sel;
647 GT_SetGadgetAttrsA(lvgad, win, NULL, tags);
650 break;
652 case IDCMP_GADGETUP:
653 gad = (struct Gadget *) msg->IAddress;
655 switch (gad->GadgetID)
657 case ID_OK:
658 doit = TRUE;
659 break;
661 case ID_CANCEL:
662 done = TRUE;
663 break;
665 case ID_LISTVIEW:
666 CurrentTime(&sec, &micro);
667 if (DoubleClick(secold, microold, sec, micro))
668 doit = TRUE;
669 secold = sec;
670 microold = micro;
671 break;
673 break;
675 } /* switch(msg->Class) */
677 GT_ReplyIMsg(msg);
679 } /* while((msg = GT_GetIMsg(win->UserPort))) */
681 } /* while (!done && !doit) */
683 if (doit)
685 struct Node *node;
686 IPTR sel;
688 struct TagItem gettags[] =
690 { GTLV_Selected, (IPTR) &sel },
691 { TAG_DONE } };
693 GT_GetGadgetAttrsA(lvgad, win, NULL, gettags);
695 i = 0;
696 ForeachNode(&ci->matchlist, node)
698 if ((WORD) sel == i)
700 AddPart(ci->match, node->ln_Name, sizeof(ci->match));
702 retval = TRUE;
703 break;
705 i++;
708 } /* if (doit) */
710 CloseWindow(win);
712 } /* if ((win = OpenWindowTagList(NULL, wintags))) */
714 } /* if (gad) */
716 FreeGadgets(gadlist);
717 FreeVisualInfo(vi);
719 } /* if ((vi = GetVisualInfoA(ci->fh->window->WScreen, NULL))) */
721 FreeScreenDrawInfo(ci->fh->window->WScreen, dri);
723 } /* if ((dri = GetScreenDrawInfo(fh->window->WScreen))) */
725 return retval;
728 /****************************************************************************************/
730 void Completion(struct filehandle *fh, BOOL withinfo)
732 struct completioninfo *ci;
734 if ((ci = InitCompletion(fh, withinfo)))
736 PrepareCompletion(fh, ci);
738 if (!ci->dirpart[0] && !ci->filepart[0])
740 DoFileReq(fh, ci);
742 else
744 if (PreparePattern(fh, ci))
746 BOOL doprint = FALSE;
748 DoScan(fh, ci);
750 strncpy(ci->match, ci->dirpart, sizeof(ci->match));
752 if (ci->nummatchnodes == 1)
754 AddPart(ci->match, ((struct matchnode *) GetHead(&ci->matchlist))->name, sizeof(ci->match));
756 doprint = TRUE;
758 else if (ci->nummatchnodes > 1)
760 doprint = DoChooseReq(fh, ci);
763 if (doprint)
765 WORD backspaces;
766 UBYTE c;
768 if (strchr(ci->match, ' '))
769 AddQuotes(ci, ci->match, sizeof(ci->match));
771 /* Insert as many backspaces in front of the string,
772 to erase whole "word" first (starting at ci->wordstart)
773 before reprinting expanded filename */
775 backspaces = ci->fh->inputpos - ci->wordstart;
777 memmove(ci->match + backspaces, ci->match, sizeof(ci->match) - backspaces);
778 memset(ci->match, 8, backspaces);
780 c = ci->match[strlen(ci->match) - 1];
781 if ((c != '/') && (c != ':'))
783 strncat(ci->match, " ", sizeof(ci->match));
786 InsertIntoConBuffer(ci, ci->match);
792 CleanupCompletion(ci);
794 } /* if ((ci = InitCompletion())) */
798 /****************************************************************************************/