| 1 | package lsp |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "fmt" |
| 6 | "strings" |
| 7 | ) |
| 8 | |
| 9 | // parseLocations decodes the three shapes textDocument/definition and |
| 10 | // /references may return: Location, Location[], or LocationLink[]. |
| 11 | func parseLocations(raw json.RawMessage) []Location { |
| 12 | s := strings.TrimSpace(string(raw)) |
| 13 | if s == "" || s == "null" { |
| 14 | return nil |
| 15 | } |
| 16 | if s[0] == '[' { |
| 17 | var arr []Location |
| 18 | if json.Unmarshal(raw, &arr) == nil && len(arr) > 0 && arr[0].URI != "" { |
| 19 | return arr |
| 20 | } |
| 21 | var links []struct { |
| 22 | TargetURI string `json:"targetUri"` |
| 23 | TargetRange Range `json:"targetRange"` |
| 24 | } |
| 25 | if json.Unmarshal(raw, &links) == nil { |
| 26 | out := make([]Location, 0, len(links)) |
| 27 | for _, l := range links { |
| 28 | if l.TargetURI != "" { |
| 29 | out = append(out, Location{URI: l.TargetURI, Range: l.TargetRange}) |
| 30 | } |
| 31 | } |
| 32 | return out |
| 33 | } |
| 34 | return nil |
| 35 | } |
| 36 | var one Location |
| 37 | if json.Unmarshal(raw, &one) == nil && one.URI != "" { |
| 38 | return []Location{one} |
| 39 | } |
| 40 | return nil |
| 41 | } |
| 42 | |
| 43 | // parseHover decodes a Hover.contents, which may be a MarkupContent, a single |
| 44 | // MarkedString, or an array of them. |
| 45 | func parseHover(raw json.RawMessage) string { |
| 46 | var h struct { |
| 47 | Contents json.RawMessage `json:"contents"` |
| 48 | } |
| 49 | if json.Unmarshal(raw, &h) != nil || len(h.Contents) == 0 { |
| 50 | return "" |
| 51 | } |
| 52 | return strings.TrimSpace(markedToText(h.Contents)) |
| 53 | } |
| 54 | |
| 55 | func markedToText(raw json.RawMessage) string { |
| 56 | s := strings.TrimSpace(string(raw)) |
| 57 | if s == "" || s == "null" { |
| 58 | return "" |
| 59 | } |
| 60 | switch s[0] { |
| 61 | case '"': |
| 62 | var str string |
| 63 | _ = json.Unmarshal(raw, &str) |
| 64 | return str |
| 65 | case '{': |
| 66 | var mc struct { |
| 67 | Kind string `json:"kind"` |
| 68 | Value string `json:"value"` |
| 69 | } |
| 70 | if json.Unmarshal(raw, &mc) == nil && mc.Value != "" { |
| 71 | return mc.Value |
| 72 | } |
| 73 | var ms struct { |
| 74 | Language string `json:"language"` |
| 75 | Value string `json:"value"` |
| 76 | } |
| 77 | _ = json.Unmarshal(raw, &ms) |
| 78 | return ms.Value |
| 79 | case '[': |
| 80 | var parts []json.RawMessage |
| 81 | _ = json.Unmarshal(raw, &parts) |
| 82 | var out []string |
| 83 | for _, p := range parts { |
| 84 | if t := markedToText(p); t != "" { |
| 85 | out = append(out, t) |
| 86 | } |
| 87 | } |
| 88 | return strings.Join(out, "\n") |
| 89 | } |
| 90 | return "" |
| 91 | } |
| 92 | |
| 93 | var severityName = map[int]string{1: "error", 2: "warning", 3: "info", 4: "hint"} |
| 94 | |
| 95 | func formatDiagnostics(rel string, diags []Diagnostic) string { |
| 96 | if len(diags) == 0 { |
| 97 | return "no diagnostics for " + rel |
| 98 | } |
| 99 | var b strings.Builder |
| 100 | fmt.Fprintf(&b, "%d diagnostic(s) in %s:\n", len(diags), rel) |
| 101 | for _, d := range diags { |
| 102 | sev := severityName[d.Severity] |
| 103 | if sev == "" { |
| 104 | sev = "error" |
| 105 | } |
| 106 | src := "" |
| 107 | if d.Source != "" { |
| 108 | src = " [" + d.Source + "]" |
| 109 | } |
| 110 | fmt.Fprintf(&b, "%d:%d %s%s %s\n", |
| 111 | d.Range.Start.Line+1, d.Range.Start.Character+1, sev, src, |
| 112 | strings.TrimSpace(d.Message)) |
| 113 | } |
| 114 | return strings.TrimRight(b.String(), "\n") |
| 115 | } |
| 116 |