#!/usr/local/bin/perl # PerlVision - A class library for text-mode user interface widgets. # By Ashish Gulhati (hash@netropolis.org) # V.0.2.0 # # Copyright (c) Ashish Gulhati, 1995. All Rights Reserved. # # You may use and distribute this code under the same terms as Perl itself. # RAP (Rap Ain't Pico) is a small text editor provided as an example # of how to code using PerlVision and what PerlVision programs look # like. If you'd like to provide rap as an editor on your system for # inexperienced users, just great! They'll get used to emacs keybindings # for basic stuff and won't find it intimidating to move to the mother # of all editors later. ;-) I use rap as a pico replacement on my # system. require 5.000; use Curses; use PV; package MY_Editor; use Curses; use PV; @ISA = (PV::Editbox); sub new { # A better mousetrap my $type=shift; # MY_Editor (margin,text,index,filename); my @params=(@_); my $self=new PV::Editbox (1,3,78,22,@params[0..2],0); move (23,0); attron (COLOR_PAIR(7)); addstr(" <Esc>-H for Help <Esc>-X for Menu"); $$self[12]=0; # Mark position $$self[13]="INS TOP"; # status $$self[14]=""; # status shown $$self[15]=$params[3]; # filename $$self[16]=""; # Kill buffer $$self[17]=0; # Kill buffer clear toggle ($$self[15] =~/^~/) && (substr ($$self[15],0,1)=(getpwuid ($<))[7]); bless $self; } sub statusbar { my $self=shift; substr ($$self[13],0,3) = ($$self[11] ? "OVT" : "INS"); if ($$self[13] ne $$self[14]) { move (23,70); addstr($$self[13]); refresh(); $$self[14]=$$self[13]; } } sub cursor { my $self=shift; my $total = $#{$$self[9]}; my @ret=$self->PV::Editbox::cursor(); if ($$self[8]==0) { if ($total < 18) { substr ($$self[13],5) = "ALL"; } else { substr ($$self[13],5) = "TOP"; } } elsif (($$self[8]+18) > $total) { substr ($$self[13],5) = "BOT"; } else { my $percent = $ret[1]/$total*100; substr ($$self[13],5)=sprintf("%2d%",$percent); } return @ret; } sub help { my $helptext="Help for RAP? You want help for RAP??? Just kidding. RAP is a very basic editor that uses emacs-like keystrokes for most things. Here's a summary: Keystroke sequences are presented in the Emacs format: C-<key> means hold down 'CONTROL' and type <key>. M-<key> means hold down 'ALT' and type <key>, or alternately, press 'ESCAPE' and then press <key>. Movement Commands: Arrow keys, Page Up, Page Down should work. If not, there are alternates: C-f Move forward one character C-b Move back one character C-n Move down one line C-p Move up one line C-v Move down a page M-v Move up a page C-a Move to beginning of line C-e Move to end of line M-< Move to beginning of text M-> Move to end of text Editing Commands: Typing any printable character inserts it into your text. Del, backspace, and insert should work. If not, there are alternates: M-i Toggles insert/overwrite mode C-h Backspace C-d Delete M-d Delete word forward C-k Delete to end of line C-k behaves the way it does in Emacs. The first time around, it kills everything on the line. The second time around, it kills the line itself. Any text killed with C-k or M-d is inserted into the 'cut buffer', and can be pasted back into your text at the same or a different location with a C-y. See below. Block Commands: Any command that kills more than one character of text adds that to the 'cut buffer'. The first time you kill multiple characters, the cut buffer is started afresh, and subsequent kills keep adding to it, until you do something that is not a multi-character kill. All the text in the cut buffer can be pasted back into your text with a C-y Paste cut buffer into text This doesn't clear the cut buffer, and you can paste as many times as you like. Other commands that operate on blocks: C-<space> Sets 'mark' at current cursor position. C-w Kills all text between 'mark' and current cursor position. M-w Copy all text between 'mark' and current cursor position to the cut buffer, but don't kill it. With these commands you can go to some point in your text, set 'mark', then go to some other position, and use C-w or M-w to either kill the region or copy it to the cut buffer without killing it. Now it's ready to be pasted back into your text wherever you like. RAP doesn't, and never will, support a kill-ring like Emacs. File Commands: C-x C-f Opens a file for editing C-x C-i Inserts another file into your text C-x s Saves the file you are editing C-x C-w Write editor text out to some other file C-x k Forget current file and clear the editor C-x C-c Quits the editor Miscellaneous Commands: M-x Gets you to the menu M-h Does something very dangerous. DO NOT USE! The menu bar: A number of the things mentioned above can also be done from the menu bar, but that's really just there because it looks pretty. Everything can be accomplished much faster using the right keystrokes. Two things that can be accessed only from the menu bar are the RAP 'About Box' and the Preferences dialog (which has a lot of useful options you can set to customize your RAP editing environment just like with Emacs lisp ;-) Shareware Terms & Emacs: If after 10 days of using RAP you find it useful you must either switch to Emacs and delete RAP from your disk, or send me \$1,000,000 at: 140 Sunder Nagar, New Delhi - 110003, India. Please send only A/C payee checks made out to 'Ashish Gulhati' and write your name and email address at the back of the check. Shareware only works if authors have the support of users like yourself. Please don't ignore these shareware terms. Thank you! _/_/_/ _/ _/_/_/ _/ _/ _/_/ _/ _/ _/_/_/ _/_/_/ _/_/_/ _/_/ _/_/_/_/ _/ _/ _/_/_/_/_/ _/ _/ _/ RAP Ain't Pico (C) 1995, Ashish Gulhati"; my $help1 = new PV::Viewbox (2,1,62,12,$helptext,0); my $help2 = new PV::Pushbutton (" OK ",28,13); my $help = new PV::Dialog ("",8,5,72,21,1,1, $help1,0,0,0,0,1,1,2,0, $help2,1,2,2,1,2,3,1,0); $help->activate; } sub options { my $fun0 = new PV::Static ("On Startup:",2,1,40,9); my $fun1 = new PV::Checkbox ("Mail president\@whitehouse.gov",2,3,0,1); my $fun2 = new PV::Checkbox ("Play a Rush song:",2,4,0,1); my $fun3 = new PV::Listbox ("Song",4,6,22,11, "Force Ten",0, "The Trees",0, "Kid Gloves",0, "Tom Sawyer",0, "Tai Shan",0, "2112",0, "Red Sector A",0, "Freewill",0, "Anthem",0); my $fun4 = new PV::Radio ("Lo Volume",24,6,0,1); my $fun5 = new PV::Radio ("Hi Volume",24,7,1,1); my $fun6 = new PV::RadioG ($fun4, $fun5); my $fun7 = new PV::Pushbutton ("Yes",27,9); my $options = new PV::Dialog ("",20,8,59,20,1,1, $fun1,1,2,2,1,1,1,2,0, $fun2,1,3,3,1,2,2,3,0, $fun3,0,0,4,2,3,3,4,4, $fun4,2,5,5,3,4,4,5,0, $fun5,4,6,6,3,5,5,6,0, $fun7,5,6,6,3,6,6,1,0, $fun0,0,0,0,0,0,0,0,0); $options->activate; } sub readfile { my $filename=shift; my $text=""; ($filename =~/^~/) && (substr ($filename,0,1)=(getpwuid ($<))[7]); open (INPUT, $filename) || return (0); foreach (<INPUT>) { $text.=$_; } close INPUT; return (1,$text); } sub dirty { my $self=shift; my $dirty = ($$self[6] eq "\n") ? 0 : 1; if ($$self[15]) { my @origfile = readfile ($$self[15]); if ($origfile[0]) { $origfile[1].="\n"; ($origfile[1] eq $$self[6]) && ($dirty = 0); } } return $dirty; } sub findfile { my $self=shift; if ($self->dirty) { return 0 unless (PV::PVD::yesno ("Sure you want to abandon this file?",40,1)); } my $newfile = new PV::Entryfield (2,23,50,0,"Find File:",""); $newfile->display; $newfile->activate; my $filename = ($newfile->stat); undef $newfile; move (23,0); attron (COLOR_PAIR(7)); addstr(" <Esc>-H for Help <Esc>-X for Menu "); refresh(); if ($filename) { my ($ret, $initial) = readfile ($filename); if ($ret) { $$self[6]=$initial."\n"; $$self[7]=0; $$self[15]=$filename; ($$self[15] =~/^~/) && (substr ($$self[15],0,1)=(getpwuid ($<))[7]); $self->justify(1); $self->rfsh(); } else { PV::PVD::message ("Error: Couldn't open file.",30,1); } } $self->cursor; refresh(); } sub insertfile { my $self=shift; my $newfile = new PV::Entryfield (2,23,48,0,"Insert File:",""); $newfile->display; $newfile->activate; my $filename = ($newfile->stat); undef $newfile; move (23,0); attron (COLOR_PAIR(7)); addstr(" <Esc>-H for Help <Esc>-X for Menu "); refresh(); if ($filename) { my ($ret, $initial) = readfile ($filename); if ($ret) { substr ($$self[6],$$self[7],0)=$initial; $self->justify(1); $self->rfsh(); } else { PV::PVD::message ("Error: Couldn't open file.",30,1); } } $self->cursor; refresh(); } sub saveas { my $self=shift; my $savefile = new PV::Entryfield (2,23,52,0,"Save As:",$$self[15]); $savefile->display; $savefile->activate; my $filename = ($savefile->stat); ($filename =~/^~/) && (substr ($filename,0,1)=(getpwuid ($<))[7]); undef $savefile; move (23,0); attron (COLOR_PAIR(7)); addstr(" <Esc>-H for Help <Esc>-X for Menu "); refresh(); if ($filename) { if (-e $filename) { return unless (PV::PVD::yesno ("File exists. Overwrite?",30,1)); } $$self[15]=$filename; $self->savefile; } $self->cursor; refresh(); } sub savefile { my $self=shift; if ($$self[15]) { if ($self->dirty) { open (SAVE, ">$$self[15]") || do { PV::PVD::message ("Error: Couldn't open file.",40,12); return; }; chop $$self[6]; print SAVE $$self[6]; close (SAVE); $$self[6].="\n"; } } else { $self->saveas(); } } sub killfile { my $self=shift; if ($self->dirty) { return 0 unless (PV::PVD::yesno ("Sure you want to abandon this file?",40,1)); } $$self[6]="\n"; $$self[7]=0; $$self[15]=""; $self->justify(1); $self->rfsh(); return 1; } sub word { my $self=shift; my $dir=shift; my $chars=""; if ($dir) { $chars = substr ($$self[6],$$self[7]); $chars =~ s/(\W*\w+).*/$1/s; } else { $chars = substr ($$self[6],0,$$self[7]); $chars =~ s/.*\W(\w)/$1/s; } return (length ($chars)); } sub wordforward { my $self=shift; $$self[7]+=$self->word(1); $$self[7]==length($$self[6]) && $$self[7]--; $self->cursor; refresh(); } sub wordback { my $self=shift; $$self[7]-=$self->word(0); $self->cursor; refresh(); } sub wordkill { my $self=shift; my $chars = $self->word(1); $$self[7]+$chars==length($$self[6]) && $chars--; $$self[17] && ($$self[16].=substr ($$self[6],$$self[7],$chars)); $$self[17] || ($$self[16]=substr ($$self[6],$$self[7],$chars)); $$self[17] = 1; substr ($$self[6],$$self[7],$chars) = ""; $self->justify(); $self->rfsh; $self->cursor; refresh(); } sub linekill { my $self=shift; my $chars = substr ($$self[6],$$self[7]); $chars =~ s/\n.*/\n/s; if ($chars eq "\n") { $chars = 1; } else { $chars = length ($chars)-1; } $$self[7]+$chars==length($$self[6]) && $chars--; $$self[17] && ($$self[16].=substr ($$self[6],$$self[7],$chars)); $$self[17] || ($$self[16]=substr ($$self[6],$$self[7],$chars)); $$self[17] = 1; substr ($$self[6],$$self[7],$chars) = ""; $self->justify(); $self->rfsh; $self->cursor; refresh(); } sub setmark { my $self=shift; $$self[12]=$$self[7]; move (23,0); attron (COLOR_PAIR(3)); addstr(" Mark Set "); refresh(); sleep (1); move (23,0); attron (COLOR_PAIR(7)); addstr(" <Esc>-H for Help <Esc>-X for Menu"); $self->cursor; refresh(); } sub regionkill { my $self=shift; my $reallykill=shift; my $start = (($$self[12] > $$self[7]) ? $$self[7] : $$self[12]); my $chars = (($$self[12] > $$self[7]) ? $$self[12] : $$self[7]) - $start; $$self[17] && ($$self[16].=substr ($$self[6],$start,$chars)); $$self[17] || ($$self[16]=substr ($$self[6],$start,$chars)); $$self[17] = 1; if ($reallykill) { $$self[7]=$start; substr ($$self[6],$start,$chars) = ""; $self->justify(); $self->rfsh; $self->cursor; refresh(); } } sub yank { my $self=shift; substr ($$self[6],$$self[7],0)=$$self[16]; $$self[7]+=length($$self[16]); $self->justify(1); $self->rfsh(); $self->cursor; refresh(); } sub beginning { my $self=shift; $$self[7]=0; $self->cursor; refresh(); } sub end { my $self=shift; $$self[7]=length($$self[6])-1; $self->cursor; refresh(); } sub process_key { my $self=shift; my @key=(@_); if ($key[1]==14) { $self->wordkill; return (2); } elsif ((!$key[1]) && ($key[0] eq "")) { $self->linekill; return (2); } elsif ((!$key[1]) && ($key[0] eq "")) { $self->regionkill(1); return(2); } elsif ($key[1]==22) { $self->regionkill(0); return(2); } $$self[17]=0; (($key[1]==200) && ($key[0] eq "\t")) && (return (2)); # Override TAB if ((!$key[1]) && ($key[0] eq "