package line_edit;

# system dependent variables
# see ioctl.pl or ioctl.h for your system, or uncomment
# out the system 'stty' commands and comment out reset of
# start and stop functions.
#

$TIOCGETP = 0x40067408;
$TIOCSETP = 0x80067409;
$TIOCGWINSZ = 0x40087468;
$RAW = 0x20;
$ECHO = 0x8;
$sgttyb_t   = 'C4 S';
$winsz_t = "S S S S";  # rows,cols, xpixel, ypixel
$default_num_cols = 80; # if can't be gotten from ioctl...


sub start {
#    system 'stty raw -echo';    

    ioctl(STDIN,$TIOCGETP,$sgttyb) || die "Can't ioctl TIOCGETP: $!";
    @tty_buf = unpack($sgttyb_t,$sgttyb);
    $tty_buf[4] |= $RAW;
    $tty_buf[4] &= ~$ECHO;
    $sgttyb = pack($sgttyb_t,@tty_buf);
    ioctl(STDIN,$TIOCSETP,$sgttyb) || die "Can't ioctl TIOCSETP: $!";
}

sub stop {
#    system 'stty -raw echo';

    ioctl(STDIN,$TIOCGETP,$sgttyb) || die "Can't ioctl TIOCGETP: $!";
    @tty_buf = unpack($sgttyb_t,$sgttyb);
    $tty_buf[4] &= ~$RAW;
    $tty_buf[4] |= $ECHO;
    $sgttyb = pack($sgttyb_t,@tty_buf);
    ioctl(STDIN,$TIOCSETP,$sgttyb) || die "Can't ioctl TIOCSETP: $!";

}

CONFIG: {
    $MAJOR = 0;
    $MINOR = 8;

    if (! -t STDIN) {
    	$stdin_not_tty =1;
    } else {
	$winsz=pack($winsz_t,0,0,0,0);
   	ioctl(STDIN,$TIOCGWINSZ,$winsz) || die "Can't ioctl TIOCGETP: $!";
        ($num_rows,$num_cols) = unpack($winsz_t,$winsz);
	if ($num_cols == 0) { $num_cols = $default_num_cols; }
    }

    $back_spaces = ("\b" x $num_cols);
    $blanks = (" " x $num_cols);

    $k_del =	"\177";
    $k_ctrl_a =   "\001";
    $k_ctrl_b =   "\002";
    $k_ctrl_c =   "\003";
    $k_ctrl_d =   "\004";
    $k_ctrl_e =   "\005";
    $k_ctrl_f =   "\006";
    $k_ctrl_g =   "\007";
    $k_ctrl_h =   "\010";
    $k_ctrl_i =   "\011";
    $k_ctrl_j =   "\012";
    $k_ctrl_k =   "\013";
    $k_ctrl_l =   "\014";
    $k_ctrl_m =   "\015";
    $k_ctrl_n =   "\016";
    $k_ctrl_o =   "\017";
    $k_ctrl_p =   "\020";
    $k_ctrl_r =   "\022";
    $k_ctrl_t =   "\024";
    $k_ctrl_u =   "\025";
    $k_ctrl_v =   "\026";
    $k_ctrl_w =   "\027";
    $k_ctrl_y =   "\031";
    $k_ctrl_z =   "\032";
    $k_escape =   "\033";

    $insert_mode=1;
    @recall_buffer=();
    $kill_buffer="";
    $kill_mark = -1;
    $line='';
    $index=0;
    $len=0;
    $max_index=20;
    $col=0;
}

sub refresh {
	print "\r\n$prompt$line",substr($back_spaces,0,$len-$col);
}


sub insert { # inserts string a current location
	local($s)=@_;
	local($e)= length($s);
	if ($insert_mode) {
		substr($line,$col,0) .= $s;
	        $len += $e;
	} else {
		substr($line,$col,$e) = $s;
	        $len = length($line);
	}
	print substr($line,$col,$len-$col),substr($back_spaces,0,$len-$col-$e);
	$col += $e;
}

sub goto_col {
   return if ($_[0]==$col || $col > $len || $col < 0);
   if ($_[0] < $col) {
	print substr($back_spaces,0,$col-$_[0]);
	$col=$_[0];
   } else {
	print substr($line,$col,$_[0]-$col);
	$col=$_[0];
   }
}

