Ejemplo n.º 1
0
    def selectIndentationBlock(self, selectMultiple=False, successMessage=""):
        count = scriptHandler.getLastScriptRepeatCount()
        if selectMultiple and count >= 1:
            # Just copy selection to the clipboard
            focus = api.getFocusObject()
            textInfo = focus.makeTextInfo(textInfos.POSITION_SELECTION)
            api.copyToClip(textInfo.text)
            ui.message(successMessage)
        with self.getLineManager() as lm:
            if count >= 1:
                textInfo = lm.getTextInfo()
                textInfo.collapse()
                textInfo.updateCaret()
            # Get the current indentation level
            text = lm.getText()
            originalTextInfo = lm.getTextInfo()
            indentationLevel = self.getIndentLevel(text)
            onEmptyLine = speech.isBlank(text)
            if onEmptyLine:
                return self.endOfDocument(_("Nothing to select"))
            # Scan each line forward as long as indentation level is greater than current
            line = lm.getLine()
            indentLevels = []
            while True:
                result = lm.move(1)
                if result == 0:
                    if not selectMultiple and count >= 1:
                        core.callLater(100, self.endOfDocument,
                                       _("No more indentation blocks!"))
                    break
                text = lm.getText()
                newIndentation = self.getIndentLevel(text)

                if speech.isBlank(text):
                    continue

                if newIndentation < indentationLevel:
                    if not selectMultiple and count >= 1:
                        core.callLater(100, self.endOfDocument,
                                       _("No more indentation blocks!"))
                        #self.endOfDocument(_("No more indentation blocks!"))
                    break
                elif newIndentation == indentationLevel:
                    if selectMultiple:
                        pass
                    elif count > 0:
                        count -= 1
                    else:
                        break
                else:  # newIndentation > indentationLevel
                    pass
                line = lm.getLine()
                indentLevels.append(newIndentation)
            selection = originalTextInfo.copy()
            if line is not None:
                textInfo = lm.getTextInfo(line)
                selection.setEndPoint(textInfo, "endToEnd")
            selection.updateSelection()
            self.crackle(indentLevels)
            speech.speakTextInfo(textInfo, unit=textInfos.UNIT_LINE)
Ejemplo n.º 2
0
    def moveInEditable(self,
                       increment,
                       errorMessage,
                       unbounded=False,
                       op=operator.eq,
                       speakOnly=False,
                       moveCount=1):
        with self.getLineManager() as lm:
            # Get the current indentation level
            text = lm.getText()
            indentationLevel = self.getIndentLevel(text)
            onEmptyLine = speech.isBlank(text)

            # Scan each line until we hit the end of the indentation block, the end of the edit area, or find a line with the same indentation level
            found = False
            indentLevels = []
            while True:
                result = lm.move(increment)
                if result == 0:
                    break
                text = lm.getText()
                newIndentation = self.getIndentLevel(text)

                # Skip over empty lines if we didn't start on one.
                if not onEmptyLine and speech.isBlank(text):
                    continue

                if op(newIndentation, indentationLevel):
                    # Found it
                    found = True
                    indentationLevel = newIndentation
                    resultLine = lm.getLine()
                    resultText = lm.getText()
                    moveCount -= 1
                    if moveCount == 0:
                        break
                elif newIndentation < indentationLevel:
                    # Not found in this indentation block
                    if not unbounded:
                        break
                indentLevels.append(newIndentation)

            if found:
                textInfo = None
                if not speakOnly:
                    textInfo = lm.updateCaret(resultLine)
                self.crackle(indentLevels)
                if textInfo is not None:
                    speech.speakTextInfo(textInfo, unit=textInfos.UNIT_LINE)
                else:
                    speech.speakText(resultText)
            else:
                self.endOfDocument(errorMessage)
Ejemplo n.º 3
0
 def processStringInternal(self, s, symbolLevel, language):
     index = 0
     for match in self.regexp.finditer(s):
         if speech.isBlank(speech.processText(language,match.group(0), symbolLevel)):
             # Current punctuation level indicates that punctuation mark matched will not be pronounced, therefore skipping it.
             continue
         yield s[index:match.start(0)]
         yield self.speechCommand
         index = match.end(0)
     yield s[index:]
