| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package span |
| |
| import ( |
| "fmt" |
| ) |
| |
| // Span represents a source code range in standardized form. |
| type Span struct { |
| URI URI `json:"uri"` |
| Start Point `json:"start"` |
| End Point `json:"end"` |
| } |
| |
| // Point represents a single point within a file. |
| // In general this should only be used as part of a Span, as on its own it |
| // does not carry enough information. |
| type Point struct { |
| Line int `json:"line"` |
| Column int `json:"column"` |
| Offset int `json:"offset"` |
| } |
| |
| // Offsets is the interface to an object that can convert to offset |
| // from line:column forms for a single file. |
| type Offsets interface { |
| ToOffset(line, col int) int |
| } |
| |
| // Coords is the interface to an object that can convert to line:column |
| // from offset forms for a single file. |
| type Coords interface { |
| ToCoord(offset int) (int, int) |
| } |
| |
| // Converter is the interface to an object that can convert between line:column |
| // and offset forms for a single file. |
| type Converter interface { |
| Offsets |
| Coords |
| } |
| |
| // Format implements fmt.Formatter to print the Location in a standard form. |
| // The format produced is one that can be read back in using Parse. |
| func (s Span) Format(f fmt.State, c rune) { |
| fullForm := f.Flag('+') |
| preferOffset := f.Flag('#') |
| // we should always have a uri, simplify if it is file format |
| //TODO: make sure the end of the uri is unambiguous |
| uri := string(s.URI) |
| if !fullForm { |
| if filename, err := s.URI.Filename(); err == nil { |
| uri = filename |
| } |
| } |
| fmt.Fprint(f, uri) |
| // see which bits of start to write |
| printOffset := fullForm || (s.Start.Offset > 0 && (preferOffset || s.Start.Line <= 0)) |
| printLine := fullForm || (s.Start.Line > 0 && !printOffset) |
| printColumn := fullForm || (printLine && (s.Start.Column > 1 || s.End.Column > 1)) |
| if !printLine && !printColumn && !printOffset { |
| return |
| } |
| fmt.Fprint(f, ":") |
| if printLine { |
| fmt.Fprintf(f, "%d", clamp(s.Start.Line)) |
| } |
| if printColumn { |
| fmt.Fprintf(f, ":%d", clamp(s.Start.Column)) |
| } |
| if printOffset { |
| fmt.Fprintf(f, "#%d", clamp(s.Start.Offset)) |
| } |
| // start is written, do we need end? |
| printLine = fullForm || (printLine && s.End.Line > s.Start.Line) |
| isPoint := s.End.Line == s.Start.Line && s.End.Column == s.Start.Column |
| printColumn = fullForm || (printColumn && s.End.Column > 0 && !isPoint) |
| printOffset = fullForm || (printOffset && s.End.Offset > s.Start.Offset) |
| if !printLine && !printColumn && !printOffset { |
| return |
| } |
| fmt.Fprint(f, "-") |
| if printLine { |
| fmt.Fprintf(f, "%d", clamp(s.End.Line)) |
| } |
| if printColumn { |
| if printLine { |
| fmt.Fprint(f, ":") |
| } |
| fmt.Fprintf(f, "%d", clamp(s.End.Column)) |
| } |
| if printOffset { |
| fmt.Fprintf(f, "#%d", clamp(s.End.Offset)) |
| } |
| } |
| |
| // CleanOffset returns a copy of the Span with the Offset field updated. |
| // If the field is missing and Offsets is supplied it will be used to |
| // calculate it from the line and column. |
| // The value will then be adjusted to the canonical form. |
| func (s Span) CleanOffset(offsets Offsets) Span { |
| if offsets != nil { |
| if (s.Start.Line > 1 || s.Start.Column > 1) && s.Start.Offset == 0 { |
| s.Start.updateOffset(offsets) |
| } |
| if (s.End.Line > 1 || s.End.Column > 1) && s.End.Offset == 0 { |
| s.End.updateOffset(offsets) |
| } |
| } |
| if s.Start.Offset < 0 { |
| s.Start.Offset = 0 |
| } |
| if s.End.Offset <= s.Start.Offset { |
| s.End.Offset = s.Start.Offset |
| } |
| return s |
| } |
| |
| // CleanCoords returns a copy of the Span with the Line and Column fields |
| // cleaned. |
| // If the fields are missing and Coords is supplied it will be used to |
| // calculate them from the offset. |
| // The values will then be adjusted to the canonical form. |
| func (s Span) CleanCoords(coords Coords) Span { |
| if coords != nil { |
| if s.Start.Line == 0 && s.Start.Offset > 0 { |
| s.Start.Line, s.Start.Column = coords.ToCoord(s.Start.Offset) |
| } |
| if s.End.Line == 0 && s.End.Offset > 0 { |
| s.End.Line, s.End.Column = coords.ToCoord(s.End.Offset) |
| } |
| } |
| if s.Start.Line <= 0 { |
| s.Start.Line = 0 |
| } |
| if s.Start.Line == 0 { |
| s.Start.Column = 0 |
| } else if s.Start.Column <= 0 { |
| s.Start.Column = 0 |
| } |
| if s.End.Line < s.Start.Line { |
| s.End.Line = s.Start.Line |
| } |
| if s.End.Column < s.Start.Column { |
| s.End.Column = s.Start.Column |
| } |
| if s.Start.Column <= 1 && s.End.Column <= 1 { |
| s.Start.Column = 0 |
| s.End.Column = 0 |
| } |
| if s.Start.Line <= 1 && s.End.Line <= 1 && s.Start.Column <= 1 && s.End.Column <= 1 { |
| s.Start.Line = 0 |
| s.End.Line = 0 |
| } |
| return s |
| } |
| |
| // Clean returns a copy of the Span fully normalized. |
| // If passed a converter, it will use it to fill in any missing fields by |
| // converting between offset and line column fields. |
| // It does not attempt to validate that already filled fields have consistent |
| // values. |
| func (s Span) Clean(converter Converter) Span { |
| s = s.CleanOffset(converter) |
| s = s.CleanCoords(converter) |
| if s.End.Offset == 0 { |
| // in case CleanCoords adjusted the end position |
| s.End.Offset = s.Start.Offset |
| } |
| return s |
| } |
| |
| // IsPoint returns true if the span represents a single point. |
| // It is only valid on spans that are "clean". |
| func (s Span) IsPoint() bool { |
| return s.Start == s.End |
| } |
| |
| func (p *Point) updateOffset(offsets Offsets) { |
| p.Offset = 0 |
| if p.Line <= 0 { |
| return |
| } |
| c := p.Column |
| if c < 1 { |
| c = 1 |
| } |
| if p.Line == 1 && c == 1 { |
| return |
| } |
| p.Offset = offsets.ToOffset(p.Line, c) |
| } |
| |
| func clamp(v int) int { |
| if v < 0 { |
| return 0 |
| } |
| return v |
| } |