3 # Released under the terms of the GPLv3
5 from __future__
import unicode_literals
10 from time
import sleep
, time
16 from curses
.wrapper
import wrapper
17 #from curses.textpad import Textbox
18 from .TextboxPad
import TextboxPad
19 import curses
.ascii
as ascii
21 from .IndexedHypertext
import IndexedHypertext
22 from .portability
import addstrwrapped
, _cmp
24 class QuitException(Exception):
28 def __init__(self
, scr
, config
):
29 self
.settings
= config
35 curses
.init_pair(i
, i
, curses
.COLOR_BLACK
)
36 curses
.init_pair(8, curses
.COLOR_BLACK
, curses
.COLOR_BLACK
)
45 self
.lastSearch
= None
49 self
.cachedDocuments
= []
51 # history *ends* with most recent url
52 # unHistory *ends* with *current* url
56 self
.tricky
= [""]*self
.trickyDisplayNum
62 self
.normalWpm
= self
.wpm
64 self
.interruptible
= False
67 self
.commandHistory
= []
68 self
.searchHistory
= []
73 self
.globalMarks
= self
.readMarksFile()
75 def readMarksFile(self
):
77 marksFile
= open(os
.getenv("HOME")+"/.flinksMarks", 'r')
82 m
= re
.match('(.)\s*:?\s*', marksFile
.readline())
85 url
= marksFile
.readline().strip()
86 try: point
= int(marksFile
.readline().strip())
88 marks
[mark
] = [url
, point
]
91 def writeMarksFile(self
, marks
):
92 marksFile
= open(os
.getenv("HOME")+"/.flinksMarks", 'w')
93 for (mark
,(url
,point
)) in marks
.items():
94 marksFile
.write('%s:\n\t%s\n\t%d\n' % (mark
, url
, point
))
96 # XXX: we use self.wpm as convenient shorthand for
97 # self.settings.options["wpm"], etc. The overriding below of __getattr__
98 # and __setattr__ just implement this.
99 def __getattr__(self
, name
):
100 if name
!= "settings" and name
in self
.settings
.optionNames
:
101 return self
.settings
.options
[name
]
104 def __setattr__(self
, name
, value
):
105 if name
!= "settings" and name
in self
.settings
.optionNames
:
106 self
.settings
.options
[name
] = value
108 self
.__dict
__[name
] = value
111 def __init__(self
, scr
, desiredLinkDisplayNum
, desiredTrickyDisplayNum
):
112 (maxy
, maxx
) = scr
.getmaxyx()
114 # effective maximums, i.e. with anything bigger scr.move(y,x)
116 self
.maxy
= max(0, maxy
-2)
117 self
.maxx
= max(0, maxx
-1)
119 # we aim for following layout, and squash as necessary
120 # desiredLinkDisplayNum lines of links
124 # desiredTrickyDisplayNum lines of trickies
128 squashed
= self
.maxy
< (desiredLinkDisplayNum
+
129 desiredTrickyDisplayNum
+ 6)
131 self
.wordline
= self
.maxy
//2
133 self
.linkDisplayNum
= min(desiredLinkDisplayNum
,
135 self
.trickyDisplayNum
= min(desiredTrickyDisplayNum
,
136 (self
.maxy
- self
.wordline
) - 2)
138 self
.linkline
= self
.wordline
- self
.linkDisplayNum
- (not squashed
)
140 self
.trickyline
= self
.wordline
+ (self
.maxy
> self
.wordline
) + (not squashed
)
141 self
.statusline
= self
.trickyline
+ self
.trickyDisplayNum
142 self
.infoline
= self
.statusline
+ (self
.maxy
> self
.statusline
)
143 self
.commandline
= self
.infoline
+ (self
.maxy
> self
.infoline
)
146 self
.geom
= self
.Geom(self
.scr
, self
.linkDisplayNum
,
147 self
.trickyDisplayNum
)
149 def blockGetKey(self
, promptLine
=None, prompt
=None):
150 if promptLine
and prompt
:
151 self
.displayCleanCentred(promptLine
, prompt
)
153 key
= self
.scr
.getkey()
155 if promptLine
and prompt
:
156 self
.clearLine(promptLine
)
157 if key
== '\x1b' or key
== '\x07':
161 def displayCentred(self
, line
, str, attr
=0):
162 addstrwrapped(self
.scr
, line
,
163 max(0,(self
.geom
.maxx
-len(str))//2),
164 str[:self
.geom
.maxx
],
166 def clearLine(self
, line
):
167 self
.scr
.move(line
,0)
169 def displayCleanCentred(self
, line
, str, attr
=0):
171 self
.displayCentred(line
, str, attr
)
173 def displayStatus(self
):
174 if self
.geom
.statusline
== self
.geom
.wordline
: return
175 def getStatusLine(abbreviated
=False):
177 text
= 'WPM: %d' % self
.wpm
179 text
+= '; word: %d' % (self
.itext
.atWord
+1)
180 text
+= '; sentence: %d' % (self
.itext
.sentenceIndex
.at
+1)
181 text
+= '; paragraph: %d' % (self
.itext
.paraIndex
.at
+1)
182 text
+= '; link: %d' % (self
.itext
.linkIndex
.at
+1)
183 if self
.paused
: text
+= '; PAUSED'
185 text
= '%d ' % self
.wpm
187 text
+= '%d/' % (self
.itext
.atWord
+1)
188 text
+= '%d/' % (self
.itext
.sentenceIndex
.at
+1)
189 text
+= '%d/' % (self
.itext
.paraIndex
.at
+1)
190 text
+= '%d ' % (self
.itext
.linkIndex
.at
+1)
191 if self
.paused
: text
+= 'P'
194 self
.clearLine(self
.geom
.statusline
)
196 line
= getStatusLine(self
.abbreviate
)
197 if len(line
) >= self
.geom
.maxx
and not self
.abbreviate
:
198 line
= getStatusLine(True)
199 if len(line
) >= self
.geom
.maxx
:
202 self
.displayCentred(self
.geom
.statusline
, line
)
204 def displayLinkListEntry(self
, linknum
, emph
=False):
205 if self
.geom
.linkDisplayNum
<= 0: return
206 line
= self
.geom
.linkline
+ linknum
%self
.geom
.linkDisplayNum
210 attr
= self
.attrOfLinknum(linknum
)
212 attr |
= curses
.A_BOLD
216 link
= self
.itext
.links
[linknum
]
217 str = '%s%s "%s": %s' % (
218 emphstr
, self
.strOfLinknum(linknum
),
219 self
.abbreviateStr(self
.cleanWord(link
["word"]), 10),
220 self
.abbreviateStr(link
["url"])
223 self
.displayCentred(line
, str, attr
)
224 def displayLinkList(self
):
225 for linknum
in range(
226 self
.itext
.linkIndex
.at
- self
.geom
.linkDisplayNum
,
227 self
.itext
.linkIndex
.at
+1):
228 emphasize
= (self
.itext
.linkIndex
.at
- linknum
<
229 self
.linkDisplayEmphNum
)
230 self
.displayLinkListEntry(linknum
, emphasize
)
232 def attrOfLinknum(self
, linknum
):
233 if linknum
is None or self
.geom
.linkDisplayNum
<= 0: return 1
234 c
= (linknum
%self
.geom
.linkDisplayNum
)%5+1
235 if c
>= 4: c
+=1 # don't use blue (hard to see against black)
236 return curses
.color_pair(c
)
238 def strOfLinknum(self
, linknum
):
239 if self
.geom
.linkDisplayNum
<= 0: return
240 return '[%d]' % ((linknum
%self
.geom
.linkDisplayNum
)+1)
242 def displayTrickyList(self
):
243 for i
in range(self
.geom
.trickyDisplayNum
):
244 self
.clearLine(self
.geom
.trickyline
+ i
)
246 self
.geom
.trickyline
+ i
,
250 def addTricky(self
, word
):
251 if self
.geom
.trickyDisplayNum
<= 0:
253 for t
in self
.tricky
:
255 # already in the list; don't repeat
257 for i
in range(self
.geom
.trickyDisplayNum
-1, 0, -1):
258 self
.tricky
[i
] = self
.tricky
[i
-1]
259 self
.tricky
[0] = word
260 self
.displayTrickyList()
262 def isTricky(self
, word
):
264 bool(re
.search('\d', word
)) or bool(re
.search('\w-+\w', word
))
268 def cleanWord(self
, word
):
269 for (start
, end
) in (('(',')'),('[',']'),('{','}')):
270 # remove non-matching brackets from top and tail:
274 elif word
[-1] == end
and start
not in word
:
276 return word
.strip(' ,.;:?!')
278 def abbreviateStr(self
, str, maxLen
= None):
279 if maxLen
is None: maxLen
= 2*self
.geom
.maxx
//3
280 if len(str) <= maxLen
: return str
281 return '%s...%s' % (str[:(maxLen
//2)-3], str[-((maxLen
//2)-3):])
283 def displayAll(self
):
284 self
.showCurrentWord()
286 self
.displayLinkList()
287 self
.displayTrickyList()
289 def applyUrlShortcuts(self
, command
, depth
=0):
290 words
= command
.split()
292 numArgs
, shortcut
= self
.settings
.urlShortcuts
[words
[0]]
293 except (IndexError, KeyError):
296 for arg
in range(numArgs
):
300 # last argument gets all remaining words
301 repl
= ' '.join(words
[i
:])
307 shortcut
= re
.sub('%%%d' % i
, repl
, shortcut
)
309 shortcut
= re
.sub('%c', self
.itext
.url
, shortcut
)
310 shortcut
= re
.sub('\\$HOME', os
.getenv("HOME"), shortcut
)
313 return self
.applyUrlShortcuts(shortcut
, depth
+1)
317 def go(self
, command
):
318 url
= self
.applyUrlShortcuts(command
)
321 def gotoNewUrl(self
, url
):
323 self
.history
.append(self
.itext
.url
)
325 # Turns out to be neater to have the current document in unHistory:
326 self
.unHistory
= [url
]
327 def goHistory(self
, num
=1):
329 self
.gotoUrl(self
.history
[-num
])
331 self
.unHistory
.append(self
.history
.pop())
334 def goUnHistory(self
, num
=1):
336 self
.gotoUrl(self
.unHistory
[-num
-1])
338 self
.history
.append(self
.unHistory
.pop())
342 def gotoUrl(self
, url
, noCache
=False):
345 m
= re
.match('(.*)#(.*)$', url
)
347 baseUrl
, anchor
= m
.groups()
349 baseUrl
, anchor
= url
, None
350 cached
= self
.findCachedDocument(baseUrl
)
351 if cached
and not noCache
:
354 self
.displayCleanCentred(self
.geom
.commandline
, self
.abbreviateStr("Loading %s" % url
))
357 self
.interruptible
= True
358 self
.itext
= IndexedHypertext(baseUrl
, notSentenceEnder
=self
.notSentenceEnder
)
359 self
.interruptible
= False
361 if self
.itext
.lynxErr
:
362 err
= self
.itext
.lynxErr
.strip()
363 line
= ';'.join(err
.splitlines()[0:2])
364 self
.displayCleanCentred(self
.geom
.commandline
,
365 self
.abbreviateStr("Failed: %s" % line
))
370 self
.interruptible
= True
371 n
= self
.itext
.findAnchor(anchor
)
372 self
.interruptible
= False
375 self
.itext
.seekWord(n
, setMark
=True)
377 self
.displayCleanCentred(self
.geom
.commandline
,
378 self
.abbreviateStr("No such anchor: %s" % anchor
))
381 self
.displayCleanCentred(self
.geom
.infoline
, self
.abbreviateStr("%s" % baseUrl
))
384 self
.cacheDocument(self
.itext
)
387 self
.interruptible
= False
389 self
.clearLine(self
.geom
.commandline
)
391 def cacheDocument(self
, doc
):
392 # remove any old cachings of this url:
393 for cached
in self
.cachedDocuments
:
394 if cached
[0] == doc
.url
:
395 self
.cachedDocuments
.remove(cached
)
396 self
.cachedDocuments
.append((doc
.url
, doc
))
397 if len(self
.cachedDocuments
) > self
.maxCached
:
398 self
.cachedDocuments
.pop(0)
399 def findCachedDocument(self
, url
):
400 for (u
, doc
) in self
.cachedDocuments
:
401 if u
== url
: return doc
404 def interruptSpeech(self
):
406 self
.speechsub
.kill()
407 self
.speechsub
= None
409 def saySentence(self
):
411 self
.speechsub
.wait()
412 self
.speechsub
= None
413 i
= self
.itext
.atWord
414 quote
= self
.itext
.inQuote()
416 s
= self
.itext
.sentenceIndex
.nextPos()
417 sq
= self
.itext
.startQuoteIndex
.nextPos()
418 eq
= self
.itext
.endQuoteIndex
.nextPos()
419 if eq
is not None: eq
+= 1
420 stops
= filter(lambda x
: x
is not None, (s
,sq
,eq
))
421 if stops
== (): end
=None
422 else: end
= min(stops
)
426 w
= self
.itext
.getWord(i
)
431 self
.speechsub
= self
.sayText(text
, quote
=quote
)
433 def sayText(self
, text
, wait
=False, quote
=False):
434 from subprocess
import Popen
, PIPE
436 (quote
and self
.sayGenQuotedSpeech
or self
.sayGenSpeech
) %
437 (self
.wpm
), shell
=True, stdin
=PIPE
, stdout
=PIPE
, stderr
=None)
438 speechps
= Popen(self
.sayPlaySpeech
.split(),
439 stdin
=espeakps
.stdout
, stdout
=None, stderr
=None,
440 shell
=False, close_fds
=True) # these two are important!
441 espeakps
.stdin
.write(text
.encode("utf-8"))
442 espeakps
.stdin
.close()
443 espeakps
.stdout
.close()
446 def showCurrentWord(self
, word
=None):
448 word
= self
.itext
.currentWord()
450 tricky
= self
.isTricky(word
)
451 linknum
= self
.itext
.currentLink()
454 if tricky
: attr
= curses
.A_BOLD
456 self
.displayCleanCentred(self
.geom
.wordline
, word
, attr
)
458 self
.displayCleanCentred(self
.geom
.wordline
, word
,
459 self
.attrOfLinknum(linknum
) | curses
.A_BOLD
)
461 self
.displayLinkListEntry(linknum
, True)
463 if linknum
>= self
.linkDisplayEmphNum
:
464 self
.displayLinkListEntry(linknum
-self
.linkDisplayEmphNum
,
467 if self
.speech
and self
.paused
:
468 self
.sayText(word
, quote
=self
.itext
.inQuote())
471 self
.addTricky(self
.cleanWord(word
))
473 if self
.showContext
and self
.paused
:
475 contextFrom
= self
.itext
.atWord
477 w
= self
.itext
.getWord(contextFrom
-1)
478 if self
.itext
.atSentenceEnd(contextFrom
-1):
480 if self
.itext
.atParaEnd(contextFrom
-1):
482 if w
and (len(w
) + len(contextBefore
)
483 + len(word
)//2 + 1) < self
.geom
.maxx
//3:
484 contextBefore
= w
+ ' ' + contextBefore
490 contextTo
= self
.itext
.atWord
493 if self
.itext
.atSentenceEnd(contextTo
):
495 if self
.itext
.atParaEnd(contextTo
):
497 w
+= self
.itext
.getWord(contextTo
+1)
498 if w
and (len(w
) + len(contextAfter
)
499 + len(word
)//2 + 1) < self
.geom
.maxx
//3:
500 contextAfter
+= ' ' + w
505 point
= (self
.geom
.maxx
- len(word
))//2 - len(contextBefore
)
506 attr
= curses
.color_pair(8) | curses
.A_BOLD
508 addstrwrapped(self
.scr
, self
.geom
.wordline
, point
, contextBefore
, attr
)
509 point
= (self
.geom
.maxx
+ len(word
))//2
510 addstrwrapped(self
.scr
, self
.geom
.wordline
, point
, contextAfter
, attr
)
512 if self
.showPoint
: self
.displayStatus()
515 def sigHandlerQuit(sig
, frame
):
517 signal
.signal(15, sigHandlerQuit
)
519 def sigHandlerInt(sig
, frame
):
520 # Occasionally, we can safely allow ^C to interrupt a section of
522 if self
.interruptible
:
523 raise KeyboardInterrupt
524 signal
.signal(2, sigHandlerInt
)
526 def applyCount(method
, args
):
527 # update args, replacing/appending any count argument
529 countarg
= self
.bindableMethods
[method
][2]
531 if len(args
) >= countarg
:
533 currentArg
= args
[countarg
-1]
534 if (currentArg
.__class
__ == int and
536 self
.count
= -self
.count
538 args
[countarg
-1] = self
.count
540 elif len(args
)+1 == countarg
:
546 self
.counting
= False
547 self
.countWin
.erase()
548 self
.countWin
.refresh()
557 self
.go(self
.initialUrl
)
564 if self
.blankSpace
> 0:
566 self
.displayCleanCentred(self
.geom
.wordline
, "")
569 word
= self
.itext
.readWord()
572 if self
.itext
.atParaEnd():
573 self
.blankSpace
= self
.paraBlankSpace
574 elif self
.itext
.atSentenceEnd():
575 self
.blankSpace
= self
.sentenceBlankSpace
579 if word
[-1] in "'\"":
587 self
.blankSpace
= self
.pauseBlankSpace
589 self
.showCurrentWord(word
)
591 if self
.speech
and not self
.paused
and (
592 self
.itext
.atWord
> self
.spokeTo
593 or not self
.speechsub
):
600 escBound
= 27 in self
.settings
.keyBindings
617 if c
== curses
.KEY_RESIZE
:
620 elif self
.counting
and ord('0') <= c
<= ord('9'):
621 self
.count
= 10*self
.count
+ c
- ord('0')
622 self
.countWin
.addch(chr(c
))
623 self
.countWin
.refresh()
626 binding
= self
.settings
.keyBindings
.get(c
)
627 if binding
is not None:
632 applyCount(method
, args
)
634 self
.interruptSpeech()
635 getattr(self
, 'doKey'+method
).__call
__(*args
)
636 elif self
.counting
and c
in [27,7]:
637 # esc or ^G: cancel count
644 def __init__(self
, type, time
):
645 if type not in ["word", "blank"]:
646 raise "BUG: unknown Event type"
649 nextEvent
= Event("word", time())
650 if self
.blink
: nextBlinkTime
= time() + self
.blinkDelay
652 # compensate for blanking at pauses and so on:
662 wait
= nextEvent
.time
- time()
664 sleep(min(wait
, maxStep
))
669 except KeyboardInterrupt:
672 inputTime
= time() - beforeInput
673 nextEvent
.time
+= inputTime
674 if self
.blink
: nextBlinkTime
+= inputTime
676 delay
= (60.0 * WPMFACTOR
) / self
.wpm
678 if self
.paused
or self
.itext
.atEnd():
679 nextEvent
= Event("word",
682 nextBlinkTime
= time() + self
.blinkDelay
685 if nextEvent
.type == "word":
687 if self
.blink
and nextBlinkTime
< time() and (
688 not self
.blinkWaitSentence
or
689 self
.itext
.atSentenceEnd()):
690 self
.displayCleanCentred(self
.geom
.wordline
,
692 nextBlinkTime
= time() + self
.blinkDelay
693 waitTime
= self
.blinkTime
697 if self
.blankBetween
:
698 nextEvent
= Event("blank",
699 t
+ waitTime
- delay
/3)
701 nextEvent
= Event("word",
703 elif nextEvent
.type == "blank":
704 self
.clearLine(self
.geom
.wordline
)
706 nextEvent
= Event("word",
711 # Curses threw an error, but that might just be because we
712 # need to resize and haven't yet processed KEY_RESIZE.
713 # So we resize now, and see if we get another curses error
714 # within the next couple of goes round the main loop.
718 raise "Curses returned ERR - terminal too short?"
719 except KeyboardInterrupt:
723 # emulating vim - global marks '0'-'9' give positions on last 10 exits
724 for i
in range(9,0,-1):
725 try: self
.globalMarks
[str(i
)] = self
.globalMarks
[str(i
-1)][:]
726 except KeyError: pass
727 if (self
.itext
.url
and self
.itext
.url
[0] != '!'):
728 self
.globalMarks
['0'] = \
729 [self
.itext
.url
, self
.itext
.atWord
]
731 def writeState(self
):
733 self
.writeMarksFile(self
.globalMarks
)
738 # <name> : [<min args>, <max args>, [<count arg>], [<string args>]]
739 'ChangeSpeed' : [1,1,1],
740 'SeekSentence' : [0,1,1],
741 'SeekParagraph' : [0,1,1],
742 'SeekWord' : [0,1,1],
743 'SeekLink' : [0,1,1],
747 'FollowLink' : [1,1,1],
751 'SearchAgain' : [0,1,1],
760 'Toggle' : [1,1,0,True],
765 def doKeyChangeSpeed(self
, amount
):
767 if self
.wpm
< 30: self
.wpm
= 30
770 def doKeySeekSentence(self
, n
=1):
771 self
.itext
.seekIndexRel(self
.itext
.sentenceIndex
, n
)
773 def doKeySeekParagraph(self
, n
=1):
774 self
.itext
.seekIndexRel(self
.itext
.paraIndex
, n
)
776 def doKeySeekWord(self
, n
=1):
777 self
.itext
.seekWordRel(n
)
779 def doKeySeekLink(self
, n
=1):
780 self
.itext
.seekIndexRel(self
.itext
.linkIndex
, n
)
783 def doKeySeekStart(self
):
784 self
.itext
.seekWord(0, setMark
=True)
786 def doKeySeekEnd(self
):
790 def doKeyFollowLink(self
, n
):
791 if not self
.itext
.links
: return
793 if self
.geom
.linkDisplayNum
> 0:
795 nl
= self
.itext
.linkIndex
.at
% self
.geom
.linkDisplayNum
797 nl
= self
.itext
.linkIndex
.at
800 linknum
= self
.itext
.linkIndex
.at
+ (n
-nl
)
802 linknum
= self
.itext
.linkIndex
.at
- self
.geom
.linkDisplayNum
+ (n
-nl
)
804 url
= self
.itext
.links
[linknum
]["url"]
809 def readLine(self
, prompt
, initial
='', shortPrompt
=None, history
=None,
810 getCompletions
=None):
811 if self
.abbreviate
or self
.geom
.maxx
<= 10:
812 if shortPrompt
is not None:
815 prompt
= prompt
[0]+":"
817 promptX
= max(1, self
.geom
.maxx
//6 - len(prompt
))
818 boxX
= promptX
+ len(prompt
)
819 boxLen
= min(self
.geom
.maxx
- boxX
- 1, 2*self
.geom
.maxx
//3)
821 addstrwrapped(self
.scr
, self
.geom
.commandline
, promptX
,
822 prompt
[:self
.geom
.maxx
-5])
827 pad
= curses
.newpad(1, 200)
829 box
= TextboxPad(pad
,
830 self
.geom
.commandline
, boxX
,
831 self
.geom
.commandline
, boxX
+ boxLen
- 1,
833 getCompletions
=getCompletions
)
838 class cancelReadline(Exception):
840 def cancelValidate(ch
):
841 if ch
== ascii
.BEL
or ch
== 27:
842 # ^G and escape cancel the readLine
847 read
= box
.edit(cancelValidate
)
848 if history
is not None:
849 history
.append(read
.strip())
851 except cancelReadline
:
855 self
.clearLine(self
.geom
.commandline
)
858 def filenameCompletion(partial
):
859 from glob
import glob
860 from os
.path
import isdir
862 completions
= glob(partial
+'*')
864 for i
in range(len(completions
)):
865 if isdir(completions
[i
]):
866 completions
[i
] += '/'
870 command
= self
.readLine("Go: ", history
=self
.urlHistory
,
871 getCompletions
=filenameCompletion
)
873 self
.go(command
.strip())
875 def doKeyGoCurrent(self
):
876 command
= self
.readLine("Go: ", self
.itext
.url
, history
=self
.urlHistory
)
878 self
.go(command
.strip())
880 def charOfSearchDir(self
, dir=1):
881 if dir == 1: return '/'
883 def search(self
, pattern
, n
=1):
885 self
.displayCleanCentred(self
.geom
.commandline
,
886 self
.charOfSearchDir(dir)+pattern
)
888 self
.interruptible
= True
889 for i
in range(abs(n
)):
890 ret
= self
.itext
.search(pattern
.strip(), dir)
894 self
.interruptible
= False
895 self
.clearLine(self
.geom
.commandline
)
896 def doKeySearch(self
, n
=1):
898 pattern
= self
.readLine("Search: ",
899 shortPrompt
= self
.charOfSearchDir(dir),
900 history
=self
.searchHistory
)
902 self
.search(pattern
, n
)
903 self
.lastSearch
= {"pattern": pattern
, "dir": dir}
904 def doKeySearchAgain(self
, n
=1):
906 self
.search(self
.lastSearch
["pattern"],
907 self
.lastSearch
["dir"]*n
)
909 def doKeyPause(self
):
910 self
.paused
= not self
.paused
913 self
.showCurrentWord()
915 def doKeyHistory(self
, n
=-1):
921 def doKeyCount(self
):
925 boxX
= self
.geom
.maxx
//6
926 boxLen
= min(self
.geom
.maxx
- boxX
- 1, 2*self
.geom
.maxx
//3)
927 self
.countWin
= curses
.newwin(1, boxLen
, self
.geom
.commandline
, boxX
)
928 if not self
.abbreviate
:
932 addstrwrapped(self
.countWin
, None, None, prompt
[:self
.geom
.maxx
])
933 self
.countWin
.refresh()
936 key
= self
.blockGetKey(self
.geom
.commandline
,
937 self
.abbreviate
and "m" or "Set mark:")
938 if key
is None or len(key
) != 1:
940 if 'a' <= key
<= 'z':
943 elif 'A' <= key
<= 'Z':
945 self
.globalMarks
[key
] =\
946 [self
.itext
.url
, self
.itext
.atWord
]
947 def doKeyGoMark(self
):
948 key
= self
.blockGetKey(self
.geom
.commandline
,
949 self
.abbreviate
and "'" or "Go to mark:")
950 if key
is None or len(key
) != 1:
952 if 'a' <= key
<= 'z' or key
== '\'':
954 self
.itext
.goMark(key
)
956 elif key
in self
.globalMarks
:
958 url
, pos
= self
.globalMarks
[key
]
959 if url
!= self
.itext
.url
:
961 self
.itext
.seekWord(pos
, setMark
=True)
964 def doKeyReload(self
):
965 n
= self
.itext
.atWord
966 self
.gotoUrl(self
.itext
.url
, True)
967 self
.itext
.seekWord(n
)
970 def doKeyCommand(self
):
972 self
.displayCleanCentred(self
.geom
.commandline
, err
)
973 command
= self
.readLine(':', shortPrompt
=':',
974 history
=self
.commandHistory
)
976 self
.settings
.processCommand(command
, handleErr
)
981 self
.normalWpm
= self
.wpm
982 self
.wpm
= self
.skimWpm
985 self
.skimWpm
= self
.wpm
986 self
.wpm
= self
.normalWpm
990 def doKeyRefresh(self
):
993 def doKeyToggle(self
, var
):
994 if hasattr(self
, var
) and type(getattr(self
,var
)) == bool:
995 setattr(self
, var
, not getattr(self
,var
))
998 self
.go("special:README")
1000 def doKeyQuit(self
):