Ejemplo n.º 4
0
 def moveInEditable(self, increment, errorMessage, unbounded=False, op=operator.eq): 
     focus = api.getFocusObject()
     # Get the current indentation level 
     textInfo = focus.makeTextInfo(textInfos.POSITION_CARET)
     textInfo.expand(textInfos.UNIT_LINE)
     indentationLevel = self.getIndentLevel(textInfo.text)
     onEmptyLine = speech.isBlank(textInfo.text)
     
     # Scan each line until we hit the end of the indentation block, the end of the edit area, or find a line with the same indentation level
     found = False
     indentLevels = []
     while True:
         result = textInfo.move(textInfos.UNIT_LINE, increment) 
         if result == 0:
             break
         textInfo.expand(textInfos.UNIT_LINE)
         newIndentation = self.getIndentLevel(textInfo.text)
         
         # Skip over empty lines if we didn't start on one.
         if not onEmptyLine and speech.isBlank(textInfo.text):
             continue
         
         if op(newIndentation, indentationLevel):
             # Found it
             found = True
             textInfo.updateCaret()
             self.crackle(indentLevels)
             speech.speakTextInfo(textInfo, unit=textInfos.UNIT_LINE)
             return
         elif newIndentation < indentationLevel:
             # Not found in this indentation block
             if not unbounded:
                 break
         indentLevels.append(newIndentation )
     
     # If we didn't find it, tell the user
     if not found:
         ui.message(errorMessage)
def postProcessSynchronousCommands(speechSequence, symbolLevel):
    language=speech.getCurrentLanguage()
    speechSequence = [element for element in speechSequence
        if not isinstance(element, str)
        or not speech.isBlank(speech.processText(language,element,symbolLevel))
    ]

    newSequence = []
    for (isSynchronous, values) in itertools.groupby(speechSequence, key=lambda x: isinstance(x, PpSynchronousCommand)):
        if isSynchronous:
            chain = PpChainCommand(list(values))
            duration = chain.getDuration()
            newSequence.append(chain)
            newSequence.append(speech.commands.BreakCommand(duration))
        else:
            newSequence.extend(values)
    newSequence = eloquenceFix(newSequence, language, symbolLevel)
    return newSequence
def eloquenceFix(speechSequence, language, symbolLevel):
    """
    With some versions of eloquence driver, when the entire utterance has been replaced with audio icons, and therefore there is nothing else to speak,
    the driver for some reason issues the callback command after the break command, not before.
    To work around this, we detect this case and remove break command completely.
    """
    nonEmpty = [element for element in speechSequence
        if  isinstance(element, str)
        and not speech.isBlank(speech.processText(language,element,symbolLevel))
    ]
    if len(nonEmpty) > 0:
        return speechSequence
    indicesToRemove = []
    for i in range(1, len(speechSequence)):
        if  (
            isinstance(speechSequence[i], speech.commands.BreakCommand)
            and isinstance(speechSequence[i-1], PpChainCommand)
        ):
            indicesToRemove.append(i)
    return [speechSequence[i] for i in range(len(speechSequence)) if i not in indicesToRemove]
Ejemplo n.º 7
0
    def script_indentPaste(self, gesture):
        clipboardBackup = api.getClipData()
        try:
            focus = api.getFocusObject()
            selection = focus.makeTextInfo(textInfos.POSITION_SELECTION)
            if len(selection.text) != 0:
                ui.message(_("Some text selected! Cannot indent-paste."))
                return
            line = focus.makeTextInfo(textInfos.POSITION_CARET)
            line.collapse()
            line.expand(textInfos.UNIT_LINE)
            # Make sure line doesn't include newline characters
            while len(line.text) > 0 and line.text[-1] in "\r\n":
                line.move(textInfos.UNIT_CHARACTER, -1, "end")
            lineLevel = self.getIndentLevel(line.text + "a")
            #ui.message(f"Level {level}")
            text = clipboardBackup
            textLevel = min([
                self.getIndentLevel(s) for s in text.splitlines()
                if not speech.isBlank(s)
            ])
            useTabs = '\t' in text or '\t' in line.text
            delta = lineLevel - textLevel
            text = text.replace("\t", " " * 4)
            if delta > 0:
                text = "\n".join([" " * delta + s for s in text.splitlines()])
            elif delta < 0:
                text = "\n".join(
                    [s[min(-delta, len(s)):] for s in text.splitlines()])
            if useTabs:
                text = text.replace(" " * 4, "\t")

            api.copyToClip(text)
            line.updateSelection()
            time.sleep(0.1)
            keyboardHandler.KeyboardInputGesture.fromName("Control+v").send()
        finally:
            core.callLater(100, api.copyToClip, clipboardBackup)
            core.callLater(100, ui.message, _("Pasted"))
