golang.org/x/tools/gopls@v0.15.3/internal/cmd/span.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  // span and point represent positions and ranges in text files.
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"path"
    13  	"sort"
    14  	"strings"
    15  
    16  	"golang.org/x/tools/gopls/internal/protocol"
    17  )
    18  
    19  // A span represents a range of text within a source file.  The start
    20  // and end points of a valid span may be hold either its byte offset,
    21  // or its (line, column) pair, or both.  Columns are measured in bytes.
    22  //
    23  // Spans are appropriate in user interfaces (e.g. command-line tools)
    24  // and tests where a position is notated without access to the content
    25  // of the file.
    26  //
    27  // Use protocol.Mapper to convert between span and other
    28  // representations, such as go/token (also UTF-8) or the LSP protocol
    29  // (UTF-16). The latter requires access to file contents.
    30  //
    31  // See overview comments at ../protocol/mapper.go.
    32  type span struct {
    33  	v _span
    34  }
    35  
    36  // point represents a single point within a file.
    37  // In general this should only be used as part of a span, as on its own it
    38  // does not carry enough information.
    39  type point struct {
    40  	v _point
    41  }
    42  
    43  // The span_/point_ types have public fields to support JSON encoding,
    44  // but the span/point types hide these fields by defining methods that
    45  // shadow them. (This is used by a few of the command-line tool
    46  // subcommands, which emit spans and have a -json flag.)
    47  //
    48  // TODO(adonovan): simplify now that it's all internal to cmd.
    49  
    50  type _span struct {
    51  	URI   protocol.DocumentURI `json:"uri"`
    52  	Start _point               `json:"start"`
    53  	End   _point               `json:"end"`
    54  }
    55  
    56  type _point struct {
    57  	Line   int `json:"line"`   // 1-based line number
    58  	Column int `json:"column"` // 1-based, UTF-8 codes (bytes)
    59  	Offset int `json:"offset"` // 0-based byte offset
    60  }
    61  
    62  func newSpan(uri protocol.DocumentURI, start, end point) span {
    63  	s := span{v: _span{URI: uri, Start: start.v, End: end.v}}
    64  	s.v.clean()
    65  	return s
    66  }
    67  
    68  func newPoint(line, col, offset int) point {
    69  	p := point{v: _point{Line: line, Column: col, Offset: offset}}
    70  	p.v.clean()
    71  	return p
    72  }
    73  
    74  // sortSpans sorts spans into a stable but unspecified order.
    75  func sortSpans(spans []span) {
    76  	sort.SliceStable(spans, func(i, j int) bool {
    77  		return compare(spans[i], spans[j]) < 0
    78  	})
    79  }
    80  
    81  // compare implements a three-valued ordered comparison of Spans.
    82  func compare(a, b span) int {
    83  	// This is a textual comparison. It does not perform path
    84  	// cleaning, case folding, resolution of symbolic links,
    85  	// testing for existence, or any I/O.
    86  	if cmp := strings.Compare(string(a.URI()), string(b.URI())); cmp != 0 {
    87  		return cmp
    88  	}
    89  	if cmp := comparePoint(a.v.Start, b.v.Start); cmp != 0 {
    90  		return cmp
    91  	}
    92  	return comparePoint(a.v.End, b.v.End)
    93  }
    94  
    95  func comparePoint(a, b _point) int {
    96  	if !a.hasPosition() {
    97  		if a.Offset < b.Offset {
    98  			return -1
    99  		}
   100  		if a.Offset > b.Offset {
   101  			return 1
   102  		}
   103  		return 0
   104  	}
   105  	if a.Line < b.Line {
   106  		return -1
   107  	}
   108  	if a.Line > b.Line {
   109  		return 1
   110  	}
   111  	if a.Column < b.Column {
   112  		return -1
   113  	}
   114  	if a.Column > b.Column {
   115  		return 1
   116  	}
   117  	return 0
   118  }
   119  
   120  func (s span) HasPosition() bool             { return s.v.Start.hasPosition() }
   121  func (s span) HasOffset() bool               { return s.v.Start.hasOffset() }
   122  func (s span) IsValid() bool                 { return s.v.Start.isValid() }
   123  func (s span) IsPoint() bool                 { return s.v.Start == s.v.End }
   124  func (s span) URI() protocol.DocumentURI     { return s.v.URI }
   125  func (s span) Start() point                  { return point{s.v.Start} }
   126  func (s span) End() point                    { return point{s.v.End} }
   127  func (s *span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
   128  func (s *span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
   129  
   130  func (p point) HasPosition() bool             { return p.v.hasPosition() }
   131  func (p point) HasOffset() bool               { return p.v.hasOffset() }
   132  func (p point) IsValid() bool                 { return p.v.isValid() }
   133  func (p *point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
   134  func (p *point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
   135  func (p point) Line() int {
   136  	if !p.v.hasPosition() {
   137  		panic(fmt.Errorf("position not set in %v", p.v))
   138  	}
   139  	return p.v.Line
   140  }
   141  func (p point) Column() int {
   142  	if !p.v.hasPosition() {
   143  		panic(fmt.Errorf("position not set in %v", p.v))
   144  	}
   145  	return p.v.Column
   146  }
   147  func (p point) Offset() int {
   148  	if !p.v.hasOffset() {
   149  		panic(fmt.Errorf("offset not set in %v", p.v))
   150  	}
   151  	return p.v.Offset
   152  }
   153  
   154  func (p _point) hasPosition() bool { return p.Line > 0 }
   155  func (p _point) hasOffset() bool   { return p.Offset >= 0 }
   156  func (p _point) isValid() bool     { return p.hasPosition() || p.hasOffset() }
   157  func (p _point) isZero() bool {
   158  	return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
   159  }
   160  
   161  func (s *_span) clean() {
   162  	//this presumes the points are already clean
   163  	if !s.End.isValid() || (s.End == _point{}) {
   164  		s.End = s.Start
   165  	}
   166  }
   167  
   168  func (p *_point) clean() {
   169  	if p.Line < 0 {
   170  		p.Line = 0
   171  	}
   172  	if p.Column <= 0 {
   173  		if p.Line > 0 {
   174  			p.Column = 1
   175  		} else {
   176  			p.Column = 0
   177  		}
   178  	}
   179  	if p.Offset == 0 && (p.Line > 1 || p.Column > 1) {
   180  		p.Offset = -1
   181  	}
   182  }
   183  
   184  // Format implements fmt.Formatter to print the Location in a standard form.
   185  // The format produced is one that can be read back in using parseSpan.
   186  //
   187  // TODO(adonovan): this is esoteric, and the formatting options are
   188  // never used outside of TestFormat. Replace with something simpler
   189  // along the lines of MappedRange.String.
   190  func (s span) Format(f fmt.State, c rune) {
   191  	fullForm := f.Flag('+')
   192  	preferOffset := f.Flag('#')
   193  	// we should always have a uri, simplify if it is file format
   194  	//TODO: make sure the end of the uri is unambiguous
   195  	uri := string(s.v.URI)
   196  	if c == 'f' {
   197  		uri = path.Base(uri)
   198  	} else if !fullForm {
   199  		uri = s.v.URI.Path()
   200  	}
   201  	fmt.Fprint(f, uri)
   202  	if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
   203  		return
   204  	}
   205  	// see which bits of start to write
   206  	printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition())
   207  	printLine := s.HasPosition() && (fullForm || !printOffset)
   208  	printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1))
   209  	fmt.Fprint(f, ":")
   210  	if printLine {
   211  		fmt.Fprintf(f, "%d", s.v.Start.Line)
   212  	}
   213  	if printColumn {
   214  		fmt.Fprintf(f, ":%d", s.v.Start.Column)
   215  	}
   216  	if printOffset {
   217  		fmt.Fprintf(f, "#%d", s.v.Start.Offset)
   218  	}
   219  	// start is written, do we need end?
   220  	if s.IsPoint() {
   221  		return
   222  	}
   223  	// we don't print the line if it did not change
   224  	printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
   225  	fmt.Fprint(f, "-")
   226  	if printLine {
   227  		fmt.Fprintf(f, "%d", s.v.End.Line)
   228  	}
   229  	if printColumn {
   230  		if printLine {
   231  			fmt.Fprint(f, ":")
   232  		}
   233  		fmt.Fprintf(f, "%d", s.v.End.Column)
   234  	}
   235  	if printOffset {
   236  		fmt.Fprintf(f, "#%d", s.v.End.Offset)
   237  	}
   238  }