turbo-editors/turbo-corepublic Fork 0
v1.0.0
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

problems.go · 132 lines · 4.2 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 19h ago1// The Problems window: every diagnostic the language server has reported.
2
3package app
4
5import (
6 "fmt"
7 "path/filepath"
8 "strings"
9
📦 Turbo Core f3ade8d k33g 11h ago10 "rickub.com/turbo-editors/turbo-core/editor"
11 "rickub.com/turbo-editors/turbo-core/lsp"
12 "rickub.com/turbo-editors/turbo-core/ui"
🛟 Updated. 28d5985 k33g 19h ago13)
14
15// ShowProblems lists every problem the language server has reported, for every
16// file it has spoken about, and goes to the one chosen.
17//
18// Every file, not only the one in front. A server publishes diagnostics for
19// the whole package it has loaded, so the file with the error is very often
20// not the file being edited — which is exactly the case a list is worth
21// having for. The status bar already shows the first error in the current
22// file; this is the rest of what the editor already knows.
23func (a *App) ShowProblems() {
24 problems := a.language.AllDiagnostics()
25 if len(problems) == 0 {
26 a.Message(a.noProblemsMessage())
27 return
28 }
29
30 labels := make([]string, 0, len(problems))
31 for _, problem := range problems {
32 labels = append(labels, problemLabel(problem))
33 }
34
35 dialog, list := NewChoiceDialog(fmt.Sprintf("Problems (%d)", len(problems)), labels, 0, a.screenRect())
36 a.pushModal(dialog, func(result ui.Result) {
37 if result == ui.ResultOK && list.Selected() >= 0 {
38 problem := problems[list.Selected()]
39 a.jumpTo(lsp.Location{URI: lsp.PathToURI(problem.Path), Range: problem.Diagnostic.Range})
40 }
41 })
42}
43
44// noProblemsMessage tells "there is nothing wrong" apart from "nothing has
45// looked yet", which are the same empty list and very different news.
46func (a *App) noProblemsMessage() string {
47 if !a.language.Ready() {
48 return a.language.Status()
49 }
50 return "No problems reported"
51}
52
53// problemLabel is how one problem reads in the list: how bad it is, where it
54// is, and what it says.
55//
56// The message is put last and left whole. Servers write long ones — a Rust
57// borrow-checker error runs to a paragraph — and truncating them here would
58// cut off the part that names the variable. The list scrolls sideways instead.
59func problemLabel(problem FileDiagnostic) string {
60 return fmt.Sprintf("%s %s:%d %s",
61 severityTag(problem.Diagnostic.Severity),
62 filepath.Base(problem.Path),
63 problem.Diagnostic.Range.Start.Line+1,
64 strings.ReplaceAll(problem.Diagnostic.Message, "\n", " "))
65}
66
67// severityTag is the fixed-width word in front of a problem, so the column of
68// messages lines up whatever the severities are.
69func severityTag(severity lsp.Severity) string {
70 switch severity {
71 case lsp.SeverityError:
72 return "error "
73 case lsp.SeverityWarning:
74 return "warning"
75 case lsp.SeverityInformation:
76 return "info "
77 case lsp.SeverityHint:
78 return "hint "
79 }
80 return " "
81}
82
83// refreshMarks tells every editing window which of its lines the language
84// server has something to say about.
85//
86// It runs whenever the diagnostics change and whenever a window opens, because
87// both change the answer and neither is the other's business to notice.
88func (a *App) refreshMarks() {
89 for _, window := range a.desktop.Windows() {
90 view, ok := editorViewOf(window)
91 if !ok {
92 continue
93 }
94 view.SetMarks(marksFor(a.language.Diagnostics(view.Buffer().Path())))
95 }
96}
97
98// marksFor turns a file's diagnostics into one mark per line.
99//
100// A line with several problems is marked with its **worst** one: the gutter
101// has one column, and a line that is both an error and a hint is a line you
102// want to know is an error.
103func marksFor(diagnostics []lsp.Diagnostic) map[int]editor.Severity {
104 if len(diagnostics) == 0 {
105 return nil
106 }
107
108 marks := map[int]editor.Severity{}
109 for _, diagnostic := range diagnostics {
110 line := diagnostic.Range.Start.Line
111 if severity := markSeverity(diagnostic.Severity); severity > marks[line] {
112 marks[line] = severity
113 }
114 }
115 return marks
116}
117
118// markSeverity translates the protocol's severity into the editor's own.
119//
120// A diagnostic with no severity is an error: the specification leaves it to
121// the client, and a problem nobody graded is not one to draw quietly.
122func markSeverity(severity lsp.Severity) editor.Severity {
123 switch severity {
124 case lsp.SeverityWarning:
125 return editor.MarkWarning
126 case lsp.SeverityInformation:
127 return editor.MarkInformation
128 case lsp.SeverityHint:
129 return editor.MarkHint
130 }
131 return editor.MarkError
132}