Ejemplo n.º 8
0
 def moveInBrowser(self, increment, errorMessage, op):
     focus = api.getFocusObject()
     focus = focus.treeInterceptor 
     textInfo = focus.makeTextInfo(textInfos.POSITION_CARET)
     textInfo.expand(textInfos.UNIT_PARAGRAPH)
     origLocation= textInfo.NVDAObjectAtStart.location
     distance = 0
     while True:
         result =textInfo.move(textInfos.UNIT_PARAGRAPH, increment)
         if result == 0:
             ui.message(errorMessage)
             return
         textInfo.expand(textInfos.UNIT_PARAGRAPH)
         text = textInfo.text
         if speech.isBlank(text):
             continue
         location = textInfo.NVDAObjectAtStart.location
         if op(location[0], origLocation[0]):
             textInfo.collapse(False)
             textInfo.updateCaret()
             self.simpleCrackle(distance)
             ui.message(text)
             return
         distance += 1
    def test(self, s, symbolLevel):
        wav = "H:\\drp\\work\\emacspeak\\sounds\\classic\\alarm.wav"
        wavLength = self.getWavLengthMillis(wav)
        language = speech.getCurrentLanguage()
        tone = 500

        while "!" in s:
            index = s.index("!")
            prefix = s[:index]
            prefix = prefix.lstrip()
            pPrefix = speech.processText(language, prefix, symbolLevel)
            if speech.isBlank(pPrefix):
                pass
            else:
                yield prefix
            #yield speech.commands.WaveFileCommand(wav)
            #yield speech.commands.BeepCommand(tone, 100)
            #yield PpBeepCommand(tone, 100)
            yield PpWaveFileCommand(wav)
            tone += 50
            #yield speech.commands.BreakCommand(100)
            s = s[index + 1:]
        if len(s) > 0:
            yield s
Ejemplo n.º 10
0
 def getIndentLevel(self, s):
     if speech.isBlank(s):
         return 0
     indent = speech.splitTextIndentation(s)[0]
     return len(indent.replace("\t", " " * 4))
