To: vim_dev@googlegroups.com Subject: Patch 9.0.0013 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0013 Problem: Reproducing memory access errors can be difficult. Solution: When testing, copy each line to allocated memory, so that valgrind can detect accessing memory before and/or after it. Fix uncovered problems. Files: runtime/doc/testing.txt, src/structs.h, src/globals.h, src/testdir/runtest.vim, src/memline.c, src/edit.c, src/ops.c, src/textprop.c, src/cindent.c, src/normal.c, src/netbeans.c, src/change.c, src/testdir/test_edit.vim, src/testdir/test_breakindent.vim *** ../vim-9.0.0012/runtime/doc/testing.txt 2022-06-28 11:21:06.000000000 +0100 --- runtime/doc/testing.txt 2022-06-30 16:41:57.192764888 +0100 *************** *** 268,273 **** --- 268,276 ---- Current supported values for {name} are: {name} effect when {val} is non-zero ~ + alloc_lines make a copy of every buffer line into allocated + memory, so that memory access errors can be found + by valgrind autoload `import autoload` will load the script right away, not postponed until an item is used char_avail disable the char_avail() function *************** *** 287,293 **** uptime overrules sysinfo.uptime vterm_title setting the window title by a job running in a terminal window ! ALL clear all overrides ({val} is not used) "starting" is to be used when a test should behave like startup was done. Since the tests are run by sourcing a --- 290,297 ---- uptime overrules sysinfo.uptime vterm_title setting the window title by a job running in a terminal window ! ALL clear all overrides, except alloc_lines ({val} is ! not used) "starting" is to be used when a test should behave like startup was done. Since the tests are run by sourcing a *** ../vim-9.0.0012/src/structs.h 2022-06-14 13:26:55.000000000 +0100 --- src/structs.h 2022-06-30 16:33:11.213880764 +0100 *************** *** 756,765 **** int ml_stack_top; // current top of ml_stack int ml_stack_size; // total number of entries in ml_stack ! #define ML_EMPTY 1 // empty buffer ! #define ML_LINE_DIRTY 2 // cached line was changed and allocated ! #define ML_LOCKED_DIRTY 4 // ml_locked was changed ! #define ML_LOCKED_POS 8 // ml_locked needs positive block number int ml_flags; colnr_T ml_line_len; // length of the cached line, including NUL --- 756,766 ---- int ml_stack_top; // current top of ml_stack int ml_stack_size; // total number of entries in ml_stack ! #define ML_EMPTY 0x01 // empty buffer ! #define ML_LINE_DIRTY 0x02 // cached line was changed and allocated ! #define ML_LOCKED_DIRTY 0x04 // ml_locked was changed ! #define ML_LOCKED_POS 0x08 // ml_locked needs positive block number ! #define ML_ALLOCATED 0x10 // ml_line_ptr is an allocated copy int ml_flags; colnr_T ml_line_len; // length of the cached line, including NUL *** ../vim-9.0.0012/src/globals.h 2022-06-19 12:17:14.000000000 +0100 --- src/globals.h 2022-06-30 16:44:09.300491955 +0100 *************** *** 1654,1659 **** --- 1654,1660 ---- EXTERN int disable_vterm_title_for_testing INIT(= FALSE); EXTERN long override_sysinfo_uptime INIT(= -1); EXTERN int override_autoload INIT(= FALSE); + EXTERN int ml_get_alloc_lines INIT(= FALSE); EXTERN int in_free_unref_items INIT(= FALSE); #endif *** ../vim-9.0.0012/src/testdir/runtest.vim 2022-06-15 18:59:52.000000000 +0100 --- src/testdir/runtest.vim 2022-06-30 22:07:31.526102234 +0100 *************** *** 154,159 **** --- 154,163 ---- " Prepare for calling test_garbagecollect_now(). let v:testing = 1 + " By default, copy each buffer line into allocated memory, so that valgrind can + " detect accessing memory before and after it. + call test_override('alloc_lines', 1) + " Support function: get the alloc ID by name. function GetAllocId(name) exe 'split ' . s:srcdir . '/alloc.h' *************** *** 182,188 **** " mode message. set noshowmode ! " Clear any overrides. call test_override('ALL', 0) " Some tests wipe out buffers. To be consistent, always wipe out all --- 186,192 ---- " mode message. set noshowmode ! " Clear any overrides, except "alloc_lines". call test_override('ALL', 0) " Some tests wipe out buffers. To be consistent, always wipe out all *** ../vim-9.0.0012/src/memline.c 2022-05-24 21:22:09.000000000 +0100 --- src/memline.c 2022-06-30 21:06:18.808784571 +0100 *************** *** 858,864 **** if (buf->b_ml.ml_mfp == NULL) // not open return; mf_close(buf->b_ml.ml_mfp, del_file); // close the .swp file ! if (buf->b_ml.ml_line_lnum != 0 && (buf->b_ml.ml_flags & ML_LINE_DIRTY)) vim_free(buf->b_ml.ml_line_ptr); vim_free(buf->b_ml.ml_stack); #ifdef FEAT_BYTEOFF --- 858,865 ---- if (buf->b_ml.ml_mfp == NULL) // not open return; mf_close(buf->b_ml.ml_mfp, del_file); // close the .swp file ! if (buf->b_ml.ml_line_lnum != 0 ! && (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED))) vim_free(buf->b_ml.ml_line_ptr); vim_free(buf->b_ml.ml_stack); #ifdef FEAT_BYTEOFF *************** *** 2620,2626 **** --recursive; } ml_flush_line(buf); - buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; errorret: STRCPY(questions, "???"); buf->b_ml.ml_line_len = 4; --- 2621,2626 ---- *************** *** 2686,2702 **** buf->b_ml.ml_line_ptr = (char_u *)dp + start; buf->b_ml.ml_line_len = len; buf->b_ml.ml_line_lnum = lnum; ! buf->b_ml.ml_flags &= ~ML_LINE_DIRTY; } if (will_change) buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); return buf->b_ml.ml_line_ptr; } /* * Check if a line that was just obtained by a call to ml_get * is in allocated memory. */ int ml_line_alloced(void) --- 2686,2729 ---- buf->b_ml.ml_line_ptr = (char_u *)dp + start; buf->b_ml.ml_line_len = len; buf->b_ml.ml_line_lnum = lnum; ! buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED); } if (will_change) + { buf->b_ml.ml_flags |= (ML_LOCKED_DIRTY | ML_LOCKED_POS); + #ifdef FEAT_EVAL + if (ml_get_alloc_lines && (buf->b_ml.ml_flags & ML_ALLOCATED)) + // can't make the change in the data block + buf->b_ml.ml_flags |= ML_LINE_DIRTY; + #endif + } + + #ifdef FEAT_EVAL + if (ml_get_alloc_lines + && (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) == 0) + { + char_u *p = alloc(buf->b_ml.ml_line_len); + // make sure the text is in allocated memory + if (p != NULL) + { + memmove(p, buf->b_ml.ml_line_ptr, buf->b_ml.ml_line_len); + buf->b_ml.ml_line_ptr = p; + buf->b_ml.ml_flags |= ML_ALLOCATED; + if (will_change) + // can't make the change in the data block + buf->b_ml.ml_flags |= ML_LINE_DIRTY; + } + } + #endif return buf->b_ml.ml_line_ptr; } /* * Check if a line that was just obtained by a call to ml_get * is in allocated memory. + * This ignores ML_ALLOCATED to get the same behavior as without the test + * override. */ int ml_line_alloced(void) *************** *** 3409,3414 **** --- 3436,3443 ---- * "len_arg" is the length of the text, excluding NUL. * If "has_props" is TRUE then "line_arg" includes the text properties and * "len_arg" includes the NUL of the text. + * When "copy" is TRUE copy the text into allocated memory, otherwise + * "line_arg" must be allocated and will be consumed here. */ int ml_replace_len( *************** *** 3454,3460 **** { // another line is buffered, flush it ml_flush_line(curbuf); - curbuf->b_ml.ml_flags &= ~ML_LINE_DIRTY; #ifdef FEAT_PROP_POPUP if (curbuf->b_has_textprop && !has_props) --- 3483,3488 ---- *************** *** 3488,3495 **** } #endif ! if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) // same line allocated ! vim_free(curbuf->b_ml.ml_line_ptr); // free it curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_len = len; --- 3516,3523 ---- } #endif ! if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) ! vim_free(curbuf->b_ml.ml_line_ptr); // free allocated line curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_len = len; *************** *** 4064,4070 **** --- 4092,4101 ---- entered = FALSE; } + else if (buf->b_ml.ml_flags & ML_ALLOCATED) + vim_free(buf->b_ml.ml_line_ptr); + buf->b_ml.ml_flags &= ~(ML_LINE_DIRTY | ML_ALLOCATED); buf->b_ml.ml_line_lnum = 0; } *** ../vim-9.0.0012/src/edit.c 2022-06-26 12:58:24.000000000 +0100 --- src/edit.c 2022-06-30 21:29:37.178822506 +0100 *************** *** 5013,5019 **** mch_memmove(newp + col, ptr + i, curbuf->b_ml.ml_line_len - col - i); ! if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) vim_free(curbuf->b_ml.ml_line_ptr); curbuf->b_ml.ml_line_ptr = newp; curbuf->b_ml.ml_line_len -= i; --- 5013,5019 ---- mch_memmove(newp + col, ptr + i, curbuf->b_ml.ml_line_len - col - i); ! if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) vim_free(curbuf->b_ml.ml_line_ptr); curbuf->b_ml.ml_line_ptr = newp; curbuf->b_ml.ml_line_len -= i; *************** *** 5232,5241 **** } // try to advance to the cursor column temp = 0; line = ptr = ml_get(lnum); prev_ptr = ptr; - validate_virtcol(); while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { prev_ptr = ptr; --- 5232,5241 ---- } // try to advance to the cursor column + validate_virtcol(); temp = 0; line = ptr = ml_get(lnum); prev_ptr = ptr; while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { prev_ptr = ptr; *** ../vim-9.0.0012/src/ops.c 2022-05-21 19:32:33.000000000 +0100 --- src/ops.c 2022-06-30 21:27:04.875745604 +0100 *************** *** 1273,1278 **** --- 1273,1280 ---- netbeans_removed(curbuf, pos.lnum, bd.textcol, (long)bd.textlen); + // get the line again, it may have been flushed + ptr = ml_get_buf(curbuf, pos.lnum, FALSE); netbeans_inserted(curbuf, pos.lnum, bd.textcol, &ptr[bd.textcol], bd.textlen); } *************** *** 1322,1327 **** --- 1324,1331 ---- ptr = ml_get_buf(curbuf, pos.lnum, FALSE); count = (int)STRLEN(ptr) - pos.col; netbeans_removed(curbuf, pos.lnum, pos.col, (long)count); + // get the line again, it may have been flushed + ptr = ml_get_buf(curbuf, pos.lnum, FALSE); netbeans_inserted(curbuf, pos.lnum, pos.col, &ptr[pos.col], count); pos.col = 0; *************** *** 1330,1335 **** --- 1334,1341 ---- ptr = ml_get_buf(curbuf, pos.lnum, FALSE); count = oap->end.col - pos.col + 1; netbeans_removed(curbuf, pos.lnum, pos.col, (long)count); + // get the line again, it may have been flushed + ptr = ml_get_buf(curbuf, pos.lnum, FALSE); netbeans_inserted(curbuf, pos.lnum, pos.col, &ptr[pos.col], count); } *** ../vim-9.0.0012/src/textprop.c 2022-06-16 11:34:23.000000000 +0100 --- src/textprop.c 2022-06-30 17:52:08.685848503 +0100 *************** *** 287,293 **** props + i * sizeof(textprop_T), sizeof(textprop_T) * (proplen - i)); ! if (buf->b_ml.ml_flags & ML_LINE_DIRTY) vim_free(buf->b_ml.ml_line_ptr); buf->b_ml.ml_line_ptr = newtext; buf->b_ml.ml_line_len += sizeof(textprop_T); --- 287,293 ---- props + i * sizeof(textprop_T), sizeof(textprop_T) * (proplen - i)); ! if (buf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) vim_free(buf->b_ml.ml_line_ptr); buf->b_ml.ml_line_ptr = newtext; buf->b_ml.ml_line_len += sizeof(textprop_T); *************** *** 564,570 **** mch_memmove(newtext, text, textlen); if (len > 0) mch_memmove(newtext + textlen, props, len); ! if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) vim_free(curbuf->b_ml.ml_line_ptr); curbuf->b_ml.ml_line_ptr = newtext; curbuf->b_ml.ml_line_len = textlen + len; --- 564,570 ---- mch_memmove(newtext, text, textlen); if (len > 0) mch_memmove(newtext + textlen, props, len); ! if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) vim_free(curbuf->b_ml.ml_line_ptr); curbuf->b_ml.ml_line_ptr = newtext; curbuf->b_ml.ml_line_len = textlen + len; *************** *** 698,703 **** --- 698,705 ---- // need to allocate the line now if (newtext == NULL) return; + if (buf->b_ml.ml_flags & ML_ALLOCATED) + vim_free(buf->b_ml.ml_line_ptr); buf->b_ml.ml_line_ptr = newtext; buf->b_ml.ml_flags |= ML_LINE_DIRTY; } *************** *** 1273,1278 **** --- 1275,1282 ---- return; mch_memmove(newptr, buf->b_ml.ml_line_ptr, buf->b_ml.ml_line_len); + if (buf->b_ml.ml_flags & ML_ALLOCATED) + vim_free(buf->b_ml.ml_line_ptr); buf->b_ml.ml_line_ptr = newptr; buf->b_ml.ml_flags |= ML_LINE_DIRTY; *************** *** 1766,1773 **** colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T); if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0) ! curbuf->b_ml.ml_line_ptr = ! vim_memsave(curbuf->b_ml.ml_line_ptr, newlen); curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; curbuf->b_ml.ml_line_len = newlen; } --- 1770,1782 ---- colnr_T newlen = (int)textlen + wi * (colnr_T)sizeof(textprop_T); if ((curbuf->b_ml.ml_flags & ML_LINE_DIRTY) == 0) ! { ! char_u *p = vim_memsave(curbuf->b_ml.ml_line_ptr, newlen); ! ! if (curbuf->b_ml.ml_flags & ML_ALLOCATED) ! vim_free(curbuf->b_ml.ml_line_ptr); ! curbuf->b_ml.ml_line_ptr = p; ! } curbuf->b_ml.ml_flags |= ML_LINE_DIRTY; curbuf->b_ml.ml_line_len = newlen; } *** ../vim-9.0.0012/src/cindent.c 2022-05-21 19:11:25.000000000 +0100 --- src/cindent.c 2022-06-30 20:29:34.168129338 +0100 *************** *** 2794,2801 **** break; } - l = ml_get_curline(); - // If we're in a comment or raw string now, skip to // the start of it. trypos = ind_find_start_CORS(NULL); --- 2794,2799 ---- *************** *** 2806,2811 **** --- 2804,2811 ---- continue; } + l = ml_get_curline(); + // Skip preprocessor directives and blank lines. if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) *************** *** 2905,2912 **** < ourscope - FIND_NAMESPACE_LIM) break; - l = ml_get_curline(); - // If we're in a comment or raw string now, skip // to the start of it. trypos = ind_find_start_CORS(NULL); --- 2905,2910 ---- *************** *** 2917,2922 **** --- 2915,2922 ---- continue; } + l = ml_get_curline(); + // Skip preprocessor directives and blank lines. if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) *************** *** 3196,3206 **** && trypos->col < tryposBrace->col))) trypos = NULL; // If we are looking for ',', we also look for matching // braces. ! if (trypos == NULL && terminated == ',' ! && find_last_paren(l, '{', '}')) ! trypos = find_start_brace(); if (trypos != NULL) { --- 3196,3211 ---- && trypos->col < tryposBrace->col))) trypos = NULL; + l = ml_get_curline(); + // If we are looking for ',', we also look for matching // braces. ! if (trypos == NULL && terminated == ',') ! { ! if (find_last_paren(l, '{', '}')) ! trypos = find_start_brace(); ! l = ml_get_curline(); ! } if (trypos != NULL) { *************** *** 3233,3238 **** --- 3238,3244 ---- --curwin->w_cursor.lnum; curwin->w_cursor.col = 0; } + l = ml_get_curline(); } // Get indent and pointer to text for current line, *** ../vim-9.0.0012/src/normal.c 2022-06-16 13:02:16.000000000 +0100 --- src/normal.c 2022-06-30 21:12:40.915606329 +0100 *************** *** 5120,5125 **** --- 5120,5127 ---- count = (int)STRLEN(ptr) - pos.col; netbeans_removed(curbuf, pos.lnum, pos.col, (long)count); + // line may have been flushed, get it again + ptr = ml_get(pos.lnum); netbeans_inserted(curbuf, pos.lnum, pos.col, &ptr[pos.col], count); } *** ../vim-9.0.0012/src/netbeans.c 2022-06-16 11:08:29.000000000 +0100 --- src/netbeans.c 2022-06-30 21:15:41.199085164 +0100 *************** *** 2741,2753 **** if (nbbuf->insertDone) nbbuf->modified = 1; pos.lnum = linenr; pos.col = col; off = pos2off(bufp, &pos); - // send the "insert" EVT - newtxt = alloc(newlen + 1); - vim_strncpy(newtxt, txt, newlen); p = nb_quote(newtxt); if (p != NULL) { --- 2741,2755 ---- if (nbbuf->insertDone) nbbuf->modified = 1; + // send the "insert" EVT + newtxt = alloc(newlen + 1); + vim_strncpy(newtxt, txt, newlen); + + // Note: this may make "txt" invalid pos.lnum = linenr; pos.col = col; off = pos2off(bufp, &pos); p = nb_quote(newtxt); if (p != NULL) { *** ../vim-9.0.0012/src/change.c 2022-05-31 13:31:50.000000000 +0100 --- src/change.c 2022-06-30 21:38:24.380228427 +0100 *************** *** 1535,1547 **** { // End of C comment, indent should line up // with the line containing the start of ! // the comment curwin->w_cursor.col = (colnr_T)(p - ptr); if ((pos = findmatch(NULL, NUL)) != NULL) { curwin->w_cursor.lnum = pos->lnum; newindent = get_indent(); } } } } --- 1535,1551 ---- { // End of C comment, indent should line up // with the line containing the start of ! // the comment. curwin->w_cursor.col = (colnr_T)(p - ptr); if ((pos = findmatch(NULL, NUL)) != NULL) { curwin->w_cursor.lnum = pos->lnum; newindent = get_indent(); + break; } + // this may make "ptr" invalid, get it again + ptr = ml_get(curwin->w_cursor.lnum); + p = ptr + curwin->w_cursor.col; } } } *** ../vim-9.0.0012/src/testdir/test_edit.vim 2022-06-19 15:20:24.000000000 +0100 --- src/testdir/test_edit.vim 2022-06-30 17:32:53.183525611 +0100 *************** *** 1860,1865 **** --- 1860,1868 ---- call writefile(lines, 'Xtest_edit_insertmode_ex_edit') let buf = RunVimInTerminal('-S Xtest_edit_insertmode_ex_edit', #{rows: 6}) + " Somehow this can be very slow with valgrind. A separate TermWait() works + " better than a longer time with WaitForAssert() (why?) + call TermWait(buf, 1000) call WaitForAssert({-> assert_match('^-- INSERT --\s*$', term_getline(buf, 6))}) call term_sendkeys(buf, "\\") call WaitForAssert({-> assert_notmatch('^-- INSERT --\s*$', term_getline(buf, 6))}) *** ../vim-9.0.0012/src/testdir/test_breakindent.vim 2022-05-06 12:05:02.000000000 +0100 --- src/testdir/test_breakindent.vim 2022-06-30 22:11:10.065395673 +0100 *************** *** 10,16 **** source view_util.vim source screendump.vim ! let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" func s:screen_lines(lnum, width) abort return ScreenLines([a:lnum, a:lnum + 2], a:width) --- 10,18 ---- source view_util.vim source screendump.vim ! func SetUp() ! let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" ! endfunc func s:screen_lines(lnum, width) abort return ScreenLines([a:lnum, a:lnum + 2], a:width) *************** *** 714,719 **** --- 716,724 ---- endfunc func Test_breakindent20_list() + " FIXME - this should not matter + call test_override('alloc_lines', 0) + call s:test_windows('setl breakindent breakindentopt= linebreak') " default: call setline(1, [' 1. Congress shall make no law', *************** *** 830,835 **** --- 835,843 ---- let lines = s:screen_lines2(1, 6, 20) call s:compare_lines(expect, lines) call s:close_windows('set breakindent& briopt& linebreak& list& listchars& showbreak&') + + " FIXME - this should not matter + call test_override('alloc_lines', 1) endfunc " The following used to crash Vim. This is fixed by 8.2.3391. *************** *** 873,887 **** endfunc func Test_no_spurious_match() let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50)) call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls') let @/ = '\%>3v[y]' redraw! call searchcount().total->assert_equal(1) " cleanup set hls&vim - let s:input = "\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" bwipeout! endfunc func Test_no_extra_indent() --- 881,900 ---- endfunc func Test_no_spurious_match() + " FIXME - fails under valgrind - this should not matter - timing issue? + call test_override('alloc_lines', 0) + let s:input = printf('- y %s y %s', repeat('x', 50), repeat('x', 50)) call s:test_windows('setl breakindent breakindentopt=list:-1 formatlistpat=^- hls') let @/ = '\%>3v[y]' redraw! call searchcount().total->assert_equal(1) + " cleanup set hls&vim bwipeout! + " FIXME - this should not matter + call test_override('alloc_lines', 1) endfunc func Test_no_extra_indent() *************** *** 945,952 **** endfunc func Test_breakindent_column() - " restore original - let s:input ="\tabcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOP" call s:test_windows('setl breakindent breakindentopt=column:10') redraw! " 1) default: does not indent, too wide :( --- 958,963 ---- *** ../vim-9.0.0012/src/version.c 2022-06-30 16:25:14.782979300 +0100 --- src/version.c 2022-06-30 17:33:10.191399066 +0100 *************** *** 737,738 **** --- 737,740 ---- { /* Add new patch number below this line */ + /**/ + 13, /**/ -- `The Guide says there is an art to flying,' said Ford, `or at least a knack. The knack lies in learning how to throw yourself at the ground and miss.' He smiled weakly. -- Douglas Adams, "The Hitchhiker's Guide to the Galaxy" /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///