Skip to content

Commit 8ba25be

Browse files
[vtbackend] Add customizable indicator statusline
Signed-off-by: Christian Parpart <[email protected]>
1 parent 313816e commit 8ba25be

18 files changed

+1013
-165
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Indicator Statusline
2+
3+
The indicator statusline used to be a feature, from the old DEC VT level 4 terminals.
4+
Contour revives this feature to prominently show the terminal status.
5+
6+
## Configuration
7+
8+
```
9+
profiles:
10+
your_profile:
11+
status_line:
12+
indicator:
13+
left: "{VTType} │ {InputMode:Bold,Color=#C0C030}{Search:Left= │ }{TraceMode:Bold,Color=#FFFF00,Left= │ }{ProtectedMode:Bold,Left= │ }"
14+
middle: "{Title:Left= « ,Right= » ,Color=#20c0c0}"
15+
right: "{HistoryLineCount:Faint,Color=#c0c0c0} │ {Clock:Bold} "
16+
```
17+
18+
Each segment, `left`, `middle`, and `right` may contain text to be displayed in the
19+
left, middle, or right segment of the indicator statusline.
20+
21+
This text may contain placeholders to be replaced by their respective dynamic content.
22+
23+
## Variables
24+
25+
Variable | Description
26+
---------------------|--------------------------------------------------------------------
27+
`{Clock}` | current clock in HH:MM format
28+
`{Command}` | yields the result of the given command prompt
29+
`{HistoryLineCount}` | number of lines in history (only available in primary screen)
30+
`{Hyperlink}` | reveals the hyperlink at the given mouse location
31+
`{InputMode}` | current input mode (e.g. INSERT, NORMAL, VISUAL)
32+
`{ProtectedMode}` | indicates protected mode, if currently enabled
33+
`{SearchMode}` | indicates search highlight mode, if currently active
34+
`{SearchPrompt}` | search input prompt, if currently active
35+
`{Text}` | given text (makes only sense when customized with flags)
36+
`{Title}` | current window title
37+
`{VTType}` | currently active VT emulation type
38+
39+
## Formatting Styles
40+
41+
Each Variable, as specified above, can be parametrized for customizing the look of it.
42+
The common syntax to these variables and their parameters looks as follows:
43+
44+
```
45+
{VariableName:SomeFlag,SomeKey=SomeValue}
46+
```
47+
48+
So parameters can be specified after a colon (`:`) as a comma separated list of flags and key/value pairs.
49+
A key/value pair is further split by equal sign (`=`).
50+
51+
The following list of formatting styles are supported:
52+
53+
Parameter | Description
54+
--------------------------|--------------------------------------------------------------------
55+
`Left=TEXT` | text to show on the left side, if the variable is to be shown
56+
`Right=TEXT` | text to show on the right side, if the variable is to be shown
57+
`Color=#RRGGBB` | text color in hexadecimal RGB notation
58+
`BackgroundColor=#RRGGBB` | background color in hexadecimal RGB notation
59+
`Bold` | text in bold font face
60+
`Italic` | text in italic font face
61+
`Underline` | underline text (only one underline style can be active)
62+
`CurlyUnderline` | curly underline text (only one underline style can be active)
63+
`DoubleUnderline` | double underline text (only one underline style can be active)
64+
`DottedUnderline` | dotted underline text (only one underline style can be active)
65+
`DashedUnderline` | dashed underline text (only one underline style can be active)
66+
`Blinking` | blinking text
67+
`RapidBlinking` | rapid blinking text
68+
`Overline` | overline text
69+
`Inverse` | inversed text/background coloring

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ nav:
9090
- configuration/index.md
9191
- configuration/profiles.md
9292
- configuration/colors.md
93+
- configuration/indicator-statusline.md
9394
#- Images (Advanced) : configuration/advanced/images.md
9495
#- Mouse (Advanced) : configuration/advanced/images.md
9596
#- Misc (Advanced) : configuration/advanced/mouse.md