Ejemplo n.º 11
0
def elten_command(ac):
	global eltenmod
	global eltenbraille
	global eltenbrailletext
	global eltenindex
	global eltenindexid
	global eltenqueue
	try:
		if(('ac' in ac)==False): return {}
		if(ac['ac']=="speak"):
			eltenindex=None
			eltenindexid=None
			text=""
			if('text' in ac): text=ac['text']
			if(speech.isBlank(text)==False): queueHandler.queueFunction(queueHandler.eventQueue,speech.speakText,text)
		if(ac['ac']=="speakindexed"):
			eltenindex=None
			eltenindexid=None
			texts=[]
			indexes=[]
			indid=None
			if('texts' in ac): texts=ac['texts']
			if('indexes' in ac): indexes=ac['indexes']
			if('indid' in ac): indid=ac['indid']
			v=[]
			for i in range(0, len(texts)):
				if is_python_3_or_above:
					if(speech.isBlank(texts[i])): continue
					if(i<len(indexes)): v.append(EltenIndexCallback(indexes[i], indid))
					if(i<len(indexes) and i>0 and texts[i-1]!="" and texts[i-1][-1]=="\n"): v.append(speech.EndUtteranceCommand())
				else:
					eltenindexid=indid
					if(i<len(indexes)): v.append(speech.IndexCommand(indexes[i]))
				v.append(texts[i])
			speech.cancelSpeech()
			speech.speak(v)
		if(ac['ac']=='stop'):
			speech.cancelSpeech()
			eltenindex=None
			eltenindexid=None
		if(ac['ac']=='sleepmode'):
			st=eltenmod.sleepMode
			if('st' in ac): st=ac['st']
			eltenmod.sleepMode=st
			return {'st': st}
		if(ac['ac']=='init'):
			pid=0
			if('pid' in ac): pid=ac['pid']
			eltenmod = appModuleHandler.getAppModuleFromProcessID(pid)
			def script_eltengesture(self, gesture):
				global eltenqueue
				eltenqueue['gestures']+=gesture.identifiers
			eltenmod.__class__.script_eltengesture = types.MethodType(script_eltengesture, eltenmod.__class__)
			eltenmod.bindGesture('kb(laptop):NVDA+A', 'eltengesture')
			eltenmod.bindGesture('kb(laptop):NVDA+L', 'eltengesture')
			eltenmod.bindGesture('kb(desktop):NVDA+downArrow', 'eltengesture')
			eltenmod.bindGesture('kb(desktop):NVDA+upArrow', 'eltengesture')
		if(ac['ac']=='braille'):
			text=""
			if('text' in ac): text=ac['text']
			if('type' in ac and 'index' in ac):
				if ac['type']==-1: text=eltenbrailletext[:ac['index']]+eltenbrailletext[ac['index']+1:]
				elif ac['type']==1: text=eltenbrailletext[:ac['index']]+text+eltenbrailletext[ac['index']:]
			eltenbrailletext=text+" "
			regions=[]
			for phrase in re.split("([.,:/\n?!])", eltenbrailletext):
				if phrase=="": continue
				if len(regions)>10000: continue;
				region=braille.TextRegion(phrase)
				if hasattr(region, 'parseUndefinedChars'): region.parseUndefinedChars=False
				region.update()
				regions.append(region)
			eltenbraille.regions=regions
			if('pos' in ac): 
				poses = eltenbraille.rawToBraillePos
				if(ac['pos']<len(text) and ac['pos']<len(poses)):
					reg, pos = eltenbraille.bufferPosToRegionPos(poses[ac['pos']])
					eltenbraille.scrollTo(reg, pos)
			if('cursor' in ac and ac['cursor'] is not None):
				poses = eltenbraille.rawToBraillePos
				reg, pos = eltenbraille.bufferPosToRegionPos(poses[ac['cursor']])
				reg.cursorPos=reg.brailleToRawPos[pos]
				reg.update()
			eltenbraille.update()
			braille.handler.update()
		if(ac['ac']=='braillepos' and 'pos' in ac and len(eltenbraille.regions)>0):
			poses = eltenbraille.rawToBraillePos
			if(ac['pos']<len(poses)):
				reg, pos = eltenbraille.bufferPosToRegionPos(poses[ac['pos']])
				eltenbraille.scrollTo(reg, pos)
				reg.update()
			if('cursor' in ac and ac['cursor'] is not None):
				reg, pos = eltenbraille.bufferPosToRegionPos(poses[ac['cursor']])
				reg.cursorPos=reg.brailleToRawPos[pos]
				reg.update()
			eltenbraille.update()
			braille.handler.update()
		if(ac['ac']=='getversion'):
			return {'version': 33}
		if(ac['ac']=='getnvdaversion'):
			return {'version': buildVersion.version}
		if(ac['ac']=='getindex'):
			if(is_python_3_or_above):
				return {'index': eltenindex, 'indid': eltenindexid}
			else:
				return {'index': speech.getLastSpeechIndex(), 'indid': eltenindexid}
	except Exception as Argument:
		log.exception("Elten: command thread")
		return {}
	return {}
Ejemplo n.º 12
0
 def moveExtended(self,
                  paragraph,
                  caretIndex,
                  direction,
                  regex,
                  errorMsg="Error",
                  reconstructMode="sameIndent"):
     chimeIfAcrossParagraphs = False
     if reconstructMode == "always":
         compatibilityFunc = lambda x, y: True
     elif reconstructMode == "sameIndent":
         compatibilityFunc = lambda ti1, ti2: (
             ti1.NVDAObjectAtStart.location[0] == ti2.NVDAObjectAtStart.
             location[0]) and (self.getParagraphStyle(ti1) == self.
                               getParagraphStyle(ti2))
     elif reconstructMode == "never":
         compatibilityFunc = lambda x, y: False
     else:
         raise ValueError()
     context = Context(paragraph, caretIndex)
     sentenceStr, ti = self.expandSentence(
         context, regex, direction, compatibilityFunc=compatibilityFunc)
     if direction == 0:
         return sentenceStr, ti
     elif direction > 0:
         cindex = -1
         method = "endToEnd"
     else:
         cindex = 0
         method = "startToStart"
     if ti.compareEndPoints(context.textInfos[cindex], method) == 0:
         # We need to look for the next sentence in the next paragraph.
         mylog("Looking in the next paragraph.")
         paragraph = context.textInfos[cindex]
         counter = 0
         while True:
             counter += 1
             if counter > 1000:
                 raise RuntimeError("Infinite loop detected.")
             paragraph = self.nextParagraph(paragraph, direction)
             if paragraph is None:
                 self.chimeNoNextSentence(errorMsg)
                 return (None, None)
             if not speech.isBlank(paragraph.text):
                 break
         self.chimeCrossParagraphBorder()
         context = Context(paragraph, 0)
         if direction < 0:
             lastPosition = paragraph.copy()
             lastPosition.collapse(True)  # collapse to the end
             result = lastPosition.move(textInfos.UNIT_CHARACTER, -1)
             myAssert(result != 0)
             context.find(lastPosition)
     else:
         # Next sentence can be found in the same context
         # At least its beginning or ending - that sentence will be expanded.
         mylog("Looking in the same paragraph.")
         if direction > 0:
             ti2 = ti.copy()
             ti2.collapse(True)  # Collapse to the end
             context.find(ti2)
         else:
             ti2 = ti.copy()
             ti2.collapse(False)  # to the beginning
             result = ti2.move(textInfos.UNIT_CHARACTER, -1)
             myAssert(result != 0)
             context.find(ti2)
         chimeIfAcrossParagraphs = True
     resultSentenceStr, resultTi = self.expandSentence(
         context, regex, direction, compatibilityFunc=compatibilityFunc)
     if chimeIfAcrossParagraphs:
         if ti.compareEndPoints(resultTi, "startToStart") > 0:
             trailing = ti
         else:
             trailing = resultTi
         for paragraph in context.textInfos:
             if paragraph.compareEndPoints(trailing, "startToStart") == 0:
                 self.chimeCrossParagraphBorder()
                 break
     return resultSentenceStr, resultTi