sub get {
    local ($prompt) = shift (@_);
    local ($ch,$i);

    if ($stdin_not_tty) {
		print "$prompt";
		chop($line=<STDIN>);
		print "$line\n";
		return $line;
    }

    &start;

    $i = $index;
    $line='';
    $len=0;
    $col=0;
    $search_index = $i;
    $| = 1;
	print $prompt,$line;
	while (1) {
	    $ch = getc;
	    if ((ord($ch) >= 32) && (ord($ch) <= 126)) {
		&insert($ch);
	    } elsif ($ch eq $k_ctrl_m || $ch eq $k_ctrl_j) {
		print "\r\n";
		$recall_buffer[$index]=$line;
		$index = ($index + 1) % $max_index;
		&stop;
		return $line;
	    } elsif ($ch eq $k_ctrl_a && $col) {		# (^A) 
		&goto_col(0); 
    	    } elsif ($ch eq $k_ctrl_b && $col) {		# (^B)
		&goto_col($col-1);
	    } elsif ($ch eq $k_ctrl_c) {			# (^C)
		print "\r\n";
		&stop;
		kill "INT",0;
    	    } elsif ($ch eq $k_ctrl_d && $col < $len) {	# (^D)
                substr($line,$col,1)="";
                $len--;
		print substr($line,$col,$len-$col)," ",substr($back_spaces,0,$len-$col+1);
    	    } elsif ($ch eq $k_ctrl_e && $col < $len) {	# (^E)
		&goto_col($len);
    	    } elsif ($ch eq $k_ctrl_f && $col < $len) { 	# (^F)
		&goto_col($col+1);
	    } elsif ($ch eq $k_ctrl_g && ($kill_col > 0) && ($kill_col<=$len)) {
		&goto_col($kill_col);
    	    } elsif ($ch eq $k_ctrl_k && $col < $len) { 	# (^K)
                local($e)=$len-$col;
		print substr($blanks,0,$e),substr($back_spaces,0,$e);
		$kill_buffer=substr($line,$col,$e);
		substr($line,$col)='';
		$len -= $e;
    	    } elsif ($ch eq $k_ctrl_l) {			# (^L)
		&refresh;
	    } elsif ($ch eq $k_ctrl_n || $ch eq $k_ctrl_p) {# (^N,^P) 
		if ($ch eq $k_ctrl_n) {$i = ($i+1) % $max_index; }
		else { $i = ($i-1) % $max_index;}
		if ($len) { print "\r",$prompt,substr($blanks,0,$len); }
		$col = ($len = length($line = $recall_buffer[$i]));
	        print "\r",$prompt,$line;
	    } elsif ($ch eq $k_ctrl_r && $len) {		# (^R)
		local($x)=($i-1) % $max_index;
		while(($x != $i) && ($recall_buffer[$x] !~ /$line/)) {
			$x = ($x-1) % $max_index;
		}
		if ($x == $i) { print $k_ctrl_g; }
                else {
		  if ($len) { print "\r",$prompt,substr($blanks,0,$len); }
		  $col = ($len = length($line = $recall_buffer[$x]));
	          print "\r",$prompt,$line;
                }
	    } elsif ($ch eq $k_ctrl_t && $col>1) {		# (^T) twiddle
		local($t)=substr($line,$col-2,1);
		substr($line,$col-2,1)=substr($line,$col-1,1);
		substr($line,$col-1,1)=$t;
		print "\b\b",substr($line,$col-2,2);
    	    } elsif ($ch eq $k_ctrl_u && $col>0) {		# (^U) kill
		print "\r",$prompt,substr($blanks,0,$len),"\r",$prompt;
		$line='';
		$len=$col=0;
    	    } elsif ($ch eq $k_ctrl_v) {			# (^V) version
		print "\n\n\rPCLE (Perl Command Line Editor ) version $MAJOR.$MINOR";
		print "\n\rHacked by Roland Schemers, Oakland University";
		print "\n\rSend comments to schemers@vela.acs.oakland.edu\n\r";
		&refresh;
	    } elsif ($ch eq $k_ctrl_w && ($kill_col >= 0) && ($kill_col<=$len)) {
		if ($col >= $kill_col) {
			$kill_buffer=substr($line,$kill_col,$col-$kill_col);
			local($s)=$col; local($dl)=$s-$kill_col;
			&goto_col($kill_col);
			substr($line,$kill_col,$dl)="";
			$len = length($line);
			print substr($line,$col),substr($blanks,0,$dl),
			substr($back_spaces,0,($len-$col)+($dl));
		} else {
			$kill_buffer=substr($line,$col,$kill_col-$col);
			substr($line,$col,$kill_col-$col)="";		
			$len = length($line);
			print substr($line,$col),substr($blanks,0,$kill_col-$col),
			substr($back_spaces,0,($len-$col)+($kill_col-$col));
		}

	    } elsif ($ch eq $k_ctrl_y && length($kill_buffer)) { # (^Y) yank
		&insert($kill_buffer);
	    } elsif ($ch eq $k_ctrl_z) {			# (^Z)
		print "\r\n";
		&stop;
		kill "TSTP",0;
		&start;
		&refresh;
    	    } elsif (($ch eq $k_ctrl_h || $ch eq $k_del) && $col) { #^H DEL
		substr($line,$col-1,1)=""; $len--; $col--;
		print "\b",substr($line,$col,$len-$col)," ",
			substr($back_spaces,0,$len-$col+1);
	    } elsif ($ch eq $k_escape) {
		&handle_escape;
	    } else {
		print $k_ctrl_g;
	    }
      }
}