src/contour/Config.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,13 @@ void YAMLConfigReader::loadFromEntry(YAML::Node const& node, std::string const&
350350
loadFromEntry(child["status_line"], "position", where.statusDisplayPosition);
351351
loadFromEntry(child["status_line"], "sync_to_window_title", where.syncWindowTitleWithHostWritableStatusDisplay);
352352
loadFromEntry(child["status_line"], "display", where.initialStatusDisplayType);
353+
354+
if (child["status_line"]["indicator"])
355+
{
356+
loadFromEntry(child["status_line"]["indicator"], "left", where.indicatorStatusLineLeft);
357+
loadFromEntry(child["status_line"]["indicator"], "middle", where.indicatorStatusLineMiddle);
358+
loadFromEntry(child["status_line"]["indicator"], "right", where.indicatorStatusLineRight);
359+
}
353360
}
354361
if (child["background"])
355362
{

src/contour/Config.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,18 @@ struct TerminalProfile
319319
};
320320
ConfigEntry<vtbackend::StatusDisplayPosition, documentation::StatusDisplayPosition>
321321
statusDisplayPosition { vtbackend::StatusDisplayPosition::Bottom };
322+
ConfigEntry<std::string, documentation::IndicatorStatusLineLeft> indicatorStatusLineLeft {
323+
"{VTType} │ {InputMode:Bold,Color=#C0C030}"
324+
"{SearchPrompt:Left= │ }"
325+
"{TraceMode:Bold,Color=#FFFF00,Left= │ }"
326+
"{ProtectedMode:Bold,Left= │ }"
327+
};
328+
ConfigEntry<std::string, documentation::IndicatorStatusLineMiddle> indicatorStatusLineMiddle {
329+
"{Title:Left= « ,Right= » }"
330+
};
331+
ConfigEntry<std::string, documentation::IndicatorStatusLineRight> indicatorStatusLineRight {
332+
"{HistoryLineCount:Faint,Color=#c0c0c0} │ {Clock:Bold} "
333+
};
322334
ConfigEntry<bool, documentation::SyncWindowTitleWithHostWritableStatusDisplay>
323335
syncWindowTitleWithHostWritableStatusDisplay { false };
324336
ConfigEntry<bool, documentation::HideScrollbarInAltScreen> hideScrollbarInAltScreen { true };

src/contour/ConfigDocumentation.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ constexpr StringLiteral StatusDisplayPosition {
171171
"\n"
172172
};
173173

174+
constexpr StringLiteral IndicatorStatusLineLeft { "left: {}\n" };
175+
constexpr StringLiteral IndicatorStatusLineMiddle { "middle: {}\n" };
176+
constexpr StringLiteral IndicatorStatusLineRight { "right: {}\n" };
177+
174178
constexpr StringLiteral SyncWindowTitleWithHostWritableStatusDisplay {
175179
"{comment} Synchronize the window title with the Host Writable status_line if\n"
176180
"{comment} and only if the host writable status line was denied to be shown.\n"

src/contour/TerminalSession.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,9 @@ namespace
141141
settings.maxImageRegisterCount = config.maxImageColorRegisters.value();
142142
settings.statusDisplayType = profile.initialStatusDisplayType.value();
143143
settings.statusDisplayPosition = profile.statusDisplayPosition.value();
144+
settings.indicatorStatusLine.left = profile.indicatorStatusLineLeft.value();
145+
settings.indicatorStatusLine.middle = profile.indicatorStatusLineMiddle.value();
146+
settings.indicatorStatusLine.right = profile.indicatorStatusLineRight.value();
144147
settings.syncWindowTitleWithHostWritableStatusDisplay =
145148
profile.syncWindowTitleWithHostWritableStatusDisplay.value();
146149
if (auto const* p = preferredColorPalette(profile.colors.value(), colorPreference))

src/vtbackend/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(vtbackend_HEADERS
3939
Sequence.h
4040
Sequencer.h
4141
SixelParser.h
42+
StatusLineBuilder.h
4243
Terminal.h
4344
VTType.h
4445
VTWriter.h
@@ -69,6 +70,7 @@ set(vtbackend_SOURCES
6970
Sequence.cpp
7071
Sequencer.cpp
7172
SixelParser.cpp
73+
StatusLineBuilder.cpp
7274
Terminal.cpp
7375
TerminalState.cpp
7476
VTType.cpp

src/vtbackend/Color.cpp

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#include <vtbackend/Color.h>
33

44
#include <crispy/overloaded.h>
5+
#include <crispy/utils.h>
56

67
using namespace std;
78

@@ -128,4 +129,47 @@ string to_string(RGBAColor c)
128129
return fmt::format("#{:02X}{:02X}{:02X}{:02X}", c.red(), c.green(), c.blue(), c.alpha());
129130
}
130131

132+
optional<RGBColor> parseColor(string_view const& value)
133+
{
134+
try
135+
{
136+
// "rgb:RR/GG/BB"
137+
// 0123456789a
138+
if (value.size() == 12 && value.substr(0, 4) == "rgb:" && value[6] == '/' && value[9] == '/')
139+
{
140+
auto const r = crispy::to_integer<16, uint8_t>(value.substr(4, 2));
141+
auto const g = crispy::to_integer<16, uint8_t>(value.substr(7, 2));
142+
auto const b = crispy::to_integer<16, uint8_t>(value.substr(10, 2));
143+
return RGBColor { r.value(), g.value(), b.value() };
144+
}
145+
146+
// "#RRGGBB"
147+
if (value.size() == 7 && value[0] == '#')
148+
{
149+
auto const r = crispy::to_integer<16, uint8_t>(value.substr(1, 2));
150+
auto const g = crispy::to_integer<16, uint8_t>(value.substr(3, 2));
151+
auto const b = crispy::to_integer<16, uint8_t>(value.substr(5, 2));
152+
return RGBColor { r.value(), g.value(), b.value() };
153+
}
154+
155+
// "#RGB"
156+
if (value.size() == 4 && value[0] == '#')
157+
{
158+
auto const r = crispy::to_integer<16, uint8_t>(value.substr(1, 1));
159+
auto const g = crispy::to_integer<16, uint8_t>(value.substr(2, 1));
160+
auto const b = crispy::to_integer<16, uint8_t>(value.substr(3, 1));
161+
auto const rr = static_cast<uint8_t>(r.value() << 4);
162+
auto const gg = static_cast<uint8_t>(g.value() << 4);
163+
auto const bb = static_cast<uint8_t>(b.value() << 4);
164+
return RGBColor { rr, gg, bb };
165+
}
166+
167+
return std::nullopt;
168+
}
169+
catch (...)
170+
{
171+
// that will be a formatting error in stoul() then.
172+
return std::nullopt;
173+
}
174+
}
131175
} // namespace vtbackend

src/vtbackend/Color.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,8 @@ constexpr Opacity& operator--(Opacity& value) noexcept
395395
}
396396
// }}}
397397

