blob: fe8f9477321a955a945ad39ed45ccd3d647a9018 [file] [log] [blame]
Ian Cottrellb40df0f2019-02-12 13:13:49 -05001// 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
Alan Donovan3292b362023-11-14 01:08:40 -05005package cmd
6
Alan Donovan057c0992023-11-16 13:47:55 -05007// span and point represent positions and ranges in text files.
Ian Cottrellb40df0f2019-02-12 13:13:49 -05008
9import (
Ian Cottrell2f43c6d2019-03-15 13:19:43 -040010 "encoding/json"
Ian Cottrellb40df0f2019-02-12 13:13:49 -050011 "fmt"
Ian Cottrell83df1962019-05-02 13:04:19 -040012 "path"
Alan Donovand54e12b2022-11-30 18:11:33 -050013 "sort"
14 "strings"
Alan Donovan81e741e2022-12-28 10:49:34 -050015
Alan Donovan057c0992023-11-16 13:47:55 -050016 "golang.org/x/tools/gopls/internal/lsp/protocol"
Ian Cottrellb40df0f2019-02-12 13:13:49 -050017)
18
Alan Donovan057c0992023-11-16 13:47:55 -050019// A span represents a range of text within a source file. The start
Alan Donovan55935f42023-01-08 10:56:46 -050020// 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//
Alan Donovan057c0992023-11-16 13:47:55 -050027// Use protocol.Mapper to convert between span and other
Alan Donovan55935f42023-01-08 10:56:46 -050028// representations, such as go/token (also UTF-8) or the LSP protocol
29// (UTF-16). The latter requires access to file contents.
Alan Donovanb7989342023-01-10 21:55:45 -050030//
31// See overview comments at ../lsp/protocol/mapper.go.
Alan Donovan057c0992023-11-16 13:47:55 -050032type span struct {
Alan Donovan3292b362023-11-14 01:08:40 -050033 v _span
Ian Cottrellb40df0f2019-02-12 13:13:49 -050034}
35
Alan Donovan3292b362023-11-14 01:08:40 -050036// point represents a single point within a file.
Alan Donovan057c0992023-11-16 13:47:55 -050037// In general this should only be used as part of a span, as on its own it
Ian Cottrellb40df0f2019-02-12 13:13:49 -050038// does not carry enough information.
Alan Donovan3292b362023-11-14 01:08:40 -050039type point struct {
40 v _point
Ian Cottrell2f43c6d2019-03-15 13:19:43 -040041}
42
Alan Donovan057c0992023-11-16 13:47:55 -050043// 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//
Alan Donovan3292b362023-11-14 01:08:40 -050048// TODO(adonovan): simplify now that it's all internal to cmd.
49
50type _span struct {
Alan Donovan057c0992023-11-16 13:47:55 -050051 URI protocol.DocumentURI `json:"uri"`
52 Start _point `json:"start"`
53 End _point `json:"end"`
Ian Cottrell2f43c6d2019-03-15 13:19:43 -040054}
55
Alan Donovan3292b362023-11-14 01:08:40 -050056type _point struct {
Alan Donovan02bea032023-01-04 18:52:54 -050057 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
Ian Cottrellb40df0f2019-02-12 13:13:49 -050060}
61
Alan Donovan057c0992023-11-16 13:47:55 -050062func newSpan(uri protocol.DocumentURI, start, end point) span {
63 s := span{v: _span{URI: uri, Start: start.v, End: end.v}}
Ian Cottrell2f43c6d2019-03-15 13:19:43 -040064 s.v.clean()
65 return s
66}
67
Alan Donovan3292b362023-11-14 01:08:40 -050068func newPoint(line, col, offset int) point {
69 p := point{v: _point{Line: line, Column: col, Offset: offset}}
Ian Cottrell2f43c6d2019-03-15 13:19:43 -040070 p.v.clean()
71 return p
72}
73
Alan Donovan3292b362023-11-14 01:08:40 -050074// sortSpans sorts spans into a stable but unspecified order.
Alan Donovan057c0992023-11-16 13:47:55 -050075func sortSpans(spans []span) {
Alan Donovand54e12b2022-11-30 18:11:33 -050076 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.
Alan Donovan057c0992023-11-16 13:47:55 -050082func compare(a, b span) int {
Oleksandr Redko6b6857a2023-02-06 16:18:32 +000083 // This is a textual comparison. It does not perform path
Alan Donovand54e12b2022-11-30 18:11:33 -050084 // 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
Ian Cottrell183b6882019-04-17 18:01:48 -040088 }
Alan Donovand54e12b2022-11-30 18:11:33 -050089 if cmp := comparePoint(a.v.Start, b.v.Start); cmp != 0 {
90 return cmp
Ian Cottrell183b6882019-04-17 18:01:48 -040091 }
92 return comparePoint(a.v.End, b.v.End)
93}
94
Alan Donovan3292b362023-11-14 01:08:40 -050095func comparePoint(a, b _point) int {
Ian Cottrell183b6882019-04-17 18:01:48 -040096 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
Alan Donovan057c0992023-11-16 13:47:55 -0500120func (s span) HasPosition() bool { return s.v.Start.hasPosition() }
121func (s span) HasOffset() bool { return s.v.Start.hasOffset() }
122func (s span) IsValid() bool { return s.v.Start.isValid() }
123func (s span) IsPoint() bool { return s.v.Start == s.v.End }
124func (s span) URI() protocol.DocumentURI { return s.v.URI }
125func (s span) Start() point { return point{s.v.Start} }
126func (s span) End() point { return point{s.v.End} }
127func (s *span) MarshalJSON() ([]byte, error) { return json.Marshal(&s.v) }
128func (s *span) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &s.v) }
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400129
Alan Donovan3292b362023-11-14 01:08:40 -0500130func (p point) HasPosition() bool { return p.v.hasPosition() }
131func (p point) HasOffset() bool { return p.v.hasOffset() }
132func (p point) IsValid() bool { return p.v.isValid() }
133func (p *point) MarshalJSON() ([]byte, error) { return json.Marshal(&p.v) }
134func (p *point) UnmarshalJSON(b []byte) error { return json.Unmarshal(b, &p.v) }
135func (p point) Line() int {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400136 if !p.v.hasPosition() {
137 panic(fmt.Errorf("position not set in %v", p.v))
138 }
139 return p.v.Line
140}
Alan Donovan3292b362023-11-14 01:08:40 -0500141func (p point) Column() int {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400142 if !p.v.hasPosition() {
143 panic(fmt.Errorf("position not set in %v", p.v))
144 }
145 return p.v.Column
146}
Alan Donovan3292b362023-11-14 01:08:40 -0500147func (p point) Offset() int {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400148 if !p.v.hasOffset() {
149 panic(fmt.Errorf("offset not set in %v", p.v))
150 }
151 return p.v.Offset
152}
153
Alan Donovan3292b362023-11-14 01:08:40 -0500154func (p _point) hasPosition() bool { return p.Line > 0 }
155func (p _point) hasOffset() bool { return p.Offset >= 0 }
156func (p _point) isValid() bool { return p.hasPosition() || p.hasOffset() }
157func (p _point) isZero() bool {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400158 return (p.Line == 1 && p.Column == 1) || (!p.hasPosition() && p.Offset == 0)
159}
160
Alan Donovan3292b362023-11-14 01:08:40 -0500161func (s *_span) clean() {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400162 //this presumes the points are already clean
Alan Donovan3292b362023-11-14 01:08:40 -0500163 if !s.End.isValid() || (s.End == _point{}) {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400164 s.End = s.Start
165 }
166}
167
Alan Donovan3292b362023-11-14 01:08:40 -0500168func (p *_point) clean() {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400169 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 }
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500182}
183
184// Format implements fmt.Formatter to print the Location in a standard form.
Alan Donovan3292b362023-11-14 01:08:40 -0500185// 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.
Alan Donovan057c0992023-11-16 13:47:55 -0500190func (s span) Format(f fmt.State, c rune) {
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500191 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
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400195 uri := string(s.v.URI)
Ian Cottrell83df1962019-05-02 13:04:19 -0400196 if c == 'f' {
197 uri = path.Base(uri)
198 } else if !fullForm {
Alan Donovan1cab1272023-11-17 16:16:59 -0500199 uri = s.v.URI.Path()
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500200 }
201 fmt.Fprint(f, uri)
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400202 if !s.IsValid() || (!fullForm && s.v.Start.isZero() && s.v.End.isZero()) {
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500203 return
204 }
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400205 // 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))
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500209 fmt.Fprint(f, ":")
210 if printLine {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400211 fmt.Fprintf(f, "%d", s.v.Start.Line)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500212 }
213 if printColumn {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400214 fmt.Fprintf(f, ":%d", s.v.Start.Column)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500215 }
216 if printOffset {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400217 fmt.Fprintf(f, "#%d", s.v.Start.Offset)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500218 }
219 // start is written, do we need end?
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400220 if s.IsPoint() {
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500221 return
222 }
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400223 // we don't print the line if it did not change
224 printLine = fullForm || (printLine && s.v.End.Line > s.v.Start.Line)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500225 fmt.Fprint(f, "-")
226 if printLine {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400227 fmt.Fprintf(f, "%d", s.v.End.Line)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500228 }
229 if printColumn {
230 if printLine {
231 fmt.Fprint(f, ":")
232 }
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400233 fmt.Fprintf(f, "%d", s.v.End.Column)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500234 }
235 if printOffset {
Ian Cottrell2f43c6d2019-03-15 13:19:43 -0400236 fmt.Fprintf(f, "#%d", s.v.End.Offset)
Ian Cottrellb40df0f2019-02-12 13:13:49 -0500237 }
238}