static int completeLine(int fd, const char *prompt, char *buf, size_t buflen, size_t *len, size_t *pos, size_t cols) { linenoiseCompletions lc = { 0, NULL }; int nwritten; char c = 0; completionCallback(buf,&lc); if (lc.len == 0) { beep(); } else { size_t stop = 0, i = 0; size_t clen; while(!stop) { /* Show completion or original buffer */ if (i < lc.len) { clen = strlen(lc.cvec[i]); refreshLine(fd,prompt,lc.cvec[i],clen,clen,cols); } else { refreshLine(fd,prompt,buf,*len,*pos,cols); } do { c = linenoiseReadChar(fd); } while (c == (char)-1); switch(c) { case 0: freeCompletions(&lc); return -1; case 9: /* tab */ i = (i+1) % (lc.len+1); if (i == lc.len) beep(); break; case 27: /* escape */ /* Re-show original buffer */ if (i < lc.len) { refreshLine(fd,prompt,buf,*len,*pos,cols); } stop = 1; break; default: /* Update buffer and return */ if (i < lc.len) { nwritten = snprintf(buf,buflen,"%s",lc.cvec[i]); *len = *pos = nwritten; } stop = 1; break; } } } freeCompletions(&lc); return c; /* Return last read character */ }
static int linenoisePrompt(int fd, char *buf, size_t buflen, const char *prompt) { size_t plen = strlen(prompt); size_t pos = 0; size_t len = 0; size_t cols = getColumns(); buf[0] = '\0'; buflen--; /* Make sure there is always space for the nulterm */ /* The latest history entry is always our current buffer, that * initially is just an empty string. */ linenoiseHistoryAdd(""); history_index = history_len-1; if (write(1,prompt,plen) == -1) return -1; while(1) { char c = linenoiseReadChar(fd); if (c == 0) return len; if (c == (char)-1) { refreshLine(fd,prompt,buf,len,pos,cols); continue; } /* Only autocomplete when the callback is set. It returns < 0 when * there was an error reading from fd. Otherwise it will return the * character that should be handled next. */ if (c == 9 && completionCallback != NULL) { c = completeLine(fd,prompt,buf,buflen,&len,&pos,cols); /* Return on errors */ if (c < 0) return len; /* Read next character when 0 */ if (c == 0) continue; } switch(c) { case 13: /* enter */ history_len--; free(history[history_len]); return (int)len; case 3: /* ctrl-c */ errno = EAGAIN; return -1; case 127: /* delete */ if (len > 0 && pos < len) { memmove(buf+pos,buf+pos+1,len-pos-1); len--; buf[len] = '\0'; refreshLine(fd,prompt,buf,len,pos,cols); } break; case 8: /* backspace or ctrl-h */ if (pos > 0 && len > 0) { memmove(buf+pos-1,buf+pos,len-pos); pos--; len--; buf[len] = '\0'; refreshLine(fd,prompt,buf,len,pos,cols); } break; case 4: /* ctrl-d, remove char at right of cursor */ if (len > 1 && pos < (len-1)) { memmove(buf+pos,buf+pos+1,len-pos); len--; buf[len] = '\0'; refreshLine(fd,prompt,buf,len,pos,cols); } else if (len == 0) { history_len--; free(history[history_len]); return -1; } break; case 20: /* ctrl-t */ if (pos > 0 && pos < len) { int aux = buf[pos-1]; buf[pos-1] = buf[pos]; buf[pos] = aux; if (pos != len-1) pos++; refreshLine(fd,prompt,buf,len,pos,cols); } break; case 2: /* ctrl-b */ /* left arrow */ if (pos > 0) { pos--; refreshLine(fd,prompt,buf,len,pos,cols); } break; case 6: /* ctrl-f */ /* right arrow */ if (pos != len) { pos++; refreshLine(fd,prompt,buf,len,pos,cols); } break; case 16: /* ctrl-p */ case 14: /* ctrl-n */ /* up and down arrow: history */ if (history_len > 1) { /* Update the current history entry before to * overwrite it with tne next one. */ free(history[history_index]); history[history_index] = strdup(buf); /* Show the new entry */ history_index += (c == 16) ? -1 : 1; if (history_index < 0) { history_index = 0; break; } else if (history_index >= history_len) { history_index = history_len-1; break; } strncpy(buf,history[history_index],buflen); buf[buflen] = '\0'; len = pos = strlen(buf); refreshLine(fd,prompt,buf,len,pos,cols); } break; case 27: /* escape sequence */ break; /* should be handled by linenoiseReadChar */ default: if (len < buflen) { if (len == pos) { buf[pos] = c; pos++; len++; buf[len] = '\0'; if (plen+len < cols) { /* Avoid a full update of the line in the * trivial case. */ if (write(1,&c,1) == -1) return -1; } else { refreshLine(fd,prompt,buf,len,pos,cols); } } else { memmove(buf+pos+1,buf+pos,len-pos); buf[pos] = c; len++; pos++; buf[len] = '\0'; refreshLine(fd,prompt,buf,len,pos,cols); } } break; case 21: /* Ctrl+u, delete the whole line. */ buf[0] = '\0'; pos = len = 0; refreshLine(fd,prompt,buf,len,pos,cols); break; case 11: /* Ctrl+k, delete from current to end of line. */ buf[pos] = '\0'; len = pos; refreshLine(fd,prompt,buf,len,pos,cols); break; case 1: /* Ctrl+a, go to the start of the line */ pos = 0; refreshLine(fd,prompt,buf,len,pos,cols); break; case 5: /* ctrl+e, go to the end of the line */ pos = len; refreshLine(fd,prompt,buf,len,pos,cols); break; case 12: /* ctrl+l, clear screen */ linenoiseClearScreen(); refreshLine(fd,prompt,buf,len,pos,cols); } } return len; }
int main(void) { int epollFD; epollFD = epoll_create1(EPOLL_CLOEXEC); if (epollFD < 0) { printf("Failed to create epollFD: %m\n"); exit(1); } struct epoll_event ev = { .events = EPOLLIN | EPOLLPRI, .data.fd = STDIN_FILENO }; int ret = epoll_ctl(epollFD, EPOLL_CTL_ADD, ev.data.fd, &ev); if (ret < 0) { printf("epoll_ctl() failed: %m\n"); exit(1); } linenoiseSetHandlerCallback(NULL, callback); linenoiseSetPrompt("input>"); linenoiseForcedUpdateDisplay(); while(!stopped) { #define MAX_EVENTS 10 struct epoll_event events[MAX_EVENTS]; int nfds = epoll_wait(epollFD, events, MAX_EVENTS, -1); if (nfds < 0 ) { printf("epoll_wait: %m\n"); if (errno == EINTR) continue; return -1; } /* handle selected fds */ for (int s=0; s<nfds; s++) { int fd = events[s].data.fd; if (fd < 0) continue; if (events[s].events & EPOLLIN) { switch (fd) { case STDIN_FILENO: linenoiseReadChar(); break; default: printf("Data available on %d\n", fd); } } if (events[s].events & EPOLLHUP) { printf("HUP on sock %d\n", fd); } if (events[s].events & EPOLLERR) { printf("error happened on fd %d\n", fd); int r = epoll_ctl(epollFD, EPOLL_CTL_DEL, fd, NULL); printf("epoll_ctl(CTL_DEL) returns %d: %m\n", r); close(fd); } } } sleep(1); linenoiseRemoveHandlerCallback(); printf("\n"); return 0; }