mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
terminal: New module that implements a terminal emulator
This commit integrates libvterm with Neovim and implements a terminal emulator with nvim buffers as the display mechanism. Terminal buffers can be created using any of the following methods: - Opening a file with name following the "term://[${cwd}//[${pid}:]]${cmd}" URI pattern where: - cwd is the working directory of the process - pid is the process id. This is just for use in session files where a pid would have been assigned to the saved buffer title. - cmd is the command to run - Invoking the `:terminal` ex command - Invoking the `termopen` function which returns a job id for automating the terminal window. Some extra changes were also implemented to adapt with terminal buffers. Here's an overview: - The `main` function now sets a BufReadCmd autocmd to intercept the term:// URI and spawn the terminal buffer instead of reading the file. - terminal buffers behave as if the following local buffer options were set: - `nomodifiable` - `swapfile` - `undolevels=-1` - `bufhidden=hide` - All commands that delete buffers(`:bun`, `:bd` and `:bw`) behave the same for terminal buffers, but only work when bang is passed(eg: `:bwipeout!`) - A new "terminal" mode was added. A consequence is that a new set of mapping commands were implemented with the "t" prefix(tmap, tunmap, tnoremap...) - The `edit` function(which enters insert mode) will actually enter terminal mode if the current buffer is a terminal - The `put` operator was adapted to send data to the terminal instead of modifying the buffer directly. - A window being resized will also trigger a terminal resize if the window displays the terminal.
This commit is contained in:
parent
6f471fa4fc
commit
cdedd89d22
@ -68,6 +68,7 @@
|
|||||||
#include "nvim/spell.h"
|
#include "nvim/spell.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/syntax.h"
|
#include "nvim/syntax.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/version.h"
|
#include "nvim/version.h"
|
||||||
@ -307,20 +308,28 @@ close_buffer (
|
|||||||
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
|
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
|
||||||
bool wipe_buf = (action == DOBUF_WIPE);
|
bool wipe_buf = (action == DOBUF_WIPE);
|
||||||
|
|
||||||
/*
|
// Force unloading or deleting when 'bufhidden' says so, but not for terminal
|
||||||
* Force unloading or deleting when 'bufhidden' says so.
|
// buffers.
|
||||||
* The caller must take care of NOT deleting/freeing when 'bufhidden' is
|
// The caller must take care of NOT deleting/freeing when 'bufhidden' is
|
||||||
* "hide" (otherwise we could never free or delete a buffer).
|
// "hide" (otherwise we could never free or delete a buffer).
|
||||||
*/
|
if (!buf->terminal) {
|
||||||
if (buf->b_p_bh[0] == 'd') { /* 'bufhidden' == "delete" */
|
if (buf->b_p_bh[0] == 'd') { // 'bufhidden' == "delete"
|
||||||
del_buf = true;
|
del_buf = true;
|
||||||
|
unload_buf = true;
|
||||||
|
} else if (buf->b_p_bh[0] == 'w') { // 'bufhidden' == "wipe"
|
||||||
|
del_buf = true;
|
||||||
|
unload_buf = true;
|
||||||
|
wipe_buf = true;
|
||||||
|
} else if (buf->b_p_bh[0] == 'u') // 'bufhidden' == "unload"
|
||||||
|
unload_buf = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf->terminal && (unload_buf || del_buf || wipe_buf)) {
|
||||||
|
// terminal buffers can only be wiped
|
||||||
unload_buf = true;
|
unload_buf = true;
|
||||||
} else if (buf->b_p_bh[0] == 'w') { /* 'bufhidden' == "wipe" */
|
|
||||||
del_buf = true;
|
del_buf = true;
|
||||||
unload_buf = true;
|
|
||||||
wipe_buf = true;
|
wipe_buf = true;
|
||||||
} else if (buf->b_p_bh[0] == 'u') /* 'bufhidden' == "unload" */
|
}
|
||||||
unload_buf = true;
|
|
||||||
|
|
||||||
if (win_valid(win)) {
|
if (win_valid(win)) {
|
||||||
/* Set b_last_cursor when closing the last window for the buffer.
|
/* Set b_last_cursor when closing the last window for the buffer.
|
||||||
@ -383,6 +392,10 @@ close_buffer (
|
|||||||
if (buf->b_nwindows > 0 || !unload_buf)
|
if (buf->b_nwindows > 0 || !unload_buf)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (buf->terminal) {
|
||||||
|
terminal_close(buf->terminal, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
/* Always remove the buffer when there is no file name. */
|
/* Always remove the buffer when there is no file name. */
|
||||||
if (buf->b_ffname == NULL)
|
if (buf->b_ffname == NULL)
|
||||||
del_buf = TRUE;
|
del_buf = TRUE;
|
||||||
@ -925,8 +938,8 @@ do_buffer (
|
|||||||
if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
|
if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
|
|
||||||
if (!forceit && bufIsChanged(buf)) {
|
if (!forceit && (buf->terminal || bufIsChanged(buf))) {
|
||||||
if ((p_confirm || cmdmod.confirm) && p_write) {
|
if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) {
|
||||||
dialog_changed(buf, FALSE);
|
dialog_changed(buf, FALSE);
|
||||||
if (!buf_valid(buf))
|
if (!buf_valid(buf))
|
||||||
/* Autocommand deleted buffer, oops! It's not changed
|
/* Autocommand deleted buffer, oops! It's not changed
|
||||||
@ -937,9 +950,14 @@ do_buffer (
|
|||||||
if (bufIsChanged(buf))
|
if (bufIsChanged(buf))
|
||||||
return FAIL;
|
return FAIL;
|
||||||
} else {
|
} else {
|
||||||
EMSGN(_("E89: No write since last change for buffer %" PRId64
|
if (buf->terminal) {
|
||||||
" (add ! to override)"),
|
EMSG2(_("E89: %s will be killed(add ! to override)"),
|
||||||
buf->b_fnum);
|
(char *)buf->b_fname);
|
||||||
|
} else {
|
||||||
|
EMSGN(_("E89: No write since last change for buffer %" PRId64
|
||||||
|
" (add ! to override)"),
|
||||||
|
buf->b_fnum);
|
||||||
|
}
|
||||||
return FAIL;
|
return FAIL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,7 +27,7 @@
|
|||||||
// for String
|
// for String
|
||||||
#include "nvim/api/private/defs.h"
|
#include "nvim/api/private/defs.h"
|
||||||
|
|
||||||
#define MODIFIABLE(buf) (buf->b_p_ma)
|
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Flags for w_valid.
|
* Flags for w_valid.
|
||||||
@ -103,6 +103,9 @@ typedef struct file_buffer buf_T; /* forward declaration */
|
|||||||
// for FileID
|
// for FileID
|
||||||
#include "nvim/os/fs_defs.h"
|
#include "nvim/os/fs_defs.h"
|
||||||
|
|
||||||
|
// for Terminal
|
||||||
|
#include "nvim/terminal.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The taggy struct is used to store the information about a :tag command.
|
* The taggy struct is used to store the information about a :tag command.
|
||||||
*/
|
*/
|
||||||
@ -751,6 +754,8 @@ struct file_buffer {
|
|||||||
* may use a different synblock_T. */
|
* may use a different synblock_T. */
|
||||||
|
|
||||||
signlist_T *b_signlist; /* list of signs to draw */
|
signlist_T *b_signlist; /* list of signs to draw */
|
||||||
|
|
||||||
|
Terminal *terminal; // Terminal instance associated with the buffer
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -56,6 +56,7 @@
|
|||||||
#include "nvim/tag.h"
|
#include "nvim/tag.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/mouse.h"
|
#include "nvim/mouse.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
#include "nvim/os/event.h"
|
#include "nvim/os/event.h"
|
||||||
@ -244,6 +245,11 @@ edit (
|
|||||||
long count
|
long count
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
|
if (curbuf->terminal) {
|
||||||
|
terminal_enter(curbuf->terminal, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int c = 0;
|
int c = 0;
|
||||||
char_u *ptr;
|
char_u *ptr;
|
||||||
int lastc;
|
int lastc;
|
||||||
|
213
src/nvim/eval.c
213
src/nvim/eval.c
@ -1,4 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
|
*
|
||||||
* VIM - Vi IMproved by Bram Moolenaar
|
* VIM - Vi IMproved by Bram Moolenaar
|
||||||
*
|
*
|
||||||
* Do ":help uganda" in Vim to read copying and usage conditions.
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
||||||
@ -78,6 +79,7 @@
|
|||||||
#include "nvim/tempfile.h"
|
#include "nvim/tempfile.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/mouse.h"
|
#include "nvim/mouse.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/version.h"
|
#include "nvim/version.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
@ -440,6 +442,15 @@ static struct vimvar {
|
|||||||
static dictitem_T vimvars_var; /* variable used for v: */
|
static dictitem_T vimvars_var; /* variable used for v: */
|
||||||
#define vimvarht vimvardict.dv_hashtab
|
#define vimvarht vimvardict.dv_hashtab
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Job *job;
|
||||||
|
Terminal *term;
|
||||||
|
bool exited;
|
||||||
|
int refcount;
|
||||||
|
char *autocmd_file;
|
||||||
|
} TerminalJobData;
|
||||||
|
|
||||||
|
|
||||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
# include "eval.c.generated.h"
|
# include "eval.c.generated.h"
|
||||||
#endif
|
#endif
|
||||||
@ -447,7 +458,6 @@ static dictitem_T vimvars_var; /* variable used for v: */
|
|||||||
#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
|
#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */
|
||||||
#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
|
#define FNE_CHECK_START 2 /* find_name_end(): check name starts with
|
||||||
valid character */
|
valid character */
|
||||||
|
|
||||||
// Memory pool for reusing JobEvent structures
|
// Memory pool for reusing JobEvent structures
|
||||||
typedef struct {
|
typedef struct {
|
||||||
int id;
|
int id;
|
||||||
@ -6600,6 +6610,7 @@ static struct fst {
|
|||||||
{"tan", 1, 1, f_tan},
|
{"tan", 1, 1, f_tan},
|
||||||
{"tanh", 1, 1, f_tanh},
|
{"tanh", 1, 1, f_tanh},
|
||||||
{"tempname", 0, 0, f_tempname},
|
{"tempname", 0, 0, f_tempname},
|
||||||
|
{"termopen", 1, 2, f_termopen},
|
||||||
{"test", 1, 1, f_test},
|
{"test", 1, 1, f_test},
|
||||||
{"tolower", 1, 1, f_tolower},
|
{"tolower", 1, 1, f_tolower},
|
||||||
{"toupper", 1, 1, f_toupper},
|
{"toupper", 1, 1, f_toupper},
|
||||||
@ -10750,12 +10761,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
|
|||||||
|
|
||||||
// The last item of argv must be NULL
|
// The last item of argv must be NULL
|
||||||
argv[i] = NULL;
|
argv[i] = NULL;
|
||||||
JobOptions opts = JOB_OPTIONS_INIT;
|
JobOptions opts = common_job_options(argv, (char *)argvars[0].vval.v_string);
|
||||||
opts.argv = argv;
|
|
||||||
opts.data = xstrdup((char *)argvars[0].vval.v_string);
|
|
||||||
opts.stdout_cb = on_job_stdout;
|
|
||||||
opts.stderr_cb = on_job_stderr;
|
|
||||||
opts.exit_cb = on_job_exit;
|
|
||||||
|
|
||||||
if (args && argvars[3].v_type == VAR_DICT) {
|
if (args && argvars[3].v_type == VAR_DICT) {
|
||||||
dict_T *job_opts = argvars[3].vval.v_dict;
|
dict_T *job_opts = argvars[3].vval.v_dict;
|
||||||
@ -10774,15 +10780,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
job_start(opts, &rettv->vval.v_number);
|
common_job_start(opts, rettv);
|
||||||
|
|
||||||
if (rettv->vval.v_number <= 0) {
|
|
||||||
if (rettv->vval.v_number == 0) {
|
|
||||||
EMSG(_(e_jobtblfull));
|
|
||||||
} else {
|
|
||||||
EMSG(_(e_jobexe));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// "jobstop()" function
|
// "jobstop()" function
|
||||||
@ -14870,6 +14868,72 @@ static void f_tempname(typval_T *argvars, typval_T *rettv)
|
|||||||
rettv->vval.v_string = vim_tempname();
|
rettv->vval.v_string = vim_tempname();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "termopen(cmd[, cwd])" function
|
||||||
|
static void f_termopen(typval_T *argvars, typval_T *rettv)
|
||||||
|
{
|
||||||
|
if (check_restricted() || check_secure()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (curbuf->b_changed) {
|
||||||
|
EMSG(_("Can only call this function in an unmodified buffer"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argvars[0].v_type != VAR_STRING
|
||||||
|
|| (argvars[1].v_type != VAR_STRING
|
||||||
|
&& argvars[1].v_type != VAR_UNKNOWN)) {
|
||||||
|
// Wrong argument types
|
||||||
|
EMSG(_(e_invarg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char **argv = shell_build_argv((char *)argvars[0].vval.v_string, NULL);
|
||||||
|
JobOptions opts = common_job_options(argv, NULL);
|
||||||
|
opts.pty = true;
|
||||||
|
opts.width = curwin->w_width;
|
||||||
|
opts.height = curwin->w_height;
|
||||||
|
opts.term_name = xstrdup("xterm-256color");
|
||||||
|
Job *job = common_job_start(opts, rettv);
|
||||||
|
if (!job) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
TerminalJobData *data = opts.data;
|
||||||
|
TerminalOptions topts = TERMINAL_OPTIONS_INIT;
|
||||||
|
topts.data = data;
|
||||||
|
topts.width = curwin->w_width;
|
||||||
|
topts.height = curwin->w_height;
|
||||||
|
topts.write_cb = term_write;
|
||||||
|
topts.resize_cb = term_resize;
|
||||||
|
topts.close_cb = term_close;
|
||||||
|
|
||||||
|
char *cwd = ".";
|
||||||
|
if (argvars[1].v_type == VAR_STRING
|
||||||
|
&& os_isdir(argvars[1].vval.v_string)) {
|
||||||
|
cwd = (char *)argvars[1].vval.v_string;
|
||||||
|
}
|
||||||
|
int pid = job_pid(job);
|
||||||
|
char buf[1024];
|
||||||
|
// format the title with the pid to conform with the term:// URI
|
||||||
|
snprintf(buf, sizeof(buf), "term://%s//%d:%s", cwd, pid,
|
||||||
|
(char *)argvars[0].vval.v_string);
|
||||||
|
// at this point the buffer has no terminal instance associated yet, so unset
|
||||||
|
// the 'swapfile' option to ensure no swap file will be created
|
||||||
|
curbuf->b_p_swf = false;
|
||||||
|
(void)setfname(curbuf, (uint8_t *)buf, NULL, true);
|
||||||
|
data->autocmd_file = xstrdup(buf);
|
||||||
|
// Save the job id and pid in b:terminal_job_{id,pid}
|
||||||
|
Error err;
|
||||||
|
dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_id"),
|
||||||
|
INTEGER_OBJ(rettv->vval.v_number), &err);
|
||||||
|
dict_set_value(curbuf->b_vars, cstr_as_string("terminal_job_pid"),
|
||||||
|
INTEGER_OBJ(pid), &err);
|
||||||
|
|
||||||
|
Terminal *term = terminal_open(topts);
|
||||||
|
data->term = term;
|
||||||
|
data->refcount++;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* "test(list)" function: Just checking the walls...
|
* "test(list)" function: Just checking the walls...
|
||||||
*/
|
*/
|
||||||
@ -19750,16 +19814,51 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline JobOptions common_job_options(char **argv, char *autocmd_file)
|
||||||
|
{
|
||||||
|
TerminalJobData *data = xcalloc(1, sizeof(TerminalJobData));
|
||||||
|
if (autocmd_file) {
|
||||||
|
data->autocmd_file = xstrdup(autocmd_file);
|
||||||
|
}
|
||||||
|
JobOptions opts = JOB_OPTIONS_INIT;
|
||||||
|
opts.argv = argv;
|
||||||
|
opts.data = data;
|
||||||
|
opts.stdout_cb = on_job_stdout;
|
||||||
|
opts.stderr_cb = on_job_stderr;
|
||||||
|
opts.exit_cb = on_job_exit;
|
||||||
|
return opts;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline Job *common_job_start(JobOptions opts, typval_T *rettv)
|
||||||
|
{
|
||||||
|
Job *job = job_start(opts, &rettv->vval.v_number);
|
||||||
|
TerminalJobData *data = opts.data;
|
||||||
|
data->refcount++;
|
||||||
|
|
||||||
|
if (rettv->vval.v_number <= 0) {
|
||||||
|
if (rettv->vval.v_number == 0) {
|
||||||
|
EMSG(_(e_jobtblfull));
|
||||||
|
free(data->autocmd_file);
|
||||||
|
} else {
|
||||||
|
EMSG(_(e_jobexe));
|
||||||
|
free(opts.data);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
data->job = job;
|
||||||
|
return job;
|
||||||
|
}
|
||||||
|
|
||||||
// JobActivity autocommands will execute vimscript code, so it must be executed
|
// JobActivity autocommands will execute vimscript code, so it must be executed
|
||||||
// on Nvim main loop
|
// on Nvim main loop
|
||||||
static inline void push_job_event(Job *job, RStream *rstream, char *type)
|
static inline void push_job_event(Job *job, char *type, char *data,
|
||||||
|
size_t count)
|
||||||
{
|
{
|
||||||
JobEvent *event_data = kmp_alloc(JobEventPool, job_event_pool);
|
JobEvent *event_data = kmp_alloc(JobEventPool, job_event_pool);
|
||||||
event_data->received = NULL;
|
event_data->received = NULL;
|
||||||
if (rstream) {
|
if (data) {
|
||||||
event_data->received = list_alloc();
|
event_data->received = list_alloc();
|
||||||
char *ptr = rstream_read_ptr(rstream);
|
char *ptr = data;
|
||||||
size_t count = rstream_pending(rstream);
|
|
||||||
size_t remaining = count;
|
size_t remaining = count;
|
||||||
size_t off = 0;
|
size_t off = 0;
|
||||||
|
|
||||||
@ -19780,10 +19879,10 @@ static inline void push_job_event(Job *job, RStream *rstream, char *type)
|
|||||||
off++;
|
off++;
|
||||||
}
|
}
|
||||||
list_append_string(event_data->received, (uint8_t *)ptr, off);
|
list_append_string(event_data->received, (uint8_t *)ptr, off);
|
||||||
rbuffer_consumed(rstream_buffer(rstream), count);
|
|
||||||
}
|
}
|
||||||
|
TerminalJobData *d = job_data(job);
|
||||||
event_data->id = job_id(job);
|
event_data->id = job_id(job);
|
||||||
event_data->name = job_data(job);
|
event_data->name = d->autocmd_file;
|
||||||
event_data->type = type;
|
event_data->type = type;
|
||||||
event_push((Event) {
|
event_push((Event) {
|
||||||
.handler = on_job_event,
|
.handler = on_job_event,
|
||||||
@ -19793,21 +19892,75 @@ static inline void push_job_event(Job *job, RStream *rstream, char *type)
|
|||||||
|
|
||||||
static void on_job_stdout(RStream *rstream, void *data, bool eof)
|
static void on_job_stdout(RStream *rstream, void *data, bool eof)
|
||||||
{
|
{
|
||||||
if (!eof) {
|
on_job_output(rstream, data, eof, "stdout");
|
||||||
push_job_event(data, rstream, "stdout");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_job_stderr(RStream *rstream, void *data, bool eof)
|
static void on_job_stderr(RStream *rstream, void *data, bool eof)
|
||||||
{
|
{
|
||||||
if (!eof) {
|
on_job_output(rstream, data, eof, "stderr");
|
||||||
push_job_event(data, rstream, "stderr");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_job_exit(Job *job, void *data)
|
static void on_job_output(RStream *rstream, Job *job, bool eof, char *type)
|
||||||
{
|
{
|
||||||
push_job_event(job, NULL, "exit");
|
if (eof) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TerminalJobData *data = job_data(job);
|
||||||
|
char *ptr = rstream_read_ptr(rstream);
|
||||||
|
size_t len = rstream_pending(rstream);
|
||||||
|
|
||||||
|
// The order here matters, the terminal must receive the data first because
|
||||||
|
// push_job_event will modify the read buffer(convert NULs into NLs)
|
||||||
|
if (data->term) {
|
||||||
|
terminal_receive(data->term, ptr, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
push_job_event(job, type, ptr, len);
|
||||||
|
rbuffer_consumed(rstream_buffer(rstream), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void on_job_exit(Job *job, void *d)
|
||||||
|
{
|
||||||
|
TerminalJobData *data = d;
|
||||||
|
push_job_event(job, "exit", NULL, 0);
|
||||||
|
|
||||||
|
if (data->term && !data->exited) {
|
||||||
|
data->exited = true;
|
||||||
|
terminal_close(data->term,
|
||||||
|
_("\r\n[Program exited, press any key to close]"));
|
||||||
|
}
|
||||||
|
term_job_data_decref(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void term_write(char *buf, size_t size, void *data)
|
||||||
|
{
|
||||||
|
Job *job = ((TerminalJobData *)data)->job;
|
||||||
|
WBuffer *wbuf = wstream_new_buffer(xmemdup(buf, size), size, 1, free);
|
||||||
|
job_write(job, wbuf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void term_resize(uint16_t width, uint16_t height, void *data)
|
||||||
|
{
|
||||||
|
job_resize(((TerminalJobData *)data)->job, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void term_close(void *d)
|
||||||
|
{
|
||||||
|
TerminalJobData *data = d;
|
||||||
|
if (!data->exited) {
|
||||||
|
data->exited = true;
|
||||||
|
job_stop(data->job);
|
||||||
|
}
|
||||||
|
terminal_destroy(data->term);
|
||||||
|
term_job_data_decref(d);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void term_job_data_decref(TerminalJobData *data)
|
||||||
|
{
|
||||||
|
if (!(--data->refcount)) {
|
||||||
|
free(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void on_job_event(Event event)
|
static void on_job_event(Event event)
|
||||||
|
@ -2724,7 +2724,7 @@ do_ecmd (
|
|||||||
/* close the link to the current buffer */
|
/* close the link to the current buffer */
|
||||||
u_sync(FALSE);
|
u_sync(FALSE);
|
||||||
close_buffer(oldwin, curbuf,
|
close_buffer(oldwin, curbuf,
|
||||||
(flags & ECMD_HIDE) ? 0 : DOBUF_UNLOAD, FALSE);
|
(flags & ECMD_HIDE) || curbuf->terminal ? 0 : DOBUF_UNLOAD, FALSE);
|
||||||
|
|
||||||
/* Autocommands may open a new window and leave oldwin open
|
/* Autocommands may open a new window and leave oldwin open
|
||||||
* which leads to crashes since the above call sets
|
* which leads to crashes since the above call sets
|
||||||
|
@ -2235,6 +2235,11 @@ return {
|
|||||||
flags=bit.bor(NEEDARG, EXTRA, TRLBAR, NOTRLCOM, CMDWIN),
|
flags=bit.bor(NEEDARG, EXTRA, TRLBAR, NOTRLCOM, CMDWIN),
|
||||||
func='ex_tearoff',
|
func='ex_tearoff',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='terminal',
|
||||||
|
flags=bit.bor(BANG, FILES, CMDWIN),
|
||||||
|
func='ex_terminal',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='tfirst',
|
command='tfirst',
|
||||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||||
@ -2255,6 +2260,16 @@ return {
|
|||||||
flags=bit.bor(BANG, TRLBAR),
|
flags=bit.bor(BANG, TRLBAR),
|
||||||
func='ex_tag',
|
func='ex_tag',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='tmap',
|
||||||
|
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||||
|
func='ex_map',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
command='tmapclear',
|
||||||
|
flags=bit.bor(EXTRA, TRLBAR, CMDWIN),
|
||||||
|
func='ex_mapclear',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='tmenu',
|
command='tmenu',
|
||||||
flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||||
@ -2265,6 +2280,11 @@ return {
|
|||||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||||
func='ex_tag',
|
func='ex_tag',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='tnoremap',
|
||||||
|
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||||
|
func='ex_map',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='topleft',
|
command='topleft',
|
||||||
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM),
|
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM),
|
||||||
@ -2290,6 +2310,11 @@ return {
|
|||||||
flags=bit.bor(BANG, TRLBAR, WORD1),
|
flags=bit.bor(BANG, TRLBAR, WORD1),
|
||||||
func='ex_tag',
|
func='ex_tag',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
command='tunmap',
|
||||||
|
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||||
|
func='ex_unmap',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
command='tunmenu',
|
command='tunmenu',
|
||||||
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||||
|
@ -62,6 +62,7 @@
|
|||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/syntax.h"
|
#include "nvim/syntax.h"
|
||||||
#include "nvim/tag.h"
|
#include "nvim/tag.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/version.h"
|
#include "nvim/version.h"
|
||||||
@ -71,6 +72,8 @@
|
|||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
#include "nvim/ex_cmds_defs.h"
|
#include "nvim/ex_cmds_defs.h"
|
||||||
#include "nvim/mouse.h"
|
#include "nvim/mouse.h"
|
||||||
|
#include "nvim/os/rstream.h"
|
||||||
|
#include "nvim/os/wstream.h"
|
||||||
|
|
||||||
static int quitmore = 0;
|
static int quitmore = 0;
|
||||||
static int ex_pressedreturn = FALSE;
|
static int ex_pressedreturn = FALSE;
|
||||||
@ -1510,7 +1513,9 @@ static char_u * do_one_cmd(char_u **cmdlinep,
|
|||||||
errormsg = (char_u *)_(e_sandbox);
|
errormsg = (char_u *)_(e_sandbox);
|
||||||
goto doend;
|
goto doend;
|
||||||
}
|
}
|
||||||
if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY)) {
|
if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY)
|
||||||
|
// allow :put in terminals
|
||||||
|
&& (!curbuf->terminal || ea.cmdidx != CMD_put)) {
|
||||||
/* Command not allowed in non-'modifiable' buffer */
|
/* Command not allowed in non-'modifiable' buffer */
|
||||||
errormsg = (char_u *)_(e_modifiable);
|
errormsg = (char_u *)_(e_modifiable);
|
||||||
goto doend;
|
goto doend;
|
||||||
@ -2610,7 +2615,7 @@ set_one_cmd_context (
|
|||||||
xp->xp_context = EXPAND_FILES;
|
xp->xp_context = EXPAND_FILES;
|
||||||
|
|
||||||
/* For a shell command more chars need to be escaped. */
|
/* For a shell command more chars need to be escaped. */
|
||||||
if (usefilter || ea.cmdidx == CMD_bang) {
|
if (usefilter || ea.cmdidx == CMD_bang || ea.cmdidx == CMD_terminal) {
|
||||||
#ifndef BACKSLASH_IN_FILENAME
|
#ifndef BACKSLASH_IN_FILENAME
|
||||||
xp->xp_shell = TRUE;
|
xp->xp_shell = TRUE;
|
||||||
#endif
|
#endif
|
||||||
@ -5126,8 +5131,10 @@ static void ex_quit(exarg_T *eap)
|
|||||||
|| (only_one_window() && check_changed_any(eap->forceit))) {
|
|| (only_one_window() && check_changed_any(eap->forceit))) {
|
||||||
not_exiting();
|
not_exiting();
|
||||||
} else {
|
} else {
|
||||||
if (only_one_window()) /* quit last window */
|
if (only_one_window()) {
|
||||||
|
// quit last window
|
||||||
getout(0);
|
getout(0);
|
||||||
|
}
|
||||||
/* close window; may free buffer */
|
/* close window; may free buffer */
|
||||||
win_close(curwin, !P_HID(curwin->w_buffer) || eap->forceit);
|
win_close(curwin, !P_HID(curwin->w_buffer) || eap->forceit);
|
||||||
}
|
}
|
||||||
@ -8060,7 +8067,9 @@ makeopens (
|
|||||||
/*
|
/*
|
||||||
* Wipe out an empty unnamed buffer we started in.
|
* Wipe out an empty unnamed buffer we started in.
|
||||||
*/
|
*/
|
||||||
if (put_line(fd, "if exists('s:wipebuf')") == FAIL)
|
if (put_line(fd, "if exists('s:wipebuf') "
|
||||||
|
"&& getbufvar(s:wipebuf, '&buftype') isnot# 'terminal'")
|
||||||
|
== FAIL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL)
|
if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL)
|
||||||
return FAIL;
|
return FAIL;
|
||||||
@ -8269,7 +8278,7 @@ put_view (
|
|||||||
* Load the file.
|
* Load the file.
|
||||||
*/
|
*/
|
||||||
if (wp->w_buffer->b_ffname != NULL
|
if (wp->w_buffer->b_ffname != NULL
|
||||||
&& !bt_nofile(wp->w_buffer)
|
&& (!bt_nofile(wp->w_buffer) || wp->w_buffer->terminal)
|
||||||
) {
|
) {
|
||||||
/*
|
/*
|
||||||
* Editing a file in this buffer: use ":edit file".
|
* Editing a file in this buffer: use ":edit file".
|
||||||
@ -8857,3 +8866,12 @@ static void ex_folddo(exarg_T *eap)
|
|||||||
global_exe(eap->arg);
|
global_exe(eap->arg);
|
||||||
ml_clearmarked(); /* clear rest of the marks */
|
ml_clearmarked(); /* clear rest of the marks */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void ex_terminal(exarg_T *eap)
|
||||||
|
{
|
||||||
|
char cmd[512];
|
||||||
|
snprintf(cmd, sizeof(cmd), ":enew%s | call termopen('%s') | startinsert",
|
||||||
|
eap->forceit==TRUE ? "!" : "",
|
||||||
|
strcmp((char *)eap->arg, "") ? (char *)eap->arg : (char *)p_sh);
|
||||||
|
do_cmdline_cmd((uint8_t *)cmd);
|
||||||
|
}
|
||||||
|
@ -4756,10 +4756,11 @@ buf_check_timestamp (
|
|||||||
char_u *s;
|
char_u *s;
|
||||||
char *reason;
|
char *reason;
|
||||||
|
|
||||||
/* If there is no file name, the buffer is not loaded, 'buftype' is
|
// If its a terminal, there is no file name, the buffer is not loaded,
|
||||||
* set, we are in the middle of a save or being called recursively: ignore
|
// 'buftype' is set, we are in the middle of a save or being called
|
||||||
* this buffer. */
|
// recursively: ignore this buffer.
|
||||||
if (buf->b_ffname == NULL
|
if (buf->terminal
|
||||||
|
|| buf->b_ffname == NULL
|
||||||
|| buf->b_ml.ml_mfp == NULL
|
|| buf->b_ml.ml_mfp == NULL
|
||||||
|| *buf->b_p_bt != NUL
|
|| *buf->b_p_bt != NUL
|
||||||
|| buf->b_saving
|
|| buf->b_saving
|
||||||
@ -5208,6 +5209,7 @@ static struct event_name {
|
|||||||
{"TabNew", EVENT_TABNEW},
|
{"TabNew", EVENT_TABNEW},
|
||||||
{"TabNewEntered", EVENT_TABNEWENTERED},
|
{"TabNewEntered", EVENT_TABNEWENTERED},
|
||||||
{"TermChanged", EVENT_TERMCHANGED},
|
{"TermChanged", EVENT_TERMCHANGED},
|
||||||
|
{"TermOpen", EVENT_TERMOPEN},
|
||||||
{"TermResponse", EVENT_TERMRESPONSE},
|
{"TermResponse", EVENT_TERMRESPONSE},
|
||||||
{"TextChanged", EVENT_TEXTCHANGED},
|
{"TextChanged", EVENT_TEXTCHANGED},
|
||||||
{"TextChangedI", EVENT_TEXTCHANGEDI},
|
{"TextChangedI", EVENT_TEXTCHANGEDI},
|
||||||
|
@ -100,6 +100,7 @@ typedef enum auto_event {
|
|||||||
EVENT_TABNEWENTERED, /* after entering a new tab */
|
EVENT_TABNEWENTERED, /* after entering a new tab */
|
||||||
EVENT_SHELLCMDPOST, /* after ":!cmd" */
|
EVENT_SHELLCMDPOST, /* after ":!cmd" */
|
||||||
EVENT_SHELLFILTERPOST, /* after ":1,2!cmd", ":w !cmd", ":r !cmd". */
|
EVENT_SHELLFILTERPOST, /* after ":1,2!cmd", ":w !cmd", ":r !cmd". */
|
||||||
|
EVENT_TERMOPEN, // after opening a terminal buffer
|
||||||
EVENT_TEXTCHANGED, /* text was modified */
|
EVENT_TEXTCHANGED, /* text was modified */
|
||||||
EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/
|
EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/
|
||||||
EVENT_CMDUNDEFINED, ///< command undefined
|
EVENT_CMDUNDEFINED, ///< command undefined
|
||||||
|
@ -137,7 +137,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to)
|
|||||||
int hasAnyFolding(win_T *win)
|
int hasAnyFolding(win_T *win)
|
||||||
{
|
{
|
||||||
/* very simple now, but can become more complex later */
|
/* very simple now, but can become more complex later */
|
||||||
return win->w_p_fen
|
return !win->w_buffer->terminal && win->w_p_fen
|
||||||
&& (!foldmethodIsManual(win) || !GA_EMPTY(&win->w_folds));
|
&& (!foldmethodIsManual(win) || !GA_EMPTY(&win->w_folds));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -768,6 +768,9 @@ void clearFolding(win_T *win)
|
|||||||
void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
|
void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
|
||||||
{
|
{
|
||||||
fold_T *fp;
|
fold_T *fp;
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/* Mark all folds from top to bot as maybe-small. */
|
/* Mark all folds from top to bot as maybe-small. */
|
||||||
(void)foldFind(&curwin->w_folds, top, &fp);
|
(void)foldFind(&curwin->w_folds, top, &fp);
|
||||||
|
@ -2538,6 +2538,7 @@ fix_input_buffer (
|
|||||||
* for :xmap mode is VISUAL
|
* for :xmap mode is VISUAL
|
||||||
* for :smap mode is SELECTMODE
|
* for :smap mode is SELECTMODE
|
||||||
* for :omap mode is OP_PENDING
|
* for :omap mode is OP_PENDING
|
||||||
|
* for :tmap mode is TERM_FOCUS
|
||||||
*
|
*
|
||||||
* for :abbr mode is INSERT + CMDLINE
|
* for :abbr mode is INSERT + CMDLINE
|
||||||
* for :iabbr mode is INSERT
|
* for :iabbr mode is INSERT
|
||||||
@ -3056,6 +3057,8 @@ int get_map_mode(char_u **cmdp, int forceit)
|
|||||||
mode = SELECTMODE; /* :smap */
|
mode = SELECTMODE; /* :smap */
|
||||||
else if (modec == 'o')
|
else if (modec == 'o')
|
||||||
mode = OP_PENDING; /* :omap */
|
mode = OP_PENDING; /* :omap */
|
||||||
|
else if (modec == 't')
|
||||||
|
mode = TERM_FOCUS; // :tmap
|
||||||
else {
|
else {
|
||||||
--p;
|
--p;
|
||||||
if (forceit)
|
if (forceit)
|
||||||
@ -3923,6 +3926,9 @@ makemap (
|
|||||||
case LANGMAP:
|
case LANGMAP:
|
||||||
c1 = 'l';
|
c1 = 'l';
|
||||||
break;
|
break;
|
||||||
|
case TERM_FOCUS:
|
||||||
|
c1 = 't';
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
EMSG(_("E228: makemap: Illegal mode"));
|
EMSG(_("E228: makemap: Illegal mode"));
|
||||||
return FAIL;
|
return FAIL;
|
||||||
|
@ -154,4 +154,6 @@
|
|||||||
/// zero in those cases (-Wdiv-by-zero in GCC).
|
/// zero in those cases (-Wdiv-by-zero in GCC).
|
||||||
#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
|
#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0])))))
|
||||||
|
|
||||||
|
#define RGB(r, g, b) ((r << 16) | (g << 8) | b)
|
||||||
|
|
||||||
#endif // NVIM_MACROS_H
|
#endif // NVIM_MACROS_H
|
||||||
|
@ -285,6 +285,17 @@ int main(int argc, char **argv)
|
|||||||
input_start_stdin(fd);
|
input_start_stdin(fd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// open terminals when opening files that start with term://
|
||||||
|
do_cmdline_cmd((uint8_t *)
|
||||||
|
"autocmd BufReadCmd term://* "
|
||||||
|
":call termopen( "
|
||||||
|
// Capture the command string
|
||||||
|
"matchstr(expand(\"<amatch>\"), "
|
||||||
|
"'\\c\\mterm://\\%(.\\{-}//\\%(\\d\\+:\\)\\?\\)\\?\\zs.*'), "
|
||||||
|
// capture the working directory
|
||||||
|
"get(matchlist(expand(\"<amatch>\"), "
|
||||||
|
"'\\c\\mterm://\\(.\\{-}\\)//'), 1, ''))");
|
||||||
|
|
||||||
/* Execute --cmd arguments. */
|
/* Execute --cmd arguments. */
|
||||||
exe_pre_commands(¶ms);
|
exe_pre_commands(¶ms);
|
||||||
|
|
||||||
|
@ -278,10 +278,11 @@ int ml_open(buf_T *buf)
|
|||||||
/*
|
/*
|
||||||
* When 'updatecount' is non-zero swap file may be opened later.
|
* When 'updatecount' is non-zero swap file may be opened later.
|
||||||
*/
|
*/
|
||||||
if (p_uc && buf->b_p_swf)
|
if (!buf->terminal && p_uc && buf->b_p_swf) {
|
||||||
buf->b_may_swap = true;
|
buf->b_may_swap = true;
|
||||||
else
|
} else {
|
||||||
buf->b_may_swap = false;
|
buf->b_may_swap = false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Open the memfile. No swap file is created yet.
|
* Open the memfile. No swap file is created yet.
|
||||||
@ -488,7 +489,8 @@ void ml_open_file(buf_T *buf)
|
|||||||
char_u *dirp;
|
char_u *dirp;
|
||||||
|
|
||||||
mfp = buf->b_ml.ml_mfp;
|
mfp = buf->b_ml.ml_mfp;
|
||||||
if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf || cmdmod.noswapfile) {
|
if (mfp == NULL || mfp->mf_fd >= 0 || !buf->b_p_swf || cmdmod.noswapfile
|
||||||
|
|| buf->terminal) {
|
||||||
return; /* nothing to do */
|
return; /* nothing to do */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/window.h"
|
#include "nvim/window.h"
|
||||||
@ -2643,11 +2644,13 @@ do_put (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Autocommands may be executed when saving lines for undo, which may make
|
if (!curbuf->terminal) {
|
||||||
* y_array invalid. Start undo now to avoid that. */
|
// Autocommands may be executed when saving lines for undo, which may make
|
||||||
if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) {
|
// y_array invalid. Start undo now to avoid that.
|
||||||
ELOG(_("Failed to save undo information"));
|
if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) {
|
||||||
return;
|
ELOG(_("Failed to save undo information"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (insert_string != NULL) {
|
if (insert_string != NULL) {
|
||||||
@ -2692,6 +2695,20 @@ do_put (
|
|||||||
y_array = y_current->y_array;
|
y_array = y_current->y_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (curbuf->terminal) {
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
// feed the lines to the terminal
|
||||||
|
for (int j = 0; j < y_size; j++) {
|
||||||
|
if (j) {
|
||||||
|
// terminate the previous line
|
||||||
|
terminal_send(curbuf->terminal, "\n", 1);
|
||||||
|
}
|
||||||
|
terminal_send(curbuf->terminal, (char *)y_array[j], STRLEN(y_array[j]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (y_type == MLINE) {
|
if (y_type == MLINE) {
|
||||||
if (flags & PUT_LINE_SPLIT) {
|
if (flags & PUT_LINE_SPLIT) {
|
||||||
/* "p" or "P" in Visual mode: split the lines to put the text in
|
/* "p" or "P" in Visual mode: split the lines to put the text in
|
||||||
|
@ -1746,7 +1746,7 @@ static char *(p_scbopt_values[]) = {"ver", "hor", "jump", NULL};
|
|||||||
static char *(p_debug_values[]) = {"msg", "throw", "beep", NULL};
|
static char *(p_debug_values[]) = {"msg", "throw", "beep", NULL};
|
||||||
static char *(p_ead_values[]) = {"both", "ver", "hor", NULL};
|
static char *(p_ead_values[]) = {"both", "ver", "hor", NULL};
|
||||||
static char *(p_buftype_values[]) =
|
static char *(p_buftype_values[]) =
|
||||||
{"nofile", "nowrite", "quickfix", "help", "acwrite", NULL};
|
{"nofile", "nowrite", "quickfix", "help", "acwrite", "terminal", NULL};
|
||||||
static char *(p_bufhidden_values[]) = {"hide", "unload", "delete", "wipe", NULL};
|
static char *(p_bufhidden_values[]) = {"hide", "unload", "delete", "wipe", NULL};
|
||||||
static char *(p_bs_values[]) = {"indent", "eol", "start", NULL};
|
static char *(p_bs_values[]) = {"indent", "eol", "start", NULL};
|
||||||
static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax",
|
static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax",
|
||||||
@ -4097,9 +4097,11 @@ did_set_string_option (
|
|||||||
}
|
}
|
||||||
/* When 'buftype' is set, check for valid value. */
|
/* When 'buftype' is set, check for valid value. */
|
||||||
else if (gvarp == &p_bt) {
|
else if (gvarp == &p_bt) {
|
||||||
if (check_opt_strings(curbuf->b_p_bt, p_buftype_values, FALSE) != OK)
|
if ((curbuf->terminal && curbuf->b_p_bt[0] != 't')
|
||||||
|
|| (!curbuf->terminal && curbuf->b_p_bt[0] == 't')
|
||||||
|
|| check_opt_strings(curbuf->b_p_bt, p_buftype_values, FALSE) != OK) {
|
||||||
errmsg = e_invarg;
|
errmsg = e_invarg;
|
||||||
else {
|
} else {
|
||||||
if (curwin->w_status_height) {
|
if (curwin->w_status_height) {
|
||||||
curwin->w_redr_status = TRUE;
|
curwin->w_redr_status = TRUE;
|
||||||
redraw_later(VALID);
|
redraw_later(VALID);
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include "nvim/misc2.h"
|
#include "nvim/misc2.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
|
|
||||||
#include "nvim/lib/klist.h"
|
#include "nvim/lib/klist.h"
|
||||||
|
|
||||||
@ -63,6 +64,7 @@ void event_init(void)
|
|||||||
// finish mspgack-rpc initialization
|
// finish mspgack-rpc initialization
|
||||||
channel_init();
|
channel_init();
|
||||||
server_init();
|
server_init();
|
||||||
|
terminal_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void event_teardown(void)
|
void event_teardown(void)
|
||||||
@ -83,6 +85,7 @@ void event_teardown(void)
|
|||||||
job_teardown();
|
job_teardown();
|
||||||
server_teardown();
|
server_teardown();
|
||||||
signal_teardown();
|
signal_teardown();
|
||||||
|
terminal_teardown();
|
||||||
// this last `uv_run` will return after all handles are stopped, it will
|
// this last `uv_run` will return after all handles are stopped, it will
|
||||||
// also take care of finishing any uv_close calls made by other *_teardown
|
// also take care of finishing any uv_close calls made by other *_teardown
|
||||||
// functions.
|
// functions.
|
||||||
@ -169,11 +172,6 @@ void event_push(Event event, bool deferred)
|
|||||||
void event_process(void)
|
void event_process(void)
|
||||||
{
|
{
|
||||||
process_events_from(deferred_events);
|
process_events_from(deferred_events);
|
||||||
|
|
||||||
if (must_redraw) {
|
|
||||||
update_screen(0);
|
|
||||||
ui_flush();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void process_events_from(klist_t(Event) *queue)
|
static void process_events_from(klist_t(Event) *queue)
|
||||||
|
@ -2377,7 +2377,6 @@ static void qf_fill_buffer(qf_info_T *qi)
|
|||||||
KeyTyped = old_KeyTyped;
|
KeyTyped = old_KeyTyped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Return TRUE if "buf" is the quickfix buffer.
|
* Return TRUE if "buf" is the quickfix buffer.
|
||||||
*/
|
*/
|
||||||
@ -2386,22 +2385,18 @@ int bt_quickfix(buf_T *buf)
|
|||||||
return buf != NULL && buf->b_p_bt[0] == 'q';
|
return buf != NULL && buf->b_p_bt[0] == 'q';
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Return TRUE if "buf" is a "nofile", "acwrite" or "terminal" buffer.
|
||||||
* Return TRUE if "buf" is a "nofile" or "acwrite" buffer.
|
// This means the buffer name is not a file name.
|
||||||
* This means the buffer name is not a file name.
|
|
||||||
*/
|
|
||||||
int bt_nofile(buf_T *buf)
|
int bt_nofile(buf_T *buf)
|
||||||
{
|
{
|
||||||
return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
|
return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f')
|
||||||
|| buf->b_p_bt[0] == 'a');
|
|| buf->b_p_bt[0] == 'a' || buf->terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// Return TRUE if "buf" is a "nowrite", "nofile" or "terminal" buffer.
|
||||||
* Return TRUE if "buf" is a "nowrite" or "nofile" buffer.
|
|
||||||
*/
|
|
||||||
int bt_dontwrite(buf_T *buf)
|
int bt_dontwrite(buf_T *buf)
|
||||||
{
|
{
|
||||||
return buf != NULL && buf->b_p_bt[0] == 'n';
|
return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal);
|
||||||
}
|
}
|
||||||
|
|
||||||
int bt_dontwrite_msg(buf_T *buf)
|
int bt_dontwrite_msg(buf_T *buf)
|
||||||
|
@ -131,6 +131,7 @@
|
|||||||
#include "nvim/spell.h"
|
#include "nvim/spell.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/syntax.h"
|
#include "nvim/syntax.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/version.h"
|
#include "nvim/version.h"
|
||||||
@ -2210,7 +2211,7 @@ win_line (
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Check for columns to display for 'colorcolumn'. */
|
/* Check for columns to display for 'colorcolumn'. */
|
||||||
color_cols = wp->w_p_cc_cols;
|
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
|
||||||
if (color_cols != NULL)
|
if (color_cols != NULL)
|
||||||
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
|
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
|
||||||
|
|
||||||
@ -2592,6 +2593,13 @@ win_line (
|
|||||||
off += col;
|
off += col;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// wont highlight after 1024 columns
|
||||||
|
int term_attrs[1024] = {0};
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
terminal_get_line_attributes(wp->w_buffer->terminal, wp, lnum, term_attrs);
|
||||||
|
extra_check = true;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Repeat for the whole displayed line.
|
* Repeat for the whole displayed line.
|
||||||
*/
|
*/
|
||||||
@ -3213,6 +3221,8 @@ win_line (
|
|||||||
syntax_flags = 0;
|
syntax_flags = 0;
|
||||||
else
|
else
|
||||||
syntax_flags = get_syntax_info(&syntax_seqnr);
|
syntax_flags = get_syntax_info(&syntax_seqnr);
|
||||||
|
} else if (!attr_pri) {
|
||||||
|
char_attr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Check spelling (unless at the end of the line).
|
/* Check spelling (unless at the end of the line).
|
||||||
@ -3290,6 +3300,11 @@ win_line (
|
|||||||
else
|
else
|
||||||
char_attr = hl_combine_attr(spell_attr, char_attr);
|
char_attr = hl_combine_attr(spell_attr, char_attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
char_attr = hl_combine_attr(char_attr, term_attrs[vcol]);
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Found last space before word: check for line break.
|
* Found last space before word: check for line break.
|
||||||
*/
|
*/
|
||||||
@ -3787,6 +3802,18 @@ win_line (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
// terminal buffers may need to highlight beyond the end of the
|
||||||
|
// logical line
|
||||||
|
while (col < wp->w_width) {
|
||||||
|
ScreenLines[off] = ' ';
|
||||||
|
if (enc_utf8) {
|
||||||
|
ScreenLinesUC[off] = 0;
|
||||||
|
}
|
||||||
|
ScreenAttrs[off++] = term_attrs[vcol++];
|
||||||
|
col++;
|
||||||
|
}
|
||||||
|
}
|
||||||
SCREEN_LINE(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl);
|
SCREEN_LINE(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl);
|
||||||
row++;
|
row++;
|
||||||
|
|
||||||
@ -6536,7 +6563,8 @@ int showmode(void)
|
|||||||
int sub_attr;
|
int sub_attr;
|
||||||
|
|
||||||
do_mode = ((p_smd && msg_silent == 0)
|
do_mode = ((p_smd && msg_silent == 0)
|
||||||
&& ((State & INSERT)
|
&& ((State & TERM_FOCUS)
|
||||||
|
|| (State & INSERT)
|
||||||
|| restart_edit
|
|| restart_edit
|
||||||
|| VIsual_active
|
|| VIsual_active
|
||||||
));
|
));
|
||||||
@ -6591,7 +6619,9 @@ int showmode(void)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (State & VREPLACE_FLAG)
|
if (State & TERM_FOCUS) {
|
||||||
|
MSG_PUTS_ATTR(_(" TERMINAL"), attr);
|
||||||
|
} else if (State & VREPLACE_FLAG)
|
||||||
MSG_PUTS_ATTR(_(" VREPLACE"), attr);
|
MSG_PUTS_ATTR(_(" VREPLACE"), attr);
|
||||||
else if (State & REPLACE_FLAG)
|
else if (State & REPLACE_FLAG)
|
||||||
MSG_PUTS_ATTR(_(" REPLACE"), attr);
|
MSG_PUTS_ATTR(_(" REPLACE"), attr);
|
||||||
|
@ -40,10 +40,12 @@
|
|||||||
#include "nvim/option.h"
|
#include "nvim/option.h"
|
||||||
#include "nvim/os_unix.h"
|
#include "nvim/os_unix.h"
|
||||||
#include "nvim/path.h"
|
#include "nvim/path.h"
|
||||||
|
#include "nvim/macros.h"
|
||||||
#include "nvim/regexp.h"
|
#include "nvim/regexp.h"
|
||||||
#include "nvim/screen.h"
|
#include "nvim/screen.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/syntax_defs.h"
|
#include "nvim/syntax_defs.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/ui.h"
|
#include "nvim/ui.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
#include "nvim/os/time.h"
|
#include "nvim/os/time.h"
|
||||||
@ -6636,7 +6638,7 @@ static garray_T attr_table = GA_EMPTY_INIT_VALUE;
|
|||||||
* if the combination is new.
|
* if the combination is new.
|
||||||
* Return 0 for error.
|
* Return 0 for error.
|
||||||
*/
|
*/
|
||||||
static int get_attr_entry(attrentry_T *aep)
|
int get_attr_entry(attrentry_T *aep)
|
||||||
{
|
{
|
||||||
garray_T *table = &attr_table;
|
garray_T *table = &attr_table;
|
||||||
attrentry_T *taep;
|
attrentry_T *taep;
|
||||||
@ -7424,7 +7426,6 @@ char_u *get_highlight_name(expand_T *xp, int idx)
|
|||||||
return HL_TABLE()[idx].sg_name;
|
return HL_TABLE()[idx].sg_name;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define RGB(r, g, b) ((r << 16) | (g << 8) | b)
|
|
||||||
color_name_table_T color_name_table[] = {
|
color_name_table_T color_name_table[] = {
|
||||||
// Color names taken from
|
// Color names taken from
|
||||||
// http://www.rapidtables.com/web/color/RGB_Color.htm
|
// http://www.rapidtables.com/web/color/RGB_Color.htm
|
||||||
|
1129
src/nvim/terminal.c
Normal file
1129
src/nvim/terminal.c
Normal file
File diff suppressed because it is too large
Load Diff
33
src/nvim/terminal.h
Normal file
33
src/nvim/terminal.h
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
#ifndef NVIM_TERMINAL_H
|
||||||
|
#define NVIM_TERMINAL_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
typedef struct terminal Terminal;
|
||||||
|
typedef void (*terminal_write_cb)(char *buffer, size_t size, void *data);
|
||||||
|
typedef void (*terminal_resize_cb)(uint16_t width, uint16_t height, void *data);
|
||||||
|
typedef void (*terminal_close_cb)(void *data);
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
void *data;
|
||||||
|
uint16_t width, height;
|
||||||
|
terminal_write_cb write_cb;
|
||||||
|
terminal_resize_cb resize_cb;
|
||||||
|
terminal_close_cb close_cb;
|
||||||
|
} TerminalOptions;
|
||||||
|
|
||||||
|
#define TERMINAL_OPTIONS_INIT ((TerminalOptions) { \
|
||||||
|
.data = NULL, \
|
||||||
|
.width = 80, \
|
||||||
|
.height = 24, \
|
||||||
|
.write_cb = NULL, \
|
||||||
|
.resize_cb = NULL, \
|
||||||
|
.close_cb = NULL \
|
||||||
|
})
|
||||||
|
|
||||||
|
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||||
|
# include "terminal.h.generated.h"
|
||||||
|
#endif
|
||||||
|
#endif // NVIM_TERMINAL_H
|
@ -315,6 +315,9 @@ int undo_allowed(void)
|
|||||||
*/
|
*/
|
||||||
static long get_undolevel(void)
|
static long get_undolevel(void)
|
||||||
{
|
{
|
||||||
|
if (curbuf->terminal) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
|
if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
|
||||||
return p_ul;
|
return p_ul;
|
||||||
return curbuf->b_p_ul;
|
return curbuf->b_p_ul;
|
||||||
|
@ -112,9 +112,10 @@ Error: configure did not run properly.Check auto/config.log.
|
|||||||
#define SHOWMATCH (0x700 + INSERT) /* show matching paren */
|
#define SHOWMATCH (0x700 + INSERT) /* show matching paren */
|
||||||
#define CONFIRM 0x800 /* ":confirm" prompt */
|
#define CONFIRM 0x800 /* ":confirm" prompt */
|
||||||
#define SELECTMODE 0x1000 /* Select mode, only for mappings */
|
#define SELECTMODE 0x1000 /* Select mode, only for mappings */
|
||||||
|
#define TERM_FOCUS 0x2000 // Terminal focus mode
|
||||||
|
|
||||||
#define MAP_ALL_MODES (0x3f | SELECTMODE) /* all mode bits used for
|
// all mode bits used for mapping
|
||||||
* mapping */
|
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS)
|
||||||
|
|
||||||
/* directions */
|
/* directions */
|
||||||
#define FORWARD 1
|
#define FORWARD 1
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
#include "nvim/search.h"
|
#include "nvim/search.h"
|
||||||
#include "nvim/strings.h"
|
#include "nvim/strings.h"
|
||||||
#include "nvim/syntax.h"
|
#include "nvim/syntax.h"
|
||||||
|
#include "nvim/terminal.h"
|
||||||
#include "nvim/undo.h"
|
#include "nvim/undo.h"
|
||||||
#include "nvim/os/os.h"
|
#include "nvim/os/os.h"
|
||||||
|
|
||||||
@ -1765,6 +1766,12 @@ static int close_last_window_tabpage(win_T *win, int free_buf, tabpage_T *prev_c
|
|||||||
}
|
}
|
||||||
buf_T *old_curbuf = curbuf;
|
buf_T *old_curbuf = curbuf;
|
||||||
|
|
||||||
|
Terminal *term = win->w_buffer->terminal;
|
||||||
|
if (term) {
|
||||||
|
// Don't free terminal buffers
|
||||||
|
free_buf = false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Closing the last window in a tab page. First go to another tab
|
* Closing the last window in a tab page. First go to another tab
|
||||||
* page and then close the window and the tab page. This avoids that
|
* page and then close the window and the tab page. This avoids that
|
||||||
@ -1789,6 +1796,13 @@ static int close_last_window_tabpage(win_T *win, int free_buf, tabpage_T *prev_c
|
|||||||
if (h != tabline_height())
|
if (h != tabline_height())
|
||||||
shell_new_rows();
|
shell_new_rows();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (term) {
|
||||||
|
// When a window containing a terminal buffer is closed, recalculate its
|
||||||
|
// size
|
||||||
|
terminal_resize(term, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/* Since goto_tabpage_tp above did not trigger *Enter autocommands, do
|
/* Since goto_tabpage_tp above did not trigger *Enter autocommands, do
|
||||||
* that now. */
|
* that now. */
|
||||||
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf);
|
apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf);
|
||||||
@ -1907,6 +1921,8 @@ int win_close(win_T *win, int free_buf)
|
|||||||
|| close_last_window_tabpage(win, free_buf, prev_curtab))
|
|| close_last_window_tabpage(win, free_buf, prev_curtab))
|
||||||
return FAIL;
|
return FAIL;
|
||||||
|
|
||||||
|
// let terminal buffers know that this window dimensions may be ignored
|
||||||
|
win->w_closing = true;
|
||||||
/* Free the memory used for the window and get the window that received
|
/* Free the memory used for the window and get the window that received
|
||||||
* the screen space. */
|
* the screen space. */
|
||||||
wp = win_free_mem(win, &dir, NULL);
|
wp = win_free_mem(win, &dir, NULL);
|
||||||
@ -1963,7 +1979,6 @@ int win_close(win_T *win, int free_buf)
|
|||||||
if (help_window)
|
if (help_window)
|
||||||
restore_snapshot(SNAP_HELP_IDX, close_curwin);
|
restore_snapshot(SNAP_HELP_IDX, close_curwin);
|
||||||
|
|
||||||
|
|
||||||
redraw_all_later(NOT_VALID);
|
redraw_all_later(NOT_VALID);
|
||||||
return OK;
|
return OK;
|
||||||
}
|
}
|
||||||
@ -4689,6 +4704,11 @@ void win_new_height(win_T *wp, int height)
|
|||||||
redraw_win_later(wp, SOME_VALID);
|
redraw_win_later(wp, SOME_VALID);
|
||||||
wp->w_redr_status = TRUE;
|
wp->w_redr_status = TRUE;
|
||||||
invalidate_botline_win(wp);
|
invalidate_botline_win(wp);
|
||||||
|
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
terminal_resize(wp->w_buffer->terminal, 0, wp->w_height);
|
||||||
|
redraw_win_later(wp, CLEAR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -4706,6 +4726,11 @@ void win_new_width(win_T *wp, int width)
|
|||||||
}
|
}
|
||||||
redraw_win_later(wp, NOT_VALID);
|
redraw_win_later(wp, NOT_VALID);
|
||||||
wp->w_redr_status = TRUE;
|
wp->w_redr_status = TRUE;
|
||||||
|
|
||||||
|
if (wp->w_buffer->terminal) {
|
||||||
|
terminal_resize(wp->w_buffer->terminal, wp->w_width, 0);
|
||||||
|
redraw_win_later(wp, CLEAR);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void win_comp_scroll(win_T *wp)
|
void win_comp_scroll(win_T *wp)
|
||||||
@ -5570,4 +5595,3 @@ static int frame_check_width(frame_T *topfrp, int width)
|
|||||||
|
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user