-
-
Notifications
You must be signed in to change notification settings - Fork 81
Ticket: #5020: Allocate fewer ncurses color pairs #5066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5ef1d3d
f3e6ddc
2a5fbb1
95f2560
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -49,57 +49,78 @@ | |
|
|
||
| /*** file scope type declarations ****************************************************************/ | ||
|
|
||
| typedef struct | ||
| { | ||
| int pair; // ncurses color pair index | ||
| int attrs; // attributes | ||
| } mc_tty_ncurses_color_pair_and_attrs_t; | ||
|
|
||
| /*** forward declarations (file scope functions) *************************************************/ | ||
|
|
||
| /*** file scope variables ************************************************************************/ | ||
|
|
||
| static GHashTable *mc_tty_color_color_pair_attrs = NULL; | ||
| /* | ||
| * Our bookkeeping of the ncurses color pair indices, indexed by the "{fg}.{bg}" string. | ||
| */ | ||
| static GHashTable *mc_tty_ncurses_color_pairs = NULL; | ||
|
|
||
| /* | ||
| * Indexed by mc's color index, points to the mc_tty_ncurses_color_pair_and_attrs_t object | ||
| * representing the ncurses color pair index and the attributes. | ||
| * | ||
| * mc's color index represents unique (fg, bg, attrs) tuples. Allocating an ncurses color pair | ||
| * for each of them might be too wasteful and might cause us to run out of available color pairs | ||
| * too soon (especially in 8-color terminals), if many combinations only differ in attrs. | ||
| * So we allocate a new ncurses color pair only if the (fg, bg) tuple is newly seen. | ||
| * See #5020 for details. | ||
| */ | ||
| static GArray *mc_tty_ncurses_color_pair_and_attrs = NULL; | ||
|
|
||
| static int mc_tty_ncurses_next_color_pair = 0; | ||
|
|
||
| static int overlay_colors = 0; | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
| /*** file scope functions ************************************************************************/ | ||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
||
| static inline void | ||
| mc_tty_color_attr_destroy_cb (gpointer data) | ||
| static int | ||
| get_ncurses_color_pair (int ifg, int ibg) | ||
| { | ||
| g_free (data); | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
| char *color_pair_str; | ||
| int *ncurses_color_pair; | ||
| int init_pair_ret; | ||
|
|
||
| static void | ||
| mc_tty_color_save_attr (int color_pair, int color_attr) | ||
| { | ||
| int *attr, *key; | ||
| color_pair_str = g_strdup_printf ("%d.%d", ifg, ibg); | ||
|
|
||
| attr = g_try_new0 (int, 1); | ||
| if (attr == NULL) | ||
| return; | ||
| ncurses_color_pair = | ||
| (int *) g_hash_table_lookup (mc_tty_ncurses_color_pairs, (gpointer) color_pair_str); | ||
|
|
||
| key = g_try_new (int, 1); | ||
| if (key == NULL) | ||
| if (ncurses_color_pair == NULL) | ||
| { | ||
| g_free (attr); | ||
| return; | ||
| } | ||
|
|
||
| *key = color_pair; | ||
| *attr = color_attr; | ||
|
|
||
| g_hash_table_replace (mc_tty_color_color_pair_attrs, (gpointer) key, (gpointer) attr); | ||
| } | ||
| ncurses_color_pair = g_try_new0 (int, 1); | ||
| *ncurses_color_pair = mc_tty_ncurses_next_color_pair; | ||
| #if NCURSES_VERSION_PATCH >= 20170401 && defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) \ | ||
| && defined(HAVE_NCURSES_WIDECHAR) | ||
| init_pair_ret = init_extended_pair (*ncurses_color_pair, ifg, ibg); | ||
| #else | ||
| init_pair_ret = init_pair (*ncurses_color_pair, ifg, ibg); | ||
| #endif | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
| if (init_pair_ret == ERR) | ||
| { | ||
| g_free (ncurses_color_pair); | ||
| g_free (color_pair_str); | ||
| return 0; | ||
| } | ||
|
|
||
| static int | ||
| color_get_attr (int color_pair) | ||
| { | ||
| int *fnd = NULL; | ||
| g_hash_table_insert (mc_tty_ncurses_color_pairs, color_pair_str, ncurses_color_pair); | ||
| mc_tty_ncurses_next_color_pair++; | ||
| } | ||
| else | ||
| g_free (color_pair_str); | ||
|
|
||
| if (mc_tty_color_color_pair_attrs != NULL) | ||
| fnd = (int *) g_hash_table_lookup (mc_tty_color_color_pair_attrs, (gpointer) &color_pair); | ||
| return (fnd != NULL) ? *fnd : 0; | ||
| return *ncurses_color_pair; | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
@@ -109,6 +130,8 @@ color_get_attr (int color_pair) | |
| void | ||
| tty_color_init_lib (gboolean disable, gboolean force) | ||
| { | ||
| int default_color_pair_id; | ||
|
|
||
| (void) force; | ||
|
|
||
| if (has_colors () && !disable) | ||
|
|
@@ -122,17 +145,32 @@ tty_color_init_lib (gboolean disable, gboolean force) | |
| tty_use_truecolors (NULL); | ||
| } | ||
|
|
||
| mc_tty_color_color_pair_attrs = g_hash_table_new_full ( | ||
| g_int_hash, g_int_equal, mc_tty_color_attr_destroy_cb, mc_tty_color_attr_destroy_cb); | ||
| // our tracking of ncurses's color pairs | ||
| mc_tty_ncurses_color_pairs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); | ||
|
|
||
| // ncurses color pair 0 always refers to the default colors; add it to our hash | ||
| mc_tty_ncurses_next_color_pair = 0; | ||
| default_color_pair_id = get_ncurses_color_pair (-1, -1); | ||
| g_assert (default_color_pair_id == 0); | ||
| (void) default_color_pair_id; // unused if g_assert is eliminated | ||
|
|
||
| // mapping from our index to ncurses's index and attributes | ||
| mc_tty_ncurses_color_pair_and_attrs = | ||
| g_array_new (FALSE, FALSE, sizeof (mc_tty_ncurses_color_pair_and_attrs_t)); | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
||
| void | ||
| tty_color_deinit_lib (void) | ||
| { | ||
| g_hash_table_destroy (mc_tty_color_color_pair_attrs); | ||
| mc_tty_color_color_pair_attrs = NULL; | ||
| g_hash_table_destroy (mc_tty_ncurses_color_pairs); | ||
| mc_tty_ncurses_color_pairs = NULL; | ||
|
|
||
| g_array_free (mc_tty_ncurses_color_pair_and_attrs, TRUE); | ||
| mc_tty_ncurses_color_pair_and_attrs = NULL; | ||
|
|
||
| mc_tty_ncurses_next_color_pair = 0; | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
@@ -164,36 +202,39 @@ tty_color_try_alloc_lib_pair (tty_color_lib_pair_t *mc_color_pair) | |
|
|
||
| // Shady trick: if we don't have the exact color, because it is overlaid by backwards | ||
| // compatibility indexed values, just borrow one degree of red. The user won't notice :) | ||
| if ((ifg & FLAG_TRUECOLOR) != 0) | ||
| if (ifg >= 0 && (ifg & FLAG_TRUECOLOR) != 0) | ||
| { | ||
| ifg &= ~FLAG_TRUECOLOR; | ||
| if (ifg != 0 && ifg <= overlay_colors) | ||
| if (ifg <= overlay_colors) | ||
| ifg += (1 << 16); | ||
| } | ||
|
|
||
| if ((ibg & FLAG_TRUECOLOR) != 0) | ||
| if (ibg >= 0 && (ibg & FLAG_TRUECOLOR) != 0) | ||
| { | ||
| ibg &= ~FLAG_TRUECOLOR; | ||
| if (ibg != 0 && ibg <= overlay_colors) | ||
| if (ibg <= overlay_colors) | ||
| ibg += (1 << 16); | ||
| } | ||
|
|
||
| #if NCURSES_VERSION_PATCH >= 20170401 && defined(NCURSES_EXT_COLORS) && defined(NCURSES_EXT_FUNCS) \ | ||
| && defined(HAVE_NCURSES_WIDECHAR) | ||
| init_extended_pair (mc_color_pair->pair_index, ifg, ibg); | ||
| #else | ||
| init_pair (mc_color_pair->pair_index, ifg, ibg); | ||
| #endif | ||
| mc_tty_color_save_attr (mc_color_pair->pair_index, attr); | ||
| const int ncurses_color_pair = get_ncurses_color_pair (ifg, ibg); | ||
| const mc_tty_ncurses_color_pair_and_attrs_t pair_and_attrs = { .pair = ncurses_color_pair, | ||
| .attrs = attr }; | ||
|
|
||
| g_array_insert_val (mc_tty_ncurses_color_pair_and_attrs, mc_color_pair->pair_index, | ||
| pair_and_attrs); | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
||
| void | ||
| tty_setcolor (int color) | ||
| { | ||
| mc_tty_ncurses_color_pair_and_attrs_t *pair_and_attrs; | ||
|
|
||
| color = tty_maybe_map_color (color); | ||
| attr_set (color_get_attr (color), color, NULL); | ||
| pair_and_attrs = &g_array_index (mc_tty_ncurses_color_pair_and_attrs, | ||
| mc_tty_ncurses_color_pair_and_attrs_t, color); | ||
| attr_set (pair_and_attrs->attrs, pair_and_attrs->pair, NULL); | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
@@ -264,3 +305,47 @@ tty_use_truecolors (GError **error) | |
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
|
|
||
| void | ||
| tty_colorize_area (int y, int x, int rows, int cols, int color) | ||
| { | ||
| #ifdef ENABLE_SHADOWS | ||
| cchar_t *ctext; | ||
| wchar_t wch[CCHARW_MAX + 1]; | ||
| attr_t attrs; | ||
| short color_pair; | ||
|
|
||
| if (!use_colors || !tty_clip (&y, &x, &rows, &cols)) | ||
| return; | ||
|
|
||
| color = tty_maybe_map_color (color); | ||
| color = g_array_index (mc_tty_ncurses_color_pair_and_attrs, | ||
| mc_tty_ncurses_color_pair_and_attrs_t, color) | ||
| .pair; | ||
|
|
||
| ctext = g_malloc (sizeof (cchar_t) * (cols + 1)); | ||
|
|
||
| for (int row = 0; row < rows; row++) | ||
| { | ||
| mvin_wchnstr (y + row, x, ctext, cols); | ||
|
|
||
| for (int col = 0; col < cols; col++) | ||
| { | ||
| getcchar (&ctext[col], wch, &attrs, &color_pair, NULL); | ||
| setcchar (&ctext[col], wch, attrs, color, NULL); | ||
| } | ||
|
|
||
| mvadd_wchnstr (y + row, x, ctext, cols); | ||
| } | ||
|
|
||
| g_free (ctext); | ||
| #else | ||
| (void) y; | ||
| (void) x; | ||
| (void) rows; | ||
| (void) cols; | ||
| (void) color; | ||
|
Comment on lines
+343
to
+347
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know you just moved this code, but could you please use #ifdef ENABLE_SHADOWS
tty_colorize_area (int y, int x, int rows, int cols, int color)
#else
tty_colorize_area (MC_UNUSED int, MC_UNUSED int, MC_UNUSED int, MC_UNUSED int, MC_UNUSED int)
#endif(if this works)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Currently mc uses Due to the If you don't mind, I'd rather keep this untouched – leave it to you to change if you wish to. (Your other feedback I agree with and will address.)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, shall we (or I) perform a mass search and replace then? The other items accumulated over time before I eventually introduced
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't we just leave It works here. Whether at other places a similar hack works or not is irrelevant. By the way, the refactoring I just did (as per your other request) happened to introduce another such You have a very hard time convincing me that I have to switch to the new way which is used at 10 places, the old way used at 350 places isn't good anymore. I'd also like to firmly reject on principle the idea that if I move a method to a different file (where it obviously should've belonged to from the beginning) then I'd definitely need to update the body of the method to our new standards. It's a fair request that newly written code should use the new way, you may or may not mass-update the old syntax, but moving a method to a different location shouldn't count as new code and shouldn't be subjected to the new guidelines. In this very case, I'm also reluctant to go with this change because it introduces more duplication that the current one, it's easier to get it wrong in one of the cases (which I guess our CI doesn't test, and I wouldn't want to manually test the no-shadow case either beacuse that's not what this change is about); it would also need to go consistently in the header vs. impl file which adds another ugly ifdef-branching; overall I just find it uglier, more fragile than the current code, and would need additional testing. I'd much rather spend my time on things that actually do matter – there's a lot to do. (Note: Moving the method is a prerequisite for the main commit of the PR; see the "ouch" comment and the compiler warning in the first two drafts attached to the bug.)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In C++, they have recently adopted In my hierarchy of values, I would then accept the silencer as it is less evil than non-generic (duplicating) logic code.
It's unfortunate that I should have a very hard time doing so, because you seem to be otherwise thinking rationally and have quite an experience with C. The problem with unused variable silencers is that they... well, silence the warnings. However, they are not dedicated unambiguous syntax constructs to do so, but at best, some hopefully otherwise harmless construct, which is accepted to be abused for the purpose. As such, these statements then cannot be easily identified by means other than semantic analysis and don’t directly convey the meaning to the reader. A proper way to silence warnings if needed is to use dedicated attributes (which is what the new macro is using). It resolves into a directive to the compiler that doesn't and cannot affect anything else, and can be easily found in the code. Also, its meaning is directly obvious. Moreover, there are places where the There are several classes of cases when the use of silencers is warranted:
The general approach should be to avoid the situation if possible; if not, use it. The first type of usage I find particularly problematic and want to replace it as a priority The second is used absolutely inconsistently in our code, and in my opinion should be just thrown away. I don't think that the codebase is ready for strict return checking anyway, and refactoring this properly would be a major project... which might not even make sense in the C world, in terms of what you gain for the amount you invest, unless you're working in some kind of regulated environment. In which case, you might as well reconsider your choice of language. I can see that in the last case, the macro in its current form can only mark the variable at the declaration site, which doesn't make it much better than the silencer. Maybe we can improve on that by giving it an optional parameter, so that it would expand only if the parameter evaluates to true. I'm not sure that this is doable within the C macro system and with an amount of effort invested that is worth it.
I don't think that I can be convinced that there is a problem on principle with updating the code as it is touched. In my experience, this is actually the only pragmatic and practical way of evolving huge old code bases in the right direction. The sweeping changes mostly require an inordinate amount of work and just never get done, and on top of that introduce risks, like mass-upgrades to new types ( The approach of doing something to the code that is touched when it is touched, even if it has nothing to do with the change per se (obviously in a separate commit), makes the upgrades possible in the first place and then limits the risks, as the change is more likely to be correct due to its limited nature and more stringent review. Nevertheless, what is certainly true is that it's highly problematic if we don't agree on the nature of the change and if you have some issues "on principle" with the approach itself. I expected neither of those as I made my suggestion, otherwise I wouldn't have made it. Now, of course, I'm not going to block anything on these grounds. Let's leave it the way you want for this PR.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
As long as it's a trivial improvement, why not. In this case it's a combination of two major things:
I hope you don't mind that I just decided to draw the line before this step now. :) |
||
| #endif | ||
| } | ||
|
|
||
| /* --------------------------------------------------------------------------------------------- */ | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, do you hit the warning when you compile in some configuration, or is this theory?
I'm asking because I've started eliminating these hacks in a separate branch. In the worst case, I'd have to accept that some of those must remain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's experience (probably mostly from VTE/gnome-terminal).
Look at
glib/gtestutils.h: under an#ifdef G_DISABLE_ASSERTtheseg_assert()and friends are eliminated and don't evaluate (don't even look at) their parameter.I have also shot myself in the foot with code like
g_assert (important_function_call_to_make() == 0);– works great until you disable asserts.Now, maybe there's no set of configure option to pass to mc which would set this macro, but still, it's bad practice to rely on it.