[comp.lang.icon] klondike, part 04 of 05

goer%sophist@GARGOYLE.UCHICAGO.EDU (Richard Goerwitz) (12/08/90)

---- Cut Here and feed the following to sh ----
#!/bin/sh
# this is klondike.04 (part 4 of a multipart archive)
# do not concatenate these parts, unpack them in order with /bin/sh
# file itlib.icn continued
#
if test ! -r _shar_seq_.tmp; then
	echo 'Please unpack part 1 first!'
	exit 1
fi
(read Scheck
 if test "$Scheck" != 4; then
	echo Please unpack part "$Scheck" next!
	exit 1
 else
	exit 0
 fi
) < _shar_seq_.tmp || exit 1
if test ! -f _shar_wnt_.tmp; then
	echo 'x - still skipping itlib.icn'
else
echo 'x - continuing file itlib.icn'
sed 's/^X//' << 'SHAR_EOF' >> 'itlib.icn' &&
X#    
X#	Name:	itlib.icn
X#	
X#	Title:	Icon termlib-type tools
X#	
X#	Author:	Richard L. Goerwitz
X#
X#	Version: 1.23
X#
X#########################################################################
X#
X#  I place this and future versions of itlib in the public domain - RLG
X#
X#########################################################################
X#
X#  The following library represents a series of rough functional
X#  equivalents to the standard Unix low-level termcap routines.  They
X#  are not meant as exact termlib clones.  Nor are they enhanced to
X#  take care of magic cookie terminals, terminals that use \D in their
X#  termcap entries, or, in short, anything I felt would not affect my
X#  normal, day-to-day work with ANSI and vt100 terminals.
X#
X#  Requires:  A unix platform & co-expressions.  There is an MS-DOS
X#  version, itlibdos.icn.
X#
X#  setname(term)
X#	Use only if you wish to initialize itermlib for a terminal
X#  other than what your current environment specifies.  "Term" is the
X#  name of the termcap entry to use.  Normally this initialization is
X#  done automatically, and need not concern the user.
X#
X#  getval(id)
X#	Works something like tgetnum, tgetflag, and tgetstr.  In the
X#  spirit of Icon, all three have been collapsed into one routine.
X#  Integer valued caps are returned as integers, strings as strings,
X#  and flags as records (if a flag is set, then type(flag) will return
X#  "true").  Absence of a given capability is signalled by procedure
X#  failure.
X#
X#  igoto(cm,destcol,destline) - NB:  default 1 offset (*not* zero)!
X#	Analogous to tgoto.  "Cm" is the cursor movement command for
X#  the current terminal, as obtained via getval("cm").  Igoto()
X#  returns a string which, when output via iputs, will cause the
X#  cursor to move to column "destcol" and line "destline."  Column and
X#  line are always calculated using a *one* offset.  This is far more
X#  Iconish than the normal zero offset used by tgoto.  If you want to
X#  go to the first square on your screen, then include in your program
X#  "iputs(igoto(getval("cm"),1,1))."
X#
X#  iputs(cp,affcnt)
X#	Equivalent to tputs.  "Cp" is a string obtained via getval(),
X#  or, in the case of "cm," via igoto(getval("cm"),x,y).  Affcnt is a
X#  count of affected lines.  It is only relevant for terminals which
X#  specify proportional (starred) delays in their termcap entries.
X#
X#  Bugs:  I have not tested these routines on terminals that require
X#  padding.  These routines WILL NOT WORK if your machines stty com-
X#  mand has no -g option (tisk, tisk).  This includes NeXT worksta-
X#  tions, and some others that I haven't had time to pinpoint.
X#
X##########################################################################
X#
X#  Requires: UNIX, co-expressions
X#
X#  See also: iscreen.icn (a set of companion utilities)
X#
X##########################################################################
X
X
Xglobal tc_table, tty_speed
Xrecord true()
X
X
Xprocedure check_features()
X
X    local in_params, line
X    # global tty_speed
X
X    initial {
X	find("unix",map(&features)) |
X	    er("check_features","unix system required",1)
X	find("o-expres",&features) |
X	    er("check_features","co-expressions not implemented - &$#!",1)
X	system("/bin/stty tabs") |
X	    er("check_features","can't set tabs option",1)
X    }
X
X    # clumsy, clumsy, clumsy, and probably won't work on all systems
X    tty_speed := getspeed()
X    return "term characteristics reset; features check out"
X
Xend
X
X
X
Xprocedure setname(name)
X
X    # Sets current terminal type to "name" and builds a new termcap
X    # capability database (residing in tc_table).  Fails if unable to
X    # find a termcap entry for terminal type "name."  If you want it
X    # to terminate with an error message under these circumstances,
X    # comment out "| fail" below, and uncomment the er() line.
X
X    #tc_table is global
X    
X    check_features()
X
X    tc_table := table()
X    tc_table := maketc_table(getentry(name)) | fail
X    # er("setname","no termcap entry found for "||name,3)
X    return "successfully reset for terminal " || name
X
Xend
X
X
X
Xprocedure getname()
X
X    # Getname() first checks to be sure we're running under Unix, and,
X    # if so, tries to figure out what the current terminal type is,
X    # checking successively the value of the environment variable
X    # TERM, and then the output of "tset -".  Terminates with an error
X    # message if the terminal type cannot be ascertained.
X
X    local term, tset_output
X
X    check_features()
X
X    if not (term := getenv("TERM")) then {
X	tset_output := open("/bin/tset -","pr") |
X	    er("getname","can't find tset command",1)
X	term := !tset_output
X	close(tset_output)
X    }
X    return \term |
X	er("getname","can't seem to determine your terminal type",1)
X
Xend
X
X
X
Xprocedure er(func,msg,errnum)
X
X    # short error processing utility
X    write(&errout,func,":  ",msg)
X    exit(errnum)
X
Xend
X
X
X
Xprocedure getentry(name, termcap_string)
X
X    # "Name" designates the current terminal type.  Getentry() scans
X    # the current environment for the variable TERMCAP.  If the
X    # TERMCAP string represents a termcap entry for a terminal of type
X    # "name," then getentry() returns the TERMCAP string.  Otherwise,
X    # getentry() will check to see if TERMCAP is a file name.  If so,
X    # getentry() will scan that file for an entry corresponding to
X    # "name."  If the TERMCAP string does not designate a filename,
X    # getentry() will scan /etc/termcap for the correct entry.
X    # Whatever the input file, if an entry for terminal "name" is
X    # found, getentry() returns that entry.  Otherwise, getentry()
X    # fails.
X
X    local f, getline, line, nm, ent1, ent2
X
X    # You can force getentry() to use a specific termcap file by cal-
X    # ling it with a second argument - the name of the termcap file
X    # to use instead of the regular one, or the one specified in the
X    # termcap environment variable.
X    /termcap_string := getenv("TERMCAP")
X
X    if \termcap_string ? (not match("/"), pos(0) | tab(find("|")+1), =name)
X    then return termcap_string
X    else {
X
X	# The logic here probably isn't clear.  The idea is to try to use
X	# the termcap environment variable successively as 1) a termcap en-
X	# try and then 2) as a termcap file.  If neither works, 3) go to
X	# the /etc/termcap file.  The else clause here does 2 and, if ne-
X	# cessary, 3.  The "\termcap_string ? (not match..." expression
X	# handles 1.
X
X	if find("/",\termcap_string)
X	then f := open(termcap_string)
X	/f := open("/etc/termcap") |
X	    er("getentry","I can't access your /etc/termcap file",1)
X
X	getline := create read_file(f)
X    
X	while line := @getline do {
X	    if line ? (pos(1) | tab(find("|")+1), =name, any(':|')) then {
X		entry := ""
X		while (\line | @getline) ? {
X		    if entry ||:= 1(tab(find(":")+1), pos(0))
X		    then {
X			close(f)
X			# if entry ends in tc= then add in the named tc entry
X			entry ?:= tab(find("tc=")) ||
X			    # recursively fetch the new termcap entry
X			    (move(3), getentry(tab(find(":"))) ?
X			        # remove the name field from the new entry
X			     	(tab(find(":")+1), tab(0)))
X			return entry
X		    }
X		    else {
X			\line := &null # must precede the next line
X			entry ||:= trim(trim(tab(0),'\\'),':')
X		    }
X		}
X	    }
X	}
X    }
X
X    close(f)
X    er("getentry","can't find and/or process your termcap entry",3)
X 
Xend
X
X
X
Xprocedure read_file(f)
X
X    # Suspends all non #-initial lines in the file f.
X    # Removes leading tabs and spaces from lines before suspending
X    # them.
X
X    local line
X
X    \f | er("read_tcap_file","no valid termcap file found",3)
X    while line := read(f) do {
X	match("#",line) & next
X	line ?:= (tab(many('\t ')) | &null, tab(0))
X	suspend line
X    }
X
X    fail
X
Xend
X
X
X
Xprocedure maketc_table(entry)
X
X    # Maketc_table(s) (where s is a valid termcap entry for some
X    # terminal-type): Returns a table in which the keys are termcap
X    # capability designators, and the values are the entries in
X    # "entry" for those designators.
X
X    local k, v
X
X    /entry & er("maketc_table","no entry given",8)
X    if entry[-1] ~== ":" then entry ||:= ":"
X    
X    /tc_table := table()
X
X    entry ? {
X
X	tab(find(":")+1)	# tab past initial (name) field
X
X	while tab((find(":")+1) \ 1) ? {
X	    &subject == "" & next
X	    if k := 1(move(2), ="=")
X	    then tc_table[k] := Decode(tab(find(":")))
X	    else if k := 1(move(2), ="#")
X	    then tc_table[k] := integer(tab(find(":")))
X	    else if k := 1(tab(find(":")), pos(-1))
X	    then tc_table[k] := true()
X	    else er("maketc_table", "your termcap file has a bad entry",3)
X	}
X    }
X
X    return tc_table
X
Xend
X
X
X
Xprocedure getval(id)
X
X    /tc_table := maketc_table(getentry(getname())) |
X	er("getval","can't make a table for your terminal",4)
X
X    return \tc_table[id] | fail
X	# er("getval","the current terminal doesn't support "||id,7)
X
Xend
X
X
X
Xprocedure Decode(s)
X
X    # Does things like turn ^ plus a letter into a genuine control
X    # character.
X
X    new_s := ""
X
X    s ? {
X
X	while new_s ||:= tab(upto('\\^')) do {
X	    chr := move(1)
X	    if chr == "\\" then {
X		new_s ||:= {
X		    case chr2 := move(1) of {
X			"\\" : "\\"
X			"^"  : "^"
X			"E"  : "\e"
X			"b"  : "\b"
X			"f"  : "\f"
X			"n"  : "\n"
X			"r"  : "\r"
X			"t"  : "\t"
X			default : {
X			    if any(&digits,chr2) then {
X				char(integer("8r"||chr2||move(2 to 0 by -1))) |
X				    er("Decode","bad termcap entry",3)
X			    }
X			   else chr2
X			}
X		    }
X		}
X	    }
X	    else new_s ||:= char(ord(map(move(1),&lcase,&ucase)) - 64)
X	}
X	new_s ||:= tab(0)
X    }
X
X    return new_s
X
Xend
X
X
X
Xprocedure igoto(cm,col,line)
X
X    local colline, range, increment, str, outstr, chr, x, y
X
X    if col > (tc_table["co"]) | line > (tc_table["li"]) then {
X	colline := string(\col) || "," || string(\line) | string(\col|line)
X	range := "(" || tc_table["co"]-1 || "," || tc_table["li"]-1 || ")"
X	er("igoto",colline || " out of range " || (\range|""),9)
X    } 
X
X    # Use the Iconish 1;1 upper left corner & not the C-ish 0 offsets
X    increment := -1
X    outstr := ""
X    
X    cm ? {
X	while outstr ||:= tab(find("%")) do {
X	    tab(match("%"))
X	    chr := move(1)
X	    if case chr of {
X		"." :  outstr ||:= char(line + increment)
X		"+" :  outstr ||:= char(line + ord(move(1)) + increment)
X		"d" :  {
X		    str := string(line + increment)
X		    outstr ||:= right(str, integer(tab(any('23'))), "0") | str
X		}
X	    }
X	    then line :=: col
X	    else {
X		case chr of {
X		    "n" :  line := ixor(line,96) & col := ixor(col,96)
X		    "i" :  increment := 0
X		    "r" :  line :=: col
X		    "%" :  outstr ||:= "%"
X		    "B" :  line := ior(ishift(line / 10, 4), line % 10)
X		    ">" :  {
X			x := move(1); y := move(1)
X			line > ord(x) & line +:= ord(y)
X			&null
X		    }
X		} | er("goto","bad termcap entry",5)
X	    }
X	}
X    return outstr || tab(0)
X    }
X
Xend
X
X
X
Xprocedure iputs(cp, affcnt)
X
X    local baud_rates, char_rates, i, delay, PC
X    static num_chars, char_times
X    # global tty_speed
X
X    initial {
X	num_chars := &digits ++ '.'
X	char_times := table()
X	# Baud rates in decimal, not octal (as in termio.h)
X	baud_rates := [0,7,8,9,10,11,12,13,14,15]
X	char_rates := [0,333,166,83,55,41,20,10,10,10]
X	every i := 1 to *baud_rates do {
X	    char_times[baud_rates[i]] := char_rates[i]
X	}
X    }
X
X    type(cp) == "string" |
X	er("iputs","you can't iputs() a non-string value!",10)
X
X    cp ? {
X	delay := tab(many(num_chars))
X	if ="*" then {
X	    delay *:= \affcnt |
X		er("iputs","affected line count missing",6)
X	}
X	writes(tab(0))
X    }
X
X    if (\delay, tty_speed ~= 0) then {
X	PC := tc_table["pc"] | "\000"
X	char_time := char_times[tty_speed] | (return "speed error")
X	delay := (delay * char_time) + (char_time / 2)
X	every 1 to delay by 10
X	do writes(PC)
X    }
X
X    return
X
Xend
X
X
X
Xprocedure getspeed()
X
X    local stty_g, stty_output, c_cflag, o_speed
X
X    stty_g := open("/bin/stty -g 2>&1","pr") |
X	er("getspeed","Can't access your stty command.",4)
X    stty_output := !stty_g
X    close(stty_g)
X
X    \stty_output ? {
X	# tab to the third field of the output of the stty -g cmd
X        tab(find(":")+1) & tab(find(":")+1) &
X	c_cflag := integer("16r"||tab(find(":")))
X    } | er("getspeed","Unable to unwind your stty -g output.",4)
X
X    o_speed := iand(15,c_cflag)
X    return o_speed
X
Xend
SHAR_EOF
echo 'File itlib.icn is complete' &&
true || echo 'restore of itlib.icn failed'
rm -f _shar_wnt_.tmp
fi
# ============= getchlib.icn ==============
if test -f 'getchlib.icn' -a X"$1" != X"-c"; then
	echo 'x - skipping getchlib.icn (File already exists)'
	rm -f _shar_wnt_.tmp
