Source

srw / srw.c

#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <locale.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include <wchar.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

// make sure not to use anything that can't be evaluated twice
#define MIN(x,y) ((x) < (y) ? (x) : (y))
#define MAX(x,y) ((x) > (y) ? (x) : (y))

#define BACKSPACE   CTRL('h')
#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_org()  printf("\033[1;1H")

//char  buf[4096], usrbuf[4096];
char  buf[4096];
char 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 wcur = 0, wlen = 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(STDOUT, 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, wcur, wlen, cols);

	if (start > 0 || wlen > cols) {
		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, wcur, wlen, cols);
	} else {
		my_write(STDOUT, nrml_prompt, left_prompt_len);
	}

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

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

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

		*/

	erase_usr();
	left_prompt_len = strlen(nrml_prompt);
	my_write(STDOUT, nrml_prompt, left_prompt_len);
	my_write(STDOUT, usrbuf, len);
	cursor_beg();
	cursor_fwd(left_prompt_len + wcur - start);
	fflush(stdout);
}

int width(char *buf, int *ind, int len, int crement)
{
	int nb, mask;
	wchar_t wc;

	for (; *ind > 0 && *ind < len  &&
		(buf[*ind] & 0xc0) != 0xc0 &&
		(buf[*ind] & 0x80); *ind += crement)
		;
	// skip the first, if it's ascii we still want one byte (nb = 1)
	for (nb = 1, mask = 0x40; buf[*ind] & mask; nb++, mask >>= 1)
		;
	if (mbtowc(&wc, buf + *ind, nb) < 0)
		warnx("failed multibyte to wide char conversion");
	if ((nb = wcwidth(wc)) < 0)
		warnx("failed to find width of wide char");

	return nb;
}

void run()
{
	static int escape = 0, wstart = 0, in_mb = 0;
	int rdlen, i, mask, tmp;
	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 'O' :
					case '[' :
						continue;
					case '3' : // Delete
						escape++;
						if (cur == len) break;
						tmp = cur + 1;
						i = width(usrbuf, &tmp, len, 1);
						memmove(usrbuf + cur, usrbuf + tmp, len - tmp);
						len -= tmp - cur;
						wlen = MAX(0, wlen - i);
						break;
					case '7' : // Home
						cur = wcur = 0;
						escape++;
						break;
					case '8' : // End
						cur = len;
						wcur = wlen;
						escape++;
						break;
					case 'C' : // Right arrow
						cur = MIN(cur + 1, len);
						i = width(usrbuf, &cur, len, 1);
						wcur = MIN(wcur + i, wlen);
						break;
					case 'D' : // Left Arrow
						cur = MAX(cur - 1, 0);
						i = width(usrbuf, &cur, len, -1);
						wcur = MAX(wcur - i, 0);
						break;
					default  : escape++;
				}
				escape--;
				// I get some really weird behavior from the numpad on this computer...
				if (strchr("ABMUVWXYZfijklmo][\\^~_", c)) // end of one, or unsupported
					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 = wcur = wlen = 0;
						break;
					case BACKSPACE :
					case DEL :
						tmp = cur;
						cur = MAX(cur - 1, 0);
						i = width(usrbuf, &cur, len, -1);
						wlen = MAX(0, wlen - i);
						wcur = MAX(0, wcur - i);
						if (tmp != len)
							memmove(usrbuf + cur, usrbuf + tmp, len - tmp);
						len = MAX(0, len - (tmp - cur));
						break;
					case CTRL('a') : cur = wcur = 0;          break;
					case CTRL('e') : cur = len; wcur = wlen;  break;
					case CTRL('k') : len = cur; wlen = wcur;  break;
					case CTRL('u') : start = cur = len = 0;
									 wcur = wlen = 0;         break;
					case CTRL('w') :
						for (i = cur - 1; i >= 0 && strchr(word_separators, usrbuf[i]); i--) {
						    wcur--;
							wlen--;
						}
						for (tmp = width(usrbuf, &i, len, -1);
							 !strchr(word_separators, usrbuf[i]);
							 i--, tmp = width(usrbuf, &i, len, -1)) {
							wcur -= tmp;
							wlen -= tmp;
						}
						i++;
						memmove(usrbuf + i, usrbuf + cur, len - cur);
						len -= cur - i;
						cur = i;
						break;
					case CTRL('l') :
						cursor_org();
						cursor_clr();
						fflush(stdout);
						kill(-child, SIGWINCH);
						break;
					default :
						if (cur != len)
							memmove(usrbuf + cur + 1, usrbuf + cur, len - cur);
						usrbuf[cur] = c;

						if (c & 0x80) { // not ascii, so...
							if ((c & 0x40) && !in_mb) { // start of multibyte character
								for (in_mb = 0, mask = 0x80; mask & c; in_mb++, mask >>= 1)
									;
								in_mb--;
								wstart = cur;
							} else if ((c & 0xc0) == 0x80) { // continuation of multibyte character
								if (--in_mb == 0) { // end of multibyte character
									i = width(usrbuf, &wstart, len, 1);
									wcur += i;
									wlen += i;
								}
							} else { // not the start, not continuing, something is wrong
								warnx("invalide multibyte sequence");
							}
						} else { // ascii
							wcur++;
							wlen++;
						}
						cur++;
						len++;
				}
			}
			if (!in_mb)
				print_usr();
		}

		if (FD_ISSET(mfd, &rd)) {
			erase_usr();
			for (rdlen = read(mfd, buf, sizeof(buf)); rdlen > 0; rdlen = read(mfd, buf, sizeof(buf)))
				my_write(STDOUT, buf, rdlen);
			if (rdlen < 0 && errno != EAGAIN)
				warn("failed to read from fd %d", mfd);
			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();

	if (!setlocale(LC_CTYPE, ""))
		err(1, "failed to set locale");

	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]);
	}

	if ((i = fcntl(mfd, F_GETFL)) < 0)
	    err(1, "failed to get fcntl flags from mfd");
	if (fcntl(mfd, F_SETFL, i | O_NONBLOCK) < 0)
	    err(1, "failed to set fcntl flags for mfd");

	// 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.