github.com/aretext/aretext@v1.3.0/locate/paragraph.go (about) 1 package locate 2 3 import ( 4 "io" 5 6 "github.com/aretext/aretext/text" 7 "github.com/aretext/aretext/text/segment" 8 ) 9 10 // NextParagraph locates the start of the next paragraph after the cursor. 11 // Paragraph boundaries occur at empty lines. 12 func NextParagraph(tree *text.Tree, pos uint64) uint64 { 13 reader := tree.ReaderAtPosition(pos) 14 segmentIter := segment.NewGraphemeClusterIter(reader) 15 seg := segment.Empty() 16 var prevWasNewlineFlag, nonNewlineFlag bool 17 var offset, prevOffset uint64 18 for { 19 err := segmentIter.NextSegment(seg) 20 if err == io.EOF { 21 // End of document. 22 return pos + prevOffset 23 } else if err != nil { 24 panic(err) 25 } 26 27 if seg.HasNewline() { 28 if prevWasNewlineFlag && nonNewlineFlag { 29 // An empty line is a paragraph boundary. 30 // Choose the first one after we see a non-newline. 31 break 32 } 33 prevWasNewlineFlag = true 34 } else { 35 nonNewlineFlag = true 36 prevWasNewlineFlag = false 37 } 38 39 prevOffset = offset 40 offset += seg.NumRunes() 41 } 42 return pos + offset 43 } 44 45 // PrevParagraph locates the start of the first paragraph before the cursor. 46 // Paragraph boundaries occur at empty lines. 47 func PrevParagraph(tree *text.Tree, pos uint64) uint64 { 48 reader := tree.ReverseReaderAtPosition(pos) 49 segmentIter := segment.NewReverseGraphemeClusterIter(reader) 50 seg := segment.Empty() 51 var prevWasNewlineFlag, nonNewlineFlag bool 52 var offset uint64 53 for { 54 err := segmentIter.NextSegment(seg) 55 if err == io.EOF { 56 // Start of the document. 57 return 0 58 } else if err != nil { 59 panic(err) 60 } 61 62 if seg.HasNewline() { 63 if prevWasNewlineFlag && nonNewlineFlag { 64 // An empty line is a paragraph boundary. 65 return pos - offset 66 } 67 prevWasNewlineFlag = true 68 } else { 69 prevWasNewlineFlag = false 70 nonNewlineFlag = true 71 } 72 73 offset += seg.NumRunes() 74 } 75 }