#! /bin/sh
# \
exec wish8.2 "$0" "$@"

#################################################################
# ntkinfo.tcl - a slimmed down and updated tkinfo
# (http://math-www.uni-paderborn.de/~axel/tkinfo)
# 
# Copyright (C) 1999 Mark Patton
#
# 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.
###################################################################

# To use as a lib, set the global var ntki_embed to a value.
# Interface:
# ntki::init sets up ntkinfo.
# ntki::win ?node? pops up a new toplevel and returns it.

namespace eval ntki {}

proc ntki::init {} {
    global env tcl_platform
    variable ntki
    
     array set ntki {
	cat,Z       zcat
	cat,z       "gunzip -c"
	cat,gz      "gunzip -c"
	cat,bz2     "bunzip2 -c"
	cat,zip     "unzip -p"
	id          0
	phist_len   15
	info_suffix {.info}
    }

    if {[info exist env(INFOSUFFIX)]} {
	set ntki(info_suffix) [split $env(INFOSUFFIX) :]
    }

    if {[info exist env(INFOPATH)]} {
	if {[string equal $tcl_platform(platform) windows]} {
	    set path [split $env(INFOPATH) \;]
	} else {
	    set path [split $env(INFOPATH) :]
	}
    } 
    lappend path /usr/info /usr/local/info /usr/gnu/info \
	    /usr/local/gnu/info [pwd]

    set ntki(info_path) [list]
    set ntki(dirs) [list]

    foreach dir $path {
	if {[file isdir $dir]} {
	    lappend ntki(info_path) $dir

	    set d [file join $dir dir]
	    if {![string equal [complete $d] ""]} {
		lappend ntki(dirs) $d
	    }
	}
    }
    
    set nolabel {([^:]+)::}
    set label {([^:]+:[ \t\n]+)([^.,]+)[.,]}

    set ntki(rxp,strip_menu) {\n\* Menu:[^\n]*\n}
    set ntki(rxp,parse_menu) "\n\\\*\[ \t\]+($nolabel|$label)"
    set ntki(rxp,parse_xref) "\\\*(\[Nn\]ote\[ \t\n\]+)($nolabel|$label)"
    set ntki(rxp,get_keyline) {^\n([^\n]+)\n(.*)$}
    set ntki(rxp,parse_title) {^[\n]*([^\n]+)\n[-\*\\+=]+}

    bind info <Key-space>     {ntki::scroll %W 1 pages}
    bind info <Control-space> {ntki::scroll %W -1 pages}
    bind info <Alt-space>     {ntki::scroll %W -1 pages}
    bind info <Meta-space>    {ntki::scroll %W -1 pages}
    
    bind info <Key-Home>     {%W see 1.0; break}
    bind info <Key-End>      {%W see end; break}
    
    bind info <Key-b>        {%W see 1.0}
    bind info <Key-e>        {%W see end}
    
    bind info <Key-Down>     {%W yview scroll 1 units; break}
    bind info <Key-Up>       {%W yview scroll -1 units; break}
    
    bind info <Key-c>        {ntki::win_close [winfo toplevel %W]; break}
    bind info <Key-q>        {ntki::quit; break}
    
    bind info <Key-u>        {ntki::goto [winfo toplevel %W] up}
    bind info <Key-n>        {ntki::goto [winfo toplevel %W] next}
    bind info <Key-p>        {ntki::goto [winfo toplevel %W] prev}
    bind info <bracketright> {ntki::goto [winfo toplevel %W] lnext}
    bind info <bracketleft>  {ntki::goto [winfo toplevel %W] lprev}
    bind info <Key-d>        {ntki::goto [winfo toplevel %W] dir}
    bind info <Key-g>        {ntki::prompt [winfo toplevel %W] goto}
    bind info <Key-m>        {ntki::prompt [winfo toplevel %W] menu}
    bind info <Key-f>        {ntki::prompt [winfo toplevel %W] xref}
    bind info <Key-x>        {ntki::prompt [winfo toplevel %W] xref}
    bind info <Key-t>        {ntki::goto [winfo toplevel %W] top}
    bind info <Key-w>  {
	ntki::win ($ntki::ntki(win,[winfo toplevel %W],file))$ntki::ntki(win,[winfo toplevel %W],loc)
    }
    
    bind info <Key-i> \
	    {ntki::prompt [winfo toplevel %W] index_lookup}
    bind info <Key-s> \
	    {ntki::prompt [winfo toplevel %W] search_forwards_exact}
    bind info <Key-slash> \
	    {ntki::prompt [winfo toplevel %W] search_forwards_exact}
    bind info <Key-backslash> \
	    {ntki::prompt [winfo toplevel %W] search_backwards_exact}
    bind info <Key-question> \
	    {ntki::prompt [winfo toplevel %W] search_backwards_exact}
    bind info <Key-semicolon> \
	    {ntki::prompt [winfo toplevel %W] search_forwards_regexp}
    bind info <Key-quoteright> \
	    {ntki::prompt [winfo toplevel %W] search_backwards_regexp}
    bind info <Control-s> {ntki::search_continue [winfo toplevel %W]}
    bind info <Control-c> {ntki::prompt_quit [winfo toplevel %W]; break}
    bind info <Control-g> {ntki::prompt_quit [winfo toplevel %W]; break}
    bind info <Escape>    {ntki::prompt_quit [winfo toplevel %W]}

    bind prompt <Control-u> {%W delete 0 end}
    bind prompt <Return>    {ntki::prompt_done [winfo toplevel %W]}
    bind prompt <Escape>    {ntki::prompt_quit [winfo toplevel %W]}
    bind prompt <Control-g> {ntki::prompt_quit [winfo toplevel %W]}
    bind prompt <Key-Up>    {ntki::prompt_hist_up [winfo toplevel %W]}
    bind prompt <Key-Down>  {ntki::prompt_hist_down [winfo toplevel %W]}
    bind prompt <Tab>       {ntki::prompt_complete [winfo toplevel %W]; break}
    bind prompt <Control-c> {ntki::prompt_quit [winfo toplevel %W]}

    rename init ""
    return
}

