xaizek / vifm (License: GPLv2+) (since 2018-12-07)
Vifm is a file manager with curses interface, which provides Vi[m]-like environment for managing objects within file systems, extended with some useful ideas from mutt.
Commit 36730810507c86e9be30c40b7cc71a744287949e

Handle optional "color" in a result of Lua column
The color allows overriding line-specific highlighting of a text within
a cell. Only part of the cell where the text is gets colored.

Thanks to Steven Xu (a.k.a. stevenxxiu) and Dmitry Frank (a.k.a.
dimonomid).

Closes #289 on GitHub.
Author: xaizek
Author date (UTC): 2026-06-07 11:02
Committer name: xaizek
Committer date (UTC): 2026-06-07 22:11
Parent(s): 9378992f2f7e72e65aa6370fcb49efded8477435
Signing key: 99DC5E4DB05F6BE2
Tree: 051cca077b27c8e16acc2d704f3af04ccd16aabb
File Lines added Lines deleted
ChangeLog.LuaAPI 4 0
data/man/vifm.1 6 5
data/vim/doc/app/vifm-app.txt 6 5
data/vim/doc/app/vifm-lua.txt 4 1
src/lua/vifm_viewcolumns.c 13 1
src/ui/fileview.c 18 9
src/ui/fileview.h 4 0
tests/lua/api_viewcolumns.c 39 0
File ChangeLog.LuaAPI changed (mode: 100644) (index 4e6f5502a..81ea2bc8c)
... ... documented in the regular ChangeLog.
23 23 targets for navigation menus independently of whether and how they appear targets for navigation menus independently of whether and how they appear
24 24 in the menu. in the menu.
25 25
26 Added parsing of optional "color" field in the result of a handler set up
27 by vifm.addcolumntype() which allows adjusting color of the text in a cell.
28 Thanks to Steven Xu (a.k.a. stevenxxiu) and Dmitry Frank (a.k.a. dimonomid).
29
26 30 Fixed a crash on passing a value that's not convertible to a string to Fixed a crash on passing a value that's not convertible to a string to
27 31 VifmView:loadcustom(). VifmView:loadcustom().
28 32
File data/man/vifm.1 changed (mode: 100644) (index bb8fbf4f6..384b62082)
1 .TH VIFM 1 "5 May 2026" "vifm 0.15"
1 .TH VIFM 1 "7 June 2026" "vifm 0.15"
2 2 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
3 3 .SH NAME .SH NAME
4 4 .\" --------------------------------------------------------------------------- .\" ---------------------------------------------------------------------------
 
... ... transparency:
2553 2553 CmpUnmatched CmpUnmatched
2554 2554 CmpBlank CmpBlank
2555 2555 Selected Selected
2556 CurrLine
2557 LineNr (in active pane)
2558 OtherLine
2559 LineNr (in inactive pane)
2556 Cell-specific (returned by Lua columns)
2557 CurrLine
2558 LineNr (in active pane)
2559 OtherLine
2560 LineNr (in inactive pane)
2560 2561 TopLine TopLine
2561 2562 TopLineSel TopLineSel
2562 2563 TabLineSel (for pane tabs) TabLineSel (for pane tabs)
File data/vim/doc/app/vifm-app.txt changed (mode: 100644) (index 1d832d3e6..7cee6a753)
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2026 May 5
1 *vifm-app.txt* For Vifm version 0.15 Last change: 2026 June 7
2 2
3 3 Email for bugs and suggestions: <xaizek@posteo.net> Email for bugs and suggestions: <xaizek@posteo.net>
4 4
 
