/*
	cursor.c
	Written by Eric Shalov <eric@freehack.com>.

	Demonstration of simple command-line editing interface with command
	buffer. Should work with DEC VT100, ANSI, xterm, etc..
	
	up, down - go though command history
	left, right - move cursor
	^A - go to beginning of line
	^E - go to end of line
	^U - delete to beginning of line
	^L - clear screen

	
        This program is free software; you can redistribute it and/or modify
        it under the terms of the GNU General Public License as published by
        the Free Software Foundation; either version 2 of the License, or
        (at your option) any later version.
    
        This program is distributed in the hope that it will be useful, but
        WITHOUT ANY WARRANTY; without even the implied warranty of
        MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
        General Public License for more details.
        
        You should have received a copy of the GNU General Public License
        along with this program; if not, write to the Free Software
        Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <termios.h>

#define MAX_ESCAPE 5

/* doubly-linked list for command-history: */
struct string_list {
	char *s;
	struct string_list *next, *prev;
};

/* prototypes */
char *getstring(char *buffer, int maxlen, char *prompt);

struct string_list *history = NULL;

int main() {
	char s[75];
	
	while(1) {
		getstring(s,75,"==> ");
		/* process command */
		if(*s)
			printf("Command: \"%s\".\n",s);
	}	
}

char *getstring(char *buffer, int maxlen, char *prompt) {
	unsigned char *s;
	unsigned char c;
	unsigned char escape_seq[MAX_ESCAPE+1]="";
	struct termios t;
	int escape = 0;
	int cursor=0;
	struct string_list *hp, *hn, *new_hist; /* history pointer */
	
	s = buffer;
	hn = history; /* point history pointer at first entry */
	hp = NULL;

	printf("%s",prompt);

	tcgetattr(fileno(stdin),&t);
	t.c_lflag &= ~(ECHO|ICANON);
	t.c_cc[VMIN]=1;
	t.c_cc[VTIME]=0;
	tcsetattr(fileno(stdin),TCSANOW,&t);

		*s = '\0';
		do {
			c = getchar();

			if(escape) {
				/* add character into escape sequence buffer */
				escape_seq[strlen(escape_seq)+1] = '\0';
				escape_seq[strlen(escape_seq)] = c;
				
				if(strlen(escape_seq)==MAX_ESCAPE) {
					escape=0;
					putchar('\a');
				} else
				
				if(strcmp(escape_seq,"[A")==0) {
					/* up - load next history string */
					if(hn) {
						/* jump cursor to end of line */
						printf("%s",s+cursor);
						cursor=strlen(s);
						
						/* erase current line from screen */
						while(cursor--)printf("\b \b");
					
						strcpy(s,hn->s);
						printf("%s",s);
						cursor = strlen(s);
						
						hp=hn->prev;
						hn=hn->next;
					} else {
						putchar('\a'); /* beep */
					}
					
					escape = 0;
				} else
				
				if(strcmp(escape_seq,"[B")==0) {
					/* down arrow - load previous history string */
					if(hp) {
						/* jump cursor to end of line */
						printf("%s",s+cursor);
						cursor=strlen(s);

						/* erase current line from screen */
						while(cursor--)printf("\b \b");
					
						strcpy(s,hp->s);
						printf("%s",s);
						cursor = strlen(s);
						
						hn=hp->next;
						hp=hp->prev;
					} else {
						putchar('\a'); /* beep */
					}

					escape = 0;
				} else
				
				/* Left arrow */
				if(strcmp(escape_seq,"[D")==0) {
					escape = 0;
					if(cursor) {
						--cursor;
						printf("%c[D",0x1B);
					} else putchar('\a'); /* beep */
				} else
				
				/* Right arrow */
				if(strcmp(escape_seq,"[C")==0) {
					escape = 0;
					if(cursor<strlen(s)) {
						++cursor;
						printf("%c[C",0x1B);
					} else putchar('\a'); /* beep */
				}
				
				
			} else {
			
				if(c==0x0D) {
					printf("\n");
					printf("(%s)\n",s);
				} else
				
				if(c==0x7F || c==0x08) {
					/* Handle the backspace/del key */
					if(*s) {
						--cursor;
						memcpy(s+cursor,s+cursor+1,strlen(s+cursor+1)+1);
						printf("\b \b");
					} else putchar('\a'); /* beep */
				} else
				
				if(c == 0x01) { /* ^A = move to start of line */
					if(cursor) {
						printf("%c[%dD",0x1B,cursor);
						cursor = 0;
					}
					else putchar('\a'); /* beep */
					
				} else

				if(c == 0x05) { /* ^E = move to end of line */
					if(cursor<strlen(s)) {
						printf("%c[%dC",0x1B,strlen(s)-cursor);
						cursor = strlen(s);
					} else putchar('\a'); /* beep */
				} else
				
				if(c == 0x15) { /* ^U = clear line */
					if(*s) {
						int i;
						for(i=0;i<strlen(s);i++)
							printf("\b \b");
						*s='\0';
						cursor=0;
					} else putchar('\a'); /* beep */
				} else

				if(c == 0x0C) { /* ^L = clear screen */
					/* clean the VT100 terminal, home the cursor */
					printf("%c[2J%c[H",0x1B,0x1B);
					
					printf("%s",prompt);
					printf(s);
					
					/* reposition the cursor */
					if(strlen(s)-cursor)
						printf("%c[%uD",0x1B,strlen(s)-cursor);
				} else
				
				if(c == 0x0A) { /* Return */
					putchar('\n');
				} else
	
				if(c == 0x1B) { /* handle escape sequences */
					escape=1;
					/* clear the escape sequence buffer */
					*escape_seq = '\0';
				} else
				
				/* out of range characters */
				if(c<0x20 || c>0x7F) {
					putchar('\a'); /* beep */
				} else {
					/* add a new character */
					if(strlen(s)<=(maxlen-1)) {
						memmove(s+cursor+1,s+cursor,strlen(s+cursor)+1);
						s[cursor]=c;
						printf("%s",s+cursor);
						{
							int i;
							
							/* move cursor back */
							for(i=1;i<strlen(s+cursor);i++)
								putchar('\b');
						}
						++cursor;
					} else {
						/* already filled up the buffer */
						putchar('\a');
					}
				}
			} /* else */
		} while(c != '\n');

	/* insert command at beginning command history list */
	if(*s) {
		new_hist = malloc(sizeof(struct string_list));
		new_hist->s = strdup(s);
		new_hist->prev = NULL;
		
		if(history) {
			new_hist->next=history; /* link it in */
			history->prev=new_hist; /* link it back */
			history = new_hist;
		} else {
			history = new_hist;
			history->next = NULL; /* end of list */
		}
	}

	
	return 0;
}

