turbo-editors/turbo-corepublic Fork 0
main
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.

🛟 Updated. 28d5985 · on main · k33g · 4h ago
problems.go · 132 lines · 4.2 KBGo Blame HistoryRaw
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
// The Problems window: every diagnostic the language server has reported.

package app

import (
	"fmt"
	"path/filepath"
	"strings"

	"codeberg.org/turbo-editors/turbo-core/editor"
	"codeberg.org/turbo-editors/turbo-core/lsp"
	"codeberg.org/turbo-editors/turbo-core/ui"
)

// ShowProblems lists every problem the language server has reported, for every
// file it has spoken about, and goes to the one chosen.
//
// Every file, not only the one in front. A server publishes diagnostics for
// the whole package it has loaded, so the file with the error is very often
// not the file being edited — which is exactly the case a list is worth
// having for. The status bar already shows the first error in the current
// file; this is the rest of what the editor already knows.
func (a *App) ShowProblems() {
	problems := a.language.AllDiagnostics()
	if len(problems) == 0 {
		a.Message(a.noProblemsMessage())
		return
	}

	labels := make([]string, 0, len(problems))
	for _, problem := range problems {
		labels = append(labels, problemLabel(problem))
	}

	dialog, list := NewChoiceDialog(fmt.Sprintf("Problems (%d)", len(problems)), labels, 0, a.screenRect())
	a.pushModal(dialog, func(result ui.Result) {
		if result == ui.ResultOK && list.Selected() >= 0 {
			problem := problems[list.Selected()]
			a.jumpTo(lsp.Location{URI: lsp.PathToURI(problem.Path), Range: problem.Diagnostic.Range})
		}
	})
}

// noProblemsMessage tells "there is nothing wrong" apart from "nothing has
// looked yet", which are the same empty list and very different news.
func (a *App) noProblemsMessage() string {
	if !a.language.Ready() {
		return a.language.Status()
	}
	return "No problems reported"
}

// problemLabel is how one problem reads in the list: how bad it is, where it
// is, and what it says.
//
// The message is put last and left whole. Servers write long ones — a Rust
// borrow-checker error runs to a paragraph — and truncating them here would
// cut off the part that names the variable. The list scrolls sideways instead.
func problemLabel(problem FileDiagnostic) string {
	return fmt.Sprintf("%s  %s:%d  %s",
		severityTag(problem.Diagnostic.Severity),
		filepath.Base(problem.Path),
		problem.Diagnostic.Range.Start.Line+1,
		strings.ReplaceAll(problem.Diagnostic.Message, "\n", " "))
}

// severityTag is the fixed-width word in front of a problem, so the column of
// messages lines up whatever the severities are.
func severityTag(severity lsp.Severity) string {
	switch severity {
	case lsp.SeverityError:
		return "error  "
	case lsp.SeverityWarning:
		return "warning"
	case lsp.SeverityInformation:
		return "info   "
	case lsp.SeverityHint:
		return "hint   "
	}
	return "       "
}

// refreshMarks tells every editing window which of its lines the language
// server has something to say about.
//
// It runs whenever the diagnostics change and whenever a window opens, because
// both change the answer and neither is the other's business to notice.
func (a *App) refreshMarks() {
	for _, window := range a.desktop.Windows() {
		view, ok := editorViewOf(window)
		if !ok {
			continue
		}
		view.SetMarks(marksFor(a.language.Diagnostics(view.Buffer().Path())))
	}
}

// marksFor turns a file's diagnostics into one mark per line.
//
// A line with several problems is marked with its **worst** one: the gutter
// has one column, and a line that is both an error and a hint is a line you
// want to know is an error.
func marksFor(diagnostics []lsp.Diagnostic) map[int]editor.Severity {
	if len(diagnostics) == 0 {
		return nil
	}

	marks := map[int]editor.Severity{}
	for _, diagnostic := range diagnostics {
		line := diagnostic.Range.Start.Line
		if severity := markSeverity(diagnostic.Severity); severity > marks[line] {
			marks[line] = severity
		}
	}
	return marks
}

// markSeverity translates the protocol's severity into the editor's own.
//
// A diagnostic with no severity is an error: the specification leaves it to
// the client, and a problem nobody graded is not one to draw quietly.
func markSeverity(severity lsp.Severity) editor.Severity {
	switch severity {
	case lsp.SeverityWarning:
		return editor.MarkWarning
	case lsp.SeverityInformation:
		return editor.MarkInformation
	case lsp.SeverityHint:
		return editor.MarkHint
	}
	return editor.MarkError
}