#!/usr/bin/perl -w # # Xanadu(R) Zigzag(tm) Hyperstructure Kit curses interface, $Revision: 0.70 $ # # Designed by Ted Nelson # Programmed by Andrew Pam ("xanni") and Bek Oberin ("gossamer") # Copyright (c) 1997-1999 Project Xanadu # # This is only a partial implementation, with only a few structure and view # operations; however, they are enough to allow you to create, view and explore # complex multidimensional structures in quantum hyperspace. # # A forthcoming tutorial will present you with strange spaces to explore, # beginning with easy 2D concepts, the peculiar geography of this system and # operating recommendations for getting around in it comfortably and changing # your structures without losing cells or losing track of your stuff. (This # will emphasize pragmatic adaptations to the peculiarities of this limited # implementation.) # # Forthcoming documentation will explain the space, the theory, structure # operations, view operations, and the official planned extensions. # # For more information, visit http://www.xanadu.net/zz/ # # ===================== Change Log # # Inital zigzag implementation # $Id: zigzag,v 0.70 1999/05/14 13:45:51 xanni Exp $ # # $Log: zigzag,v $ # Revision 0.70 1999/05/14 13:45:51 xanni # * Fixed missing "defined" bug in "<", ">", "b" and "h" keymappings # * Replaced direct access to %ZZ with cell_get(), cell_set() and cell_nbr() # * Changed db_open() and db_close() to slice_open() and slice_close_all() # * Simplified input_process_digit() and input_process_backspace() # # Revision 0.68 1999/03/13 13:06:35 xanni # Added two new user_error() messages for invalid deletion attempts # # Revision 0.67 1999/03/13 05:03:44 xanni # Minor naming fixes, cell_create() moved from Zigzag.pm # # Revision 0.66 1999/01/07 07:17:26 xanni # Implemented display_draw_layout() to replace window_draw_* functions # # Revision 0.65 1999/01/07 04:03:04 xanni # Derived from the monolithic zigzag 0.64 # # Revision 0.64 1999/01/07 03:55:11 xanni # Minor cosmetic changes # # Revision 0.63 1999/01/06 05:07:14 xanni # Created display_dirty() and generally fixed up dirty handling # Minor cleanup of error handling and Meta support # # Revision 0.62 1998/11/23 13:59:55 xanni # Renamed the "Midden" cell to "Recycle pile", fixed Meta (aka Alt) support # # Revision 0.61 1998/11/21 16:46:33 xanni # Defined $Hcells and $Vcells, fixed $Cell_Width bug, # created display_draw_link_* functions, finished making # window_draw_* functions independent of the UI toolkit. # # Revision 0.60 1998/11/21 08:24:21 xanni # Introduced window_draw_preview, cleaned up display_draw_quad # # Older revisions are listed in the CHANGES file # use integer; use strict; use Curses; # Available at http://www.cpan.org/modules/by-module/Curses use POSIX; #require 'sys/ioctl.ph'; use Zigzag; # Define some aliases for consistency *display_clear = *Curses::clear; *display_refresh = *Curses::refresh; # Note: We are using the following coding conventions: # Constants are named in ALLCAPS # Global variables are named with Initial Caps # Local variables and functions are named in lowercase # Put brackets around all function arguments # Prototype the number and type of arguments expected by functions # Matching braces should line up with each other vertically # Use the $TRUE and $FALSE constants instead of "1" and "0" # Define constants #my ($VERSION) = q$Revision: 0.70 $ =~ /([\d\.]+)/; my $VERSION = q$Id: zigzag,v 0.70 1999/05/14 13:45:51 xanni Exp $; my $FALSE = 0; my $TRUE = !$FALSE; my $CELLS_PER_WIN = 5; # Number of cells displayed across each window my $EDITOR = choose_editor(); my $TEMP_FILE = "/tmp/zigzag-$<-$^T"; # Filename used for external editing my $LOTS_OF_COLOURS = $TRUE; # What style my $USE_STATUSBAR = $TRUE; # Are we using the status bar? my $STATUS_DELAY = 20; # Tenths of a second to display status info my $COMMAND_SYNC_COUNT = 20; # Sync the DB after this many commands # Declare globals my $StatusTimer; # Times messages' duration on the status bar my @Window_Dirty; # Flags to indicate windows need redrawing my $Status_Dirty; # Flags to indicate status bar needs redrawing #my $Input_Buffer; # Cell number entered from the keyboard my $Display_Resized; # True when window has been resized my $Zigzag_Terminated; # True when interrupted by a signal #my $Command_Count; # Counts commands between DB syncs #my $Hcells; # Horizontal cells per window #my $Vcells; # Vertical cells per window # curses-specific globals my @Window; # Curses window handles my $Cell_Width; # Current display width of the cells my $Display_Has_Colour; # True if colour is supported my $Status; # Status bar handle my $Window_Xcentre; # Centre column of curses windows my $Window_Ycentre; # Centre row of curses windows # Initialise global zigzag data structures my %Keymap_Directions = ( # Direction key mappings "s" => "0L", "S" => "0L", "f" => "0R", "F" => "0R", "e" => "0U", "E" => "0U", "c" => "0D", "C" => "0D", "d" => "0I", "D" => "0O", "j" => "1L", "J" => "1L", &KEY_LEFT => "1L", "l" => "1R", "L" => "1R", &KEY_RIGHT => "1R", "i" => "1U", "I" => "1U", &KEY_UP => "1U", "," => "1D", &KEY_DOWN => "1D", "k" => "1I", &KEY_PPAGE => "1I", "K" => "1O", &KEY_NPAGE => "1O", ); my %Keymap = ( # Keyboard mappings "\r" => 'atcursor_execute(0);', "\n" => 'atcursor_execute(0);', "<" => '@_ = input_get_direction(); atcursor_import(@_) if defined $_[0];', ">" => '@_ = input_get_direction(); atcursor_export(@_) if defined $_[0];', chr(127) => 'atcursor_delete(1);', &KEY_DC => 'atcursor_delete(1);', &KEY_BACKSPACE => 'input_process_backspace();', meta_key("s") => 'atcursor_hop(0, "L");', meta_key("f") => 'atcursor_hop(0, "R");', meta_key("e") => 'atcursor_hop(0, "U");', meta_key("c") => 'atcursor_hop(0, "D");', meta_key("D") => 'atcursor_edit(0);', meta_key("d") => 'atcursor_edit(0);', "\cD" => 'atcursor_edit(0);', meta_key("j") => 'atcursor_hop(1, "L");', meta_key("l") => 'atcursor_hop(1, "R");', meta_key("i") => 'atcursor_hop(1, "U");', meta_key(",") => 'atcursor_hop(1, "D");', meta_key("K") => 'atcursor_edit(1);', meta_key("k") => 'atcursor_edit(1);', "\cK" => 'atcursor_edit(1);', "b" => '@_ = input_get_direction(); atcursor_break_link(@_) if defined $_[0];', "G" => 'cursor_jump_input(get_cursor(0));', "g" => 'cursor_jump_input(get_cursor(1));', &KEY_HOME => 'cursor_jump_input(get_cursor(1));', "h" => '@_ = input_get_direction(); atcursor_hop(@_) if defined $_[0];', "\cL" => 'display_refresh(curscr()); $@ = "";', "M" => 'atcursor_select(0);', "m" => 'atcursor_select(1);', meta_key("m") => 'rotate_selection()', meta_key("M") => 'push_selection()', "N" => 'cell_create(0);', &KEY_IC => 'cell_create(0);', "n" => 'cell_create(1);', "Q" => 'view_quadrant_toggle(0);', "q" => 'view_quadrant_toggle(1);', "R" => 'view_reset(0);', "r" => 'view_reset(1);', "T" => 'atcursor_clone(0);', "t" => 'atcursor_clone(1);', meta_key("T") => 'atcursor_copy(0);', meta_key("t") => 'atcursor_copy(1);', "V" => 'view_raster_toggle(0);', "v" => 'view_raster_toggle(1);', "\cV" => 'display_status_draw(version());', meta_key("V") => 'display_status_draw(version());', meta_key("v") => 'display_status_draw(version());', "X" => 'view_rotate(0, "X");', "x" => 'view_rotate(1, "X");', "\cX" => '$Zigzag_Terminated = "^X";', meta_key("X") => 'view_flip(0, "X");', meta_key("x") => 'view_flip(1, "X");', "Y" => 'view_rotate(0, "Y");', "y" => 'view_rotate(1, "Y");', meta_key("Y") => 'view_flip(0, "Y");', meta_key("y") => 'view_flip(1, "Y");', "Z" => 'view_rotate(0, "Z");', "z" => 'view_rotate(1, "Z");', meta_key("Z") => 'view_flip(0, "Z");', meta_key("z") => 'view_flip(1, "Z");', ); # # Some helper functions # sub version() { return "zigzag version $VERSION (Zigzag $Zigzag::VERSION)"; } sub meta_key($) # This is just a little helper macro, returns the META/ALT key code { return(chr(ord($_[0]) | 0x80)); } sub cursor_jump_input($) # Jump cursor to $Input_Buffer # Or to 0 if no input buffer. { my $dest = defined($Input_Buffer) ? $Input_Buffer : 0; cursor_jump($_[0], $dest); undef $Input_Buffer; $Status_Dirty = $TRUE; } sub cell_create($) # Create a new cell and optionally edit it { my $edit = $_[0]; my ($curs, $dir) = input_get_direction(); if ($curs) { atcursor_insert($curs, $dir); cursor_move_direction($curs, $dir); atcursor_edit($curs) if $edit; } } sub atcursor_edit($) # Invoke an external text editor to edit the cell under a given cursor { my $cell = get_lastcell(get_lastcell(get_cursor($_[0]), "-d.cursor"), "-d.clone"); # Save $cell contents in a temporary file open(TEMP, ">$TEMP_FILE") || die "Can't open \"$TEMP_FILE\": $!\n"; print TEMP cell_get($cell), "\n"; close(TEMP); # Invoke the editor on the temporary file display_close(); if (!system("$EDITOR $TEMP_FILE")) { undef $/; # Read entire file into cell open(TEMP, "<$TEMP_FILE") || die "Can't open \"$TEMP_FILE\": $!\n"; $_ = ; close(TEMP); $/=""; # Remove trailing blank lines chomp; cell_set($cell, $_); } display_open(); unlink $TEMP_FILE; } sub atcursor_import(@) # Import cells from a text file { my $curs = get_cursor($_[0]); my $cell = get_lastcell($curs, "-d.cursor"); my $dim = get_dimension($curs, $_[1]); # Not in the Cursor dimension! return if $dim eq "d.cursor"; # Invoke the editor on the temporary file display_close(); system("$EDITOR $TEMP_FILE"); display_open(); $/ = ""; # Blank lines are separators if (open(TEMP, "<$TEMP_FILE")) { while() { chomp; if ($_) { s/\n\|/\n/g; # Strip off protective bars my $new = cell_new($_); cell_insert($new, $cell, $dim); $cell = $new; } } close(TEMP); display_dirty(); } unlink $TEMP_FILE; } sub atcursor_export(@) # Export cells to a text file { my $curs = get_cursor($_[0]); my $dim = get_dimension($curs, $_[1]); my $start = get_lastcell($curs, "-d.cursor"); my $index = $start; my $loop = $FALSE; open(TEMP, ">$TEMP_FILE") || die "Can't open \"$TEMP_FILE\": $!\n"; while (defined $index && !$loop) { if (!defined cell_nbr($index, "-d.cursor")) # Don't export cursor cells { my $cell; foreach $cell (get_contained($index)) { print TEMP "\n|" unless $cell eq $index; # Separate contained cells $_ = cell_get(get_lastcell($cell, "-d.clone")); # $_ = get_cell_contents($cell); chomp; s/\n/\n\|/g; # Protect blank lines in cells print TEMP; } print TEMP "\n\n"; } $index = cell_nbr($index, $dim); $loop = $index eq $start; } close(TEMP); # Invoke the editor on the temporary file display_close(); system("$EDITOR $TEMP_FILE"); display_open(); unlink $TEMP_FILE; } # # Functions that are implemented using a particular user interface toolkit # (Currently curses) # Named: display_* # sub display_open() # (Re)initialise display { initscr(); # Initialise Curses $@ = ""; # Ignore Curses error cbreak(); # Disable line buffering noecho(); # Disable echo intrflush(0); # Disable buffer flush on interrupts keypad(1); # Enable function keys timeout(0); # Use non-blocking input nonl(); # Disable CRLF conversion leaveok(1); # Disable cursor $Display_Has_Colour = has_colors(); # For Curses to know if it can use colour $LOTS_OF_COLOURS = $FALSE unless $Display_Has_Colour; # set up for colours if ($Display_Has_Colour) { start_color(); init_pair(1, COLOR_WHITE, COLOR_BLACK); init_pair(2, COLOR_YELLOW, COLOR_BLACK); init_pair(3, COLOR_BLACK, COLOR_WHITE); init_pair(4, COLOR_BLUE, COLOR_BLACK); init_pair(5, COLOR_GREEN, COLOR_BLACK); init_pair(6, COLOR_BLACK, COLOR_GREEN); init_pair(7, COLOR_BLACK, COLOR_BLUE); init_pair(8, COLOR_YELLOW, COLOR_GREEN); init_pair(9, COLOR_BLACK, COLOR_BLUE); } display_clear(); # Clear screen display_refresh(); my $Reserved = 0; # Lines for status bar if ($USE_STATUSBAR) { $Reserved = 1; $Status = newwin(1, $COLS, $LINES - 1, 0); bkgd($Status, $Display_Has_Colour ? COLOR_PAIR(3) : A_REVERSE); $Status_Dirty = $TRUE; } $Window[0] = newwin($LINES - $Reserved, $COLS / 2, 0, 0); $Window[1] = newwin($LINES - $Reserved, $COLS / 2, 0, $COLS / 2); $Hcells = $CELLS_PER_WIN; $Vcells = int(($LINES + 1 - $Reserved) / 4) * 2 - 1; $Cell_Width = int(($COLS - 4) / ($CELLS_PER_WIN * 2)); $Window_Xcentre = int($COLS / 4) - int($Cell_Width / 2); $Window_Ycentre = int(($LINES - 1) / 2); # Mark all windows dirty to ensure they all get redrawn # We can't use foreach (@Window_Dirty) because the first time this code # is executed @Window_Dirty is empty and this code initialises it. for ($_ = 0; $_ <= $#Window; $_++) { $Window_Dirty[$_] = $TRUE; } } sub display_close() # Free all windows and exit Curses { foreach (@Window) { delwin($_); } delwin($Status) if $USE_STATUSBAR; endwin(); } sub display_resize() # Handle display size changes { # NOTE: Calling initscr() more than once is non-portable. It seems to cause # strange visual effects, and sometimes even segfaults! The commented # out code below is the proper way to do it, but unfortunately modifying # $LINES and $COLS doesn't seem to get passed back to curses by the Perl # Curses interface. :-( display_close(); # my $getwinsz = &TIOCGWINSZ || die "No TIOCGWINSZ"; # my $winsz = "ss"; # ioctl(STDIN, $getwinsz, $winsz) || die "TIOCGWINSZ failed"; # ($LINES, $COLS) = unpack("ss", $winsz); display_open(); $Display_Resized = $FALSE; } sub display_dirty() # The data structure has changed, so we will have to redraw { foreach (@Window_Dirty) { $_ = $TRUE; } } sub display_status_draw($) # Actually draw the status bar, if enabled { return unless $USE_STATUSBAR; erase($Status); attrset($Status, $Display_Has_Colour ? COLOR_PAIR(3) : A_REVERSE); if ($_[0]) { addstr($Status, 0, 0, $_[0]); $StatusTimer = $STATUS_DELAY; } addstr($Status, 0, $COLS - 10, $Input_Buffer) if defined($Input_Buffer); display_refresh($Status); $Status_Dirty = $FALSE; } sub display_monospace($$) # Helper function to trim or space-pad text to a given width # Only needed for user interfaces with monospaced fonts { my ($contents, $width) = @_; if (!defined $contents || ($contents eq "")) { $_ = " " x $width; } else { $contents =~ tr/\t/ /; # Tab compression my $len = length($contents); # Trim and/or pad with trailing spaces as necessary if ($len == $width) { $_ = $contents; } elsif ($len > $width) { $_ = substr($contents, 0, $width); } else # $len < $width { $_ = substr($contents, 0, $len) . " " x ($width - $len); } } } # # Functions that draw things in a given window. The windows are a # logical thing that may be any subsection of the display, or all # of it. # Named: display_draw_* # sub display_draw_cell($$$$) # Draw cell at row and column { my ($win, $cell, $row, $col) = @_; my $cursor = cell_nbr($cell, "+d.cursor") unless defined cell_nbr($cell, "-d.cursor"); my $number = -1; my $content; while (defined $cursor) { $cursor = cell_nbr($cursor, "-d.2"); $number++; } ($content) = split(/\n/, get_cell_contents($cell)); # Just the first line # Clone cells get a special colour in high-colour mode attron($win, COLOR_PAIR(2) | A_BOLD) if $LOTS_OF_COLOURS && is_clone($cell); # Ditto selected cells attron($win, COLOR_PAIR(3) | A_BOLD) if $LOTS_OF_COLOURS && is_selected($cell); # Colour/highlight the cursors attron($win, $Display_Has_Colour ? COLOR_PAIR(6) : A_UNDERLINE) if $number == 1; attron($win, $Display_Has_Colour ? COLOR_PAIR(7) : A_REVERSE) if $number > 1; addnstr($win, $Window_Ycentre + $row * 2, $Window_Xcentre + $col * $Cell_Width, display_monospace($content, $Cell_Width - 1), $Cell_Width - 1); # End colour/highlight attrset($win, A_NORMAL); } sub display_draw_quad($$$) # Draw all or part of a quad cell { my ($win, $cell, $preview) = @_; my $contents = ""; my $width = ($COLS / 2 - 1) - $Window_Xcentre; # $maxlen = the maximum amount of cell contents we'll have room to display my $maxlen = $width * $Window_Ycentre; @_ = get_contained($cell); do { $contents .= substr(get_cell_contents(shift), 0, $maxlen) . "\n"; } until (($#_ < 0) || (length($contents) >= $maxlen)); $_ = wordbreak($contents, $width); $contents = substr($contents, length); attrset($win, A_REVERSE); addnstr($win, $Window_Ycentre, $Window_Xcentre, display_monospace($_, $width), $width); attrset($win, A_NORMAL); return if $preview; # Draw the remainder of the cell contents my $i; for ($i = 1; $Window_Ycentre + $i < $LINES - ($USE_STATUSBAR ? 2 : 1); $i++) { $_ = wordbreak($contents, $width); $contents = substr($contents, length); attrset($win, A_REVERSE); addnstr($win, $Window_Ycentre + $i, $Window_Xcentre, display_monospace($_, $width), $width); attrset($win, A_NORMAL); } } sub display_draw_link_horizontal($$$) # Draw a horizontal connection next to a cell { my ($win, $y, $x) = @_; addch($win, $Window_Ycentre + $y * 2, $Window_Xcentre + ($x < 0 ? $x + 1 : $x) * $Cell_Width - 1, ACS_HLINE); } sub display_draw_link_vertical($$$) # Draw a vertical connection above or below a cell { my ($win, $y, $x) = @_; addch($win, $Window_Ycentre + $y * 2 + ($y < 0 ? 1 : -1), $Window_Xcentre + $x * $Cell_Width + int($Cell_Width / 2), ACS_VLINE); } sub display_draw_layout($$$$) # Draw given display window from a layout { my ($win, $lref, $quad, $preview) = @_; my ($x, $y); for ($y = -int($Vcells / 2); $y <= int($Vcells / 2); $y++) { if (!$preview || ($y == 0)) { for ($x = -int($Hcells / 2); $x <= int($Hcells / 2); $x++) { if ($quad && ($x == 0) && ($y == 0)) { display_draw_quad($win, $$lref{"0,0"}, $preview); } elsif (!$quad || ($x < 0) || ($y < 0)) { my $cell = $$lref{"$x,$y"}; display_draw_cell($win, $cell, $y, $x) if defined $cell; display_draw_link_horizontal($win, $y, $x) if defined $$lref{"$x-$y"}; display_draw_link_vertical($win, $y, $x) if defined $$lref{"$x|$y"}; } } } elsif (!$quad || $y < 0) { my $cell = $$lref{"0,$y"}; display_draw_cell($win, $cell, $y, 0) if defined $cell; display_draw_link_vertical($win, $y, 0) if defined $$lref{"0|$y"}; } } } sub display_draw_window($$) # Redraw given display window { my ($number, $preview) = @_; # Local variables my $win = $Window[$number]; my $curs = get_cursor($number); my $name = cell_get($curs); my $cell = get_lastcell($curs, "-d.cursor"); $curs = cell_nbr($curs, "+d.1"); my $right = cell_get($curs); $curs = cell_nbr($curs, "+d.1"); my $down = cell_get($curs); $curs = cell_nbr($curs, "+d.1"); my $out = cell_get($curs); my $raster = cell_get(cell_nbr($curs, "+d.1")); my $quad = ($raster =~ /Q$/); # Draw window border, title and current cell number erase($win) unless $preview; attrset($win, A_NORMAL); box($win, 0, 0); addstr($win, 0, int($COLS / 4) - int(length($name) / 2) - 6, " $name Window ($raster) "); if ($USE_STATUSBAR) { addstr($win, $LINES - 2, int($COLS / 2) - length($cell) - 3, " $cell "); } else { addstr($Window[0], $LINES - 1, 1, " $Input_Buffer ") if (defined $Input_Buffer) && ($number == 0); addstr($win, $LINES - 1, int($COLS / 2) - length($cell) - 3, " $cell "); } # Display window contents. my %layout; if ($preview) { layout_preview(\%layout, $cell, $right, $down); } elsif ($raster =~ /^H/) { layout_Hraster(\%layout, $cell, $right, $down); } else { layout_Iraster(\%layout, $cell, $right, $down); } display_draw_layout($Window[$number], \%layout, $quad, $preview); attrset($win, A_BOLD); # Display clone flag if we aren't using lots of colours if (!$LOTS_OF_COLOURS && is_clone($cell)) { addch($win, $Window_Ycentre - 1, $Window_Xcentre, "c"); } # Display selected-cell flag if we aren't using lots of colours if (!$LOTS_OF_COLOURS && is_active_selected($cell)) { addch($win, $Window_Ycentre - 1, $Window_Xcentre, "m"); } # Display dimension guide addstr($win, 1, 1, "+---> $right "); addstr($win, 2, 1, "|\\ "); addstr($win, 3, 1, "| \\| $out "); addstr($win, 4, 1, "V -+ "); addstr($win, 5, 1, "$down "); attrset($win, A_BOLD); $Window_Dirty[$number] = $FALSE unless $preview; display_refresh($win); } # # Handle keyboard input. # Named: input_* # sub input_get_any() # Attempt to get any input # NOTE: This function is also user interface toolkit specific! { my $key = getch(); if (($key eq "\e") && (($_ = getch()) ne -1)) { $key = meta_key($_); } return $key eq -1 ? undef : $key; } sub input_get_direction() # Attempt to get a direction key { my $key; while (!defined($key = input_get_any())) {} # Idle until we get a key if ($_ = $Keymap_Directions{$key}) { @_ = split //; } else { # The key isn't a direction key user_error(1, $key); undef @_; } } sub input_process_digit($) # Process numeric input from keyboard { # If this is the first digit if (not defined $Input_Buffer) { $Input_Buffer = $_[0]; $Status_Dirty = $TRUE; } # Otherwise add another digit if the number isn't too large elsif (length($Input_Buffer) < 9) { $Input_Buffer .= $_[0]; $Status_Dirty = $TRUE; } else { user_error(0, ""); } } sub input_process_backspace() # Handle backspace key { if (defined $Input_Buffer) { # Remove the last character, if any chop $Input_Buffer; undef $Input_Buffer if $Input_Buffer eq ""; $Status_Dirty = $TRUE; } else { user_error(0, ""); } } # # Handle signals and errors # sub catchsig() # Handle fatal signals { $Zigzag_Terminated = $_[0]; } sub catchwinch() # Handle window size change signals { $Display_Resized = $_[0]; } sub user_error($$) # Handle user errors { my ($errno, $text) = @_; # I feel like this next should be a global. It's not. my @errors = ( "Key is not a valid keystroke", "Cannot insert - invalid neigbours", "Cannot jump to invalid or cursor cell", "Error executing cell", "Cannot hop - no neigbours", "Cannot jump to cell that does not exist", "Cannot break link - none exists", "Error opening file", "Cannot insert cells in selected dimension", "Cannot delete essential cell!", "Cannot delete essential dimension" ); beep(); if ($errno && $USE_STATUSBAR) { $errno--; my $errmsg = "Error $errno"; $errmsg .= ": $errors[$errno]" if $errno <= $#errors; if ($text) { display_status_draw(substr("$errmsg ($text)", 0, $COLS)); } else { display_status_draw(substr("$errmsg", 0, $COLS)); } } if ($USE_STATUSBAR) { display_refresh($Status); } else { display_refresh(); } } # # Initialization functions # sub choose_editor() # Choose editor based on ordinary user preferences, if available. # That means look at VISUAL and EDITOR environment variables, in that # order. # # If neither is set, use old logic: Select first program available # from a list that includes several common editors. { my @edlist = qw(/usr/local/bin/mule /usr/bin/emacs /bin/vi /bin/joe ); my $editor = $ENV{VISUAL} || $ENV{EDITOR}; return $editor if $editor; my $e; foreach $e (@edlist) { return $e if -x $e; } print STDERR "Sorry, Zigzag couldn't locate any usable editor. Please set your EDITOR variable and try again.\n"; exit 0; } # # Background functions, if any, can be executed here # sub idle() { select(undef, undef, undef, 0.1); # sleep for 1/10 second if ($USE_STATUSBAR && $StatusTimer) { $StatusTimer--; if (!$StatusTimer) { display_status_draw(NULL); } } } # # Begin main. # slice_open(shift); # Set the interrupt handlers $SIG{INT} = $SIG{TERM} = \&catchsig; $SIG{WINCH} = \&catchwinch; display_open(); eval { my $key_pressed = input_get_any(); my $i; # Redraw the status line if dirty if ($Status_Dirty) { if ($USE_STATUSBAR) { display_status_draw(NULL); } else # Status information is in $Window[0] instead { $Window_Dirty[0] = $TRUE; $Status_Dirty = $FALSE; } } # Partially or fully redraw dirty windows for ($i = 0; $i <= $#Window_Dirty; $i++) { display_draw_window($i, defined($key_pressed)) if $Window_Dirty[$i]; } if (!defined($key_pressed)) { idle(); } else { if ($key_pressed =~ /^\d$/) { input_process_digit($key_pressed); } elsif ($_ = $Keymap_Directions{$key_pressed}) { @_ = split //; atcursor_make_link($_[0], $_[1]); } elsif ($_ = $Keymap{$key_pressed}) { if (++$Command_Count > $COMMAND_SYNC_COUNT) { slice_sync_all(); $Command_Count = 0; } eval; die if $@; } else # The key isn't a digit, a direction key or in %Keymap { user_error(1, $key_pressed); } } display_resize() if $Display_Resized; } until ($@ || $Zigzag_Terminated); display_clear(); display_refresh(); display_close(); slice_close_all(); die if $@; print STDERR "Terminated by $Zigzag_Terminated signal\n" if $Zigzag_Terminated; # # End. #