proc ntki::goto {w dir} {
    variable ntki

    set file $ntki(win,$w,file)
    set loc $ntki(win,$w,loc)

    switch -exact -- $dir {
	up {
	    set node [lindex $ntki(info,$file,$loc) 1]
	} next {
	    set node [lindex $ntki(info,$file,$loc) 3]
	} prev {
	    set node [lindex $ntki(info,$file,$loc) 2]
	} top {
	    set node Top
	} dir {
	    set node ($ntki(win,$w,dir))Top
	} lnext {
	    set node [lnext $w $loc]
	} lprev {
	    set node [lprev $w $loc]
	}
    }
    
    if {![string equal $node ""]} {
	win_display $w $node
    }
    
    return
}

proc ntki::scroll {wt dir unit} {
    set w [winfo toplevel $wt]
    
    if {$dir == 1 && [lindex [$wt yview] 1] == 1} {
	goto $w lnext
    } elseif {$dir == -1 && [lindex [$wt yview] 0] == 0} {
	goto $w lprev
    } else {
	$wt yview scroll $dir $unit
    }

    return -code break
}

proc ntki::win {{node ""}} {
    variable ntki
    
    set w .ntki[incr ntki(id)]

    toplevel $w -class ntkinfo
    wm title $w Ntkinfo
    
    wm protocol $w WM_DELETE_WINDOW [list ntki::win_close $w]
    
    menu $w.menubar
    $w configure -menu $w.menubar

    $w.menubar add cascade -label File -menu $w.menubar.file -underline 0
    $w.menubar add cascade -label Node -menu $w.menubar.node -underline 0
    $w.menubar add cascade -label Directories -menu $w.menubar.dir -underline 0
    $w.menubar add cascade -label Search -menu $w.menubar.search -underline 0
    $w.menubar add cascade -label Help -menu $w.menubar.help -underline 0

    menu $w.menubar.file

    $w.menubar.file add command -label "Go to Node... " \
	    -command [list ntki::prompt $w goto] -accelerator g
    $w.menubar.file add command -label "New Window " -accelerator w\
	    -command "ntki::win (\$ntki::ntki(win,$w,file))\$ntki::ntki(win,$w,loc)"
    $w.menubar.file add command -label "Close Window" \
	    -command [list ntki::win_close $w] -accelerator c
    $w.menubar.file add command -label Quit -command {ntki::quit} \
	    -accelerator q   

    menu $w.menubar.node

    $w.menubar.node add command -label "Next Section" -accelerator n\
	    -command [list ntki::goto $w next]
    $w.menubar.node add command -label "Previous Section" -accelerator p\
	    -command [list ntki::goto $w prev]
    $w.menubar.node add command -label "Up" -accelerator u\
	    -command [list ntki::goto $w up]
    $w.menubar.node add command -label "Top"  -accelerator t\
	    -command [list ntki::goto $w top]

    menu $w.menubar.dir

    set ntki(win,$w,dir) [lindex $ntki(dirs) 0]    
    foreach dir $ntki(dirs) {
	    $w.menubar.dir add radiobutton -label $dir -value $dir\
		    -variable ntki::ntki(win,$w,dir) \
		    -command [list ntki::goto $w dir]
    }

    menu $w.menubar.search

    $w.menubar.search add command -label "Exact forward..." -accelerator /\
	    -command [list ntki::prompt $w search_forwards_exact]
    $w.menubar.search add command -label "Exact backward..." -accelerator \\ \
	    -command [list ntki::prompt $w search_backwards_exact]
    $w.menubar.search add command -label "Regexp forward..." -accelerator \; \
	    -command [list ntki::prompt $w search_forwards_regexp]
    $w.menubar.search add command -label "Regexp backward..."  -accelerator ' \
	    -command [list ntki::prompt $w search_backwards_regexp]
    $w.menubar.search add command -label "Continue" -accelerator C-s \
	    -command [list ntki::search_continue $w]

    menu $w.menubar.history

    menu $w.menubar.help

    $w.menubar.help add command -label "Contents" \
	    -command [list ntki::win_display $w (ntkinfo)Top]
    $w.menubar.help add command -label "About ntkinfo" \
	    -command [list ntki::win_display $w (ntkinfo)About]
    
    frame $w.toolbar

    pack [button $w.toolbar.prev -text "Previous" \
	    -command [list ntki::goto $w prev]] -side left
    pack [button $w.toolbar.next -text "Next" \
	    -command [list ntki::goto $w next]] -side left
    pack [button $w.toolbar.up -text "Up" \
	     -command [list ntki::goto $w up]] -side left
    pack [button $w.toolbar.top -text "Top" \
	     -command [list ntki::goto $w top]] -side left

    frame $w.main
    pack [scrollbar $w.main.scroll -orient vertical \
	    -command [list $w.main.text yview]] -side right -fill y
    pack [text $w.main.text -wrap word -state disabled \
	    -yscroll [list $w.main.scroll set]] -side right -expand 1\
	    -fill both

    frame $w.status
    pack [label $w.status.node -relief sunken] -side left
    pack [label $w.status.msg -relief sunken -anchor w] -fill x -expand 1\
	    -side left
    
    entry $w.status.input
    bindtags $w.status.input [concat prompt [bindtags $w.status.input]]

    set color [option get . linkColor ""]
    set title_font [option get . titleFont ""]

    $w.main.text tag configure xref -foreground $color
    $w.main.text tag configure menu -foreground $color
    $w.main.text tag configure title -font $title_font
    
    $w.main.text tag bind xref <Enter> [list %W configure -cursor hand2]
    $w.main.text tag bind xref <Leave> [list %W configure -cursor left_ptr]
    $w.main.text tag bind menu <Enter> [list %W configure -cursor hand2]
    $w.main.text tag bind menu <Leave> [list %W configure -cursor left_ptr]
    $w.main.text tag raise sel
    $w.main.text configure -cursor left_ptr

    $w.main.text tag bind xref <Button-1> \
	    "ntki::win_display $w \[ntki::get_cur_node %W xref\]"
    $w.main.text tag bind xref <Button-2> \
	    {ntki::win [ntki::get_cur_node %W xref]}
    $w.main.text tag bind xref <Button-3> \
	    {ntki::win [ntki::get_cur_node %W xref]}

    $w.main.text tag bind menu <Button-1> \
	    "ntki::win_display $w \[ntki::get_cur_node %W menu\]"
    $w.main.text tag bind menu <Button-2> \
	    {ntki::win [ntki::get_cur_node %W menu]}
    $w.main.text tag bind menu <Button-3> \
	    {ntki::win [ntki::get_cur_node %W menu]}
	    
    bindtags $w.main.text [concat info [bindtags $w.main.text]]

    pack $w.toolbar -side top -fill x
    pack $w.main -side top -fill both -expand 1
    pack $w.status -side top -fill x

    set ntki(win,$w,history) {}
    set ntki(win,$w,hist_loc) 0
    set ntki(win,$w,hist_cur) ""
    set ntki(win,$w,file) ""
    set ntki(win,$w,loc) ""

    focus $w.main.text

    win_display $w $node
    return $w
}

