commit 98fa5e3861a515b0b9f7b212e7eb5abf550e1f33
parent b1db0ac6e9850a858120ac43596405208a3fc581
Author: Jan Klemkow <j.klemkow@wemelug.de>
Date: Mon, 26 Oct 2015 22:17:57 +0100
wip
Diffstat:
M | lchat.c | | | 174 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------- |
M | slackline.c | | | 58 | ++++++++++++++++++++++++++++++++++++++++++++++++++++------ |
M | slackline.h | | | 16 | +++++++--------- |
3 files changed, 216 insertions(+), 32 deletions(-)
diff --git a/lchat.c b/lchat.c
@@ -1,6 +1,10 @@
#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <term.h>
#include <termios.h>
#include <unistd.h>
@@ -9,29 +13,104 @@
struct termios origin_term;
-void
+static void
exit_handler(void)
{
if (tcsetattr(STDIN_FILENO, TCSANOW, &origin_term) == -1)
err(EXIT_FAILURE, "tcsetattr");
}
+static void
+line_output(struct slackline *sl, char *file)
+{
+ int fd;
+
+ if ((fd = open(file, O_WRONLY|O_APPEND)) == -1)
+ err(EXIT_FAILURE, "open: %s", file);
+
+ /* replace NUL-terminator with newline as line separator for file */
+ sl->buf[sl->len] = '\n';
+
+ if (write(fd, sl->buf, sl->len + 1) == -1)
+ err(EXIT_FAILURE, "write");
+
+ if (close(fd) == -1)
+ err(EXIT_FAILURE, "close");
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "lchar [-nH] [-p prompt] [-i in] [-o out] [directory]\n");
+ exit(EXIT_FAILURE);
+}
+
int
-main(void)
+main(int argc, char *argv[])
{
+ char tail_cmd[BUFSIZ];
+ struct pollfd pfd[2];
struct termios term;
struct slackline *sl = sl_init();
- char *term_name = getenv("TERM");
int fd = STDIN_FILENO;
int c;
+ int ch;
+ bool empty_line = true;
+ size_t history_len = 0;
+ char *prompt = ">";
+ size_t prompt_len = strlen(prompt);
+ char *dir = ".";
+ char *in_file = NULL;
+ char *out_file = NULL;
+ FILE *tail_fh;
+
+ while ((ch = getopt(argc, argv, "H:i:no:p:h")) != -1) {
+ switch (ch) {
+ case 'H':
+ errno = 0;
+ history_len = strtoull(optarg, NULL, 0);
+ if (errno != 0)
+ err(EXIT_FAILURE, "strtoull");
+ break;
+ case 'i':
+ if ((in_file = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case 'n':
+ empty_line = false;
+ break;
+ case 'o':
+ if ((out_file = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "strdup");
+ break;
+ case 'p':
+ if ((prompt = strdup(optarg)) == NULL)
+ err(EXIT_FAILURE, "strdup");
+ prompt_len = strlen(prompt);
+ break;
+ case 'h':
+ default:
+ usage();
+ /* NOTREACHED */
+ }
+ }
+ argc -= optind;
+ argv += optind;
- if (term_name == NULL)
- errx(EXIT_FAILURE, "environment TERM is not set");
+ if (argc > 1)
+ usage();
- switch (tgetent(NULL, term_name)) {
- case -1: err(EXIT_FAILURE, "tgetent");
- case 0: errx(EXIT_FAILURE, "no termcap entry found for %s", term_name);
- }
+ if (argc == 1)
+ if ((dir = strdup(argv[0])) == NULL)
+ err(EXIT_FAILURE, "strdup");
+
+ if (in_file == NULL)
+ if (asprintf(&in_file, "%s/in", dir) == -1)
+ err(EXIT_FAILURE, "asprintf");
+
+ if (out_file == NULL)
+ if (asprintf(&out_file, "%s/out", dir) == -1)
+ err(EXIT_FAILURE, "asprintf");
if (isatty(fd) == 0)
err(EXIT_FAILURE, "isatty");
@@ -47,7 +126,14 @@ main(void)
if (tcgetattr(fd, &term) == -1)
err(EXIT_FAILURE, "tcgetattr");
- cfmakeraw(&term);
+ /* TODO: clean up this block. copied from cfmakeraw(3) */
+ term.c_iflag &= ~(IMAXBEL|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
+// term.c_oflag &= ~OPOST;
+ term.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN);
+ term.c_cflag &= ~(CSIZE|PARENB);
+ term.c_cflag |= CS8;
+ term.c_cc[VMIN] = 1;
+ term.c_cc[VTIME] = 0;
if (tcsetattr(fd, TCSANOW, &term) == -1)
err(EXIT_FAILURE, "tcsetattr");
@@ -55,13 +141,67 @@ main(void)
setbuf(stdin, NULL);
setbuf(stdout, NULL);
- while ((c = getchar()) != 13) {
- if (sl_keystroke(sl, c) == -1)
- errx(EXIT_FAILURE, "sl_keystroke");
- printf("\r\033[2K%s", sl->buf);
+ /* open external source */
+ snprintf(tail_cmd, sizeof tail_cmd, "exec tail -n %zd -f %s",
+ history_len, out_file);
+ if ((tail_fh = popen(tail_cmd, "r")) == NULL)
+ err(EXIT_FAILURE, "unable to open pipe to tail command");
+
+ pfd[0].fd = fd;
+ pfd[0].events = POLLIN;
+
+ pfd[1].fd = fileno(tail_fh);
+ pfd[1].events = POLLIN;
+
+ /* print initial prompt */
+ fputs(prompt, stdout);
+
+ for (;;) {
+ poll(pfd, 2, INFTIM);
+
+ /* carriage return and erase the whole line */
+ fputs("\r\033[2K", stdout);
+
+ /* handle keyboard intput */
+ if (pfd[0].revents & POLLIN) {
+ c = getchar();
+ if (c == 13) { /* return */
+ if (sl->len == 0 && empty_line == false)
+ continue;
+ line_output(sl, in_file);
+ sl_reset(sl);
+ }
+ if (sl_keystroke(sl, c) == -1)
+ errx(EXIT_FAILURE, "sl_keystroke");
+ }
+
+ /* handle tail command error and its broken pipe */
+ if (pfd[1].revents & POLLHUP)
+ break;
+
+ /* handle file intput */
+ if (pfd[1].revents & POLLIN) {
+ char buf[BUFSIZ];
+ ssize_t n = read(pfd[1].fd, buf, sizeof buf);
+ if (n == 0)
+ errx(EXIT_FAILURE, "tail command exited");
+ if (n == -1)
+ err(EXIT_FAILURE, "read");
+ if (write(STDOUT_FILENO, buf, n) == -1)
+ err(EXIT_FAILURE, "write");
+ putchar('\a'); /* ring the bell on external input */
+ }
+
+ /* show current input line */
+ fputs(prompt, stdout);
+ fputs(sl->buf, stdout);
+
+ if (sl->cur < sl->len) { /* move the cursor */
+ putchar('\r');
+ /* HACK: because \033[0C does the same as \033[1C */
+ if (sl->cur + prompt_len > 0)
+ printf("\033[%zdC", sl->cur + prompt_len);
+ }
}
-
- puts("\r");
-
return EXIT_SUCCESS;
}
diff --git a/slackline.c b/slackline.c
@@ -1,6 +1,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <stdbool.h>
#include "slackline.h"
@@ -18,9 +19,7 @@ sl_init(void)
return NULL;
}
- sl->buf[0] = '\0';
- sl->len = 0;
- sl->cur = 0;
+ sl_reset(sl);
return sl;
}
@@ -32,14 +31,54 @@ sl_free(struct slackline *sl)
free(sl);
}
+void
+sl_reset(struct slackline *sl)
+{
+ sl->buf[0] = '\0';
+ sl->len = 0;
+ sl->cur = 0;
+ sl->esc = ESC_NONE;
+}
+
int
sl_keystroke(struct slackline *sl, int key)
{
if (sl == NULL || sl->len < sl->cur)
return -1;
+ /* handle escape sequences */
+ switch (sl->esc) {
+ case ESC_NONE:
+ break;
+ case ESC:
+ sl->esc = key == '[' ? ESC_BRACKET : ESC_NONE;
+ return 0;
+ case ESC_BRACKET:
+ switch (key) {
+ case 'A': /* up */
+ case 'B': /* down */
+ break;
+ case 'C': /* right */
+ if (sl->cur < sl->len)
+ sl->cur++;
+ break;
+ case 'D': /* left */
+ if (sl->cur > 0)
+ sl->cur--;
+ break;
+ case 'H': /* Home */
+ sl->cur = 0;
+ break;
+ case 'F': /* End */
+ sl->cur = sl->len;
+ break;
+ }
+ sl->esc = ESC_NONE;
+ return 0;
+ }
+
/* add character to buffer */
- if (key >= 32 && key <= 127) {
+ if (key >= 32 && key < 127) {
if (sl->cur < sl->len) {
memmove(sl->buf + sl->cur + 1, sl->buf + sl->cur,
sl->len - sl->cur);
@@ -54,12 +93,19 @@ sl_keystroke(struct slackline *sl, int key)
/* handle ctl keys */
switch (key) {
- case 8: /* backspace */
+ case 27: /* Escape */
+ sl->esc = ESC;
+ break;
+ case 127: /* backspace */
+ case 8: /* backspace */
if (sl->cur == 0)
break;
+ if (sl->cur < sl->len)
+ memmove(sl->buf + sl->cur - 1, sl->buf + sl->cur,
+ sl->len - sl->cur);
sl->cur--;
sl->len--;
- sl->buf[sl->cur] = '\0';
+ sl->buf[sl->len] = '\0';
break;
}
diff --git a/slackline.h b/slackline.h
@@ -1,23 +1,21 @@
#ifndef SLACKLIINE_H
#define SLACKLIINE_H
-/*
- * +-+-+-+-+-+
- * |c|c|c|0|0|
- * +-+-+-+-+-+
- * ^ ^
- * len bufsize
- */
+#include <stdbool.h>
+
+enum esc_seq {ESC_NONE, ESC, ESC_BRACKET};
struct slackline {
char *buf;
size_t bufsize;
size_t len;
size_t cur;
+ enum esc_seq esc;
};
-struct slackline * sl_init(void);
-void sl_free(struct slackline *);
+struct slackline *sl_init(void);
+void sl_free(struct slackline *sl);
+void sl_reset(struct slackline *sl);
int sl_keystroke(struct slackline *sl, int key);
#endif