github.com/jmigpin/editor@v1.6.0/core/lsproto/util.go (about) 1 package lsproto 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "flag" 7 "fmt" 8 "io" 9 "log" 10 "os" 11 "path/filepath" 12 "runtime" 13 "sort" 14 "strings" 15 "unicode/utf16" 16 17 "github.com/jmigpin/editor/util/iout/iorw" 18 "github.com/jmigpin/editor/util/osutil" 19 "github.com/jmigpin/editor/util/parseutil" 20 ) 21 22 //---------- 23 24 var logger0 = log.New(os.Stdout, "", log.Lshortfile) 25 26 func logTestVerbose() bool { 27 f := flag.Lookup("test.v") 28 return f != nil && f.Value.String() == "true" 29 } 30 31 func logPrintf(f string, args ...interface{}) { 32 if !logTestVerbose() { 33 return 34 } 35 logger0.Output(2, fmt.Sprintf(f, args...)) 36 } 37 38 func logJson(prefix string, v interface{}) { 39 if !logTestVerbose() { 40 return 41 } 42 b, err := json.MarshalIndent(v, "", "\t") 43 if err != nil { 44 panic(err) 45 } 46 logger0.Output(2, fmt.Sprintf("%v%v", prefix, string(b))) 47 } 48 49 //---------- 50 51 func encodeJson(a interface{}) ([]byte, error) { 52 buf := &bytes.Buffer{} 53 enc := json.NewEncoder(buf) 54 err := enc.Encode(a) 55 if err != nil { 56 return nil, err 57 } 58 b := buf.Bytes() 59 return b, nil 60 } 61 62 func decodeJson(r io.Reader, a interface{}) error { 63 dec := json.NewDecoder(r) 64 return dec.Decode(a) 65 } 66 func decodeJsonRaw(raw json.RawMessage, a interface{}) error { 67 return json.Unmarshal(raw, a) 68 } 69 70 //---------- 71 72 func Utf16Column(rd iorw.ReaderAt, lineStartOffset, utf8Col int) (int, error) { 73 b, err := rd.ReadFastAt(lineStartOffset, utf8Col) 74 if err != nil { 75 return 0, err 76 } 77 return len(utf16.Encode([]rune(string(b)))), nil 78 } 79 80 // Input and result is zero based. 81 func Utf8Column(rd iorw.ReaderAt, lineStartOffset, utf16Col int) (int, error) { 82 // ensure good limits 83 n := utf16Col * 2 84 if lineStartOffset+n > rd.Max() { 85 n = rd.Max() - lineStartOffset 86 } 87 88 b, err := rd.ReadFastAt(lineStartOffset, n) 89 if err != nil { 90 return 0, err 91 } 92 93 enc := utf16.Encode([]rune(string(b))) 94 if len(enc) < utf16Col { 95 return 0, fmt.Errorf("encoded string smaller then utf16col") 96 } 97 nthChar := len(enc[:utf16Col]) 98 99 return nthChar, nil 100 } 101 102 //---------- 103 104 func OffsetToPosition(rd iorw.ReaderAt, offset int) (Position, error) { 105 l, c, err := parseutil.IndexLineColumn(rd, offset) 106 if err != nil { 107 return Position{}, err 108 } 109 // zero based 110 l, c = l-1, c-1 111 112 // character offset in utf16 113 c2, err := Utf16Column(rd, offset-c, c) 114 if err != nil { 115 return Position{}, err 116 } 117 118 return Position{Line: l, Character: c2}, nil 119 } 120 121 func RangeToOffsetLen(rd iorw.ReaderAt, rang *Range) (int, int, error) { 122 l1, _ := rang.Start.OneBased() 123 l2, _ := rang.End.OneBased() 124 125 // line start offset 126 // TODO: improve getting lso2 127 lso1, err := parseutil.LineColumnIndex(rd, l1, 1) 128 if err != nil { 129 return 0, 0, err 130 } 131 lso2, err := parseutil.LineColumnIndex(rd, l2, 1) 132 if err != nil { 133 return 0, 0, err 134 } 135 136 // translate utf16 columns to utf8 (input and results are zero based) 137 u16c1, err := Utf8Column(rd, lso1, rang.Start.Character) 138 if err != nil { 139 return 0, 0, err 140 } 141 u16c2, err := Utf8Column(rd, lso2, rang.End.Character) 142 if err != nil { 143 return 0, 0, err 144 } 145 146 // start/end (range) 147 start := lso1 + u16c1 148 end := lso2 + u16c2 149 150 offset := start 151 length := end - start 152 153 return offset, length, nil 154 } 155 156 //---------- 157 158 func JsonGetPath(v interface{}, path string) (interface{}, error) { 159 args := strings.Split(path, ".") 160 return jsonGetPath2(v, args) 161 } 162 163 // TODO: incomplete 164 func jsonGetPath2(v interface{}, args []string) (interface{}, error) { 165 // handle last arg 166 if len(args) == 0 { 167 switch t := v.(type) { 168 case bool, int, float32, float64: 169 return t, nil 170 } 171 return nil, fmt.Errorf("unhandled last type: %T", v) 172 } 173 // handle args: len(args)>0 174 arg, args2 := args[0], args[1:] 175 switch t := v.(type) { 176 case map[string]interface{}: 177 if v, ok := t[arg]; ok { 178 return jsonGetPath2(v, args2) 179 } 180 return nil, fmt.Errorf("not found: %v", arg) 181 } 182 return nil, fmt.Errorf("unhandled type: %T (arg=%v)", v, arg) 183 } 184 185 //---------- 186 187 func UrlToAbsFilename(url string) (string, error) { 188 return parseutil.UrlToAbsFilename(url) 189 } 190 191 func AbsFilenameToUrl(filename string) (string, error) { 192 if runtime.GOOS == "windows" { 193 // gopls requires casing to match the OS names in windows (error: case mismatch in path ...) 194 if u, err := osutil.FsCaseFilename(filename); err == nil { 195 filename = u 196 } 197 } 198 return parseutil.AbsFilenameToUrl(filename) 199 } 200 201 //---------- 202 203 type ManagerCallHierarchyCalls struct { 204 item *CallHierarchyItem 205 calls []*CallHierarchyCall 206 } 207 208 func ManagerCallHierarchyCallsToString(mcalls []*ManagerCallHierarchyCalls, typ CallHierarchyCallType, baseDir string) (string, error) { 209 res := []string{} 210 211 // build title 212 s1 := "incoming" 213 if typ == OutgoingChct { 214 s1 = "outgoing" 215 } 216 u := fmt.Sprintf("lsproto call hierarchy %s calls:", s1) 217 res = append(res, u) 218 219 for _, mcall := range mcalls { 220 // build subtitle 221 s2 := "to" 222 if typ == OutgoingChct { 223 s2 = "from" 224 } 225 // count results for subtitle 226 nres := 0 227 for _, call := range mcall.calls { 228 nres += len(call.FromRanges) 229 } 230 s3 := fmt.Sprintf("calls %s %v: %v results", s2, mcall.item.Name, nres) 231 res = append(res, s3) 232 233 res2 := []string{} 234 for _, call := range mcall.calls { 235 item := call.Item() 236 237 // item for filename 238 fileItem := item 239 if typ == OutgoingChct { 240 fileItem = mcall.item 241 } 242 filename, err := UrlToAbsFilename(string(fileItem.Uri)) 243 if err != nil { 244 return "", err 245 } 246 // use basedir to output filename 247 if baseDir != "" { 248 if u, err := filepath.Rel(baseDir, filename); err == nil { 249 filename = u 250 } 251 } 252 253 for _, r := range call.FromRanges { 254 line, col := r.Start.OneBased() 255 u := fmt.Sprintf("\t%s:%d:%d: %s", filename, line, col, item.Name) 256 res2 = append(res2, u) 257 } 258 } 259 sort.Strings(res2) 260 res = append(res, res2...) 261 } 262 w := strings.Join(res, "\n") 263 return w, nil 264 } 265 266 //---------- 267 268 func LocationsToString(locations []*Location, baseDir string) (string, error) { 269 res := []string{} 270 for _, loc := range locations { 271 filename, err := UrlToAbsFilename(string(loc.Uri)) 272 if err != nil { 273 return "", err 274 } 275 276 // use basedir to output filename 277 if baseDir != "" { 278 if u, err := filepath.Rel(baseDir, filename); err == nil { 279 filename = u 280 } 281 } 282 283 line, col := loc.Range.Start.OneBased() 284 u := fmt.Sprintf("\t%v:%v:%v\n", filename, line, col) 285 res = append(res, u) 286 } 287 sort.Strings(res) 288 return strings.Join(res, ""), nil 289 } 290 291 //---------- 292 293 func CompletionListToString(clist *CompletionList) []string { 294 res := []string{} 295 for _, ci := range clist.Items { 296 u := []string{} 297 if ci.Deprecated { 298 u = append(u, "*deprecated*") 299 } 300 ci.Label = strings.TrimSpace(ci.Label) // NOTE: clangd is sending with spaces 301 u = append(u, ci.Label) 302 if ci.Detail != "" { 303 u = append(u, ci.Detail) 304 } 305 res = append(res, strings.Join(u, " ")) 306 } 307 308 //// add documentation if there is only 1 result 309 //if len(compList.Items) == 1 { 310 // doc := compList.Items[0].Documentation 311 // if doc != "" { 312 // res[0] += "\n\n" + doc 313 // } 314 //} 315 316 return res 317 } 318 319 //---------- 320 321 func PatchTextEdits(src []byte, edits []*TextEdit) ([]byte, error) { 322 sortTextEdits(edits) 323 res := bytes.Buffer{} // resulting patched src 324 rd := iorw.NewBytesReadWriterAt(src) 325 start := 0 326 for _, e := range edits { 327 offset, n, err := RangeToOffsetLen(rd, e.Range) 328 if err != nil { 329 return nil, err 330 } 331 res.Write(src[start:offset]) 332 res.Write([]byte(e.NewText)) 333 start = offset + n 334 } 335 res.Write(src[start:]) // rest of the src 336 return res.Bytes(), nil 337 } 338 339 func sortTextEdits(edits []*TextEdit) { 340 sort.Slice(edits, func(i, j int) bool { 341 p1, p2 := &edits[i].Range.Start, &edits[j].Range.Start 342 return p1.Line < p2.Line || 343 (p1.Line == p2.Line && p1.Character <= p2.Character) 344 }) 345 } 346 347 //----------