feat: click support for 'statusline', 'winbar' #18650

The mouseclick item "%@" is now supported by 'statusline' and 'winbar'.
Previously it was only supported by 'tabline'.
This commit is contained in:
Famiu Haque
2022-05-23 19:11:24 +06:00
committed by GitHub
parent 9e1ee9fb1d
commit 4c6626f03d
10 changed files with 529 additions and 386 deletions

View File

@@ -6009,7 +6009,7 @@ A jump table for the options with a short description can be found at |Q_op|.
the label, e.g.: %3Xclose%X. Use %999X for a "close current the label, e.g.: %3Xclose%X. Use %999X for a "close current
tab" label. Clicking this label with left mouse button closes tab" label. Clicking this label with left mouse button closes
specified tab page. specified tab page.
@ N For 'tabline': start of execute function label. Use %X or %T to @ N Start of execute function label. Use %X or %T to
end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this end the label, e.g.: %10@SwitchBuffer@foo.c%X. Clicking this
label runs specified function: in the example when clicking once label runs specified function: in the example when clicking once
using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l', using left mouse button on "foo.c" "SwitchBuffer(10, 1, 'l',
@@ -6033,8 +6033,6 @@ A jump table for the options with a short description can be found at |Q_op|.
is a bug that denotes that new mouse button recognition was is a bug that denotes that new mouse button recognition was
added without modifying code that reacts on mouse clicks on added without modifying code that reacts on mouse clicks on
this label. this label.
Note: to test whether your version of Neovim contains this
feature use `has('tablineat')`.
< - Where to truncate line if too long. Default is at the start. < - Where to truncate line if too long. Default is at the start.
No width fields allowed. No width fields allowed.
= - Separation point between alignment sections. Each section will = - Separation point between alignment sections. Each section will

View File

@@ -6,6 +6,8 @@
// for FILE // for FILE
#include <stdio.h> #include <stdio.h>
#include "grid_defs.h"
typedef struct file_buffer buf_T; // Forward declaration typedef struct file_buffer buf_T; // Forward declaration
// Reference to a buffer that stores the value of buf_free_count. // Reference to a buffer that stores the value of buf_free_count.
@@ -1492,6 +1494,16 @@ struct window_S {
// Location list reference used in the location list window. // Location list reference used in the location list window.
// In a non-location list window, w_llist_ref is NULL. // In a non-location list window, w_llist_ref is NULL.
qf_info_T *w_llist_ref; qf_info_T *w_llist_ref;
// Status line click definitions
StlClickDefinition *w_status_click_defs;
// Size of the w_status_click_defs array
size_t w_status_click_defs_size;
// Window bar click definitions
StlClickDefinition *w_winbar_click_defs;
// Size of the w_winbar_click_defs array
size_t w_winbar_click_defs_size;
}; };
static inline int win_hl_attr(win_T *wp, int hlf) static inline int win_hl_attr(win_T *wp, int hlf)

View File

@@ -111,4 +111,22 @@ struct ScreenGrid {
false, 0, 0, NULL, false, true, 0, \ false, 0, 0, NULL, false, true, 0, \
0, 0, 0, 0, 0, false } 0, 0, 0, 0, 0, false }
/// Status line click definition
typedef struct {
enum {
kStlClickDisabled = 0, ///< Clicks to this area are ignored.
kStlClickTabSwitch, ///< Switch to the given tab.
kStlClickTabClose, ///< Close given tab.
kStlClickFuncRun, ///< Run user function.
} type; ///< Type of the click.
int tabnr; ///< Tab page number.
char *func; ///< Function to run.
} StlClickDefinition;
/// Used for tabline clicks
typedef struct {
StlClickDefinition def; ///< Click definition.
const char *start; ///< Location where region starts.
} StlClickRecord;
#endif // NVIM_GRID_DEFS_H #endif // NVIM_GRID_DEFS_H

View File

