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
use crate::config::Settings;
use crate::env;
#[derive(
Debug,
Default,
Clone,
Copy,
PartialEq,
strum::Display,
strum::EnumString,
strum::EnumIs,
serde::Serialize,
serde::Deserialize,
)]
#[serde(rename_all = "kebab-case")]
#[strum(serialize_all = "kebab-case")]
pub(crate) enum TaskOutput {
Interleave,
KeepOrder,
#[default]
Prefix,
Replacing,
Timed,
Quiet,
Silent,
}
impl TaskOutput {
/// Reduce a (possibly verbosity-carrying) output value to a STYLE-only value.
///
/// `Quiet` historically meant "interleaved streams + suppressed mise output".
/// The stream-style half of that is `Interleave`; the suppression half is
/// applied separately via the `quiet()` predicate at mise's own metadata
/// print sites. `Silent` is resolved before this is ever called (see
/// `OutputHandler::output`), so it passes through unchanged.
pub(crate) fn style_only(self) -> TaskOutput {
match self {
TaskOutput::Quiet => {
deprecated_at!(
"2026.9.3",
"2027.9.3",
"task-output-quiet",
"The quiet task output mode is deprecated. Use output=\"interleave\" with task.quiet=true or a per-task quiet=true, or use --output interleave --quiet."
);
TaskOutput::Interleave
}
other => other,
}
}
/// Like [`style_only`](Self::style_only), but a `raw` task downgrades every
/// non-suppression style to `Interleave` (raw needs inherited stdio for
/// stdin passthrough). Mirrors the pre-existing `raw` handling for the
/// global `task.output` setting.
pub(crate) fn style_with_raw(self, raw: bool) -> TaskOutput {
let style = self.style_only();
// `raw` needs inherited stdio, but must never un-silence a `Silent` task.
if raw && !matches!(style, TaskOutput::Silent) {
TaskOutput::Interleave
} else {
style
}
}
}
/// Returns the first line of a message for display unless task_show_full_cmd is true
/// In CI mode, returns the full first line without truncation
/// Otherwise, truncates to terminal width with ellipsis
pub(crate) fn trunc(prefix: &str, msg: &str) -> String {
let settings = Settings::get();
// Skip width truncation when explicitly disabled
if settings.task.show_full_cmd {
return msg.to_string();
}
let msg = msg.lines().next().unwrap_or_default();
if settings.ci {
return msg.to_string();
}
let prefix_len = console::measure_text_width(prefix);
// Ensure we have at least 20 characters for the message, even with very long prefixes
let available_width = (*env::TERM_WIDTH).saturating_sub(prefix_len + 1);
let max_width = available_width.max(20); // Always show at least 20 chars of message
console::truncate_str(msg, max_width, "…").to_string()
}