sub handle_escape {

	local($char) = getc(STDIN);

	if ($char eq 'b' && $col) {					# b
		local($x)=$col;
		--$x if ($col == $len);
		while ($x && substr($line,$x,1) =~ /\w/) { --$x; }
		while ($x && substr($line,$x,1) =~ /\W/) { --$x; }
		while ($x && substr($line,$x-1,1) =~ /\w/) { --$x; }
		&goto_col($x);
	} elsif ($char eq 'c' && $len && $col < $len) {
		local($x)=$col;
		while ($x<$len && substr($line,$x,1) =~ /\W/) { ++$x; }
		if ($x<$len) {
		  substr($line,$x,1) =~ tr/a-z/A-Z/;
		  while ($x<$len && substr($line,$x,1) =~ /\w/) { ++$x; }
                }
		&goto_col($x);
	} elsif ($char eq 'd' && $col<$len) {				# d
		local($s)=$col;
		$x=$s;
		while ($x<$len && substr($line,$x,1) =~ /\W/) { ++$x; }
		while ($x<$len && substr($line,$x,1) =~ /\w/) { ++$x; }
		local($l)=$x-$s;
		substr($line,$s,$x-$s)='';
		$len = length($line);
		local($left)=$len-$s;
		print substr($line,$s),substr($blanks,0,$l),
			substr($back_spaces,0,$l+$left);
#		&goto_col($x);
	} elsif ($char eq 'f' && $col<$len) {				# f
		local($x)=$col;
		while ($x<$len && substr($line,$x,1) =~ /\w/) { ++$x; }
		while ($x<$len && substr($line,$x,1) =~ /\W/) { ++$x; }
		&goto_col($x);
	} elsif ($char eq 'g' && ($kill_col > 0) && ($kill_col<=$len)) {
		&goto_col($kill_col);
	} elsif ($char eq 'i' && $insert_mode == 0) {			# i
		$insert_mode=1;
	} elsif (($char eq 'l' || $char eq 'u') && $len && $col < $len) {			# l,u
		local($x)=$col;
		while ($x<$len && substr($line,$x,1) =~ /\W/) { ++$x; }
		if ($x<$len) {
		  local($y)=$x;
 		  while ($x<$len && substr($line,$x,1) =~ /\w/) { ++$x; }
		  if ($char eq 'l') {
			substr($line,$y,$x-$y) =~ tr/A-Z/a-z/;
		  } else {
			substr($line,$y,$x-$y) =~ tr/a-z/A-Z/;
		  }
                }
		&goto_col($x);
	} elsif ($char eq 'o' && $insert_mode) {			# o
		$insert_mode=0;
	} elsif ($char eq 'w' && ($kill_col >= 0) && ($kill_col<=$len)) {
		if ($col >= $kill_col) {
			$kill_buffer=substr($line,$kill_col,$col-$kill_col);
		} else {
			$kill_buffer=substr($line,$col,$kill_col-$col);
		}
	} elsif ($char eq 'x' && ($kill_col > 0) && ($kill_col<=$len)) {
		local($t)=$col;
		&goto_col($kill_col);
		$kill_col=$t;
	} elsif ($char eq ' ') {
		$kill_col=$col;
	} elsif ($char eq '=') {
		&show_files;
	    } elsif ($char eq '[') {
		&handle_arrows;
	    }
	
	else {
		print $k_ctrl_g;
	}
}