# get node cursor is over
proc ntki::get_cur_node {wt tag} {
    set first [lindex [$wt tag prevrange $tag current] 0]

    # why does this happen?
    if {[string equal $first ""]} {
	return
    }

    foreach {first last} [$wt tag nextrange $tag $first] break
    regsub -all -- \n [string trim [$wt get $first $last]] " " node

    return $node
}

proc ntki::win_close {w} {
    variable ntki

    set ntki(win,$w,interrupt) 1
    set file $ntki(win,$w,file)

    foreach el [array names ntki win,$w,*] {
	unset ntki($el)
    }
    destroy $w
    
    if {[llength [winfo children .]] == 0} {
	exit
    }

    check_flush $file
    return
}

proc ntki::quit {} {
    global ntki_embed
    variable ntki

    if {![info exist ntki_embed]} {
	exit
    }
    
    foreach w [lmatch_prefix [winfo children .] .ntki] {
	win_close $w
    }
    
    return
}

# For xrefs, there are two forms:
#   *note nodeSpec::terminator
#   *note label: nodeSpec terminator
# Terminator is ``.'' or ``,'', forms may wrap across lines.

# For menus, there are two forms:
#   * nodeSpec::	comments...
#   * label: nodeSpec[\t.,] comments...	

# An info file is stored as ntki(info,$file,$node)
# A node is a list of {file up prev next} followed
# by {text tag} pairs.

