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 }