mirror of
https://github.com/neovim/neovim.git
synced 2025-09-06 11:28:22 +00:00
vim-patch:8.2.4165: the nv_g_cmd() function is too long
Problem: The nv_g_cmd() function is too long.
Solution: Move code to separate functions. (Yegappan Lakshmanan,
closes vim/vim#9576)
05386ca1d4
This commit is contained in:
@@ -5879,13 +5879,218 @@ static void nv_suspend(cmdarg_T *cap)
|
|||||||
do_cmdline_cmd("st");
|
do_cmdline_cmd("st");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// "gv": Reselect the previous Visual area. If Visual already active,
|
||||||
|
/// exchange previous and current Visual area.
|
||||||
|
static void nv_gv_cmd(cmdarg_T *cap)
|
||||||
|
{
|
||||||
|
if (checkclearop(cap->oap)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curbuf->b_visual.vi_start.lnum == 0
|
||||||
|
|| curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count
|
||||||
|
|| curbuf->b_visual.vi_end.lnum == 0) {
|
||||||
|
beep_flush();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos_T tpos;
|
||||||
|
// set w_cursor to the start of the Visual area, tpos to the end
|
||||||
|
if (VIsual_active) {
|
||||||
|
int i = VIsual_mode;
|
||||||
|
VIsual_mode = curbuf->b_visual.vi_mode;
|
||||||
|
curbuf->b_visual.vi_mode = i;
|
||||||
|
curbuf->b_visual_mode_eval = i;
|
||||||
|
i = curwin->w_curswant;
|
||||||
|
curwin->w_curswant = curbuf->b_visual.vi_curswant;
|
||||||
|
curbuf->b_visual.vi_curswant = i;
|
||||||
|
|
||||||
|
tpos = curbuf->b_visual.vi_end;
|
||||||
|
curbuf->b_visual.vi_end = curwin->w_cursor;
|
||||||
|
curwin->w_cursor = curbuf->b_visual.vi_start;
|
||||||
|
curbuf->b_visual.vi_start = VIsual;
|
||||||
|
} else {
|
||||||
|
VIsual_mode = curbuf->b_visual.vi_mode;
|
||||||
|
curwin->w_curswant = curbuf->b_visual.vi_curswant;
|
||||||
|
tpos = curbuf->b_visual.vi_end;
|
||||||
|
curwin->w_cursor = curbuf->b_visual.vi_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
VIsual_active = true;
|
||||||
|
VIsual_reselect = true;
|
||||||
|
|
||||||
|
// Set Visual to the start and w_cursor to the end of the Visual
|
||||||
|
// area. Make sure they are on an existing character.
|
||||||
|
check_cursor();
|
||||||
|
VIsual = curwin->w_cursor;
|
||||||
|
curwin->w_cursor = tpos;
|
||||||
|
check_cursor();
|
||||||
|
update_topline(curwin);
|
||||||
|
|
||||||
|
// When called from normal "g" command: start Select mode when
|
||||||
|
// 'selectmode' contains "cmd". When called for K_SELECT, always
|
||||||
|
// start Select mode.
|
||||||
|
if (cap->arg) {
|
||||||
|
VIsual_select = true;
|
||||||
|
VIsual_select_reg = 0;
|
||||||
|
} else {
|
||||||
|
may_start_select('c');
|
||||||
|
}
|
||||||
|
setmouse();
|
||||||
|
redraw_curbuf_later(INVERTED);
|
||||||
|
showmode();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "g0", "g^" : Like "0" and "^" but for screen lines.
|
||||||
|
/// "gm": middle of "g0" and "g$".
|
||||||
|
static void nv_g_home_m_cmd(cmdarg_T *cap)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
const bool flag = cap->nchar == '^';
|
||||||
|
|
||||||
|
cap->oap->motion_type = kMTCharWise;
|
||||||
|
cap->oap->inclusive = false;
|
||||||
|
if (curwin->w_p_wrap
|
||||||
|
&& curwin->w_width_inner != 0) {
|
||||||
|
int width1 = curwin->w_width_inner - curwin_col_off();
|
||||||
|
int width2 = width1 + curwin_col_off2();
|
||||||
|
|
||||||
|
validate_virtcol();
|
||||||
|
i = 0;
|
||||||
|
if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) {
|
||||||
|
i = (curwin->w_virtcol - width1) / width2 * width2 + width1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i = curwin->w_leftcol;
|
||||||
|
}
|
||||||
|
// Go to the middle of the screen line. When 'number' or
|
||||||
|
// 'relativenumber' is on and lines are wrapping the middle can be more
|
||||||
|
// to the left.
|
||||||
|
if (cap->nchar == 'm') {
|
||||||
|
i += (curwin->w_width_inner - curwin_col_off()
|
||||||
|
+ ((curwin->w_p_wrap && i > 0) ? curwin_col_off2() : 0)) / 2;
|
||||||
|
}
|
||||||
|
coladvance((colnr_T)i);
|
||||||
|
if (flag) {
|
||||||
|
do {
|
||||||
|
i = gchar_cursor();
|
||||||
|
} while (ascii_iswhite(i) && oneright());
|
||||||
|
curwin->w_valid &= ~VALID_WCOL;
|
||||||
|
}
|
||||||
|
curwin->w_set_curswant = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "g_": to the last non-blank character in the line or <count> lines downward.
|
||||||
|
static void nv_g_underscore_cmd(cmdarg_T *cap)
|
||||||
|
{
|
||||||
|
cap->oap->motion_type = kMTCharWise;
|
||||||
|
cap->oap->inclusive = true;
|
||||||
|
curwin->w_curswant = MAXCOL;
|
||||||
|
if (cursor_down(cap->count1 - 1, cap->oap->op_type == OP_NOP) == false) {
|
||||||
|
clearopbeep(cap->oap);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char_u *ptr = get_cursor_line_ptr();
|
||||||
|
|
||||||
|
// In Visual mode we may end up after the line.
|
||||||
|
if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) {
|
||||||
|
curwin->w_cursor.col--;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decrease the cursor column until it's on a non-blank.
|
||||||
|
while (curwin->w_cursor.col > 0 && ascii_iswhite(ptr[curwin->w_cursor.col])) {
|
||||||
|
curwin->w_cursor.col--;
|
||||||
|
}
|
||||||
|
curwin->w_set_curswant = true;
|
||||||
|
adjust_for_sel(cap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "g$" : Like "$" but for screen lines.
|
||||||
|
static void nv_g_dollar_cmd(cmdarg_T *cap)
|
||||||
|
{
|
||||||
|
oparg_T *oap = cap->oap;
|
||||||
|
int i;
|
||||||
|
int col_off = curwin_col_off();
|
||||||
|
|
||||||
|
oap->motion_type = kMTCharWise;
|
||||||
|
oap->inclusive = true;
|
||||||
|
if (curwin->w_p_wrap && curwin->w_width_inner != 0) {
|
||||||
|
curwin->w_curswant = MAXCOL; // so we stay at the end
|
||||||
|
if (cap->count1 == 1) {
|
||||||
|
int width1 = curwin->w_width_inner - col_off;
|
||||||
|
int width2 = width1 + curwin_col_off2();
|
||||||
|
|
||||||
|
validate_virtcol();
|
||||||
|
i = width1 - 1;
|
||||||
|
if (curwin->w_virtcol >= (colnr_T)width1) {
|
||||||
|
i += ((curwin->w_virtcol - width1) / width2 + 1) * width2;
|
||||||
|
}
|
||||||
|
coladvance((colnr_T)i);
|
||||||
|
|
||||||
|
// Make sure we stick in this column.
|
||||||
|
validate_virtcol();
|
||||||
|
curwin->w_curswant = curwin->w_virtcol;
|
||||||
|
curwin->w_set_curswant = false;
|
||||||
|
if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) {
|
||||||
|
// Check for landing on a character that got split at
|
||||||
|
// the end of the line. We do not want to advance to
|
||||||
|
// the next screen line.
|
||||||
|
if (curwin->w_virtcol > (colnr_T)i) {
|
||||||
|
curwin->w_cursor.col--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) {
|
||||||
|
clearopbeep(oap);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (cap->count1 > 1) {
|
||||||
|
// if it fails, let the cursor still move to the last char
|
||||||
|
(void)cursor_down(cap->count1 - 1, false);
|
||||||
|
}
|
||||||
|
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
|
||||||
|
coladvance((colnr_T)i);
|
||||||
|
|
||||||
|
// if the character doesn't fit move one back
|
||||||
|
if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) {
|
||||||
|
colnr_T vcol;
|
||||||
|
|
||||||
|
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
|
||||||
|
if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) {
|
||||||
|
curwin->w_cursor.col--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure we stick in this column.
|
||||||
|
validate_virtcol();
|
||||||
|
curwin->w_curswant = curwin->w_virtcol;
|
||||||
|
curwin->w_set_curswant = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// "gi": start Insert at the last position.
|
||||||
|
static void nv_gi_cmd(cmdarg_T *cap)
|
||||||
|
{
|
||||||
|
if (curbuf->b_last_insert.mark.lnum != 0) {
|
||||||
|
curwin->w_cursor = curbuf->b_last_insert.mark;
|
||||||
|
check_cursor_lnum();
|
||||||
|
int i = (int)STRLEN(get_cursor_line_ptr());
|
||||||
|
if (curwin->w_cursor.col > (colnr_T)i) {
|
||||||
|
if (virtual_active()) {
|
||||||
|
curwin->w_cursor.coladd += curwin->w_cursor.col - i;
|
||||||
|
}
|
||||||
|
curwin->w_cursor.col = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cap->cmdchar = 'i';
|
||||||
|
nv_edit(cap);
|
||||||
|
}
|
||||||
|
|
||||||
/// Commands starting with "g".
|
/// Commands starting with "g".
|
||||||
static void nv_g_cmd(cmdarg_T *cap)
|
static void nv_g_cmd(cmdarg_T *cap)
|
||||||
{
|
{
|
||||||
oparg_T *oap = cap->oap;
|
oparg_T *oap = cap->oap;
|
||||||
pos_T tpos;
|
|
||||||
int i;
|
int i;
|
||||||
bool flag = false;
|
|
||||||
|
|
||||||
switch (cap->nchar) {
|
switch (cap->nchar) {
|
||||||
// "g^A/g^X": Sequentially increment visually selected region.
|
// "g^A/g^X": Sequentially increment visually selected region.
|
||||||
@@ -5918,59 +6123,7 @@ static void nv_g_cmd(cmdarg_T *cap)
|
|||||||
// "gv": Reselect the previous Visual area. If Visual already active,
|
// "gv": Reselect the previous Visual area. If Visual already active,
|
||||||
// exchange previous and current Visual area.
|
// exchange previous and current Visual area.
|
||||||
case 'v':
|
case 'v':
|
||||||
if (checkclearop(oap)) {
|
nv_gv_cmd(cap);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curbuf->b_visual.vi_start.lnum == 0
|
|
||||||
|| curbuf->b_visual.vi_start.lnum > curbuf->b_ml.ml_line_count
|
|
||||||
|| curbuf->b_visual.vi_end.lnum == 0) {
|
|
||||||
beep_flush();
|
|
||||||
} else {
|
|
||||||
// set w_cursor to the start of the Visual area, tpos to the end
|
|
||||||
if (VIsual_active) {
|
|
||||||
i = VIsual_mode;
|
|
||||||
VIsual_mode = curbuf->b_visual.vi_mode;
|
|
||||||
curbuf->b_visual.vi_mode = i;
|
|
||||||
curbuf->b_visual_mode_eval = i;
|
|
||||||
i = curwin->w_curswant;
|
|
||||||
curwin->w_curswant = curbuf->b_visual.vi_curswant;
|
|
||||||
curbuf->b_visual.vi_curswant = i;
|
|
||||||
|
|
||||||
tpos = curbuf->b_visual.vi_end;
|
|
||||||
curbuf->b_visual.vi_end = curwin->w_cursor;
|
|
||||||
curwin->w_cursor = curbuf->b_visual.vi_start;
|
|
||||||
curbuf->b_visual.vi_start = VIsual;
|
|
||||||
} else {
|
|
||||||
VIsual_mode = curbuf->b_visual.vi_mode;
|
|
||||||
curwin->w_curswant = curbuf->b_visual.vi_curswant;
|
|
||||||
tpos = curbuf->b_visual.vi_end;
|
|
||||||
curwin->w_cursor = curbuf->b_visual.vi_start;
|
|
||||||
}
|
|
||||||
|
|
||||||
VIsual_active = true;
|
|
||||||
VIsual_reselect = true;
|
|
||||||
|
|
||||||
// Set Visual to the start and w_cursor to the end of the Visual
|
|
||||||
// area. Make sure they are on an existing character.
|
|
||||||
check_cursor();
|
|
||||||
VIsual = curwin->w_cursor;
|
|
||||||
curwin->w_cursor = tpos;
|
|
||||||
check_cursor();
|
|
||||||
update_topline(curwin);
|
|
||||||
// When called from normal "g" command: start Select mode when
|
|
||||||
// 'selectmode' contains "cmd". When called for K_SELECT, always
|
|
||||||
// start Select mode.
|
|
||||||
if (cap->arg) {
|
|
||||||
VIsual_select = true;
|
|
||||||
VIsual_select_reg = 0;
|
|
||||||
} else {
|
|
||||||
may_start_select('c');
|
|
||||||
}
|
|
||||||
setmouse();
|
|
||||||
redraw_curbuf_later(INVERTED);
|
|
||||||
showmode();
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
// "gV": Don't reselect the previous Visual area after a Select mode mapping of menu.
|
// "gV": Don't reselect the previous Visual area after a Select mode mapping of menu.
|
||||||
case 'V':
|
case 'V':
|
||||||
@@ -6036,47 +6189,14 @@ static void nv_g_cmd(cmdarg_T *cap)
|
|||||||
nv_join(cap);
|
nv_join(cap);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// "g0", "g^" and "g$": Like "0", "^" and "$" but for screen lines.
|
// "g0", "g^" : Like "0" and "^" but for screen lines.
|
||||||
// "gm": middle of "g0" and "g$".
|
// "gm": middle of "g0" and "g$".
|
||||||
case '^':
|
case '^':
|
||||||
flag = true;
|
|
||||||
FALLTHROUGH;
|
|
||||||
|
|
||||||
case '0':
|
case '0':
|
||||||
case 'm':
|
case 'm':
|
||||||
case K_HOME:
|
case K_HOME:
|
||||||
case K_KHOME:
|
case K_KHOME:
|
||||||
oap->motion_type = kMTCharWise;
|
nv_g_home_m_cmd(cap);
|
||||||
oap->inclusive = false;
|
|
||||||
if (curwin->w_p_wrap
|
|
||||||
&& curwin->w_width_inner != 0) {
|
|
||||||
int width1 = curwin->w_width_inner - curwin_col_off();
|
|
||||||
int width2 = width1 + curwin_col_off2();
|
|
||||||
|
|
||||||
validate_virtcol();
|
|
||||||
i = 0;
|
|
||||||
if (curwin->w_virtcol >= (colnr_T)width1 && width2 > 0) {
|
|
||||||
i = (curwin->w_virtcol - width1) / width2 * width2 + width1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
i = curwin->w_leftcol;
|
|
||||||
}
|
|
||||||
// Go to the middle of the screen line. When 'number' or
|
|
||||||
// 'relativenumber' is on and lines are wrapping the middle can be more
|
|
||||||
// to the left.
|
|
||||||
if (cap->nchar == 'm') {
|
|
||||||
i += (curwin->w_width_inner - curwin_col_off()
|
|
||||||
+ ((curwin->w_p_wrap && i > 0)
|
|
||||||
? curwin_col_off2() : 0)) / 2;
|
|
||||||
}
|
|
||||||
coladvance((colnr_T)i);
|
|
||||||
if (flag) {
|
|
||||||
do {
|
|
||||||
i = gchar_cursor();
|
|
||||||
} while (ascii_iswhite(i) && oneright());
|
|
||||||
curwin->w_valid &= ~VALID_WCOL;
|
|
||||||
}
|
|
||||||
curwin->w_set_curswant = true;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'M':
|
case 'M':
|
||||||
@@ -6091,93 +6211,16 @@ static void nv_g_cmd(cmdarg_T *cap)
|
|||||||
curwin->w_set_curswant = true;
|
curwin->w_set_curswant = true;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case '_':
|
|
||||||
// "g_": to the last non-blank character in the line or <count> lines downward.
|
// "g_": to the last non-blank character in the line or <count> lines downward.
|
||||||
cap->oap->motion_type = kMTCharWise;
|
case '_':
|
||||||
cap->oap->inclusive = true;
|
nv_g_underscore_cmd(cap);
|
||||||
curwin->w_curswant = MAXCOL;
|
|
||||||
if (cursor_down(cap->count1 - 1,
|
|
||||||
cap->oap->op_type == OP_NOP) == false) {
|
|
||||||
clearopbeep(cap->oap);
|
|
||||||
} else {
|
|
||||||
char_u *ptr = get_cursor_line_ptr();
|
|
||||||
|
|
||||||
// In Visual mode we may end up after the line.
|
|
||||||
if (curwin->w_cursor.col > 0 && ptr[curwin->w_cursor.col] == NUL) {
|
|
||||||
curwin->w_cursor.col--;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decrease the cursor column until it's on a non-blank.
|
|
||||||
while (curwin->w_cursor.col > 0
|
|
||||||
&& ascii_iswhite(ptr[curwin->w_cursor.col])) {
|
|
||||||
curwin->w_cursor.col--;
|
|
||||||
}
|
|
||||||
curwin->w_set_curswant = true;
|
|
||||||
adjust_for_sel(cap);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// "g$" : Like "$" but for screen lines.
|
||||||
case '$':
|
case '$':
|
||||||
case K_END:
|
case K_END:
|
||||||
case K_KEND: {
|
case K_KEND:
|
||||||
int col_off = curwin_col_off();
|
nv_g_dollar_cmd(cap);
|
||||||
|
|
||||||
oap->motion_type = kMTCharWise;
|
|
||||||
oap->inclusive = true;
|
|
||||||
if (curwin->w_p_wrap
|
|
||||||
&& curwin->w_width_inner != 0) {
|
|
||||||
curwin->w_curswant = MAXCOL; // so we stay at the end
|
|
||||||
if (cap->count1 == 1) {
|
|
||||||
int width1 = curwin->w_width_inner - col_off;
|
|
||||||
int width2 = width1 + curwin_col_off2();
|
|
||||||
|
|
||||||
validate_virtcol();
|
|
||||||
i = width1 - 1;
|
|
||||||
if (curwin->w_virtcol >= (colnr_T)width1) {
|
|
||||||
i += ((curwin->w_virtcol - width1) / width2 + 1)
|
|
||||||
* width2;
|
|
||||||
}
|
|
||||||
coladvance((colnr_T)i);
|
|
||||||
|
|
||||||
// Make sure we stick in this column.
|
|
||||||
validate_virtcol();
|
|
||||||
curwin->w_curswant = curwin->w_virtcol;
|
|
||||||
curwin->w_set_curswant = false;
|
|
||||||
if (curwin->w_cursor.col > 0 && curwin->w_p_wrap) {
|
|
||||||
// Check for landing on a character that got split at
|
|
||||||
// the end of the line. We do not want to advance to
|
|
||||||
// the next screen line.
|
|
||||||
if (curwin->w_virtcol > (colnr_T)i) {
|
|
||||||
curwin->w_cursor.col--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (nv_screengo(oap, FORWARD, cap->count1 - 1) == false) {
|
|
||||||
clearopbeep(oap);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (cap->count1 > 1) {
|
|
||||||
// if it fails, let the cursor still move to the last char
|
|
||||||
(void)cursor_down(cap->count1 - 1, false);
|
|
||||||
}
|
|
||||||
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
|
|
||||||
coladvance((colnr_T)i);
|
|
||||||
|
|
||||||
// if the character doesn't fit move one back
|
|
||||||
if (curwin->w_cursor.col > 0 && utf_ptr2cells((const char *)get_cursor_pos_ptr()) > 1) {
|
|
||||||
colnr_T vcol;
|
|
||||||
|
|
||||||
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &vcol);
|
|
||||||
if (vcol >= curwin->w_leftcol + curwin->w_width - col_off) {
|
|
||||||
curwin->w_cursor.col--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we stick in this column.
|
|
||||||
validate_virtcol();
|
|
||||||
curwin->w_curswant = curwin->w_virtcol;
|
|
||||||
curwin->w_set_curswant = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// "g*" and "g#", like "*" and "#" but without using "\<" and "\>"
|
// "g*" and "g#", like "*" and "#" but without using "\<" and "\>"
|
||||||
@@ -6209,19 +6252,7 @@ static void nv_g_cmd(cmdarg_T *cap)
|
|||||||
|
|
||||||
// "gi": start Insert at the last position.
|
// "gi": start Insert at the last position.
|
||||||
case 'i':
|
case 'i':
|
||||||
if (curbuf->b_last_insert.mark.lnum != 0) {
|
nv_gi_cmd(cap);
|
||||||
curwin->w_cursor = curbuf->b_last_insert.mark;
|
|
||||||
check_cursor_lnum();
|
|
||||||
i = (int)STRLEN(get_cursor_line_ptr());
|
|
||||||
if (curwin->w_cursor.col > (colnr_T)i) {
|
|
||||||
if (virtual_active()) {
|
|
||||||
curwin->w_cursor.coladd += curwin->w_cursor.col - i;
|
|
||||||
}
|
|
||||||
curwin->w_cursor.col = i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cap->cmdchar = 'i';
|
|
||||||
nv_edit(cap);
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// "gI": Start insert in column 1.
|
// "gI": Start insert in column 1.
|
||||||
|
Reference in New Issue
Block a user