Skip to content

Commit fca59ef

Browse files
Merge pull request #1459 from contour-terminal/feature/customizable-statusline
Add customizable statusline
2 parents c9cad17 + 20d94f1 commit fca59ef

25 files changed

+1294
-242
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/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ set(crispy_SOURCES
2424
escape.h
2525
file_descriptor.h
2626
flags.h
27+
interpolated_string.cpp interpolated_string.h
2728
logstore.cpp logstore.h
2829
overloaded.h
2930
reference.h
@@ -129,6 +130,7 @@ if(CRISPY_TESTING)
129130
TrieMap_test.cpp
130131
base64_test.cpp
131132
compose_test.cpp
133+
interpolated_string_test.cpp
132134
utils_test.cpp
133135
result_test.cpp
134136
ring_test.cpp

src/crispy/flags.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ class flags
126126
return flags<flag_type>::from_value(_value | static_cast<value_type>(other));
127127
}
128128

129+
[[nodiscard]] auto reduce(auto init, auto f) const
130+
{
131+
auto result = std::move(init);
132+
for (auto i = 0u; i < sizeof(flag_type) * 8; ++i)
133+
if (auto const flag = static_cast<flag_type>(1 << i); test(flag))
134+
result = f(std::move(result), flag);
135+
return result;
136+
}
137+
129138
private:
130139
value_type _value = 0;
131140
};

src/crispy/interpolated_string.cpp

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
#include <crispy/interpolated_string.h>
3+
4+
namespace crispy
5+
{
6+
7+
namespace
8+
{
9+
void parse_attribute(string_interpolation* interpolation, std::string_view attribute)
10+
{
11+
auto const equal = attribute.find('=');
12+
if (equal != std::string_view::npos)
13+
{
14+
auto const key = attribute.substr(0, equal);
15+
auto const value = attribute.substr(equal + 1);
16+
interpolation->attributes[key] = value;
17+
}
18+
else
19+
{
20+
interpolation->flags.insert(attribute);
21+
}
22+
}
23+
} // anonymous namespace
24+
25+
string_interpolation parse_interpolation(std::string_view text)
26+
{
27+
auto result = string_interpolation {};
28+
auto const colon = text.find(':');
29+
if (colon != std::string_view::npos)
30+
{
31+
result.name = text.substr(0, colon);
32+
auto const attributes = text.substr(colon + 1);
33+
size_t pos = 0;
34+
while (pos < attributes.size())
35+
{
36+
auto const comma = attributes.find(',', pos);
37+
if (comma == std::string_view::npos)
38+
{
39+
parse_attribute(&result, attributes.substr(pos));
40+
break;
41+
}
42+
else
43+
{
44+
parse_attribute(&result, attributes.substr(pos, comma - pos));
45+
pos = comma + 1;
46+
}
47+
}
48+
}
49+
else
50+
{
51+
result.name = text;
52+
}
53+
return result;
54+
}
55+
56+
interpolated_string parse_interpolated_string(std::string_view text)
57+
{
58+
// "< {Clock:Bold,Italic,Color=#FFFF00} | {VTType} | {InputMode} {Search:Bold,Color=Yellow} >"
59+
60+
auto fragments = interpolated_string {};
61+
62+
size_t pos = 0;
63+
while (pos < text.size())
64+
{
65+
auto const openBrace = text.find('{', pos);
66+
if (openBrace == std::string_view::npos)
67+
{
68+
// no more open braces found, so we're done.
69+
fragments.emplace_back(text.substr(pos));
70+
return fragments;
71+
}
72+
73+
if (auto const textFragment = text.substr(pos, openBrace - pos); !textFragment.empty())
74+
// add text fragment before the open brace
75+
fragments.emplace_back(textFragment);
76+
77+
auto const closeBrace = text.find('}', openBrace);
78+
if (closeBrace == std::string_view::npos)
79+
{
80+
// no matching close brace found, so we're done.
81+
fragments.emplace_back(parse_interpolation(text.substr(openBrace)));
82+
return fragments;
83+
}
84+
else
85+
{
86+
// add interpolation fragment
87+
auto const fragment = text.substr(openBrace + 1, closeBrace - openBrace - 1);
88+
fragments.emplace_back(parse_interpolation(fragment));
89+
pos = closeBrace + 1;
90+
}
91+
}
92+
93+
return fragments;
94+
}
95+
96+
} // namespace crispy

0 commit comments

Comments
 (0)