/// 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); } } }
/// 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; }
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; }