Patch 8.0.0797
Problem: Finished job in terminal window is not handled.
Solution: Add the scrollback buffer. Use it to fill the buffer when the job
has ended.
Files: src/terminal.c, src/screen.c, src/proto/terminal.pro,
src/channel.c, src/os_unix.c, src/buffer.c
*** ../vim-8.0.0796/src/terminal.c 2017-07-28 15:11:34.267537205 +0200
--- src/terminal.c 2017-07-28 21:45:01.385884157 +0200
***************
*** 33,54 ****
* while, if the terminal window is visible, the screen contents is drawn.
*
* TODO:
! * - if 'term' starts witth "xterm" use it for $TERM.
! * - To set BS correctly, check get_stty(); Pass the fd of the pty.
! * - include functions from #1871
! * - do not store terminal buffer in viminfo. Or prefix term:// ?
! * - Add a scrollback buffer (contains lines to scroll off the top).
! * Can use the buf_T lines, store attributes somewhere else?
* - When the job ends:
- * - Write "-- JOB ENDED --" in the terminal.
- * - Put the terminal contents in the scrollback buffer.
- * - Free the terminal emulator.
* - Display the scrollback buffer (but with attributes).
* Make the buffer not modifiable, drop attributes when making changes.
* - Need an option or argument to drop the window+buffer right away, to be
* used for a shell or Vim.
* - add a character in :ls output
* - when closing window and job has not ended, make terminal hidden?
* - don't allow exiting Vim when a terminal is still running a job
* - use win_del_lines() to make scroll-up efficient.
* - add test for giving error for invalid 'termsize' value.
--- 33,51 ----
* while, if the terminal window is visible, the screen contents is drawn.
*
* TODO:
! * - For the scrollback buffer store lines in the buffer, only attributes in
! * tl_scrollback.
* - When the job ends:
* - Display the scrollback buffer (but with attributes).
* Make the buffer not modifiable, drop attributes when making changes.
* - Need an option or argument to drop the window+buffer right away, to be
* used for a shell or Vim.
+ * - To set BS correctly, check get_stty(); Pass the fd of the pty.
+ * - Patch for functions: Yasuhiro Matsumoto, #1871
+ * - do not store terminal buffer in viminfo. Or prefix term:// ?
* - add a character in :ls output
* - when closing window and job has not ended, make terminal hidden?
+ * - when closing window and job has ended, make buffer hidden?
* - don't allow exiting Vim when a terminal is still running a job
* - use win_del_lines() to make scroll-up efficient.
* - add test for giving error for invalid 'termsize' value.
***************
*** 80,85 ****
--- 77,87 ----
#include "libvterm/include/vterm.h"
+ typedef struct sb_line_S {
+ int sb_cols; /* can differ per line */
+ VTermScreenCell *sb_cells; /* allocated */
+ } sb_line_T;
+
/* typedef term_T in structs.h */
struct terminal_S {
term_T *tl_next;
***************
*** 106,111 ****
--- 108,115 ----
int tl_dirty_row_start; /* -1 if nothing dirty */
int tl_dirty_row_end; /* row below last one to update */
+ garray_T tl_scrollback;
+
pos_T tl_cursor;
int tl_cursor_visible;
};
***************
*** 124,130 ****
*/
static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
static void term_report_winsize(term_T *term, int rows, int cols);
! static void term_free(term_T *term);
/**************************************
* 1. Generic code for all systems.
--- 128,134 ----
*/
static int term_and_job_init(term_T *term, int rows, int cols, char_u *cmd);
static void term_report_winsize(term_T *term, int rows, int cols);
! static void term_free_vterm(term_T *term);
/**************************************
* 1. Generic code for all systems.
***************
*** 179,184 ****
--- 183,189 ----
return;
term->tl_dirty_row_end = MAX_ROW;
term->tl_cursor_visible = TRUE;
+ ga_init2(&term->tl_scrollback, sizeof(sb_line_T), 300);
/* Open a new window or tab. */
vim_memset(&split_ea, 0, sizeof(split_ea));
***************
*** 238,245 ****
}
else
{
! free_terminal(term);
! curbuf->b_term = NULL;
/* Wiping out the buffer will also close the window and call
* free_terminal(). */
--- 243,249 ----
}
else
{
! free_terminal(curbuf);
/* Wiping out the buffer will also close the window and call
* free_terminal(). */
***************
*** 255,263 ****
* Called when wiping out a buffer.
*/
void
! free_terminal(term_T *term)
{
term_T *tp;
if (term == NULL)
return;
--- 259,269 ----
* Called when wiping out a buffer.
*/
void
! free_terminal(buf_T *buf)
{
+ term_T *term = buf->b_term;
term_T *tp;
+ int i;
if (term == NULL)
return;
***************
*** 279,288 ****
job_unref(term->tl_job);
}
! term_free(term);
vim_free(term->tl_title);
vim_free(term->tl_status_text);
vim_free(term);
}
/*
--- 285,299 ----
job_unref(term->tl_job);
}
! for (i = 0; i < term->tl_scrollback.ga_len; ++i)
! vim_free(((sb_line_T *)term->tl_scrollback.ga_data + i) ->sb_cells);
! ga_clear(&term->tl_scrollback);
!
! term_free_vterm(term);
vim_free(term->tl_title);
vim_free(term->tl_status_text);
vim_free(term);
+ buf->b_term = NULL;
}
/*
***************
*** 344,349 ****
--- 355,365 ----
size_t len = STRLEN(msg);
term_T *term = buffer->b_term;
+ if (term->tl_vterm == NULL)
+ {
+ ch_logn(channel, "NOT writing %d bytes to terminal", (int)len);
+ return;
+ }
ch_logn(channel, "writing %d bytes to terminal", (int)len);
term_write_job_output(term, msg, len);
***************
*** 459,464 ****
--- 475,489 ----
}
/*
+ * Return TRUE if the job for "buf" is still running.
+ */
+ static int
+ term_job_running(term_T *term)
+ {
+ return term->tl_job != NULL && term->tl_job->jv_status == JOB_STARTED;
+ }
+
+ /*
* Get a key from the user without mapping.
* TODO: use terminal mode mappings.
*/
***************
*** 480,487 ****
* Wait for input and send it to the job.
* Return when the start of a CTRL-W command is typed or anything else that
* should be handled as a Normal mode command.
*/
! void
terminal_loop(void)
{
char buf[KEY_BUF_LEN];
--- 505,514 ----
* Wait for input and send it to the job.
* Return when the start of a CTRL-W command is typed or anything else that
* should be handled as a Normal mode command.
+ * Returns OK if a typed character is to be handled in Normal mode, FAIL if
+ * the terminal was closed.
*/
! int
terminal_loop(void)
{
char buf[KEY_BUF_LEN];
***************
*** 491,496 ****
--- 518,527 ----
int dragging_outside = FALSE;
int termkey = 0;
+ if (curbuf->b_term->tl_vterm == NULL || !term_job_running(curbuf->b_term))
+ /* job finished */
+ return OK;
+
if (*curwin->w_p_tk != NUL)
termkey = string_to_key(curwin->w_p_tk, TRUE);
***************
*** 500,505 ****
--- 531,540 ----
update_screen(0);
update_cursor(curbuf->b_term, FALSE);
c = term_vgetc();
+ if (curbuf->b_term->tl_vterm == NULL
+ || !term_job_running(curbuf->b_term))
+ /* job finished while waiting for a character */
+ break;
if (c == (termkey == 0 ? Ctrl_W : termkey))
{
***************
*** 511,516 ****
--- 546,555 ----
#ifdef FEAT_CMDL_INFO
clear_showcmd();
#endif
+ if (curbuf->b_term->tl_vterm == NULL
+ || !term_job_running(curbuf->b_term))
+ /* job finished while waiting for a character */
+ break;
if (termkey == 0 && c == '.')
/* "CTRL-W .": send CTRL-W to the job */
***************
*** 519,525 ****
{
stuffcharReadbuff(Ctrl_W);
stuffcharReadbuff(c);
! return;
}
}
--- 558,564 ----
{
stuffcharReadbuff(Ctrl_W);
stuffcharReadbuff(c);
! return OK;
}
}
***************
*** 529,535 ****
case NUL:
case K_ZERO:
stuffcharReadbuff(c);
! return;
case K_IGNORE: continue;
--- 568,574 ----
case NUL:
case K_ZERO:
stuffcharReadbuff(c);
! return OK;
case K_IGNORE: continue;
***************
*** 561,567 ****
/* click outside the current window */
stuffcharReadbuff(c);
mouse_was_outside = TRUE;
! return;
}
}
mouse_was_outside = FALSE;
--- 600,606 ----
/* click outside the current window */
stuffcharReadbuff(c);
mouse_was_outside = TRUE;
! return OK;
}
}
mouse_was_outside = FALSE;
***************
*** 571,616 ****
if (len > 0)
/* TODO: if FAIL is returned, stop? */
channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
! (char_u *)buf, (int)len,
NULL);
! }
! }
!
! /*
! * Called when a job has finished.
! */
! void
! term_job_ended(job_T *job)
! {
! term_T *term;
! int did_one = FALSE;
!
! for (term = first_term; term != NULL; term = term->tl_next)
! if (term->tl_job == job)
! {
! vim_free(term->tl_title);
! term->tl_title = NULL;
! vim_free(term->tl_status_text);
! term->tl_status_text = NULL;
! redraw_buf_and_status_later(term->tl_buffer, VALID);
! did_one = TRUE;
! }
! if (did_one)
! redraw_statuslines();
! if (curbuf->b_term != NULL)
! {
! if (curbuf->b_term->tl_job == job)
! maketitle();
! update_cursor(curbuf->b_term, TRUE);
}
! }
!
! /*
! * Return TRUE if the job for "buf" is still running.
! */
! static int
! term_job_running(term_T *term)
! {
! return term->tl_job != NULL && term->tl_job->jv_status == JOB_STARTED;
}
static void
--- 610,618 ----
if (len > 0)
/* TODO: if FAIL is returned, stop? */
channel_send(curbuf->b_term->tl_job->jv_channel, PART_IN,
! (char_u *)buf, (int)len, NULL);
}
! return FAIL;
}
static void
***************
*** 740,745 ****
--- 742,889 ----
return 1;
}
+ /*
+ * Handle a line that is pushed off the top of the screen.
+ */
+ static int
+ handle_pushline(int cols, const VTermScreenCell *cells, void *user)
+ {
+ term_T *term = (term_T *)user;
+
+ /* TODO: Limit the number of lines that are stored. */
+ /* TODO: put the text in the buffer. */
+ if (ga_grow(&term->tl_scrollback, 1) == OK)
+ {
+ VTermScreenCell *p;
+ int len;
+ int i;
+
+ /* do not store empty cells at the end */
+ for (i = 0; i < cols; ++i)
+ if (cells[i].chars[0] != 0)
+ len = i + 1;
+
+ p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
+ if (p != NULL)
+ {
+ sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ + term->tl_scrollback.ga_len;
+
+ mch_memmove(p, cells, sizeof(VTermScreenCell) * len);
+ line->sb_cols = len;
+ line->sb_cells = p;
+ ++term->tl_scrollback.ga_len;
+ }
+ }
+ return 0; /* ignored */
+ }
+
+ /*
+ * Fill the buffer with the scrollback lines and current lines of the
terminal.
+ * Called after the job has ended.
+ */
+ static void
+ move_scrollback_to_buffer(term_T *term)
+ {
+ linenr_T lnum;
+ garray_T ga;
+ int c;
+ int col;
+ int i;
+ win_T *wp;
+ int len;
+ int lines_skipped = 0;
+ VTermPos pos;
+ VTermScreenCell cell;
+ VTermScreenCell *p;
+ VTermScreen *screen = vterm_obtain_screen(term->tl_vterm);
+
+ /* Append the the visible lines to the scrollback. */
+ for (pos.row = 0; pos.row < term->tl_rows; ++pos.row)
+ {
+ len = 0;
+ for (pos.col = 0; pos.col < term->tl_cols; ++pos.col)
+ if (vterm_screen_get_cell(screen, pos, &cell) != 0
+ && cell.chars[0] != NUL)
+ len = pos.col + 1;
+
+ if (len > 0)
+ {
+ while (lines_skipped > 0)
+ {
+ /* Line was skipped, add an empty line. */
+ --lines_skipped;
+ if (ga_grow(&term->tl_scrollback, 1) == OK)
+ {
+ sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ + term->tl_scrollback.ga_len;
+
+ line->sb_cols = 0;
+ line->sb_cells = NULL;
+ ++term->tl_scrollback.ga_len;
+ }
+ }
+
+ p = (VTermScreenCell *)alloc((int)sizeof(VTermScreenCell) * len);
+ if (p != NULL && ga_grow(&term->tl_scrollback, 1) == OK)
+ {
+ sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data
+ + term->tl_scrollback.ga_len;
+
+ for (pos.col = 0; pos.col < len; ++pos.col)
+ {
+ if (vterm_screen_get_cell(screen, pos, &cell) == 0)
+ vim_memset(p + pos.col, 0, sizeof(cell));
+ else
+ p[pos.col] = cell;
+ }
+ line->sb_cols = len;
+ line->sb_cells = p;
+ ++term->tl_scrollback.ga_len;
+ }
+ else
+ vim_free(p);
+ }
+ }
+
+ /* Add the text to the buffer. */
+ ga_init2(&ga, 1, 100);
+ for (lnum = 0; lnum < term->tl_scrollback.ga_len; ++lnum)
+ {
+ sb_line_T *line = (sb_line_T *)term->tl_scrollback.ga_data + lnum;
+
+ ga.ga_len = 0;
+ for (col = 0; col < line->sb_cols; ++col)
+ for (i = 0; (c = line->sb_cells[col].chars[i]) != 0 || i == 0; ++i)
+ {
+ if (ga_grow(&ga, MB_MAXBYTES) == FAIL)
+ goto failed;
+ ga.ga_len += mb_char2bytes(c == NUL ? ' ' : c,
+ (char_u *)ga.ga_data + ga.ga_len);
+ }
+ *((char_u *)ga.ga_data + ga.ga_len) = NUL;
+ ml_append_buf(term->tl_buffer, lnum, ga.ga_data, ga.ga_len + 1, FALSE);
+ }
+
+ /* Delete the empty line that was in the empty buffer. */
+ curbuf = term->tl_buffer;
+ ml_delete(lnum + 1, FALSE);
+ curbuf = curwin->w_buffer;
+
+ failed:
+ ga_clear(&ga);
+
+ FOR_ALL_WINDOWS(wp)
+ {
+ if (wp->w_buffer == term->tl_buffer)
+ {
+ wp->w_cursor.lnum = term->tl_buffer->b_ml.ml_line_count;
+ wp->w_cursor.col = 0;
+ wp->w_valid = 0;
+ }
+ }
+ }
+
static VTermScreenCallbacks screen_callbacks = {
handle_damage, /* damage */
handle_moverect, /* moverect */
***************
*** 747,757 ****
handle_settermprop, /* settermprop */
NULL, /* bell */
handle_resize, /* resize */
! NULL, /* sb_pushline */
NULL /* sb_popline */
};
/*
* Reverse engineer the RGB value into a cterm color index.
* First color is 1. Return 0 if no match found.
*/
--- 891,941 ----
handle_settermprop, /* settermprop */
NULL, /* bell */
handle_resize, /* resize */
! handle_pushline, /* sb_pushline */
NULL /* sb_popline */
};
/*
+ * Called when a channel has been closed.
+ */
+ void
+ term_channel_closed(channel_T *ch)
+ {
+ term_T *term;
+ int did_one = FALSE;
+
+ for (term = first_term; term != NULL; term = term->tl_next)
+ if (term->tl_job == ch->ch_job)
+ {
+ vim_free(term->tl_title);
+ term->tl_title = NULL;
+ vim_free(term->tl_status_text);
+ term->tl_status_text = NULL;
+
+ /* move the lines into the buffer and free the vterm */
+ move_scrollback_to_buffer(term);
+ term_free_vterm(term);
+
+ redraw_buf_and_status_later(term->tl_buffer, NOT_VALID);
+ did_one = TRUE;
+ }
+ if (did_one)
+ {
+ redraw_statuslines();
+
+ /* Need to break out of vgetc(). */
+ ins_char_typebuf(K_IGNORE);
+
+ if (curbuf->b_term != NULL)
+ {
+ if (curbuf->b_term->tl_job == ch->ch_job)
+ maketitle();
+ update_cursor(curbuf->b_term, TRUE);
+ }
+ }
+ }
+
+ /*
* Reverse engineer the RGB value into a cterm color index.
* First color is 1. Return 0 if no match found.
*/
***************
*** 911,927 ****
}
/*
! * Called to update the window that contains the terminal.
*/
! void
term_update_window(win_T *wp)
{
term_T *term = wp->w_buffer->b_term;
! VTerm *vterm = term->tl_vterm;
! VTermScreen *screen = vterm_obtain_screen(vterm);
! VTermState *state = vterm_obtain_state(vterm);
VTermPos pos;
/*
* If the window was resized a redraw will be triggered and we get here.
* Adjust the size of the vterm unless 'termsize' specifies a fixed size.
--- 1095,1118 ----
}
/*
! * Called to update the window that contains a terminal.
! * Returns FAIL when there is no terminal running in this window.
*/
! int
term_update_window(win_T *wp)
{
term_T *term = wp->w_buffer->b_term;
! VTerm *vterm;
! VTermScreen *screen;
! VTermState *state;
VTermPos pos;
+ if (term == NULL || term->tl_vterm == NULL)
+ return FAIL;
+ vterm = term->tl_vterm;
+ screen = vterm_obtain_screen(vterm);
+ state = vterm_obtain_state(vterm);
+
/*
* If the window was resized a redraw will be triggered and we get here.
* Adjust the size of the vterm unless 'termsize' specifies a fixed size.
***************
*** 1022,1027 ****
--- 1213,1220 ----
screen_line(wp->w_winrow + pos.row, wp->w_wincol,
pos.col, wp->w_width, FALSE);
}
+
+ return OK;
}
/*
***************
*** 1351,1364 ****
* Free the terminal emulator part of "term".
*/
static void
! term_free(term_T *term)
{
if (term->tl_winpty != NULL)
winpty_free(term->tl_winpty);
if (term->tl_winpty_config != NULL)
winpty_config_free(term->tl_winpty_config);
if (term->tl_vterm != NULL)
vterm_free(term->tl_vterm);
}
/*
--- 1544,1560 ----
* Free the terminal emulator part of "term".
*/
static void
! term_free_vterm(term_T *term)
{
if (term->tl_winpty != NULL)
winpty_free(term->tl_winpty);
+ term->tl_winpty = NULL;
if (term->tl_winpty_config != NULL)
winpty_config_free(term->tl_winpty_config);
+ term->tl_winpty_config = NULL;
if (term->tl_vterm != NULL)
vterm_free(term->tl_vterm);
+ term->tl_vterm = NULL
}
/*
***************
*** 1406,1415 ****
* Free the terminal emulator part of "term".
*/
static void
! term_free(term_T *term)
{
if (term->tl_vterm != NULL)
vterm_free(term->tl_vterm);
}
/*
--- 1602,1612 ----
* Free the terminal emulator part of "term".
*/
static void
! term_free_vterm(term_T *term)
{
if (term->tl_vterm != NULL)
vterm_free(term->tl_vterm);
+ term->tl_vterm = NULL;
}
/*
*** ../vim-8.0.0796/src/screen.c 2017-07-19 12:51:48.018228639 +0200
--- src/screen.c 2017-07-28 18:38:03.302769617 +0200
***************
*** 1200,1210 ****
#endif
#ifdef FEAT_TERMINAL
! if (wp->w_buffer->b_term != NULL)
{
- /* This window contains a terminal, redraw works completely
- * differently. */
- term_update_window(wp);
wp->w_redr_type = 0;
return;
}
--- 1200,1209 ----
#endif
#ifdef FEAT_TERMINAL
! /* If this window contains a terminal, redraw works completely
differently.
! */
! if (term_update_window(wp) == OK)
{
wp->w_redr_type = 0;
return;
}
***************
*** 6849,6862 ****
p = NameBuff;
len = (int)STRLEN(p);
! if (wp->w_buffer->b_help
#ifdef FEAT_QUICKFIX
|| wp->w_p_pvw
#endif
|| bufIsChanged(wp->w_buffer)
|| wp->w_buffer->b_p_ro)
*(p + len++) = ' ';
! if (wp->w_buffer->b_help)
{
STRCPY(p + len, _("[Help]"));
len += (int)STRLEN(p + len);
--- 6848,6861 ----
p = NameBuff;
len = (int)STRLEN(p);
! if (bt_help(wp->w_buffer)
#ifdef FEAT_QUICKFIX
|| wp->w_p_pvw
#endif
|| bufIsChanged(wp->w_buffer)
|| wp->w_buffer->b_p_ro)
*(p + len++) = ' ';
! if (bt_help(wp->w_buffer))
{
STRCPY(p + len, _("[Help]"));
len += (int)STRLEN(p + len);
*** ../vim-8.0.0796/src/proto/terminal.pro 2017-07-27 22:14:55.305968217
+0200
--- src/proto/terminal.pro 2017-07-28 21:39:43.975980817 +0200
***************
*** 1,10 ****
/* terminal.c */
void ex_terminal(exarg_T *eap);
! void free_terminal(term_T *term);
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
! void terminal_loop(void);
! void term_job_ended(job_T *job);
! void term_update_window(win_T *wp);
char_u *term_get_status_text(term_T *term);
int set_ref_in_term(int copyID);
/* vim: set ft=c : */
--- 1,10 ----
/* terminal.c */
void ex_terminal(exarg_T *eap);
! void free_terminal(buf_T *buf);
void write_to_term(buf_T *buffer, char_u *msg, channel_T *channel);
! int terminal_loop(void);
! void term_channel_closed(channel_T *ch);
! int term_update_window(win_T *wp);
char_u *term_get_status_text(term_T *term);
int set_ref_in_term(int copyID);
/* vim: set ft=c : */
*** ../vim-8.0.0796/src/channel.c 2017-07-23 19:50:39.036922744 +0200
--- src/channel.c 2017-07-28 20:24:44.785952120 +0200
***************
*** 2921,2926 ****
--- 2921,2930 ----
}
channel->ch_nb_close_cb = NULL;
+
+ #ifdef FEAT_TERMINAL
+ term_channel_closed(channel);
+ #endif
}
/*
***************
*** 4696,4705 ****
* not use "job" after this! */
job_free(job);
}
-
- #ifdef FEAT_TERMINAL
- term_job_ended(job);
- #endif
}
/*
--- 4700,4705 ----
*** ../vim-8.0.0796/src/os_unix.c 2017-07-28 15:55:28.572158705 +0200
--- src/os_unix.c 2017-07-28 21:13:01.302365432 +0200
***************
*** 412,417 ****
--- 412,420 ----
#ifdef MESSAGE_QUEUE
parse_queued_messages();
+ /* If input was put directly in typeahead buffer bail out here. */
+ if (typebuf_changed(tb_change_cnt))
+ return 0;
#endif
if (wtime < 0 && did_start_blocking)
/* blocking and already waited for p_ut */
*** ../vim-8.0.0796/src/buffer.c 2017-07-27 22:03:45.546703088 +0200
--- src/buffer.c 2017-07-28 21:39:51.959928375 +0200
***************
*** 858,864 ****
channel_buffer_free(buf);
#endif
#ifdef FEAT_TERMINAL
! free_terminal(buf->b_term);
#endif
buf_hashtab_remove(buf);
--- 858,864 ----
channel_buffer_free(buf);
#endif
#ifdef FEAT_TERMINAL
! free_terminal(buf);
#endif
buf_hashtab_remove(buf);
*** ../vim-8.0.0796/src/version.c 2017-07-28 18:01:54.042023391 +0200
--- src/version.c 2017-07-28 21:48:23.908535689 +0200
***************
*** 771,772 ****
--- 771,774 ----
{ /* Add new patch number below this line */
+ /**/
+ 797,
/**/
--
Facepalm statement #4: "3000 year old graves? That's not possible, it's only
2014!"
/// Bram Moolenaar -- [email protected] -- http://www.Moolenaar.net \\\
/// sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ \\\
\\\ an exciting new programming language -- http://www.Zimbu.org ///
\\\ help me help AIDS victims -- http://ICCF-Holland.org ///
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.