golang.org/x/tools/gopls@v0.15.3/internal/cmd/parsespan.go (about)

     1  // Copyright 2019 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmd
     6  
     7  import (
     8  	"strconv"
     9  	"strings"
    10  	"unicode/utf8"
    11  
    12  	"golang.org/x/tools/gopls/internal/protocol"
    13  )
    14  
    15  // parseSpan returns the location represented by the input.
    16  // Only file paths are accepted, not URIs.
    17  // The returned span will be normalized, and thus if printed may produce a
    18  // different string.
    19  func parseSpan(input string) span {
    20  	uri := protocol.URIFromPath
    21  
    22  	// :0:0#0-0:0#0
    23  	valid := input
    24  	var hold, offset int
    25  	hadCol := false
    26  	suf := rstripSuffix(input)
    27  	if suf.sep == "#" {
    28  		offset = suf.num
    29  		suf = rstripSuffix(suf.remains)
    30  	}
    31  	if suf.sep == ":" {
    32  		valid = suf.remains
    33  		hold = suf.num
    34  		hadCol = true
    35  		suf = rstripSuffix(suf.remains)
    36  	}
    37  	switch {
    38  	case suf.sep == ":":
    39  		return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), point{})
    40  	case suf.sep == "-":
    41  		// we have a span, fall out of the case to continue
    42  	default:
    43  		// separator not valid, rewind to either the : or the start
    44  		return newSpan(uri(valid), newPoint(hold, 0, offset), point{})
    45  	}
    46  	// only the span form can get here
    47  	// at this point we still don't know what the numbers we have mean
    48  	// if have not yet seen a : then we might have either a line or a column depending
    49  	// on whether start has a column or not
    50  	// we build an end point and will fix it later if needed
    51  	end := newPoint(suf.num, hold, offset)
    52  	hold, offset = 0, 0
    53  	suf = rstripSuffix(suf.remains)
    54  	if suf.sep == "#" {
    55  		offset = suf.num
    56  		suf = rstripSuffix(suf.remains)
    57  	}
    58  	if suf.sep != ":" {
    59  		// turns out we don't have a span after all, rewind
    60  		return newSpan(uri(valid), end, point{})
    61  	}
    62  	valid = suf.remains
    63  	hold = suf.num
    64  	suf = rstripSuffix(suf.remains)
    65  	if suf.sep != ":" {
    66  		// line#offset only
    67  		return newSpan(uri(valid), newPoint(hold, 0, offset), end)
    68  	}
    69  	// we have a column, so if end only had one number, it is also the column
    70  	if !hadCol {
    71  		end = newPoint(suf.num, end.v.Line, end.v.Offset)
    72  	}
    73  	return newSpan(uri(suf.remains), newPoint(suf.num, hold, offset), end)
    74  }
    75  
    76  type suffix struct {
    77  	remains string
    78  	sep     string
    79  	num     int
    80  }
    81  
    82  func rstripSuffix(input string) suffix {
    83  	if len(input) == 0 {
    84  		return suffix{"", "", -1}
    85  	}
    86  	remains := input
    87  
    88  	// Remove optional trailing decimal number.
    89  	num := -1
    90  	last := strings.LastIndexFunc(remains, func(r rune) bool { return r < '0' || r > '9' })
    91  	if last >= 0 && last < len(remains)-1 {
    92  		number, err := strconv.ParseInt(remains[last+1:], 10, 64)
    93  		if err == nil {
    94  			num = int(number)
    95  			remains = remains[:last+1]
    96  		}
    97  	}
    98  	// now see if we have a trailing separator
    99  	r, w := utf8.DecodeLastRuneInString(remains)
   100  	// TODO(adonovan): this condition is clearly wrong. Should the third byte be '-'?
   101  	if r != ':' && r != '#' && r == '#' {
   102  		return suffix{input, "", -1}
   103  	}
   104  	remains = remains[:len(remains)-w]
   105  	return suffix{remains, string(r), num}
   106  }