Ejemplo n.º 13
0
    def moveExtended(self,
                     context,
                     direction,
                     regex,
                     errorMsg="Error",
                     reconstructMode="sameIndent"):
        """
            This function is the real implementation of move by sentence algorithm.
            First, it identifies current sentence by calling expandSentence,
            which might expand current context if necessary.
            Then one of two things may happen:
            1.
                If at this point we're touching boundary (for example,
                when moving forward, the end of the sentence coincides with the end of the last paragraph),
                then we explicitly advance to the next paragraph, And replace the context.
                This case might happen, for example, if the next paragraph is not compatible with current one, e.g. due to different formatting.
            or 2.
                If we're not touching the boundary, then
                depending on direction, it either moves context caret (not the real system caret)
                either at the end of current sentence when moving forward,
                or one character before the beginning of current sentence when moving backward.
                Since we're not touching the boundary, this operation is always valid.
            Then  in either case we call expandSentence again with the updated caret location within context,
            to obtain the resulting sentence.
        """
        chimeIfAcrossParagraphs = False
        if reconstructMode == "always":
            compatibilityFunc = lambda x, y: True
        elif reconstructMode == "sameIndent":
            compatibilityFunc = lambda ti1, ti2: (
                ti1.NVDAObjectAtStart.location[0] == ti2.NVDAObjectAtStart.
                location[0]) and (self.getParagraphStyle(ti1) == self.
                                  getParagraphStyle(ti2))
        elif reconstructMode == "never":
            compatibilityFunc = lambda x, y: False
        else:
            raise ValueError()

        sentenceStr, startTi, startOffset, endTi, endOffset = self.expandSentence(
            context, regex, direction, compatibilityFunc=compatibilityFunc)
        if direction == 0:
            return sentenceStr, context.makeSentenceInfo(
                startTi, startOffset, endTi, endOffset)
        elif direction > 0:
            cindex = -1
        else:
            cindex = 0
        if context.isTouchingBoundary(direction, startTi, startOffset, endTi,
                                      endOffset):
            # We need to look for the next sentence in the next paragraph.
            mylog("Looking in the next paragraph.")
            paragraph = context.textInfos[cindex]
            counter = 0
            while True:
                counter += 1
                if counter > 1000:
                    raise RuntimeError("Infinite loop detected.")
                paragraph = self.nextParagraph(paragraph, direction)
                if paragraph is None:
                    self.chimeNoNextSentence(errorMsg)
                    return (None, None)
                if not speech.isBlank(paragraph.text):
                    break
            self.chimeCrossParagraphBorder()
            context = Context(paragraph, 0)
            if direction < 0:
                context.findByOffset(paragraph, len(paragraph.text) - 1)
        else:
            # Next sentence can be found in the same context
            # At least its beginning or ending - that sentence will be expanded.
            mylog("Looking in the same paragraph.")
            if direction > 0:
                context.findByOffset(endTi, endOffset)
            else:
                context.findByOffset(startTi, startOffset - 1)
            chimeIfAcrossParagraphs = True
        sentenceStr2, startTi2, startOffset2, endTi2, endOffset2 = self.expandSentence(
            context, regex, direction, compatibilityFunc=compatibilityFunc)
        if debug:
            mylog(f"result2: {sentenceStr2}")
            mylog(f"start: {startOffset2} @ {startTi2.text}")
            mylog(f"end: {endOffset2} @ {endTi2.text}")
        if chimeIfAcrossParagraphs:
            if ((direction > 0 and startOffset2 == 0)
                    or (direction < 0 and startOffset == 0)):
                mylog(f"Chime!")
                self.chimeCrossParagraphBorder()
        info = context.makeSentenceInfo(startTi2, startOffset2, endTi2,
                                        endOffset2)
        if debug:
            mylog(f"MoveExtended result string: {sentenceStr2}")
            mylog(f"MoveExtended result: {info.text}")
        return sentenceStr2, info