sub handle_arrows {
    local($char) = getc(STDIN);

    if ($char eq 'D' && $col){
	&goto_col($col-1);
    } elsif ($char eq 'C' && $col < $len){
	&goto_col($col+1);
    } elsif ($char eq 'A' || $char eq 'B'){
	if ($char eq 'B') {$i = ($i+1) % $max_index; }
		else { $i = ($i-1) % $max_index;}
		if ($len) { print "\r",$prompt,substr($blanks,0,$len); }
		$col = ($len = length($line = $recall_buffer[$i]));
	        print "\r",$prompt,$line;
    } else{
	print $k_ctrl_g;
    }
}


sub find_file_name { # return "." if cursor not on a file
  local($si,$ei);
  if ($len==0){ return ""; }

  if ($col==$len) {
     if (substr($line,$col-1,1) eq ' ') { return ""; }
     $si=$ei=$col-1;
     while($si && substr($line,$si-1,1) =~ /\S/) { $si--; }
     if ($si==0 && substr($line,0,1) =~ /\s/) {$si++;}
     return substr($line,$si,$ei-$si+1); 
  }

    $si=$col;
     if (substr($line,$col-1,1) =~ /\s/) {
		if (substr($line,$col,1) =~ /\s/) { return ""; }
     } else {
        while($si && substr($line,$si-1,1) =~ /\S/) { $si--; }
        if ($si==0 && substr($line,0,1) =~ /\s/) {$si++;}
     }

     $ei=$col;     
     while($ei < $len && substr($line,$ei,1) =~ /\S/) { $ei++; }
     if ($ei==$len ) {$ei--;}
     return substr($line,$si,$ei-$si+1); 
  
}

sub show_files {

local($max_length,@files)=0;

local($the_dir,$the_file);

local($_)=&find_file_name;

if ($_ eq "") {	$the_dir=".";	$the_file="";} 
else {
	if (m!/!) {($the_dir,$the_file) = m|(^.*/)(.*$)|; }
	else { $the_dir="."; $the_file=$_; }
}

  if (opendir(EXPAND_DIR,$the_dir)) {
	  while($_=readdir(EXPAND_DIR)) {
	    if (/^$the_file/) {
	        if (length($_)>$max_length) { $max_length=length($_); }
	        push(@files,$_);
            }
	  }
	  closedir(EXPAND_DIR);
	  $max_length+=5; 

	  local($num)= $#files+1; 
	  local($cpl)=int($num_cols/$max_length);
	  local($nl) =int($num/$cpl+0.5);
	  @files = sort(@files);
	  local($l,$n,$nc)=(0,0,0);

          while ($n< $num) {
		$nc=$cpl;
		print "\n\r";
		local($f)=0;
		while ($nc-- && $n < $num) {
			$_ = $files[$l+$f];
			if (-d "$the_dir/$_") { $_ .= "/"; }
			printf("%-${max_length}s",$_);
			$n++;
			$f += $nl;
		}
		$l++;
          }
	  &refresh;
  } else {
	print $k_ctrl_g;	
  }

}

1;

__END__

32 <= char <= 126	normal character insert
^A			beginning of line
^B			back one character
^C			sends INT signal
^D			delete current character
^E			end of line
^F			forward one character
^G			goto mark
^H			backspace 
^J			return line
^K			kill to end of line
^L			redraw line
^M			return line
^N			next line in recall buffer
^P			previous line in recall buffer
^R			reverse search for regex. Current line is taken as 
                                                           search string
^T			twiddle previous two characters
^U			kill line
^V			show version
^W			wipe from cursor to mark
^Y			yank kill buffer
^Z			suspend
^H, del			del character


Esc b			move cursor to start of previous word
Esc c			capitilize  current/next word and move to end of word
Esc d			delete from cursor to end of next word
Esc f			move cursor to start of next word
Esc i			enter insert mode
Esc l			lowercase
Esc o   		enter overwrite mode
Esc u			uppercase
Esc w			copy from mark to cursor
Esc x			eXchange cursor and mark
Esc space 		(set mark)
Esc =		 	list pathnames matching current word

Not done/used:


^I			smart expand? (past list of words which can be expanded)


Esc (number)		repeat command
Esc g 			goto line
Esg p   		print lines in buffer
Esc h   		delete from current cursor to pervious word
Esc *   		expand all matching file names
Esc Esc 		expand current file name