else
> _shar_wnt_.tmp
echo 'x - extracting getchlib.icn (Text)'
sed 's/^X//' << 'SHAR_EOF' > 'getchlib.icn' &&
X############################################################################
X#
X#	Name:	 getchlib.icn
X#
X#	Title:	 Implementation of getch() for Unix (and more)
X#
X#	Author:	 Richard L. Goerwitz
X#
X#	Version: 1.11
X#
X############################################################################
X#
X#  I place this and future versions of getchlib in the public domain - RLG
X#
X############################################################################
X#
X#  Implementing getch() is a much, much more complex affair under Unix
X#  than it is under, say, MS-DOS.  This library represents one,
X#  solution to the problem - one which can be run as a library, and
X#  need not be compiled into the run-time system.
X#
X#  Four basic utilities are included here:
X#
X#	getch()		- waits until a keystroke is available &
X#	    returns it without displaying it on the screen
X#	getche()	- same as getch() only with echo
X#	getse(s)	- like getche() only for strings.  The optional
X#	    argument s gives getse() something to start with.  Use this
X#           if, say, you want to read single characters in cbreak mode,
X#           but get more input if the character read is the first part
X#           of a longer command.  If the user backspaces over everything
X#           that has been input, getse() fails.  Returns on \r or \n.
X#	reset_tty()	- absolutely vital routine for putting the cur-
X#           rent tty line back into cooked mode; call it before exiting
X#           or you will find yourself with a locked-up terminal; use it
X#           also if you must temporarily restore the terminal to cooked
X#           mode
X#
X#  Note that getse() *must* be used in place of read(&input) if you
X#  are planning on using getch() or getche(), since read(&input)
X#  assumes a tty with "sane" settings.
X#
X#  Warning:  The routines below do not do any sophisticated output
X#  processing.  As noted above, they also put your tty line in raw
X#  mode.  I know, I know:  "Raw is overkill - use cbreak."  But in
X#  a world that includes SysV, one must pick a lowest common denomi-
X#  nator.  And no, icanon != cbreak.
X#
X#  Bugs:  These routines will not work on systems that do not imple-
X#  ment the -g option for the stty command.  The NeXT workstation is
X#  an example of such a system.  Tisk, tisk.
X#
X############################################################################
X#
X#  Example program:
X#
X#      The following program is a simple file viewer.  To run, it
X#  needs to be linked with itlib.icn, iscreen.icn, and this file
X#  (getchlib.icn).
X#
X#  procedure main(a)
X#
X#      # Simple pager/file searcher for Unix systems.  Must be linked
X#      # with itlib.icn and iscreen.icn.
X#  
X#      local intext, c, s
X#  
X#      # Open input file
X#      intext := open(a[1],"r") | {
X#  	write(&errout,"Can't open input file.")
X#  	exit(1)
X#      }
X#  
X#      # Initialize screen
X#      clear()
X#      print_screen(intext) | exit(0)
X#  
X#      # Prompt & read input
X#      repeat {
X#  	iputs(igoto(getval("cm"), 1, getval("li")))
X#  	emphasize()
X#  	writes("More? (y/n or /search):")
X#  	write_ce(" ")
X#  	case c := getche() of {
X#  	    "y" : print_screen(intext) | break
X#  	    " " : print_screen(intext) | break
X#  	    "n" : break
X#  	    "q" : break
X#  	    "/" : {
X#  		iputs(igoto(getval("cm"), 1, getval("li")))
X#  		emphasize()
X#  		writes("Enter search string:")
X#  		write_ce(" ")
X#  		pattern := GetMoreInput()
X#  		/pattern | "" == pattern & next
X#  		# For more complex patterns, use findre() (IPL findre.icn)
X#  		if not find(pattern, s := !intext) then {
X#  		    iputs(igoto(getval("cm"), 1, getval("li")))
X#  		    emphasize()
X#  		    write_ce("String not found.")
X#  		    break
X#  		}
X#  		else print_screen(intext, s) | break
X#  	    }
X#  	}
X#      }
X#  
X#      reset_tty()
X#      write()
X#      exit(0)
X#
X#  end
X#  
X#  procedure GetMoreInput(c)
X#  
X#      local input_string
X#      static BS
X#      initial BS := getval("bc") | "\b"
X#  
X#      /c := ""
X#      if any('\n\r', chr := getch())
X#      then return c
X#      else {
X#  	chr == BS & fail
X#  	writes(chr)
X#  	input_string := getse(c || chr) | fail
X#  	if any('\n\r', input_string)
X#  	then fail else (return input_string)
X#      }
X#  
X#  end
X#  
X#  procedure print_screen(f,s)
X#  
X#      if /s then
X#  	begin := 1
X#      # Print top line, if one is supplied
X#      else {
X#  	iputs(igoto(getval("cm"), 1, 1))
X#  	write_ce(s ? tab(getval("co") | 0))
X#  	begin := 2
X#      }
X#  
X#      # Fill the screen with lines from f; clear and fail on EOF.
X#      every i := begin to getval("li") - 1 do {
X#  	iputs(igoto(getval("cm"), 1, i))
X#  	if not write_ce(read(f) ? tab(getval("co") | 0)) then {
X#  	    # Clear remaining lines on the screen.
X#  	    every j := i to getval("li") do {
X#  		iputs(igoto(getval("cm"), 1, j))
X#  		iputs(getval("ce"))
X#  	    }
X#  	    iputs(igoto(getval("cm"), 1, i))
X#  	    fail
X#  	}
X#      }
X#      return
X#  
X#  end
X#  
X#  procedure write_ce(s)
X#  
X#      normal()
X#      iputs(getval("ce")) |
X#  	writes(repl(" ",getval("co") - *s))
X#      writes(s)
X#      return
X#
X#  end
X#
X############################################################################
X#
X#  Requires: UNIX
X#
X#  Links: itlib.icn
X#
X############################################################################
X
X
Xglobal c_cc, current_mode		# what mode are we in, raw or cooked?
Xrecord termio_struct(vintr,vquit,verase,vkill)
X
Xprocedure getse(s)
X
X    # getse() - like getche, only for strings instead of single chars
X    #
X    # This procedure *must* be used instead of read(&input) if getch
X    # and/or getche are to be used, since these put the current tty
X    # line in raw mode.
X    #
X    # Note that the buffer can be initialized by calling getse with a
X    # string argument.  Note also that, as getse now stands, it will
X    # fail if the user backspaces over everything that has been input.
X    # This change does not coincide with its behavior in previous ver-
X    # sions.  It can be changed by commenting out the line "if *s < 1
SHAR_EOF
true || echo 'restore of getchlib.icn failed'
fi
echo 'End of  part 4'
echo 'File getchlib.icn is continued in part 5'
echo 5 > _shar_seq_.tmp
exit 0