Skip to content

Commit b492e52

Browse files
[vtbackend] Add customizable indicator statusline
Signed-off-by: Christian Parpart <[email protected]>
1 parent f54a8a3 commit b492e52

21 files changed

+1048
-189
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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}{SearchPrompt: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, as specified via parameter `Program=...`
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
70+
71+
These parameters apply to all variables above.
72+
73+
The `Command` variable is the only one that requires a special attribute, `Program` whose value
74+
is the command to execute.

metainfo.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
<release version="0.4.4" urgency="medium" type="development">
108108
<description>
109109
<ul>
110+
<li>Add ability to customize the indicator statusline through configuration (#687)</li>
110111
<li>Add generation of config file from internal state (#1282)</li>
111112
<li>Add SGRSAVE and SGRRESTORE VT sequences to save and restore SGR state (They intentionally conflict with XTPUSHSGR and XTPOPSGR)</li>
112113
<li>Fixes corruption of sixel image on high resolution (#1049)</li>

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: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,13 @@ void YAMLConfigReader::loadFromEntry(YAML::Node const& node, std::string const&
403403
loadFromEntry(child["status_line"], "position", where.statusDisplayPosition);
404404
loadFromEntry(child["status_line"], "sync_to_window_title", where.syncWindowTitleWithHostWritableStatusDisplay);
405405
loadFromEntry(child["status_line"], "display", where.initialStatusDisplayType);
406+
407+
if (child["status_line"]["indicator"])
408+
{
409+
loadFromEntry(child["status_line"]["indicator"], "left", where.indicatorStatusLineLeft);
410+
loadFromEntry(child["status_line"]["indicator"], "middle", where.indicatorStatusLineMiddle);
411+
loadFromEntry(child["status_line"]["indicator"], "right", where.indicatorStatusLineRight);
412+
}
406413
}
407414
if (child["background"])
408415
{
@@ -1933,6 +1940,14 @@ std::string YAMLConfigWriter::createString(Config const& c)
19331940
process(entry.initialStatusDisplayType);
19341941
process(entry.statusDisplayPosition);
19351942
process(entry.syncWindowTitleWithHostWritableStatusDisplay);
1943+
1944+
doc.append(addOffset("indicator:\n", Offset::levels * OneOffset));
1945+
{
1946+
const auto _ = Offset {};
1947+
process(entry.indicatorStatusLineLeft);
1948+
process(entry.indicatorStatusLineMiddle);
1949+
process(entry.indicatorStatusLineRight);
1950+
}
19361951
}
19371952

19381953
doc.append(addOffset("\n"

src/contour/Config.h

Lines changed: 13 additions & 1 deletion
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+
" {InputMode:Bold,Color=#FFFF00}"
324+
"{SearchPrompt:Left= │ }"
325+
"{TraceMode:Bold,Color=#FFFF00,Left= │ }"
326+
"{ProtectedMode:Bold,Left= │ }"
327+
};
328+
ConfigEntry<std::string, documentation::IndicatorStatusLineMiddle> indicatorStatusLineMiddle {
329+
" {Clock:Bold} {Title:Left= « ,Right= » }"
330+
};
331+
ConfigEntry<std::string, documentation::IndicatorStatusLineRight> indicatorStatusLineRight {
332+
"{HistoryLineCount:Faint,Color=#c0c0c0} "
333+
};
322334
ConfigEntry<bool, documentation::SyncWindowTitleWithHostWritableStatusDisplay>
323335
syncWindowTitleWithHostWritableStatusDisplay { false };
324336
ConfigEntry<bool, documentation::HideScrollbarInAltScreen> hideScrollbarInAltScreen { true };
@@ -351,7 +363,7 @@ struct TerminalProfile
351363
ConfigEntry<std::chrono::milliseconds, documentation::HighlightTimeout> highlightTimeout { 100 };
352364
ConfigEntry<bool, documentation::HighlightDoubleClickerWord> highlightDoubleClickedWord { true };
353365
ConfigEntry<vtbackend::StatusDisplayType, documentation::InitialStatusLine> initialStatusDisplayType {
354-
vtbackend::StatusDisplayType::None
366+
vtbackend::StatusDisplayType::Indicator
355367
};
356368
ConfigEntry<vtbackend::Opacity, documentation::BackgroundOpacity> backgroundOpacity { vtbackend::Opacity(
357369
0xFF) };

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/crispy/interpolated_string_test.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ TEST_CASE("interpolated_string.parse_interpolated_string")
2222
{
2323
using crispy::parse_interpolated_string;
2424

25-
auto const interpolated = parse_interpolated_string(
26-
"< {Clock:Bold,Italic,Color=#FFFF00} | {VTType}");
25+
auto const interpolated = parse_interpolated_string("< {Clock:Bold,Italic,Color=#FFFF00} | {VTType}");
2726

2827
CHECK(interpolated.size() == 4);
2928

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

0 commit comments

Comments
 (0)