github.com/aretext/aretext@v1.3.0/locate/string.go (about) 1 package locate 2 3 import ( 4 "github.com/aretext/aretext/syntax/parser" 5 "github.com/aretext/aretext/text" 6 ) 7 8 // StringObject locates the start and end positions for a single- or double-quoted string. 9 func StringObject(quoteRune rune, textTree *text.Tree, syntaxParser *parser.P, includeQuotes bool, pos uint64) (uint64, uint64) { 10 // If the cursor is inside a string syntax token starting with the quote rune, use that. 11 startPos, endPos, ok := stringObjectFromSyntaxToken(quoteRune, textTree, syntaxParser, includeQuotes, pos) 12 if ok { 13 return startPos, endPos 14 } 15 16 // Otherwise, find the string object from opening/closing quotes. 17 return stringObjectFromOpenAndCloseQuotes(quoteRune, textTree, includeQuotes, pos) 18 } 19 20 func stringObjectFromSyntaxToken(quoteRune rune, textTree *text.Tree, syntaxParser *parser.P, includeQuotes bool, pos uint64) (uint64, uint64, bool) { 21 if syntaxParser == nil { 22 return 0, 0, false 23 } 24 25 token := syntaxParser.TokenAtPosition(pos) 26 if token.Role == parser.TokenRoleString { 27 reader := textTree.ReaderAtPosition(token.StartPos) 28 r, _, err := reader.ReadRune() 29 if err == nil && r == quoteRune { 30 startPos, endPos := adjustStringObjectForIncludeQuotes(token.StartPos, token.EndPos, includeQuotes) 31 return startPos, endPos, true 32 } 33 } 34 35 return 0, 0, false 36 } 37 38 func stringObjectFromOpenAndCloseQuotes(quoteRune rune, textTree *text.Tree, includeQuotes bool, pos uint64) (uint64, uint64) { 39 if isCursorOnQuote(quoteRune, textTree, pos) { 40 // The cursor is on a quote, but we don't know if it's an opening or closing quote. 41 // First check if there's a closing quote after this quote on the same line. 42 endPos, ok := findNextQuoteInLine(quoteRune, textTree, pos+1) 43 if ok { 44 return adjustStringObjectForIncludeQuotes(pos, endPos, includeQuotes) 45 } 46 47 // Otherwise check if there's an opening quote before this quote on the same line. 48 startPos, ok := findPrevQuoteInLine(quoteRune, textTree, pos) 49 if ok { 50 return adjustStringObjectForIncludeQuotes(startPos, pos+1, includeQuotes) 51 } 52 } else { 53 // The cursor isn't on a quote, so look backwards for the start quote, then forwards for the end quote. 54 startPos, ok := findPrevQuoteInLine(quoteRune, textTree, pos) 55 if ok { 56 endPos, ok := findNextQuoteInLine(quoteRune, textTree, pos) 57 if ok { 58 return adjustStringObjectForIncludeQuotes(startPos, endPos, includeQuotes) 59 } 60 } 61 } 62 63 // Could not find string object. 64 return pos, pos 65 } 66 67 func adjustStringObjectForIncludeQuotes(startPos uint64, endPos uint64, includeQuotes bool) (uint64, uint64) { 68 if !includeQuotes { 69 startPos++ 70 if endPos > startPos { 71 endPos-- 72 } else { 73 endPos = startPos 74 } 75 } 76 77 return startPos, endPos 78 } 79 80 func isCursorOnQuote(quoteRune rune, textTree *text.Tree, pos uint64) bool { 81 reader := textTree.ReaderAtPosition(pos) 82 r, _, err := reader.ReadRune() 83 return err == nil && r == quoteRune 84 } 85 86 func findNextQuoteInLine(quoteRune rune, textTree *text.Tree, pos uint64) (uint64, bool) { 87 reader := textTree.ReaderAtPosition(pos) 88 for { 89 r, _, err := reader.ReadRune() 90 if err != nil || r == '\n' { 91 return 0, false 92 } 93 94 pos++ 95 96 if r == quoteRune { 97 return pos, true 98 } 99 } 100 } 101 102 func findPrevQuoteInLine(quoteRune rune, textTree *text.Tree, pos uint64) (uint64, bool) { 103 reader := textTree.ReverseReaderAtPosition(pos) 104 for { 105 r, _, err := reader.ReadRune() 106 if err != nil || r == '\n' { 107 return 0, false 108 } 109 110 pos-- 111 112 if r == quoteRune { 113 return pos, true 114 } 115 } 116 }