/* Given a map from key to value, add values to out of the form key=value */ static void export_func(const std::map<wcstring, wcstring> &envs, std::vector<std::string> &out) { out.reserve(out.size() + envs.size()); std::map<wcstring, wcstring>::const_iterator iter; for (iter = envs.begin(); iter != envs.end(); ++iter) { const wcstring &key = iter->first; const std::string &ks = wcs2string(key); std::string vs = wcs2string(iter->second); /* Arrays in the value are ASCII record separator (0x1e) delimited. But some variables should have colons. Add those. */ if (variable_is_colon_delimited_array(key)) { /* Replace ARRAY_SEP with colon */ std::replace(vs.begin(), vs.end(), (char)ARRAY_SEP, ':'); } /* Put a string on the vector */ out.push_back(std::string()); std::string &str = out.back(); str.reserve(ks.size() + 1 + vs.size()); /* Append our environment variable data to it */ str.append(ks); str.append("="); str.append(vs); } }
static void export_func(const std::map<wcstring, wcstring> &envs, std::vector<std::string> &out) { std::map<wcstring, wcstring>::const_iterator iter; for (iter = envs.begin(); iter != envs.end(); ++iter) { const std::string ks = wcs2string(iter->first); std::string vs = wcs2string(iter->second); for (size_t i=0; i < vs.size(); i++) { char &vc = vs.at(i); if (vc == ARRAY_SEP) vc = ':'; } /* Put a string on the vector */ out.push_back(std::string()); std::string &str = out.back(); str.reserve(ks.size() + 1 + vs.size()); /* Append our environment variable data to it */ str.append(ks); str.append("="); str.append(vs); } }
/// Properly sets all locale information. static void handle_locale(const wchar_t *env_var_name) { debug(2, L"handle_locale() called in response to '%ls' changing", env_var_name); const char *old_msg_locale = setlocale(LC_MESSAGES, NULL); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &value = wcs2string(val); const std::string &name = wcs2string(env_var_name); debug(2, L"locale var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } char *locale = setlocale(LC_ALL, ""); fish_setlocale(); debug(2, L"handle_locale() setlocale(): '%s'", locale); const char *new_msg_locale = setlocale(LC_MESSAGES, NULL); debug(3, L"old LC_MESSAGES locale: '%s'", old_msg_locale); debug(3, L"new LC_MESSAGES locale: '%s'", new_msg_locale); #ifdef HAVE__NL_MSG_CAT_CNTR if (strcmp(old_msg_locale, new_msg_locale)) { // Make change known to GNU gettext. extern int _nl_msg_cat_cntr; _nl_msg_cat_cntr++; } #endif }
/// Properly sets all timezone information. static void handle_timezone(const wchar_t *env_var_name) { debug(2, L"handle_timezone() called in response to '%ls' changing", env_var_name); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &value = wcs2string(val); const std::string &name = wcs2string(env_var_name); debug(2, L"timezone var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } tzset(); }
static bool load_or_save_variables(bool save) { const wcstring wdir = fishd_get_config(); const std::string dir = wcs2string(wdir); if (dir.empty()) return false; const std::string machine_id = get_machine_identifier(); const std::string machine_id_path = get_variables_file_path(dir, machine_id); bool success = load_or_save_variables_at_path(save, machine_id_path); if (! success && ! save && errno == ENOENT) { /* We failed to load, because the file was not found. Older fish used the hostname only. Try *moving* the filename based on the hostname into place; if that succeeds try again. Silently "upgraded." */ std::string hostname_id; if (get_hostname_identifier(&hostname_id) && hostname_id != machine_id) { std::string hostname_path = get_variables_file_path(dir, hostname_id); if (0 == rename(hostname_path.c_str(), machine_id_path.c_str())) { /* We renamed - try again */ success = load_or_save_variables_at_path(save, machine_id_path); } } } return success; }
/// Push all curses/terminfo env vars into the global environment where they can be found by those /// libraries. static void handle_curses(const wchar_t *env_var_name) { debug(2, L"handle_curses() called in response to '%ls' changing", env_var_name); const env_var_t val = env_get_string(env_var_name, ENV_EXPORT); const std::string &name = wcs2string(env_var_name); const std::string &value = wcs2string(val); debug(2, L"curses var %s='%s'", name.c_str(), value.c_str()); if (val.empty()) { unsetenv(name.c_str()); } else { setenv(name.c_str(), value.c_str(), 1); } // TODO: Modify input_init() to allow calling it when the terminfo env vars are dynamically // changed. At the present time it can be called just once. Also, we should really only do this // if the TERM var is set. // input_init(); }
void make_pipe(const wchar_t *test_path) { wcstring vars_path = test_path ? wcstring(test_path) : default_vars_path(); vars_path.append(L".notifier"); const std::string narrow_path = wcs2string(vars_path); int fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); if (fd < 0 && errno == ENOENT) { /* File doesn't exist, try creating it */ if (mkfifo(narrow_path.c_str(), 0600) >= 0) { fd = wopen_cloexec(vars_path, O_RDWR | O_NONBLOCK, 0600); } } if (fd < 0) { // Maybe open failed, maybe mkfifo failed int err = errno; report_error(err, L"Unable to make or open a FIFO for universal variables with path '%ls'", vars_path.c_str()); } else { pipe_fd = fd; } }
wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { cstring narrow_path = wcs2string(pathname); char *narrow_res = realpath(narrow_path.c_str(), NULL); if (!narrow_res) return NULL; wchar_t *res; wcstring wide_res = str2wcstring(narrow_res); if (resolved_path) { wcslcpy(resolved_path, wide_res.c_str(), PATH_MAX); res = resolved_path; } else { res = wcsdup(wide_res.c_str()); } #if __APPLE__ && __DARWIN_C_LEVEL < 200809L // OS X Snow Leopard is broken with respect to the dynamically allocated buffer returned by // realpath(). It's not dynamically allocated so attempting to free that buffer triggers a // malloc/free error. Thus we don't attempt the free in this case. #else free(narrow_res); #endif return res; }
wcstring wgettext2(const wcstring &in) { wgettext_init_if_necessary(); std::string mbs_in = wcs2string(in); char *out = gettext(mbs_in.c_str()); wcstring result = format_string(L"%s", out); return result; }
/* Helper function to convert from a null_terminated_array_t<wchar_t> to a null_terminated_array_t<char_t> */ null_terminated_array_t<char> convert_wide_array_to_narrow(const null_terminated_array_t<wchar_t> &wide_arr) { const wchar_t *const *arr = wide_arr.get(); if (! arr) return null_terminated_array_t<char>(); std::vector<std::string> list; for (size_t i=0; arr[i]; i++) { list.push_back(wcs2string(arr[i])); } return null_terminated_array_t<char>(list); }
wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { if (pathname.size() == 0) return NULL; cstring real_path(""); cstring narrow_path = wcs2string(pathname); // Strip trailing slashes. This is needed to be bug-for-bug compatible with GNU realpath which // treats "/a//" as equivalent to "/a" whether or not /a exists. while (narrow_path.size() > 1 && narrow_path.at(narrow_path.size() - 1) == '/') { narrow_path.erase(narrow_path.size() - 1, 1); } char *narrow_res = realpath(narrow_path.c_str(), NULL); if (narrow_res) { real_path.append(narrow_res); } else { size_t pathsep_idx = narrow_path.rfind('/'); if (pathsep_idx == 0) { // If the only pathsep is the first character then it's an absolute path with a // single path component and thus doesn't need conversion. real_path = narrow_path; } else { if (pathsep_idx == cstring::npos) { // No pathsep means a single path component relative to pwd. narrow_res = realpath(".", NULL); if (!narrow_res) DIE("unexpected realpath(\".\") failure"); pathsep_idx = 0; } else { // Only call realpath() on the portion up to the last component. narrow_res = realpath(narrow_path.substr(0, pathsep_idx).c_str(), NULL); if (!narrow_res) return NULL; pathsep_idx++; } real_path.append(narrow_res); // This test is to deal with pathological cases such as /../../x => //x. if (real_path.size() > 1) real_path.append("/"); real_path.append(narrow_path.substr(pathsep_idx, cstring::npos)); } } #if __APPLE__ && __DARWIN_C_LEVEL < 200809L // OS X Snow Leopard is broken with respect to the dynamically allocated buffer returned by // realpath(). It's not dynamically allocated so attempting to free that buffer triggers a // malloc/free error. Thus we don't attempt the free in this case. #else free(narrow_res); #endif wcstring wreal_path = str2wcstring(real_path); if (resolved_path) { wcslcpy(resolved_path, wreal_path.c_str(), PATH_MAX); return resolved_path; } return wcsdup(wreal_path.c_str()); }
static wcstring default_vars_path() { wcstring wdir = fishd_get_config(); const std::string dir = wcs2string(wdir); if (dir.empty()) return L""; const std::string machine_id = get_machine_identifier(); const std::string machine_id_path = get_variables_file_path(dir, machine_id); return str2wcstring(machine_id_path); }
static std::string html_colorize(const wcstring &text, const std::vector<highlight_spec_t> &colors) { if (text.empty()) { return ""; } assert(colors.size() == text.size()); wcstring html = L"<pre><code>"; highlight_spec_t last_color = highlight_spec_normal; for (size_t i = 0; i < text.size(); i++) { // Handle colors. highlight_spec_t color = colors.at(i); if (i > 0 && color != last_color) { html.append(L"</span>"); } if (i == 0 || color != last_color) { append_format(html, L"<span class=\"%ls\">", html_class_name_for_color(color)); } last_color = color; // Handle text. wchar_t wc = text.at(i); switch (wc) { case L'&': { html.append(L"&"); break; } case L'\'': { html.append(L"'"); break; } case L'"': { html.append(L"""); break; } case L'<': { html.append(L"<"); break; } case L'>': { html.append(L">"); break; } default: { html.push_back(wc); break; } } } html.append(L"</span></code></pre>"); return wcs2string(html); }
bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir) { struct dirent *d = readdir(dir); if (!d) return false; out_name = str2wcstring(d->d_name); if (out_is_dir) { /* The caller cares if this is a directory, so check */ bool is_dir = false; /* We may be able to skip stat, if the readdir can tell us the file type directly */ bool check_with_stat = true; #ifdef HAVE_STRUCT_DIRENT_D_TYPE if (d->d_type == DT_DIR) { /* Known directory */ is_dir = true; check_with_stat = false; } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { /* We want to treat symlinks to directories as directories. Use stat to resolve it. */ check_with_stat = true; } else { /* Regular file */ is_dir = false; check_with_stat = false; } #endif // HAVE_STRUCT_DIRENT_D_TYPE if (check_with_stat) { /* We couldn't determine the file type from the dirent; check by stat'ing it */ cstring fullpath = wcs2string(dir_path); fullpath.push_back('/'); fullpath.append(d->d_name); struct stat buf; if (stat(fullpath.c_str(), &buf) != 0) { is_dir = false; } else { is_dir = !!(S_ISDIR(buf.st_mode)); } } *out_is_dir = is_dir; } return true; }
/* Output our YAML to a file */ bool history_lru_node_t::write_yaml_to_file(FILE *f) const { std::string cmd = wcs2string(key); escape_yaml(cmd); if (fprintf(f, "- cmd: %s\n", cmd.c_str()) < 0) return false; if (fprintf(f, " when: %ld\n", (long)timestamp) < 0) return false; if (! required_paths.empty()) { if (fputs(" paths:\n", f) < 0) return false; for (path_list_t::const_iterator iter = required_paths.begin(); iter != required_paths.end(); ++iter) { std::string path = wcs2string(*iter); escape_yaml(path); if (fprintf(f, " - %s\n", path.c_str()) < 0) return false; } } return true; }
const wchar_t *wgetenv(const wcstring &name) { ASSERT_IS_MAIN_THREAD(); cstring name_narrow = wcs2string(name); char *res_narrow = getenv(name_narrow.c_str()); static wcstring out; if (!res_narrow) return 0; out = format_string(L"%s", res_narrow); return out.c_str(); }
/** Attempts tilde expansion of the string specified, modifying it in place. */ static void expand_home_directory(wcstring &input) { const wchar_t * const in = input.c_str(); if (in[0] == HOME_DIRECTORY) { int tilde_error = 0; size_t tail_idx; wcstring home; if (in[1] == '/' || in[1] == '\0') { /* Current users home directory */ home = env_get_string(L"HOME"); tail_idx = 1; } else { /* Some other users home directory */ const wchar_t *name_end = wcschr(in, L'/'); if (name_end) { tail_idx = name_end - in; } else { tail_idx = wcslen(in); } wcstring name_str = input.substr(1, tail_idx - 1); std::string name_cstr = wcs2string(name_str); struct passwd *userinfo = getpwnam(name_cstr.c_str()); if (userinfo == NULL) { tilde_error = 1; input[0] = L'~'; } else { home = str2wcstring(userinfo->pw_dir); } } if (! tilde_error) { input.replace(input.begin(), input.begin() + tail_idx, home); } } }
/** Load or save all variables */ static void load_or_save( int save) { const wcstring wdir = fishd_get_config(); char hostname[HOSTNAME_LEN]; connection_t c; int fd; if (wdir.empty()) return; std::string dir = wcs2string( wdir ); gethostname( hostname, HOSTNAME_LEN ); std::string name; name.append(dir); name.append("/"); name.append(FILE); name.append(hostname); debug( 4, L"Open file for %s: '%s'", save?"saving":"loading", name.c_str() ); /* OK to not use CLO_EXEC here because fishd is single threaded */ fd = open(name.c_str(), save?(O_CREAT | O_TRUNC | O_WRONLY):O_RDONLY, 0600); if( fd == -1 ) { debug( 1, L"Could not open load/save file. No previous saves?" ); wperror( L"open" ); return; } debug( 4, L"File open on fd %d", c.fd ); connection_init( &c, fd ); if( save ) { write_loop( c.fd, SAVE_MSG, strlen(SAVE_MSG) ); enqueue_all( &c ); } else read_message( &c ); connection_destroy( &c ); }
wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { cstring tmp = wcs2string(pathname); char narrow_buff[PATH_MAX]; char *narrow_res = realpath(tmp.c_str(), narrow_buff); wchar_t *res; if (!narrow_res) return 0; const wcstring wide_res = str2wcstring(narrow_res); if (resolved_path) { wcslcpy(resolved_path, wide_res.c_str(), PATH_MAX); res = resolved_path; } else { res = wcsdup(wide_res.c_str()); } return res; }
/// Evaluate math expressions. static int evaluate_expression(const wchar_t *cmd, parser_t &parser, io_streams_t &streams, math_cmd_opts_t &opts, wcstring &expression) { UNUSED(parser); int retval = STATUS_CMD_OK; te_error_t error; std::string narrow_str = wcs2string(expression); // Switch locale while computing stuff. // This means that the "." is always the radix character, // so numbers work the same across locales. char *saved_locale = strdup(setlocale(LC_NUMERIC, NULL)); setlocale(LC_NUMERIC, "C"); double v = te_interp(narrow_str.c_str(), &error); if (error.position == 0) { // Check some runtime errors after the fact. // TODO: Really, this should be done in tinyexpr // (e.g. infinite is the result of "x / 0"), // but that's much more work. const char *error_message = NULL; if (std::isinf(v)) { error_message = "Result is infinite"; } else if (std::isnan(v)) { error_message = "Result is not a number"; } else if (std::abs(v) >= kMaximumContiguousInteger) { error_message = "Result magnitude is too large"; } if (error_message) { streams.err.append_format(L"%ls: Error: %s\n", cmd, error_message); streams.err.append_format(L"'%ls'\n", expression.c_str()); retval = STATUS_CMD_ERROR; } else { streams.out.append(format_double(v, opts)); streams.out.push_back(L'\n'); } } else { streams.err.append_format(L"%ls: Error: %ls\n", cmd, math_describe_error(error).c_str()); streams.err.append_format(L"'%ls'\n", expression.c_str()); streams.err.append_format(L"%*ls%ls\n", error.position - 1, L" ",L"^"); retval = STATUS_CMD_ERROR; } setlocale(LC_NUMERIC, saved_locale); free(saved_locale); return retval; }
const wcstring &wgettext(const wchar_t *in) { // Preserve errno across this since this is often used in printing error messages. int err = errno; wcstring key = in; wgettext_init_if_necessary(); auto wmap = wgettext_map.acquire(); wcstring &val = wmap.value[key]; if (val.empty()) { cstring mbs_in = wcs2string(key); char *out = fish_gettext(mbs_in.c_str()); val = format_string(L"%s", out); } errno = err; // The returned string is stored in the map. // TODO: If we want to shrink the map, this would be a problem. return val; }
static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) { ASSERT_IS_NOT_FORKED_CHILD(); cstring tmp = wcs2string(pathname); int fd; #ifdef O_CLOEXEC // Prefer to use O_CLOEXEC. It has to both be defined and nonzero. if (cloexec) { fd = open(tmp.c_str(), flags | O_CLOEXEC, mode); } else { fd = open(tmp.c_str(), flags, mode); } #else fd = open(tmp.c_str(), flags, mode); if (fd >= 0 && !set_cloexec(fd)) { close(fd); fd = -1; } #endif return fd; }
static int wopen_internal(const wcstring &pathname, int flags, mode_t mode, bool cloexec) { ASSERT_IS_NOT_FORKED_CHILD(); cstring tmp = wcs2string(pathname); /* Prefer to use O_CLOEXEC. It has to both be defined and nonzero. */ #ifdef O_CLOEXEC if (cloexec && (O_CLOEXEC != 0)) { flags |= O_CLOEXEC; cloexec = false; } #endif int fd = ::open(tmp.c_str(), flags, mode); if (cloexec && fd >= 0 && ! set_cloexec(fd)) { close(fd); fd = -1; } return fd; }
bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir) { struct dirent *d = readdir(dir); if (!d) return false; out_name = str2wcstring(d->d_name); if (out_is_dir) { /* The caller cares if this is a directory, so check */ bool is_dir; if (d->d_type == DT_DIR) { is_dir = true; } else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) { /* We want to treat symlinks to directories as directories. Use stat to resolve it. */ cstring fullpath = wcs2string(dir_path); fullpath.push_back('/'); fullpath.append(d->d_name); struct stat buf; if (stat(fullpath.c_str(), &buf) != 0) { is_dir = false; } else { is_dir = !!(S_ISDIR(buf.st_mode)); } } else { is_dir = false; } *out_is_dir = is_dir; } return true; }
/// Return the key name if the recent sequence of characters matches a known terminfo sequence. char *const key_name(unsigned char c) { static char recent_chars[8] = {0}; recent_chars[0] = recent_chars[1]; recent_chars[1] = recent_chars[2]; recent_chars[2] = recent_chars[3]; recent_chars[3] = recent_chars[4]; recent_chars[4] = recent_chars[5]; recent_chars[5] = recent_chars[6]; recent_chars[6] = recent_chars[7]; recent_chars[7] = c; for (int idx = 7; idx >= 0; idx--) { wcstring out_name; wcstring seq = str2wcstring(recent_chars + idx, 8 - idx); bool found = input_terminfo_get_name(seq, &out_name); if (found) { return strdup(wcs2string(out_name).c_str()); } } return NULL; }
const wchar_t *wgettext(const wchar_t *in) { if (!in) return in; // preserve errno across this since this is often used in printing error messages int err = errno; wgettext_init_if_necessary(); wcstring key = in; scoped_lock lock(wgettext_lock); wcstring *& val = wgettext_map[key]; if (val == NULL) { cstring mbs_in = wcs2string(key); char *out = gettext(mbs_in.c_str()); val = new wcstring(format_string(L"%s", out)); } errno = err; return val->c_str(); }
const wchar_t *wgettext(const wchar_t *in) { if (!in) return in; // preserve errno across this since this is often used in printing error messages int err = errno; wgettext_init_if_necessary(); wcstring key = in; scoped_lock lock(wgettext_lock); wcstring *& val = wgettext_map[key]; if (val == NULL) { cstring mbs_in = wcs2string(key); char *out = fish_gettext(mbs_in.c_str()); val = new wcstring(format_string(L"%s", out)); //note that this writes into the map! } errno = err; return val->c_str(); //looks dangerous but is safe, since the string is stored in the map }
wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path) { cstring narrow_path = wcs2string(pathname); char *narrow_res = realpath(narrow_path.c_str(), NULL); if (!narrow_res) return NULL; wchar_t *res; wcstring wide_res = str2wcstring(narrow_res); if (resolved_path) { wcslcpy(resolved_path, wide_res.c_str(), PATH_MAX); res = resolved_path; } else { res = wcsdup(wide_res.c_str()); } free(narrow_res); return res; }
static std::string no_colorize(const wcstring &text) { return wcs2string(text); }
void s_write(screen_t *s, const wcstring &left_prompt, const wcstring &right_prompt, const wcstring &commandline, size_t explicit_len, const std::vector<highlight_spec_t> &colors, const std::vector<int> &indent, size_t cursor_pos, const page_rendering_t &pager, bool cursor_is_within_pager) { screen_data_t::cursor_t cursor_arr; // Turn the command line into the explicit portion and the autosuggestion. const wcstring explicit_command_line = commandline.substr(0, explicit_len); const wcstring autosuggestion = commandline.substr(explicit_len); // If we are using a dumb terminal, don't try any fancy stuff, just print out the text. // right_prompt not supported. if (is_dumb()) { const std::string prompt_narrow = wcs2string(left_prompt); const std::string command_line_narrow = wcs2string(explicit_command_line); write_loop(STDOUT_FILENO, "\r", 1); write_loop(STDOUT_FILENO, prompt_narrow.c_str(), prompt_narrow.size()); write_loop(STDOUT_FILENO, command_line_narrow.c_str(), command_line_narrow.size()); return; } s_check_status(s); const size_t screen_width = common_get_width(); // Completely ignore impossibly small screens. if (screen_width < 4) { return; } // Compute a layout. const screen_layout_t layout = compute_layout(s, screen_width, left_prompt, right_prompt, explicit_command_line, autosuggestion, indent); // Determine whether, if we have an autosuggestion, it was truncated. s->autosuggestion_is_truncated = !autosuggestion.empty() && autosuggestion != layout.autosuggestion; // Clear the desired screen. s->desired.resize(0); s->desired.cursor.x = s->desired.cursor.y = 0; // Append spaces for the left prompt. for (size_t i = 0; i < layout.left_prompt_space; i++) { s_desired_append_char(s, L' ', highlight_spec_t{}, 0, layout.left_prompt_space); } // If overflowing, give the prompt its own line to improve the situation. size_t first_line_prompt_space = layout.left_prompt_space; if (layout.prompts_get_own_line) { s_desired_append_char(s, L'\n', highlight_spec_t{}, 0, 0); first_line_prompt_space = 0; } // Reconstruct the command line. wcstring effective_commandline = explicit_command_line + layout.autosuggestion; // Output the command line. size_t i; for (i = 0; i < effective_commandline.size(); i++) { // Grab the current cursor's x,y position if this character matches the cursor's offset. if (!cursor_is_within_pager && i == cursor_pos) { cursor_arr = s->desired.cursor; } s_desired_append_char(s, effective_commandline.at(i), colors[i], indent[i], first_line_prompt_space); } // Cursor may have been at the end too. if (!cursor_is_within_pager && i == cursor_pos) { cursor_arr = s->desired.cursor; } // Now that we've output everything, set the cursor to the position that we saved in the loop // above. s->desired.cursor = cursor_arr; if (cursor_is_within_pager) { s->desired.cursor.x = (int)cursor_pos; s->desired.cursor.y = (int)s->desired.line_count(); } // Append pager_data (none if empty). s->desired.append_lines(pager.screen_data); s_update(s, layout.left_prompt, layout.right_prompt); s_save_status(s); }