@@ -241,8 +241,8 @@ retnomove:
} }
curwin->w_cursor.lnum = curwin->w_topline; curwin->w_cursor.lnum = curwin->w_topline;
} else if (on_status_line && which_button == MOUSE_LEFT) { } else if (on_status_line) {
if (dragwin != NULL) { if (which_button == MOUSE_LEFT && dragwin != NULL) {
// Drag the status line // Drag the status line
count = row - dragwin->w_winrow - dragwin->w_height + 1 count = row - dragwin->w_winrow - dragwin->w_height + 1
- on_status_line; - on_status_line;

View File

@@ -32,6 +32,7 @@
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/getchar.h" #include "nvim/getchar.h"
#include "nvim/globals.h" #include "nvim/globals.h"
#include "nvim/grid_defs.h"
#include "nvim/indent.h" #include "nvim/indent.h"
#include "nvim/keycodes.h" #include "nvim/keycodes.h"
#include "nvim/log.h" #include "nvim/log.h"
@@ -1432,6 +1433,63 @@ static void move_tab_to_mouse(void)
} }
} }
/// Call click definition function for column "col" in the "click_defs" array for button
/// "which_button".
static void call_click_def_func(StlClickDefinition *click_defs, int col, int which_button)
{
typval_T argv[] = {
{
.v_lock = VAR_FIXED,
.v_type = VAR_NUMBER,
.vval = {
.v_number = (varnumber_T)click_defs[col].tabnr
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_NUMBER,
.vval = {
.v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK
? 4
: ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK
? 3
: ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK
? 2
: 1)))
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_STRING,
.vval = {
.v_string = (which_button == MOUSE_LEFT
? "l"
: (which_button == MOUSE_RIGHT
? "r"
: (which_button == MOUSE_MIDDLE
? "m"
: "?")))
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_STRING,
.vval = {
.v_string = (char[]) {
(char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '),
(char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '),
(char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '),
(char)(mod_mask & MOD_MASK_META ? 'm' : ' '),
NUL
}
},
}
};
typval_T rettv;
(void)call_vim_function(click_defs[col].func, ARRAY_SIZE(argv), argv, &rettv);
tv_clear(&rettv);
}
/// Do the appropriate action for the current mouse click in the current mode. /// Do the appropriate action for the current mouse click in the current mode.
/// Not used for Command-line mode. /// Not used for Command-line mode.
/// ///
@@ -1481,6 +1539,7 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
int jump_flags = 0; // flags for jump_to_mouse() int jump_flags = 0; // flags for jump_to_mouse()
pos_T start_visual; pos_T start_visual;
bool moved; // Has cursor moved? bool moved; // Has cursor moved?
bool in_winbar; // mouse in window bar
bool in_status_line; // mouse in status line bool in_status_line; // mouse in status line
static bool in_tab_line = false; // mouse clicked in tab line static bool in_tab_line = false; // mouse clicked in tab line
bool in_sep_line; // mouse in vertical separator line bool in_sep_line; // mouse in vertical separator line
@@ -1711,67 +1770,11 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
} }
} }
break; break;
case kStlClickFuncRun: { case kStlClickFuncRun:
typval_T argv[] = { call_click_def_func(tab_page_click_defs, mouse_col, which_button);
{
.v_lock = VAR_FIXED,
.v_type = VAR_NUMBER,
.vval = {
.v_number = (varnumber_T)tab_page_click_defs[mouse_col].tabnr
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_NUMBER,
.vval = {
.v_number = ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_4CLICK
? 4
: ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_3CLICK
? 3
: ((mod_mask & MOD_MASK_MULTI_CLICK) == MOD_MASK_2CLICK
? 2
: 1)))
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_STRING,
.vval = {
.v_string = (which_button == MOUSE_LEFT
? "l"
: (which_button == MOUSE_RIGHT
? "r"
: (which_button == MOUSE_MIDDLE
? "m"
: "?")))
},
},
{
.v_lock = VAR_FIXED,
.v_type = VAR_STRING,
.vval = {
.v_string = (char[]) {
(char)(mod_mask & MOD_MASK_SHIFT ? 's' : ' '),
(char)(mod_mask & MOD_MASK_CTRL ? 'c' : ' '),
(char)(mod_mask & MOD_MASK_ALT ? 'a' : ' '),
(char)(mod_mask & MOD_MASK_META ? 'm' : ' '),
NUL
}
},
}
};
typval_T rettv;
funcexe_T funcexe = FUNCEXE_INIT;
funcexe.firstline = curwin->w_cursor.lnum;
funcexe.lastline = curwin->w_cursor.lnum;
funcexe.evaluate = true;
(void)call_func(tab_page_click_defs[mouse_col].func, -1,
&rettv, ARRAY_SIZE(argv), argv, &funcexe);
tv_clear(&rettv);
break; break;
} }
} }
}
return true; return true;
} else if (is_drag && in_tab_line) { } else if (is_drag && in_tab_line) {
move_tab_to_mouse(); move_tab_to_mouse();
@@ -1840,15 +1843,39 @@ bool do_mouse(oparg_T *oap, int c, int dir, long count, bool fixindent)
oap == NULL ? NULL : &(oap->inclusive), oap == NULL ? NULL : &(oap->inclusive),
which_button); which_button);
// A click in the window bar has no side effects.
if (jump_flags & MOUSE_WINBAR) {
return false;
}
moved = (jump_flags & CURSOR_MOVED); moved = (jump_flags & CURSOR_MOVED);
in_winbar = (jump_flags & MOUSE_WINBAR);
in_status_line = (jump_flags & IN_STATUS_LINE); in_status_line = (jump_flags & IN_STATUS_LINE);
in_sep_line = (jump_flags & IN_SEP_LINE); in_sep_line = (jump_flags & IN_SEP_LINE);
if ((in_winbar || in_status_line) && is_click) {
// Handle click event on window bar or status lin
int click_grid = mouse_grid;
int click_row = mouse_row;
int click_col = mouse_col;
win_T *wp = mouse_find_win(&click_grid, &click_row, &click_col);
StlClickDefinition *click_defs = in_status_line ? wp->w_status_click_defs
: wp->w_winbar_click_defs;
if (click_defs != NULL) {
switch (click_defs[click_col].type) {
case kStlClickDisabled:
break;
case kStlClickFuncRun:
call_click_def_func(click_defs, click_col, which_button);
break;
default:
assert(false && "winbar and statusline only support %@ for clicks");
break;
}
}
return false;
} else if (in_winbar) {
// A drag or release event in the window bar has no side effects.
return false;
}
// When jumping to another window, clear a pending operator. That's a bit // When jumping to another window, clear a pending operator. That's a bit
// friendlier than beeping and not jumping to that window. // friendlier than beeping and not jumping to that window.

