Source

srw / srw.c

#include <err.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/wait.h>

#ifdef __linux__
 #include <pty.h>
#else  // __linux__
 #include <util.h>
#endif // __linux__

#define STDIN  0
#define STDOUT 1
#define STDERR 2

#define MAX(a, b)   (((a) > (b)) ? (a) : (b))
#define MIN(a, b)   (((a) < (b)) ? (a) : (b))

#define BELL        CTRL('g')
#define BACKSPACE   CTRL('h')
#define TAB         CTRL('i')
#define LINEFEED    CTRL('j')
#define RETURN      CTRL('m')
#define ESC         0x1b
#define DEL         0x7f

#define cursor_clr()  printf("\033[J")
#define cursor_beg()  printf("\033[1G")
#define cursor_fwd(n) printf("\033[%dC", MAX(1, (n)))
#define cursor_bak(n) printf("\033[%dD", MAX(1, (n)))

char  csi[] = {ESC, '[', 0};
char  buf[4096], usrbuf[4096];
char  scrl_prompt_left[]  = "<";
char  scrl_prompt_right[] = ">";
char  nrml_prompt_default[] = ">";
char *nrml_prompt = nrml_prompt_default;
char  word_separators[] = " \t~!@#$%^&*-+=\\|/?.,><";

int start = 0, cur = 0, len = 0;
int mfd;
pid_t child;

void usage(void)
{
	printf("Usage: srw [-p prompt] cmd [args]...\n");
	exit(1);
}

void sigchld(int unused)
{
	pid_t pid;

	while ((pid = waitpid(-1, NULL, WNOHANG)) > 0) {
		if (pid == child) {
			printf("\n");
			warnx("subprocess died");
			kill(getpid(), SIGINT);
		}
	}
}

void my_write(int fd, const char *buf, size_t count)
{
	int i, wrlen;

	for (i = wrlen = 0; i < count; i += wrlen)
		if ((wrlen = write(fd, buf + i, count - i)) < 0)
			warn("failed to write fd = %d", fd);
}

void erase_usr(void)
{
	cursor_beg();
	cursor_clr();
	fflush(stdout);
}

int start_pos(int start, int cur, int len, int cols)
{
    int ret;

    ret = start;
    ret = MIN(ret, len - cols);
    ret = MAX(ret, 0);
    ret = MAX(ret, cur - cols);
    ret = MIN(ret, cur);

    return ret;
}

void print_usr(void)
{
	int cols, left_prompt_len, right_prompt_len = 0;
	struct winsize ws;

	if (ioctl(1, TIOCGWINSZ, &ws) < 0)
		err(1, "failed ioctl");

	cols = ws.ws_col - 1;

	erase_usr();

	left_prompt_len = strlen(nrml_prompt);
	cols -= left_prompt_len;

	start = start_pos(start, cur, len, cols);

	if (start > 0) {
	    left_prompt_len = strlen(scrl_prompt_left);
	    my_write(STDOUT, scrl_prompt_left, left_prompt_len);
	    cols = ws.ws_col - 1 - left_prompt_len;
	    start = start_pos(start, cur, len, cols);
	} else {
	    my_write(STDOUT, nrml_prompt, left_prompt_len);
	}

	if (start + cols < len) {
	    right_prompt_len = strlen(scrl_prompt_right);
	    cols -= right_prompt_len;
	    start = start_pos(start, cur, len, cols);
	}

	my_write(STDOUT, usrbuf + start, MIN(cols, len));

	if (start + cols < len)
	    my_write(STDOUT, scrl_prompt_right, right_prompt_len);

	cursor_beg();
	cursor_fwd(left_prompt_len + cur - start);
	fflush(stdout);
}

void shift_buf(char *buf, int ind, int len, int delta)
{
    int i;

    for (i = (delta > 0) ? len - 1 : ind ; i >= ind && i < len; i -= (delta > 0) ? 1 : -1)
	buf[i + delta] = buf[i];
}

void run()
{
    static int escape = 0;
    int rdlen, i;
    char c;
    fd_set rd;

    for (;;) {
	FD_ZERO(&rd);
	FD_SET(STDIN, &rd);
	FD_SET(mfd,   &rd);

	select(mfd + 1, &rd, NULL, NULL, NULL);

	if (FD_ISSET(STDIN, &rd)) {
	    if ((rdlen = read(STDIN, &c, 1)) < 0)
		warn("failed to read from stdin");

	    if (escape) {
		switch (c) {
		    case '[' : continue;
		    case 'C' : cur = MIN(cur + 1, len); break;
		    case 'D' : cur = MAX(cur - 1, 0  ); break;
		}
		escape = 0;
	    } else {
		switch (c) {
		    case ESC :
			escape = 1;
			break;
		    case RETURN :
		    case LINEFEED :
			erase_usr();
			usrbuf[len++] = '\n';
			my_write(mfd, usrbuf, len);
			start = cur = len = 0;
			break;
		    case BACKSPACE :
		    case DEL :
			if (cur != len)
			    shift_buf(usrbuf, cur, len, -1);
			cur = MAX(0, cur - 1);
			len = MAX(0, len - 1);
			break;
		    case CTRL('a') :
			cur = 0;
			break;
		    case CTRL('e') :
			cur = len;
			break;
		    case CTRL('u') :
			start = cur = len = 0;
			break;
		    case CTRL('w') :
			for (i = cur - 1; i >= 0 && strchr(word_separators, usrbuf[i]); i--)
			    ;
			for (; i >=0 && !strchr(word_separators, usrbuf[i]); i--)
			    ;
			i++;
			shift_buf(usrbuf, cur, len, -(cur - i));
			len -= cur - i;
			cur = i;
			break;
		    default :
			if (cur != len)
			    shift_buf(usrbuf, cur, len, 1);
			usrbuf[cur++] = c;
			len++;
			break;
		}
	    }
	    print_usr();
	}
	if (FD_ISSET(mfd, &rd)) {
	    if ((rdlen = read(mfd, buf, sizeof(buf))) < 0)
		warn("failed to read from master");

	    erase_usr();
	    my_write(STDOUT, buf, rdlen);
	    print_usr();
	}
    }
}

int main(int argc, char **argv)
{
    struct termios set;
    int i;

    for (i = 1; i < argc; i++) {
	if (argv[i][0] != '-')
	    break;
	if (argv[i][2] != '\0')
	    usage();
	switch (argv[i][1]) {
	    case 'p' : nrml_prompt = argv[++i]; break;
	    case 'v' : printf("srw-"VERSION" © Evan Gates\n"); exit(1);
	    default  : usage();
	}
    }

    if (i >= argc)
	usage();

    tcgetattr(0, &set);
    set.c_lflag &= ~ECHO;

    if (signal(SIGCHLD, sigchld) == SIG_ERR)
	err(1, "failed to install SIGCHLD handler");

    if ((child = forkpty(&mfd, NULL, &set, NULL)) == 0) { //child
	execvp(argv[i], &argv[i]);
	err(1, "failed execvp %s", argv[i]);
    }

    // read one character at a time;
    set.c_lflag    &= ~(ICANON | ECHO);
    set.c_cc[VMIN ] = 1;
    set.c_cc[VTIME] = 0;
    tcsetattr(0, TCSANOW, &set);

    print_usr();
    run();

    return 0;
}
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.