github.com/v2fly/tools@v0.100.0/internal/span/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 span contains support for representing with positions and ranges in 6 // text files. 7 package span 8 9 import ( 10 "encoding/json" 11 "fmt" 12 "path" 13 ) 14 15 // Span represents a source code range in standardized form. 16 type Span struct { 17 v span 18 } 19 20 // Point represents a single point within a file. 21 // In general this should only be used as part of a Span, as on its own it 22 // does not carry enough information. 23 type Point struct { 24 v point 25 } 26 27 type span struct { 28 URI URI `json:"uri"` 29 Start point `json:"start"` 30 End point `json:"end"` 31 } 32 33 type point struct { 34 Line int `json:"line"` 35 Column int `json:"column"` 36 Offset int `json:"offset"` 37 } 38 39 // Invalid is a span that reports false from IsValid 40 var Invalid = Span{v: span{Start: invalidPoint.v, End: invalidPoint.v}} 41 42 var invalidPoint = Point{v: point{Line: 0, Column: 0, Offset: -1}} 43 44 // Converter is the interface to an object that can convert between line:column 45 // and offset forms for a single file. 46 type Converter interface { 47 //ToPosition converts from an offset to a line:column pair. 48 ToPosition(offset int) (int, int, error) 49 //ToOffset converts from a line:column pair to an offset. 50 ToOffset(line, col int) (int, error) 51 } 52 53 func New(uri URI, start Point, end Point) Span { 54 s := Span{v: span{URI: uri, Start: start.v, End: end.v}} 55 s.v.clean() 56 return s 57 } 58 59 func NewPoint(line, col, offset int) Point { 60 p := Point{v: point{Line: line, Column: col, Offset: offset}} 61 p.v.clean() 62 return p 63 } 64 65 func Compare(a, b Span) int { 66 if r := CompareURI(a.URI(), b.URI()); r != 0 { 67 return r 68 } 69 if r := comparePoint(a.v.Start, b.v.Start); r != 0 { 70 return r 71 } 72 return comparePoint(a.v.End, b.v.End) 73 } 74 75 func ComparePoint(a, b Point) int { 76 return comparePoint(a.v, b.v) 77 } 78 79 func comparePoint(a, b point) int { 80 if !a.hasPosition() { 81 if a.Offset < b.Offset { 82 return -1 83 } 84 if a.Offset > b.Offset { 85 return 1 86 } 87 return 0 88 } 89 if a.Line < b.Line { 90 return -1 91 } 92 if a.Line > b.Line { 93 return 1 94 } 95 if a.Column < b.Column { 96 return -1 97 } 98 if a.Column > b.Column { 99 return 1 100 } 101 return 0 102 } 103 104 func (s Span) HasPosition() bool { return s.v.Start.hasPosition() } 105 func (s Span) HasOffset() bool { return s.v.Start.hasOffset() } 106 func (s Span) IsValid() bool { return s.v.Start.isValid() } 107 func (s Span) IsPoint() bool { return s.v.Start == s.v.End } 108 func (s Span) URI() URI { return s.v.URI } 109 func (s Span) Start() Point { return Point{s.v.Start} } 110 func (s Span) End() Point { return Point{s.v.End} } 111 func (s *Span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) } 112 func (s *Span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) } 113 114 func (p Point) HasPosition() bool { return p.v.hasPosition() } 115 func (p Point) HasOffset() bool { return p.v.hasOffset() } 116 func (p Point) IsValid() bool { return p.v.isValid() } 117 func (p *Point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) } 118 func (p *Point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) } 119 func (p Point) Line() int { 120 if !p.v.hasPosition() { 121 panic(fmt.Errorf("position not set in %v", p.v)) 122 } 123 return p.v.Line 124 } 125 func (p Point) Column() int { 126 if !p.v.hasPosition() { 127 panic(fmt.Errorf("position not set in %v", p.v)) 128 } 129 return p.v.Column 130 } 131 func (p Point) Offset() int { 132 if !p.v.hasOffset() { 133 panic(fmt.Errorf("offset not set in %v", p.v)) 134 } 135 return p.v.Offset 136 } 137 138 func (p point) hasPosition() bool { return p.Line > 0 } 139 func (p point) hasOffset() bool { return p.Offset >= 0 } 140 func (p point) isValid() bool { return p.hasPosition() || p.hasOffset() } 141 func (p point) isZero() bool { 142 return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0) 143 } 144 145 func (s *span) clean() { 146 //this presumes the points are already clean 147 if !s.End.isValid() || (s.End == point{}) { 148 s.End = s.Start 149 } 150 } 151 152 func (p *point) clean() { 153 if p.Line < 0 { 154 p.Line = 0 155 } 156 if p.Column <= 0 { 157 if p.Line > 0 { 158 p.Column = 1 159 } else { 160 p.Column = 0 161 } 162 } 163 if p.Offset == 0 && (p.Line > 1 || p.Column > 1) { 164 p.Offset = -1 165 } 166 } 167 168 // Format implements fmt.Formatter to print the Location in a standard form. 169 // The format produced is one that can be read back in using Parse. 170 func (s Span) Format(f fmt.State, c rune) { 171 fullForm := f.Flag('+') 172 preferOffset := f.Flag('#') 173 // we should always have a uri, simplify if it is file format 174 //TODO: make sure the end of the uri is unambiguous 175 uri := string(s.v.URI) 176 if c == 'f' { 177 uri = path.Base(uri) 178 } else if !fullForm { 179 uri = s.v.URI.Filename() 180 } 181 fmt.Fprint(f, uri) 182 if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) { 183 return 184 } 185 // see which bits of start to write 186 printOffset := s.HasOffset() && (fullForm || preferOffset || !s.HasPosition()) 187 printLine := s.HasPosition() && (fullForm || !printOffset) 188 printColumn := printLine && (fullForm || (s.v.Start.Column > 1 || s.v.End.Column > 1)) 189 fmt.Fprint(f, ":") 190 if printLine { 191 fmt.Fprintf(f, "%d", s.v.Start.Line) 192 } 193 if printColumn { 194 fmt.Fprintf(f, ":%d", s.v.Start.Column) 195 } 196 if printOffset { 197 fmt.Fprintf(f, "#%d", s.v.Start.Offset) 198 } 199 // start is written, do we need end? 200 if s.IsPoint() { 201 return 202 } 203 // we don't print the line if it did not change 204 printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line) 205 fmt.Fprint(f, "-") 206 if printLine { 207 fmt.Fprintf(f, "%d", s.v.End.Line) 208 } 209 if printColumn { 210 if printLine { 211 fmt.Fprint(f, ":") 212 } 213 fmt.Fprintf(f, "%d", s.v.End.Column) 214 } 215 if printOffset { 216 fmt.Fprintf(f, "#%d", s.v.End.Offset) 217 } 218 } 219 220 func (s Span) WithPosition(c Converter) (Span, error) { 221 if err := s.update(c, true, false); err != nil { 222 return Span{}, err 223 } 224 return s, nil 225 } 226 227 func (s Span) WithOffset(c Converter) (Span, error) { 228 if err := s.update(c, false, true); err != nil { 229 return Span{}, err 230 } 231 return s, nil 232 } 233 234 func (s Span) WithAll(c Converter) (Span, error) { 235 if err := s.update(c, true, true); err != nil { 236 return Span{}, err 237 } 238 return s, nil 239 } 240 241 func (s *Span) update(c Converter, withPos, withOffset bool) error { 242 if !s.IsValid() { 243 return fmt.Errorf("cannot add information to an invalid span") 244 } 245 if withPos && !s.HasPosition() { 246 if err := s.v.Start.updatePosition(c); err != nil { 247 return err 248 } 249 if s.v.End.Offset == s.v.Start.Offset { 250 s.v.End = s.v.Start 251 } else if err := s.v.End.updatePosition(c); err != nil { 252 return err 253 } 254 } 255 if withOffset && (!s.HasOffset() || (s.v.End.hasPosition() && !s.v.End.hasOffset())) { 256 if err := s.v.Start.updateOffset(c); err != nil { 257 return err 258 } 259 if s.v.End.Line == s.v.Start.Line && s.v.End.Column == s.v.Start.Column { 260 s.v.End.Offset = s.v.Start.Offset 261 } else if err := s.v.End.updateOffset(c); err != nil { 262 return err 263 } 264 } 265 return nil 266 } 267 268 func (p *point) updatePosition(c Converter) error { 269 line, col, err := c.ToPosition(p.Offset) 270 if err != nil { 271 return err 272 } 273 p.Line = line 274 p.Column = col 275 return nil 276 } 277 278 func (p *point) updateOffset(c Converter) error { 279 offset, err := c.ToOffset(p.Line, p.Column) 280 if err != nil { 281 return err 282 } 283 p.Offset = offset 284 return nil 285 }