View File

@@ -88,6 +88,7 @@
#include "nvim/fold.h" #include "nvim/fold.h"
#include "nvim/garray.h" #include "nvim/garray.h"
#include "nvim/getchar.h" #include "nvim/getchar.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight.h" #include "nvim/highlight.h"
#include "nvim/highlight_group.h" #include "nvim/highlight_group.h"
#include "nvim/indent.h" #include "nvim/indent.h"
@@ -5337,11 +5338,27 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
attr = (wp == curwin) ? HL_ATTR(HLF_WBR) : HL_ATTR(HLF_WBRNC); attr = (wp == curwin) ? HL_ATTR(HLF_WBR) : HL_ATTR(HLF_WBRNC);
maxwidth = wp->w_width_inner; maxwidth = wp->w_width_inner;
use_sandbox = was_set_insecurely(wp, "winbar", 0); use_sandbox = was_set_insecurely(wp, "winbar", 0);
stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
// Allocate / resize the click definitions array for winbar if needed.
if (wp->w_winbar_height && wp->w_winbar_click_defs_size < (size_t)maxwidth) {
xfree(wp->w_winbar_click_defs);
wp->w_winbar_click_defs_size = (size_t)maxwidth;
wp->w_winbar_click_defs = xcalloc(wp->w_winbar_click_defs_size, sizeof(StlClickRecord));
}
} else { } else {
row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp); row = is_stl_global ? (Rows - p_ch - 1) : W_ENDROW(wp);
fillchar = fillchar_status(&attr, wp); fillchar = fillchar_status(&attr, wp);
maxwidth = is_stl_global ? Columns : wp->w_width; maxwidth = is_stl_global ? Columns : wp->w_width;
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
// Allocate / resize the click definitions array for statusline if needed.
if (wp->w_status_click_defs_size < (size_t)maxwidth) {
xfree(wp->w_status_click_defs);
wp->w_status_click_defs_size = maxwidth;
wp->w_status_click_defs = xcalloc(wp->w_status_click_defs_size, sizeof(StlClickRecord));
}
if (draw_ruler) { if (draw_ruler) {
stl = p_ruf; stl = p_ruf;
// advance past any leading group spec - implicit in ru_col // advance past any leading group spec - implicit in ru_col
@@ -5445,8 +5462,16 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
grid_puts_line_flush(false); grid_puts_line_flush(false);
if (wp == NULL) { // Fill the tab_page_click_defs, w_status_click_defs or w_winbar_click_defs array for clicking
// Fill the tab_page_click_defs array for clicking in the tab pages line. // in the tab page line, status line or window bar
StlClickDefinition *click_defs = (wp == NULL) ? tab_page_click_defs
: draw_winbar ? wp->w_winbar_click_defs
: wp->w_status_click_defs;
if (click_defs == NULL) {
goto theend;
}
col = 0; col = 0;
len = 0; len = 0;
p = buf; p = buf;
@@ -5456,14 +5481,18 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
for (n = 0; tabtab[n].start != NULL; n++) { for (n = 0; tabtab[n].start != NULL; n++) {
len += vim_strnsize(p, (int)(tabtab[n].start - (char *)p)); len += vim_strnsize(p, (int)(tabtab[n].start - (char *)p));
while (col < len) { while (col < len) {
tab_page_click_defs[col++] = cur_click_def; click_defs[col++] = cur_click_def;
} }
p = (char_u *)tabtab[n].start; p = (char_u *)tabtab[n].start;
cur_click_def = tabtab[n].def; cur_click_def = tabtab[n].def;
if ((wp != NULL) && !(cur_click_def.type == kStlClickDisabled
|| cur_click_def.type == kStlClickFuncRun)) {
// window bar and status line only support click functions
cur_click_def.type = kStlClickDisabled;
} }
while (col < Columns) {
tab_page_click_defs[col++] = cur_click_def;
} }
while (col < maxwidth) {
click_defs[col++] = cur_click_def;
} }
theend: theend:
@@ -5572,7 +5601,6 @@ void check_for_delay(bool check_msg_scroll)
} }
} }
/// Resize the screen to Rows and Columns. /// Resize the screen to Rows and Columns.
/// ///
/// Allocate default_grid.chars[] and other grid arrays. /// Allocate default_grid.chars[] and other grid arrays.
@@ -5641,7 +5669,7 @@ retry:
StlClickDefinition *new_tab_page_click_defs = StlClickDefinition *new_tab_page_click_defs =
xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs)); xcalloc((size_t)Columns, sizeof(*new_tab_page_click_defs));
clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size);
xfree(tab_page_click_defs); xfree(tab_page_click_defs);
tab_page_click_defs = new_tab_page_click_defs; tab_page_click_defs = new_tab_page_click_defs;
@@ -5672,19 +5700,19 @@ retry:
resizing = false; resizing = false;
} }
/// Clear tab_page_click_defs table /// Clear status line, window bar or tab page line click definition table
/// ///
/// @param[out] tpcd Table to clear. /// @param[out] tpcd Table to clear.
/// @param[in] tpcd_size Size of the table. /// @param[in] tpcd_size Size of the table.
void clear_tab_page_click_defs(StlClickDefinition *const tpcd, const long tpcd_size) void stl_clear_click_defs(StlClickDefinition *const click_defs, const long click_defs_size)
{ {
if (tpcd != NULL) { if (click_defs != NULL) {
for (long i = 0; i < tpcd_size; i++) { for (long i = 0; i < click_defs_size; i++) {
if (i == 0 || tpcd[i].func != tpcd[i - 1].func) { if (i == 0 || click_defs[i].func != click_defs[i - 1].func) {
xfree(tpcd[i].func); xfree(click_defs[i].func);
} }
} }
memset(tpcd, 0, (size_t)tpcd_size * sizeof(tpcd[0])); memset(click_defs, 0, (size_t)click_defs_size * sizeof(click_defs[0]));
} }
} }
@@ -6200,7 +6228,7 @@ void draw_tabline(void)
// Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect. // Init TabPageIdxs[] to zero: Clicking outside of tabs has no effect.
assert(Columns == tab_page_click_defs_size); assert(Columns == tab_page_click_defs_size);
clear_tab_page_click_defs(tab_page_click_defs, tab_page_click_defs_size); stl_clear_click_defs(tab_page_click_defs, tab_page_click_defs_size);
// Use the 'tabline' option if it's set. // Use the 'tabline' option if it's set.
if (*p_tal != NUL) { if (*p_tal != NUL) {

View File

@@ -30,24 +30,6 @@ typedef enum {
// Maximum columns for terminal highlight attributes // Maximum columns for terminal highlight attributes
#define TERM_ATTRS_MAX 1024 #define TERM_ATTRS_MAX 1024
/// Status line click definition
typedef struct {
enum {
kStlClickDisabled = 0, ///< Clicks to this area are ignored.
kStlClickTabSwitch, ///< Switch to the given tab.
kStlClickTabClose, ///< Close given tab.
kStlClickFuncRun, ///< Run user function.
} type; ///< Type of the click.
int tabnr; ///< Tab page number.
char *func; ///< Function to run.
} StlClickDefinition;
/// Used for tabline clicks
typedef struct {
StlClickDefinition def; ///< Click definition.
const char *start; ///< Location where region starts.
} StlClickRecord;
/// Array defining what should be done when tabline is clicked /// Array defining what should be done when tabline is clicked
EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL); EXTERN StlClickDefinition *tab_page_click_defs INIT(= NULL);

View File

@@ -5119,6 +5119,12 @@ static void win_free(win_T *wp, tabpage_T *tp)
xfree(wp->w_localdir); xfree(wp->w_localdir);
xfree(wp->w_prevdir); xfree(wp->w_prevdir);
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
xfree(wp->w_status_click_defs);
stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
xfree(wp->w_winbar_click_defs);
// Remove the window from the b_wininfo lists, it may happen that the // Remove the window from the b_wininfo lists, it may happen that the
// freed memory is re-used for another window. // freed memory is re-used for another window.
FOR_ALL_BUFFERS(buf) { FOR_ALL_BUFFERS(buf) {
@@ -6612,6 +6618,23 @@ void last_status(bool morewin)
global_stl_height() > 0); global_stl_height() > 0);
} }
// Remove status line from window, replacing it with a horizontal separator if needed.
static void win_remove_status_line(win_T *wp, bool add_hsep)
{
wp->w_status_height = 0;
if (add_hsep) {
wp->w_hsep_height = 1;
} else {
win_new_height(wp, wp->w_height + STATUS_HEIGHT);
}
comp_col();
stl_clear_click_defs(wp->w_status_click_defs, wp->w_status_click_defs_size);
xfree(wp->w_status_click_defs);
wp->w_status_click_defs_size = 0;
wp->w_status_click_defs = NULL;
}
// Look for resizable frames and take lines from them to make room for the statusline // Look for resizable frames and take lines from them to make room for the statusline
static void resize_frame_for_status(frame_T *fr) static void resize_frame_for_status(frame_T *fr)
{ {
@@ -6652,10 +6675,7 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
if (is_last) { if (is_last) {
if (wp->w_status_height != 0 && (!statusline || is_stl_global)) { if (wp->w_status_height != 0 && (!statusline || is_stl_global)) {
// Remove status line win_remove_status_line(wp, false);
wp->w_status_height = 0;
win_new_height(wp, wp->w_height + STATUS_HEIGHT);
comp_col();
} else if (wp->w_status_height == 0 && !is_stl_global && statusline) { } else if (wp->w_status_height == 0 && !is_stl_global && statusline) {
// Add statusline to window if needed // Add statusline to window if needed
wp->w_status_height = STATUS_HEIGHT; wp->w_status_height = STATUS_HEIGHT;
@@ -6665,9 +6685,7 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
} else if (wp->w_status_height != 0 && is_stl_global) { } else if (wp->w_status_height != 0 && is_stl_global) {
// If statusline is global and the window has a statusline, replace it with a horizontal // If statusline is global and the window has a statusline, replace it with a horizontal
// separator // separator
wp->w_status_height = 0; win_remove_status_line(wp, true);
wp->w_hsep_height = 1;
comp_col();
} else if (wp->w_status_height == 0 && !is_stl_global) { } else if (wp->w_status_height == 0 && !is_stl_global) {
// If statusline isn't global and the window doesn't have a statusline, re-add it // If statusline isn't global and the window doesn't have a statusline, re-add it
wp->w_status_height = STATUS_HEIGHT; wp->w_status_height = STATUS_HEIGHT;
@@ -6675,13 +6693,8 @@ static void last_status_rec(frame_T *fr, bool statusline, bool is_stl_global)
comp_col(); comp_col();
} }
redraw_all_later(SOME_VALID); redraw_all_later(SOME_VALID);
} else if (fr->fr_layout == FR_COL) {
// For a column frame, recursively call this function for all child frames
FOR_ALL_FRAMES(fp, fr->fr_child) {
last_status_rec(fp, statusline, is_stl_global);
}
} else { } else {
// For a row frame, recursively call this function for all child frames // For a column or row frame, recursively call this function for all child frames
FOR_ALL_FRAMES(fp, fr->fr_child) { FOR_ALL_FRAMES(fp, fr->fr_child) {
last_status_rec(fp, statusline, is_stl_global); last_status_rec(fp, statusline, is_stl_global);
} }
@@ -6697,6 +6710,14 @@ void set_winbar(void)
wp->w_winbar_height = winbar_height; wp->w_winbar_height = winbar_height;
win_set_inner_size(wp); win_set_inner_size(wp);
wp->w_redr_winbar = winbar_height; wp->w_redr_winbar = winbar_height;
if (winbar_height == 0) {
// When removing winbar, deallocate the w_winbar_click_defs array
stl_clear_click_defs(wp->w_winbar_click_defs, wp->w_winbar_click_defs_size);
xfree(wp->w_winbar_click_defs);
wp->w_winbar_click_defs_size = 0;
wp->w_winbar_click_defs = NULL;
}
} }
} }
} }