398+
std::optional<RGBColor> parseColor(std::string_view const& value);
399+
398400
} // namespace vtbackend
399401

400402
// {{{ fmtlib custom formatter

src/vtbackend/Screen.cpp

Lines changed: 11 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,7 @@ void Screen<Cell>::writeTextEnd()
507507
return;
508508

509509
if (vtTraceSequenceLog)
510-
vtTraceSequenceLog()("text: \"{}\"", _pendingCharTraceLog);
510+
vtTraceSequenceLog()("[{}] text: \"{}\"", _name, _pendingCharTraceLog);
511511

512512
_pendingCharTraceLog.clear();
513513
#endif
@@ -1790,7 +1790,10 @@ template <typename Cell>
17901790
CRISPY_REQUIRES(CellConcept<Cell>)
17911791
void Screen<Cell>::setGraphicsRendition(GraphicsRendition rendition)
17921792
{
1793-
_terminal->setGraphicsRendition(rendition);
1793+
if (rendition == GraphicsRendition::Reset)
1794+
_cursor.graphicsRendition = {};
1795+
else
1796+
_cursor.graphicsRendition.flags = CellUtil::makeCellFlags(rendition, _cursor.graphicsRendition.flags);
17941797
}
17951798

17961799
template <typename Cell>
@@ -2546,50 +2549,6 @@ namespace impl
25462549
return ApplyResult::Invalid;
25472550
}
25482551

