To: vim-dev@vim.org Subject: Patch 6.1.142 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=ISO-8859-1 Content-Transfer-Encoding: 8bit ------------ Patch 6.1.142 Problem: Defining paragraphs without a separating blank line isn't possible. Paragraphs can't be formatted automatically. Solution: Allow defining paragraphs with lines that end in white space. Added the 'w' and 'a' flags in 'formatoptions'. Files: runtime/doc/change.txt, src/edit.c, src/misc1.c, src/normal.c, src/option.h, src/ops.c, src/proto/edit.pro, src/proto/ops.pro, src/vim.h *** ../vim61.141/runtime/doc/change.txt Fri Mar 22 21:18:35 2002 --- runtime/doc/change.txt Sun Jul 28 21:37:34 2002 *************** *** 1,4 **** ! *change.txt* For Vim version 6.1. Last change: 2002 Feb 17 VIM REFERENCE MANUAL by Bram Moolenaar --- 1,4 ---- ! *change.txt* For Vim version 6.1. Last change: 2002 Jul 28 VIM REFERENCE MANUAL by Bram Moolenaar *************** *** 1235,1240 **** --- 1236,1245 ---- Note that formatting will not change blank lines or lines containing only the comment leader. A new paragraph starts after such a line, or when the comment leader changes. + w Trailing white space indicates a paragraph continues in the next line. + A line that ends in a non-white character ends a paragraph. + a Automatic formatting of paragraphs. Every time text is inserted or + deleted the paragraph will be reformatted. See |auto-format|. n When formatting text, recognize numbered lists. The indent of the text after the number is used for the next line. The number may optionally be followed by '.', ':', ')', ']' or '}'. Note that *************** *** 1312,1315 **** --- 1317,1359 ---- < for Mail/news (format all, don't start comment with "o" command): > :set fo=tcrq < + + Automatic formatting *auto-format* + + When the 'a' flag is present in 'formatoptions' text is formatted + automatically when inserting text or deleting text. This works nice for + editing text paragraphs. A few hints on how to use this: + + - You need to properly define paragraphs. The simplest is paragraphs that are + separated by a blank line. When there is no separating blank line, consider + using the 'w' flag and adding a space at the end of each line in the + paragraphs except the last one. + + - You can set the 'formatoptions' based on the type of file |filetype| or + specifically for one file with a |modeline|. + + - Set 'formatoptions' to "aw2tcq" to make text with indents like this: + + bla bla foobar bla + bla foobar bla foobar bla + bla bla foobar bla + bla foobar bla bla foobar + + And a few warnings: + + - When part of the text is not properly separated in paragraphs, making + changes in this text will cause it to be formatted anyway. Consider doing > + + :set fo-=a + + - When using the 'w' flag (trailing space means paragraph continues) and + deleting the last line of a paragraph with |dd|, the paragraph will be + joined with the next one. + + - Changed text is saved for undo. Formatting is also a change. Thus each + format action saves text for undo. This may consume quite a lot of memory. + + - Formatting a long paragraph and/or with complicated indenting may be slow. + + vim:tw=78:ts=8:ft=help:norl: *** ../vim61.141/src/edit.c Thu Jun 6 20:36:15 2002 --- src/edit.c Sun Jul 28 21:34:42 2002 *************** *** 121,126 **** --- 121,129 ---- static void ins_ctrl_v __ARGS((void)); static void undisplay_dollar __ARGS((void)); static void insert_special __ARGS((int, int, int)); + #ifdef FEAT_COMMENTS + static int cmplen __ARGS((char_u *s1, char_u *s2)); + #endif static void redo_literal __ARGS((int c)); static void start_arrow __ARGS((pos_T *end_insert_pos)); static void stop_insert __ARGS((pos_T *end_insert_pos)); *************** *** 825,830 **** --- 828,834 ---- /* insert the contents of a register */ case Ctrl_R: ins_reg(); + auto_format(); inserted_space = FALSE; break; *************** *** 919,924 **** --- 923,929 ---- } # endif ins_shift(c, lastc); + auto_format(); inserted_space = FALSE; break; *************** *** 926,947 **** --- 931,956 ---- case K_DEL: case K_KDEL: ins_del(); + auto_format(); break; /* delete character before the cursor */ case K_BS: case Ctrl_H: did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space); + auto_format(); break; /* delete word before the cursor */ case Ctrl_W: did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space); + auto_format(); break; /* delete all inserted text in current line */ case Ctrl_U: did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space); + auto_format(); inserted_space = FALSE; break; *************** *** 1052,1057 **** --- 1061,1067 ---- inserted_space = FALSE; if (ins_tab()) goto normalchar; /* insert TAB as a normal char */ + auto_format(); break; case K_KENTER: *************** *** 1069,1074 **** --- 1079,1085 ---- #endif if (ins_eol(c) && !p_im) goto doESCkey; /* out of memory */ + auto_format(); inserted_space = FALSE; break; *************** *** 1182,1187 **** --- 1193,1199 ---- revins_legal++; #endif c = Ctrl_V; /* pretend CTRL-V is last character */ + auto_format(); } } break; *************** *** 1221,1226 **** --- 1233,1241 ---- revins_chars++; #endif } + + auto_format(); + #ifdef FEAT_FOLDING /* When inserting a character the cursor line must never be in a * closed fold. */ *************** *** 2358,2363 **** --- 2373,2380 ---- curwin->w_cursor.col++; } + auto_format(); + ins_compl_free(); started_completion = FALSE; completion_matches = 0; *************** *** 3704,3709 **** --- 3721,3727 ---- int no_leader = FALSE; int do_comments = (flags & INSCHAR_DO_COM); #endif + int fo_white_par; int first_line = TRUE; int fo_ins_blank; #ifdef FEAT_MBYTE *************** *** 3717,3726 **** --- 3735,3747 ---- #ifdef FEAT_MBYTE fo_multibyte = has_format_option(FO_MBYTE_BREAK); #endif + fo_white_par = has_format_option(FO_WHITE_PAR); /* * Try to break the line in two or more pieces when: * - Always do this if we have been called to do formatting only. + * - Always do this when 'formatoptions' has the 'a' flag and the line + * ends in white space. * - Otherwise: * - Don't do this if inserting a blank * - Don't do this if an existing character is being replaced, unless *************** *** 3940,3952 **** saved_text[startcol] = NUL; /* Backspace over characters that will move to the next line */ ! backspace_until_column(foundcol); } else #endif { /* put cursor after pos. to break line */ ! curwin->w_cursor.col = foundcol; } /* --- 3961,3975 ---- saved_text[startcol] = NUL; /* Backspace over characters that will move to the next line */ ! if (!fo_white_par) ! backspace_until_column(foundcol); } else #endif { /* put cursor after pos. to break line */ ! if (!fo_white_par) ! curwin->w_cursor.col = foundcol; } /* *************** *** 3954,3959 **** --- 3977,3983 ---- * Only insert/delete lines, but don't really redraw the window. */ open_line(FORWARD, OPENLINE_DELSPACES + + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) #ifdef FEAT_COMMENTS + (do_comments ? OPENLINE_DO_COM : 0) #endif *************** *** 4195,4200 **** --- 4219,4320 ---- } } } + + /* + * Called after inserting or deleting text: When 'formatoptions' includes the + * 'a' flag format from the current line until the end of the paragraph. + * Keep the cursor at the same position relative to the text. + * The caller must have saved the cursor line for undo, following ones will be + * saved here. + */ + void + auto_format() + { + pos_T pos; + colnr_T len; + char_u *old, *pold; + char_u *new, *pnew; + + if (!has_format_option(FO_AUTO)) + return; + + pos = curwin->w_cursor; + old = ml_get_curline(); + + /* Don't format in Insert mode when the cursor is on a trailing blank, the + * user might insert normal text next. */ + if (*old != NUL && pos.col == STRLEN(old) && vim_iswhite(old[pos.col - 1])) + return; + + old = vim_strsave(old); + format_lines((linenr_T)-1); + + /* Advance to the same text position again. This is tricky, indents + * may have changed and comment leaders may have been inserted. */ + curwin->w_cursor.lnum = pos.lnum; + curwin->w_cursor.col = 0; + pold = old; + while (1) + { + if (curwin->w_cursor.lnum >= curbuf->b_ml.ml_line_count) + { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + curwin->w_cursor.col = MAXCOL; + break; + } + /* Make "pold" and "pnew" point to the start of the line, ignoring + * indent and comment leader. */ + pold = skipwhite(pold); + new = ml_get_curline(); + pnew = skipwhite(new); + #ifdef FEAT_COMMENTS + len = get_leader_len(new, NULL, FALSE); + if (len > 0) + { + char_u *p; + + /* Skip the leader if the old text matches after it, ignoring + * white space. Keep in mind that the leader may appear in + * the text! */ + p = skipwhite(new + len); + if (cmplen(pold, pnew) < cmplen(pold, p)) + pnew = p; + } + #endif + + len = (colnr_T)STRLEN(pnew); + if ((pold - old) + len >= pos.col) + { + curwin->w_cursor.col = pos.col - (pold - old) + (pnew - new); + break; + } + /* Cursor wraps to next line */ + ++curwin->w_cursor.lnum; + pold += len; + } + check_cursor(); + vim_free(old); + } + + #ifdef FEAT_COMMENTS + /* + * Return the number of bytes for which strings "s1" and "s2" are equal. + */ + static int + cmplen(s1, s2) + char_u *s1; + char_u *s2; + { + char_u *p1 = s1, *p2 = s2; + + while (*p1 == *p2 && *p1 != NUL) + { + ++p1; + ++p2; + } + return (int)(p1 - s1); + } + #endif /* * Find out textwidth to be used for formatting: *** ../vim61.141/src/misc1.c Sun May 5 22:51:14 2002 --- src/misc1.c Sun Jul 14 17:38:10 2002 *************** *** 1049,1055 **** { /* truncate current line at cursor */ saved_line[curwin->w_cursor.col] = NUL; ! if (trunc_line) /* Remove trailing white space */ truncate_spaces(saved_line); ml_replace(curwin->w_cursor.lnum, saved_line, FALSE); saved_line = NULL; --- 1049,1056 ---- { /* truncate current line at cursor */ saved_line[curwin->w_cursor.col] = NUL; ! /* Remove trailing white space, unless OPENLINE_KEEPTRAIL used. */ ! if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) truncate_spaces(saved_line); ml_replace(curwin->w_cursor.lnum, saved_line, FALSE); saved_line = NULL; *** ../vim61.141/src/normal.c Sun Jul 21 21:47:39 2002 --- src/normal.c Sun Jul 14 21:53:05 2002 *************** *** 1701,1706 **** --- 1701,1707 ---- oap->is_VIsual ? (int)cap->count1 : #endif 1); + auto_format(); break; case OP_JOIN_NS: *************** *** 1711,1717 **** --- 1712,1721 ---- curbuf->b_ml.ml_line_count) beep_flush(); else + { do_do_join(oap->line_count, oap->op_type == OP_JOIN); + auto_format(); + } break; case OP_DELETE: *************** *** 1721,1727 **** --- 1725,1734 ---- if (empty_region_error) vim_beep(); else + { (void)op_delete(oap); + auto_format(); + } break; case OP_YANK: *************** *** 1833,1838 **** --- 1840,1849 ---- op_insert(oap, cap->count1); + /* TODO: when inserting in several lines, should format all + * the lines. */ + auto_format(); + if (restart_edit == 0) restart_edit = restart_edit_save; } *************** *** 7987,7992 **** --- 7998,8004 ---- did_visual_put = TRUE; /* tell op_delete() to correct '] mark */ } #endif + auto_format(); } } *** ../vim61.141/src/option.h Wed Mar 6 22:16:36 2002 --- src/option.h Sun Jul 14 17:07:26 2002 *************** *** 107,116 **** #define FO_MBYTE_JOIN 'M' /* no space before/after multi-byte char */ #define FO_MBYTE_JOIN2 'B' /* no space between multi-byte chars */ #define FO_ONE_LETTER '1' #define DFLT_FO_VI "vt" #define DFLT_FO_VIM "tcq" ! #define FO_ALL "tcroq2vlb1mMBn," /* for do_set() */ /* characters for the p_cpo option: */ #define CPO_ALTREAD 'a' /* ":read" sets alternate file name */ --- 107,118 ---- #define FO_MBYTE_JOIN 'M' /* no space before/after multi-byte char */ #define FO_MBYTE_JOIN2 'B' /* no space between multi-byte chars */ #define FO_ONE_LETTER '1' + #define FO_WHITE_PAR 'w' /* trailing white space continues paragr. */ + #define FO_AUTO 'a' /* automatic formatting */ #define DFLT_FO_VI "vt" #define DFLT_FO_VIM "tcq" ! #define FO_ALL "tcroq2vlb1mMBn,aw" /* for do_set() */ /* characters for the p_cpo option: */ #define CPO_ALTREAD 'a' /* ":read" sets alternate file name */ *** ../vim61.141/src/ops.c Sun May 19 21:31:03 2002 --- src/ops.c Sun Jul 28 21:46:09 2002 *************** *** 111,116 **** --- 111,117 ---- #if defined(FEAT_CLIPBOARD) || defined(FEAT_EVAL) static void str_to_reg __ARGS((struct yankreg *y_ptr, int type, char_u *str, long len)); #endif + static int ends_in_white __ARGS((linenr_T lnum)); #ifdef FEAT_COMMENTS static int same_leader __ARGS((int, char_u *, int, char_u *)); static int fmt_check_par __ARGS((linenr_T, int *, char_u **, int do_comments)); *************** *** 2415,2421 **** copy_spaces(newp + offset, (size_t)vpos.coladd); offset += vpos.coladd; # endif ! mch_memmove(newp + offset, ins_text, ins_len); offset += ins_len; oldp += bd.textcol; mch_memmove(newp + offset, oldp, STRLEN(oldp) + 1); --- 2416,2422 ---- copy_spaces(newp + offset, (size_t)vpos.coladd); offset += vpos.coladd; # endif ! mch_memmove(newp + offset, ins_text, (size_t)ins_len); offset += ins_len; oldp += bd.textcol; mch_memmove(newp + offset, oldp, STRLEN(oldp) + 1); *************** *** 3984,3989 **** --- 3985,4052 ---- oparg_T *oap; { long old_line_count = curbuf->b_ml.ml_line_count; + + if (u_save((linenr_T)(oap->start.lnum - 1), + (linenr_T)(oap->end.lnum + 1)) == FAIL) + return; + + #ifdef FEAT_VISUAL + if (oap->is_VIsual) + /* When there is no change: need to remove the Visual selection */ + redraw_curbuf_later(INVERTED); + #endif + + /* Set '[ mark at the start of the formatted area */ + curbuf->b_op_start = oap->start; + + format_lines(oap->line_count); + + /* + * Leave the cursor at the first non-blank of the last formatted line. + * If the cursor was moved one line back (e.g. with "Q}") go to the next + * line, so "." will do the next lines. + */ + if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) + ++curwin->w_cursor.lnum; + beginline(BL_WHITE | BL_FIX); + old_line_count = curbuf->b_ml.ml_line_count - old_line_count; + msgmore(old_line_count); + + /* put '] mark on the end of the formatted area */ + curbuf->b_op_end = curwin->w_cursor; + + #ifdef FEAT_VISUAL + if (oap->is_VIsual) + { + win_T *wp; + + FOR_ALL_WINDOWS(wp) + { + if (wp->w_old_cursor_lnum != 0) + { + /* When lines have been inserted or deleted, adjust the end of + * the Visual area to be redrawn. */ + if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) + wp->w_old_cursor_lnum += old_line_count; + else + wp->w_old_visual_lnum += old_line_count; + } + } + } + #endif + } + + /* + * Format "line_count" lines, starting at the cursor position. + * When "line_count" is negative, format until the end of the paragraph. + * Lines after the cursor line are saved for undo, caller must have saved the + * first line. + */ + void + format_lines(line_count) + linenr_T line_count; + { + int max_len; int is_not_par; /* current line not part of parag. */ int next_is_not_par; /* next line not part of paragraph */ int is_end_par; /* at end of paragraph */ *************** *** 4000,4034 **** int second_indent = -1; int do_second_indent; int do_number_indent; int first_par_line = TRUE; int smd_save; long count; int need_set_indent = TRUE; /* set indent of next paragraph */ int force_format = FALSE; ! int max_len; ! ! if (u_save((linenr_T)(oap->start.lnum - 1), ! (linenr_T)(oap->end.lnum + 1)) == FAIL) ! return; ! ! #ifdef FEAT_VISUAL ! if (oap->is_VIsual) ! /* When there is no change: need to remove the Visual selection */ ! redraw_curbuf_later(INVERTED); ! #endif /* length of a line to force formatting: 3 * 'tw' */ max_len = comp_textwidth(TRUE) * 3; - /* Set '[ mark at the start of the formatted area */ - curbuf->b_op_start = oap->start; - /* check for 'q', '2' and '1' in 'formatoptions' */ #ifdef FEAT_COMMENTS do_comments = has_format_option(FO_Q_COMS); #endif do_second_indent = has_format_option(FO_Q_SECOND); do_number_indent = has_format_option(FO_Q_NUMBER); /* * Get info about the previous and current line. --- 4063,4086 ---- int second_indent = -1; int do_second_indent; int do_number_indent; + int do_trail_white; int first_par_line = TRUE; int smd_save; long count; int need_set_indent = TRUE; /* set indent of next paragraph */ int force_format = FALSE; ! int old_State = State; /* length of a line to force formatting: 3 * 'tw' */ max_len = comp_textwidth(TRUE) * 3; /* check for 'q', '2' and '1' in 'formatoptions' */ #ifdef FEAT_COMMENTS do_comments = has_format_option(FO_Q_COMS); #endif do_second_indent = has_format_option(FO_Q_SECOND); do_number_indent = has_format_option(FO_Q_NUMBER); + do_trail_white = has_format_option(FO_WHITE_PAR); /* * Get info about the previous and current line. *************** *** 4047,4055 **** #endif ); is_end_par = (is_not_par || next_is_not_par); curwin->w_cursor.lnum--; ! for (count = oap->line_count; count > 0 && !got_int; --count) { /* * Advance to next paragraph. --- 4099,4109 ---- #endif ); is_end_par = (is_not_par || next_is_not_par); + if (!is_end_par && do_trail_white) + is_end_par = !ends_in_white(curwin->w_cursor.lnum - 1); curwin->w_cursor.lnum--; ! for (count = line_count; count != 0 && !got_int; --count) { /* * Advance to next paragraph. *************** *** 4068,4074 **** /* * The last line to be formatted. */ ! if (count == 1) { next_is_not_par = TRUE; #ifdef FEAT_COMMENTS --- 4122,4128 ---- /* * The last line to be formatted. */ ! if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) { next_is_not_par = TRUE; #ifdef FEAT_COMMENTS *************** *** 4089,4099 **** } advance = TRUE; is_end_par = (is_not_par || next_is_not_par || next_is_start_par); /* * Skip lines that are not in a paragraph. */ ! if (!is_not_par) { /* * For the first line of a paragraph, check indent of second line. --- 4143,4160 ---- } advance = TRUE; is_end_par = (is_not_par || next_is_not_par || next_is_start_par); + if (!is_end_par && do_trail_white) + is_end_par = !ends_in_white(curwin->w_cursor.lnum); /* * Skip lines that are not in a paragraph. */ ! if (is_not_par) ! { ! if (line_count < 0) ! break; ! } ! else { /* * For the first line of a paragraph, check indent of second line. *************** *** 4152,4164 **** + (do_comments ? INSCHAR_DO_COM : 0) #endif , second_indent); ! State = NORMAL; p_smd = smd_save; second_indent = -1; /* at end of par.: need to set indent of next par. */ need_set_indent = is_end_par; if (is_end_par) first_par_line = TRUE; force_format = FALSE; } --- 4213,4231 ---- + (do_comments ? INSCHAR_DO_COM : 0) #endif , second_indent); ! State = old_State; p_smd = smd_save; second_indent = -1; /* at end of par.: need to set indent of next par. */ need_set_indent = is_end_par; if (is_end_par) + { + /* When called with a negative line count, break at the + * end of the paragraph. */ + if (line_count < 0) + break; first_par_line = TRUE; + } force_format = FALSE; } *************** *** 4171,4176 **** --- 4238,4245 ---- advance = FALSE; curwin->w_cursor.lnum++; curwin->w_cursor.col = 0; + if (line_count < 0 && u_save_cursor() == FAIL) + break; #ifdef FEAT_COMMENTS (void)del_bytes((long)next_leader_len, FALSE); #endif *************** *** 4190,4229 **** } line_breakcheck(); } ! /* ! * Leave the cursor at the first non-blank of the last formatted line. ! * If the cursor was moved one line back (e.g. with "Q}") go to the next ! * line, so "." will do the next lines. ! */ ! if (oap->end_adjusted && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) ! ++curwin->w_cursor.lnum; ! beginline(BL_WHITE | BL_FIX); ! old_line_count = curbuf->b_ml.ml_line_count - old_line_count; ! msgmore(old_line_count); ! ! /* put '] mark on the end of the formatted area */ ! curbuf->b_op_end = curwin->w_cursor; ! ! #ifdef FEAT_VISUAL ! if (oap->is_VIsual) ! { ! win_T *wp; ! FOR_ALL_WINDOWS(wp) ! { ! if (wp->w_old_cursor_lnum != 0) ! { ! /* When lines have been inserted or deleted, adjust the end of ! * the Visual area to be redrawn. */ ! if (wp->w_old_cursor_lnum > wp->w_old_visual_lnum) ! wp->w_old_cursor_lnum += old_line_count; ! else ! wp->w_old_visual_lnum += old_line_count; ! } ! } ! } ! #endif } /* --- 4259,4276 ---- } line_breakcheck(); } + } ! /* ! * Return TRUE if line "lnum" ends in a white character. ! */ ! static int ! ends_in_white(lnum) ! linenr_T lnum; ! { ! char_u *s = ml_get(lnum); ! return (*s != NUL && vim_iswhite(s[STRLEN(s) - 1])); } /* *** ../vim61.141/src/proto/edit.pro Fri Mar 22 21:41:07 2002 --- src/proto/edit.pro Sun Jul 14 18:43:49 2002 *************** *** 13,18 **** --- 13,19 ---- void ins_compl_check_keys __ARGS((void)); int get_literal __ARGS((void)); void insertchar __ARGS((int c, int flags, int second_indent)); + void auto_format __ARGS((void)); int comp_textwidth __ARGS((int ff)); int stop_arrow __ARGS((void)); void set_last_insert __ARGS((int c)); *** ../vim61.141/src/proto/ops.pro Fri Mar 22 21:41:18 2002 --- src/proto/ops.pro Sun Jul 14 18:19:46 2002 *************** *** 30,35 **** --- 30,36 ---- void do_do_join __ARGS((long count, int insert_space)); int do_join __ARGS((int insert_space)); void op_format __ARGS((oparg_T *oap)); + void format_lines __ARGS((linenr_T line_count)); int do_addsub __ARGS((int command, linenr_T Prenum1)); int read_viminfo_register __ARGS((vir_T *virp, int force)); void write_viminfo_registers __ARGS((FILE *fp)); *** ../vim61.141/src/vim.h Wed Apr 10 10:36:02 2002 --- src/vim.h Sun Jul 14 17:38:58 2002 *************** *** 834,839 **** --- 834,840 ---- /* flags for open_line() */ #define OPENLINE_DELSPACES 1 /* delete spaces after cursor */ #define OPENLINE_DO_COM 2 /* format comments */ + #define OPENLINE_KEEPTRAIL 4 /* keep trailing spaces */ /* * There are four history tables: *** ../vim61.141/src/version.c Sun Jul 21 21:47:39 2002 --- src/version.c Sun Jul 28 21:48:28 2002 *************** *** 608,609 **** --- 608,611 ---- { /* Add new patch number below this line */ + /**/ + 142, /**/ -- Q: What is a patch 22? A: A patch you need to include to make it possible to include patches. /// Bram Moolenaar -- Bram@moolenaar.net -- http://www.moolenaar.net \\\ /// Creator of Vim -- http://vim.sf.net -- ftp://ftp.vim.org/pub/vim \\\ \\\ Project leader for A-A-P -- http://www.a-a-p.org /// \\\ Lord Of The Rings helps Uganda - http://iccf-holland.org/lotr.html ///