| // 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" |
| "unicode/utf8" |
| ) |
| |
| // ToUTF16Column calculates the utf16 column expressed by the point given the |
| // supplied file contents. |
| // This is used to convert from the native (always in bytes) column |
| // representation and the utf16 counts used by some editors. |
| func ToUTF16Column(p Point, content []byte) (int, error) { |
| if !p.HasPosition() { |
| return -1, fmt.Errorf("ToUTF16Column: point is missing position") |
| } |
| if !p.HasOffset() { |
| return -1, fmt.Errorf("ToUTF16Column: point is missing offset") |
| } |
| offset := p.Offset() // 0-based |
| colZero := p.Column() - 1 // 0-based |
| if colZero == 0 { |
| // 0-based column 0, so it must be chr 1 |
| return 1, nil |
| } else if colZero < 0 { |
| return -1, fmt.Errorf("ToUTF16Column: column is invalid (%v)", colZero) |
| } |
| // work out the offset at the start of the line using the column |
| lineOffset := offset - colZero |
| if lineOffset < 0 || offset > len(content) { |
| return -1, fmt.Errorf("ToUTF16Column: offsets %v-%v outside file contents (%v)", lineOffset, offset, len(content)) |
| } |
| // Use the offset to pick out the line start. |
| // This cannot panic: offset > len(content) and lineOffset < offset. |
| start := content[lineOffset:] |
| |
| // Now, truncate down to the supplied column. |
| start = start[:colZero] |
| |
| cnt := 0 |
| for _, r := range string(start) { |
| cnt++ |
| if r > 0xffff { |
| cnt++ |
| } |
| } |
| return cnt + 1, nil // the +1 is for 1-based columns |
| } |
| |
| // FromUTF16Column advances the point by the utf16 character offset given the |
| // supplied line contents. |
| // This is used to convert from the utf16 counts used by some editors to the |
| // native (always in bytes) column representation. |
| // |
| // The resulting Point always has an offset. |
| // |
| // TODO: it looks like this may incorrectly confer a "position" to the |
| // resulting Point, when it shouldn't. If p.HasPosition() == false, the |
| // resulting Point will return p.HasPosition() == true, but have the wrong |
| // position. |
| func FromUTF16Column(p Point, chr int, content []byte) (Point, error) { |
| if !p.HasOffset() { |
| return Point{}, fmt.Errorf("FromUTF16Column: point is missing offset") |
| } |
| // if chr is 1 then no adjustment needed |
| if chr <= 1 { |
| return p, nil |
| } |
| if p.Offset() >= len(content) { |
| return p, fmt.Errorf("FromUTF16Column: offset (%v) greater than length of content (%v)", p.Offset(), len(content)) |
| } |
| remains := content[p.Offset():] |
| // scan forward the specified number of characters |
| for count := 1; count < chr; count++ { |
| if len(remains) <= 0 { |
| return Point{}, fmt.Errorf("FromUTF16Column: chr goes beyond the content") |
| } |
| r, w := utf8.DecodeRune(remains) |
| if r == '\n' { |
| // Per the LSP spec: |
| // |
| // > If the character value is greater than the line length it |
| // > defaults back to the line length. |
| break |
| } |
| remains = remains[w:] |
| if r >= 0x10000 { |
| // a two point rune |
| count++ |
| // if we finished in a two point rune, do not advance past the first |
| if count >= chr { |
| break |
| } |
| } |
| p.v.Column += w |
| p.v.Offset += w |
| } |
| return p, nil |
| } |