Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes14kdownloads
list.py409 linesDownload Raw Back to rules_block
1# Lists2import logging3 4from ..common.utils import isStrSpace5from .state_block import StateBlock6 7LOGGER = logging.getLogger(__name__)8 9 10# Search `[-+*][\n ]`, returns next pos after marker on success11# or -1 on fail.12def skipBulletListMarker(state: StateBlock, startLine: int) -> int:13    pos = state.bMarks[startLine] + state.tShift[startLine]14    maximum = state.eMarks[startLine]15 16    try:17        marker = state.src[pos]18    except IndexError:19        return -120    pos += 121 22    if marker not in ("*", "-", "+"):23        return -124 25    if pos < maximum:26        ch = state.src[pos]27 28        if not isStrSpace(ch):29            # " -test " - is not a list item30            return -131 32    return pos33 34 35# Search `\d+[.)][\n ]`, returns next pos after marker on success36# or -1 on fail.37def skipOrderedListMarker(state: StateBlock, startLine: int) -> int:38    start = state.bMarks[startLine] + state.tShift[startLine]39    pos = start40    maximum = state.eMarks[startLine]41 42    # List marker should have at least 2 chars (digit + dot)43    if pos + 1 >= maximum:44        return -145 46    ch = state.src[pos]47    pos += 148 49    ch_ord = ord(ch)50    # /* 0 */  /* 9 */51    if ch_ord < 0x30 or ch_ord > 0x39:52        return -153 54    while True:55        # EOL -> fail56        if pos >= maximum:57            return -158 59        ch = state.src[pos]60        pos += 161 62        # /* 0 */  /* 9 */63        ch_ord = ord(ch)64        if ch_ord >= 0x30 and ch_ord <= 0x39:65            # List marker should have no more than 9 digits66            # (prevents integer overflow in browsers)67            if pos - start >= 10:68                return -169 70            continue71 72        # found valid marker73        if ch in (")", "."):74            break75 76        return -177 78    if pos < maximum:79        ch = state.src[pos]80 81        if not isStrSpace(ch):82            # " 1.test " - is not a list item83            return -184 85    return pos86 87 88def markTightParagraphs(state: StateBlock, idx: int) -> None:89    level = state.level + 290 91    i = idx + 292    length = len(state.tokens) - 293    while i < length:94        if state.tokens[i].level == level and state.tokens[i].type == "paragraph_open":95            state.tokens[i + 2].hidden = True96            state.tokens[i].hidden = True97            i += 298        i += 199 100 101def list_block(state: StateBlock, startLine: int, endLine: int, silent: bool) -> bool:102    LOGGER.debug("entering list: %s, %s, %s, %s", state, startLine, endLine, silent)103 104    isTerminatingParagraph = False105    tight = True106 107    if state.is_code_block(startLine):108        return False109 110    # Special case:111    #  - item 1112    #   - item 2113    #    - item 3114    #     - item 4115    #      - this one is a paragraph continuation116    if (117        state.listIndent >= 0118        and state.sCount[startLine] - state.listIndent >= 4119        and state.sCount[startLine] < state.blkIndent120    ):121        return False122 123    # limit conditions when list can interrupt124    # a paragraph (validation mode only)125    # Next list item should still terminate previous list item126    #127    # This code can fail if plugins use blkIndent as well as lists,128    # but I hope the spec gets fixed long before that happens.129    #130    if (131        silent132        and state.parentType == "paragraph"133        and state.sCount[startLine] >= state.blkIndent134    ):135        isTerminatingParagraph = True136 137    # Detect list type and position after marker138    posAfterMarker = skipOrderedListMarker(state, startLine)139    if posAfterMarker >= 0:140        isOrdered = True141        start = state.bMarks[startLine] + state.tShift[startLine]142        markerValue = int(state.src[start : posAfterMarker - 1])143 144        # If we're starting a new ordered list right after145        # a paragraph, it should start with 1.146        if isTerminatingParagraph and markerValue != 1:147            return False148    else:149        posAfterMarker = skipBulletListMarker(state, startLine)150        if posAfterMarker >= 0:151            isOrdered = False152        else:153            return False154 155    # If we're starting a new unordered list right after156    # a paragraph, first line should not be empty.157    if (158        isTerminatingParagraph159        and state.skipSpaces(posAfterMarker) >= state.eMarks[startLine]160    ):161        return False162 163    # We should terminate list on style change. Remember first one to compare.164    markerChar = state.src[posAfterMarker - 1]165 166    # For validation mode we can terminate immediately167    if silent:168        return True169 170    # Start list171    listTokIdx = len(state.tokens)172 173    if isOrdered:174        token = state.push("ordered_list_open", "ol", 1)175        if markerValue != 1:176            token.attrs = {"start": markerValue}177 178    else:179        token = state.push("bullet_list_open", "ul", 1)180 181    token.map = listLines = [startLine, 0]182    token.markup = markerChar183 184    #185    # Iterate list items186    #187 188    nextLine = startLine189    prevEmptyEnd = False190    terminatorRules = state.md.block.ruler.getRules("list")191 192    oldParentType = state.parentType193    state.parentType = "list"194 195    while nextLine < endLine:196        pos = posAfterMarker197        maximum = state.eMarks[nextLine]198 199        initial = offset = (200            state.sCount[nextLine]201            + posAfterMarker202            - (state.bMarks[startLine] + state.tShift[startLine])203        )204 205        while pos < maximum:206            ch = state.src[pos]207 208            if ch == "\t":209                offset += 4 - (offset + state.bsCount[nextLine]) % 4210            elif ch == " ":211                offset += 1212            else:213                break214 215            pos += 1216 217        contentStart = pos218 219        # trimming space in "-    \n  3" case, indent is 1 here220        indentAfterMarker = 1 if contentStart >= maximum else offset - initial221 222        # If we have more than 4 spaces, the indent is 1223        # (the rest is just indented code block)224        if indentAfterMarker > 4:225            indentAfterMarker = 1226 227        # "  -  test"228        #  ^^^^^ - calculating total length of this thing229        indent = initial + indentAfterMarker230 231        # Run subparser & write tokens232        token = state.push("list_item_open", "li", 1)233        token.markup = markerChar234        token.map = itemLines = [startLine, 0]235        if isOrdered:236            token.info = state.src[start : posAfterMarker - 1]237 238        # Detect GFM task checkbox: `[ ] ` or `[x] `/`[X] ` at content start239        checkboxLen = 0240        if state.md.options.get("tasklists", False) and contentStart < maximum:241            checked = _detect_task_checkbox(state.src, contentStart, maximum)242            if checked is not None:243                token.meta = {"checked": checked}244                # Advance content past the checkbox: `[x]` (3 chars) + whitespace.245                # `_detect_task_checkbox` already guarantees a whitespace char at246                # pos+3, so we always consume 4 characters.247                checkboxLen = 4248 249        # change current state, then restore it after parser subcall250        oldTight = state.tight251        oldBMark = state.bMarks[startLine]252        oldTShift = state.tShift[startLine]253        oldSCount = state.sCount[startLine]254 255        #  - example list256        # ^ listIndent position will be here257        #   ^ blkIndent position will be here258        #259        oldListIndent = state.listIndent260        state.listIndent = state.blkIndent261        state.blkIndent = indent262 263        state.tight = True264        state.tShift[startLine] = contentStart - state.bMarks[startLine]265        state.sCount[startLine] = offset266 267        # If we detected a checkbox, advance bMarks past it so that268        # getLines() doesn't include the checkbox text in the content.269        if checkboxLen:270            state.bMarks[startLine] = contentStart + checkboxLen271            state.tShift[startLine] = 0272 273        if contentStart >= maximum and state.isEmpty(startLine + 1):274            # workaround for this case275            # (list item is empty, list terminates before "foo"):276            # ~~~~~~~~277            #   -278            #279            #     foo280            # ~~~~~~~~281            state.line = min(state.line + 2, endLine)282        else:283            # NOTE in list.js this was:284            # state.md.block.tokenize(state, startLine, endLine, True)285            # but  tokeniz does not take the final parameter286            state.md.block.tokenize(state, startLine, endLine)287 288        # If any of list item is tight, mark list as tight289        if (not state.tight) or prevEmptyEnd:290            tight = False291 292        # Item become loose if finish with empty line,293        # but we should filter last element, because it means list finish294        prevEmptyEnd = (state.line - startLine) > 1 and state.isEmpty(state.line - 1)295 296        state.blkIndent = state.listIndent297        state.listIndent = oldListIndent298        if checkboxLen:299            state.bMarks[startLine] = oldBMark300        state.tShift[startLine] = oldTShift301        state.sCount[startLine] = oldSCount302        state.tight = oldTight303 304        token = state.push("list_item_close", "li", -1)305        token.markup = markerChar306 307        nextLine = startLine = state.line308        itemLines[1] = nextLine309 310        if nextLine >= endLine:311            break312 313        contentStart = state.bMarks[startLine]314 315        #316        # Try to check if list is terminated or continued.317        #318        if state.sCount[nextLine] < state.blkIndent:319            break320 321        if state.is_code_block(startLine):322            break323 324        # fail if terminating block found325        terminate = False326        for terminatorRule in terminatorRules:327            if terminatorRule(state, nextLine, endLine, True):328                terminate = True329                break330 331        if terminate:332            break333 334        # fail if list has another type335        if isOrdered:336            posAfterMarker = skipOrderedListMarker(state, nextLine)337            if posAfterMarker < 0:338                break339            start = state.bMarks[nextLine] + state.tShift[nextLine]340        else:341            posAfterMarker = skipBulletListMarker(state, nextLine)342            if posAfterMarker < 0:343                break344 345        if markerChar != state.src[posAfterMarker - 1]:346            break347 348    # Finalize list349 350    # If any direct list item has a task checkbox, add class to the list351    if state.md.options.get("tasklists", False):352        containsTask = False353        level = state.tokens[listTokIdx].level354        for j in range(listTokIdx + 1, len(state.tokens)):355            tok = state.tokens[j]356            if (357                tok.level == level + 1358                and tok.type == "list_item_open"359                and tok.meta360                and "checked" in tok.meta361            ):362                tok.attrJoin("class", "task-list-item")363                containsTask = True364        if containsTask:365            state.tokens[listTokIdx].attrJoin("class", "contains-task-list")366 367    if isOrdered:368        token = state.push("ordered_list_close", "ol", -1)369    else:370        token = state.push("bullet_list_close", "ul", -1)371 372    token.markup = markerChar373 374    listLines[1] = nextLine375    state.line = nextLine376 377    state.parentType = oldParentType378 379    # mark paragraphs tight if needed380    if tight:381        markTightParagraphs(state, listTokIdx)382 383    return True384 385 386def _detect_task_checkbox(src: str, pos: int, maximum: int) -> bool | None:387    """Detect ``[ ]``, ``[x]``, or ``[X]`` at *pos*, followed by whitespace.388 389    Returns ``True`` (checked), ``False`` (unchecked), or ``None`` (no match).390    """391    # Need at least 4 chars: `[`, char, `]`, whitespace392    if pos + 4 > maximum:393        return None394    if src[pos] != "[":395        return None396    inner = src[pos + 1]397    if src[pos + 2] != "]":398        return None399    if inner == " ":400        checked = False401    elif inner in ("x", "X"):402        checked = True403    else:404        return None405    # After `]`, must have whitespace406    if src[pos + 3] not in (" ", "\t"):407        return None408    return checked409 
codekingpro/portable-devtools · Team Ai