Beispiel #1
0
static size_t calc_prompt_lines(const wcstring &prompt) {
    // Hack for the common case where there's no newline at all. I don't know if a newline can
    // appear in an escape sequence, so if we detect a newline we have to defer to
    // calc_prompt_width_and_lines.
    size_t result = 1;
    if (prompt.find(L'\n') != wcstring::npos || prompt.find(L'\f') != wcstring::npos) {
        result = calc_prompt_layout(prompt, cached_layouts).line_count;
    }
    return result;
}
Beispiel #2
0
static screen_layout_t compute_layout(screen_t *s, size_t screen_width,
                                      const wcstring &left_prompt_str,
                                      const wcstring &right_prompt_str, const wcstring &commandline,
                                      const wcstring &autosuggestion_str,
                                      const std::vector<int> &indent) {
    UNUSED(s);
    screen_layout_t result = {};

    // Start by ensuring that the prompts themselves can fit.
    const wchar_t *left_prompt = left_prompt_str.c_str();
    const wchar_t *right_prompt = right_prompt_str.c_str();
    const wchar_t *autosuggestion = autosuggestion_str.c_str();

    prompt_layout_t left_prompt_layout = calc_prompt_layout(left_prompt_str, cached_layouts);
    prompt_layout_t right_prompt_layout = calc_prompt_layout(right_prompt_str, cached_layouts);

    size_t left_prompt_width = left_prompt_layout.last_line_width;
    size_t right_prompt_width = right_prompt_layout.last_line_width;

    if (left_prompt_layout.max_line_width > screen_width) {
        // If we have a multi-line prompt, see if the longest line fits; if not neuter the whole
        // left prompt.
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    if (left_prompt_width + right_prompt_width >= screen_width) {
        // Nix right_prompt.
        right_prompt = L"";
        right_prompt_width = 0;
    }

    if (left_prompt_width + right_prompt_width >= screen_width) {
        // Still doesn't fit, neuter left_prompt.
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    // Now we should definitely fit.
    assert(left_prompt_width + right_prompt_width < screen_width);

    // Convert commandline to a list of lines and their widths.
    wcstring_list_t command_lines(1);
    std::vector<size_t> line_widths(1);
    for (size_t i = 0; i < commandline.size(); i++) {
        wchar_t c = commandline.at(i);
        if (c == L'\n') {
            // Make a new line.
            command_lines.push_back(wcstring());
            line_widths.push_back(indent.at(i) * INDENT_STEP);
        } else {
            command_lines.back() += c;
            line_widths.back() += fish_wcwidth_min_0(c);
        }
    }
    const size_t first_command_line_width = line_widths.at(0);

    // If we have more than one line, ensure we have no autosuggestion.
    if (command_lines.size() > 1) {
        autosuggestion = L"";
    }

    // Compute the width of the autosuggestion at all possible truncation offsets.
    std::vector<size_t> autosuggest_truncated_widths;
    autosuggest_truncated_widths.reserve(1 + std::wcslen(autosuggestion));
    size_t autosuggest_total_width = 0;
    for (size_t i = 0; autosuggestion[i] != L'\0'; i++) {
        autosuggest_truncated_widths.push_back(autosuggest_total_width);
        autosuggest_total_width += fish_wcwidth_min_0(autosuggestion[i]);
    }

    // Here are the layouts we try in turn:
    //
    // 1. Left prompt visible, right prompt visible, command line visible, autosuggestion visible.
    //
    // 2. Left prompt visible, right prompt visible, command line visible, autosuggestion truncated
    // (possibly to zero).
    //
    // 3. Left prompt visible, right prompt hidden, command line visible, autosuggestion hidden.
    //
    // 4. Newline separator (left prompt visible, right prompt hidden, command line visible,
    // autosuggestion visible).
    //
    // A remark about layout #4: if we've pushed the command line to a new line, why can't we draw
    // the right prompt? The issue is resizing: if you resize the window smaller, then the right
    // prompt will wrap to the next line. This means that we can't go back to the line that we were
    // on, and things turn to chaos very quickly.
    size_t calculated_width;
    bool done = false;

    // Case 1
    if (!done) {
        calculated_width = left_prompt_width + right_prompt_width + first_command_line_width +
                           autosuggest_total_width;
        if (calculated_width < screen_width) {
            result.left_prompt = left_prompt;
            result.left_prompt_space = left_prompt_width;
            result.right_prompt = right_prompt;
            result.autosuggestion = autosuggestion;
            done = true;
        }
    }

    // Case 2. Note that we require strict inequality so that there's always at least one space
    // between the left edge and the rprompt.
    if (!done) {
        calculated_width = left_prompt_width + right_prompt_width + first_command_line_width;
        if (calculated_width < screen_width) {
            result.left_prompt = left_prompt;
            result.left_prompt_space = left_prompt_width;
            result.right_prompt = right_prompt;

            // Need at least two characters to show an autosuggestion.
            size_t available_autosuggest_space =
                screen_width - (left_prompt_width + right_prompt_width + first_command_line_width);
            if (autosuggest_total_width > 0 && available_autosuggest_space > 2) {
                size_t truncation_offset = truncation_offset_for_width(
                    autosuggest_truncated_widths, available_autosuggest_space - 2);
                result.autosuggestion = wcstring(autosuggestion, truncation_offset);
                result.autosuggestion.push_back(ellipsis_char);
            }
            done = true;
        }
    }

    // Case 3
    if (!done) {
        calculated_width = left_prompt_width + first_command_line_width;
        if (calculated_width < screen_width) {
            result.left_prompt = left_prompt;
            result.left_prompt_space = left_prompt_width;
            done = true;
        }
    }

    // Case 4
    if (!done) {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        // See remark about for why we can't use the right prompt here result.right_prompt =
        // right_prompt. If the command wraps, and the prompt is not short, place the command on its
        // own line. A short prompt is 33% or less of the terminal's width.
        const size_t prompt_percent_width = (100 * left_prompt_width) / screen_width;
        if (left_prompt_width + first_command_line_width + 1 > screen_width &&
            prompt_percent_width > 33) {
            result.prompts_get_own_line = true;
        }
    }

    return result;
}
Beispiel #3
0
/// Update the screen to match the desired output.
static void s_update(screen_t *scr, const wcstring &left_prompt, const wcstring &right_prompt) {
    const environment_t &vars = env_stack_t::principal();
    const scoped_buffer_t buffering(*scr);
    const size_t left_prompt_width =
        calc_prompt_layout(left_prompt, cached_layouts).last_line_width;
    const size_t right_prompt_width =
        calc_prompt_layout(right_prompt, cached_layouts).last_line_width;

    int screen_width = common_get_width();

    // Figure out how many following lines we need to clear (probably 0).
    size_t actual_lines_before_reset = scr->actual_lines_before_reset;
    scr->actual_lines_before_reset = 0;

    bool need_clear_lines = scr->need_clear_lines;
    bool need_clear_screen = scr->need_clear_screen;
    bool has_cleared_screen = false;

    if (scr->actual_width != screen_width) {
        // Ensure we don't issue a clear screen for the very first output, to avoid issue #402.
        if (scr->actual_width != SCREEN_WIDTH_UNINITIALIZED) {
            need_clear_screen = true;
            s_move(scr, 0, 0);
            s_reset(scr, screen_reset_current_line_contents);

            need_clear_lines = need_clear_lines || scr->need_clear_lines;
            need_clear_screen = need_clear_screen || scr->need_clear_screen;
        }
        scr->actual_width = screen_width;
    }

    scr->need_clear_lines = false;
    scr->need_clear_screen = false;

    // Determine how many lines have stuff on them; we need to clear lines with stuff that we don't
    // want.
    const size_t lines_with_stuff = std::max(actual_lines_before_reset, scr->actual.line_count());

    if (left_prompt != scr->actual_left_prompt) {
        s_move(scr, 0, 0);
        s_write_str(scr, left_prompt.c_str());
        scr->actual_left_prompt = left_prompt;
        scr->actual.cursor.x = (int)left_prompt_width;
    }

    for (size_t i = 0; i < scr->desired.line_count(); i++) {
        const line_t &o_line = scr->desired.line(i);
        line_t &s_line = scr->actual.create_line(i);
        size_t start_pos = i == 0 ? left_prompt_width : 0;
        int current_width = 0;

        // If this is the last line, maybe we should clear the screen.
        const bool should_clear_screen_this_line =
            need_clear_screen && i + 1 == scr->desired.line_count() && clr_eos != NULL;

        // Note that skip_remaining is a width, not a character count.
        size_t skip_remaining = start_pos;

        if (!should_clear_screen_this_line) {
            // Compute how much we should skip. At a minimum we skip over the prompt. But also skip
            // over the shared prefix of what we want to output now, and what we output before, to
            // avoid repeatedly outputting it.
            const size_t shared_prefix = line_shared_prefix(o_line, s_line);
            if (shared_prefix > 0) {
                size_t prefix_width = fish_wcswidth(&o_line.text.at(0), shared_prefix);
                if (prefix_width > skip_remaining) skip_remaining = prefix_width;
            }

            // If we're soft wrapped, and if we're going to change the first character of the next
            // line, don't skip over the last two characters so that we maintain soft-wrapping.
            if (o_line.is_soft_wrapped && i + 1 < scr->desired.line_count()) {
                bool next_line_will_change = true;
                if (i + 1 < scr->actual.line_count()) {  //!OCLINT
                    if (line_shared_prefix(scr->desired.line(i + 1), scr->actual.line(i + 1)) > 0) {
                        next_line_will_change = false;
                    }
                }
                if (next_line_will_change) {
                    skip_remaining = std::min(skip_remaining, (size_t)(scr->actual_width - 2));
                }
            }
        }

        // Skip over skip_remaining width worth of characters.
        size_t j = 0;
        for (; j < o_line.size(); j++) {
            size_t width = fish_wcwidth_min_0(o_line.char_at(j));
            if (skip_remaining < width) break;
            skip_remaining -= width;
            current_width += width;
        }

        // Skip over zero-width characters (e.g. combining marks at the end of the prompt).
        for (; j < o_line.size(); j++) {
            int width = fish_wcwidth_min_0(o_line.char_at(j));
            if (width > 0) break;
        }

        // Now actually output stuff.
        for (; j < o_line.size(); j++) {
            // If we are about to output into the last column, clear the screen first. If we clear
            // the screen after we output into the last column, it can erase the last character due
            // to the sticky right cursor. If we clear the screen too early, we can defeat soft
            // wrapping.
            if (j + 1 == (size_t)screen_width && should_clear_screen_this_line &&
                !has_cleared_screen) {
                s_move(scr, current_width, (int)i);
                s_write_mbs(scr, clr_eos);
                has_cleared_screen = true;
            }

            perform_any_impending_soft_wrap(scr, current_width, (int)i);
            s_move(scr, current_width, (int)i);
            s_set_color(scr, vars, o_line.color_at(j));
            s_write_char(scr, o_line.char_at(j));
            current_width += fish_wcwidth_min_0(o_line.char_at(j));
        }

        // Clear the screen if we have not done so yet.
        if (should_clear_screen_this_line && !has_cleared_screen) {
            s_move(scr, current_width, (int)i);
            s_write_mbs(scr, clr_eos);
            has_cleared_screen = true;
        }

        bool clear_remainder = false;
        // Clear the remainder of the line if we need to clear and if we didn't write to the end of
        // the line. If we did write to the end of the line, the "sticky right edge" (as part of
        // auto_right_margin) means that we'll be clearing the last character we wrote!
        if (has_cleared_screen) {
            // Already cleared everything.
            clear_remainder = false;
        } else if (need_clear_lines && current_width < screen_width) {
            clear_remainder = true;
        } else if (right_prompt_width < scr->last_right_prompt_width) {
            clear_remainder = true;
        } else {
            int prev_width =
                s_line.text.empty() ? 0 : fish_wcswidth(&s_line.text.at(0), s_line.text.size());
            clear_remainder = prev_width > current_width;
        }
        if (clear_remainder && clr_eol) {
            s_set_color(scr, vars, highlight_spec_t{});
            s_move(scr, current_width, (int)i);
            s_write_mbs(scr, clr_eol);
        }

        // Output any rprompt if this is the first line.
        if (i == 0 && right_prompt_width > 0) {  //!OCLINT(Use early exit/continue)
            s_move(scr, (int)(screen_width - right_prompt_width), (int)i);
            s_set_color(scr, vars, highlight_spec_t{});
            s_write_str(scr, right_prompt.c_str());
            scr->actual.cursor.x += right_prompt_width;

            // We output in the last column. Some terms (Linux) push the cursor further right, past
            // the window. Others make it "stick." Since we don't really know which is which, issue
            // a cr so it goes back to the left.
            //
            // However, if the user is resizing the window smaller, then it's possible the cursor
            // wrapped. If so, then a cr will go to the beginning of the following line! So instead
            // issue a bunch of "move left" commands to get back onto the line, and then jump to the
            // front of it.
            s_move(scr, scr->actual.cursor.x - (int)right_prompt_width, scr->actual.cursor.y);
            s_write_str(scr, L"\r");
            scr->actual.cursor.x = 0;
        }
    }

    // Clear remaining lines (if any) if we haven't cleared the screen.
    if (!has_cleared_screen && scr->desired.line_count() < lines_with_stuff && clr_eol) {
        s_set_color(scr, vars, highlight_spec_t{});
        for (size_t i = scr->desired.line_count(); i < lines_with_stuff; i++) {
            s_move(scr, 0, (int)i);
            s_write_mbs(scr, clr_eol);
        }
    }

    s_move(scr, scr->desired.cursor.x, scr->desired.cursor.y);
    s_set_color(scr, vars, highlight_spec_t{});

    // We have now synced our actual screen against our desired screen. Note that this is a big
    // assignment!
    scr->actual = scr->desired;
    scr->last_right_prompt_width = right_prompt_width;
}
Beispiel #4
0
/**
   Update the screen to match the desired output.
*/
static void s_update(screen_t *scr, const wchar_t *left_prompt, const wchar_t *right_prompt)
{
    //if (test_stuff(scr)) return;
    const size_t left_prompt_width = calc_prompt_layout(left_prompt).last_line_width;
    const size_t right_prompt_width = calc_prompt_layout(right_prompt).last_line_width;

    int screen_width = common_get_width();

    /* Figure out how many following lines we need to clear (probably 0) */
    size_t actual_lines_before_reset = scr->actual_lines_before_reset;
    scr->actual_lines_before_reset = 0;

    data_buffer_t output;

    bool need_clear_lines = scr->need_clear_lines;
    bool need_clear_screen = scr->need_clear_screen;
    bool has_cleared_screen = false;

    if (scr->actual_width != screen_width)
    {
        /* Ensure we don't issue a clear screen for the very first output, to avoid https://github.com/fish-shell/fish-shell/issues/402 */
        if (scr->actual_width != SCREEN_WIDTH_UNINITIALIZED)
        {
            need_clear_screen = true;
            s_move(scr, &output, 0, 0);
            s_reset(scr, screen_reset_current_line_contents);

            need_clear_lines = need_clear_lines || scr->need_clear_lines;
            need_clear_screen = need_clear_screen || scr->need_clear_screen;
        }
        scr->actual_width = screen_width;
    }

    scr->need_clear_lines = false;
    scr->need_clear_screen = false;

    /* Determine how many lines have stuff on them; we need to clear lines with stuff that we don't want */
    const size_t lines_with_stuff = maxi(actual_lines_before_reset, scr->actual.line_count());
    if (lines_with_stuff > scr->desired.line_count())
    {
        /* There are lines that we output to previously that will need to be cleared */
        //need_clear_lines = true;
    }

    if (wcscmp(left_prompt, scr->actual_left_prompt.c_str()))
    {
        s_move(scr, &output, 0, 0);
        s_write_str(&output, left_prompt);
        scr->actual_left_prompt = left_prompt;
        scr->actual.cursor.x = (int)left_prompt_width;
    }

    for (size_t i=0; i < scr->desired.line_count(); i++)
    {
        const line_t &o_line = scr->desired.line(i);
        line_t &s_line = scr->actual.create_line(i);
        size_t start_pos = (i==0 ? left_prompt_width : 0);
        int current_width = 0;

        /* If this is the last line, maybe we should clear the screen */
        const bool should_clear_screen_this_line = (need_clear_screen && i + 1 == scr->desired.line_count() && clr_eos != NULL);

        /* Note that skip_remaining is a width, not a character count */
        size_t skip_remaining = start_pos;

        if (! should_clear_screen_this_line)
        {
            /* Compute how much we should skip. At a minimum we skip over the prompt. But also skip over the shared prefix of what we want to output now, and what we output before, to avoid repeatedly outputting it. */
            const size_t shared_prefix = line_shared_prefix(o_line, s_line);
            if (shared_prefix > 0)
            {
                int prefix_width = fish_wcswidth(&o_line.text.at(0), shared_prefix);
                if (prefix_width > skip_remaining)
                    skip_remaining = prefix_width;
            }

            /* If we're soft wrapped, and if we're going to change the first character of the next line, don't skip over the last two characters so that we maintain soft-wrapping */
            if (o_line.is_soft_wrapped && i + 1 < scr->desired.line_count())
            {
                bool first_character_of_next_line_will_change = true;
                if (i + 1 < scr->actual.line_count())
                {
                    if (line_shared_prefix(scr->desired.line(i+1), scr->actual.line(i+1)) > 0)
                    {
                        first_character_of_next_line_will_change = false;
                    }
                }
                if (first_character_of_next_line_will_change)
                {
                    skip_remaining = mini(skip_remaining, (size_t)(scr->actual_width - 2));
                }
            }
        }

        /* Skip over skip_remaining width worth of characters */
        size_t j = 0;
        for (; j < o_line.size(); j++)
        {
            int width = fish_wcwidth_min_0(o_line.char_at(j));
            if (skip_remaining < width)
                break;
            skip_remaining -= width;
            current_width += width;
        }

        /* Skip over zero-width characters (e.g. combining marks at the end of the prompt) */
        for (; j < o_line.size(); j++)
        {
            int width = fish_wcwidth_min_0(o_line.char_at(j));
            if (width > 0)
                break;
        }

        /* Now actually output stuff */
        for (; j < o_line.size(); j++)
        {
            /* If we are about to output into the last column, clear the screen first. If we clear the screen after we output into the last column, it can erase the last character due to the sticky right cursor. If we clear the screen too early, we can defeat soft wrapping. */
            if (j + 1 == screen_width && should_clear_screen_this_line && ! has_cleared_screen)
            {
                s_move(scr, &output, current_width, (int)i);
                s_write_mbs(&output, clr_eos);
                has_cleared_screen = true;
            }

            perform_any_impending_soft_wrap(scr, current_width, (int)i);
            s_move(scr, &output, current_width, (int)i);
            s_set_color(scr, &output, o_line.color_at(j));
            s_write_char(scr, &output, o_line.char_at(j));
            current_width += fish_wcwidth_min_0(o_line.char_at(j));
        }

        /* Clear the screen if we have not done so yet. */
        if (should_clear_screen_this_line && ! has_cleared_screen)
        {
            s_move(scr, &output, current_width, (int)i);
            s_write_mbs(&output, clr_eos);
            has_cleared_screen = true;
        }

        bool clear_remainder = false;
        /* Clear the remainder of the line if we need to clear and if we didn't write to the end of the line. If we did write to the end of the line, the "sticky right edge" (as part of auto_right_margin) means that we'll be clearing the last character we wrote! */
        if (has_cleared_screen)
        {
            /* Already cleared everything */
            clear_remainder = false;
        }
        else if (need_clear_lines && current_width < screen_width)
        {
            clear_remainder = true;
        }
        else if (right_prompt_width < scr->last_right_prompt_width)
        {
            clear_remainder = true;
        }
        else
        {
            int prev_width = (s_line.text.empty() ? 0 : fish_wcswidth(&s_line.text.at(0), s_line.text.size()));
            clear_remainder = prev_width > current_width;

        }
        if (clear_remainder)
        {
            s_set_color(scr, &output, 0xffffffff);
            s_move(scr, &output, current_width, (int)i);
            s_write_mbs(&output, clr_eol);
        }

        /* Output any rprompt if this is the first line. */
        if (i == 0 && right_prompt_width > 0)
        {
            s_move(scr, &output, (int)(screen_width - right_prompt_width), (int)i);
            s_set_color(scr, &output, 0xffffffff);
            s_write_str(&output, right_prompt);
            scr->actual.cursor.x += right_prompt_width;

            /* We output in the last column. Some terms (Linux) push the cursor further right, past the window. Others make it "stick." Since we don't really know which is which, issue a cr so it goes back to the left.

               However, if the user is resizing the window smaller, then it's possible the cursor wrapped. If so, then a cr will go to the beginning of the following line! So instead issue a bunch of "move left" commands to get back onto the line, and then jump to the front of it (!)
            */

            s_move(scr, &output, scr->actual.cursor.x - (int)right_prompt_width, scr->actual.cursor.y);
            s_write_str(&output, L"\r");
            scr->actual.cursor.x = 0;
        }
    }


    /* Clear remaining lines (if any) if we haven't cleared the screen. */
    if (! has_cleared_screen && scr->desired.line_count() < lines_with_stuff)
    {
        s_set_color(scr, &output, 0xffffffff);
        for (size_t i=scr->desired.line_count(); i < lines_with_stuff; i++)
        {
            s_move(scr, &output, 0, (int)i);
            s_write_mbs(&output, clr_eol);
        }
    }

    s_move(scr, &output, scr->desired.cursor.x, scr->desired.cursor.y);
    s_set_color(scr, &output, 0xffffffff);

    if (! output.empty())
    {
        write_loop(1, &output.at(0), output.size());
    }

    /* We have now synced our actual screen against our desired screen. Note that this is a big assignment! */
    scr->actual = scr->desired;
    scr->last_right_prompt_width = right_prompt_width;
}
Beispiel #5
0
static screen_layout_t compute_layout(screen_t *s,
                                      size_t screen_width,
                                      const wcstring &left_prompt_str,
                                      const wcstring &right_prompt_str,
                                      const wcstring &commandline,
                                      const wcstring &autosuggestion_str,
                                      const int *indent)
{
    screen_layout_t result = {};

    /* Start by ensuring that the prompts themselves can fit */
    const wchar_t *left_prompt = left_prompt_str.c_str();
    const wchar_t *right_prompt = right_prompt_str.c_str();
    const wchar_t *autosuggestion = autosuggestion_str.c_str();

    prompt_layout_t left_prompt_layout = calc_prompt_layout(left_prompt);
    prompt_layout_t right_prompt_layout = calc_prompt_layout(right_prompt);

    size_t left_prompt_width = left_prompt_layout.last_line_width;
    size_t right_prompt_width = right_prompt_layout.last_line_width;

    if (left_prompt_layout.max_line_width > screen_width)
    {
        /* If we have a multi-line prompt, see if the longest line fits; if not neuter the whole left prompt */
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    if (left_prompt_width + right_prompt_width >= screen_width)
    {
        /* Nix right_prompt */
        right_prompt = L"";
        right_prompt_width = 0;
    }

    if (left_prompt_width + right_prompt_width >= screen_width)
    {
        /* Still doesn't fit, neuter left_prompt */
        left_prompt = L"> ";
        left_prompt_width = 2;
    }

    /* Now we should definitely fit */
    assert(left_prompt_width + right_prompt_width < screen_width);


    /* Convert commandline to a list of lines and their widths */
    wcstring_list_t command_lines(1);
    std::vector<size_t> line_widths(1);
    for (size_t i=0; i < commandline.size(); i++)
    {
        wchar_t c = commandline.at(i);
        if (c == L'\n')
        {
            /* Make a new line */
            command_lines.push_back(wcstring());
            line_widths.push_back(indent[i]*INDENT_STEP);
        }
        else
        {
            command_lines.back() += c;
            line_widths.back() += fish_wcwidth_min_0(c);
        }
    }
    const size_t first_command_line_width = line_widths.at(0);

    /* If we have more than one line, ensure we have no autosuggestion */
    if (command_lines.size() > 1)
    {
        autosuggestion = L"";
    }

    /* Compute the width of the autosuggestion at all possible truncation offsets */
    std::vector<size_t> autosuggestion_truncated_widths;
    autosuggestion_truncated_widths.reserve(1 + wcslen(autosuggestion));
    size_t autosuggestion_total_width = 0;
    for (size_t i=0; autosuggestion[i] != L'\0'; i++)
    {
        autosuggestion_truncated_widths.push_back(autosuggestion_total_width);
        autosuggestion_total_width += fish_wcwidth_min_0(autosuggestion[i]);
    }

    /* Here are the layouts we try in turn:

    1. Left prompt visible, right prompt visible, command line visible, autosuggestion visible
    2. Left prompt visible, right prompt visible, command line visible, autosuggestion truncated (possibly to zero)
    3. Left prompt visible, right prompt hidden, command line visible, autosuggestion hidden
    4. Newline separator (left prompt visible, right prompt hidden, command line visible, autosuggestion visible)

    A remark about layout #4: if we've pushed the command line to a new line, why can't we draw the right prompt? The issue is resizing: if you resize the window smaller, then the right prompt will wrap to the next line. This means that we can't go back to the line that we were on, and things turn to chaos very quickly.

    */

    bool done = false;

    /* Case 1 */
    if (! done && left_prompt_width + right_prompt_width + first_command_line_width + autosuggestion_total_width < screen_width)
    {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        result.right_prompt = right_prompt;
        result.autosuggestion = autosuggestion;
        done = true;
    }

    /* Case 2. Note that we require strict inequality so that there's always at least one space between the left edge and the rprompt */
    if (! done && left_prompt_width + right_prompt_width + first_command_line_width < screen_width)
    {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        result.right_prompt = right_prompt;

        /* Need at least two characters to show an autosuggestion */
        size_t available_autosuggestion_space = screen_width - (left_prompt_width + right_prompt_width + first_command_line_width);
        if (autosuggestion_total_width > 0 && available_autosuggestion_space > 2)
        {
            size_t truncation_offset = truncation_offset_for_width(autosuggestion_truncated_widths, available_autosuggestion_space - 2);
            result.autosuggestion = wcstring(autosuggestion, truncation_offset);
            result.autosuggestion.push_back(ellipsis_char);
        }
        done = true;
    }

    /* Case 3 */
    if (! done && left_prompt_width + first_command_line_width < screen_width)
    {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        done = true;
    }

    /* Case 4 */
    if (! done)
    {
        result.left_prompt = left_prompt;
        result.left_prompt_space = left_prompt_width;
        // See remark about for why we can't use the right prompt here
        //result.right_prompt = right_prompt;
        result.prompts_get_own_line = true;
        done = true;
    }

    assert(done);
    return result;
}