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  }