# If only on element is present then it is the file
# we have to load for the indirect node.

proc ntki::info_load {w file {to_open ""}} {
    variable ntki

    if {[string equal $to_open ""]} {
	set to_open $file
    }

    $w.status.msg configure -text "loading $to_open..."

    switch -glob -- $to_open {
	*.Z	{set to_open "|$ntki(cat,Z) $to_open"}
	*.z	{set to_open "|$ntki(cat,z) $to_open"}
	*.gz	{set to_open "|$ntki(cat,gz) $to_open"}
	*.bz2   {set to_open "|$ntki(cat,bz2) $to_open"}
	*.zip   {set to_open "|$ntki(cat,zip) $to_open"}
    }

    if {[catch {open $to_open r} fd]} {
	$w.status.msg configure -text "Can't open $to_open: $fd"
	return
    }

    set text [read $fd]
    close $fd

    regsub -all -- $ntki(rxp,strip_menu) $text "" text

    regsub -all -- $ntki(rxp,parse_menu) $text \
	    "\n\\3\ftext\f\\2\\4\fmenu\f" text

    regsub -all -- $ntki(rxp,parse_xref) $text \
	    "\\1\\4\ftext\f\\3\\5\fxref\f" text

    foreach node_text [lrange [split $text \037] 1 end] {
	regexp -- $ntki(rxp,get_keyline) $node_text x keyline node_text

	switch -glob -- $keyline {
	    {[Ii]ndirect:} {
		foreach {indir_file offset} \
			[lreplace [split $node_text \n:] end end] {
		    lappend ntki(indir_files,$file) $indir_file $offset
		}
	    }
	    {[Tt]ag [Tt]able:} {
		set pairs [split $node_text \n\177]
		
		if {[string equal -nocase [lindex $pairs 0] (Indirect)]} {
		    foreach {inode offset} \
			    [lreplace [lrange $pairs 1 end] end end] {
			foreach {f off} $ntki(indir_files,$file) {
			    if {$off > $offset} {
				break
			    }
			    set indir_file $f
			}
			set ntki(info,$file,[string range $inode 6 end]) \
				$indir_file
		    }

		    unset ntki(indir_files,$file)
		}
	    }
	    {[Ee]nd [Tt]ag [Tt]able} {}
	    default {
		regsub -- $ntki(rxp,parse_title) $node_text \
			\\1\ftitle\f node_text
		
		set fkey ""
		set loc  ""
		set next ""
		set prev ""
		set up   ""

		foreach key [split $keyline ",\t"] {
		    set key [string trim $key]
	    
		    switch -glob -- $key {
			File:* {
			    set fkey [string trim [string range $key 5 end]]
			}
			Node:* {
			    set loc [string trim [string range $key 5 end]]}
			Up:* {
			    set up [string trim [string range $key 3 end]]
			}
			Prev:* {
			    set prev [string trim [string range $key 5 end]]
			}
			Next:* {
			    set next [string trim [string range $key 5 end]]
			}
			Previous:* {
			    set prev [string trim [string range $key 9 end]]
			}
		    }
		}

		set ntki(info,$file,$loc) [list $fkey $up $prev $next]
		foreach {el1 el2} [split $node_text \f] {
		    lappend ntki(info,$file,$loc) $el1 $el2
		}
	    }
	}
    }
    
    return
}

