Make status bar contents configurable

Started calling it a status bar rather than just a bar now.

Inventing our own format, because:
 - tmux doesn't have quite enough useful variables,
   besides #h/#{host_short}, #{USER}, and perhaps #{PWD}.
 - zsh uses weird names for everything.
 - bash uses mildly inconvenient \escapes, and lacks formatting.

Also bumping C++ to 17, because the project already requires it
to build anyway (it's a relict from the time of sdn's conception).
This commit is contained in:
2026-07-24 10:37:49 +02:00
parent b2701d93bd
commit 28dcbd8a2c
6 changed files with 104 additions and 10 deletions
+1 -1
View File
@@ -22,7 +22,7 @@ target_link_directories (${PROJECT_NAME}
PUBLIC ${NCURSESW_LIBRARY_DIRS} ${ACL_LIBRARY_DIRS})
target_link_libraries (${PROJECT_NAME}
PUBLIC ${NCURSESW_LIBRARIES} ${ACL_LIBRARIES})
target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_14)
target_compile_features (${PROJECT_NAME} PUBLIC cxx_std_17)
target_compile_definitions (${PROJECT_NAME} PUBLIC
PROJECT_NAME=\"${PROJECT_NAME}\" PROJECT_VERSION=\"${PROJECT_VERSION}\")
if (MSYS)
+1 -1
View File
@@ -1,6 +1,6 @@
.POSIX:
SHELL = /bin/sh
CXXFLAGS = -g -std=c++14 -Wall -Wextra -Wno-misleading-indentation -pedantic
CXXFLAGS = -g -std=c++17 -Wall -Wextra -Wno-misleading-indentation -pedantic
CPPFLAGS = `sed -ne '/^project (\([^ )]*\) VERSION \([^ )]*\).*/ \
s//-DPROJECT_NAME="\1" -DPROJECT_VERSION="\2"/p' CMakeLists.txt`
+4 -2
View File
@@ -1,5 +1,7 @@
Unreleased
* Added a configurable status bar template, also replacing the "cwd" attribute.
* Added C-b and C-f bindings for page up and page down navigation
in normal mode. The respective actions now move by visible lines
rather than by the full height of the terminal.
@@ -7,10 +9,10 @@ Unreleased
* Added an sdn-edit script analogous to sdn-view, bound it to F4,
and moved the original key binding to F14.
* Fixed zsh 5.9.2 compatibility in sdn-install.
* Renamed "ic" and "dc" keys to "insert" and "delete" respectively.
* Fixed zsh 5.9.2 compatibility in sdn-install.
* Fixed configuration initialisation possibly not working the first time.
* Fixed %view{hex} not working in the mc.ext.ini parser.
+8 -1
View File
@@ -79,11 +79,18 @@ that of git, only named colours aren't supported:
cursor 231 202
select 202 bold
bar 16 255 ul
cwd bold
input
cmdline 145
....
The left side of the status bar defaults to the current working directory.
Add a line such as the following to '~/.config/sdn/config' to show
'user@host' with a tilde-shortened path instead:
....
status '%u@%h %B%~%b'
....
Filename colours are taken from the `LS_COLORS` environment variable.
Run `dircolors` to get some defaults.
+30 -1
View File
@@ -57,6 +57,28 @@ However it also enforces any pending change to the shell's working directory.
The zero-based index of the
.Ql full-view
column that entries are ordered by.
.It status Em string
Template for the left side of the status bar.
The following escapes are supported:
.Bl -tag -width 7n -compact
.It %H
Full hostname.
.It %h
Hostname up to the first dot.
.It %u
Username.
.It %d
Current working directory.
.It %~
Current working directory with
.Ev HOME
replaced by a tilde.
.It \&%B , \&%b
Turn bold on and off for subsequent text.
.It %%
A literal percent sign.
.El
Unknown sequences are reproduced literally.
.El
.Sh ENVIRONMENT
.Bl -tag -width 15n
@@ -87,6 +109,14 @@ Custom key binding overrides.
Redefine terminal attributes for UI elements.
.El
.Sh EXAMPLES
.Ss Pa config
The left side of the status bar defaults to the current working directory.
To show, e.g.,
.Ar user@host
followed by a bold tilde-shortened path:
.Bd -literal -offset indent
status '%u@%h %B%~%b'
.Ed
.Ss Pa bindings
Key names or combinations follow the Emacs syntax for Control and Meta prefixes
and
@@ -122,7 +152,6 @@ following may work:
cursor 231 202
select 202 bold
bar 16 255 ul
cwd bold
input
cmdline 145
.Ed
+60 -4
View File
@@ -149,6 +149,30 @@ fun untilde (const string &path) -> string {
return path;
}
fun tildeify (const string &path) -> string {
const char *home = getenv ("HOME");
if (!home) {
if (const auto *pw = getpwuid (getuid ()))
home = pw->pw_dir;
}
if (home && *home) {
size_t len = strlen (home);
if (!path.compare (0, len, home) && strchr ("/", path[len]))
return "~" + path.substr (len);
}
return path;
}
fun get_user () -> const char * {
if (auto user = getenv ("USER"))
return user;
if (auto user = getenv ("LOGNAME"))
return user;
if (const auto *pw = getpwuid (getuid ()))
return pw->pw_name;
return "";
}
fun needs_shell_quoting (const string &v) -> bool {
// IEEE Std 1003.1 sh + the exclamation mark because of csh/bash
// history expansion, implicitly also the NUL character
@@ -558,6 +582,7 @@ struct level {
static struct {
ncstring cmdline; ///< Outer command line
string cwd; ///< Current working directory
wstring status = L"%d"; ///< Status bar template
string start_dir; ///< Starting directory
vector<entry> entries; ///< Current directory entries
set<string> selection; ///< Filenames of selected entries
@@ -591,11 +616,11 @@ static struct {
void (*editor_on_change) (); ///< Callback on editor change
map<action, void (*) ()> editor_on; ///< Handlers for custom actions
enum { AT_CURSOR, AT_SELECT, AT_BAR, AT_CWD, AT_INPUT, AT_INFO, AT_CMDLINE,
enum { AT_CURSOR, AT_SELECT, AT_BAR, AT_INPUT, AT_INFO, AT_CMDLINE,
AT_COUNT };
chtype attrs[AT_COUNT] = {A_REVERSE, A_BOLD, 0, A_BOLD, 0, A_ITALIC, 0};
chtype attrs[AT_COUNT] = {A_REVERSE, A_BOLD, 0, 0, A_ITALIC, 0};
const char *attr_names[AT_COUNT] =
{"cursor", "select", "bar", "cwd", "input", "info", "cmdline"};
{"cursor", "select", "bar", "input", "info", "cmdline"};
map<int, chtype> ls_colors; ///< LS_COLORS decoded
map<string, chtype> ls_exts; ///< LS_COLORS file extensions
@@ -779,6 +804,34 @@ fun make_entry (const struct dirent *f) -> entry {
return e;
}
fun format_status (const wstring &tpl) -> ncstring {
char hostname[256] = "";
(void) gethostname (hostname, sizeof hostname);
string host_short = hostname;
if (auto dot = host_short.find ('.'); dot != string::npos)
host_short.resize (dot);
ncstring s; chtype attrs = 0;
for (size_t i = 0; i < tpl.size (); i++) {
if (tpl[i] != L'%' || i + 1 >= tpl.size ()) {
s += apply_attrs (tpl.substr (i, 1), attrs);
continue;
}
switch (tpl[++i]) {
case L'%': s += apply_attrs (L"%", attrs); break;
case L'H': s += apply_attrs (to_wide (hostname), attrs); break;
case L'h': s += apply_attrs (to_wide (host_short), attrs); break;
case L'u': s += apply_attrs (to_wide (get_user ()), attrs); break;
case L'd': s += apply_attrs (to_wide (g.cwd), attrs); break;
case L'~': s += apply_attrs (to_wide (tildeify (g.cwd)), attrs); break;
case L'B': attrs |= A_BOLD; break;
case L'b': attrs &= ~A_BOLD; break;
default: s += apply_attrs (tpl.substr (i - 1, 2), attrs);
}
}
return s;
}
fun inline visible_lines () -> int { return max (0, LINES - 2); }
fun update () {
@@ -815,7 +868,7 @@ fun update () {
hline (' ', COLS - width);
}
auto bar = apply_attrs (to_wide (g.cwd), g.attrs[g.AT_CWD]);
auto bar = format_status (g.status);
if (!g.show_hidden)
bar += apply_attrs (L" (hidden)", 0);
if (g.out_of_date)
@@ -2029,6 +2082,8 @@ fun load_config () {
g.ext_helpers = tokens.at (1) == "1";
else if (tokens.front () == "sort-column" && tokens.size () > 1)
g.sort_column = stoi (tokens.at (1));
else if (tokens.front () == "status" && tokens.size () > 1)
g.status = to_wide (tokens.at (1));
else if (tokens.front () == "history")
load_history_level (tokens);
}
@@ -2046,6 +2101,7 @@ fun save_config () {
write_line (*config, {"ext-helpers", g.ext_helpers ? "1" : "0"});
write_line (*config, {"sort-column", to_string (g.sort_column)});
write_line (*config, {"status", to_mb (g.status)});
char hostname[256];
if (gethostname (hostname, sizeof hostname))