rework default shortcuts
[flinks.git] / flinkspkg / Browser.py
blob19c58ddaf9187e7f7477ec1b54502a3e713f5fa8
1 # Part of flinks
2 # (C) Martin Bays 2008
3 # Released under the terms of the GPLv3
5 import sys, os
6 from string import *
8 from time import sleep
9 import re
11 import signal
13 import curses
14 from curses.wrapper import wrapper
15 #from curses.textpad import Textbox
16 from TextboxPad import TextboxPad
17 import curses.ascii as ascii
19 import pickle
21 from IndexedHypertext import IndexedHypertext
23 class QuitException(Exception):
24 pass
26 class Browser:
27 def __init__(self, scr, config):
29 self.settings = config
30 self.scr = scr
32 self.setGeom()
34 for i in range(1,8):
35 curses.init_pair(i, i, curses.COLOR_BLACK)
37 self.scr.timeout(0)
39 self.scr.leaveok(1)
40 curses.curs_set(0)
42 self.delay = 60.0/self.wpm
43 self.paused = True
45 self.lastSearch = None
47 self.indexedHypertext = None
49 self.cachedDocuments = []
51 # history *ends* with most recent url
52 # unHistory *ends* with *current* url
53 self.history = []
54 self.unHistory = []
56 self.tricky = [""]*self.trickyDisplayNum
58 self.counting = False
59 self.count = 0
61 self.skim = False
62 self.normalWpm = self.wpm
64 self.interruptible = False
66 self.urlHistory = []
67 self.commandHistory = []
68 self.searchHistory = []
70 try:
71 marksFile = open(os.getenv("HOME")+"/.flinksMarks", 'r')
72 self.globalMarks = pickle.load(marksFile)
73 except IOError:
74 self.globalMarks = {}
76 # XXX: we use self.wpm as convenient shorthand for
77 # self.settings.options["wpm"], etc. The overriding below of __getattr__
78 # and __setattr__ just implement this.
79 def __getattr__(self, name):
80 if name != "settings" and name in self.settings.optionNames:
81 return self.settings.options[name]
82 else:
83 raise AttributeError
84 def __setattr__(self, name, value):
85 if name != "settings" and name in self.settings.optionNames:
86 self.settings.options[name] = value
87 else:
88 self.__dict__[name] = value
90 class Geom:
91 def __init__(self, scr, desiredLinkDisplayNum, desiredTrickyDisplayNum):
92 (maxy, maxx) = scr.getmaxyx()
94 # effective maximums, i.e. with anything bigger scr.move(y,x)
95 # returns ERR:
96 self.maxy = max(0, maxy-2)
97 self.maxx = max(0, maxx-1)
99 # we aim for following layout, and squash as necessary
100 # desiredLinkDisplayNum lines of links
102 # wordline
104 # desiredTrickyDisplayNum lines of trickies
105 # statusline
106 # infoline
107 # commandline
108 squashed = self.maxy < (desiredLinkDisplayNum +
109 desiredTrickyDisplayNum + 6)
111 self.wordline = self.maxy/2
113 self.linkDisplayNum = min(desiredLinkDisplayNum,
114 self.wordline-1)
115 self.trickyDisplayNum = min(desiredTrickyDisplayNum,
116 (self.maxy - self.wordline) - 2)
118 self.linkline = self.wordline - self.linkDisplayNum - (not squashed)
120 self.trickyline = self.wordline + (self.maxy > self.wordline) + (not squashed)
121 self.statusline = self.trickyline + self.trickyDisplayNum
122 self.infoline = self.statusline + (self.maxy > self.statusline)
123 self.commandline = self.infoline + (self.maxy > self.infoline)
125 def setGeom(self):
126 self.geom = self.Geom(self.scr, self.linkDisplayNum,
127 self.trickyDisplayNum)
129 def blockGetKey(self, promptLine=None, prompt=None):
130 if promptLine and prompt:
131 self.displayCleanCentred(promptLine, prompt)
132 self.scr.timeout(-1)
133 key = self.scr.getkey()
134 self.scr.timeout(0)
135 if promptLine and prompt:
136 self.clearLine(promptLine)
137 if key == '\x1b' or key == '\x07':
138 # esc or ^G: cancel
139 return None
140 return key
141 def displayCentred(self, line, str, attr=0):
142 self.scr.addstr(line,
143 max(0,(self.geom.maxx-len(str))/2),
144 str[:self.geom.maxx],
145 attr)
146 def clearLine(self, line):
147 self.scr.move(line,0)
148 self.scr.clrtoeol()
149 def displayCleanCentred(self, line, str, attr=0):
150 self.clearLine(line)
151 self.displayCentred(line, str, attr)
152 self.scr.refresh()
153 def displayStatus(self):
154 if self.geom.statusline == self.geom.wordline: return
155 def getStatusLine(abbreviated=False):
156 if not abbreviated:
157 text = 'WPM: %d' % self.wpm
158 if self.showPoint:
159 text += '; word: %d' % (self.indexedHypertext.atWord+1)
160 text += '; sentence: %d' % (self.indexedHypertext.atSentence+1)
161 text += '; paragraph: %d' % (self.indexedHypertext.atPara+1)
162 text += '; link: %d' % (self.indexedHypertext.lastLink+1)
163 if self.paused: text += '; PAUSED'
164 else:
165 text = '%d ' % self.wpm
166 if self.showPoint:
167 text += '%d/' % (self.indexedHypertext.atWord+1)
168 text += '%d/' % (self.indexedHypertext.atSentence+1)
169 text += '%d/' % (self.indexedHypertext.atPara+1)
170 text += '%d ' % (self.indexedHypertext.lastLink+1)
171 if self.paused: text += 'P'
172 return text
174 self.clearLine(self.geom.statusline)
176 line = getStatusLine(self.abbreviate)
177 if len(line) >= self.geom.maxx and not self.abbreviate:
178 line = getStatusLine(True)
179 if len(line) >= self.geom.maxx:
180 return
182 self.displayCentred(self.geom.statusline, line)
184 def displayLinkListEntry(self, linknum, emph=False):
185 if self.geom.linkDisplayNum <= 0: return
186 line = self.geom.linkline + linknum%self.geom.linkDisplayNum
187 if linknum < 0:
188 self.clearLine(line)
189 return
190 attr = self.attrOfLinknum(linknum)
191 if emph:
192 attr |= curses.A_BOLD
193 emphstr = '*'
194 else:
195 emphstr = ''
196 link = self.indexedHypertext.links[linknum]
197 str = '%s%s "%s": %s' % (
198 emphstr, self.strOfLinknum(linknum),
199 self.abbreviateStr(self.cleanWord(link["word"]), 10),
200 self.abbreviateStr(link["url"])
202 self.clearLine(line)
203 self.displayCentred(line, str, attr)
204 def displayLinkList(self):
205 for linknum in range(
206 self.indexedHypertext.lastLink - self.geom.linkDisplayNum,
207 self.indexedHypertext.lastLink+1):
208 emphasize = (self.indexedHypertext.lastLink - linknum <
209 self.linkDisplayEmphNum)
210 self.displayLinkListEntry(linknum, emphasize)
212 def attrOfLinknum(self, linknum):
213 if self.geom.linkDisplayNum <= 0: return 1
214 c = (linknum%self.geom.linkDisplayNum)%5+1
215 if c >= 4: c+=1 # don't use blue (hard to see against black)
216 return curses.color_pair(c)
218 def strOfLinknum(self, linknum):
219 if self.geom.linkDisplayNum <= 0: return
220 return '[%d]' % ((linknum%self.geom.linkDisplayNum)+1)
222 def displayTrickyList(self):
223 for i in range(self.geom.trickyDisplayNum):
224 self.clearLine(self.geom.trickyline + i)
225 self.displayCentred(
226 self.geom.trickyline + i,
227 self.tricky[i]
230 def addTricky(self, word):
231 if self.geom.trickyDisplayNum <= 0:
232 return
233 for t in self.tricky:
234 if word == t:
235 # already in the list; don't repeat
236 return
237 for i in range(self.geom.trickyDisplayNum-1, 0, -1):
238 self.tricky[i] = self.tricky[i-1]
239 self.tricky[0] = word
240 self.displayTrickyList()
242 def isTricky(self, word):
243 return (
244 bool(re.search('\d', word)) or bool(re.search('\w-\w', word))
245 or len(word) > 15
248 def cleanWord(self, word):
249 return strip(word, ' ,.;:?!(){}')
251 def abbreviateStr(self, str, maxLen = None):
252 if maxLen is None: maxLen = 2*self.geom.maxx/3
253 if len(str) <= maxLen: return str
254 return '%s...%s' % (str[:(maxLen/2)-3], str[-((maxLen/2)-3):])
256 def refreshMoved(self):
257 self.showCurrentWord()
258 self.displayStatus()
259 self.displayLinkList()
260 self.displayTrickyList()
262 def applyUrlShortcuts(self, command, depth=0):
263 words = split(command)
264 try:
265 numArgs, shortcut = self.settings.urlShortcuts[words[0]]
266 except (IndexError, KeyError):
267 return command
269 for arg in range(numArgs):
270 i = arg+1
271 try:
272 if i == numArgs:
273 # last argument gets all remaining words
274 repl = join(words[i:])
275 else:
276 repl = words[i]
277 except IndexError:
278 return shortcut
280 shortcut = re.sub('%%%d' % i, repl, shortcut)
282 shortcut = re.sub('%c', self.indexedHypertext.url, shortcut)
283 shortcut = re.sub('\\$HOME', os.getenv("HOME"), shortcut)
284 # apply recursively
285 if depth < 10:
286 return self.applyUrlShortcuts(shortcut, depth+1)
287 else:
288 return shortcut
290 def go(self, command):
291 url = self.applyUrlShortcuts(command)
292 self.gotoNewUrl(url)
294 def gotoNewUrl(self, url):
295 if self.indexedHypertext:
296 self.history.append(self.indexedHypertext.url)
297 self.gotoUrl(url)
298 # Turns out to be neater to have the current document in unHistory:
299 self.unHistory = [url]
300 def goHistory(self, num=1):
301 try:
302 self.gotoUrl(self.history[-num])
303 for i in range(num):
304 self.unHistory.append(self.history.pop())
305 except IndexError:
306 return None
307 def goUnHistory(self, num=1):
308 try:
309 self.gotoUrl(self.unHistory[-num-1])
310 for i in range(num):
311 self.history.append(self.unHistory.pop())
312 except IndexError:
313 return None
315 def gotoUrl(self, url, noCache=False):
316 shownError = False
317 try:
318 m = re.match('(.*)#(.*)$', url)
319 if m:
320 baseUrl, anchor = m.groups()
321 else:
322 baseUrl, anchor = url, None
323 cached = self.findCachedDocument(baseUrl)
324 if cached and not noCache:
325 self.indexedHypertext = cached
326 else:
327 self.displayCleanCentred(self.geom.commandline, self.abbreviateStr("Loading %s" % url))
328 self.scr.refresh()
330 self.interruptible = True
331 self.indexedHypertext = IndexedHypertext(baseUrl)
332 self.interruptible = False
334 if self.indexedHypertext.isEmpty():
335 err = self.indexedHypertext.lynxErr.strip()
336 line = join(err.splitlines()[0:2], '; ')
337 self.displayCleanCentred(self.geom.commandline,
338 self.abbreviateStr("Failed: %s" % line))
339 shownError = True
340 return False
342 if anchor:
343 self.interruptible = True
344 n = self.indexedHypertext.findAnchor(anchor)
345 self.interruptible = False
347 if n is not None:
348 self.indexedHypertext.mark('\'')
349 self.indexedHypertext.seekWord(n)
350 else:
351 self.displayCleanCentred(self.geom.commandline,
352 self.abbreviateStr("No such anchor: %s" % anchor))
353 shownError = True
355 self.displayCleanCentred(self.geom.infoline, self.abbreviateStr("%s" % baseUrl))
356 self.refreshMoved()
358 if not noCache: self.cacheDocument(self.indexedHypertext)
360 finally:
361 self.interruptible = False
362 if not shownError:
363 self.clearLine(self.geom.commandline)
365 def cacheDocument(self, doc):
366 if self.findCachedDocument(doc.url):
367 return
368 else:
369 self.cachedDocuments.append((doc.url, doc))
370 if len(self.cachedDocuments) > self.maxCached:
371 self.cachedDocuments.pop(0)
372 def findCachedDocument(self, url):
373 for (u, doc) in self.cachedDocuments:
374 if u == url: return doc
375 return None
377 def showCurrentWord(self, word=None):
378 if word is None:
379 word = self.indexedHypertext.currentWord()
381 tricky = self.isTricky(word)
382 linknum = self.indexedHypertext.currentLink()
384 if linknum is None:
385 if tricky: attr = curses.A_BOLD
386 else: attr = 0
387 self.displayCleanCentred(self.geom.wordline, word, attr)
388 else:
389 self.displayCleanCentred(self.geom.wordline, word,
390 self.attrOfLinknum(linknum) | curses.A_BOLD)
392 self.displayLinkListEntry(linknum, True)
394 if linknum >= self.linkDisplayEmphNum:
395 self.displayLinkListEntry(linknum-self.linkDisplayEmphNum,
396 False)
398 if tricky:
399 self.addTricky(self.cleanWord(word))
401 if self.showPoint: self.displayStatus()
403 def mainLoop(self):
404 def sigHandlerQuit(sig, frame):
405 raise QuitException
406 signal.signal(15, sigHandlerQuit)
408 def sigHandlerInt(sig, frame):
409 # Occasionally, we can safely allow ^C to interrupt a section of
410 # code.
411 if self.interruptible:
412 raise KeyboardInterrupt
413 signal.signal(2, sigHandlerInt)
415 def applyCount(method, args):
416 # update args, replacing/appending any count argument
417 try:
418 countarg = self.bindableMethods[method][2]
419 if countarg > 0:
420 if len(args) >= countarg:
422 currentArg = args[countarg-1]
423 if (currentArg.__class__ == int and
424 currentArg < 0):
425 self.count = -self.count
427 args[countarg-1] = self.count
429 elif len(args)+1 == countarg:
430 args.append(count)
431 except IndexError:
432 pass
433 endCount()
434 def endCount():
435 self.counting = False
436 self.countWin.erase()
437 self.countWin.refresh()
438 del self.countWin
440 def handleResize():
441 self.setGeom()
442 self.scr.erase()
443 self.scr.refresh()
444 self.refreshMoved()
446 self.go(self.initialUrl)
447 self.displayStatus()
449 self.blankSpace = 0
451 if self.blink:
452 self.blinkWait = max(1,
453 ( (self.blinkPeriod - self.blinkLength) * self.wpm
454 + 30000) / 60000)
455 self.blinkNum = 0
457 def flashWord():
458 # handle blink timers
459 if self.blink:
460 if self.blinkNum > 0:
461 # blink in progress
462 self.blinkNum -= 1
463 if self.blinkNum > 0:
464 return
465 elif self.blinkWait == 0:
466 if (not self.blinkWaitSentence or
467 self.indexedHypertext.atSentenceEnd()):
468 # start blink
469 self.blinkNum = max(1,
470 (self.blinkLength * self.wpm + 30000) / 60000)
471 self.displayCleanCentred(self.geom.wordline, "{*blink*}")
472 self.blinkWait = max(1,
473 ( (self.blinkPeriod - self.blinkLength) *
474 self.wpm + 30000) / 60000)
475 return
476 elif self.blinkWait > 0:
477 self.blinkWait -= 1
479 # handle blanking
480 if self.blankSpace >= 0:
481 self.blankSpace -= 1
482 self.displayCleanCentred(self.geom.wordline, "")
483 return
485 # no special cases apply - actually show the next word:
486 try:
487 word = self.indexedHypertext.readWord()
488 if self.indexedHypertext.atParaEnd():
489 self.blankSpace = self.paraBlankSpace
490 elif self.indexedHypertext.atSentenceEnd():
491 self.blankSpace = self.sentenceBlankSpace
492 except StopIteration:
493 word = None
495 self.showCurrentWord(word)
496 return
498 ERRTimeout = 0
499 # main loop:
500 while True:
501 try:
502 if ERRTimeout > 0:
503 ERRTimeout -= 1
504 try:
505 if not self.paused and not self.indexedHypertext.atEnd():
506 flashWord()
508 # handle input:
509 c = None
510 escBound = self.settings.keyBindings.has_key(27)
511 metaBit = False
512 while c != -1:
513 c = self.scr.getch()
515 if not escBound:
516 if c == 27:
517 metaBit = True
518 continue
519 elif metaBit:
520 if c == -1:
521 c = 27
522 else:
523 # set high bit
524 c |= 0x80
525 metaBit = False
527 if c == curses.KEY_RESIZE:
528 handleResize()
530 elif self.counting and ord('0') <= c <= ord('9'):
531 self.count = 10*self.count + c - ord('0')
532 self.countWin.addch(chr(c))
533 self.countWin.refresh()
535 else:
536 binding = self.settings.keyBindings.get(c)
537 if binding is not None:
538 method = binding[0]
539 args = binding[1][:]
541 if self.counting:
542 applyCount(method, args)
544 getattr(self, 'doKey'+method).__call__(*args)
545 elif self.counting and c in [27,7]:
546 # esc or ^G: cancel count
547 endCount()
549 # sleep, with blanking if self.blankBetween
550 if (self.blankBetween and
551 not self.paused and
552 not self.indexedHypertext.atEnd() and
553 not (self.blink and self.blinkNum > 0)):
554 sleep(self.delay/2)
555 self.clearLine(self.geom.wordline)
556 self.scr.refresh()
557 sleep(self.delay/2)
558 else:
559 sleep(self.delay)
560 except curses.error:
561 if ERRTimeout == 0:
562 # Curses threw an error, but that might just be because we
563 # need to resize and haven't yet processed KEY_RESIZE.
564 # So we resize now, and see if we get another curses error
565 # within the next couple of goes round the main loop.
566 self.setGeom()
567 ERRTimeout = 2
568 else:
569 raise "Curses returned ERR - terminal too short?"
570 except KeyboardInterrupt:
571 pass
573 def onExit(self):
574 # emulating vim - global marks '0'-'9' give positions on last 10 exits
575 for i in range(9,0,-1):
576 try: self.globalMarks[str(i)] = self.globalMarks[str(i-1)][:]
577 except KeyError: pass
578 self.globalMarks['0'] = \
579 [self.indexedHypertext.url, self.indexedHypertext.atWord]
581 def writeState(self):
582 try:
583 marksFile = open(os.getenv("HOME")+"/.flinksMarks", 'w')
584 pickle.dump(self.globalMarks, marksFile)
585 except IOError:
586 pass
588 bindableMethods = {
589 # <name> : [<min args>, <max args>, [<count arg>]]
590 'ChangeSpeed' : [1,1,1],
591 'SeekSentence' : [0,1,1],
592 'SeekParagraph' : [0,1,1],
593 'SeekWord' : [0,1,1],
594 'SeekLink' : [0,1,1],
595 'SeekStart' : [0,0],
596 'SeekEnd' : [0,0],
597 'History' : [0,1,1],
598 'FollowLink' : [1,1,1],
599 'Go' : [0,0],
600 'GoCurrent' : [0,0],
601 'Search' : [0,1,1],
602 'SearchAgain' : [0,1,1],
603 'Count' : [0,0],
604 'Mark' : [0,0],
605 'GoMark' : [0,0],
606 'Reload' : [0,0],
607 'Pause' : [0,0],
608 'Command' : [0,0],
609 'Skim' : [0,0],
610 'Refresh' : [0,0],
611 'Quit' : [0,0],
614 def doKeyChangeSpeed(self, amount):
615 self.wpm += amount
616 if self.wpm < 30: self.wpm = 30
617 self.delay = 60.0/self.wpm
618 self.displayStatus()
620 def doKeySeekSentence(self, n=1):
621 self.indexedHypertext.seekSentenceRelative(n)
622 self.refreshMoved()
623 def doKeySeekParagraph(self, n=1):
624 self.indexedHypertext.seekParaRelative(n)
625 self.refreshMoved()
626 def doKeySeekWord(self, n=1):
627 self.indexedHypertext.seekWordRelative(n)
628 self.refreshMoved()
629 def doKeySeekLink(self, n=1):
630 self.indexedHypertext.seekLinkRelative(n)
631 self.refreshMoved()
633 def doKeySeekStart(self):
634 self.indexedHypertext.mark('\'')
635 self.indexedHypertext.seekWord(0)
636 self.refreshMoved()
637 def doKeySeekEnd(self):
638 self.indexedHypertext.mark('\'')
639 self.indexedHypertext.seekEnd()
640 self.refreshMoved()
642 def doKeyFollowLink(self, n):
643 n -= 1
644 nl = self.indexedHypertext.lastLink % self.geom.linkDisplayNum
645 if n <= nl:
646 linknum = self.indexedHypertext.lastLink + (n-nl)
647 else:
648 linknum = self.indexedHypertext.lastLink - self.geom.linkDisplayNum + (n-nl)
649 try:
650 url = self.indexedHypertext.links[linknum]["url"]
651 self.gotoNewUrl(url)
652 except IndexError:
653 pass
655 def readLine(self, prompt, initial='', shortPrompt=None, history=None):
656 if self.abbreviate or self.geom.maxx <= 10:
657 if shortPrompt is not None:
658 prompt = shortPrompt
659 else:
660 prompt = prompt[0]+":"
662 promptX = max(1, self.geom.maxx/6 - len(prompt))
663 boxX = promptX + len(prompt)
664 boxLen = min(self.geom.maxx - boxX - 1, 2*self.geom.maxx/3)
666 self.scr.addstr(self.geom.commandline, promptX,
667 prompt[:self.geom.maxx-5])
668 self.scr.refresh()
670 curses.curs_set(1)
672 pad = curses.newpad(1, 200)
673 pad.scrollok(1)
674 box = TextboxPad(pad,
675 self.geom.commandline, boxX,
676 self.geom.commandline, boxX + boxLen - 1,
677 history=history)
679 for char in initial:
680 box.do_command(char)
682 class cancelReadline(Exception):
683 pass
684 def cancelValidate(ch):
685 if ch == ascii.BEL or ch == 27:
686 # ^G and escape cancel the readLine
687 raise cancelReadline
688 return ch
689 try:
690 try:
691 read = box.edit(cancelValidate)
692 if history is not None:
693 history.append(strip(read))
694 return read
695 except cancelReadline:
696 return None
697 finally:
698 curses.curs_set(0)
699 self.clearLine(self.geom.commandline)
701 def doKeyGo(self):
702 command = self.readLine("Go: ", history=self.urlHistory)
703 if command:
704 self.go(strip(command))
706 def doKeyGoCurrent(self):
707 command = self.readLine("Go: ", self.indexedHypertext.url, history=self.urlHistory)
708 if command:
709 self.go(strip(command))
711 def charOfSearchDir(self, dir=1):
712 if dir == 1: return '/'
713 else: return '?'
714 def search(self, pattern, n=1):
715 dir = cmp(n,0)
716 self.displayCleanCentred(self.geom.commandline,
717 self.charOfSearchDir(dir)+pattern)
718 try:
719 for i in range(abs(n)):
720 ret = self.indexedHypertext.search(strip(pattern), dir)
721 if not ret: break
722 self.refreshMoved()
723 finally:
724 self.clearLine(self.geom.commandline)
725 def doKeySearch(self, n=1):
726 dir = cmp(n,0)
727 pattern = self.readLine("Search: ",
728 shortPrompt = self.charOfSearchDir(dir),
729 history=self.searchHistory)
730 if pattern:
731 self.search(pattern, n)
732 self.lastSearch = {"pattern": pattern, "dir": dir}
733 def doKeySearchAgain(self, n=1):
734 if self.lastSearch:
735 self.search(self.lastSearch["pattern"],
736 self.lastSearch["dir"]*n)
738 def doKeyPause(self):
739 self.paused = not self.paused
740 self.displayStatus()
742 def doKeyHistory(self, n=-1):
743 if n < 0:
744 self.goHistory(-n)
745 else:
746 self.goUnHistory(n)
748 def doKeyCount(self):
749 self.counting = True
750 self.count = 0
752 boxX = self.geom.maxx/6
753 boxLen = min(self.geom.maxx - boxX - 1, 2*self.geom.maxx/3)
754 self.countWin = curses.newwin(1, boxLen, self.geom.commandline, boxX)
755 if not self.abbreviate:
756 prompt = "Count: "
757 else:
758 prompt = "C"
759 self.countWin.addstr(prompt[:self.geom.maxx])
760 self.countWin.refresh()
762 def doKeyMark(self):
763 key = self.blockGetKey(self.geom.commandline,
764 self.abbreviate and "m" or "Set mark:")
765 if key is None or len(key) != 1:
766 return
767 if 'a' <= key <= 'z':
768 # local mark
769 self.indexedHypertext.mark(key)
770 elif 'A' <= key <= 'Z':
771 # global marks
772 self.globalMarks[key] =\
773 [self.indexedHypertext.url, self.indexedHypertext.atWord]
774 def doKeyGoMark(self):
775 key = self.blockGetKey(self.geom.commandline,
776 self.abbreviate and "'" or "Go to mark:")
777 if key is None or len(key) != 1:
778 return
779 if 'a' <= key <= 'z' or key == '\'':
780 # local mark
781 self.indexedHypertext.goMark(key)
782 self.refreshMoved()
783 elif self.globalMarks.has_key(key):
784 # global marks
785 url, pos = self.globalMarks[key]
786 if url != self.indexedHypertext.url:
787 self.gotoNewUrl(url)
788 self.indexedHypertext.seekWord(pos)
789 self.refreshMoved()
791 def doKeyReload(self):
792 n = self.indexedHypertext.atWord
793 self.gotoUrl(self.indexedHypertext.url, True)
794 self.indexedHypertext.seekWord(n)
795 self.refreshMoved()
797 def doKeyCommand(self):
798 def handleErr(err):
799 self.displayCleanCentred(self.geom.commandline, err)
800 command = self.readLine(':', shortPrompt=':',
801 history=self.commandHistory)
802 if command:
803 self.settings.processCommand(command, handleErr)
804 self.refreshMoved()
806 def doKeySkim(self):
807 if not self.skim:
808 self.normalWpm = self.wpm
809 self.wpm = self.skimWpm
810 self.skim = True
811 else:
812 self.skimWpm = self.wpm
813 self.wpm = self.normalWpm
814 self.skim = False
815 self.delay = 60.0/self.wpm
816 self.refreshMoved()
818 def doKeyRefresh(self):
819 self.scr.redrawwin()
821 def doKeyQuit(self):
822 raise QuitException