# Node can be of form "(prefix)loc", or "loc".
# If not absolute prefix is searched for in info_path.

proc ntki::parse_node {w node file_var loc_var} {
    variable ntki
    upvar 1 $loc_var loc
    upvar 1 $file_var file

    set file ""
    if {![regexp -- {^\((.*)\)(.*)$} $node x file loc]} {
	if {![string equal $ntki(win,$w,file) ""]} {
	    set file $ntki(win,$w,file)
	    set loc $node
	}
    }
    
    if {[string equal $loc ""]} {
	set loc Top
    }

    if {![string equal [file pathtype $file] absolute]} {
	foreach dir $ntki(info_path) {
	    set f [complete [file join $dir $file]]
	    if {[file isfile $f]} {
		set file $f
	    }
	}
    }

    set file [complete $file]
    return
}

proc ntki::complete {prefix {compress_only 0}} {
    variable ntki

    if {!$compress_only} {
	set suf_list $ntki(info_suffix) 
    }
    lappend suf_list ""

    foreach suf $suf_list {
	foreach suf2 [list "" .gz .z .Z .zip .bz2] {
	    set file $prefix$suf$suf2

	    if {[file isfile $file]} {
		return $file
	    }
	}
    }
    
    return
}

# flush a file if its not loaded into a window

proc ntki::check_flush {file} {
    variable ntki

    set used 0
    foreach id [array names ntki win,*,file] {
	if {[string equal $ntki($id) $file]} {
	    set used 1
	    break
	}
    }
    
    if {!$used} {
	foreach el [array names ntki info,$file,*] {
	    unset ntki($el)
	}
    }
    
    return
}

proc ntki::win_display {w node} {
    variable ntki

    parse_node $w $node file loc

    $w.status.msg configure -text ""

    if {![info exists ntki(info,$file,Top)]} {
	after idle [list ntki::check_flush $ntki(win,$w,file)]
    	info_load $w $file
    }

    if {![info exists ntki(info,$file,$loc)]} {
	$w.status.msg configure -text "$node not found"
    	bell
	return
    }

    if {[llength $ntki(info,$file,$loc)] == 1} {
	info_load $w $file [complete [file join [file dirname $file] \
		[lindex $ntki(info,$file,$loc) 0]] 1]
    }

    $w.main.text configure -state normal
    $w.main.text delete 1.0 end

    foreach {text tags text2 tags2} [lrange $ntki(info,$file,$loc) 4 end] {
	$w.main.text insert end $text $tags $text2 $tags2
    }

    $w.main.text configure -state disabled

    set ntki(win,$w,file) $file
    set ntki(win,$w,loc) $loc

    set spec [file rootname [file rootname [file tail $file]]]
    $w.status.node configure -text ($spec)$loc 
    wm title $w "Ntkinfo: ($spec)$loc"

    $w.toolbar.next configure \
	    -text "Next: [lindex $ntki(info,$file,$loc) 3]"
    $w.toolbar.prev configure \
	    -text "Previous:  [lindex $ntki(info,$file,$loc) 2]"
    $w.toolbar.up configure \
	    -text "Up: [lindex $ntki(info,$file,$loc) 1]"

    return
}


proc ntki::prompt_hist_up {w} {
    variable ntki

    if {$ntki(win,$w,hist_loc)} {
        if {$ntki(win,$w,hist_loc) == [llength $ntki(win,$w,history)]} {
            set ntki(win,$w,hist_cur) [$w.status.input get]
        }
        $w.status.input delete 0 end
        $w.status.input insert 0 \
		[lindex $ntki(win,$w,history) [incr ntki(win,$w,hist_loc) -1]]
    }

    return
}


