1 # Browser for "Info files" as used by the Emacs documentation system.
3 # Now you can read Info files even if you can't spare the memory, time or
4 # disk space to run Emacs. (I have used this extensively on a Macintosh
5 # with 1 Megabyte main memory and a 20 Meg harddisk.)
7 # You can give this to someone with great fear of complex computer
8 # systems, as long as they can use a mouse.
10 # Another reason to use this is to encourage the use of Info for on-line
11 # documentation of software that is not related to Emacs or GNU.
12 # (In particular, I plan to redo the Python and STDWIN documentation
16 # NB: this is not a self-executing script. You must startup Python,
17 # import ibrowse, and call ibrowse.main(). On UNIX, the script 'ib'
23 # - The pathname of the directory (or directories) containing
24 # the standard Info files should be set by editing the
25 # value assigned to INFOPATH in module ifile.py.
27 # - The default font should be set by editing the value of FONT
28 # in this module (ibrowse.py).
30 # - For fastest I/O, you may look at BLOCKSIZE and a few other
31 # constants in ifile.py.
34 # This is a fairly large Python program, split in the following modules:
36 # ibrowse.py Main program and user interface.
37 # This is the only module that imports stdwin.
39 # ifile.py This module knows about the format of Info files.
40 # It is imported by all of the others.
42 # itags.py This module knows how to read prebuilt tag tables,
43 # including indirect ones used by large texinfo files.
45 # icache.py Caches tag tables and visited nodes.
48 # XXX There should really be a different tutorial, as the user interface
49 # XXX differs considerably from Emacs...
55 from stdwinevents
import *
57 from ifile
import NoSuchFile
, NoSuchNode
62 # This should be an acceptable argument for stdwin.setfont();
63 # on the Mac, this can be a pair (fontname, pointsize), while
64 # under X11 it should be a standard X11 font name.
65 # For best results, use a constant width font like Courier;
66 # many Info files contain tabs that don't align with other text
67 # unless all characters have the same width.
69 #FONT = ('Monaco', 9) # Mac
70 FONT
= '-schumacher-clean-medium-r-normal--14-140-75-75-c-70-iso8859-1' # X11
73 # Try not to destroy the list of windows when reload() is used.
74 # This is useful during debugging, and harmless in production...
83 # Default main function -- start at the '(dir)' node.
89 # Start at an arbitrary node.
90 # The default file is 'ibrowse'.
93 stdwin
.setdefscrollbars(0, 1)
95 stdwin
.setdefwinsize(76*stdwin
.textwidth('x'), 22*stdwin
.lineheight())
96 makewindow('ibrowse', ref
)
100 # Open a new browser window.
101 # Arguments specify the default file and a node reference
102 # (if the node reference specifies a file, the default file is ignored).
104 def makewindow(file, ref
):
105 win
= stdwin
.open('Info file Browser, by Guido van Rossum')
106 win
.mainmenu
= makemainmenu(win
)
107 win
.navimenu
= makenavimenu(win
)
108 win
.textobj
= win
.textcreate((0, 0), win
.getwinsize())
113 win
.dispatch
= idispatch
119 # Create the 'Ibrowse' menu for a new browser window.
121 def makemainmenu(win
):
122 mp
= win
.menucreate('Ibrowse')
124 additem(mp
, 'New window (clone)', 'K', iclone
)
125 additem(mp
, 'Help (tutorial)', 'H', itutor
)
126 additem(mp
, 'Command summary', '?', isummary
)
127 additem(mp
, 'Close this window', 'W', iclose
)
128 additem(mp
, '', '', None)
129 additem(mp
, 'Copy to clipboard', 'C', icopy
)
130 additem(mp
, '', '', None)
131 additem(mp
, 'Search regexp...', 'S', isearch
)
132 additem(mp
, '', '', None)
133 additem(mp
, 'Reset node cache', '', iresetnodecache
)
134 additem(mp
, 'Reset entire cache', '', iresetcache
)
135 additem(mp
, '', '', None)
136 additem(mp
, 'Quit', 'Q', iquit
)
139 # Create the 'Navigation' menu for a new browser window.
141 def makenavimenu(win
):
142 mp
= win
.menucreate('Navigation')
144 additem(mp
, 'Menu item...', 'M', imenu
)
145 additem(mp
, 'Follow reference...', 'F', ifollow
)
146 additem(mp
, 'Go to node...', 'G', igoto
)
147 additem(mp
, '', '', None)
148 additem(mp
, 'Next node in tree', 'N', inext
)
149 additem(mp
, 'Previous node in tree', 'P', iprev
)
150 additem(mp
, 'Up in tree', 'U', iup
)
151 additem(mp
, 'Last visited node', 'L', ilast
)
152 additem(mp
, 'Top of tree', 'T', itop
)
153 additem(mp
, 'Directory node', 'D', idir
)
156 # Add an item to a menu, and a function to its list of callbacks.
157 # (Specifying all in one call is the only way to keep the menu
158 # and the list of callbacks in synchrony.)
160 def additem(mp
, text
, shortcut
, function
):
162 mp
.additem(text
, shortcut
)
165 mp
.callback
.append(function
)
168 # Stdwin event processing main loop.
169 # Return when there are no windows left.
170 # Note that windows not in the windows list don't get their events.
174 event
= stdwin
.getevent()
175 if event
[1] in windows
:
177 event
[1].dispatch(event
)
178 except KeyboardInterrupt:
179 # The user can type Control-C (or whatever)
180 # to leave the browser without closing
181 # the window. Mainly useful for
185 # During debugging, it was annoying if
186 # every mistake in a callback caused the
187 # whole browser to crash, hence this
188 # handler. In a production version
189 # it may be better to disable this.
194 if type(val
) <> type(''):
196 msg
= msg
+ ': ' + val
197 msg
= 'Oops, an exception occurred: ' + msg
203 # Handle one event. The window is taken from the event's window item.
204 # This function is placed as a method (named 'dispatch') on the window,
205 # so the main loop will be able to handle windows of a different kind
206 # as well, as long as they are all placed in the list of windows.
208 def idispatch(event
):
209 type, win
, detail
= event
211 if not keybindings
.has_key(detail
):
212 detail
= string
.lower(detail
)
213 if keybindings
.has_key(detail
):
214 keybindings
[detail
](win
)
216 if detail
in '0123456789':
218 if i
< 0: i
= len(win
.menu
) + i
219 if 0 <= i
< len(win
.menu
):
220 topic
, ref
= win
.menu
[i
]
225 if type == WE_COMMAND
:
226 if detail
== WC_LEFT
:
228 elif detail
== WC_RIGHT
:
230 elif detail
== WC_UP
:
232 elif detail
== WC_DOWN
:
234 elif detail
== WC_BACKSPACE
:
236 elif detail
== WC_RETURN
:
244 pass # A THINK C console menu was selected
245 elif mp
in (win
.mainmenu
, win
.navimenu
):
246 mp
.callback
[item
](win
)
247 elif mp
== win
.nodemenu
:
248 topic
, ref
= win
.menu
[item
]
250 elif mp
== win
.footmenu
:
251 topic
, ref
= win
.footnotes
[item
]
255 win
.textobj
.move((0, 0), win
.getwinsize())
256 (left
, top
), (right
, bottom
) = win
.textobj
.getrect()
257 win
.setdocsize(0, bottom
)
262 if not win
.textobj
.event(event
):
270 win
.textobj
.setfocus(0, 0) # To restart searches
273 lh
= stdwin
.lineheight() # XXX Should really use the window's...
274 h
, v
= win
.getorigin()
275 docwidth
, docheight
= win
.getdocsize()
276 width
, height
= win
.getwinsize()
277 if v
+ height
>= docheight
:
280 increment
= max(lh
, ((height
- 2*lh
) / lh
) * lh
)
285 lh
= stdwin
.lineheight() # XXX Should really use the window's...
286 h
, v
= win
.getorigin()
290 width
, height
= win
.getwinsize()
291 increment
= max(lh
, ((height
- 2*lh
) / lh
) * lh
)
292 v
= max(0, v
- increment
)
296 # Ibrowse menu callbacks
299 stdwin
.setdefwinsize(win
.getwinsize())
300 makewindow(win
.file, win
.node
)
303 # The course looks best at 76x22...
304 stdwin
.setdefwinsize(76*stdwin
.textwidth('x'), 22*stdwin
.lineheight())
305 makewindow('ibrowse', 'Help')
308 stdwin
.setdefwinsize(76*stdwin
.textwidth('x'), 22*stdwin
.lineheight())
309 makewindow('ibrowse', 'Summary')
313 # Remove the window from the windows list so the mainloop
314 # will notice if all windows are gone.
315 # Delete the textobj since it constitutes a circular reference
316 # to the window which would prevent it from being closed.
317 # (Deletion is done by assigning None to avoid crashes
318 # when closing a half-initialized window.)
325 focustext
= win
.textobj
.getfocustext()
329 stdwin
.rotatecutbuffers(1)
330 stdwin
.setcutbuffer(0, focustext
)
331 # XXX Should also set the primary selection...
335 pat
= stdwin
.askstr('Search pattern:', win
.pat
)
336 except KeyboardInterrupt:
341 stdwin
.message('No previous pattern')
344 cpat
= regexp
.compile(pat
)
345 except regexp
.error
, msg
:
346 stdwin
.message('Bad pattern: ' + msg
)
349 f1
, f2
= win
.textobj
.getfocus()
351 match
= cpat
.match(text
, f2
)
356 win
.textobj
.setfocus(a
, b
)
359 def iresetnodecache(win
):
360 icache
.resetnodecache()
362 def iresetcache(win
):
366 for win
in windows
[:]:
370 # Navigation menu callbacks
373 ichoice(win
, 'Menu item (abbreviated):', win
.menu
, whichmenuitem(win
))
376 ichoice(win
, 'Follow reference named (abbreviated):', \
377 win
.footnotes
, whichfootnote(win
))
381 choice
= stdwin
.askstr('Go to node (full name):', '')
382 except KeyboardInterrupt:
385 stdwin
.message('Sorry, Go to has no default')
390 prev
, next
, up
= win
.header
397 prev
, next
, up
= win
.header
404 prev
, next
, up
= win
.header
415 lastnode
, lastfocus
= win
.last
[i
]
417 if len(win
.last
) > i
+1:
418 # The move succeeded -- restore the focus
419 win
.textobj
.setfocus(lastfocus
)
420 # Delete the stack top even if the move failed,
421 # else the whole stack would remain unreachable
422 del win
.last
[i
:] # Delete the entry pushed by imove as well!
431 # Special and generic callbacks
435 default
= whichmenuitem(win
)
436 for topic
, ref
in win
.menu
:
440 topic
, ref
= win
.menu
[0]
445 def ichoice(win
, prompt
, list, default
):
453 choice
= stdwin
.askstr(prompt
, default
)
454 except KeyboardInterrupt:
458 choice
= string
.lower(choice
)
460 for topic
, ref
in list:
461 topic
= string
.lower(topic
)
462 if topic
[:n
] == choice
:
465 stdwin
.message('Sorry, no topic matches ' + `choice`
)
468 # Follow a reference, in the same window.
471 savetitle
= win
.gettitle()
472 win
.settitle('Looking for ' + ref
+ '...')
475 file, node
, header
, menu
, footnotes
, text
= \
476 icache
.get_node(win
.file, ref
)
477 except NoSuchFile
, file:
478 win
.settitle(savetitle
)
480 'Sorry, I can\'t find a file named ' + `
file`
+ '.')
482 except NoSuchNode
, node
:
483 win
.settitle(savetitle
)
485 'Sorry, I can\'t find a node named ' + `node`
+ '.')
488 win
.settitle('Found (' + file + ')' + node
+ '...')
490 if win
.file and win
.node
:
491 lastnode
= '(' + win
.file + ')' + win
.node
492 win
.last
.append(lastnode
, win
.textobj
.getfocus())
497 win
.footnotes
= footnotes
500 win
.setorigin(0, 0) # Scroll to the beginnning
501 win
.textobj
.settext(text
)
502 win
.textobj
.setfocus(0, 0)
503 (left
, top
), (right
, bottom
) = win
.textobj
.getrect()
504 win
.setdocsize(0, bottom
)
506 if win
.footmenu
: win
.footmenu
.close()
507 if win
.nodemenu
: win
.nodemenu
.close()
513 win
.nodemenu
= win
.menucreate('Menu')
515 for topic
, ref
in menu
:
517 win
.nodemenu
.additem(topic
, `digit`
)
519 win
.nodemenu
.additem(topic
)
522 win
.footnotes
= footnotes
524 win
.footmenu
= win
.menucreate('Footnotes')
525 for topic
, ref
in footnotes
:
526 win
.footmenu
.additem(topic
)
528 win
.settitle('(' + win
.file + ')' + win
.node
)
531 # Find menu item at focus
533 findmenu
= regexp
.compile('^\* [mM]enu:').match
534 findmenuitem
= regexp
.compile( \
535 '^\* ([^:]+):[ \t]*(:|\([^\t]*\)[^\t,\n.]*|[^:(][^\t,\n.]*)').match
537 def whichmenuitem(win
):
540 match
= findmenu(win
.text
)
545 f1
, f2
= win
.textobj
.getfocus()
547 while i
< len(win
.text
):
548 match
= findmenuitem(win
.text
, i
)
551 (a
, b
), (a1
, b1
), (a2
, b2
) = match
554 lastmatch
= win
.text
[a1
:b1
]
559 # Find footnote at focus
562 regexp
.compile('\*[nN]ote ([^:]+):[ \t]*(:|[^:][^\t,\n.]*)').match
564 def whichfootnote(win
):
565 if not win
.footnotes
:
568 f1
, f2
= win
.textobj
.getfocus()
570 while i
< len(win
.text
):
571 match
= findfootnote(win
.text
, i
)
574 (a
, b
), (a1
, b1
), (a2
, b2
) = match
577 lastmatch
= win
.text
[a1
:b1
]
582 # Now all the "methods" are defined, we can initialize the table
589 keybindings
['k'] = iclone
590 keybindings
['h'] = itutor
591 keybindings
['?'] = isummary
592 keybindings
['w'] = iclose
594 keybindings
['c'] = icopy
596 keybindings
['s'] = isearch
598 keybindings
['q'] = iquit
600 # Navigation commands
602 keybindings
['m'] = imenu
603 keybindings
['f'] = ifollow
604 keybindings
['g'] = igoto
606 keybindings
['n'] = inext
607 keybindings
['p'] = iprev
608 keybindings
['u'] = iup
609 keybindings
['l'] = ilast
610 keybindings
['d'] = idir
611 keybindings
['t'] = itop
615 keybindings
['b'] = ibeginning
616 keybindings
['.'] = ibeginning
617 keybindings
[' '] = iforward