github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/lsp/lsppos/lsppos.go (about) 1 // Copyright 2021 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 lsppos provides utilities for working with LSP positions. 6 // 7 // See https://microsoft.github.io/language-server-protocol/specification#textDocuments 8 // for a description of LSP positions. Notably: 9 // - Positions are specified by a 0-based line count and 0-based utf-16 10 // character offset. 11 // - Positions are line-ending agnostic: there is no way to specify \r|\n or 12 // \n|. Instead the former maps to the end of the current line, and the 13 // latter to the start of the next line. 14 package lsppos 15 16 import ( 17 "sort" 18 "unicode/utf8" 19 ) 20 21 type Mapper struct { 22 nonASCII bool 23 src []byte 24 25 // Start-of-line positions. If src is newline-terminated, the final entry will be empty. 26 lines []int 27 } 28 29 func NewMapper(src []byte) *Mapper { 30 m := &Mapper{src: src} 31 if len(src) == 0 { 32 return m 33 } 34 m.lines = []int{0} 35 for offset, b := range src { 36 if b == '\n' { 37 m.lines = append(m.lines, offset+1) 38 } 39 if b >= utf8.RuneSelf { 40 m.nonASCII = true 41 } 42 } 43 return m 44 } 45 46 func (m *Mapper) Position(offset int) (line, char int) { 47 if offset < 0 || offset > len(m.src) { 48 return -1, -1 49 } 50 nextLine := sort.Search(len(m.lines), func(i int) bool { 51 return offset < m.lines[i] 52 }) 53 if nextLine == 0 { 54 return -1, -1 55 } 56 line = nextLine - 1 57 start := m.lines[line] 58 var charOffset int 59 if m.nonASCII { 60 charOffset = UTF16len(m.src[start:offset]) 61 } else { 62 charOffset = offset - start 63 } 64 65 var eol int 66 if line == len(m.lines)-1 { 67 eol = len(m.src) 68 } else { 69 eol = m.lines[line+1] - 1 70 } 71 72 // Adjustment for line-endings: \r|\n is the same as |\r\n. 73 if offset == eol && offset > 0 && m.src[offset-1] == '\r' { 74 charOffset-- 75 } 76 77 return line, charOffset 78 } 79 80 func UTF16len(buf []byte) int { 81 cnt := 0 82 for _, r := range string(buf) { 83 cnt++ 84 if r >= 1<<16 { 85 cnt++ 86 } 87 } 88 return cnt 89 }