Exemple #1
0
/// Print the specified part of the completion list, using the specified column offsets and quoting
/// style.
///
/// \param cols number of columns to print in
/// \param width_per_column An array specifying the width of each column
/// \param row_start The first row to print
/// \param row_stop the row after the last row to print
/// \param prefix The string to print before each completion
/// \param lst The list of completions to print
void pager_t::completion_print(size_t cols, int *width_per_column, size_t row_start,
                               size_t row_stop, const wcstring &prefix, const comp_info_list_t &lst,
                               page_rendering_t *rendering) const {
    // Teach the rendering about the rows it printed.
    assert(row_start >= 0);
    assert(row_stop >= row_start);
    rendering->row_start = row_start;
    rendering->row_end = row_stop;

    size_t rows = (lst.size() - 1) / cols + 1;

    size_t effective_selected_idx = this->visual_selected_completion_index(rows, cols);

    for (size_t row = row_start; row < row_stop; row++) {
        for (size_t col = 0; col < cols; col++) {
            int is_last = (col == (cols - 1));

            if (lst.size() <= col * rows + row) continue;

            size_t idx = col * rows + row;
            const comp_t *el = &lst.at(idx);
            bool is_selected = (idx == effective_selected_idx);

            // Print this completion on its own "line".
            line_t line = completion_print_item(
                prefix, el, row, col,
                width_per_column[col] - (is_last ? 0 : PAGER_SPACER_STRING_WIDTH), row % 2,
                is_selected, rendering);

            // If there's more to come, append two spaces.
            if (col + 1 < cols) {
                line.append(PAGER_SPACER_STRING, 0);
            }

            // Append this to the real line.
            rendering->screen_data.create_line(row - row_start).append_line(line);
        }
    }
}
Exemple #2
0
/// Try to print the list of completions l with the prefix prefix using cols as the number of
/// columns. Return true if the completion list was printed, false if the terminal is to narrow for
/// the specified number of columns. Always succeeds if cols is 1.
bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst,
                                   page_rendering_t *rendering, size_t suggested_start_row) const {
    // The calculated preferred width of each column.
    int pref_width[PAGER_MAX_COLS] = {0};
    // The calculated minimum width of each column.
    int min_width[PAGER_MAX_COLS] = {0};
    // If the list can be printed with this width, width will contain the width of each column.
    int *width = pref_width;

    // Set to one if the list should be printed at this width.
    bool print = false;

    // Compute the effective term width and term height, accounting for disclosure.
    size_t term_width = this->available_term_width;
    size_t term_height =
        this->available_term_height - 1 -
        (search_field_shown ? 1 : 0);  // we always subtract 1 to make room for a comment row
    if (!this->fully_disclosed) {
        term_height = mini(term_height, (size_t)PAGER_UNDISCLOSED_MAX_ROWS);
    }

    size_t row_count = divide_round_up(lst.size(), cols);

    // We have more to disclose if we are not fully disclosed and there's more rows than we have in
    // our term height.
    if (!this->fully_disclosed && row_count > term_height) {
        rendering->remaining_to_disclose = row_count - term_height;
    } else {
        rendering->remaining_to_disclose = 0;
    }

    // If we have only one row remaining to disclose, then squelch the comment row. This prevents us
    // from consuming a line to show "...and 1 more row".
    if (!this->fully_disclosed && rendering->remaining_to_disclose == 1) {
        term_height += 1;
        rendering->remaining_to_disclose = 0;
    }

    size_t pref_tot_width = 0;
    size_t min_tot_width = 0;

    // Skip completions on tiny terminals.
    if (term_width < PAGER_MIN_WIDTH) return true;

    // Calculate how wide the list would be.
    for (size_t col = 0; col < cols; col++) {
        for (size_t row = 0; row < row_count; row++) {
            int pref, min;
            const comp_t *c;
            if (lst.size() <= col * row_count + row) continue;

            c = &lst.at(col * row_count + row);
            pref = c->pref_width;
            min = c->min_width;

            if (col != cols - 1) {
                pref += 2;
                min += 2;
            }
            min_width[col] = maxi(min_width[col], min);
            pref_width[col] = maxi(pref_width[col], pref);
        }
        min_tot_width += min_width[col];
        pref_tot_width += pref_width[col];
    }

    // Force fit if one column.
    if (cols == 1) {
        if (pref_tot_width > term_width) {
            pref_width[0] = term_width;
        }
        width = pref_width;
        print = true;
    } else if (pref_tot_width <= term_width) {
        // Terminal is wide enough. Print the list!
        width = pref_width;
        print = true;
    }

    if (print) {
        // Determine the starting and stop row.
        size_t start_row = 0, stop_row = 0;
        if (row_count <= term_height) {
            // Easy, we can show everything.
            start_row = 0;
            stop_row = row_count;
        } else {
            // We can only show part of the full list. Determine which part based on the
            // suggested_start_row.
            assert(row_count > term_height);
            size_t last_starting_row = row_count - term_height;
            start_row = mini(suggested_start_row, last_starting_row);
            stop_row = start_row + term_height;
            assert(start_row >= 0 && start_row <= last_starting_row);
        }

        assert(stop_row >= start_row);
        assert(stop_row <= row_count);
        assert(stop_row - start_row <= term_height);
        completion_print(cols, width, start_row, stop_row, prefix, lst, rendering);

        // Ellipsis helper string. Either empty or containing the ellipsis char.
        const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'};

        // Add the progress line. It's a "more to disclose" line if necessary, or a row listing if
        // it's scrollable; otherwise ignore it.
        wcstring progress_text;
        if (rendering->remaining_to_disclose == 1) {
            // I don't expect this case to ever happen.
            progress_text = format_string(_(L"%lsand 1 more row"), ellipsis_string);
        } else if (rendering->remaining_to_disclose > 1) {
            progress_text = format_string(_(L"%lsand %lu more rows"), ellipsis_string,
                                          (unsigned long)rendering->remaining_to_disclose);
        } else if (start_row > 0 || stop_row < row_count) {
            // We have a scrollable interface. The +1 here is because we are zero indexed, but want
            // to present things as 1-indexed. We do not add 1 to stop_row or row_count because
            // these are the "past the last value".
            progress_text =
                format_string(_(L"rows %lu to %lu of %lu"), start_row + 1, stop_row, row_count);
        } else if (completion_infos.empty() && !unfiltered_completion_infos.empty()) {
            // Everything is filtered.
            progress_text = _(L"(no matches)");
        }

        if (!progress_text.empty()) {
            line_t &line = rendering->screen_data.add_line();
            print_max(progress_text, highlight_spec_pager_progress |
                                         highlight_make_background(highlight_spec_pager_progress),
                      term_width, true /* has_more */, &line);
        }

        if (search_field_shown) {
            // Add the search field.
            wcstring search_field_text = search_field_line.text;
            // Append spaces to make it at least the required width.
            if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH) {
                search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' ');
            }
            line_t *search_field = &rendering->screen_data.insert_line_at_index(0);

            // We limit the width to term_width - 1.
            int search_field_written = print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal,
                                                 term_width - 1, false, search_field);
            print_max(search_field_text, highlight_modifier_force_underline,
                      term_width - search_field_written - 1, false, search_field);
        }
    }
    return print;
}
Exemple #3
0
bool pager_t::completion_try_print(size_t cols, const wcstring &prefix, const comp_info_list_t &lst, page_rendering_t *rendering, size_t suggested_start_row) const
{
    /*
      The calculated preferred width of each column
    */
    int pref_width[PAGER_MAX_COLS] = {0};
    /*
      The calculated minimum width of each column
    */
    int min_width[PAGER_MAX_COLS] = {0};
    /*
      If the list can be printed with this width, width will contain the width of each column
    */
    int *width=pref_width;

    /* Set to one if the list should be printed at this width */
    bool print = false;

    /* Compute the effective term width and term height, accounting for disclosure */
    int term_width = this->available_term_width;
    int term_height = this->available_term_height - 1 - (search_field_shown ? 1 : 0); // we always subtract 1 to make room for a comment row
    if (! this->fully_disclosed)
    {
        term_height = mini(term_height, PAGER_UNDISCLOSED_MAX_ROWS);
    }

    size_t row_count = divide_round_up(lst.size(), cols);

    /* We have more to disclose if we are not fully disclosed and there's more rows than we have in our term height */
    if (! this->fully_disclosed && row_count > term_height)
    {
        rendering->remaining_to_disclose = row_count - term_height;
    }
    else
    {
        rendering->remaining_to_disclose = 0;
    }

    int pref_tot_width=0;
    int min_tot_width = 0;

    /* Skip completions on tiny terminals */
    if (term_width < PAGER_MIN_WIDTH)
        return true;

    /* Calculate how wide the list would be */
    for (long col = 0; col < cols; col++)
    {
        for (long row = 0; row<row_count; row++)
        {
            int pref,min;
            const comp_t *c;
            if (lst.size() <= col*row_count + row)
                continue;

            c = &lst.at(col*row_count + row);
            pref = c->pref_width;
            min = c->min_width;

            if (col != cols-1)
            {
                pref += 2;
                min += 2;
            }
            min_width[col] = maxi(min_width[col],
                                  min);
            pref_width[col] = maxi(pref_width[col],
                                   pref);
        }
        min_tot_width += min_width[col];
        pref_tot_width += pref_width[col];
    }
    /*
      Force fit if one column
    */
    if (cols == 1)
    {
        if (pref_tot_width > term_width)
        {
            pref_width[0] = term_width;
        }
        width = pref_width;
        print = true;
    }
    else if (pref_tot_width <= term_width)
    {
        /* Terminal is wide enough. Print the list! */
        width = pref_width;
        print = true;
    }
    else
    {
        long next_rows = (lst.size()-1)/(cols-1)+1;
        /*    fwprintf( stderr,
          L"cols %d, min_tot %d, term %d, rows=%d, nextrows %d, termrows %d, diff %d\n",
          cols,
          min_tot_width, term_width,
          rows, next_rows, term_height,
          pref_tot_width-term_width );
        */
        if (min_tot_width < term_width &&
                (((row_count < term_height) && (next_rows >= term_height)) ||
                 (pref_tot_width-term_width< 4 && cols < 3)))
        {
            /*
              Terminal almost wide enough, or squeezing makes the
              whole list fit on-screen.

              This part of the code is really important. People hate
              having to scroll through the completion list. In cases
              where there are a huge number of completions, it can't
              be helped, but it is not uncommon for the completions to
              _almost_ fit on one screen. In those cases, it is almost
              always desirable to 'squeeze' the completions into a
              single page.

              If we are using N columns and can get everything to
              fit using squeezing, but everything would also fit
              using N-1 columns, don't try.
            */

            int tot_width = min_tot_width;
            width = min_width;

            while (tot_width < term_width)
            {
                for (long i=0; (i<cols) && (tot_width < term_width); i++)
                {
                    if (width[i] < pref_width[i])
                    {
                        width[i]++;
                        tot_width++;
                    }
                }
            }
            print = true;
        }
    }

    if (print)
    {
        /* Determine the starting and stop row */
        size_t start_row = 0, stop_row = 0;
        if (row_count <= term_height)
        {
            /* Easy, we can show everything */
            start_row = 0;
            stop_row = row_count;
        }
        else
        {
            /* We can only show part of the full list. Determine which part based on the suggested_start_row */
            assert(row_count > term_height);
            size_t last_starting_row = row_count - term_height;
            start_row = mini(suggested_start_row, last_starting_row);
            stop_row = start_row + term_height;
            assert(start_row >= 0 && start_row <= last_starting_row);
        }

        assert(stop_row >= start_row);
        assert(stop_row <= row_count);
        assert(stop_row - start_row <= term_height);
        completion_print(cols, width, start_row, stop_row, prefix, lst, rendering);

        /* Ellipsis helper string. Either empty or containing the ellipsis char */
        const wchar_t ellipsis_string[] = {ellipsis_char == L'\x2026' ? L'\x2026' : L'\0', L'\0'};

        /* Add the progress line. It's a "more to disclose" line if necessary, or a row listing if it's scrollable; otherwise ignore it */
        wcstring progress_text;
        if (rendering->remaining_to_disclose == 1)
        {
            /* I don't expect this case to ever happen */
            progress_text = format_string(L"%lsand 1 more row", ellipsis_string);
        }
        else if (rendering->remaining_to_disclose > 1)
        {
            progress_text = format_string(L"%lsand %lu more rows", ellipsis_string, (unsigned long)rendering->remaining_to_disclose);
        }
        else if (start_row > 0 || stop_row < row_count)
        {
            /* We have a scrollable interface. The +1 here is because we are zero indexed, but want to present things as 1-indexed. We do not add 1 to stop_row or row_count because these are the "past the last value" */
            progress_text = format_string(L"rows %lu to %lu of %lu", start_row + 1, stop_row, row_count);
        }
        else if (completion_infos.empty() && ! unfiltered_completion_infos.empty())
        {
            /* Everything is filtered */
            progress_text = L"(no matches)";
        }

        if (! progress_text.empty())
        {
            line_t &line = rendering->screen_data.add_line();
            print_max(progress_text.c_str(), highlight_spec_pager_progress | highlight_make_background(highlight_spec_pager_progress), term_width, true /* has_more */, &line);
        }

        if (search_field_shown)
        {
            /* Add the search field */
            wcstring search_field_text = search_field_line.text;
            /* Append spaces to make it at least the required width */
            if (search_field_text.size() < PAGER_SEARCH_FIELD_WIDTH)
            {
                search_field_text.append(PAGER_SEARCH_FIELD_WIDTH - search_field_text.size(), L' ');
            }
            line_t *search_field = &rendering->screen_data.insert_line_at_index(0);

            /* We limit the width to term_width - 1 */
            int search_field_written = print_max(SEARCH_FIELD_PROMPT, highlight_spec_normal, term_width - 1, false, search_field);
            search_field_written += print_max(search_field_text, highlight_modifier_force_underline, term_width - search_field_written - 1, false, search_field);
        }

    }
    return print;
}