Пример #1
0
/// Calculate layout information for the given prompt. Does some clever magic to detect common
/// escape sequences that may be embeded in a prompt, such as those to set visual attributes.
static prompt_layout_t calc_prompt_layout(const wcstring &prompt, layout_cache_t &cache) {
    if (auto cached_layout = cache.find_prompt_layout(prompt)) {
        return *cached_layout;
    }

    prompt_layout_t prompt_layout = {1, 0, 0};
    size_t current_line_width = 0;

    for (int j = 0; prompt[j]; j++) {
        if (prompt[j] == L'\x1B') {
            // This is the start of an escape code. Skip over it if it's at least one char long.
            size_t len = escape_code_length(&prompt[j]);
            if (len > 0) j += len - 1;
        } else if (prompt[j] == L'\t') {
            current_line_width = next_tab_stop(current_line_width);
        } else if (prompt[j] == L'\n' || prompt[j] == L'\f') {
            // PCA: At least one prompt uses \f\r as a newline. It's unclear to me what this is
            // meant to do, but terminals seem to treat it as a newline so we do the same.
            current_line_width = 0;
            prompt_layout.line_count += 1;
        } else if (prompt[j] == L'\r') {
            current_line_width = 0;
        } else {
            // Ordinary char. Add its width with care to ignore control chars which have width -1.
            current_line_width += fish_wcwidth_min_0(prompt[j]);
            if (current_line_width > prompt_layout.max_line_width) {
                prompt_layout.max_line_width = current_line_width;
            }
        }
    }
    prompt_layout.last_line_width = current_line_width;
    cache.add_prompt_layout(prompt, prompt_layout);
    return prompt_layout;
}
Пример #2
0
/// Calculate layout information for the given prompt. Does some clever magic to detect common
/// escape sequences that may be embeded in a prompt, such as color codes.
static prompt_layout_t calc_prompt_layout(const wchar_t *prompt) {
    size_t current_line_width = 0;
    size_t j;

    prompt_layout_t prompt_layout = {};
    prompt_layout.line_count = 1;

    for (j = 0; prompt[j]; j++) {
        if (prompt[j] == L'\x1b') {
            // This is the start of an escape code. Skip over it if it's at least one character
            // long.
            size_t escape_len = escape_code_length(&prompt[j]);
            if (escape_len > 0) {
                j += escape_len - 1;
            }
        } else if (prompt[j] == L'\t') {
            current_line_width = next_tab_stop(current_line_width);
        } else if (prompt[j] == L'\n' || prompt[j] == L'\f') {
            // PCA: At least one prompt uses \f\r as a newline. It's unclear to me what this is
            // meant to do, but terminals seem to treat it as a newline so we do the same.
            current_line_width = 0;
            prompt_layout.line_count += 1;
        } else if (prompt[j] == L'\r') {
            current_line_width = 0;
        } else {
            // Ordinary decent character. Just add width. This returns -1 for a control character -
            // don't add that.
            current_line_width += fish_wcwidth_min_0(prompt[j]);
            prompt_layout.max_line_width = maxi(prompt_layout.max_line_width, current_line_width);
        }
    }
    prompt_layout.last_line_width = current_line_width;
    return prompt_layout;
}
Пример #3
0
/// Convert a wide character to a multibyte string and append it to the buffer.
static void s_write_char(screen_t *s, wchar_t c) {
    scoped_buffer_t outp(*s);
    s->actual.cursor.x += fish_wcwidth_min_0(c);
    s->outp().writech(c);
    if (s->actual.cursor.x == s->actual_width && allow_soft_wrap()) {
        s->soft_wrap_location.x = 0;
        s->soft_wrap_location.y = s->actual.cursor.y + 1;

        // Note that our cursor position may be a lie: Apple Terminal makes the right cursor stick
        // to the margin, while Ubuntu makes it "go off the end" (but still doesn't wrap). We rely
        // on s_move to fix this up.
    } else {
        invalidate_soft_wrap(s);
    }
}
Пример #4
0
/// Appends a character to the end of the line that the output cursor is on. This function
/// automatically handles linebreaks and lines longer than the screen width.
static void s_desired_append_char(screen_t *s, wchar_t b, highlight_spec_t c, int indent,
                                  size_t prompt_width) {
    int line_no = s->desired.cursor.y;

    if (b == L'\n') {
        // Current line is definitely hard wrapped.
        s->desired.create_line(s->desired.line_count());
        s->desired.line(s->desired.cursor.y).is_soft_wrapped = false;
        s->desired.cursor.y++;
        s->desired.cursor.x = 0;
        for (size_t i = 0; i < prompt_width + indent * INDENT_STEP; i++) {
            s_desired_append_char(s, L' ', highlight_spec_t{}, indent, prompt_width);
        }
    } else if (b == L'\r') {
        line_t &current = s->desired.line(line_no);
        current.clear();
        s->desired.cursor.x = 0;
    } else {
        int screen_width = common_get_width();
        int cw = fish_wcwidth_min_0(b);

        s->desired.create_line(line_no);

        // Check if we are at the end of the line. If so, continue on the next line.
        if ((s->desired.cursor.x + cw) > screen_width) {
            // Current line is soft wrapped (assuming we support it).
            s->desired.line(s->desired.cursor.y).is_soft_wrapped = true;
            // std::fwprintf(stderr, L"\n\n1 Soft wrapping %d\n\n", s->desired.cursor.y);

            line_no = (int)s->desired.line_count();
            s->desired.add_line();
            s->desired.cursor.y++;
            s->desired.cursor.x = 0;
        }

        line_t &line = s->desired.line(line_no);
        line.append(b, c);
        s->desired.cursor.x += cw;

        // Maybe wrap the cursor to the next line, even if the line itself did not wrap. This
        // avoids wonkiness in the last column.
        if (s->desired.cursor.x >= screen_width) {
            line.is_soft_wrapped = true;
            s->desired.cursor.x = 0;
            s->desired.cursor.y++;
        }
    }
}
Пример #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 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;
}
Пример #6
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;
}
Пример #7
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;
}
Пример #8
0
/**
   Appends a character to the end of the line that the output cursor is
   on. This function automatically handles linebreaks and lines longer
   than the screen width.
*/
static void s_desired_append_char(screen_t *s,
                                  wchar_t b,
                                  int c,
                                  int indent,
                                  size_t prompt_width)
{
    int line_no = s->desired.cursor.y;

    switch (b)
    {
        case L'\n':
        {
            int i;
            /* Current line is definitely hard wrapped */
            s->desired.create_line(s->desired.line_count());
            s->desired.line(s->desired.cursor.y).is_soft_wrapped = false;
            s->desired.cursor.y++;
            s->desired.cursor.x=0;
            for (i=0; i < prompt_width+indent*INDENT_STEP; i++)
            {
                s_desired_append_char(s, L' ', 0, indent, prompt_width);
            }
            break;
        }

        case L'\r':
        {
            line_t &current = s->desired.line(line_no);
            current.clear();
            s->desired.cursor.x = 0;
            break;
        }

        default:
        {
            int screen_width = common_get_width();
            int cw = fish_wcwidth_min_0(b);

            s->desired.create_line(line_no);

            /*
                   Check if we are at the end of the line. If so, continue on the next line.
                   */
            if ((s->desired.cursor.x + cw) > screen_width)
            {
                /* Current line is soft wrapped (assuming we support it) */
                s->desired.line(s->desired.cursor.y).is_soft_wrapped = true;
                //fprintf(stderr, "\n\n1 Soft wrapping %d\n\n", s->desired.cursor.y);

                line_no = (int)s->desired.line_count();
                s->desired.add_line();
                s->desired.cursor.y++;
                s->desired.cursor.x=0;
                for (size_t i=0; i < prompt_width; i++)
                {
                    s_desired_append_char(s, L' ', 0, indent, prompt_width);
                }
            }

            line_t &line = s->desired.line(line_no);
            line.append(b, c);
            s->desired.cursor.x+= cw;

            /* Maybe wrap the cursor to the next line, even if the line itself did not wrap. This avoids wonkiness in the last column. */
            if (s->desired.cursor.x >= screen_width)
            {
                line.is_soft_wrapped = true;
                s->desired.cursor.x = 0;
                s->desired.cursor.y++;
            }
            break;
        }
    }

}
Пример #9
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;
}
Пример #10
0
/**
   Calculate layout information for the given prompt. Does some clever magic
   to detect common escape sequences that may be embeded in a prompt,
   such as color codes.
*/
static prompt_layout_t calc_prompt_layout(const wchar_t *prompt)
{
    size_t current_line_width = 0;
    size_t j, k;

    prompt_layout_t prompt_layout = {};
    prompt_layout.line_count = 1;

    for (j=0; prompt[j]; j++)
    {
        if (prompt[j] == L'\x1b')
        {
            /*
             This is the start of an escape code. Try to guess its width.
             */
            size_t p;
            int len=0;
            bool found = false;

            /*
             Detect these terminfo color escapes with parameter
             value 0..7, all of which don't move the cursor
             */
            char * const esc[] =
            {
                set_a_foreground,
                set_a_background,
                set_foreground,
                set_background,
            }
            ;

            /*
             Detect these semi-common terminfo escapes without any
             parameter values, all of which don't move the cursor
             */
            char * const esc2[] =
            {
                enter_bold_mode,
                exit_attribute_mode,
                enter_underline_mode,
                exit_underline_mode,
                enter_standout_mode,
                exit_standout_mode,
                flash_screen,
                enter_subscript_mode,
                exit_subscript_mode,
                enter_superscript_mode,
                exit_superscript_mode,
                enter_blink_mode,
                enter_italics_mode,
                exit_italics_mode,
                enter_reverse_mode,
                enter_shadow_mode,
                exit_shadow_mode,
                enter_standout_mode,
                exit_standout_mode,
                enter_secure_mode
            }
            ;

            for (p=0; p < sizeof esc / sizeof *esc && !found; p++)
            {
                if (!esc[p])
                    continue;

                for (k=0; k<8; k++)
                {
                    len = try_sequence(tparm(esc[p],k), &prompt[j]);
                    if (len)
                    {
                        j += (len-1);
                        found = true;
                        break;
                    }
                }
            }

            /* PCA for term256 support, let's just detect the escape codes directly */
            if (! found)
            {
                len = is_term256_escape(&prompt[j]);
                if (len)
                {
                    j += (len - 1);
                    found = true;
                }
            }


            for (p=0; p < (sizeof(esc2)/sizeof(char *)) && !found; p++)
            {
                if (!esc2[p])
                    continue;
                /*
                 Test both padded and unpadded version, just to
                 be safe. Most versions of tparm don't actually
                 seem to do anything these days.
                 */
                len = maxi(try_sequence(tparm(esc2[p]), &prompt[j]),
                           try_sequence(esc2[p], &prompt[j]));

                if (len)
                {
                    j += (len-1);
                    found = true;
                }
            }

            if (!found)
            {
                if (prompt[j+1] == L'k')
                {
                    const env_var_t term_name = env_get_string(L"TERM");
                    if (!term_name.missing() && wcsstr(term_name.c_str(), L"screen") == term_name)
                    {
                        const wchar_t *end;
                        j+=2;
                        found = true;
                        end = wcsstr(&prompt[j], L"\x1b\\");
                        if (end)
                        {
                            /*
                             You'd thing this should be
                             '(end-prompt)+2', in order to move j
                             past the end of the string, but there is
                             a 'j++' at the end of each lap, so j
                             should always point to the last menged
                             character, e.g. +1.
                             */
                            j = (end-prompt)+1;
                        }
                        else
                        {
                            break;
                        }
                    }
                }
            }

        }
        else if (prompt[j] == L'\t')
        {
            current_line_width = next_tab_stop(current_line_width);
        }
        else if (prompt[j] == L'\n' || prompt[j] == L'\f')
        {
            /* PCA: At least one prompt uses \f\r as a newline. It's unclear to me what this is meant to do, but terminals seem to treat it as a newline so we do the same. */
            current_line_width = 0;
            prompt_layout.line_count += 1;
        }
        else if (prompt[j] == L'\r')
        {
            current_line_width = 0;
        }
        else
        {
            /* Ordinary decent character. Just add width. This returns -1 for a control character - don't add that. */
            current_line_width += fish_wcwidth_min_0(prompt[j]);
            prompt_layout.max_line_width = maxi(prompt_layout.max_line_width, current_line_width);
        }
    }
    prompt_layout.last_line_width = current_line_width;
    return prompt_layout;
}