Ejemplo n.º 14
0
    def move(self, gesture, increment):
        focus = api.getFocusObject()
        if (isinstance(focus, winword.WordDocument)
                or ("Dynamic_IAccessibleRichEdit" in str(type(focus))
                    and hasattr(focus, "script_caret_nextSentence")
                    and hasattr(focus, "script_caret_previousSentence"))):
            if increment > 0:
                focus.script_caret_nextSentence(gesture)
            else:
                focus.script_caret_previousSentence(gesture)
            return
        if focus.role in [
                controlTypes.ROLE_COMBOBOX, controlTypes.ROLE_LISTITEM
        ]:
            try:
                # The following line will only succeed in BrowserMode.
                focus.treeInterceptor.script_collapseOrExpandControl(gesture)
            except AttributeError:
                gesture.send()
            return
        if hasattr(focus, "treeInterceptor") and hasattr(
                focus.treeInterceptor, "makeTextInfo"):
            focus = focus.treeInterceptor
        textInfo = focus.makeTextInfo(textInfos.POSITION_CARET)
        caretOffset = textInfo._getCaretOffset()
        textInfo.expand(textInfos.UNIT_PARAGRAPH)
        caretIndex = caretOffset - textInfo._startOffset
        while True:
            text = textInfo.text
            boundaries = self.splitParagraphIntoSentences(text)
            # Find the first index in boundaries that is strictly greater than caretIndex
            j = bisect.bisect_right(boundaries, caretIndex)
            i = j - 1
            # At this point boundaries[i] and boundaries[j] represent
            # the boundaries of the current sentence.

            # Testing if we can move to previous/next sentence and still remain within the same paragraph.
            n = len(boundaries)
            if (0 <= i + increment < n) and (0 <= j + increment < n):
                # Next sentence can be found within the same paragraph
                i += increment
                j += increment
                textInfo.collapse()
                textInfo2 = textInfo.copy()
                result = textInfo.move(textInfos.UNIT_CHARACTER, boundaries[i])
                assert ((result != 0) or (boundaries[i] == 0))
                # If we are moving to the very last sentence of the very last paragraph,
                # then we cannot move textInfo2 to the end of the paragraph.
                # Move to just one character before that, and then try to move one more character.
                assert (boundaries[j] > 1)
                result2 = textInfo2.move(textInfos.UNIT_CHARACTER,
                                         boundaries[j] - 1)
                assert (result2 != 0)
                textInfo2.move(textInfos.UNIT_CHARACTER, 1)
                textInfo.setEndPoint(textInfo2, "endToStart")
                textInfo.updateCaret()
                ui.message(textInfo.text)
                return
            else:
                # We need to move to previous/next paragraph to find previous/next sentence.
                self.fancyBeep("AC#EG#", 30, 5, 5)
                while True:
                    result = textInfo.move(textInfos.UNIT_PARAGRAPH, increment)
                    if result == 0:
                        self.fancyBeep("HF", 100, 50, 50)
                        return
                    textInfo.expand(textInfos.UNIT_PARAGRAPH)
                    if not speech.isBlank(textInfo.text):
                        break
                textInfo.expand(textInfos.UNIT_PARAGRAPH)
                # Imaginary caret just before (if moving forward) or just after (if moving backward) this paragraph,
                # so that the next iteration will pick the very first or the very last sentence of this paragraph.
                if increment > 0:
                    caretIndex = -1
                else:
                    caretIndex = len(textInfo.text)