code.gitea.io/gitea@v1.19.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  	dollars := false
    35  	if b.parseDollars && line[pos] == '$' && line[pos+1] == '$' {
    36  		dollars = true
    37  	} else if line[pos] != '\\' || line[pos+1] != '[' {
    38  		return nil, parser.NoChildren
    39  	}
    40  
    41  	node := NewBlock(dollars, pos)
    42  
    43  	// Now we need to check if the ending block is on the segment...
    44  	endBytes := []byte{'\\', ']'}
    45  	if dollars {
    46  		endBytes = []byte{'$', '$'}
    47  	}
    48  	idx := bytes.Index(line[pos+2:], endBytes)
    49  	if idx >= 0 {
    50  		segment.Stop = segment.Start + idx + 2
    51  		reader.Advance(segment.Len() - 1)
    52  		segment.Start += 2
    53  		node.Lines().Append(segment)
    54  		node.Closed = true
    55  		return node, parser.Close | parser.NoChildren
    56  	}
    57  
    58  	reader.Advance(segment.Len() - 1)
    59  	segment.Start += 2
    60  	node.Lines().Append(segment)
    61  	return node, parser.NoChildren
    62  }
    63  
    64  // Continue parses the current line and returns a result of parsing.
    65  func (b *blockParser) Continue(node ast.Node, reader text.Reader, pc parser.Context) parser.State {
    66  	block := node.(*Block)
    67  	if block.Closed {
    68  		return parser.Close
    69  	}
    70  
    71  	line, segment := reader.PeekLine()
    72  	w, pos := util.IndentWidth(line, 0)
    73  	if w < 4 {
    74  		if block.Dollars {
    75  			i := pos
    76  			for ; i < len(line) && line[i] == '$'; i++ {
    77  			}
    78  			length := i - pos
    79  			if length >= 2 && util.IsBlank(line[i:]) {
    80  				reader.Advance(segment.Stop - segment.Start - segment.Padding)
    81  				block.Closed = true
    82  				return parser.Close
    83  			}
    84  		} else if len(line[pos:]) > 1 && line[pos] == '\\' && line[pos+1] == ']' && util.IsBlank(line[pos+2:]) {
    85  			reader.Advance(segment.Stop - segment.Start - segment.Padding)
    86  			block.Closed = true
    87  			return parser.Close
    88  		}
    89  	}
    90  
    91  	pos, padding := util.IndentPosition(line, 0, block.Indent)
    92  	seg := text.NewSegmentPadding(segment.Start+pos, segment.Stop, padding)
    93  	node.Lines().Append(seg)
    94  	reader.AdvanceAndSetPadding(segment.Stop-segment.Start-pos-1, padding)
    95  	return parser.Continue | parser.NoChildren
    96  }
    97  
    98  // Close will be called when the parser returns Close.
    99  func (b *blockParser) Close(node ast.Node, reader text.Reader, pc parser.Context) {
   100  	// noop
   101  }
   102  
   103  // CanInterruptParagraph returns true if the parser can interrupt paragraphs,
   104  // otherwise false.
   105  func (b *blockParser) CanInterruptParagraph() bool {
   106  	return true
   107  }
   108  
   109  // CanAcceptIndentedLine returns true if the parser can open new node when
   110  // the given line is being indented more than 3 spaces.
   111  func (b *blockParser) CanAcceptIndentedLine() bool {
   112  	return false
   113  }
   114  
   115  // Trigger returns a list of characters that triggers Parse method of
   116  // this parser.
   117  // If Trigger returns a nil, Open will be called with any lines.
   118  //
   119  // We leave this as nil as our parse method is quick enough
   120  func (b *blockParser) Trigger() []byte {
   121  	return nil
   122  }