mirror of
https://github.com/neovim/neovim.git
synced 2025-02-25 18:55:25 -06:00
Merge PR #2076 'Builtin terminal emulation'
This commit is contained in:
commit
a6e53a3797
@ -516,33 +516,6 @@ ArrayOf(Integer, 2) buffer_get_mark(Buffer buffer, String name, Error *err)
|
||||
return rv;
|
||||
}
|
||||
|
||||
// Find a window that contains "buf" and switch to it.
|
||||
// If there is no such window, use the current window and change "curbuf".
|
||||
// Caller must initialize save_curbuf to NULL.
|
||||
// restore_win_for_buf() MUST be called later!
|
||||
static void switch_to_win_for_buf(buf_T *buf,
|
||||
win_T **save_curwinp,
|
||||
tabpage_T **save_curtabp,
|
||||
buf_T **save_curbufp)
|
||||
{
|
||||
win_T *wp;
|
||||
tabpage_T *tp;
|
||||
|
||||
if (!find_win_for_buf(buf, &wp, &tp)
|
||||
|| switch_win(save_curwinp, save_curtabp, wp, tp, true) == FAIL)
|
||||
switch_buffer(save_curbufp, buf);
|
||||
}
|
||||
|
||||
static void restore_win_for_buf(win_T *save_curwin,
|
||||
tabpage_T *save_curtab,
|
||||
buf_T *save_curbuf)
|
||||
{
|
||||
if (save_curbuf == NULL) {
|
||||
restore_win(save_curwin, save_curtab, true);
|
||||
} else {
|
||||
restore_buffer(save_curbuf);
|
||||
}
|
||||
}
|
||||
|
||||
// Check if deleting lines made the cursor position invalid.
|
||||
// Changed the lines from "lo" to "hi" and added "extra" lines (negative if
|
||||
|
@ -68,6 +68,7 @@
|
||||
#include "nvim/spell.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/version.h"
|
||||
@ -307,20 +308,28 @@ close_buffer (
|
||||
bool del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE);
|
||||
bool wipe_buf = (action == DOBUF_WIPE);
|
||||
|
||||
/*
|
||||
* Force unloading or deleting when 'bufhidden' says so.
|
||||
* The caller must take care of NOT deleting/freeing when 'bufhidden' is
|
||||
* "hide" (otherwise we could never free or delete a buffer).
|
||||
*/
|
||||
if (buf->b_p_bh[0] == 'd') { /* 'bufhidden' == "delete" */
|
||||
del_buf = true;
|
||||
// Force unloading or deleting when 'bufhidden' says so, but not for terminal
|
||||
// buffers.
|
||||
// The caller must take care of NOT deleting/freeing when 'bufhidden' is
|
||||
// "hide" (otherwise we could never free or delete a buffer).
|
||||
if (!buf->terminal) {
|
||||
if (buf->b_p_bh[0] == 'd') { // 'bufhidden' == "delete"
|
||||
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;
|
||||
} 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 (win_valid(win)) {
|
||||
/* 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)
|
||||
return;
|
||||
|
||||
if (buf->terminal) {
|
||||
terminal_close(buf->terminal, NULL);
|
||||
}
|
||||
|
||||
/* Always remove the buffer when there is no file name. */
|
||||
if (buf->b_ffname == NULL)
|
||||
del_buf = TRUE;
|
||||
@ -925,8 +938,8 @@ do_buffer (
|
||||
if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl)
|
||||
return FAIL;
|
||||
|
||||
if (!forceit && bufIsChanged(buf)) {
|
||||
if ((p_confirm || cmdmod.confirm) && p_write) {
|
||||
if (!forceit && (buf->terminal || bufIsChanged(buf))) {
|
||||
if ((p_confirm || cmdmod.confirm) && p_write && !buf->terminal) {
|
||||
dialog_changed(buf, FALSE);
|
||||
if (!buf_valid(buf))
|
||||
/* Autocommand deleted buffer, oops! It's not changed
|
||||
@ -937,9 +950,14 @@ do_buffer (
|
||||
if (bufIsChanged(buf))
|
||||
return FAIL;
|
||||
} else {
|
||||
EMSGN(_("E89: No write since last change for buffer %" PRId64
|
||||
" (add ! to override)"),
|
||||
buf->b_fnum);
|
||||
if (buf->terminal) {
|
||||
EMSG2(_("E89: %s will be killed(add ! to override)"),
|
||||
(char *)buf->b_fname);
|
||||
} else {
|
||||
EMSGN(_("E89: No write since last change for buffer %" PRId64
|
||||
" (add ! to override)"),
|
||||
buf->b_fnum);
|
||||
}
|
||||
return FAIL;
|
||||
}
|
||||
}
|
||||
@ -2145,7 +2163,7 @@ void buflist_list(exarg_T *eap)
|
||||
(curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '),
|
||||
buf->b_ml.ml_mfp == NULL ? ' ' :
|
||||
(buf->b_nwindows == 0 ? 'h' : 'a'),
|
||||
!buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' '),
|
||||
!MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' '),
|
||||
(buf->b_flags & BF_READERR) ? 'x'
|
||||
: (bufIsChanged(buf) ? '+' : ' '),
|
||||
NameBuff);
|
||||
@ -2623,7 +2641,7 @@ void maketitle(void)
|
||||
|
||||
switch (bufIsChanged(curbuf)
|
||||
+ (curbuf->b_p_ro * 2)
|
||||
+ (!curbuf->b_p_ma * 4)) {
|
||||
+ (!MODIFIABLE(curbuf) * 4)) {
|
||||
case 1: STRCAT(buf, " +"); break;
|
||||
case 2: STRCAT(buf, " ="); break;
|
||||
case 3: STRCAT(buf, " =+"); break;
|
||||
@ -3250,7 +3268,7 @@ build_stl_str_hl (
|
||||
itemisflag = TRUE;
|
||||
switch ((opt == STL_MODIFIED_ALT)
|
||||
+ bufIsChanged(wp->w_buffer) * 2
|
||||
+ (!wp->w_buffer->b_p_ma) * 4) {
|
||||
+ (!MODIFIABLE(wp->w_buffer)) * 4) {
|
||||
case 2: str = (char_u *)"[+]"; break;
|
||||
case 3: str = (char_u *)",+"; break;
|
||||
case 4: str = (char_u *)"[-]"; break;
|
||||
|
@ -1,6 +1,7 @@
|
||||
#ifndef NVIM_BUFFER_H
|
||||
#define NVIM_BUFFER_H
|
||||
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/pos.h" // for linenr_T
|
||||
#include "nvim/ex_cmds_defs.h" // for exarg_T
|
||||
|
||||
@ -45,4 +46,44 @@ enum bfa_values {
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "buffer.h.generated.h"
|
||||
#endif
|
||||
|
||||
// Find a window that contains "buf" and switch to it.
|
||||
// If there is no such window, use the current window and change "curbuf".
|
||||
// Caller must initialize save_curbuf to NULL.
|
||||
// restore_win_for_buf() MUST be called later!
|
||||
static inline void switch_to_win_for_buf(buf_T *buf,
|
||||
win_T **save_curwinp,
|
||||
tabpage_T **save_curtabp,
|
||||
buf_T **save_curbufp)
|
||||
{
|
||||
win_T *wp;
|
||||
tabpage_T *tp;
|
||||
|
||||
if (!find_win_for_buf(buf, &wp, &tp)
|
||||
|| switch_win(save_curwinp, save_curtabp, wp, tp, true) == FAIL)
|
||||
switch_buffer(save_curbufp, buf);
|
||||
}
|
||||
|
||||
static inline void restore_win_for_buf(win_T *save_curwin,
|
||||
tabpage_T *save_curtab,
|
||||
buf_T *save_curbuf)
|
||||
{
|
||||
if (save_curbuf == NULL) {
|
||||
restore_win(save_curwin, save_curtab, true);
|
||||
} else {
|
||||
restore_buffer(save_curbuf);
|
||||
}
|
||||
}
|
||||
|
||||
#define WITH_BUFFER(b, code) \
|
||||
do { \
|
||||
buf_T *save_curbuf = NULL; \
|
||||
win_T *save_curwin = NULL; \
|
||||
tabpage_T *save_curtab = NULL; \
|
||||
switch_to_win_for_buf(b, &save_curwin, &save_curtab, &save_curbuf); \
|
||||
code; \
|
||||
restore_win_for_buf(save_curwin, save_curtab, save_curbuf); \
|
||||
} while (0)
|
||||
|
||||
|
||||
#endif // NVIM_BUFFER_H
|
||||
|
@ -27,6 +27,8 @@
|
||||
// for String
|
||||
#include "nvim/api/private/defs.h"
|
||||
|
||||
#define MODIFIABLE(buf) (!buf->terminal && buf->b_p_ma)
|
||||
|
||||
/*
|
||||
* Flags for w_valid.
|
||||
* These are set when something in a window structure becomes invalid, except
|
||||
@ -101,6 +103,9 @@ typedef struct file_buffer buf_T; /* forward declaration */
|
||||
// for FileID
|
||||
#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.
|
||||
*/
|
||||
@ -749,6 +754,8 @@ struct file_buffer {
|
||||
* may use a different synblock_T. */
|
||||
|
||||
signlist_T *b_signlist; /* list of signs to draw */
|
||||
|
||||
Terminal *terminal; // Terminal instance associated with the buffer
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -2078,7 +2078,7 @@ void ex_diffgetput(exarg_T *eap)
|
||||
if ((curtab->tp_diffbuf[idx_other] != curbuf)
|
||||
&& (curtab->tp_diffbuf[idx_other] != NULL)) {
|
||||
if ((eap->cmdidx != CMD_diffput)
|
||||
|| curtab->tp_diffbuf[idx_other]->b_p_ma) {
|
||||
|| MODIFIABLE(curtab->tp_diffbuf[idx_other])) {
|
||||
break;
|
||||
}
|
||||
found_not_ma = TRUE;
|
||||
@ -2098,7 +2098,8 @@ void ex_diffgetput(exarg_T *eap)
|
||||
for (i = idx_other + 1; i < DB_COUNT; ++i) {
|
||||
if ((curtab->tp_diffbuf[i] != curbuf)
|
||||
&& (curtab->tp_diffbuf[i] != NULL)
|
||||
&& ((eap->cmdidx != CMD_diffput) || curtab->tp_diffbuf[i]->b_p_ma)) {
|
||||
&& ((eap->cmdidx != CMD_diffput)
|
||||
|| MODIFIABLE(curtab->tp_diffbuf[i]))) {
|
||||
EMSG(_("E101: More than two buffers in diff mode, don't know "
|
||||
"which one to use"));
|
||||
return;
|
||||
|
@ -56,6 +56,7 @@
|
||||
#include "nvim/tag.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
#include "nvim/os/event.h"
|
||||
@ -244,6 +245,11 @@ edit (
|
||||
long count
|
||||
)
|
||||
{
|
||||
if (curbuf->terminal) {
|
||||
terminal_enter(curbuf->terminal, true);
|
||||
return false;
|
||||
}
|
||||
|
||||
int c = 0;
|
||||
char_u *ptr;
|
||||
int lastc;
|
||||
|
214
src/nvim/eval.c
214
src/nvim/eval.c
@ -1,4 +1,5 @@
|
||||
/*
|
||||
*
|
||||
* VIM - Vi IMproved by Bram Moolenaar
|
||||
*
|
||||
* Do ":help uganda" in Vim to read copying and usage conditions.
|
||||
@ -78,6 +79,7 @@
|
||||
#include "nvim/tempfile.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/version.h"
|
||||
#include "nvim/window.h"
|
||||
@ -440,6 +442,15 @@ static struct vimvar {
|
||||
static dictitem_T vimvars_var; /* variable used for v: */
|
||||
#define vimvarht vimvardict.dv_hashtab
|
||||
|
||||
typedef struct {
|
||||
Job *job;
|
||||
Terminal *term;
|
||||
bool exited;
|
||||
int refcount;
|
||||
char *autocmd_file;
|
||||
} TerminalJobData;
|
||||
|
||||
|
||||
#ifdef INCLUDE_GENERATED_DECLARATIONS
|
||||
# include "eval.c.generated.h"
|
||||
#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_CHECK_START 2 /* find_name_end(): check name starts with
|
||||
valid character */
|
||||
|
||||
// Memory pool for reusing JobEvent structures
|
||||
typedef struct {
|
||||
int id;
|
||||
@ -6600,6 +6610,7 @@ static struct fst {
|
||||
{"tan", 1, 1, f_tan},
|
||||
{"tanh", 1, 1, f_tanh},
|
||||
{"tempname", 0, 0, f_tempname},
|
||||
{"termopen", 1, 2, f_termopen},
|
||||
{"test", 1, 1, f_test},
|
||||
{"tolower", 1, 1, f_tolower},
|
||||
{"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
|
||||
argv[i] = NULL;
|
||||
JobOptions opts = JOB_OPTIONS_INIT;
|
||||
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;
|
||||
JobOptions opts = common_job_options(argv, (char *)argvars[0].vval.v_string);
|
||||
|
||||
if (args && argvars[3].v_type == VAR_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);
|
||||
|
||||
if (rettv->vval.v_number <= 0) {
|
||||
if (rettv->vval.v_number == 0) {
|
||||
EMSG(_(e_jobtblfull));
|
||||
} else {
|
||||
EMSG(_(e_jobexe));
|
||||
}
|
||||
}
|
||||
common_job_start(opts, rettv);
|
||||
}
|
||||
|
||||
// "jobstop()" function
|
||||
@ -14870,6 +14868,72 @@ static void f_tempname(typval_T *argvars, typval_T *rettv)
|
||||
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...
|
||||
*/
|
||||
@ -19750,16 +19814,52 @@ char_u *do_string_sub(char_u *str, char_u *pat, char_u *sub, char_u *flags)
|
||||
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)
|
||||
{
|
||||
TerminalJobData *data = opts.data;
|
||||
data->refcount++;
|
||||
Job *job = job_start(opts, &rettv->vval.v_number);
|
||||
|
||||
if (rettv->vval.v_number <= 0) {
|
||||
if (rettv->vval.v_number == 0) {
|
||||
EMSG(_(e_jobtblfull));
|
||||
free(opts.term_name);
|
||||
free(data->autocmd_file);
|
||||
free(data);
|
||||
} else {
|
||||
EMSG(_(e_jobexe));
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
data->job = job;
|
||||
return job;
|
||||
}
|
||||
|
||||
// JobActivity autocommands will execute vimscript code, so it must be executed
|
||||
// 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);
|
||||
event_data->received = NULL;
|
||||
if (rstream) {
|
||||
if (data) {
|
||||
event_data->received = list_alloc();
|
||||
char *ptr = rstream_read_ptr(rstream);
|
||||
size_t count = rstream_pending(rstream);
|
||||
char *ptr = data;
|
||||
size_t remaining = count;
|
||||
size_t off = 0;
|
||||
|
||||
@ -19780,10 +19880,10 @@ static inline void push_job_event(Job *job, RStream *rstream, char *type)
|
||||
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->name = job_data(job);
|
||||
event_data->name = d->autocmd_file;
|
||||
event_data->type = type;
|
||||
event_push((Event) {
|
||||
.handler = on_job_event,
|
||||
@ -19793,21 +19893,75 @@ static inline void push_job_event(Job *job, RStream *rstream, char *type)
|
||||
|
||||
static void on_job_stdout(RStream *rstream, void *data, bool eof)
|
||||
{
|
||||
if (!eof) {
|
||||
push_job_event(data, rstream, "stdout");
|
||||
}
|
||||
on_job_output(rstream, data, eof, "stdout");
|
||||
}
|
||||
|
||||
static void on_job_stderr(RStream *rstream, void *data, bool eof)
|
||||
{
|
||||
if (!eof) {
|
||||
push_job_event(data, rstream, "stderr");
|
||||
}
|
||||
on_job_output(rstream, data, eof, "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)
|
||||
|
@ -2724,7 +2724,7 @@ do_ecmd (
|
||||
/* close the link to the current buffer */
|
||||
u_sync(FALSE);
|
||||
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
|
||||
* which leads to crashes since the above call sets
|
||||
@ -3656,7 +3656,7 @@ void do_sub(exarg_T *eap)
|
||||
if (eap->skip) /* not executing commands, only parsing */
|
||||
return;
|
||||
|
||||
if (!do_count && !curbuf->b_p_ma) {
|
||||
if (!do_count && !MODIFIABLE(curbuf)) {
|
||||
/* Substitution is not allowed in non-'modifiable' buffer */
|
||||
EMSG(_(e_modifiable));
|
||||
return;
|
||||
|
@ -2235,6 +2235,11 @@ return {
|
||||
flags=bit.bor(NEEDARG, EXTRA, TRLBAR, NOTRLCOM, CMDWIN),
|
||||
func='ex_tearoff',
|
||||
},
|
||||
{
|
||||
command='terminal',
|
||||
flags=bit.bor(BANG, FILES, CMDWIN),
|
||||
func='ex_terminal',
|
||||
},
|
||||
{
|
||||
command='tfirst',
|
||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||
@ -2255,6 +2260,16 @@ return {
|
||||
flags=bit.bor(BANG, TRLBAR),
|
||||
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',
|
||||
flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||
@ -2265,6 +2280,11 @@ return {
|
||||
flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR),
|
||||
func='ex_tag',
|
||||
},
|
||||
{
|
||||
command='tnoremap',
|
||||
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||
func='ex_map',
|
||||
},
|
||||
{
|
||||
command='topleft',
|
||||
flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM),
|
||||
@ -2290,6 +2310,11 @@ return {
|
||||
flags=bit.bor(BANG, TRLBAR, WORD1),
|
||||
func='ex_tag',
|
||||
},
|
||||
{
|
||||
command='tunmap',
|
||||
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||
func='ex_unmap',
|
||||
},
|
||||
{
|
||||
command='tunmenu',
|
||||
flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN),
|
||||
|
@ -62,6 +62,7 @@
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/tag.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/version.h"
|
||||
@ -71,6 +72,8 @@
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/ex_cmds_defs.h"
|
||||
#include "nvim/mouse.h"
|
||||
#include "nvim/os/rstream.h"
|
||||
#include "nvim/os/wstream.h"
|
||||
|
||||
static int quitmore = 0;
|
||||
static int ex_pressedreturn = FALSE;
|
||||
@ -1510,7 +1513,9 @@ static char_u * do_one_cmd(char_u **cmdlinep,
|
||||
errormsg = (char_u *)_(e_sandbox);
|
||||
goto doend;
|
||||
}
|
||||
if (!curbuf->b_p_ma && (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 */
|
||||
errormsg = (char_u *)_(e_modifiable);
|
||||
goto doend;
|
||||
@ -2610,7 +2615,7 @@ set_one_cmd_context (
|
||||
xp->xp_context = EXPAND_FILES;
|
||||
|
||||
/* 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
|
||||
xp->xp_shell = TRUE;
|
||||
#endif
|
||||
@ -5126,8 +5131,10 @@ static void ex_quit(exarg_T *eap)
|
||||
|| (only_one_window() && check_changed_any(eap->forceit))) {
|
||||
not_exiting();
|
||||
} else {
|
||||
if (only_one_window()) /* quit last window */
|
||||
if (only_one_window()) {
|
||||
// quit last window
|
||||
getout(0);
|
||||
}
|
||||
/* close window; may free buffer */
|
||||
win_close(curwin, !P_HID(curwin->w_buffer) || eap->forceit);
|
||||
}
|
||||
@ -8060,7 +8067,9 @@ makeopens (
|
||||
/*
|
||||
* 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;
|
||||
if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL)
|
||||
return FAIL;
|
||||
@ -8269,7 +8278,7 @@ put_view (
|
||||
* Load the file.
|
||||
*/
|
||||
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".
|
||||
@ -8857,3 +8866,12 @@ static void ex_folddo(exarg_T *eap)
|
||||
global_exe(eap->arg);
|
||||
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 *reason;
|
||||
|
||||
/* If there is no file name, the buffer is not loaded, 'buftype' is
|
||||
* set, we are in the middle of a save or being called recursively: ignore
|
||||
* this buffer. */
|
||||
if (buf->b_ffname == NULL
|
||||
// If its a terminal, there is no file name, the buffer is not loaded,
|
||||
// 'buftype' is set, we are in the middle of a save or being called
|
||||
// recursively: ignore this buffer.
|
||||
if (buf->terminal
|
||||
|| buf->b_ffname == NULL
|
||||
|| buf->b_ml.ml_mfp == NULL
|
||||
|| *buf->b_p_bt != NUL
|
||||
|| buf->b_saving
|
||||
@ -5208,6 +5209,7 @@ static struct event_name {
|
||||
{"TabNew", EVENT_TABNEW},
|
||||
{"TabNewEntered", EVENT_TABNEWENTERED},
|
||||
{"TermChanged", EVENT_TERMCHANGED},
|
||||
{"TermOpen", EVENT_TERMOPEN},
|
||||
{"TermResponse", EVENT_TERMRESPONSE},
|
||||
{"TextChanged", EVENT_TEXTCHANGED},
|
||||
{"TextChangedI", EVENT_TEXTCHANGEDI},
|
||||
|
@ -100,6 +100,7 @@ typedef enum auto_event {
|
||||
EVENT_TABNEWENTERED, /* after entering a new tab */
|
||||
EVENT_SHELLCMDPOST, /* after ":!cmd" */
|
||||
EVENT_SHELLFILTERPOST, /* after ":1,2!cmd", ":w !cmd", ":r !cmd". */
|
||||
EVENT_TERMOPEN, // after opening a terminal buffer
|
||||
EVENT_TEXTCHANGED, /* text was modified */
|
||||
EVENT_TEXTCHANGEDI, /* text was modified in Insert mode*/
|
||||
EVENT_CMDUNDEFINED, ///< command undefined
|
||||
|
@ -137,7 +137,7 @@ void copyFoldingState(win_T *wp_from, win_T *wp_to)
|
||||
int hasAnyFolding(win_T *win)
|
||||
{
|
||||
/* 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));
|
||||
}
|
||||
|
||||
@ -768,6 +768,9 @@ void clearFolding(win_T *win)
|
||||
void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
|
||||
{
|
||||
fold_T *fp;
|
||||
if (wp->w_buffer->terminal) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Mark all folds from top to bot as maybe-small. */
|
||||
(void)foldFind(&curwin->w_folds, top, &fp);
|
||||
@ -1563,7 +1566,7 @@ static void setSmallMaybe(garray_T *gap)
|
||||
*/
|
||||
static void foldCreateMarkers(linenr_T start, linenr_T end)
|
||||
{
|
||||
if (!curbuf->b_p_ma) {
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
return;
|
||||
}
|
||||
|
@ -2538,6 +2538,7 @@ fix_input_buffer (
|
||||
* for :xmap mode is VISUAL
|
||||
* for :smap mode is SELECTMODE
|
||||
* for :omap mode is OP_PENDING
|
||||
* for :tmap mode is TERM_FOCUS
|
||||
*
|
||||
* for :abbr mode is INSERT + CMDLINE
|
||||
* for :iabbr mode is INSERT
|
||||
@ -3056,6 +3057,8 @@ int get_map_mode(char_u **cmdp, int forceit)
|
||||
mode = SELECTMODE; /* :smap */
|
||||
else if (modec == 'o')
|
||||
mode = OP_PENDING; /* :omap */
|
||||
else if (modec == 't')
|
||||
mode = TERM_FOCUS; // :tmap
|
||||
else {
|
||||
--p;
|
||||
if (forceit)
|
||||
@ -3923,6 +3926,9 @@ makemap (
|
||||
case LANGMAP:
|
||||
c1 = 'l';
|
||||
break;
|
||||
case TERM_FOCUS:
|
||||
c1 = 't';
|
||||
break;
|
||||
default:
|
||||
EMSG(_("E228: makemap: Illegal mode"));
|
||||
return FAIL;
|
||||
|
@ -154,4 +154,6 @@
|
||||
/// 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 RGB(r, g, b) ((r << 16) | (g << 8) | b)
|
||||
|
||||
#endif // NVIM_MACROS_H
|
||||
|
@ -285,6 +285,17 @@ int main(int argc, char **argv)
|
||||
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. */
|
||||
exe_pre_commands(¶ms);
|
||||
|
||||
|
@ -16,6 +16,8 @@
|
||||
#define uint64_t_eq kh_int64_hash_equal
|
||||
#define uint32_t_hash kh_int_hash_func
|
||||
#define uint32_t_eq kh_int_hash_equal
|
||||
#define int_hash kh_int_hash_func
|
||||
#define int_eq kh_int_hash_equal
|
||||
|
||||
#if defined(ARCH_64)
|
||||
#define ptr_t_hash(key) uint64_t_hash((uint64_t)key)
|
||||
@ -87,6 +89,11 @@
|
||||
} \
|
||||
\
|
||||
return rv; \
|
||||
} \
|
||||
\
|
||||
void map_##T##_##U##_clear(Map(T, U) *map) \
|
||||
{ \
|
||||
kh_clear(T##_##U##_map, map->table); \
|
||||
}
|
||||
|
||||
static inline khint_t String_hash(String s)
|
||||
@ -104,6 +111,7 @@ static inline bool String_eq(String a, String b)
|
||||
}
|
||||
|
||||
|
||||
MAP_IMPL(int, int, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER)
|
||||
MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER)
|
||||
|
@ -19,8 +19,10 @@
|
||||
U map_##T##_##U##_get(Map(T, U) *map, T key); \
|
||||
bool map_##T##_##U##_has(Map(T, U) *map, T key); \
|
||||
U map_##T##_##U##_put(Map(T, U) *map, T key, U value); \
|
||||
U map_##T##_##U##_del(Map(T, U) *map, T key);
|
||||
U map_##T##_##U##_del(Map(T, U) *map, T key); \
|
||||
void map_##T##_##U##_clear(Map(T, U) *map);
|
||||
|
||||
MAP_DECLS(int, int)
|
||||
MAP_DECLS(cstr_t, uint64_t)
|
||||
MAP_DECLS(cstr_t, ptr_t)
|
||||
MAP_DECLS(ptr_t, ptr_t)
|
||||
@ -33,6 +35,7 @@ MAP_DECLS(String, MsgpackRpcRequestHandler)
|
||||
#define map_has(T, U) map_##T##_##U##_has
|
||||
#define map_put(T, U) map_##T##_##U##_put
|
||||
#define map_del(T, U) map_##T##_##U##_del
|
||||
#define map_clear(T, U) map_##T##_##U##_clear
|
||||
|
||||
#define pmap_new(T) map_new(T, ptr_t)
|
||||
#define pmap_free(T) map_free(T, ptr_t)
|
||||
@ -40,6 +43,7 @@ MAP_DECLS(String, MsgpackRpcRequestHandler)
|
||||
#define pmap_has(T) map_has(T, ptr_t)
|
||||
#define pmap_put(T) map_put(T, ptr_t)
|
||||
#define pmap_del(T) map_del(T, ptr_t)
|
||||
#define pmap_clear(T) map_clear(T, ptr_t)
|
||||
|
||||
#define map_foreach(map, key, value, block) \
|
||||
kh_foreach(map->table, key, value, block)
|
||||
|
@ -278,10 +278,11 @@ int ml_open(buf_T *buf)
|
||||
/*
|
||||
* 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;
|
||||
else
|
||||
} else {
|
||||
buf->b_may_swap = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Open the memfile. No swap file is created yet.
|
||||
@ -488,7 +489,8 @@ void ml_open_file(buf_T *buf)
|
||||
char_u *dirp;
|
||||
|
||||
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 */
|
||||
}
|
||||
|
||||
|
@ -1465,7 +1465,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
|
||||
|
||||
/* Force a redraw when operating on an empty Visual region, when
|
||||
* 'modifiable is off or creating a fold. */
|
||||
if (oap->is_VIsual && (oap->empty || !curbuf->b_p_ma
|
||||
if (oap->is_VIsual && (oap->empty || !MODIFIABLE(curbuf)
|
||||
|| oap->op_type == OP_FOLD
|
||||
))
|
||||
redraw_curbuf_later(INVERTED);
|
||||
@ -5513,9 +5513,9 @@ static void nv_Replace(cmdarg_T *cap)
|
||||
VIsual_mode = 'V';
|
||||
nv_operator(cap);
|
||||
} else if (!checkclearopq(cap->oap)) {
|
||||
if (!curbuf->b_p_ma)
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
else {
|
||||
} else {
|
||||
if (virtual_active())
|
||||
coladvance(getviscol());
|
||||
invoke_edit(cap, false, cap->arg ? 'V' : 'R', false);
|
||||
@ -5533,9 +5533,9 @@ static void nv_vreplace(cmdarg_T *cap)
|
||||
cap->nchar = cap->extra_char;
|
||||
nv_replace(cap); /* Do same as "r" in Visual mode for now */
|
||||
} else if (!checkclearopq(cap->oap)) {
|
||||
if (!curbuf->b_p_ma)
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
else {
|
||||
} else {
|
||||
if (cap->extra_char == Ctrl_V) /* get another character */
|
||||
cap->extra_char = get_literal();
|
||||
stuffcharReadbuff(cap->extra_char);
|
||||
|
@ -45,6 +45,7 @@
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/search.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/window.h"
|
||||
@ -583,7 +584,7 @@ void op_reindent(oparg_T *oap, Indenter how)
|
||||
linenr_T start_lnum = curwin->w_cursor.lnum;
|
||||
|
||||
/* Don't even try when 'modifiable' is off. */
|
||||
if (!curbuf->b_p_ma) {
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
return;
|
||||
}
|
||||
@ -1329,7 +1330,7 @@ int op_delete(oparg_T *oap)
|
||||
if (oap->empty)
|
||||
return u_save_cursor();
|
||||
|
||||
if (!curbuf->b_p_ma) {
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
return FAIL;
|
||||
}
|
||||
@ -2643,11 +2644,13 @@ do_put (
|
||||
return;
|
||||
}
|
||||
|
||||
/* Autocommands may be executed when saving lines for undo, which may make
|
||||
* y_array invalid. Start undo now to avoid that. */
|
||||
if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) {
|
||||
ELOG(_("Failed to save undo information"));
|
||||
return;
|
||||
if (!curbuf->terminal) {
|
||||
// Autocommands may be executed when saving lines for undo, which may make
|
||||
// y_array invalid. Start undo now to avoid that.
|
||||
if (u_save(curwin->w_cursor.lnum, curwin->w_cursor.lnum + 1) == FAIL) {
|
||||
ELOG(_("Failed to save undo information"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (insert_string != NULL) {
|
||||
@ -2692,6 +2695,20 @@ do_put (
|
||||
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 (flags & PUT_LINE_SPLIT) {
|
||||
/* "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_ead_values[]) = {"both", "ver", "hor", NULL};
|
||||
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_bs_values[]) = {"indent", "eol", "start", NULL};
|
||||
static char *(p_fdm_values[]) = {"manual", "expr", "marker", "indent", "syntax",
|
||||
@ -3748,7 +3748,7 @@ did_set_string_option (
|
||||
/* 'encoding' and 'fileencoding' */
|
||||
else if (varp == &p_enc || gvarp == &p_fenc || varp == &p_tenc) {
|
||||
if (gvarp == &p_fenc) {
|
||||
if (!curbuf->b_p_ma && opt_flags != OPT_GLOBAL)
|
||||
if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL)
|
||||
errmsg = e_modifiable;
|
||||
else if (vim_strchr(*varp, ',') != NULL)
|
||||
/* No comma allowed in 'fileencoding'; catches confusing it
|
||||
@ -3819,7 +3819,7 @@ did_set_string_option (
|
||||
}
|
||||
/* 'fileformat' */
|
||||
else if (gvarp == &p_ff) {
|
||||
if (!curbuf->b_p_ma && !(opt_flags & OPT_GLOBAL))
|
||||
if (!MODIFIABLE(curbuf) && !(opt_flags & OPT_GLOBAL))
|
||||
errmsg = e_modifiable;
|
||||
else if (check_opt_strings(*varp, p_ff_values, FALSE) != OK)
|
||||
errmsg = e_invarg;
|
||||
@ -4097,9 +4097,11 @@ did_set_string_option (
|
||||
}
|
||||
/* When 'buftype' is set, check for valid value. */
|
||||
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;
|
||||
else {
|
||||
} else {
|
||||
if (curwin->w_status_height) {
|
||||
curwin->w_redr_status = TRUE;
|
||||
redraw_later(VALID);
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include "nvim/misc2.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/terminal.h"
|
||||
|
||||
#include "nvim/lib/klist.h"
|
||||
|
||||
@ -63,6 +64,7 @@ void event_init(void)
|
||||
// finish mspgack-rpc initialization
|
||||
channel_init();
|
||||
server_init();
|
||||
terminal_init();
|
||||
}
|
||||
|
||||
void event_teardown(void)
|
||||
@ -74,11 +76,16 @@ void event_teardown(void)
|
||||
|
||||
process_events_from(immediate_events);
|
||||
process_events_from(deferred_events);
|
||||
// reset the stop_flag to ensure `uv_run` below won't exit early. This hack
|
||||
// is required because the `process_events_from` above may call `event_push`
|
||||
// which will set the stop_flag to 1(uv_stop)
|
||||
uv_default_loop()->stop_flag = 0;
|
||||
input_stop_stdin();
|
||||
channel_teardown();
|
||||
job_teardown();
|
||||
server_teardown();
|
||||
signal_teardown();
|
||||
terminal_teardown();
|
||||
// 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
|
||||
// functions.
|
||||
@ -153,17 +160,18 @@ void event_disable_deferred(void)
|
||||
// Queue an event
|
||||
void event_push(Event event, bool deferred)
|
||||
{
|
||||
// Sometimes libuv will run pending callbacks(timer for example) before
|
||||
// blocking for a poll. If this happens and the callback pushes a event to one
|
||||
// of the queues, the event would only be processed after the poll
|
||||
// returns(user hits a key for example). To avoid this scenario, we call
|
||||
// uv_stop when a event is enqueued.
|
||||
uv_stop(uv_default_loop());
|
||||
*kl_pushp(Event, deferred ? deferred_events : immediate_events) = event;
|
||||
}
|
||||
|
||||
void event_process(void)
|
||||
{
|
||||
process_events_from(deferred_events);
|
||||
|
||||
if (must_redraw) {
|
||||
update_screen(0);
|
||||
ui_flush();
|
||||
}
|
||||
}
|
||||
|
||||
static void process_events_from(klist_t(Event) *queue)
|
||||
|
@ -406,6 +406,12 @@ static void close_cb(uv_handle_t *handle)
|
||||
job_decref(handle_get_job(handle));
|
||||
}
|
||||
|
||||
static void job_exited(Event event)
|
||||
{
|
||||
Job *job = event.data;
|
||||
process_close(job);
|
||||
}
|
||||
|
||||
static void chld_handler(uv_signal_t *handle, int signum)
|
||||
{
|
||||
int stat = 0;
|
||||
@ -433,7 +439,12 @@ static void chld_handler(uv_signal_t *handle, int signum)
|
||||
} else if (WIFSIGNALED(stat)) {
|
||||
job->status = WTERMSIG(stat);
|
||||
}
|
||||
process_close(job);
|
||||
if (exiting) {
|
||||
// don't enqueue more events when exiting
|
||||
process_close(job);
|
||||
} else {
|
||||
event_push((Event) {.handler = job_exited, .data = job}, false);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -2377,7 +2377,6 @@ static void qf_fill_buffer(qf_info_T *qi)
|
||||
KeyTyped = old_KeyTyped;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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 TRUE if "buf" is a "nofile" or "acwrite" buffer.
|
||||
* This means the buffer name is not a file name.
|
||||
*/
|
||||
// Return TRUE if "buf" is a "nofile", "acwrite" or "terminal" buffer.
|
||||
// This means the buffer name is not a file name.
|
||||
int bt_nofile(buf_T *buf)
|
||||
{
|
||||
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" or "nofile" buffer.
|
||||
*/
|
||||
// Return TRUE if "buf" is a "nowrite", "nofile" or "terminal" buffer.
|
||||
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)
|
||||
|
@ -131,6 +131,7 @@
|
||||
#include "nvim/spell.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/undo.h"
|
||||
#include "nvim/version.h"
|
||||
@ -2210,7 +2211,7 @@ win_line (
|
||||
}
|
||||
|
||||
/* 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)
|
||||
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
|
||||
|
||||
@ -2592,6 +2593,13 @@ win_line (
|
||||
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.
|
||||
*/
|
||||
@ -3213,6 +3221,8 @@ win_line (
|
||||
syntax_flags = 0;
|
||||
else
|
||||
syntax_flags = get_syntax_info(&syntax_seqnr);
|
||||
} else if (!attr_pri) {
|
||||
char_attr = 0;
|
||||
}
|
||||
|
||||
/* Check spelling (unless at the end of the line).
|
||||
@ -3290,6 +3300,11 @@ win_line (
|
||||
else
|
||||
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.
|
||||
*/
|
||||
@ -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);
|
||||
row++;
|
||||
|
||||
@ -6536,7 +6563,8 @@ int showmode(void)
|
||||
int sub_attr;
|
||||
|
||||
do_mode = ((p_smd && msg_silent == 0)
|
||||
&& ((State & INSERT)
|
||||
&& ((State & TERM_FOCUS)
|
||||
|| (State & INSERT)
|
||||
|| restart_edit
|
||||
|| VIsual_active
|
||||
));
|
||||
@ -6591,7 +6619,9 @@ int showmode(void)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (State & VREPLACE_FLAG)
|
||||
if (State & TERM_FOCUS) {
|
||||
MSG_PUTS_ATTR(_(" TERMINAL"), attr);
|
||||
} else if (State & VREPLACE_FLAG)
|
||||
MSG_PUTS_ATTR(_(" VREPLACE"), attr);
|
||||
else if (State & REPLACE_FLAG)
|
||||
MSG_PUTS_ATTR(_(" REPLACE"), attr);
|
||||
|
@ -40,10 +40,12 @@
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/os_unix.h"
|
||||
#include "nvim/path.h"
|
||||
#include "nvim/macros.h"
|
||||
#include "nvim/regexp.h"
|
||||
#include "nvim/screen.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax_defs.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/ui.h"
|
||||
#include "nvim/os/os.h"
|
||||
#include "nvim/os/time.h"
|
||||
@ -6636,7 +6638,7 @@ static garray_T attr_table = GA_EMPTY_INIT_VALUE;
|
||||
* if the combination is new.
|
||||
* Return 0 for error.
|
||||
*/
|
||||
static int get_attr_entry(attrentry_T *aep)
|
||||
int get_attr_entry(attrentry_T *aep)
|
||||
{
|
||||
garray_T *table = &attr_table;
|
||||
attrentry_T *taep;
|
||||
@ -6723,6 +6725,10 @@ int hl_combine_attr(int char_attr, int prim_attr)
|
||||
return prim_attr;
|
||||
}
|
||||
|
||||
if (prim_attr == 0) {
|
||||
return char_attr;
|
||||
}
|
||||
|
||||
// Find the entry for char_attr
|
||||
char_aep = syn_cterm_attr2entry(char_attr);
|
||||
|
||||
@ -7420,7 +7426,6 @@ char_u *get_highlight_name(expand_T *xp, int idx)
|
||||
return HL_TABLE()[idx].sg_name;
|
||||
}
|
||||
|
||||
#define RGB(r, g, b) ((r << 16) | (g << 8) | b)
|
||||
color_name_table_T color_name_table[] = {
|
||||
// Color names taken from
|
||||
// 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
|
@ -23,6 +23,7 @@
|
||||
#include "nvim/normal.h"
|
||||
#include "nvim/option.h"
|
||||
#include "nvim/os_unix.h"
|
||||
#include "nvim/os/event.h"
|
||||
#include "nvim/os/time.h"
|
||||
#include "nvim/os/input.h"
|
||||
#include "nvim/os/signal.h"
|
||||
@ -208,10 +209,8 @@ void ui_detach(UI *ui)
|
||||
}
|
||||
|
||||
ui_count--;
|
||||
|
||||
if (ui_count) {
|
||||
ui_refresh();
|
||||
}
|
||||
// schedule a refresh
|
||||
event_push((Event) { .handler = refresh }, false);
|
||||
}
|
||||
|
||||
void ui_clear(void)
|
||||
@ -486,3 +485,10 @@ static void ui_change_mode(void)
|
||||
conceal_check_cursur_line();
|
||||
}
|
||||
|
||||
static void refresh(Event event)
|
||||
{
|
||||
if (ui_count) {
|
||||
ui_refresh();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,7 +289,7 @@ int u_savedel(linenr_T lnum, long nlines)
|
||||
int undo_allowed(void)
|
||||
{
|
||||
/* Don't allow changes when 'modifiable' is off. */
|
||||
if (!curbuf->b_p_ma) {
|
||||
if (!MODIFIABLE(curbuf)) {
|
||||
EMSG(_(e_modifiable));
|
||||
return FALSE;
|
||||
}
|
||||
@ -315,6 +315,9 @@ int undo_allowed(void)
|
||||
*/
|
||||
static long get_undolevel(void)
|
||||
{
|
||||
if (curbuf->terminal) {
|
||||
return -1;
|
||||
}
|
||||
if (curbuf->b_p_ul == NO_LOCAL_UNDOLEVEL)
|
||||
return 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 CONFIRM 0x800 /* ":confirm" prompt */
|
||||
#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
|
||||
* mapping */
|
||||
// all mode bits used for mapping
|
||||
#define MAP_ALL_MODES (0x3f | SELECTMODE | TERM_FOCUS)
|
||||
|
||||
/* directions */
|
||||
#define FORWARD 1
|
||||
@ -321,11 +322,6 @@ enum {
|
||||
#define fnamencmp(x, y, n) vim_fnamencmp((char_u *)(x), (char_u *)(y), \
|
||||
(size_t)(n))
|
||||
|
||||
#ifndef EINTR
|
||||
# define read_eintr(fd, buf, count) read((fd), (buf), (count))
|
||||
# define write_eintr(fd, buf, count) write((fd), (buf), (count))
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Enums need a typecast to be used as array index (for Ultrix).
|
||||
*/
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include "nvim/search.h"
|
||||
#include "nvim/strings.h"
|
||||
#include "nvim/syntax.h"
|
||||
#include "nvim/terminal.h"
|
||||
#include "nvim/undo.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;
|
||||
|
||||
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
|
||||
* 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())
|
||||
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
|
||||
* that now. */
|
||||
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))
|
||||
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
|
||||
* the screen space. */
|
||||
wp = win_free_mem(win, &dir, NULL);
|
||||
@ -1963,7 +1979,6 @@ int win_close(win_T *win, int free_buf)
|
||||
if (help_window)
|
||||
restore_snapshot(SNAP_HELP_IDX, close_curwin);
|
||||
|
||||
|
||||
redraw_all_later(NOT_VALID);
|
||||
return OK;
|
||||
}
|
||||
@ -4689,6 +4704,11 @@ void win_new_height(win_T *wp, int height)
|
||||
redraw_win_later(wp, SOME_VALID);
|
||||
wp->w_redr_status = TRUE;
|
||||
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);
|
||||
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)
|
||||
@ -5570,4 +5595,3 @@ static int frame_check_width(frame_T *topfrp, int width)
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
@ -174,24 +174,14 @@ describe('jobs', function()
|
||||
|
||||
it('echoing input', function()
|
||||
send('test')
|
||||
-- the tty driver will echo input by default
|
||||
eq('test', next_chunk())
|
||||
end)
|
||||
|
||||
it('resizing window', function()
|
||||
nvim('command', 'call jobresize(j, 40, 10)')
|
||||
eq('screen resized. rows: 10, columns: 40', next_chunk())
|
||||
eq('rows: 10, cols: 40', next_chunk())
|
||||
nvim('command', 'call jobresize(j, 10, 40)')
|
||||
eq('screen resized. rows: 40, columns: 10', next_chunk())
|
||||
end)
|
||||
|
||||
-- FIXME This test is flawed because there is no telling when the OS will send chunks of data.
|
||||
pending('preprocessing ctrl+c with terminal driver', function()
|
||||
send('\\<c-c>')
|
||||
eq('^Cinterrupt received, press again to exit', next_chunk())
|
||||
send('\\<c-c>')
|
||||
eq('^Ctty done', next_chunk())
|
||||
eq({'notification', 'exit', {0}}, next_message())
|
||||
eq('rows: 40, cols: 10', next_chunk())
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include <stdlib.h>
|
||||
#include <uv.h>
|
||||
|
||||
uv_tty_t tty;
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
bool owns_tty(void)
|
||||
@ -13,10 +15,10 @@ bool owns_tty(void)
|
||||
return GetCurrentProcessId() == dwProcessId;
|
||||
}
|
||||
#else
|
||||
#include <unistd.h>
|
||||
bool owns_tty(void)
|
||||
{
|
||||
// TODO: Check if the process is the session leader
|
||||
return true;
|
||||
return getsid(0) == getpid();
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -29,26 +31,20 @@ static void walk_cb(uv_handle_t *handle, void *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
static void sigwinch_cb(uv_signal_t *handle, int signum)
|
||||
static void sigwinch_handler(int signum)
|
||||
{
|
||||
int width, height;
|
||||
uv_tty_t *tty = handle->data;
|
||||
uv_tty_get_winsize(tty, &width, &height);
|
||||
fprintf(stderr, "screen resized. rows: %d, columns: %d\n", height, width);
|
||||
uv_tty_get_winsize(&tty, &width, &height);
|
||||
fprintf(stderr, "rows: %d, cols: %d\n", height, width);
|
||||
}
|
||||
|
||||
static void sigint_cb(uv_signal_t *handle, int signum)
|
||||
{
|
||||
bool *interrupted = handle->data;
|
||||
|
||||
if (*interrupted) {
|
||||
uv_walk(uv_default_loop(), walk_cb, NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
*interrupted = true;
|
||||
fprintf(stderr, "interrupt received, press again to exit\n");
|
||||
}
|
||||
// static void sigwinch_cb(uv_signal_t *handle, int signum)
|
||||
// {
|
||||
// int width, height;
|
||||
// uv_tty_t *tty = handle->data;
|
||||
// uv_tty_get_winsize(tty, &width, &height);
|
||||
// fprintf(stderr, "rows: %d, cols: %d\n", height, width);
|
||||
// }
|
||||
|
||||
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
|
||||
{
|
||||
@ -63,13 +59,20 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
|
||||
return;
|
||||
}
|
||||
|
||||
fprintf(stderr, "received data: ");
|
||||
int *interrupted = stream->data;
|
||||
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
if (buf->base[i] == 3) {
|
||||
(*interrupted)++;
|
||||
}
|
||||
}
|
||||
|
||||
uv_loop_t write_loop;
|
||||
uv_loop_init(&write_loop);
|
||||
uv_tty_t out;
|
||||
uv_tty_init(&write_loop, &out, 1, 0);
|
||||
uv_write_t req;
|
||||
uv_buf_t b = {.base = buf->base, .len = buf->len};
|
||||
uv_buf_t b = {.base = buf->base, .len = (size_t)cnt};
|
||||
uv_write(&req, (uv_stream_t *)&out, &b, 1, NULL);
|
||||
uv_run(&write_loop, UV_RUN_DEFAULT);
|
||||
uv_close((uv_handle_t *)&out, NULL);
|
||||
@ -78,6 +81,12 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
|
||||
abort();
|
||||
}
|
||||
free(buf->base);
|
||||
|
||||
if (*interrupted >= 2) {
|
||||
uv_walk(uv_default_loop(), walk_cb, NULL);
|
||||
} else if (*interrupted == 1) {
|
||||
fprintf(stderr, "interrupt received, press again to exit\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void prepare_cb(uv_prepare_t *handle)
|
||||
@ -88,6 +97,11 @@ static void prepare_cb(uv_prepare_t *handle)
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (!owns_tty()) {
|
||||
fprintf(stderr, "process does not own the terminal\n");
|
||||
exit(2);
|
||||
}
|
||||
|
||||
if (!is_terminal(stdin)) {
|
||||
fprintf(stderr, "stdin is not a terminal\n");
|
||||
exit(2);
|
||||
@ -103,20 +117,34 @@ int main(int argc, char **argv)
|
||||
exit(2);
|
||||
}
|
||||
|
||||
bool interrupted = false;
|
||||
if (argc > 1) {
|
||||
int count = atoi(argv[1]);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
printf("line%d\n", i);
|
||||
}
|
||||
fflush(stdout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int interrupted = 0;
|
||||
uv_prepare_t prepare;
|
||||
uv_prepare_init(uv_default_loop(), &prepare);
|
||||
uv_prepare_start(&prepare, prepare_cb);
|
||||
uv_tty_t tty;
|
||||
// uv_tty_t tty;
|
||||
uv_tty_init(uv_default_loop(), &tty, fileno(stderr), 1);
|
||||
uv_tty_set_mode(&tty, UV_TTY_MODE_RAW);
|
||||
tty.data = &interrupted;
|
||||
uv_read_start((uv_stream_t *)&tty, alloc_cb, read_cb);
|
||||
uv_signal_t sigwinch_watcher, sigint_watcher;
|
||||
uv_signal_init(uv_default_loop(), &sigwinch_watcher);
|
||||
sigwinch_watcher.data = &tty;
|
||||
uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
|
||||
uv_signal_init(uv_default_loop(), &sigint_watcher);
|
||||
sigint_watcher.data = &interrupted;
|
||||
uv_signal_start(&sigint_watcher, sigint_cb, SIGINT);
|
||||
struct sigaction sa;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
sa.sa_flags = 0;
|
||||
sa.sa_handler = sigwinch_handler;
|
||||
sigaction(SIGWINCH, &sa, NULL);
|
||||
// uv_signal_t sigwinch_watcher;
|
||||
// uv_signal_init(uv_default_loop(), &sigwinch_watcher);
|
||||
// sigwinch_watcher.data = &tty;
|
||||
// uv_signal_start(&sigwinch_watcher, sigwinch_cb, SIGWINCH);
|
||||
uv_run(uv_default_loop(), UV_RUN_DEFAULT);
|
||||
fprintf(stderr, "tty done\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
158
test/functional/terminal/altscreen_spec.lua
Normal file
158
test/functional/terminal/altscreen_spec.lua
Normal file
@ -0,0 +1,158 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
|
||||
local feed = helpers.feed
|
||||
local feed_data = thelpers.feed_data
|
||||
local enter_altscreen = thelpers.enter_altscreen
|
||||
local exit_altscreen = thelpers.exit_altscreen
|
||||
|
||||
describe('terminal altscreen', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = thelpers.screen_setup()
|
||||
feed_data({'line1', 'line2', 'line3', 'line4', 'line5', 'line6',
|
||||
'line7', 'line8', ''})
|
||||
screen:expect([[
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
line8 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
enter_altscreen()
|
||||
screen:expect([[
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(10, curbuf('line_count'))
|
||||
end)
|
||||
|
||||
it('wont clear lines already in the scrollback', function()
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
|
|
||||
|
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('on exit', function()
|
||||
before_each(exit_altscreen)
|
||||
|
||||
it('restores buffer state', function()
|
||||
screen:expect([[
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
line8 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with lines printed after the screen height limit', function()
|
||||
before_each(function()
|
||||
feed_data({'line9', 'line10', 'line11', 'line12', 'line13',
|
||||
'line14', 'line15', 'line16', ''})
|
||||
screen:expect([[
|
||||
line12 |
|
||||
line13 |
|
||||
line14 |
|
||||
line15 |
|
||||
line16 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('wont modify line count', function()
|
||||
eq(10, curbuf('line_count'))
|
||||
end)
|
||||
|
||||
it('wont modify lines in the scrollback', function()
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line12 |
|
||||
line13 |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('after height is decreased by 2', function()
|
||||
local function wait_removal()
|
||||
screen:try_resize(screen._width, screen._height - 2)
|
||||
screen:expect([[
|
||||
|
|
||||
|
|
||||
rows: 4, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end
|
||||
|
||||
it('removes 2 lines from the bottom of the visible buffer', function()
|
||||
wait_removal()
|
||||
feed('<c-\\><c-n>4k')
|
||||
screen:expect([[
|
||||
^line3 |
|
||||
|
|
||||
|
|
||||
rows: 4, cols: 50 |
|
||||
|
|
||||
]])
|
||||
eq(8, curbuf('line_count'))
|
||||
end)
|
||||
|
||||
describe('and after exit', function()
|
||||
before_each(function()
|
||||
wait_removal()
|
||||
exit_altscreen()
|
||||
end)
|
||||
|
||||
it('restore buffer state', function()
|
||||
-- FIXME(tarruda): Note that the last line was lost after restoring the
|
||||
-- screen. This is a libvterm bug: When the main screen is restored it
|
||||
-- seems to "cut" lines that would have been left below the new visible
|
||||
-- screen.
|
||||
screen:expect([[
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
159
test/functional/terminal/buffer_spec.lua
Normal file
159
test/functional/terminal/buffer_spec.lua
Normal file
@ -0,0 +1,159 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
|
||||
local wait, execute, eq = helpers.wait, helpers.execute, helpers.eq
|
||||
|
||||
|
||||
describe('terminal buffer', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
execute('set modifiable swapfile undolevels=20')
|
||||
wait()
|
||||
screen = thelpers.screen_setup()
|
||||
end)
|
||||
|
||||
describe('when a new file is edited', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>:set bufhidden=wipe<cr>:enew<cr>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:enew |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will hide the buffer, ignoring the bufhidden option', function()
|
||||
feed(':bnext:l<esc>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('swap and undo', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
it('does not create swap files', function()
|
||||
local swapfile = nvim('command_output', 'swapname'):gsub('\n', '')
|
||||
eq(nil, io.open(swapfile))
|
||||
end)
|
||||
|
||||
it('does not create undofiles files', function()
|
||||
local undofile = nvim('eval', 'undofile(bufname("%"))')
|
||||
eq(nil, io.open(undofile))
|
||||
end)
|
||||
end)
|
||||
|
||||
it('cannot be modified directly', function()
|
||||
feed('<c-\\><c-n>dd')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
E21: Cannot make changes, 'modifiable' is off |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('sends data to the terminal when the "put" operator is used', function()
|
||||
feed('<c-\\><c-n>gg"ayj')
|
||||
execute('let @a = "appended " . @a')
|
||||
feed('"ap"ap')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
:let @a = "appended " . @a |
|
||||
]])
|
||||
-- operator count is also taken into consideration
|
||||
feed('3"ap')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
:let @a = "appended " . @a |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('sends data to the terminal when the ":put" command is used', function()
|
||||
feed('<c-\\><c-n>gg"ayj')
|
||||
execute('let @a = "appended " . @a')
|
||||
execute('put a')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
appended tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
:put a |
|
||||
]])
|
||||
-- line argument is only used to move the cursor
|
||||
execute('6put a')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
appended tty ready |
|
||||
appended tty ready |
|
||||
{2: } |
|
||||
|
|
||||
^ |
|
||||
:6put a |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('can be deleted', function()
|
||||
feed('<c-\\><c-n>:bd!<cr>')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:bd! |
|
||||
]])
|
||||
execute('bnext')
|
||||
screen:expect([[
|
||||
^ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
~ |
|
||||
:bnext |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
174
test/functional/terminal/cursor_spec.lua
Normal file
174
test/functional/terminal/cursor_spec.lua
Normal file
@ -0,0 +1,174 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
|
||||
local nvim_dir, execute, eq = helpers.nvim_dir, helpers.execute, helpers.eq
|
||||
local hide_cursor = thelpers.hide_cursor
|
||||
local show_cursor = thelpers.show_cursor
|
||||
|
||||
|
||||
describe('terminal cursor', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = thelpers.screen_setup()
|
||||
end)
|
||||
|
||||
|
||||
it('moves the screen cursor when focused', function()
|
||||
thelpers.feed_data('testing cursor')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
testing cursor{1: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(2, screen._cursor.row)
|
||||
eq(15, screen._cursor.col)
|
||||
end)
|
||||
|
||||
it('is highlighted when not focused', function()
|
||||
feed('<c-\\><c-n>')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('with number column', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>:set number<cr>')
|
||||
end)
|
||||
|
||||
it('is positioned correctly when unfocused', function()
|
||||
screen:expect([[
|
||||
1 tty ready |
|
||||
2 {2: } |
|
||||
3 |
|
||||
4 |
|
||||
5 |
|
||||
6 ^ |
|
||||
:set number |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('is positioned correctly when focused', function()
|
||||
feed('i')
|
||||
screen:expect([[
|
||||
1 tty ready |
|
||||
2 {1: } |
|
||||
3 |
|
||||
4 |
|
||||
5 |
|
||||
6 |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('when invisible', function()
|
||||
it('is not highlighted and is detached from screen cursor', function()
|
||||
hide_cursor()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
show_cursor()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{1: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
-- same for when the terminal is unfocused
|
||||
feed('<c-\\><c-n>')
|
||||
hide_cursor()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
show_cursor()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
describe('cursor with customized highlighting', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
nvim('set_var', 'terminal_focused_cursor_highlight', 'CursorFocused')
|
||||
nvim('set_var', 'terminal_unfocused_cursor_highlight', 'CursorUnfocused')
|
||||
nvim('command', 'highlight CursorFocused ctermfg=45 ctermbg=46')
|
||||
nvim('command', 'highlight CursorUnfocused ctermfg=55 ctermbg=56')
|
||||
screen = Screen.new(50, 7)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {foreground = 45, background = 46},
|
||||
[2] = {foreground = 55, background = 56}
|
||||
})
|
||||
screen:set_default_attr_ignore({
|
||||
[1] = {bold = true},
|
||||
[2] = {foreground = 12},
|
||||
[3] = {bold = true, reverse = true},
|
||||
[5] = {background = 11},
|
||||
[6] = {foreground = 130},
|
||||
})
|
||||
screen:attach(false)
|
||||
execute('term "' ..nvim_dir.. '/tty-test"')
|
||||
end)
|
||||
|
||||
it('overrides the default highlighting', function()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{1: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
feed('<c-\\><c-n>')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{2: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
^ |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
95
test/functional/terminal/helpers.lua
Normal file
95
test/functional/terminal/helpers.lua
Normal file
@ -0,0 +1,95 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local nvim_dir = helpers.nvim_dir
|
||||
local execute, nvim = helpers.execute, helpers.nvim
|
||||
|
||||
local function feed_data(data)
|
||||
nvim('set_var', 'term_data', data)
|
||||
nvim('command', 'call jobsend(b:terminal_job_id, term_data)')
|
||||
end
|
||||
|
||||
local function feed_termcode(data)
|
||||
-- feed with the job API
|
||||
nvim('command', 'call jobsend(b:terminal_job_id, "\\x1b'..data..'")')
|
||||
end
|
||||
-- some helpers for controlling the terminal. the codes were taken from
|
||||
-- infocmp xterm-256color which is less what libvterm understands
|
||||
-- civis/cnorm
|
||||
local function hide_cursor() feed_termcode('[?25l') end
|
||||
local function show_cursor() feed_termcode('[?25h') end
|
||||
-- smcup/rmcup
|
||||
local function enter_altscreen() feed_termcode('[?1049h') end
|
||||
local function exit_altscreen() feed_termcode('[?1049l') end
|
||||
-- character attributes
|
||||
local function set_fg(num) feed_termcode('[38;5;'..num..'m') end
|
||||
local function set_bg(num) feed_termcode('[48;5;'..num..'m') end
|
||||
local function set_bold() feed_termcode('[1m') end
|
||||
local function set_italic() feed_termcode('[3m') end
|
||||
local function set_underline() feed_termcode('[4m') end
|
||||
local function clear_attrs() feed_termcode('[0;10m') end
|
||||
-- mouse
|
||||
local function enable_mouse() feed_termcode('[?1002h') end
|
||||
local function disable_mouse() feed_termcode('[?1002l') end
|
||||
|
||||
|
||||
|
||||
local function screen_setup(extra_height)
|
||||
nvim('set_var', 'terminal_scrollback_buffer_size', 10)
|
||||
if not extra_height then extra_height = 0 end
|
||||
local screen = Screen.new(50, 7 + extra_height)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {reverse = true}, -- focused cursor
|
||||
[2] = {background = 11}, -- unfocused cursor
|
||||
})
|
||||
screen:set_default_attr_ignore({
|
||||
[1] = {bold = true},
|
||||
[2] = {foreground = 12},
|
||||
[3] = {bold = true, reverse = true},
|
||||
[5] = {background = 11},
|
||||
[6] = {foreground = 130},
|
||||
[7] = {foreground = 15, background = 1}, -- error message
|
||||
})
|
||||
|
||||
screen:attach(false)
|
||||
-- tty-test puts the terminal into raw mode and echoes all input. tests are
|
||||
-- done by feeding it with terminfo codes to control the display and
|
||||
-- verifying output with screen:expect.
|
||||
execute('term ' ..nvim_dir.. '/tty-test')
|
||||
-- wait for "tty ready" to be printed before each test or the terminal may
|
||||
-- still be in canonical mode(will echo characters for example)
|
||||
--
|
||||
local empty_line = ' '
|
||||
local expected = {
|
||||
'tty ready ',
|
||||
'{1: } ',
|
||||
empty_line,
|
||||
empty_line,
|
||||
empty_line,
|
||||
empty_line,
|
||||
}
|
||||
for i = 1, extra_height do
|
||||
table.insert(expected, empty_line)
|
||||
end
|
||||
|
||||
table.insert(expected, '-- TERMINAL -- ')
|
||||
screen:expect(table.concat(expected, '\n'))
|
||||
return screen
|
||||
end
|
||||
|
||||
return {
|
||||
feed_data = feed_data,
|
||||
feed_termcode = feed_termcode,
|
||||
hide_cursor = hide_cursor,
|
||||
show_cursor = show_cursor,
|
||||
enter_altscreen = enter_altscreen,
|
||||
exit_altscreen = exit_altscreen,
|
||||
set_fg = set_fg,
|
||||
set_bg = set_bg,
|
||||
set_bold = set_bold,
|
||||
set_italic = set_italic,
|
||||
set_underline = set_underline,
|
||||
clear_attrs = clear_attrs,
|
||||
enable_mouse = enable_mouse,
|
||||
disable_mouse = disable_mouse,
|
||||
screen_setup = screen_setup
|
||||
}
|
163
test/functional/terminal/highlight_spec.lua
Normal file
163
test/functional/terminal/highlight_spec.lua
Normal file
@ -0,0 +1,163 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
|
||||
local nvim_dir, execute = helpers.nvim_dir, helpers.execute
|
||||
|
||||
|
||||
describe('terminal window highlighting', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(50, 7)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {foreground = 45},
|
||||
[2] = {background = 46},
|
||||
[3] = {foreground = 45, background = 46},
|
||||
[4] = {bold = true, italic = true, underline = true}
|
||||
})
|
||||
screen:set_default_attr_ignore({
|
||||
[1] = {bold = true},
|
||||
[2] = {foreground = 12},
|
||||
[3] = {bold = true, reverse = true},
|
||||
[5] = {background = 11},
|
||||
[6] = {foreground = 130},
|
||||
[7] = {reverse = true},
|
||||
[8] = {background = 11}
|
||||
})
|
||||
screen:attach(false)
|
||||
execute('term "' ..nvim_dir.. '/tty-test"')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
function descr(title, attr_num, set_attrs_fn)
|
||||
local function sub(s)
|
||||
return s:gsub('NUM', attr_num)
|
||||
end
|
||||
|
||||
describe(title, function()
|
||||
before_each(function()
|
||||
set_attrs_fn()
|
||||
thelpers.feed_data('text')
|
||||
thelpers.clear_attrs()
|
||||
thelpers.feed_data('text')
|
||||
end)
|
||||
|
||||
local function pass_attrs()
|
||||
local s = sub([[
|
||||
tty ready |
|
||||
{NUM:text}text |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
screen:expect(s)
|
||||
end
|
||||
|
||||
it('will pass the corresponding attributes', pass_attrs)
|
||||
|
||||
it('will pass the corresponding attributes on scrollback', function()
|
||||
pass_attrs()
|
||||
local lines = {}
|
||||
for i = 1, 8 do
|
||||
table.insert(lines, 'line'..tostring(i))
|
||||
end
|
||||
table.insert(lines, '')
|
||||
thelpers.feed_data(lines)
|
||||
screen:expect([[
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
line8 |
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
feed('<c-\\><c-n>gg')
|
||||
local s = sub([[
|
||||
^tty ready |
|
||||
{NUM:text}textline1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
|
|
||||
]])
|
||||
screen:expect(s)
|
||||
end)
|
||||
end)
|
||||
end
|
||||
|
||||
descr('foreground', 1, function() thelpers.set_fg(45) end)
|
||||
descr('background', 2, function() thelpers.set_bg(46) end)
|
||||
descr('foreground and background', 3, function()
|
||||
thelpers.set_fg(45)
|
||||
thelpers.set_bg(46)
|
||||
end)
|
||||
descr('bold, italics and underline', 4, function()
|
||||
thelpers.set_bold()
|
||||
thelpers.set_italic()
|
||||
thelpers.set_underline()
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
describe('terminal window highlighting with custom palette', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = Screen.new(50, 7)
|
||||
screen:set_default_attr_ids({
|
||||
[1] = {foreground = 1193046}
|
||||
})
|
||||
screen:set_default_attr_ignore({
|
||||
[1] = {bold = true},
|
||||
[2] = {foreground = 12},
|
||||
[3] = {bold = true, reverse = true},
|
||||
[5] = {background = 11},
|
||||
[6] = {foreground = 130},
|
||||
[7] = {reverse = true},
|
||||
[8] = {background = 11}
|
||||
})
|
||||
screen:attach(true)
|
||||
nvim('set_var', 'terminal_color_3', '#123456')
|
||||
execute('term "' ..nvim_dir.. '/tty-test"')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will use the custom color', function()
|
||||
thelpers.set_fg(3)
|
||||
thelpers.feed_data('text')
|
||||
thelpers.clear_attrs()
|
||||
thelpers.feed_data('text')
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{1:text}text |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
188
test/functional/terminal/mouse_spec.lua
Normal file
188
test/functional/terminal/mouse_spec.lua
Normal file
@ -0,0 +1,188 @@
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local helpers = require('test.functional.helpers')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
|
||||
local feed, execute, nvim = helpers.feed, helpers.execute, helpers.nvim
|
||||
local feed_data = thelpers.feed_data
|
||||
|
||||
describe('terminal mouse', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
nvim('set_option', 'statusline', '==========')
|
||||
nvim('command', 'highlight StatusLine cterm=NONE')
|
||||
nvim('command', 'highlight StatusLineNC cterm=NONE')
|
||||
nvim('command', 'highlight VertSplit cterm=NONE')
|
||||
screen = thelpers.screen_setup()
|
||||
local lines = {}
|
||||
for i = 1, 30 do
|
||||
table.insert(lines, 'line'..tostring(i))
|
||||
end
|
||||
table.insert(lines, '')
|
||||
feed_data(lines)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
describe('when the terminal has focus', function()
|
||||
it('will exit focus when scrolled', function()
|
||||
feed('<MouseDown><0,0>')
|
||||
screen:expect([[
|
||||
line23 |
|
||||
line24 |
|
||||
line25 |
|
||||
line26 |
|
||||
line27 |
|
||||
^line28 |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('with mouse events enabled by the program', function()
|
||||
before_each(function()
|
||||
thelpers.enable_mouse()
|
||||
thelpers.feed_data('mouse enabled\n')
|
||||
screen:expect([[
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
mouse enabled |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will forward mouse clicks to the program', function()
|
||||
feed('<LeftMouse><1,2>')
|
||||
screen:expect([[
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
mouse enabled |
|
||||
"#{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will forward mouse scroll to the program', function()
|
||||
feed('<MouseDown><0,0>')
|
||||
screen:expect([[
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
mouse enabled |
|
||||
`!!{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with a split window and other buffer', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>:vsp<cr>')
|
||||
screen:expect([[
|
||||
line21 |line21 |
|
||||
line22 |line22 |
|
||||
line23 |line23 |
|
||||
line24 |line24 |
|
||||
^rows: 5, cols: 24 |rows: 5, cols: 24 |
|
||||
========== ========== |
|
||||
|
|
||||
]])
|
||||
feed(':enew | set number<cr>')
|
||||
screen:expect([[
|
||||
1 ^ |line21 |
|
||||
~ |line22 |
|
||||
~ |line23 |
|
||||
~ |line24 |
|
||||
~ |rows: 5, cols: 24 |
|
||||
========== ========== |
|
||||
:enew | set number |
|
||||
]])
|
||||
feed('30iline\n<esc>')
|
||||
screen:expect([[
|
||||
27 line |line21 |
|
||||
28 line |line22 |
|
||||
29 line |line23 |
|
||||
30 line |line24 |
|
||||
31 ^ |rows: 5, cols: 24 |
|
||||
========== ========== |
|
||||
|
|
||||
]])
|
||||
feed('<c-w>li')
|
||||
screen:expect([[
|
||||
27 line |line22 |
|
||||
28 line |line23 |
|
||||
29 line |line24 |
|
||||
30 line |rows: 5, cols: 24 |
|
||||
31 |{1: } |
|
||||
========== ========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
-- enabling mouse won't affect interaction with other windows
|
||||
thelpers.enable_mouse()
|
||||
thelpers.feed_data('mouse enabled\n')
|
||||
screen:expect([[
|
||||
27 line |line23 |
|
||||
28 line |line24 |
|
||||
29 line |rows: 5, cols: 24 |
|
||||
30 line |mouse enabled |
|
||||
31 |{1: } |
|
||||
========== ========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('wont lose focus if another window is scrolled', function()
|
||||
feed('<MouseDown><0,0><MouseDown><0,0>')
|
||||
screen:expect([[
|
||||
21 line |line23 |
|
||||
22 line |line24 |
|
||||
23 line |rows: 5, cols: 24 |
|
||||
24 line |mouse enabled |
|
||||
25 line |{1: } |
|
||||
========== ========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
feed('<S-MouseUp><0,0>')
|
||||
screen:expect([[
|
||||
26 line |line23 |
|
||||
27 line |line24 |
|
||||
28 line |rows: 5, cols: 24 |
|
||||
29 line |mouse enabled |
|
||||
30 line |{1: } |
|
||||
========== ========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will lose focus if another window is clicked', function()
|
||||
feed('<LeftMouse><5,1>')
|
||||
screen:expect([[
|
||||
27 line |line23 |
|
||||
28 l^ine |line24 |
|
||||
29 line |rows: 5, cols: 24 |
|
||||
30 line |mouse enabled |
|
||||
31 |{2: } |
|
||||
========== ========== |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
348
test/functional/terminal/scrollback_spec.lua
Normal file
348
test/functional/terminal/scrollback_spec.lua
Normal file
@ -0,0 +1,348 @@
|
||||
local Screen = require('test.functional.ui.screen')
|
||||
local helpers = require('test.functional.helpers')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
|
||||
local feed, nvim_dir, execute = helpers.feed, helpers.nvim_dir, helpers.execute
|
||||
local wait = helpers.wait
|
||||
local feed_data = thelpers.feed_data
|
||||
|
||||
describe('terminal scrollback', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = thelpers.screen_setup()
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
describe('when the limit is crossed', function()
|
||||
before_each(function()
|
||||
local lines = {}
|
||||
for i = 1, 30 do
|
||||
table.insert(lines, 'line'..tostring(i))
|
||||
end
|
||||
table.insert(lines, '')
|
||||
feed_data(lines)
|
||||
screen:expect([[
|
||||
line26 |
|
||||
line27 |
|
||||
line28 |
|
||||
line29 |
|
||||
line30 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('will delete extra lines at the top', function()
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^line16 |
|
||||
line17 |
|
||||
line18 |
|
||||
line19 |
|
||||
line20 |
|
||||
line21 |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with the cursor at the last row', function()
|
||||
before_each(function()
|
||||
feed_data({'line1', 'line2', 'line3', 'line4', ''})
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
describe('and 1 line is printed', function()
|
||||
before_each(function() feed_data({'line5', ''}) end)
|
||||
|
||||
it('will hide the top line', function()
|
||||
screen:expect([[
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(7, curbuf('line_count'))
|
||||
end)
|
||||
|
||||
describe('and then 3 more lines are printed', function()
|
||||
before_each(function() feed_data({'line6', 'line7', 'line8'}) end)
|
||||
|
||||
it('will hide the top 4 lines', function()
|
||||
screen:expect([[
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
line8{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
|
||||
feed('<c-\\><c-n>6k')
|
||||
screen:expect([[
|
||||
^line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
|
|
||||
]])
|
||||
|
||||
feed('gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
|
|
||||
]])
|
||||
|
||||
feed('G')
|
||||
screen:expect([[
|
||||
line3 |
|
||||
line4 |
|
||||
line5 |
|
||||
line6 |
|
||||
line7 |
|
||||
^line8{2: } |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
|
||||
describe('and the height is decreased by 1', function()
|
||||
local function will_hide_top_line()
|
||||
screen:try_resize(screen._width, screen._height - 1)
|
||||
screen:expect([[
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
rows: 5, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end
|
||||
|
||||
it('will hide top line', will_hide_top_line)
|
||||
|
||||
describe('and then decreased by 2', function()
|
||||
before_each(function()
|
||||
will_hide_top_line()
|
||||
screen:try_resize(screen._width, screen._height - 2)
|
||||
end)
|
||||
|
||||
it('will hide the top 3 lines', function()
|
||||
screen:expect([[
|
||||
rows: 5, cols: 50 |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(8, curbuf('line_count'))
|
||||
feed('<c-\\><c-n>3k')
|
||||
screen:expect([[
|
||||
^line4 |
|
||||
rows: 5, cols: 50 |
|
||||
rows: 3, cols: 50 |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with empty lines after the cursor', function()
|
||||
describe('and the height is decreased by 2', function()
|
||||
before_each(function()
|
||||
screen:try_resize(screen._width, screen._height - 2)
|
||||
end)
|
||||
|
||||
local function will_delete_last_two_lines()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
rows: 4, cols: 50 |
|
||||
{1: } |
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(4, curbuf('line_count'))
|
||||
end
|
||||
|
||||
it('will delete the last two empty lines', will_delete_last_two_lines)
|
||||
|
||||
describe('and then decreased by 1', function()
|
||||
before_each(function()
|
||||
will_delete_last_two_lines()
|
||||
screen:try_resize(screen._width, screen._height - 1)
|
||||
end)
|
||||
|
||||
it('will delete the last line and hide the first', function()
|
||||
screen:expect([[
|
||||
rows: 4, cols: 50 |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(4, curbuf('line_count'))
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
rows: 4, cols: 50 |
|
||||
rows: 3, cols: 50 |
|
||||
|
|
||||
]])
|
||||
feed('a')
|
||||
screen:expect([[
|
||||
rows: 4, cols: 50 |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with 4 lines hidden in the scrollback', function()
|
||||
before_each(function()
|
||||
feed_data({'line1', 'line2', 'line3', 'line4', ''})
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
screen:try_resize(screen._width, screen._height - 3)
|
||||
screen:expect([[
|
||||
line4 |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(7, curbuf('line_count'))
|
||||
end)
|
||||
|
||||
describe('and the height is increased by 1', function()
|
||||
local function pop_then_push()
|
||||
screen:try_resize(screen._width, screen._height + 1)
|
||||
screen:expect([[
|
||||
line4 |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 4, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end
|
||||
|
||||
it('will pop 1 line and then push it back', pop_then_push)
|
||||
|
||||
describe('and then by 3', function()
|
||||
before_each(function()
|
||||
pop_then_push()
|
||||
eq(8, curbuf('line_count'))
|
||||
screen:try_resize(screen._width, screen._height + 3)
|
||||
end)
|
||||
|
||||
local function pop3_then_push1()
|
||||
screen:expect([[
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 4, cols: 50 |
|
||||
rows: 7, cols: 50 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
eq(9, curbuf('line_count'))
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 4, cols: 50 |
|
||||
|
|
||||
]])
|
||||
end
|
||||
|
||||
it('will pop 3 lines and then push one back', pop3_then_push1)
|
||||
|
||||
describe('and then by 4', function()
|
||||
before_each(function()
|
||||
pop3_then_push1()
|
||||
feed('Gi')
|
||||
screen:try_resize(screen._width, screen._height + 4)
|
||||
end)
|
||||
|
||||
it('will show all lines and leave a blank one at the end', function()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 4, cols: 50 |
|
||||
rows: 7, cols: 50 |
|
||||
rows: 11, cols: 50 |
|
||||
{1: } |
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
-- since there's an empty line after the cursor, the buffer line
|
||||
-- count equals the terminal screen height
|
||||
eq(11, curbuf('line_count'))
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('terminal prints more lines than the screen height and exits', function()
|
||||
it('will push extra lines to scrollback', function()
|
||||
clear()
|
||||
local screen = Screen.new(50, 7)
|
||||
screen:attach(false)
|
||||
execute('term ' ..nvim_dir.. '/tty-test 10')
|
||||
wait()
|
||||
screen:expect([[
|
||||
line6 |
|
||||
line7 |
|
||||
line8 |
|
||||
line9 |
|
||||
|
|
||||
[Program exited, press any key to close] |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
64
test/functional/terminal/window_spec.lua
Normal file
64
test/functional/terminal/window_spec.lua
Normal file
@ -0,0 +1,64 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local feed, clear, nvim = helpers.feed, helpers.clear, helpers.nvim
|
||||
local wait, eq = helpers.wait, helpers.eq
|
||||
|
||||
|
||||
describe('terminal window', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
screen = thelpers.screen_setup()
|
||||
end)
|
||||
|
||||
describe('with colorcolumn set', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>:set colorcolumn=20<cr>i')
|
||||
wait()
|
||||
end)
|
||||
|
||||
it('wont show the color column', function()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
{1: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('with fold set', function()
|
||||
before_each(function()
|
||||
feed('<c-\\><c-n>:set foldenable foldmethod=manual<cr>i')
|
||||
thelpers.feed_data({'line1', 'line2', 'line3', 'line4', ''})
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
|
||||
it('wont show any folds', function()
|
||||
feed('<c-\\><c-n>ggvGzf')
|
||||
wait()
|
||||
screen:expect([[
|
||||
^tty ready |
|
||||
line1 |
|
||||
line2 |
|
||||
line3 |
|
||||
line4 |
|
||||
{2: } |
|
||||
|
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
|
138
test/functional/terminal/window_split_tab_spec.lua
Normal file
138
test/functional/terminal/window_split_tab_spec.lua
Normal file
@ -0,0 +1,138 @@
|
||||
local helpers = require('test.functional.helpers')
|
||||
local thelpers = require('test.functional.terminal.helpers')
|
||||
local clear, eq, curbuf = helpers.clear, helpers.eq, helpers.curbuf
|
||||
local feed, nvim = helpers.feed, helpers.nvim
|
||||
local feed_data = thelpers.feed_data
|
||||
|
||||
describe('terminal', function()
|
||||
local screen
|
||||
|
||||
before_each(function()
|
||||
clear()
|
||||
-- set the statusline to a constant value because of variables like pid
|
||||
-- and current directory and to improve visibility of splits
|
||||
nvim('set_option', 'statusline', '==========')
|
||||
nvim('command', 'highlight StatusLine cterm=NONE')
|
||||
nvim('command', 'highlight StatusLineNC cterm=NONE')
|
||||
nvim('command', 'highlight VertSplit cterm=NONE')
|
||||
screen = thelpers.screen_setup(3)
|
||||
end)
|
||||
|
||||
after_each(function()
|
||||
screen:detach()
|
||||
end)
|
||||
|
||||
describe('when the screen is resized', function()
|
||||
it('will forward a resize request to the program', function()
|
||||
screen:try_resize(screen._width + 3, screen._height + 5)
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
rows: 14, cols: 53 |
|
||||
{1: } |
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
screen:try_resize(screen._width - 6, screen._height - 10)
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
rows: 14, cols: 53 |
|
||||
rows: 4, cols: 47 |
|
||||
{1: } |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
|
||||
describe('split horizontally', function()
|
||||
before_each(function()
|
||||
nvim('command', 'sp')
|
||||
end)
|
||||
|
||||
local function reduce_height()
|
||||
screen:expect([[
|
||||
tty ready |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
~ |
|
||||
========== |
|
||||
tty ready |
|
||||
rows: 3, cols: 50 |
|
||||
{2: } |
|
||||
========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end
|
||||
|
||||
it('uses the minimum height of all window displaying it', reduce_height)
|
||||
|
||||
describe('and then vertically', function()
|
||||
before_each(function()
|
||||
reduce_height()
|
||||
nvim('command', 'vsp')
|
||||
end)
|
||||
|
||||
local function reduce_width()
|
||||
screen:expect([[
|
||||
rows: 3, cols: 50 |rows: 3, cols: 50 |
|
||||
rows: 3, cols: 24 |rows: 3, cols: 24 |
|
||||
{1: } |{2: } |
|
||||
~ |~ |
|
||||
========== ========== |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 3, cols: 24 |
|
||||
{2: } |
|
||||
========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
feed('<c-\\><c-n>gg')
|
||||
screen:expect([[
|
||||
^tty ready |rows: 3, cols: 50 |
|
||||
rows: 3, cols: 50 |rows: 3, cols: 24 |
|
||||
rows: 3, cols: 24 |{2: } |
|
||||
{2: } |~ |
|
||||
========== ========== |
|
||||
rows: 3, cols: 50 |
|
||||
rows: 3, cols: 24 |
|
||||
{2: } |
|
||||
========== |
|
||||
|
|
||||
]])
|
||||
end
|
||||
|
||||
it('uses the minimum width of all window displaying it', reduce_width)
|
||||
|
||||
describe('and then closes one of the vertical splits with q:', function()
|
||||
before_each(function()
|
||||
reduce_width()
|
||||
nvim('command', 'q')
|
||||
feed('<c-w>ja')
|
||||
end)
|
||||
|
||||
it('will restore the width', function()
|
||||
screen:expect([[
|
||||
rows: 3, cols: 24 |
|
||||
rows: 3, cols: 50 |
|
||||
{2: } |
|
||||
~ |
|
||||
========== |
|
||||
rows: 3, cols: 24 |
|
||||
rows: 3, cols: 50 |
|
||||
{1: } |
|
||||
========== |
|
||||
-- TERMINAL -- |
|
||||
]])
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
||||
end)
|
@ -190,8 +190,11 @@ function Screen:set_default_attr_ignore(attr_ignore)
|
||||
self._default_attr_ignore = attr_ignore
|
||||
end
|
||||
|
||||
function Screen:attach()
|
||||
request('ui_attach', self._width, self._height, true)
|
||||
function Screen:attach(rgb)
|
||||
if rgb == nil then
|
||||
rgb = true
|
||||
end
|
||||
request('ui_attach', self._width, self._height, rgb)
|
||||
end
|
||||
|
||||
function Screen:detach()
|
||||
@ -500,6 +503,7 @@ function Screen:snapshot_util(attrs, ignore)
|
||||
else
|
||||
print( "]], "..attrstr..")\n")
|
||||
end
|
||||
io.stdout:flush()
|
||||
end
|
||||
|
||||
function pprint_attrs(attrs)
|
||||
|
Loading…
Reference in New Issue
Block a user