View File

@@ -1,260 +0,0 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear, command, feed = helpers.clear, helpers.command, helpers.feed
local eq, funcs, meths = helpers.eq, helpers.funcs, helpers.meths
describe('global statusline', function()
local screen
before_each(function()
clear()
screen = Screen.new(60, 16)
screen:attach()
command('set laststatus=3')
command('set ruler')
end)
it('works', function()
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue1};
[2] = {bold = true, reverse = true};
}}
feed('i<CR><CR>')
screen:expect{grid=[[
|
|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] [+] 3,1 All}|
{3:-- INSERT --} |
]], attr_ids={
[1] = {bold = true, foreground = Screen.colors.Blue};
[2] = {bold = true, reverse = true};
[3] = {bold = true};
}}
end)
it('works with splits', function()
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
screen:expect{grid=[[
│ │ │^ |
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }├────────────────┤{2:~}│{2:~ }|
{2:~ }│ │{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}├────────────────────|
{2:~ }│{2:~ }│{2:~}│ |
────────────────────┴────────────────┴─┤{2:~ }|
│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{3:[No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {reverse = true};
[2] = {bold = true, foreground = Screen.colors.Blue1};
[3] = {bold = true, reverse = true};
}}
end)
it('works when switching between values of laststatus', function()
command('set laststatus=1')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
0,0-1 All |
]], attr_ids={
[1] = {foreground = Screen.colors.Blue, bold = true};
}}
command('set laststatus=3')
screen:expect{grid=[[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {foreground = Screen.colors.Blue, bold = true};
[2] = {reverse = true, bold = true};
}}
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
command('set laststatus=2')
screen:expect{grid=[[
│ │ │^ |
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{1:< Name] 0,0-1 }│{2:~}│{2:~ }|
{2:~ }│ │{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{3:<No Name] 0,0-1 All}|
{2:~ }│{2:~ }│{2:~}│ |
{1:<No Name] 0,0-1 All < Name] 0,0-1 <}│{2:~ }|
│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{1:[No Name] 0,0-1 All <No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {reverse = true};
[2] = {foreground = Screen.colors.Blue, bold = true};
[3] = {reverse = true, bold = true};
}}
command('set laststatus=3')
screen:expect{grid=[[
│ │ │^ |
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }├────────────────┤{2:~}│{2:~ }|
{2:~ }│ │{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}├────────────────────|
{2:~ }│{2:~ }│{2:~}│ |
────────────────────┴────────────────┴─┤{2:~ }|
│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{3:[No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {reverse = true};
[2] = {foreground = Screen.colors.Blue, bold = true};
[3] = {reverse = true, bold = true};
}}
command('set laststatus=0')
screen:expect{grid=[[
│ │ │^ |
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{1:< Name] 0,0-1 }│{2:~}│{2:~ }|
{2:~ }│ │{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{3:<No Name] 0,0-1 All}|
{2:~ }│{2:~ }│{2:~}│ |
{1:<No Name] 0,0-1 All < Name] 0,0-1 <}│{2:~ }|
│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
0,0-1 All |
]], attr_ids={
[1] = {reverse = true};
[2] = {foreground = Screen.colors.Blue, bold = true};
[3] = {reverse = true, bold = true};
}}
command('set laststatus=3')
screen:expect{grid=[[
│ │ │^ |
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }├────────────────┤{2:~}│{2:~ }|
{2:~ }│ │{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}│{2:~ }|
{2:~ }│{2:~ }│{2:~}├────────────────────|
{2:~ }│{2:~ }│{2:~}│ |
────────────────────┴────────────────┴─┤{2:~ }|
│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{2:~ }│{2:~ }|
{3:[No Name] 0,0-1 All}|
|
]], attr_ids={
[1] = {reverse = true};
[2] = {foreground = Screen.colors.Blue, bold = true};
[3] = {reverse = true, bold = true};
}}
end)
it('win_move_statusline() can reduce cmdheight to 1', function()
eq(1, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, -1)
eq(2, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, -1)
eq(3, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, 1)
eq(2, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, 1)
eq(1, meths.get_option('cmdheight'))
end)
it('mouse dragging can reduce cmdheight to 1', function()
command('set mouse=a')
meths.input_mouse('left', 'press', '', 0, 14, 10)
eq(1, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
eq(2, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 12, 10)
eq(3, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
eq(2, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 14, 10)
eq(1, meths.get_option('cmdheight'))
end)
end)

View File

@@ -0,0 +1,317 @@
local helpers = require('test.functional.helpers')(after_each)
local Screen = require('test.functional.ui.screen')
local clear = helpers.clear
local command = helpers.command
local feed = helpers.feed
local eq = helpers.eq
local funcs = helpers.funcs
local meths = helpers.meths
local exec = helpers.exec
local exec_lua = helpers.exec_lua
local eval = helpers.eval
describe('statusline clicks', function()
local screen
before_each(function()
clear()
screen = Screen.new(40, 8)
screen:attach()
command('set laststatus=2')
exec([=[
function! MyClickFunc(minwid, clicks, button, mods)
let g:testvar = printf("%d %d %s", a:minwid, a:clicks, a:button)
endfunction
]=])
end)
it('works', function()
meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
meths.input_mouse('left', 'press', '', 0, 6, 17)
eq('0 1 l', eval("g:testvar"))
meths.input_mouse('right', 'press', '', 0, 6, 17)
eq('0 1 r', eval("g:testvar"))
end)
it('works for winbar', function()
meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
meths.input_mouse('left', 'press', '', 0, 0, 17)
eq('0 1 l', eval("g:testvar"))
meths.input_mouse('right', 'press', '', 0, 6, 17)
eq('0 1 r', eval("g:testvar"))
end)
it('works for winbar in floating window', function()
meths.open_win(0, true, { width=30, height=4, relative='editor', row=1, col=5,
border = "single" })
meths.set_option_value('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T',
{ scope = 'local' })
meths.input_mouse('left', 'press', '', 0, 2, 23)
eq('0 1 l', eval("g:testvar"))
end)
it('works when there are multiple windows', function()
command('split')
meths.set_option('statusline', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
meths.set_option('winbar', 'Not clicky stuff %0@MyClickFunc@Clicky stuff%T')
meths.input_mouse('left', 'press', '', 0, 0, 17)
eq('0 1 l', eval("g:testvar"))
meths.input_mouse('right', 'press', '', 0, 4, 17)
eq('0 1 r', eval("g:testvar"))
meths.input_mouse('middle', 'press', '', 0, 3, 17)
eq('0 1 m', eval("g:testvar"))
meths.input_mouse('left', 'press', '', 0, 6, 17)
eq('0 1 l', eval("g:testvar"))
end)
it('works with Lua function', function()
exec_lua([[
function clicky_func(minwid, clicks, button, mods)
vim.g.testvar = string.format("%d %d %s", minwid, clicks, button)
end
]])
meths.set_option('statusline', 'Not clicky stuff %0@v:lua.clicky_func@Clicky stuff%T')
meths.input_mouse('left', 'press', '', 0, 6, 17)
eq('0 1 l', eval("g:testvar"))
end)
it('ignores unsupported click items', function()
command('tabnew | tabprevious')
meths.set_option('statusline', '%2TNot clicky stuff%T')
meths.input_mouse('left', 'press', '', 0, 6, 0)
eq(1, meths.get_current_tabpage().id)
meths.set_option('statusline', '%2XNot clicky stuff%X')
meths.input_mouse('left', 'press', '', 0, 6, 0)
eq(2, #meths.list_tabpages())
end)
end)
describe('global statusline', function()
local screen
before_each(function()
clear()
screen = Screen.new(60, 16)
screen:attach()
screen:set_default_attr_ids({
[1] = {bold = true, foreground = Screen.colors.Blue};
[2] = {bold = true, reverse = true};
[3] = {bold = true};
[4] = {reverse = true};
})
command('set laststatus=3')
command('set ruler')
end)
it('works', function()
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]])
feed('i<CR><CR>')
screen:expect([[
|
|
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] [+] 3,1 All}|
{3:-- INSERT --} |
]])
end)
it('works with splits', function()
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
screen:expect([[
│ │ │^ |
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }├────────────────┤{1:~}│{1:~ }|
{1:~ }│ │{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}├────────────────────|
{1:~ }│{1:~ }│{1:~}│ |
────────────────────┴────────────────┴─┤{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]])
end)
it('works when switching between values of laststatus', function()
command('set laststatus=1')
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
0,0-1 All |
]])
command('set laststatus=3')
screen:expect([[
^ |
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]])
command('vsplit | split | vsplit | vsplit | wincmd l | split | 2wincmd l | split')
command('set laststatus=2')
screen:expect([[
│ │ │^ |
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{4:< Name] 0,0-1 }│{1:~}│{1:~ }|
{1:~ }│ │{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{2:<No Name] 0,0-1 All}|
{1:~ }│{1:~ }│{1:~}│ |
{4:<No Name] 0,0-1 All < Name] 0,0-1 <}│{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{4:[No Name] 0,0-1 All <No Name] 0,0-1 All}|
|
]])
command('set laststatus=3')
screen:expect([[
│ │ │^ |
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }├────────────────┤{1:~}│{1:~ }|
{1:~ }│ │{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}├────────────────────|
{1:~ }│{1:~ }│{1:~}│ |
────────────────────┴────────────────┴─┤{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]])
command('set laststatus=0')
screen:expect([[
│ │ │^ |
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{4:< Name] 0,0-1 }│{1:~}│{1:~ }|
{1:~ }│ │{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{2:<No Name] 0,0-1 All}|
{1:~ }│{1:~ }│{1:~}│ |
{4:<No Name] 0,0-1 All < Name] 0,0-1 <}│{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
0,0-1 All |
]])
command('set laststatus=3')
screen:expect([[
│ │ │^ |
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }├────────────────┤{1:~}│{1:~ }|
{1:~ }│ │{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}│{1:~ }|
{1:~ }│{1:~ }│{1:~}├────────────────────|
{1:~ }│{1:~ }│{1:~}│ |
────────────────────┴────────────────┴─┤{1:~ }|
│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{1:~ }│{1:~ }|
{2:[No Name] 0,0-1 All}|
|
]])
end)
it('win_move_statusline() can reduce cmdheight to 1', function()
eq(1, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, -1)
eq(2, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, -1)
eq(3, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, 1)
eq(2, meths.get_option('cmdheight'))
funcs.win_move_statusline(0, 1)
eq(1, meths.get_option('cmdheight'))
end)
it('mouse dragging can reduce cmdheight to 1', function()
command('set mouse=a')
meths.input_mouse('left', 'press', '', 0, 14, 10)
eq(1, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
eq(2, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 12, 10)
eq(3, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 13, 10)
eq(2, meths.get_option('cmdheight'))
meths.input_mouse('left', 'drag', '', 0, 14, 10)
eq(1, meths.get_option('cmdheight'))
end)
end)