1# Block quotes 
    2from __future__ import annotations 
    3 
    4import logging 
    5 
    6from ..common.utils import isStrSpace 
    7from .state_block import StateBlock 
    8 
    9LOGGER = logging.getLogger(__name__) 
    10 
    11 
    12def blockquote(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool: 
    13    LOGGER.debug( 
    14        "entering blockquote: %s, %s, %s, %s", state, startLine, endLine, silent 
    15    ) 
    16 
    17    oldLineMax = state.lineMax 
    18    pos = state.bMarks[startLine] + state.tShift[startLine] 
    19    max = state.eMarks[startLine] 
    20 
    21    if state.is_code_block(startLine): 
    22        return False 
    23 
    24    # check the block quote marker 
    25    try: 
    26        if state.src[pos] != ">": 
    27            return False 
    28    except IndexError: 
    29        return False 
    30    pos += 1 
    31 
    32    # we know that it's going to be a valid blockquote, 
    33    # so no point trying to find the end of it in silent mode 
    34    if silent: 
    35        return True 
    36 
    37    # set offset past spaces and ">" 
    38    initial = offset = state.sCount[startLine] + 1 
    39 
    40    try: 
    41        second_char: str | None = state.src[pos] 
    42    except IndexError: 
    43        second_char = None 
    44 
    45    # skip one optional space after '>' 
    46    if second_char == " ": 
    47        # ' >   test ' 
    48        #     ^ -- position start of line here: 
    49        pos += 1 
    50        initial += 1 
    51        offset += 1 
    52        adjustTab = False 
    53        spaceAfterMarker = True 
    54    elif second_char == "\t": 
    55        spaceAfterMarker = True 
    56 
    57        if (state.bsCount[startLine] + offset) % 4 == 3: 
    58            # '  >\t  test ' 
    59            #       ^ -- position start of line here (tab has width==1) 
    60            pos += 1 
    61            initial += 1 
    62            offset += 1 
    63            adjustTab = False 
    64        else: 
    65            # ' >\t  test ' 
    66            #    ^ -- position start of line here + shift bsCount slightly 
    67            #         to make extra space appear 
    68            adjustTab = True 
    69 
    70    else: 
    71        spaceAfterMarker = False 
    72 
    73    oldBMarks = [state.bMarks[startLine]] 
    74    state.bMarks[startLine] = pos 
    75 
    76    while pos < max: 
    77        ch = state.src[pos] 
    78 
    79        if isStrSpace(ch): 
    80            if ch == "\t": 
    81                offset += ( 
    82                    4 
    83                    - (offset + state.bsCount[startLine] + (1 if adjustTab else 0)) % 4 
    84                ) 
    85            else: 
    86                offset += 1 
    87 
    88        else: 
    89            break 
    90 
    91        pos += 1 
    92 
    93    oldBSCount = [state.bsCount[startLine]] 
    94    state.bsCount[startLine] = ( 
    95        state.sCount[startLine] + 1 + (1 if spaceAfterMarker else 0) 
    96    ) 
    97 
    98    lastLineEmpty = pos >= max 
    99 
    100    oldSCount = [state.sCount[startLine]] 
    101    state.sCount[startLine] = offset - initial 
    102 
    103    oldTShift = [state.tShift[startLine]] 
    104    state.tShift[startLine] = pos - state.bMarks[startLine] 
    105 
    106    terminatorRules = state.md.block.ruler.getRules("blockquote") 
    107 
    108    oldParentType = state.parentType 
    109    state.parentType = "blockquote" 
    110 
    111    # Search the end of the block 
    112    # 
    113    # Block ends with either: 
    114    #  1. an empty line outside: 
    115    #     ``` 
    116    #     > test 
    117    # 
    118    #     ``` 
    119    #  2. an empty line inside: 
    120    #     ``` 
    121    #     > 
    122    #     test 
    123    #     ``` 
    124    #  3. another tag: 
    125    #     ``` 
    126    #     > test 
    127    #      - - - 
    128    #     ``` 
    129 
    130    # for (nextLine = startLine + 1; nextLine < endLine; nextLine++) { 
    131    nextLine = startLine + 1 
    132    while nextLine < endLine: 
    133        # check if it's outdented, i.e. it's inside list item and indented 
    134        # less than said list item: 
    135        # 
    136        # ``` 
    137        # 1. anything 
    138        #    > current blockquote 
    139        # 2. checking this line 
    140        # ``` 
    141        isOutdented = state.sCount[nextLine] < state.blkIndent 
    142 
    143        pos = state.bMarks[nextLine] + state.tShift[nextLine] 
    144        max = state.eMarks[nextLine] 
    145 
    146        if pos >= max: 
    147            # Case 1: line is not inside the blockquote, and this line is empty. 
    148            break 
    149 
    150        evaluatesTrue = state.src[pos] == ">" and not isOutdented 
    151        pos += 1 
    152        if evaluatesTrue: 
    153            # This line is inside the blockquote. 
    154 
    155            # set offset past spaces and ">" 
    156            initial = offset = state.sCount[nextLine] + 1 
    157 
    158            try: 
    159                next_char: str | None = state.src[pos] 
    160            except IndexError: 
    161                next_char = None 
    162 
    163            # skip one optional space after '>' 
    164            if next_char == " ": 
    165                # ' >   test ' 
    166                #     ^ -- position start of line here: 
    167                pos += 1 
    168                initial += 1 
    169                offset += 1 
    170                adjustTab = False 
    171                spaceAfterMarker = True 
    172            elif next_char == "\t": 
    173                spaceAfterMarker = True 
    174 
    175                if (state.bsCount[nextLine] + offset) % 4 == 3: 
    176                    # '  >\t  test ' 
    177                    #       ^ -- position start of line here (tab has width==1) 
    178                    pos += 1 
    179                    initial += 1 
    180                    offset += 1 
    181                    adjustTab = False 
    182                else: 
    183                    # ' >\t  test ' 
    184                    #    ^ -- position start of line here + shift bsCount slightly 
    185                    #         to make extra space appear 
    186                    adjustTab = True 
    187 
    188            else: 
    189                spaceAfterMarker = False 
    190 
    191            oldBMarks.append(state.bMarks[nextLine]) 
    192            state.bMarks[nextLine] = pos 
    193 
    194            while pos < max: 
    195                ch = state.src[pos] 
    196 
    197                if isStrSpace(ch): 
    198                    if ch == "\t": 
    199                        offset += ( 
    200                            4 
    201                            - ( 
    202                                offset 
    203                                + state.bsCount[nextLine] 
    204                                + (1 if adjustTab else 0) 
    205                            ) 
    206                            % 4 
    207                        ) 
    208                    else: 
    209                        offset += 1 
    210                else: 
    211                    break 
    212 
    213                pos += 1 
    214 
    215            lastLineEmpty = pos >= max 
    216 
    217            oldBSCount.append(state.bsCount[nextLine]) 
    218            state.bsCount[nextLine] = ( 
    219                state.sCount[nextLine] + 1 + (1 if spaceAfterMarker else 0) 
    220            ) 
    221 
    222            oldSCount.append(state.sCount[nextLine]) 
    223            state.sCount[nextLine] = offset - initial 
    224 
    225            oldTShift.append(state.tShift[nextLine]) 
    226            state.tShift[nextLine] = pos - state.bMarks[nextLine] 
    227 
    228            nextLine += 1 
    229            continue 
    230 
    231        # Case 2: line is not inside the blockquote, and the last line was empty. 
    232        if lastLineEmpty: 
    233            break 
    234 
    235        # Case 3: another tag found. 
    236        terminate = False 
    237 
    238        for terminatorRule in terminatorRules: 
    239            if terminatorRule(state, nextLine, endLine, True): 
    240                terminate = True 
    241                break 
    242 
    243        if terminate: 
    244            # Quirk to enforce "hard termination mode" for paragraphs; 
    245            # normally if you call `tokenize(state, startLine, nextLine)`, 
    246            # paragraphs will look below nextLine for paragraph continuation, 
    247            # but if blockquote is terminated by another tag, they shouldn't 
    248            state.lineMax = nextLine 
    249 
    250            if state.blkIndent != 0: 
    251                # state.blkIndent was non-zero, we now set it to zero, 
    252                # so we need to re-calculate all offsets to appear as 
    253                # if indent wasn't changed 
    254                oldBMarks.append(state.bMarks[nextLine]) 
    255                oldBSCount.append(state.bsCount[nextLine]) 
    256                oldTShift.append(state.tShift[nextLine]) 
    257                oldSCount.append(state.sCount[nextLine]) 
    258                state.sCount[nextLine] -= state.blkIndent 
    259 
    260            break 
    261 
    262        oldBMarks.append(state.bMarks[nextLine]) 
    263        oldBSCount.append(state.bsCount[nextLine]) 
    264        oldTShift.append(state.tShift[nextLine]) 
    265        oldSCount.append(state.sCount[nextLine]) 
    266 
    267        # A negative indentation means that this is a paragraph continuation 
    268        # 
    269        state.sCount[nextLine] = -1 
    270 
    271        nextLine += 1 
    272 
    273    oldIndent = state.blkIndent 
    274    state.blkIndent = 0 
    275 
    276    token = state.push("blockquote_open", "blockquote", 1) 
    277    token.markup = ">" 
    278    token.map = lines = [startLine, 0] 
    279 
    280    state.md.block.tokenize(state, startLine, nextLine) 
    281 
    282    token = state.push("blockquote_close", "blockquote", -1) 
    283    token.markup = ">" 
    284 
    285    state.lineMax = oldLineMax 
    286    state.parentType = oldParentType 
    287    lines[1] = state.line 
    288 
    289    # Restore original tShift; this might not be necessary since the parser 
    290    # has already been here, but just to make sure we can do that. 
    291    for i, item in enumerate(oldTShift): 
    292        state.bMarks[i + startLine] = oldBMarks[i] 
    293        state.tShift[i + startLine] = item 
    294        state.sCount[i + startLine] = oldSCount[i] 
    295        state.bsCount[i + startLine] = oldBSCount[i] 
    296 
    297    state.blkIndent = oldIndent 
    298 
    299    return True