proc ntki::prompt_hist_down {w} {
    variable ntki

    if {[incr ntki(win,$w,hist_loc)] > [llength $ntki(win,$w,history)]} {
        incr ntki(win,$w,hist_loc) -1
    } else {
        $w.status.input delete 0 end
        if {$ntki(win,$w,hist_loc) == [llength $ntki(win,$w,history)]} {
            $w.status.input insert 0 $ntki(win,$w,hist_cur)
        } else {
            $w.status.input insert 0 \
		    [lindex $ntki(win,$w,history) $ntki(win,$w,hist_loc)]
        }
    }
    
    return
}                                                      

proc ntki::prompt {w type} {
    variable ntki
    
    set ntki(win,$w,prompt) $type
    pack configure $w.status.msg -expand 0
    pack $w.status.input -side left -fill x -expand 1
    focus $w.status.input
    
    switch -glob -- $type {
	search* {
	    set msg "Search ([join [lrange [split $type _] 1 end] {, }]) :"
	} goto {
	    set msg  "Go to node:"
	} menu {
	    set msg "Go to menu:"
	} xref {
	    set msg "Go to xref:"
	}
    }

    $w.status.msg configure -text $msg

    return
}

proc ntki::hist_add {w line} {
    variable ntki
    

    return
}

# Somtimes a node's next field points to the first node in
# a menu which can cause cycles if you don't catch the case

proc ntki::lnext {w loc} {
    variable ntki

    set file $ntki(win,$w,file)
    set lnext [lindex [nodes $w $loc menu] 0]
    
    if {[string equal $lnext ""]} {
	set lnext $loc

        while {[string equal [lindex $ntki(info,$file,$lnext) 3] ""]} {
            set lnext [lindex $ntki(info,$file,$lnext) 1]
            
            if {[string equal $lnext ""] || [string equal $lnext Top]} {
                return
            } elseif {[string equal [lindex $ntki(info,$file,$lnext) 3] \
		    [lindex [nodes $w $lnext menu] 0]]} {
		set lnext [lindex $ntki(info,$file,$lnext) 1]
	    }
        }
	set lnext [lindex $ntki(info,$file,$lnext) 3]
    }

    return $lnext
}

proc ntki::lprev {w loc} {
    variable ntki

    set file $ntki(win,$w,file)
    set node [lindex $ntki(info,$file,$loc) 2]

    while {[string equal $node ""]} {
        set loc [lindex $ntki(info,$file,$loc) 1]

        if {[string equal $loc ""] || [string equal $loc (dir)]} {
            break
        }
        
        set node [lindex $ntki(info,$file,$loc) 2]
    }
    
    return $node
}

# do an initial search at the current location for
# the pattern. If not found loop through the logical
# structure of the document.

proc ntki::win_search {w pattern dir type {start @0,0}} {
    variable ntki
    
    set wt $w.main.text
    if {[string equal $dir forwards]} {
	set stop end
    } else {
	set stop 1.0
    }

    if {[string equal $type regexp] && [catch {regexp $pattern ""} error]} {
	$w.status.msg configure -text $error
	return
    }

    set beg [$wt search -$dir -$type -nocase -count c -- $pattern $start $stop]
    set ranges [$wt tag ranges sel]

    if {[llength $ranges]} {
	$wt tag remove sel [lindex $ranges 0] [lindex $ranges 1]
    }

    if {![string equal $beg ""]} {
	$wt tag add sel $beg "$beg + $c chars"
	$wt see $beg

	set ntki(win,$w,search_start) "$beg + $c chars"
	set ntki(win,$w,search_patt) $pattern
	set ntki(win,$w,search_type) $type
	set ntki(win,$w,search_dir) $dir
	
	$w.status.msg configure -text "Searching... found"
	return
    }

    set file $ntki(win,$w,file)
    set loc $ntki(win,$w,loc)
    catch {unset ntki(win,$w,interrupt)}

    while {1} {
	$w.status.msg configure -text "Searching..."

	if {[info exists ntki(win,$w,interrupt)]} {
	    $w.status.msg configure -text "Search interrupted"
	}

	if {[string equal $dir forwards]} {
	    set loc [lnext $w $loc]
	} else {
	    set loc [lprev $w $loc]
	}

	if {[string equal $loc ""] || [string match (*) $loc]} {
	    $w.status.msg configure -text "Not found"
	    return
	}

	if {[llength $ntki(info,$file,$loc)] == 1} {
	    info_load $w $file [complete [file join [file dirname $file] \
		    [lindex $ntki(info,$file,$loc) 0]] 1]
	}

	if {[string equal $type exact]} {
	    foreach {text tags} [lrange $ntki(info,$file,$loc) 4 end] {
		if {[string first $pattern $text] != -1} {
		    set found 1
		}
	    }
	} else {
	    foreach {text tags} [lrange $ntki(info,$file,$loc) 4 end] {
		if {[regexp -- $pattern $text]} {
		    set found 1
		}
	    }
	}
	
	if {[info exists found]} {
	    win_display $w $loc
	    win_search $w $pattern $dir $type
	    return
	}
	
	update
    }

    return
}

