code.gitea.io/gitea@v1.22.3/modules/markup/markdown/math/block_parser.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package math
     5  
     6  import (
     7  	"bytes"
     8  
     9  	"github.com/yuin/goldmark/ast"
    10  	"github.com/yuin/goldmark/parser"
    11  	"github.com/yuin/goldmark/text"
    12  	"github.com/yuin/goldmark/util"
    13  )
    14  
    15  type blockParser struct {
    16  	parseDollars bool
    17  }
    18  
    19  // NewBlockParser creates a new math BlockParser
    20  func NewBlockParser(parseDollarBlocks bool) parser.BlockParser {
    21  	return &blockParser{
    22  		parseDollars: parseDollarBlocks,
    23  	}
    24  }
    25  
    26  // Open parses the current line and returns a result of parsing.
    27  func (b *blockParser) Open(parent ast.Node, reader text.Reader, pc parser.Context) (ast.Node, parser.State) {
    28  	line, segment := reader.PeekLine()
    29  	pos := pc.BlockOffset()
    30  	if pos == -1 || len(line[pos:]) < 2 {
    31  		return nil, parser.NoChildren
    32  	}
    33  
    34  	var dollars bool
    35  	if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
    36  		dollars = true
    37  	} else if line[pos] == '\\' && line[pos+1] == '[' {
    38  		if len(line[pos:]) >= 3 && line[pos+2] == '!' && bytes.Contains(line[pos:], []byte(`\]`)) {
    39  			// do not process escaped attention block: "> \[!NOTE\]"
    40  			return nil, parser.NoChildren
    41  		}
    42  		dollars = false
    43  	} else {
    44  		return nil, parser.NoChildren
    45  	}
    46  
    47  	node := NewBlock(dollars, pos)
    48  
    49  	// Now we need to check if the ending block is on the segment...
    50  	endBytes := []byte{'\\', ']'}
    51  	if dollars {
    52  		endBytes = []byte{'$', '$'}
    53  	}
    54  	idx := bytes.Index(line[pos+2:], endBytes)
    55  	if idx >= 0 {
    56  		segment.Stop = segment.Start + idx + 2
    57  		reader.Advance(segment.Len() - 1)
    58  		segment.Start += 2
    59  		node.Lines().Append(segment)
    60  		node.Closed = true
    61  		return node, parser.Close | parser.NoChildren
    62  	}
    63  
    64  	reader.Advance(segment.Len() - 1)
    65  	segment.Start += 2
    66  	node.Lines().Append(segment)
    67  	return node, parser.NoChildren
    68  }
    69  
    70  // Continue parses the current line and returns a result of parsing.
    71  func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
    72  	block := node.(*Block)
    73  	if block.Closed {
    74  		return parser.Close
    75  	}
    76  
    77  	line, segment := reader.PeekLine()
    78  	w, pos := util.IndentWidth(line, 0)
    79  	if w < 4 {
    80  		if block.Dollars {
    81  			i := pos
    82  			for ; i < len(line) && line[i] == '$'; i++ {
    83  			}
    84  			length := i - pos
    85  			if length >= 2 && util.IsBlank(line[i:]) {
    86  				reader.Advance(segment.Stop - segment.Start - segment.Padding)
    87  				block.Closed = true
    88  				return parser.Close
    89  			}
    90  		} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) {
    91  			reader.Advance(segment.Stop - segment.Start - segment.Padding)
    92  			block.Closed = true
    93  			return parser.Close
    94  		}
    95  	}
    96  
    97  	pos, padding := util.IndentPosition(line, 0, block.Indent)
    98  	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
    99  	node.Lines().Append(seg)
   100  	reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
   101  	return parser.Continue | parser.NoChildren
   102  }
   103  
   104  // Close will be called when the parser returns Close.
   105  func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
   106  	// noop
   107  }
   108  
   109  // CanInterruptParagraph returns true if the parser can interrupt paragraphs,
   110  // otherwise false.
   111  func (b *blockParser) CanInterruptParagraph() bool {
   112  	return true
   113  }
   114  
   115  // CanAcceptIndentedLine returns true if the parser can open new node when
   116  // the given line is being indented more than 3 spaces.
   117  func (b *blockParser) CanAcceptIndentedLine() bool {
   118  	return false
   119  }
   120  
   121  // Trigger returns a list of characters that triggers Parse method of
   122  // this parser.
   123  // If Trigger returns a nil, Open will be called with any lines.
   124  //
   125  // We leave this as nil as our parse method is quick enough
   126  func (b *blockParser) Trigger() []byte {
   127  	return nil
   128  }