2549-
optional<RGBColor> parseColor(string_view const& value)
2550-
{
2551-
try
2552-
{
2553-
// "rgb:RR/GG/BB"
2554-
// 0123456789a
2555-
if (value.size() == 12 && value.substr(0, 4) == "rgb:" && value[6] == '/' && value[9] == '/')
2556-
{
2557-
auto const r = crispy::to_integer<16, uint8_t>(value.substr(4, 2));
2558-
auto const g = crispy::to_integer<16, uint8_t>(value.substr(7, 2));
2559-
auto const b = crispy::to_integer<16, uint8_t>(value.substr(10, 2));
2560-
return RGBColor { r.value(), g.value(), b.value() };
2561-
}
2562-
2563-
// "#RRGGBB"
2564-
if (value.size() == 7 && value[0] == '#')
2565-
{
2566-
auto const r = crispy::to_integer<16, uint8_t>(value.substr(1, 2));
2567-
auto const g = crispy::to_integer<16, uint8_t>(value.substr(3, 2));
2568-
auto const b = crispy::to_integer<16, uint8_t>(value.substr(5, 2));
2569-
return RGBColor { r.value(), g.value(), b.value() };
2570-
}
2571-
2572-
// "#RGB"
2573-
if (value.size() == 4 && value[0] == '#')
2574-
{
2575-
auto const r = crispy::to_integer<16, uint8_t>(value.substr(1, 1));
2576-
auto const g = crispy::to_integer<16, uint8_t>(value.substr(2, 1));
2577-
auto const b = crispy::to_integer<16, uint8_t>(value.substr(3, 1));
2578-
auto const rr = static_cast<uint8_t>(r.value() << 4);
2579-
auto const gg = static_cast<uint8_t>(g.value() << 4);
2580-
auto const bb = static_cast<uint8_t>(b.value() << 4);
2581-
return RGBColor { rr, gg, bb };
2582-
}
2583-
2584-
return std::nullopt;
2585-
}
2586-
catch (...)
2587-
{
2588-
// that will be a formatting error in stoul() then.
2589-
return std::nullopt;
2590-
}
2591-
}
2592-
25932552
Color parseColor(Sequence const& seq, size_t* pi)
25942553
{
25952554
// We are at parameter index `i`.
@@ -2701,9 +2660,7 @@ namespace impl
27012660
return Color {};
27022661
}
27032662

2704-
template <typename Target>
2705-
CRISPY_REQUIRES((CellConcept<Target> || std::is_same_v<Target, Terminal>) )
2706-
ApplyResult applySGR(Target& target, Sequence const& seq, size_t parameterStart, size_t parameterEnd)
2663+
ApplyResult applySGR(auto& target, Sequence const& seq, size_t parameterStart, size_t parameterEnd)
27072664
{
27082665
if (parameterStart == parameterEnd)
27092666
{
@@ -2916,7 +2873,7 @@ namespace impl
29162873
auto const& value = seq.intermediateCharacters();
29172874
if (value == "?")
29182875
screen.requestDynamicColor(name);
2919-
else if (auto color = parseColor(value); color.has_value())
2876+
else if (auto color = vtbackend::parseColor(value); color.has_value())
29202877
screen.setDynamicColor(name, color.value());
29212878
else
29222879
return ApplyResult::Invalid;
@@ -2948,7 +2905,7 @@ namespace impl
29482905
queryColor((uint8_t) index);
29492906
index = -1;
29502907
}
2951-
else if (auto const color = parseColor(value))
2908+
else if (auto const color = vtbackend::parseColor(value))
29522909
{
29532910
setColor((uint8_t) index, color.value());
29542911
index = -1;
@@ -3408,10 +3365,10 @@ void Screen<Cell>::processSequence(Sequence const& seq)
34083365
{
34093366
if (auto const* fd = seq.functionDefinition(_terminal->activeSequences()))
34103367
{
3411-
vtTraceSequenceLog()("Processing {:<14} {}", fd->documentation.mnemonic, seq.text());
3368+
vtTraceSequenceLog()("[{}] Processing {:<14} {}", _name, fd->documentation.mnemonic, seq.text());
34123369
}
34133370
else
3414-
vtTraceSequenceLog()("Processing unknown sequence: {}", seq.text());
3371+
vtTraceSequenceLog()("[{}] Processing unknown sequence: {}", _name, seq.text());
34153372
}
34163373
#endif
34173374

@@ -3750,7 +3707,7 @@ ApplyResult Screen<Cell>::apply(FunctionDefinition const& function, Sequence con
37503707
case SCOSC: saveCursor(); break;
37513708
case SD: scrollDown(seq.param_or<LineCount>(0, LineCount { 1 })); break;
37523709
case SETMARK: setMark(); break;
3753-
case SGR: return impl::applySGR(*_terminal, seq, 0, seq.parameterCount());
3710+
case SGR: return impl::applySGR(*this, seq, 0, seq.parameterCount());
37543711
case SGRRESTORE: restoreGraphicsRendition(); return ApplyResult::Ok;
37553712
case SGRSAVE: saveGraphicsRendition(); return ApplyResult::Ok;
37563713
case SM: {

0 commit comments

Comments
 (0)