/* WGets.c */ #include "Sys.h" #include "Util.h" #include "Curses.h" #include "Complete.h" #ifdef USE_CURSES /* The only reason we need to include this junk, is because on some systems * the function killchar() is actually a macro that uses definitions in * termios.h. Example: #define killchar() (__baset.c_cc[VKILL]) */ #ifdef HAVE_TERMIOS_H # include #else # ifdef HAVE_TERMIO_H # include # else # ifdef HAVE_SYS_IOCTL_H # include /* For TIOxxxx constants. */ # endif # include # endif #endif /* !HAVE_TERMIOS_H */ #include #include "Strn.h" #include "LineList.h" #include "WGets.h" /* Pointer to current position in the buffer. */ static char *gBufPtr; /* When we draw the display window in the buffer, this is the first * character to draw, at the far left. * * The "display window" is the current area being edited by the user. * For example, you specify that you can only use 10 screen columns to * input a string that can have a maximum size of 30 characters. * * Let's say the current buffer has "abcdefghijklmnopqrstuvw" in it. * abcdefghijklmnopqrstuvw * ^ ^ ^ * s c e * * The "display window" for this example would be "fghijklmno" * (gWinStartPtr) points to "f" and (gWinEndPtr) points to "o". * (gBufPtr) points to the current character under the cursor, so * the letter "i" would be the hilited/blinking cursor. * * This display window allows you to set aside a certain amount of screen * area for editing, but allowing for longer input strings. The display * window will scroll as needed. */ static char *gWinStartPtr; /* This would be the last character drawn in the display window. */ static char *gWinEndPtr; /* Number of characters in the buffer. */ static size_t gBufLen; /* If true, the display window needs to be redrawn. */ static int gNeedUpdate; /* The curses library window we are doing the editing in. This window * is different from what I call the "display window." The display * window is a subregion of the curses window, and does not have to have * a separate WINDOW pointer just for the editing. */ static WINDOW *gW; /* The column and row where the display window starts. */ static int gSy, gSx; /* This is the buffer for the characters typed. */ static char *gDst; /* This is the length of the display window on screen. It should <= * the size of the buffer itself. */ static int gWindowWidth; /* This is the size of the buffer. */ static size_t gDstSize; /* This flag tells whether we are allowed to use the contents of the buffer * passed by the caller, and whether the contents had length 1 or more. */ static int gHadStartingString; /* This is a flag to tell if the user did any editing. If any characters * are added or deleted, this flag will be set. If the user just used the * arrow keys to move around and/or just hit return, it will be false. */ static int gChanged; /* This is a flag to tell if we have moved at all on the line before * hitting return. This is mostly used for ^D handling. We want ^D to * return EOF if they hit right it away on a new line. */ static int gMoved; /* We have the flexibility with respect to echoing characters. We can just * echo the same character we read back to the screen like normal, always * echo "bullets," or not echo at all. */ static int gEchoMode; /* You can specify that the routine maintain a history buffer. If so, then * the user can use the arrow keys to move up and down through the history * to edit previous lines. */ static LineListPtr gHistory; /* This is a pointer to the line in the history that is being used as a copy * for editing. */ static LinePtr gCurHistLine; static void wg_SetCursorPos(char *newPos) { if (newPos > gWinEndPtr) { /* Shift window right. * (Text will appear to shift to the left.) */ gWinStartPtr = newPos; if (gWindowWidth > 7) gWinStartPtr -= gWindowWidth * 2 / 10; else if (gWindowWidth > 1) gWinStartPtr -= 1; gBufPtr = newPos; gWinEndPtr = gWinStartPtr + gWindowWidth - 1; } else if (newPos < gWinStartPtr) { /* Shift window left. * (Text will appear to shift to the right.) */ gWinStartPtr = newPos; if (gWindowWidth > 7) gWinStartPtr -= gWindowWidth * 2 / 10; else if (gWindowWidth > 1) gWinStartPtr -= 1; if (gWinStartPtr < gDst) gWinStartPtr = gDst; gBufPtr = newPos; gWinEndPtr = gWinStartPtr + gWindowWidth - 1; } else { /* Can just move cursor without shifting window. */ gBufPtr = newPos; } } /* wg_SetCursorPos */ static void wg_AddCh(int c) { size_t n; char *limit; if (gBufLen < gDstSize) { limit = gDst + gBufLen; if (gBufPtr == limit) { /* Just add a character to the end. No need to do * a memory move for this. */ *gBufPtr = c; gBufLen++; wg_SetCursorPos(gBufPtr + 1); } else { /* Have to move characters after the cursor over one * position so we can insert a character. */ n = limit - gBufPtr; MEMMOVE(gBufPtr + 1, gBufPtr, n); *gBufPtr = c; gBufLen++; wg_SetCursorPos(gBufPtr + 1); } gNeedUpdate = 1; gChanged = 1; } else { beep(); } } /* wg_AddCh */ static void wg_KillCh(int count) { size_t n; char *limit; if (count > gBufPtr - gDst) count = gBufPtr - gDst; if (count) { limit = gDst + gBufLen; if (gBufPtr != limit) { /* Delete the characters before the character under the * cursor, and move everything after it back one. */ n = limit - gBufPtr; memcpy(gBufPtr - count, gBufPtr, n); } gBufLen -= count; wg_SetCursorPos(gBufPtr - count); /* Does a --gBufPtr. */ gNeedUpdate = 1; gChanged = 1; } else { beep(); } } /* wg_KillCh */ static int IsWordChar(char c) { return !isspace(c) && c != '/'; } static void wg_KillWord(void) { int count; int off = gBufPtr - gDst - 1; count = off; /* Find the end of the previous word */ while (off >= 0 && !IsWordChar(gDst[off])) off--; /* Find the start of the word */ while (off >= 0 && IsWordChar(gDst[off])) off--; count = count - off; wg_KillCh(count); } /* wg_KillWord */ static void wg_ForwardKillCh(void) { size_t n; char *limit; if (gBufLen > 0) { limit = gDst + gBufLen; if (gBufPtr == limit) { /* Nothing in front to delete. */ beep(); } else { n = limit - gBufPtr - 1; memcpy(gBufPtr, gBufPtr + 1, n); --gBufLen; gNeedUpdate = 1; gChanged = 1; } } else { beep(); } } /* wg_ForwardKillCh */ static void wg_GoLeft(void) { if (gBufPtr > gDst) { wg_SetCursorPos(gBufPtr - 1); /* Does a --gBufPtr. */ gNeedUpdate = 1; gMoved = 1; } else { beep(); } } /* wg_GoLeft */ static void wg_GoRight(void) { if (gBufPtr < (gDst + gBufLen)) { wg_SetCursorPos(gBufPtr + 1); /* Does a ++gBufPtr. */ gNeedUpdate = 1; gMoved = 1; } else { beep(); } } /* wg_GoRight */ static void wg_GoLineStart(void) { wg_SetCursorPos(gDst); gNeedUpdate = 1; gMoved = 1; } /* wg_GoLineStart */ static void wg_GoLineEnd(void) { wg_SetCursorPos(gDst + gBufLen); gNeedUpdate = 1; gMoved = 1; } /* wg_GoLineEnd */ static void wg_LineKill(void) { gBufPtr = gDst; gWinStartPtr = gBufPtr; gWinEndPtr = gWinStartPtr + gWindowWidth - 1; gBufPtr[gDstSize] = '\0'; gBufLen = 0; gNeedUpdate = 1; /* Reset this so it acts as a new line. We want them to be able to * hit ^D until they do something with this line. */ gMoved = 0; /* We now have an empty string. If we originally had something in the * buffer, then mark it as changed since we just erased that. */ gChanged = gHadStartingString; } /* wg_LineKill */ static void wg_HistoryUp(void) { if (gHistory == wg_NoHistory) { /* Not using history. */ beep(); return; } if (gCurHistLine != NULL) { /* If not null, then the user had already scrolled up and was * editing a line in the history. */ gCurHistLine = gCurHistLine->prev; } else { /* Was on original line to edit, but wants to go back one. */ gCurHistLine = gHistory->last; if (gCurHistLine == NULL) { /* No lines at all in the history. */ beep(); return; } } wg_LineKill(); if (gCurHistLine != NULL) { Strncpy(gDst, gCurHistLine->line, gDstSize); gBufLen = strlen(gDst); wg_GoLineEnd(); } /* Otherwise, was on the first line in the history, but went "up" from here * which wraps around to the bottom. This last line is the new line * to edit. */ } /* wg_HistoryUp */ static void wg_HistoryDown(void) { if (gHistory == wg_NoHistory) { /* Not using history. */ beep(); return; } if (gCurHistLine != NULL) { /* If not null, then the user had already scrolled up and was * editing a line in the history. */ gCurHistLine = gCurHistLine->next; } else { /* Was on original line to edit, but wants to go down one. * We'll wrap around and go to the very first line. */ gCurHistLine = gHistory->first; if (gCurHistLine == NULL) { /* No lines at all in the history. */ beep(); return; } } wg_LineKill(); if (gCurHistLine != NULL) { Strncpy(gDst, gCurHistLine->line, gDstSize); gBufLen = strlen(gDst); wg_GoLineEnd(); } /* Otherwise, was on the last line in the history, but went down from here * which means we should resume editing a fresh line. */ } /* wg_HistoryDown */ static void wg_Update(void) { char *lastCharPtr; char *cp; wmove(gW, gSy, gSx); lastCharPtr = gDst + gBufLen; *lastCharPtr = '\0'; if (gEchoMode == wg_RegularEcho) { for (cp = gWinStartPtr; cp < lastCharPtr; cp++) { if (cp > gWinEndPtr) goto xx; waddch(gW, (unsigned char) *cp); } } else if (gEchoMode == wg_BulletEcho) { for (cp = gWinStartPtr; cp < lastCharPtr; cp++) { if (cp > gWinEndPtr) goto xx; waddch(gW, wg_Bullet); } } else /* if (gEchoMode == wg_NoEcho) */ { for (cp = gWinStartPtr; cp < lastCharPtr; cp++) { if (cp > gWinEndPtr) goto xx; waddch(gW, ' '); } } /* Rest of display window is empty, so write out spaces. */ for ( ; cp <= gWinEndPtr; cp++) waddch(gW, ' '); xx: wmove(gW, gSy, gSx + (gBufPtr - gWinStartPtr)); wrefresh(gW); gNeedUpdate = 0; } /* wg_Update */ int wg_Gets(WGetsParamPtr wgpp) { int c, result; int lineKill; int maxx, maxy; #ifdef WG_DEBUG FILE *trace; #endif /* Sanity checks. */ if (wgpp == NULL) return (wg_BadParamBlock); if (wgpp->dstSize < 2) return (wg_DstSizeTooSmall); gDstSize = wgpp->dstSize - 1; /* Leave room for nul. */ if (wgpp->fieldLen < 1) return (wg_WindowTooSmall); gWindowWidth = wgpp->fieldLen; if (wgpp->w == NULL) return (wg_BadCursesWINDOW); gW = wgpp->w; getmaxyx(gW, maxy, maxx); if ((wgpp->sy < 0) || (wgpp->sy > maxy)) return (wg_BadCoordinates); gSy = wgpp->sy; if ((wgpp->sx < 0) || (wgpp->sx > maxx)) return (wg_BadCoordinates); gSx = wgpp->sx; if (wgpp->dst == NULL) return (wg_BadBufferPointer); gDst = wgpp->dst; gHistory = wgpp->history; /* Will be NULL if not using history. */ gCurHistLine = NULL; /* Means we haven't scrolled into history. */ gEchoMode = wgpp->echoMode; gChanged = 0; gMoved = 0; result = 0; wmove(gW, gSy, gSx); wrefresh(gW); #ifdef WG_DEBUG trace = fopen(wg_TraceFileName, "a"); if (trace != NULL) { fprintf(trace, "\n"); } #endif cbreak(); /* Should already have echo turned off. */ /* noecho(); */ nodelay(gW, FALSE); keypad(gW, TRUE); #ifdef HAVE_NOTIMEOUT notimeout(gW, TRUE); #endif lineKill = (int) killchar(); gNeedUpdate = 1; gBufPtr = gDst; gWinStartPtr = gBufPtr; gWinEndPtr = gWinStartPtr + gWindowWidth - 1; gBufPtr[gDstSize] = '\0'; gHadStartingString = 0; if (wgpp->useCurrentContents) { gBufLen = strlen(gBufPtr); if (gBufLen > 0) gHadStartingString = 1; } else { gBufLen = 0; } while (1) { if (gNeedUpdate) wg_Update(); c = wgetch(gW); #ifdef WG_DEBUG if (trace != NULL) { switch (c) { case '\r': fprintf(trace, "(\\r)\n"); break; case '\n': fprintf(trace, "(\\n)\n"); break; #ifdef KEY_ENTER case KEY_ENTER: fprintf(trace, "(KEY_ENTER)\n"); break; #endif default: fprintf(trace, "[%c] = 0x%X\n", c, c); } } #endif switch (c) { case '\r': case '\n': #ifdef KEY_ENTER case KEY_ENTER: #endif goto done; case '\b': #ifdef KEY_BACKSPACE case KEY_BACKSPACE: #endif case 0x7f: wg_KillCh(1); break; #ifdef KEY_FWDDEL /* Need to find a real symbol for forward delete. */ case KEY_FWDDEL: wg_ForwardKillCh(); break; #endif #ifdef KEY_EXIT case KEY_EXIT: #endif #ifdef KEY_CLOSE case KEY_CLOSE: #endif #ifdef KEY_CANCEL case KEY_CANCEL: #endif case 0x04: /* Control-D */ /* If we haven't changed the buffer, and the cursor has * not moved from the first position, return EOF. */ if (!gChanged && !gMoved) { result = wg_EOF; goto done; } /* fall */ #ifdef KEY_DC case KEY_DC: #endif if (gBufPtr == gDst + gBufLen) { wg_AddCh('*'); wg_Update(); CompleteOptions(gDst, gBufPtr-gDst-1); wg_KillCh(1); } else { wg_ForwardKillCh(); /* Emacs ^D */ } break; #ifdef KEY_CLEAR case KEY_CLEAR: #endif case 0x0C: /* Control-L */ touchwin(curscr); wrefresh(curscr); break; #ifdef KEY_LEFT case KEY_LEFT: #endif case 0x02: /* Control-F */ wg_GoLeft(); break; #ifdef KEY_RIGHT case KEY_RIGHT: #endif case 0x06: /* Control-B */ wg_GoRight(); break; #ifdef KEY_UP case KEY_UP: #endif case 0x10: /* Control-P */ wg_HistoryUp(); break; #ifdef KEY_DOWN case KEY_DOWN: #endif case 0x0E: /* Control-N */ wg_HistoryDown(); break; #ifdef KEY_HOME case KEY_HOME: #endif case 0x01: /* Control-A */ wg_GoLineStart(); break; #ifdef KEY_END case KEY_END: #endif case 0x05: /* Control-E */ wg_GoLineEnd(); break; #ifdef KEY_EOL case KEY_EOL: #endif case 0x0B: while (gBufLen > 0 && gBufPtr < gDst + gBufLen) wg_ForwardKillCh(); /* Emacs ^K */ break; case -1: /* This can happen if getch() was interrupted by a * signal like ^Z. */ break; case 23: /* ^W */ wg_KillWord(); break; case '\t': { int i; char *comp; char *tmp; for (i=0;i<3;i++) wg_AddCh('.'); wg_Update(); comp = CompleteGet(gDst, gBufPtr-gDst-3); wg_KillCh(3); gDst[gBufLen] = '\0'; if (comp) { for (tmp = comp; *tmp; tmp++) wg_AddCh(*tmp); free(comp); } break; } default: if (c < 0400) { if (c == lineKill) wg_LineKill(); else wg_AddCh(c); } } } done: nocbreak(); gDst[gBufLen] = '\0'; wgpp->changed = gChanged; wgpp->dstLen = gBufLen; if ((gHistory != wg_NoHistory) && (gBufLen > 0)) AddLine(wgpp->history, gDst); #ifdef WG_DEBUG if (trace != NULL) { fprintf(trace, "\n"); fclose(trace); } #endif return (result); } /* wg_Gets */ #endif /* USE_CURSES */ /* eof */