srw / srw.c

#include <unistd.h>
#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <stdio.h>
#include <pty.h>
#include <termios.h>
#include <sys/ioctl.h>

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

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

char csi[] = {0x1b, '[', 0};
char buf[4096], usrbuf[4096];
char  nrml_prompt_default[] = ">";
char  scrl_prompt_default[] = "<";
char *nrml_prompt = nrml_prompt_default;
char *scrl_prompt = scrl_prompt_default;
int pos = 0;

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

void sigchld(int unused)
{
	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 prompt(int *start, int *cols, int pos)
{
	int prompt_len, scrl_len, nrml_len;
	char *prompt;

	nrml_len = strlen(nrml_prompt);
	scrl_len = strlen(scrl_prompt);

	*start = MAX(0, pos - *cols + nrml_len);

	prompt     = *start ? scrl_prompt : nrml_prompt;
	prompt_len = *start ? scrl_len    : nrml_len;

	*start = MAX(0, pos - *cols + prompt_len);

	my_write(STDOUT, prompt, prompt_len);
	*cols -= prompt_len;
}

void erase_usr(void)
{
	printf("%s1G", csi); //first column
	printf("%sJ",  csi); //erase
	fflush(stdout);
}

void print_usr(void)
{
	int start, cols;
	struct winsize ws;

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

	cols = ws.ws_col - 1;

	erase_usr();
	prompt(&start, &cols, pos);
	my_write(STDOUT, usrbuf + start, pos - start);
}

int main(int argc, char **argv)
{
	int mfd, rdlen, i;
	fd_set rd;
	struct termios set;

	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 'P' : scrl_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 (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();

	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, usrbuf + pos, 1)) < 0)
				warn("failed to read from stdin");

			switch (usrbuf[pos++]) {
				case '\n':
					erase_usr();
					my_write(mfd, usrbuf, pos);
					pos = 0;
					break;
				case '\b':
				case 127 :
					pos -= (pos > 1) ? 2 : 1;
					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();
		}
	}
	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.