... ... transparency:
2217 2217 CmpUnmatched CmpUnmatched
2218 2218 CmpBlank CmpBlank
2219 2219 Selected Selected
2220 CurrLine
2221 LineNr (in active pane)
2222 OtherLine
2223 LineNr (in inactive pane)
2220 Cell-specific (returned by Lua columns)
2221 CurrLine
2222 LineNr (in active pane)
2223 OtherLine
2224 LineNr (in inactive pane)
2224 2225 TopLine TopLine
2225 2226 TopLineSel TopLineSel
2226 2227 TabLineSel (for pane tabs) TabLineSel (for pane tabs)
File data/vim/doc/app/vifm-lua.txt changed (mode: 100644) (index fe492fdec..07bba8339)
... ... Fields of {info} argument for {column}.handler:
564 564 Fields of table returned by {column}.handler: Fields of table returned by {column}.handler:
565 565 - "text" (string) - "text" (string)
566 566 Table cell's value as a string or convertible to it. Table cell's value as a string or convertible to it.
567 - "color" (|vifm-l_VifmColor|) (optional)
568 Color of the text (doesn't affect parts of a cell without the text).
567 569 - "matchstart" (integer) (default: 0) - "matchstart" (integer) (default: 0)
568 570 For a search match this is the end position of a substring found in For a search match this is the end position of a substring found in
569 571 entry's name, zero otherwise. entry's name, zero otherwise.
 
... ... Return:~
1351 1353 -------------------------------------------------------------------------------- --------------------------------------------------------------------------------
1352 1354 *vifm-l_VifmColor* *vifm-l_VifmColor*
1353 1355
1354 Instances of this type are returned by:
1356 Instances of this type can be returned from callbacks of
1357 |vifm-l_vifm.addcolumntype()|. They are also returned by:
1355 1358 * |vifm-l_vifm.color.new()| * |vifm-l_vifm.color.new()|
1356 1359
1357 1360 This type represents a combination of foreground color, background color and This type represents a combination of foreground color, background color and
File src/lua/vifm_viewcolumns.c changed (mode: 100644) (index f241b5e59..1592bb876)
... ... lua_viewcolumn_handler(void *data, size_t buf_len, char buf[],
270 270 } }
271 271 } }
272 272
273 lua_pop(lua, 3); /* matchend, matchstart, handler's result */
273 lua_pop(lua, 2); /* matchend, matchstart */
274
275 if(lua_getfield(lua, -1, "color") == LUA_TUSERDATA)
276 {
277 col_attr_t *color = luaL_testudata(lua, -1, "VifmColor");
278 if(color != NULL)
279 {
280 cdt->custom_color = 1;
281 cdt->custom_hi = *color;
282 }
283 }
284
285 lua_pop(lua, 2); /* color, handler's result */
274 286 } }
275 287
276 288 /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */ /* vim: set tabstop=2 softtabstop=2 shiftwidth=2 noexpandtab cinoptions-=(0 : */
File src/ui/fileview.c changed (mode: 100644) (index 38f776ead..84badae62)
... ... static void draw_line_number(const column_data_t *cdt, int column);
126 126 static int is_primary_colored_column_id(int id); static int is_primary_colored_column_id(int id);
127 127 static int is_primary_column_id(int id); static int is_primary_column_id(int id);
128 128 static cchar_t prepare_col_color(const view_t *view, int is_primary_colored, static cchar_t prepare_col_color(const view_t *view, int is_primary_colored,
129 int line_nr, const column_data_t *cdt, int real_id);
129 int line_nr, const column_data_t *cdt, int real_id, int filling);
130 130 static void mix_in_common_colors(col_attr_t *col, const view_t *view, static void mix_in_common_colors(col_attr_t *col, const view_t *view,
131 131 dir_entry_t *entry, int line_color); dir_entry_t *entry, int line_color);
132 132 static void mix_in_file_hi(const view_t *view, dir_entry_t *entry, int type_hi, static void mix_in_file_hi(const view_t *view, dir_entry_t *entry, int type_hi,
 
... ... column_line_print(const char buf[], int offset, AlignType align,
1182 1182
1183 1183 const int numbers_visible = (offset == 0 && cdt->number_width > 0); const int numbers_visible = (offset == 0 && cdt->number_width > 0);
1184 1184 const int padding = (cfg.extra_padding != 0); const int padding = (cfg.extra_padding != 0);
1185 const int filling = (info->id == FILL_COLUMN_ID);
1185 1186
1186 1187 const int is_primary_colored = is_primary_colored_column_id(info->id); const int is_primary_colored = is_primary_colored_column_id(info->id);
1187 1188 const cchar_t line_attrs = const cchar_t line_attrs =
1188 prepare_col_color(view, is_primary_colored, 0, cdt, info->real_id);
1189 prepare_col_color(view, is_primary_colored, 0, cdt, info->real_id, filling);
1189 1190
1190 1191 /* Non-empty prefix contains tree pseudo-graphics. */ /* Non-empty prefix contains tree pseudo-graphics. */
1191 1192 const int is_treeable_column = is_primary_column_id(info->id); const int is_treeable_column = is_primary_column_id(info->id);
 
... ... column_line_print(const char buf[], int offset, AlignType align,
1222 1223 buf += extra_prefix; buf += extra_prefix;
1223 1224
1224 1225 checked_wmove(view->win, cdt->current_line, final_offset - extra_prefix); checked_wmove(view->win, cdt->current_line, final_offset - extra_prefix);
1225 cchar_t cch = prepare_col_color(view, 0, 0, cdt, /*real_id=*/-1);
1226 cchar_t cch =
1227 prepare_col_color(view, 0, 0, cdt, /*real_id=*/-1, /*filling=*/0);
1226 1228 wprinta(view->win, print_buf, &cch, 0); wprinta(view->win, print_buf, &cch, 0);
1227 1229 } }
1228 1230
 
... ... column_line_print(const char buf[], int offset, AlignType align,
1248 1250 /* Draw match highlighting if there is any. */ /* Draw match highlighting if there is any. */
1249 1251 int match_from = (cdt->custom_match ? cdt->match_from : info->match_from); int match_from = (cdt->custom_match ? cdt->match_from : info->match_from);
1250 1252 int match_to = (cdt->custom_match ? cdt->match_to : info->match_to); int match_to = (cdt->custom_match ? cdt->match_to : info->match_to);
1251 if(info->id != FILL_COLUMN_ID && match_from != match_to)
1253 if(!filling && match_from != match_to)
1252 1254 { {
1253 1255 /* Calculate number of screen characters before the match. */ /* Calculate number of screen characters before the match. */
1254 1256 size_t match_start_col = utf8_nstrsw(print_buf, match_from); size_t match_start_col = utf8_nstrsw(print_buf, match_from);
 
... ... column_line_print(const char buf[], int offset, AlignType align,
1262 1264 /* Use a given prefix value at most once. */ /* Use a given prefix value at most once. */
1263 1265 *cdt->prefix_len = 0; *cdt->prefix_len = 0;
1264 1266
1265 if(info->id != FILL_COLUMN_ID)
1267 if(!filling)
1266 1268 { {
1267 /* Any match for this column has already been consumed, so reset it in
1268 * preparation of processing the next column. */
1269 /* Custom match and color for this cell have already been consumed, so reset
1270 * them in preparation for processing the next column. */
1269 1271 cdt->custom_match = 0; cdt->custom_match = 0;
1272 cdt->custom_color = 0;
1270 1273 } }
1271 1274 } }
1272 1275
 
... ... draw_line_number(const column_data_t *cdt, int column)
1289 1292 num); num);
1290 1293
1291 1294 checked_wmove(view->win, cdt->current_line, column); checked_wmove(view->win, cdt->current_line, column);
1292 cchar_t cch = prepare_col_color(view, 0, 1, cdt, /*real_id=*/-1);
1295 cchar_t cch =
1296 prepare_col_color(view, 0, 1, cdt, /*real_id=*/-1, /*filling=*/0);
1293 1297 wprinta(view->win, num_str, &cch, 0); wprinta(view->win, num_str, &cch, 0);
1294 1298 } }
1295 1299
 