# cleanup after a prompt is used

proc ntki::prompt_quit {w} {
    variable ntki

    pack forget $w.status.input
    pack configure $w.status.msg -expand 1
    focus $w.main.text 
    $w.status.msg configure -text ""

    set ntki(win,$w,interrupt) 1

    return
}

# complete on prompt input

proc ntki::prompt_complete {w} {
    variable ntki

    set prefix [$w.status.input get]

    switch -exact -- $ntki(win,$w,prompt) {
	goto {
	    set nodes [all_nodes $ntki(win,$w,file)]
	} menu {
	    set nodes [nodes $w $ntki(win,$w,loc) menu]
	} xref {
	    set nodes [nodes $w $ntki(win,$w,loc) xref]
	}
    }

    set nodes [lmatch_prefix $nodes $prefix]

    if {[llength $nodes] == 0} {
	bell
    } elseif {[llength $nodes] == 1} {
	$w.status.input delete 0 end
	$w.status.input insert 0 [lindex $nodes 0]
    }
    
    return
}

# called when user done entering input into prompt

proc ntki::prompt_done {w} {
    variable ntki

    pack forget $w.status.input
    pack configure $w.status.msg -expand 1
    focus $w.main.text

    set input [$w.status.input get]
    prompt_quit $w

    if {[string equal $input ""]} {
	return
    }

    set ntki(win,$w,history) [lreplace $ntki(win,$w,history) 0 \
	    [expr {[llength $ntki(win,$w,history)] - $ntki(phist_len)}]]
    lappend ntki(win,$w,history) $input
    set ntki(win,$w,hist_loc) [llength $ntki(win,$w,history)]    

    switch -glob -- $ntki(win,$w,prompt) {
	search* {
	    foreach {x dir type} [split $ntki(win,$w,prompt) _] break
	    win_search $w $input $dir $type
	} goto {
	    win_display $w $input
	} menu {
	    if {[lsearch -exact [nodes $w $ntki(win,$w,loc) menu] $input] \
		    == -1} {
		bell
	    } else {
		win_display $w $input
	    }
	} xref {
	    if {[lsearch -exact [nodes $w $ntki(win,$w,loc) xref] $input]\
		    == -1} {
		bell
	    } else {
		win_display $w $input
	    }
	}
    }

    $w.status.input delete 0 end


    return
}

# return a list of all menu or xref nodes currently displayed

proc ntki::nodes {w loc type} {
    variable ntki

    set nodes [list]
    set file $ntki(win,$w,file)

    foreach {text tag} $ntki(info,$file,$loc) {
	if {[string equal $tag $type]} {
	    lappend nodes $text 
	}
    }

    return $nodes
}

# return a list of all nodes present in a loaded file

proc ntki::all_nodes {file} {
    variable ntki

    set nodes [list]
    foreach node [array names ntki info,$file,*] {
	lappend nodes [lindex [split $node ,] 2]
    }
    
    return $nodes
}

proc ntki::search_continue {w} {
    variable ntki

    if {![info exists ntki(win,$w,search_start)]} {
	bell
	return
    }

    win_search $w $ntki(win,$w,search_patt) $ntki(win,$w,search_dir)\
	    $ntki(win,$w,search_type) $ntki(win,$w,search_start)
    
    return
}

if {![info exists ntki_embed]} {
    source ./misc.tcl
    wm withdraw .
    option add *linkColor blue
    option add *titleFont {Courier 14 bold}
    option add *Ntkinfo*Text*font {Courier 12}

    ntki::init
    if {[llength $argv]} {
	ntki::win [lindex $argv 0]
    } else {
	ntki::win (dir)Top
    }
}