... ... is_primary_column_id(int id)
1362 1366 * used for drawing on a window. */ * used for drawing on a window. */
1363 1367 static cchar_t static cchar_t
1364 1368 prepare_col_color(const view_t *view, int is_primary_colored, int line_nr, prepare_col_color(const view_t *view, int is_primary_colored, int line_nr,
1365 const column_data_t *cdt, int real_id)
1369 const column_data_t *cdt, int real_id, int filling)
1366 1370 { {
1367 1371 const col_scheme_t *const cs = ui_view_get_cs(view); const col_scheme_t *const cs = ui_view_get_cs(view);
1368 1372 col_attr_t col = ui_get_win_color(view, cs); col_attr_t col = ui_get_win_color(view, cs);
 
... ... prepare_col_color(const view_t *view, int is_primary_colored, int line_nr,
1395 1399 const int line_color = with_line_hi ? cdt->line_hi_group : -1; const int line_color = with_line_hi ? cdt->line_hi_group : -1;
1396 1400 mix_in_common_colors(&col, view, cdt->entry, line_color); mix_in_common_colors(&col, view, cdt->entry, line_color);
1397 1401
1402 if(!filling && cdt->custom_color)
1403 {
1404 cs_mix_colors(&col, &cdt->custom_hi);
1405 }
1406
1398 1407 if(is_current) if(is_current)
1399 1408 { {
1400 1409 int color = (view == curr_view || !cdt->is_main) ? CURR_LINE_COLOR int color = (view == curr_view || !cdt->is_main) ? CURR_LINE_COLOR
File src/ui/fileview.h changed (mode: 100644) (index e8755d1bf..25d0dc3ce)
23 23 #include <stddef.h> /* size_t */ #include <stddef.h> /* size_t */
24 24
25 25 #include "../utils/test_helpers.h" #include "../utils/test_helpers.h"
26 #include "colors.h"
26 27
27 28 struct dir_entry_t; struct dir_entry_t;
28 29 struct view_t; struct view_t;
 
... ... typedef struct
59 60 int custom_match; /* Whether the fields below have meaningful values. */ int custom_match; /* Whether the fields below have meaningful values. */
60 61 int match_from; /* Start offset of the match. */ int match_from; /* Start offset of the match. */
61 62 int match_to; /* End offset of the match. */ int match_to; /* End offset of the match. */
63
64 int custom_color; /* Whether custom_hi has a meaningful value. */
65 col_attr_t custom_hi; /* Custom cell-specific attribute. */
62 66 } }
63 67 column_data_t; column_data_t;
64 68
File tests/lua/api_viewcolumns.c changed (mode: 100644) (index 7d81a8bb1..6a362ff26)
... ... TEST(columns_are_used)
131 131 curr_stats.vlua = NULL; curr_stats.vlua = NULL;
132 132 } }
133 133
134 TEST(columns_and_colors)
135 {
136 opt_handlers_setup();
137 lwin.columns = columns_create();
138 curr_stats.vlua = vlua;
139
140 GLUA_EQ(vlua, "",
141 "function bad_type()"
142 " return { text = 'table', color = { } }"
143 "end");
144 GLUA_EQ(vlua, "",
145 "function bad_user_data()"
146 " return { text = 'udata', color = vifm.currview() }"
147 "end");
148 GLUA_EQ(vlua, "",
149 "function good(info)"
150 " return { text = info.entry.name, color = vifm.color.new {} }"
151 "end");
152 GLUA_EQ(vlua, "true",
153 "print(vifm.addcolumntype { name = 'BadType', handler = bad_type })");
154 GLUA_EQ(vlua, "true",
155 "print(vifm.addcolumntype { name = 'BadUserData',"
156 " handler = bad_user_data })");
157 GLUA_EQ(vlua, "true",
158 "print(vifm.addcolumntype { name = 'Good', handler = good })");
159
160 process_set_args("viewcolumns=10{BadType},10{BadUserData},10{Good}", 0, 1);
161
162 dir_entry_t entry = { .name = "name", .origin = "origin" };
163 column_data_t cdt = { .view = &lwin, .entry = &entry };
164
165 columns_set_line_print_func(&column_line_print);
166 columns_format_line(lwin.columns, &cdt, MAX_WIDTH);
167 assert_string_equal(" table udata name ", print_buffer);
168
169 opt_handlers_teardown();
170 curr_stats.vlua = NULL;
171 }
172
134 173 TEST(symlinks, IF(not_windows)) TEST(symlinks, IF(not_windows))
135 174 { {
136 175 assert_success(make_symlink("something", SANDBOX_PATH "/symlink")); assert_success(make_symlink("something", SANDBOX_PATH "/symlink"));
Hints

Before first commit, do not forget to setup your git environment:
git config --global user.name "your_name_here"
git config --global user.email "your@email_here"

Clone this repository using HTTP(S):
git clone https://code.reversed.top/user/xaizek/vifm

Clone this repository using ssh (do not forget to upload a key first):
git clone ssh://rocketgit@code.reversed.top/user/xaizek/vifm

You are allowed to anonymously push to this repository.
This means that your pushed commits will automatically be transformed into a pull request:
... clone the repository ...
... make some changes and some commits ...
git push origin master