diff options
author | Bram Moolenaar <Bram@vim.org> | 2005-06-13 22:28:56 +0000 |
---|---|---|
committer | Bram Moolenaar <Bram@vim.org> | 2005-06-13 22:28:56 +0000 |
commit | 9ba0eb850c0f4c94df3b7f7461610bf0b073f712 (patch) | |
tree | 11638af8ad8ecdfd337a6db15914b2e2cdff3aea /src | |
parent | bac97eb8ae6b067466cab0481cac2f25b335ffe7 (diff) | |
download | vim-git-9ba0eb850c0f4c94df3b7f7461610bf0b073f712.tar.gz |
updated for version 7.0084v7.0084
Diffstat (limited to 'src')
-rw-r--r-- | src/Make_mvc.mak | 15 | ||||
-rw-r--r-- | src/eval.c | 1108 | ||||
-rw-r--r-- | src/ex_cmds.c | 4 | ||||
-rw-r--r-- | src/ex_docmd.c | 1 | ||||
-rw-r--r-- | src/ex_getln.c | 8 | ||||
-rw-r--r-- | src/gui_w32.c | 13 | ||||
-rw-r--r-- | src/gui_w48.c | 23 | ||||
-rw-r--r-- | src/if_cscope.c | 2 | ||||
-rw-r--r-- | src/if_python.c | 38 | ||||
-rw-r--r-- | src/misc1.c | 21 | ||||
-rw-r--r-- | src/misc2.c | 102 | ||||
-rw-r--r-- | src/normal.c | 7 | ||||
-rw-r--r-- | src/option.c | 30 | ||||
-rw-r--r-- | src/os_mswin.c | 18 | ||||
-rw-r--r-- | src/os_w32exe.c | 1 | ||||
-rw-r--r-- | src/os_win32.c | 14 | ||||
-rw-r--r-- | src/proto/misc1.pro | 1 | ||||
-rw-r--r-- | src/proto/misc2.pro | 1 | ||||
-rw-r--r-- | src/proto/spell.pro | 3 | ||||
-rw-r--r-- | src/spell.c | 2291 | ||||
-rw-r--r-- | src/tag.c | 11 | ||||
-rw-r--r-- | src/ui.c | 1 | ||||
-rw-r--r-- | src/version.h | 4 |
23 files changed, 3156 insertions, 561 deletions
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak index 4d4ea225e..eb5aa7e9e 100644 --- a/src/Make_mvc.mak +++ b/src/Make_mvc.mak @@ -110,7 +110,8 @@ TARGETOS = BOTH -# Select one of eight object code directories, depends on GUI, OLE and DEBUG. +# Select one of eight object code directories, depends on GUI, OLE, DEBUG and +# interfaces. # If you change something else, do "make clean" first! !if "$(GUI)" == "yes" OBJDIR = .\ObjG @@ -120,6 +121,18 @@ OBJDIR = .\ObjC !if "$(OLE)" == "yes" OBJDIR = $(OBJDIR)O !endif +!ifdef PERL +OBJDIR = $(OBJDIR)L +!endif +!ifdef PYTHON +OBJDIR = $(OBJDIR)Y +!endif +!ifdef TCL +OBJDIR = $(OBJDIR)T +!endif +!ifdef RUBY +OBJDIR = $(OBJDIR)R +!endif !ifdef MZSCHEME OBJDIR = $(OBJDIR)Z !endif diff --git a/src/eval.c b/src/eval.c index aa3935034..913f8ed0f 100644 --- a/src/eval.c +++ b/src/eval.c @@ -264,12 +264,11 @@ typedef struct #define VV_RO 2 /* read-only */ #define VV_RO_SBX 4 /* read-only in the sandbox */ -#define VV_NAME(s, t) s, sizeof(s) - 1, {{t}}, {0} +#define VV_NAME(s, t) s, {{t}}, {0} static struct vimvar { char *vv_name; /* name of variable, without v: */ - int vv_len; /* length of name */ dictitem_T vv_di; /* value and name for key */ char vv_filler[16]; /* space for LONGEST name below!!! */ char vv_flags; /* VV_COMPAT, VV_RO, VV_RO_SBX */ @@ -594,9 +593,12 @@ static void free_tv __ARGS((typval_T *varp)); static void clear_tv __ARGS((typval_T *varp)); static void init_tv __ARGS((typval_T *varp)); static long get_tv_number __ARGS((typval_T *varp)); +static long get_tv_number_chk __ARGS((typval_T *varp, int *denote)); static linenr_T get_tv_lnum __ARGS((typval_T *argvars)); static char_u *get_tv_string __ARGS((typval_T *varp)); +static char_u *get_tv_string_chk __ARGS((typval_T *varp)); static char_u *get_tv_string_buf __ARGS((typval_T *varp, char_u *buf)); +static char_u *get_tv_string_buf_chk __ARGS((typval_T *varp, char_u *buf)); static dictitem_T *find_var __ARGS((char_u *name, hashtab_T **htp)); static dictitem_T *find_var_in_ht __ARGS((hashtab_T *ht, char_u *varname, int writing)); static hashtab_T *find_var_ht __ARGS((char_u *name, char_u **varname)); @@ -1023,7 +1025,7 @@ eval_to_bool(arg, error, nextcmd, skip) *error = FALSE; if (!skip) { - retval = (get_tv_number(&tv) != 0); + retval = (get_tv_number_chk(&tv, error) != 0); clear_tv(&tv); } } @@ -1137,7 +1139,7 @@ eval_to_number(expr) retval = -1; else { - retval = get_tv_number(&rettv); + retval = get_tv_number_chk(&rettv, NULL); clear_tv(&rettv); } --emsg_off; @@ -1775,8 +1777,8 @@ ex_let_one(arg, tv, copy, endchars, op) { c1 = name[len]; name[len] = NUL; - p = get_tv_string(tv); - if (op != NULL && *op == '.') + p = get_tv_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { int mustfree = FALSE; char_u *s = vim_getenv(name, &mustfree); @@ -1789,15 +1791,18 @@ ex_let_one(arg, tv, copy, endchars, op) } } if (p != NULL) + { vim_setenv(name, p); - if (STRICMP(name, "HOME") == 0) - init_homedir(); - else if (didset_vim && STRICMP(name, "VIM") == 0) - didset_vim = FALSE; - else if (didset_vimruntime && STRICMP(name, "VIMRUNTIME") == 0) - didset_vimruntime = FALSE; + if (STRICMP(name, "HOME") == 0) + init_homedir(); + else if (didset_vim && STRICMP(name, "VIM") == 0) + didset_vim = FALSE; + else if (didset_vimruntime + && STRICMP(name, "VIMRUNTIME") == 0) + didset_vimruntime = FALSE; + arg_end = arg; + } name[len] = c1; - arg_end = arg; vim_free(tofree); } } @@ -1827,8 +1832,8 @@ ex_let_one(arg, tv, copy, endchars, op) *p = NUL; n = get_tv_number(tv); - s = get_tv_string(tv); - if (op != NULL && *op != '=') + s = get_tv_string_chk(tv); /* != NULL if number or string */ + if (s != NULL && op != NULL && *op != '=') { opt_type = get_option_value(arg, &numval, &stringval, opt_flags); @@ -1852,9 +1857,12 @@ ex_let_one(arg, tv, copy, endchars, op) } } } - set_option_value(arg, n, s, opt_flags); + if (s != NULL) + { + set_option_value(arg, n, s, opt_flags); + arg_end = p; + } *p = c1; - arg_end = p; vim_free(stringval); } } @@ -1875,8 +1883,8 @@ ex_let_one(arg, tv, copy, endchars, op) char_u *tofree = NULL; char_u *s; - p = get_tv_string(tv); - if (op != NULL && *op == '.') + p = get_tv_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { s = get_reg_contents(*arg == '@' ? '"' : *arg, FALSE, FALSE); if (s != NULL) @@ -1886,8 +1894,10 @@ ex_let_one(arg, tv, copy, endchars, op) } } if (p != NULL) + { write_reg_contents(*arg == '@' ? '"' : *arg, p, -1, FALSE); - arg_end = arg + 1; + arg_end = arg + 1; + } vim_free(tofree); } } @@ -2070,6 +2080,12 @@ get_lval(name, rettv, lp, unlet, skip, quiet, fne_flags) empty1 = FALSE; if (eval1(&p, &var1, TRUE) == FAIL) /* recursive! */ return NULL; + if (get_tv_string_chk(&var1) == NULL) + { + /* not a number or string */ + clear_tv(&var1); + return NULL; + } } /* Optionally get the second index [ :expr]. */ @@ -2104,6 +2120,14 @@ get_lval(name, rettv, lp, unlet, skip, quiet, fne_flags) clear_tv(&var1); return NULL; } + if (get_tv_string_chk(&var2) == NULL) + { + /* not a number or string */ + if (!empty1) + clear_tv(&var1); + clear_tv(&var2); + return NULL; + } } lp->ll_range = TRUE; } @@ -2130,7 +2154,7 @@ get_lval(name, rettv, lp, unlet, skip, quiet, fne_flags) if (len == -1) { /* "[key]": get key from "var1" */ - key = get_tv_string(&var1); + key = get_tv_string(&var1); /* is number or string */ if (*key == NUL) { if (!quiet) @@ -2176,7 +2200,7 @@ get_lval(name, rettv, lp, unlet, skip, quiet, fne_flags) lp->ll_n1 = 0; else { - lp->ll_n1 = get_tv_number(&var1); + lp->ll_n1 = get_tv_number(&var1); /* is number or string */ clear_tv(&var1); } lp->ll_dict = NULL; @@ -2199,7 +2223,7 @@ get_lval(name, rettv, lp, unlet, skip, quiet, fne_flags) */ if (lp->ll_range && !lp->ll_empty2) { - lp->ll_n2 = get_tv_number(&var2); + lp->ll_n2 = get_tv_number(&var2); /* is number or string */ clear_tv(&var2); if (lp->ll_n2 < 0) { @@ -3340,9 +3364,13 @@ eval1(arg, rettv, evaluate) result = FALSE; if (evaluate) { - if (get_tv_number(rettv) != 0) + int error = FALSE; + + if (get_tv_number_chk(rettv, &error) != 0) result = TRUE; clear_tv(rettv); + if (error) + return FAIL; } /* @@ -3398,6 +3426,7 @@ eval2(arg, rettv, evaluate) typval_T var2; long result; int first; + int error = FALSE; /* * Get the first variable. @@ -3414,9 +3443,11 @@ eval2(arg, rettv, evaluate) { if (evaluate && first) { - if (get_tv_number(rettv) != 0) + if (get_tv_number_chk(rettv, &error) != 0) result = TRUE; clear_tv(rettv); + if (error) + return FAIL; first = FALSE; } @@ -3432,9 +3463,11 @@ eval2(arg, rettv, evaluate) */ if (evaluate && !result) { - if (get_tv_number(&var2) != 0) + if (get_tv_number_chk(&var2, &error) != 0) result = TRUE; clear_tv(&var2); + if (error) + return FAIL; } if (evaluate) { @@ -3464,6 +3497,7 @@ eval3(arg, rettv, evaluate) typval_T var2; long result; int first; + int error = FALSE; /* * Get the first variable. @@ -3480,9 +3514,11 @@ eval3(arg, rettv, evaluate) { if (evaluate && first) { - if (get_tv_number(rettv) == 0) + if (get_tv_number_chk(rettv, &error) == 0) result = FALSE; clear_tv(rettv); + if (error) + return FAIL; first = FALSE; } @@ -3498,9 +3534,11 @@ eval3(arg, rettv, evaluate) */ if (evaluate && result) { - if (get_tv_number(&var2) == 0) + if (get_tv_number_chk(&var2, &error) == 0) result = FALSE; clear_tv(&var2); + if (error) + return FAIL; } if (evaluate) { @@ -3832,6 +3870,22 @@ eval5(arg, rettv, evaluate) if (op != '+' && op != '-' && op != '.') break; + if (op != '+' || rettv->v_type != VAR_LIST) + { + /* For "list + ...", an illegal use of the first operand as + * a number cannot be determined before evaluating the 2nd + * operand: if this is also a list, all is ok. + * For "something . ...", "something - ..." or "non-list + ...", + * we know that the first operand needs to be a string or number + * without evaluating the 2nd operand. So check before to avoid + * side effects after an error. */ + if (evaluate && get_tv_string_chk(rettv) == NULL) + { + clear_tv(rettv); + return FAIL; + } + } + /* * Get the second variable. */ @@ -3849,8 +3903,14 @@ eval5(arg, rettv, evaluate) */ if (op == '.') { - s1 = get_tv_string_buf(rettv, buf1); - s2 = get_tv_string_buf(&var2, buf2); + s1 = get_tv_string_buf(rettv, buf1); /* already checked */ + s2 = get_tv_string_buf_chk(&var2, buf2); + if (s2 == NULL) /* type error ? */ + { + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } p = concat_str(s1, s2); clear_tv(rettv); rettv->v_type = VAR_STRING; @@ -3872,8 +3932,24 @@ eval5(arg, rettv, evaluate) } else { - n1 = get_tv_number(rettv); - n2 = get_tv_number(&var2); + int error = FALSE; + + n1 = get_tv_number_chk(rettv, &error); + if (error) + { + /* This can only happen for "list + non-list". + * For "non-list + ..." or "something - ...", we returned + * before evaluating the 2nd operand. */ + clear_tv(rettv); + return FAIL; + } + n2 = get_tv_number_chk(&var2, &error); + if (error) + { + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } clear_tv(rettv); if (op == '+') n1 = n1 + n2; @@ -3908,6 +3984,7 @@ eval6(arg, rettv, evaluate) typval_T var2; int op; long n1, n2; + int error = FALSE; /* * Get the first variable. @@ -3926,8 +4003,10 @@ eval6(arg, rettv, evaluate) if (evaluate) { - n1 = get_tv_number(rettv); + n1 = get_tv_number_chk(rettv, &error); clear_tv(rettv); + if (error) + return FAIL; } else n1 = 0; @@ -3941,8 +4020,10 @@ eval6(arg, rettv, evaluate) if (evaluate) { - n2 = get_tv_number(&var2); + n2 = get_tv_number_chk(&var2, &error); clear_tv(&var2); + if (error) + return FAIL; /* * Compute the result. @@ -4175,18 +4256,28 @@ eval7(arg, rettv, evaluate) */ if (ret == OK && evaluate && end_leader > start_leader) { - val = get_tv_number(rettv); - while (end_leader > start_leader) + int error = FALSE; + + val = get_tv_number_chk(rettv, &error); + if (error) { - --end_leader; - if (*end_leader == '!') - val = !val; - else if (*end_leader == '-') - val = -val; + clear_tv(rettv); + ret = FAIL; + } + else + { + while (end_leader > start_leader) + { + --end_leader; + if (*end_leader == '!') + val = !val; + else if (*end_leader == '-') + val = -val; + } + clear_tv(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = val; } - clear_tv(rettv); - rettv->v_type = VAR_NUMBER; - rettv->vval.v_number = val; } return ret; @@ -4243,6 +4334,12 @@ eval_index(arg, rettv, evaluate, verbose) empty1 = TRUE; else if (eval1(arg, &var1, evaluate) == FAIL) /* recursive! */ return FAIL; + else if (evaluate && get_tv_string_chk(&var1) == NULL) + { + /* not a number or string */ + clear_tv(&var1); + return FAIL; + } /* * Get the second variable from inside the [:]. @@ -4255,7 +4352,16 @@ eval_index(arg, rettv, evaluate, verbose) empty2 = TRUE; else if (eval1(arg, &var2, evaluate) == FAIL) /* recursive! */ { - clear_tv(&var1); + if (!empty1) + clear_tv(&var1); + return FAIL; + } + else if (evaluate && get_tv_string_chk(&var2) == NULL) + { + /* not a number or string */ + if (!empty1) + clear_tv(&var1); + clear_tv(&var2); return FAIL; } } @@ -4928,6 +5034,7 @@ tv_equal(tv1, tv2, ic) int ic; /* ignore case */ { char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + char_u *s1, *s2; if (tv1->v_type == VAR_LIST || tv2->v_type == VAR_LIST) { @@ -4963,12 +5070,13 @@ tv_equal(tv1, tv2, ic) if (get_tv_number(tv1) != get_tv_number(tv2)) return FALSE; } - else if (!ic && STRCMP(get_tv_string_buf(tv1, buf1), - get_tv_string_buf(tv2, buf2)) != 0) - return FALSE; - else if (ic && STRICMP(get_tv_string_buf(tv1, buf1), - get_tv_string_buf(tv2, buf2)) != 0) - return FALSE; + else + { + s1 = get_tv_string_buf(tv1, buf1); + s2 = get_tv_string_buf(tv2, buf2); + if ((ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2)) != 0) + return FALSE; + } return TRUE; } @@ -5828,10 +5936,12 @@ get_dict_tv(arg, rettv, evaluate) clear_tv(&tvkey); goto failret; } - key = get_tv_string_buf(&tvkey, buf); - if (*key == NUL) + key = get_tv_string_buf_chk(&tvkey, buf); + if (key == NULL || *key == NUL) { - EMSG(_(e_emptykey)); + /* "key" is NULL when get_tv_string_buf_chk() gave an errmsg */ + if (key != NULL) + EMSG(_(e_emptykey)); clear_tv(&tvkey); goto failret; } @@ -6731,12 +6841,12 @@ f_append(argvars, rettv) typval_T *rettv; { long lnum; + char_u *line; list_T *l = NULL; listitem_T *li = NULL; typval_T *tv; long added = 0; - rettv->vval.v_number = 1; /* Default: Failed */ lnum = get_tv_lnum(argvars); if (lnum >= 0 && lnum <= curbuf->b_ml.ml_line_count @@ -6749,6 +6859,7 @@ f_append(argvars, rettv) return; li = l->lv_first; } + rettv->vval.v_number = 0; /* Default: Success */ for (;;) { if (l == NULL) @@ -6757,7 +6868,13 @@ f_append(argvars, rettv) break; /* end of list */ else tv = &li->li_tv; /* append item from list */ - ml_append(lnum + added, get_tv_string(tv), (colnr_T)0, FALSE); + line = get_tv_string_chk(tv); + if (line == NULL) /* type error */ + { + rettv->vval.v_number = 1; /* Failed */ + break; + } + ml_append(lnum + added, line, (colnr_T)0, FALSE); ++added; if (l == NULL) break; @@ -6767,8 +6884,9 @@ f_append(argvars, rettv) appended_lines_mark(lnum, added); if (curwin->w_cursor.lnum > lnum) curwin->w_cursor.lnum += added; - rettv->vval.v_number = 0; /* Success */ } + else + rettv->vval.v_number = 1; /* Failed */ } /* @@ -6805,7 +6923,7 @@ f_argv(argvars, rettv) { int idx; - idx = get_tv_number(&argvars[0]); + idx = get_tv_number_chk(&argvars[0], NULL); if (idx >= 0 && idx < ARGCOUNT) rettv->vval.v_string = vim_strsave(alist_name(&ARGLIST[idx])); else @@ -6829,13 +6947,17 @@ f_browse(argvars, rettv) char_u *defname; char_u buf[NUMBUFLEN]; char_u buf2[NUMBUFLEN]; + int error = FALSE; - save = get_tv_number(&argvars[0]); - title = get_tv_string(&argvars[1]); - initdir = get_tv_string_buf(&argvars[2], buf); - defname = get_tv_string_buf(&argvars[3], buf2); + save = get_tv_number_chk(&argvars[0], &error); + title = get_tv_string_chk(&argvars[1]); + initdir = get_tv_string_buf_chk(&argvars[2], buf); + defname = get_tv_string_buf_chk(&argvars[3], buf2); - rettv->vval.v_string = + if (error || title == NULL || initdir == NULL || defname == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = do_browse(save ? BROWSE_SAVE : 0, title, defname, NULL, initdir, NULL, curbuf); #else @@ -6858,10 +6980,13 @@ f_browsedir(argvars, rettv) char_u *initdir; char_u buf[NUMBUFLEN]; - title = get_tv_string(&argvars[0]); - initdir = get_tv_string_buf(&argvars[1], buf); + title = get_tv_string_chk(&argvars[0]); + initdir = get_tv_string_buf_chk(&argvars[1], buf); - rettv->vval.v_string = do_browse(BROWSE_DIR, + if (title == NULL || initdir == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = do_browse(BROWSE_DIR, title, NULL, NULL, initdir, NULL, curbuf); #else rettv->vval.v_string = NULL; @@ -6994,6 +7119,7 @@ f_bufname(argvars, rettv) { buf_T *buf; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ ++emsg_off; buf = get_buf_tv(&argvars[0]); rettv->v_type = VAR_STRING; @@ -7014,6 +7140,7 @@ f_bufnr(argvars, rettv) { buf_T *buf; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ ++emsg_off; buf = get_buf_tv(&argvars[0]); if (buf != NULL) @@ -7037,6 +7164,7 @@ f_bufwinnr(argvars, rettv) #endif buf_T *buf; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ ++emsg_off; buf = get_buf_tv(&argvars[0]); #ifdef FEAT_WINDOWS @@ -7067,7 +7195,7 @@ f_byte2line(argvars, rettv) #else long boff = 0; - boff = get_tv_number(&argvars[0]) - 1; + boff = get_tv_number(&argvars[0]) - 1; /* boff gets -1 on type error */ if (boff < 0) rettv->vval.v_number = -1; else @@ -7091,10 +7219,10 @@ f_byteidx(argvars, rettv) char_u *str; long idx; - str = get_tv_string(&argvars[0]); - idx = get_tv_number(&argvars[1]); + str = get_tv_string_chk(&argvars[0]); + idx = get_tv_number_chk(&argvars[1], NULL); rettv->vval.v_number = -1; - if (idx < 0) + if (str == NULL || idx < 0) return; #ifdef FEAT_MBYTE @@ -7140,6 +7268,8 @@ f_call(argvars, rettv) func = argvars[0].vval.v_string; else func = get_tv_string(&argvars[0]); + if (*func == NUL) + return; /* type error or empty name */ if (argvars[2].v_type != VAR_UNKNOWN) { @@ -7185,8 +7315,7 @@ f_char2nr(argvars, rettv) { #ifdef FEAT_MBYTE if (has_mbyte) - rettv->vval.v_number = - (*mb_ptr2char)(get_tv_string(&argvars[0])); + rettv->vval.v_number = (*mb_ptr2char)(get_tv_string(&argvars[0])); else #endif rettv->vval.v_number = get_tv_string(&argvars[0])[0]; @@ -7285,26 +7414,35 @@ f_confirm(argvars, rettv) char_u buf2[NUMBUFLEN]; int def = 1; int type = VIM_GENERIC; - int c; + char_u *typestr; + int error = FALSE; - message = get_tv_string(&argvars[0]); + message = get_tv_string_chk(&argvars[0]); + if (message == NULL) + error = TRUE; if (argvars[1].v_type != VAR_UNKNOWN) { - buttons = get_tv_string_buf(&argvars[1], buf); + buttons = get_tv_string_buf_chk(&argvars[1], buf); + if (buttons == NULL) + error = TRUE; if (argvars[2].v_type != VAR_UNKNOWN) { - def = get_tv_number(&argvars[2]); + def = get_tv_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { - /* avoid that TOUPPER_ASC calls get_tv_string_buf() twice */ - c = *get_tv_string_buf(&argvars[3], buf2); - switch (TOUPPER_ASC(c)) + typestr = get_tv_string_buf_chk(&argvars[3], buf2); + if (typestr == NULL) + error = TRUE; + else { - case 'E': type = VIM_ERROR; break; - case 'Q': type = VIM_QUESTION; break; - case 'I': type = VIM_INFO; break; - case 'W': type = VIM_WARNING; break; - case 'G': type = VIM_GENERIC; break; + switch (TOUPPER_ASC(*typestr)) + { + case 'E': type = VIM_ERROR; break; + case 'Q': type = VIM_QUESTION; break; + case 'I': type = VIM_INFO; break; + case 'W': type = VIM_WARNING; break; + case 'G': type = VIM_GENERIC; break; + } } } } @@ -7313,7 +7451,10 @@ f_confirm(argvars, rettv) if (buttons == NULL || *buttons == NUL) buttons = (char_u *)_("&Ok"); - rettv->vval.v_number = do_dialog(type, NULL, message, buttons, + if (error) + rettv->vval.v_number = 0; + else + rettv->vval.v_number = do_dialog(type, NULL, message, buttons, def, NULL); #else rettv->vval.v_number = 0; @@ -7353,14 +7494,21 @@ f_count(argvars, rettv) li = l->lv_first; if (argvars[2].v_type != VAR_UNKNOWN) { - ic = get_tv_number(&argvars[2]); + int error = FALSE; + + ic = get_tv_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) { - idx = get_tv_number(&argvars[3]); - li = list_find(l, idx); - if (li == NULL) - EMSGN(_(e_listidx), idx); + idx = get_tv_number_chk(&argvars[3], &error); + if (!error) + { + li = list_find(l, idx); + if (li == NULL) + EMSGN(_(e_listidx), idx); + } } + if (error) + li = NULL; } for ( ; li != NULL; li = li->li_next) @@ -7376,14 +7524,16 @@ f_count(argvars, rettv) if ((d = argvars[0].vval.v_dict) != NULL) { + int error = FALSE; + if (argvars[2].v_type != VAR_UNKNOWN) { - ic = get_tv_number(&argvars[2]); + ic = get_tv_number_chk(&argvars[2], &error); if (argvars[3].v_type != VAR_UNKNOWN) EMSG(_(e_invarg)); } - todo = d->dv_hashtab.ht_used; + todo = error ? 0 : d->dv_hashtab.ht_used; for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { if (!HASHITEM_EMPTY(hi)) @@ -7446,9 +7596,11 @@ f_cursor(argvars, rettv) long line, col; line = get_tv_lnum(argvars); + col = get_tv_number_chk(&argvars[1], NULL); + if (line < 0 || col < 0) + return; /* type error; errmsg already given */ if (line > 0) curwin->w_cursor.lnum = line; - col = get_tv_number(&argvars[1]); if (col > 0) curwin->w_cursor.col = col - 1; #ifdef FEAT_VIRTUALEDIT @@ -7478,7 +7630,7 @@ f_deepcopy(argvars, rettv) int noref = 0; if (argvars[1].v_type != VAR_UNKNOWN) - noref = get_tv_number(&argvars[1]); + noref = get_tv_number_chk(&argvars[1], NULL); if (noref < 0 || noref > 1) EMSG(_(e_invarg)); else @@ -7549,6 +7701,8 @@ f_diff_hlID(argvars, rettv) int filler_lines; int col; + if (lnum < 0) /* ignore type error in {lnum} arg */ + lnum = 0; if (lnum != prev_lnum || changedtick != curbuf->b_changedtick || fnum != curbuf->b_fnum) @@ -7578,7 +7732,7 @@ f_diff_hlID(argvars, rettv) if (hlID == HLF_CHD || hlID == HLF_TXD) { - col = get_tv_number(&argvars[1]) - 1; + col = get_tv_number(&argvars[1]) - 1; /* ignore type error in {col} */ if (col >= change_start && col <= change_end) hlID = HLF_TXD; /* changed text */ else @@ -7650,11 +7804,15 @@ f_eval(argvars, rettv) { char_u *s; - s = get_tv_string(&argvars[0]); - s = skipwhite(s); + s = get_tv_string_chk(&argvars[0]); + if (s != NULL) + s = skipwhite(s); - if (eval1(&s, rettv, TRUE) == FAIL) + if (s == NULL || eval1(&s, rettv, TRUE) == FAIL) + { + rettv->v_type = VAR_NUMBER; rettv->vval.v_number = 0; + } else if (*s != NUL) EMSG(_(e_trailing)); } @@ -7772,6 +7930,7 @@ f_expand(argvars, rettv) char_u *errormsg; int flags = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; expand_T xpc; + int error = FALSE; rettv->v_type = VAR_STRING; s = get_tv_string(&argvars[0]); @@ -7785,12 +7944,18 @@ f_expand(argvars, rettv) { /* When the optional second argument is non-zero, don't remove matches * for 'suffixes' and 'wildignore' */ - if (argvars[1].v_type != VAR_UNKNOWN && get_tv_number(&argvars[1])) + if (argvars[1].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[1], &error)) flags |= WILD_KEEP_ALL; - ExpandInit(&xpc); - xpc.xp_context = EXPAND_FILES; - rettv->vval.v_string = ExpandOne(&xpc, s, NULL, flags, WILD_ALL); - ExpandCleanup(&xpc); + if (!error) + { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + rettv->vval.v_string = ExpandOne(&xpc, s, NULL, flags, WILD_ALL); + ExpandCleanup(&xpc); + } + else + rettv->vval.v_string = NULL; } } @@ -7809,6 +7974,7 @@ f_extend(argvars, rettv) list_T *l1, *l2; listitem_T *item; long before; + int error = FALSE; l1 = argvars[0].vval.v_list; l2 = argvars[1].vval.v_list; @@ -7817,7 +7983,10 @@ f_extend(argvars, rettv) { if (argvars[2].v_type != VAR_UNKNOWN) { - before = get_tv_number(&argvars[2]); + before = get_tv_number_chk(&argvars[2], &error); + if (error) + return; /* type error; errmsg already given */ + if (before == l1->lv_len) item = NULL; else @@ -7857,7 +8026,9 @@ f_extend(argvars, rettv) { static char *(av[]) = {"keep", "force", "error"}; - action = get_tv_string(&argvars[2]); + action = get_tv_string_chk(&argvars[2]); + if (action == NULL) + return; /* type error; errmsg already given */ for (i = 0; i < 3; ++i) if (STRCMP(action, av[i]) == 0) break; @@ -7963,22 +8134,30 @@ findfilendir(argvars, rettv, dir) if (argvars[1].v_type != VAR_UNKNOWN) { - p = get_tv_string_buf(&argvars[1], pathbuf); - if (*p != NUL) - path = p; + p = get_tv_string_buf_chk(&argvars[1], pathbuf); + if (p == NULL) + count = -1; /* error */ + else + { + if (*p != NUL) + path = p; - if (argvars[2].v_type != VAR_UNKNOWN) - count = get_tv_number(&argvars[2]); + if (argvars[2].v_type != VAR_UNKNOWN) + count = get_tv_number_chk(&argvars[2], NULL); /* -1: error */ + } } - do + if (*fname != NUL && count >= 0) { - vim_free(fresult); - fresult = find_file_in_path_option(first ? fname : NULL, - first ? (int)STRLEN(fname) : 0, - 0, first, path, dir, NULL); - first = FALSE; - } while (--count > 0 && fresult != NULL); + do + { + vim_free(fresult); + fresult = find_file_in_path_option(first ? fname : NULL, + first ? (int)STRLEN(fname) : 0, + 0, first, path, dir, NULL); + first = FALSE; + } while (--count > 0 && fresult != NULL); + } rettv->vval.v_string = fresult; #else @@ -8073,53 +8252,60 @@ filter_map(argvars, rettv, map) return; } - prepare_vimvar(VV_VAL, &save_val); - expr = skipwhite(get_tv_string_buf(&argvars[1], buf)); - - if (argvars[0].v_type == VAR_DICT) + expr = get_tv_string_buf_chk(&argvars[1], buf); + /* On type errors, the preceding call has already displayed an error + * message. Avoid a misleading error message for an empty string that + * was not passed as argument. */ + if (expr != NULL) { - prepare_vimvar(VV_KEY, &save_key); - vimvars[VV_KEY].vv_type = VAR_STRING; + prepare_vimvar(VV_VAL, &save_val); + expr = skipwhite(expr); - ht = &d->dv_hashtab; - hash_lock(ht); - todo = ht->ht_used; - for (hi = ht->ht_array; todo > 0; ++hi) + if (argvars[0].v_type == VAR_DICT) { - if (!HASHITEM_EMPTY(hi)) + prepare_vimvar(VV_KEY, &save_key); + vimvars[VV_KEY].vv_type = VAR_STRING; + + ht = &d->dv_hashtab; + hash_lock(ht); + todo = ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) { - --todo; - di = HI2DI(hi); - if (tv_check_lock(di->di_tv.v_lock, msg)) + if (!HASHITEM_EMPTY(hi)) + { + --todo; + di = HI2DI(hi); + if (tv_check_lock(di->di_tv.v_lock, msg)) + break; + vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); + if (filter_map_one(&di->di_tv, expr, map, &rem) == FAIL) + break; + if (!map && rem) + dictitem_remove(d, di); + clear_tv(&vimvars[VV_KEY].vv_tv); + } + } + hash_unlock(ht); + + restore_vimvar(VV_KEY, &save_key); + } + else + { + for (li = l->lv_first; li != NULL; li = nli) + { + if (tv_check_lock(li->li_tv.v_lock, msg)) break; - vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); - if (filter_map_one(&di->di_tv, expr, map, &rem) == FAIL) + nli = li->li_next; + if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL) break; if (!map && rem) - dictitem_remove(d, di); - clear_tv(&vimvars[VV_KEY].vv_tv); + listitem_remove(l, li); } } - hash_unlock(ht); - restore_vimvar(VV_KEY, &save_key); - } - else - { - for (li = l->lv_first; li != NULL; li = nli) - { - if (tv_check_lock(li->li_tv.v_lock, msg)) - break; - nli = li->li_next; - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL) - break; - if (!map && rem) - listitem_remove(l, li); - } + restore_vimvar(VV_VAL, &save_val); } - restore_vimvar(VV_VAL, &save_val); - copy_tv(&argvars[0], rettv); } @@ -8150,9 +8336,15 @@ filter_map_one(tv, expr, map, remp) } else { + int error = FALSE; + /* filter(): when expr is zero remove the item */ - *remp = (get_tv_number(&rettv) == 0); + *remp = (get_tv_number_chk(&rettv, &error) == 0); clear_tv(&rettv); + /* On type error, nothing has been removed; return FAIL to stop the + * loop. The error message was given by get_tv_number_chk(). */ + if (error) + return FAIL; } clear_tv(&vimvars[VV_VAL].vv_tv); return OK; @@ -8206,11 +8398,15 @@ f_fnamemodify(argvars, rettv) char_u *fbuf = NULL; char_u buf[NUMBUFLEN]; - fname = get_tv_string(&argvars[0]); - mods = get_tv_string_buf(&argvars[1], buf); - len = (int)STRLEN(fname); - - (void)modify_fname(mods, &usedlen, &fname, &fbuf, &len); + fname = get_tv_string_chk(&argvars[0]); + mods = get_tv_string_buf_chk(&argvars[1], buf); + if (fname == NULL || mods == NULL) + fname = NULL; + else + { + len = (int)STRLEN(fname); + (void)modify_fname(mods, &usedlen, &fname, &fbuf, &len); + } rettv->v_type = VAR_STRING; if (fname == NULL) @@ -8381,6 +8577,9 @@ f_foldtextresult(argvars, rettv) rettv->vval.v_string = NULL; #ifdef FEAT_FOLDING lnum = get_tv_lnum(argvars); + /* treat illegal types and illegal string values for {lnum} the same */ + if (lnum < 0) + lnum = 0; fold_count = foldedCount(curwin, lnum, &foldinfo); if (fold_count > 0) { @@ -8455,8 +8654,10 @@ f_get(argvars, rettv) { if ((l = argvars[0].vval.v_list) != NULL) { - li = list_find(l, get_tv_number(&argvars[1])); - if (li != NULL) + int error = FALSE; + + li = list_find(l, get_tv_number_chk(&argvars[1], &error)); + if (!error && li != NULL) tv = &li->li_tv; } } @@ -8496,9 +8697,10 @@ f_getbufvar(argvars, rettv) char_u *varname; dictitem_T *v; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + varname = get_tv_string_chk(&argvars[1]); ++emsg_off; buf = get_buf_tv(&argvars[0]); - varname = get_tv_string(&argvars[1]); rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -8518,6 +8720,11 @@ f_getbufvar(argvars, rettv) } else { + if (*varname == NUL) + /* let getbufvar({nr}, "") return the "b:" dictionary. The + * scope prefix before the NUL byte is required by + * find_var_in_ht(). */ + varname = (char_u *)"b:" + 2; /* look up the variable */ v = find_var_in_ht(&buf->b_vars.dv_hashtab, varname, FALSE); if (v != NULL) @@ -8537,17 +8744,18 @@ f_getchar(argvars, rettv) typval_T *rettv; { varnumber_T n; + int error = FALSE; ++no_mapping; ++allow_keys; if (argvars[0].v_type == VAR_UNKNOWN) /* getchar(): blocking wait. */ n = safe_vgetc(); - else if (get_tv_number(&argvars[0]) == 1) + else if (get_tv_number_chk(&argvars[0], &error) == 1) /* getchar(1): only check if char avail */ n = vpeekc(); - else if (vpeekc() == NUL) - /* getchar(0) and no char avail: return zero */ + else if (error || vpeekc() == NUL) + /* illegal argument or getchar(0) and no char avail: return zero */ n = 0; else /* getchar(0) and char avail: return char */ @@ -8858,8 +9066,9 @@ f_getline(argvars, rettv) listitem_T *li; lnum = get_tv_lnum(argvars); - - if (argvars[1].v_type == VAR_UNKNOWN) + if (lnum < 0) + rettv->vval.v_number = 0; /* failure; error message already given */ + else if (argvars[1].v_type == VAR_UNKNOWN) { if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) p = ml_get(lnum); @@ -8874,7 +9083,8 @@ f_getline(argvars, rettv) end = get_tv_lnum(&argvars[1]); if (end < lnum) { - EMSG(_(e_invrange)); + if (end >= 0) /* else: error message already given */ + EMSG(_(e_invrange)); rettv->vval.v_number = 0; } else @@ -8945,12 +9155,14 @@ f_getreg(argvars, rettv) char_u *strregname; int regname; int arg2 = FALSE; + int error = FALSE; if (argvars[0].v_type != VAR_UNKNOWN) { - strregname = get_tv_string(&argvars[0]); + strregname = get_tv_string_chk(&argvars[0]); + error = strregname == NULL; if (argvars[1].v_type != VAR_UNKNOWN) - arg2 = get_tv_number(&argvars[1]); + arg2 = get_tv_number_chk(&argvars[1], &error); } else strregname = vimvars[VV_REG].vv_str; @@ -8959,7 +9171,8 @@ f_getreg(argvars, rettv) regname = '"'; rettv->v_type = VAR_STRING; - rettv->vval.v_string = get_reg_contents(regname, TRUE, arg2); + rettv->vval.v_string = error ? NULL : + get_reg_contents(regname, TRUE, arg2); } /* @@ -8976,7 +9189,15 @@ f_getregtype(argvars, rettv) long reglen = 0; if (argvars[0].v_type != VAR_UNKNOWN) - strregname = get_tv_string(&argvars[0]); + { + strregname = get_tv_string_chk(&argvars[0]); + if (strregname == NULL) /* type error; errmsg already given */ + { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + return; + } + } else /* Default to v:register */ strregname = vimvars[VV_REG].vv_str; @@ -9056,9 +9277,9 @@ f_getwinvar(argvars, rettv) char_u *varname; dictitem_T *v; - ++emsg_off; win = find_win_by_nr(&argvars[0]); - varname = get_tv_string(&argvars[1]); + varname = get_tv_string_chk(&argvars[1]); + ++emsg_off; rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; @@ -9081,6 +9302,11 @@ f_getwinvar(argvars, rettv) } else { + if (*varname == NUL) + /* let getwinvar({nr}, "") return the "w:" dictionary. The + * scope prefix before the NUL byte is required by + * find_var_in_ht(). */ + varname = (char_u *)"w:" + 2; /* look up the variable */ v = find_var_in_ht(&win->w_vars.dv_hashtab, varname, FALSE); if (v != NULL) @@ -9118,10 +9344,13 @@ f_globpath(argvars, rettv) typval_T *rettv; { char_u buf1[NUMBUFLEN]; + char_u *file = get_tv_string_buf_chk(&argvars[1], buf1); rettv->v_type = VAR_STRING; - rettv->vval.v_string = globpath(get_tv_string(&argvars[0]), - get_tv_string_buf(&argvars[1], buf1)); + if (file == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = globpath(get_tv_string(&argvars[0]), file); } /* @@ -9697,7 +9926,8 @@ f_histadd(argvars, rettv) if (check_restricted() || check_secure()) return; #ifdef FEAT_CMDHIST - histype = get_histtype(get_tv_string(&argvars[0])); + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + histype = str != NULL ? get_histtype(str) : -1; if (histype >= 0) { str = get_tv_string_buf(&argvars[1], buf); @@ -9723,17 +9953,21 @@ f_histdel(argvars, rettv) #ifdef FEAT_CMDHIST int n; char_u buf[NUMBUFLEN]; + char_u *str; - if (argvars[1].v_type == VAR_UNKNOWN) + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + if (str == NULL) + n = 0; + else if (argvars[1].v_type == VAR_UNKNOWN) /* only one argument: clear entire history */ - n = clr_history(get_histtype(get_tv_string(&argvars[0]))); + n = clr_history(get_histtype(str)); else if (argvars[1].v_type == VAR_NUMBER) /* index given: remove that entry */ - n = del_history_idx(get_histtype(get_tv_string(&argvars[0])), + n = del_history_idx(get_histtype(str), (int)get_tv_number(&argvars[1])); else /* string given: remove all matching entries */ - n = del_history_entry(get_histtype(get_tv_string(&argvars[0])), + n = del_history_entry(get_histtype(str), get_tv_string_buf(&argvars[1], buf)); rettv->vval.v_number = n; #else @@ -9753,13 +9987,21 @@ f_histget(argvars, rettv) #ifdef FEAT_CMDHIST int type; int idx; + char_u *str; - type = get_histtype(get_tv_string(&argvars[0])); - if (argvars[1].v_type == VAR_UNKNOWN) - idx = get_history_idx(type); + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + if (str == NULL) + rettv->vval.v_string = NULL; else - idx = (int)get_tv_number(&argvars[1]); - rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); + { + type = get_histtype(str); + if (argvars[1].v_type == VAR_UNKNOWN) + idx = get_history_idx(type); + else + idx = (int)get_tv_number_chk(&argvars[1], NULL); + /* -1 on type error */ + rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); + } #else rettv->vval.v_string = NULL; #endif @@ -9778,7 +10020,9 @@ f_histnr(argvars, rettv) int i; #ifdef FEAT_CMDHIST - i = get_histtype(get_tv_string(&argvars[0])); + char_u *history = get_tv_string_chk(&argvars[0]); + + i = history == NULL ? HIST_CMD - 1 : get_histtype(history); if (i >= HIST_CMD && i < HIST_COUNT) i = get_history_idx(i); else @@ -9905,12 +10149,16 @@ f_index(argvars, rettv) item = l->lv_first; if (argvars[2].v_type != VAR_UNKNOWN) { + int error = FALSE; + /* Start at specified item. Use the cached index that list_find() * sets, so that a negative number also works. */ - item = list_find(l, get_tv_number(&argvars[2])); + item = list_find(l, get_tv_number_chk(&argvars[2], &error)); idx = l->lv_idx; if (argvars[3].v_type != VAR_UNKNOWN) - ic = get_tv_number(&argvars[3]); + ic = get_tv_number_chk(&argvars[3], &error); + if (error) + item = NULL; } for ( ; item != NULL; item = item->li_next, ++idx) @@ -9933,11 +10181,12 @@ f_input(argvars, rettv) typval_T *argvars; typval_T *rettv; { - char_u *prompt = get_tv_string(&argvars[0]); + char_u *prompt = get_tv_string_chk(&argvars[0]); char_u *p = NULL; int c; char_u buf[NUMBUFLEN]; int cmd_silent_save = cmd_silent; + char_u *defstr = (char_u *)""; rettv->v_type = VAR_STRING; @@ -9971,17 +10220,22 @@ f_input(argvars, rettv) *p = c; } cmdline_row = msg_row; - } - if (argvars[1].v_type != VAR_UNKNOWN) - stuffReadbuffSpec(get_tv_string_buf(&argvars[1], buf)); + if (argvars[1].v_type != VAR_UNKNOWN) + { + defstr = get_tv_string_buf_chk(&argvars[1], buf); + if (defstr != NULL) + stuffReadbuffSpec(defstr); + } - rettv->vval.v_string = + if (defstr != NULL) + rettv->vval.v_string = getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr); - /* since the user typed this, no need to wait for return */ - need_wait_return = FALSE; - msg_didout = FALSE; + /* since the user typed this, no need to wait for return */ + need_wait_return = FALSE; + msg_didout = FALSE; + } cmd_silent = cmd_silent_save; } @@ -9999,21 +10253,25 @@ f_inputdialog(argvars, rettv) { char_u *message; char_u buf[NUMBUFLEN]; + char_u *defstr = (char_u *)""; - message = get_tv_string(&argvars[0]); - if (argvars[1].v_type != VAR_UNKNOWN) + message = get_tv_string_chk(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN + && (defstr = get_tv_string_buf_chk(&argvars[1], buf)) != NULL) { - STRNCPY(IObuff, get_tv_string_buf(&argvars[1], buf), IOSIZE); + STRNCPY(IObuff, defstr, IOSIZE); IObuff[IOSIZE - 1] = NUL; } else IObuff[0] = NUL; - if (do_dialog(VIM_QUESTION, NULL, message, (char_u *)_("&OK\n&Cancel"), - 1, IObuff) == 1) + if (message != NULL && defstr != NULL + && do_dialog(VIM_QUESTION, NULL, message, + (char_u *)_("&OK\n&Cancel"), 1, IObuff) == 1) rettv->vval.v_string = vim_strsave(IObuff); else { - if (argvars[1].v_type != VAR_UNKNOWN + if (message != NULL && defstr != NULL + && argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) rettv->vval.v_string = vim_strsave( get_tv_string_buf(&argvars[2], buf)); @@ -10099,6 +10357,7 @@ f_insert(argvars, rettv) long before = 0; listitem_T *item; list_T *l; + int error = FALSE; rettv->vval.v_number = 0; if (argvars[0].v_type != VAR_LIST) @@ -10107,7 +10366,9 @@ f_insert(argvars, rettv) && !tv_check_lock(l->lv_lock, (char_u *)"insert()")) { if (argvars[2].v_type != VAR_UNKNOWN) - before = get_tv_number(&argvars[2]); + before = get_tv_number_chk(&argvars[2], &error); + if (error) + return; /* type error; errmsg already given */ if (before == l->lv_len) item = NULL; @@ -10339,14 +10600,19 @@ f_join(argvars, rettv) if (argvars[1].v_type == VAR_UNKNOWN) sep = (char_u *)" "; else - sep = get_tv_string(&argvars[1]); - - ga_init2(&ga, (int)sizeof(char), 80); - list_join(&ga, argvars[0].vval.v_list, sep, TRUE); - ga_append(&ga, NUL); + sep = get_tv_string_chk(&argvars[1]); rettv->v_type = VAR_STRING; - rettv->vval.v_string = (char_u *)ga.ga_data; + + if (sep != NULL) + { + ga_init2(&ga, (int)sizeof(char), 80); + list_join(&ga, argvars[0].vval.v_list, sep, TRUE); + ga_append(&ga, NUL); + rettv->vval.v_string = (char_u *)ga.ga_data; + } + else + rettv->vval.v_string = NULL; } /* @@ -10577,9 +10843,12 @@ get_maparg(argvars, rettv, exact) return; if (argvars[1].v_type != VAR_UNKNOWN) - which = get_tv_string_buf(&argvars[1], buf); + which = get_tv_string_buf_chk(&argvars[1], buf); else which = (char_u *)""; + if (which == NULL) + return; + mode = get_map_mode(&which, 0); keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE); @@ -10683,11 +10952,17 @@ find_some_match(argvars, rettv, type) else expr = str = get_tv_string(&argvars[0]); - pat = get_tv_string_buf(&argvars[1], patbuf); + pat = get_tv_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) + goto theend; if (argvars[2].v_type != VAR_UNKNOWN) { - start = get_tv_number(&argvars[2]); + int error = FALSE; + + start = get_tv_number_chk(&argvars[2], &error); + if (error) + goto theend; if (l != NULL) { li = list_find(l, start); @@ -10705,7 +10980,9 @@ find_some_match(argvars, rettv, type) } if (argvars[3].v_type != VAR_UNKNOWN) - nth = get_tv_number(&argvars[3]); + nth = get_tv_number_chk(&argvars[3], &error); + if (error) + goto theend; } regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); @@ -10856,6 +11133,7 @@ max_min(argvars, rettv, domax) { long n = 0; long i; + int error = FALSE; if (argvars[0].v_type == VAR_LIST) { @@ -10868,13 +11146,13 @@ max_min(argvars, rettv, domax) li = l->lv_first; if (li != NULL) { - n = get_tv_number(&li->li_tv); + n = get_tv_number_chk(&li->li_tv, &error); while (1) { li = li->li_next; if (li == NULL) break; - i = get_tv_number(&li->li_tv); + i = get_tv_number_chk(&li->li_tv, &error); if (domax ? i > n : i < n) n = i; } @@ -10897,7 +11175,7 @@ max_min(argvars, rettv, domax) if (!HASHITEM_EMPTY(hi)) { --todo; - i = get_tv_number(&HI2DI(hi)->di_tv); + i = get_tv_number_chk(&HI2DI(hi)->di_tv, &error); if (first) { n = i; @@ -10911,7 +11189,7 @@ max_min(argvars, rettv, domax) } else EMSG(_(e_listdictarg)); - rettv->vval.v_number = n; + rettv->vval.v_number = error ? 0 : n; } /* @@ -10990,11 +11268,11 @@ f_mkdir(argvars, rettv) if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_UNKNOWN) - prot = get_tv_number(&argvars[2]); - if (STRCMP(get_tv_string(&argvars[1]), "p") == 0) + prot = get_tv_number_chk(&argvars[2], NULL); + if (prot != -1 && STRCMP(get_tv_string(&argvars[1]), "p") == 0) mkdir_recurse(dir, prot); } - rettv->vval.v_number = vim_mkdir_emsg(dir, prot); + rettv->vval.v_number = prot != -1 ? vim_mkdir_emsg(dir, prot) : 0; } #endif @@ -11050,7 +11328,7 @@ f_nextnonblank(argvars, rettv) for (lnum = get_tv_lnum(argvars); ; ++lnum) { - if (lnum > curbuf->b_ml.ml_line_count) + if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { lnum = 0; break; @@ -11117,8 +11395,9 @@ f_range(argvars, rettv) long i; list_T *l; listitem_T *li; + int error = FALSE; - start = get_tv_number(&argvars[0]); + start = get_tv_number_chk(&argvars[0], &error); if (argvars[1].v_type == VAR_UNKNOWN) { end = start - 1; @@ -11126,12 +11405,14 @@ f_range(argvars, rettv) } else { - end = get_tv_number(&argvars[1]); + end = get_tv_number_chk(&argvars[1], &error); if (argvars[2].v_type != VAR_UNKNOWN) - stride = get_tv_number(&argvars[2]); + stride = get_tv_number_chk(&argvars[2], &error); } rettv->vval.v_number = 0; + if (error) + return; /* type error; errmsg already given */ if (stride == 0) EMSG(_("E726: Stride is zero")); else if (stride > 0 ? end < start : end > start) @@ -11375,7 +11656,9 @@ remote_common(argvars, rettv, expr) return; # endif - server_name = get_tv_string(&argvars[0]); + server_name = get_tv_string_chk(&argvars[0]); + if (server_name == NULL) + return; /* type error; errmsg already given */ keys = get_tv_string_buf(&argvars[1], buf); # ifdef WIN32 if (serverSendToVim(server_name, keys, &r, &w, expr, TRUE) < 0) @@ -11397,11 +11680,14 @@ remote_common(argvars, rettv, expr) { dictitem_T v; char_u str[30]; + char_u *idvar; sprintf((char *)str, "0x%x", (unsigned int)w); v.di_tv.v_type = VAR_STRING; v.di_tv.vval.v_string = vim_strsave(str); - set_var(get_tv_string(&argvars[2]), &v.di_tv, FALSE); + idvar = get_tv_string_chk(&argvars[2]); + if (idvar != NULL) + set_var(idvar, &v.di_tv, FALSE); vim_free(v.di_tv.vval.v_string); } } @@ -11436,7 +11722,12 @@ f_remote_foreground(argvars, rettv) #ifdef FEAT_CLIENTSERVER # ifdef WIN32 /* On Win32 it's done in this application. */ - serverForeground(get_tv_string(&argvars[0])); + { + char_u *server_name = get_tv_string_chk(&argvars[0]); + + if (server_name != NULL) + serverForeground(server_name); + } # else /* Send a foreground() expression to the server. */ argvars[1].v_type = VAR_STRING; @@ -11460,14 +11751,21 @@ f_remote_peek(argvars, rettv) # ifdef WIN32 int n = 0; # endif + char_u *serverid; if (check_restricted() || check_secure()) { rettv->vval.v_number = -1; return; } + serverid = get_tv_string_chk(&argvars[0]); + if (serverid == NULL) + { + rettv->vval.v_number = -1; + return; /* type error; errmsg already given */ + } # ifdef WIN32 - sscanf(get_tv_string(&argvars[0]), "%x", &n); + sscanf(serverid, "%x", &n); if (n == 0) rettv->vval.v_number = -1; else @@ -11481,14 +11779,18 @@ f_remote_peek(argvars, rettv) return; rettv->vval.v_number = serverPeekReply(X_DISPLAY, - serverStrToWin(get_tv_string(&argvars[0])), &s); + serverStrToWin(serverid), &s); # endif if (argvars[1].v_type != VAR_UNKNOWN && rettv->vval.v_number > 0) { + char_u *retvar; + v.di_tv.v_type = VAR_STRING; v.di_tv.vval.v_string = vim_strsave(s); - set_var(get_tv_string(&argvars[1]), &v.di_tv, FALSE); + retvar = get_tv_string_chk(&argvars[1]); + if (retvar != NULL) + set_var(retvar, &v.di_tv, FALSE); vim_free(v.di_tv.vval.v_string); } #else @@ -11505,19 +11807,21 @@ f_remote_read(argvars, rettv) char_u *r = NULL; #ifdef FEAT_CLIENTSERVER - if (!check_restricted() && !check_secure()) + char_u *serverid = get_tv_string_chk(&argvars[0]); + + if (serverid != NULL && !check_restricted() && !check_secure()) { # ifdef WIN32 /* The server's HWND is encoded in the 'id' parameter */ int n = 0; - sscanf(get_tv_string(&argvars[0]), "%x", &n); + sscanf(serverid, "%x", &n); if (n != 0) r = serverGetReply((HWND)n, FALSE, TRUE, TRUE); if (r == NULL) # else if (check_connection() == FAIL || serverReadReply(X_DISPLAY, - serverStrToWin(get_tv_string(&argvars[0])), &r, FALSE) < 0) + serverStrToWin(serverid), &r, FALSE) < 0) # endif EMSG(_("E277: Unable to read a server reply")); } @@ -11567,15 +11871,18 @@ f_remove(argvars, rettv) else if ((d = argvars[0].vval.v_dict) != NULL && !tv_check_lock(d->dv_lock, (char_u *)"remove()")) { - key = get_tv_string(&argvars[1]); - di = dict_find(d, key, -1); - if (di == NULL) - EMSG2(_(e_dictkey), key); - else + key = get_tv_string_chk(&argvars[1]); + if (key != NULL) { - *rettv = di->di_tv; - init_tv(&di->di_tv); - dictitem_remove(d, di); + di = dict_find(d, key, -1); + if (di == NULL) + EMSG2(_(e_dictkey), key); + else + { + *rettv = di->di_tv; + init_tv(&di->di_tv); + dictitem_remove(d, di); + } } } } @@ -11584,9 +11891,12 @@ f_remove(argvars, rettv) else if ((l = argvars[0].vval.v_list) != NULL && !tv_check_lock(l->lv_lock, (char_u *)"remove()")) { - idx = get_tv_number(&argvars[1]); - item = list_find(l, idx); - if (item == NULL) + int error = FALSE; + + idx = get_tv_number_chk(&argvars[1], &error); + if (error) + ; /* type error: do nothing, errmsg already given */ + else if ((item = list_find(l, idx)) == NULL) EMSGN(_(e_listidx), idx); else { @@ -11600,9 +11910,10 @@ f_remove(argvars, rettv) else { /* Remove range of items, return list with values. */ - end = get_tv_number(&argvars[2]); - item2 = list_find(l, end); - if (item2 == NULL) + end = get_tv_number_chk(&argvars[2], &error); + if (error) + ; /* type error: do nothing */ + else if ((item2 = list_find(l, end)) == NULL) EMSGN(_(e_listidx), end); else { @@ -11964,7 +12275,9 @@ get_search_arg(varp, flagsp) if (varp->v_type != VAR_UNKNOWN) { - flags = get_tv_string_buf(varp, nbuf); + flags = get_tv_string_buf_chk(varp, nbuf); + if (flags == NULL) + return 0; /* type error; errmsg already given */ while (*flags != NUL) { switch (*flags) @@ -12051,7 +12364,7 @@ f_searchpair(argvars, rettv) { char_u *spat, *mpat, *epat; char_u *skip; - char_u *pat, *pat2, *pat3; + char_u *pat, *pat2 = NULL, *pat3 = NULL; pos_T pos; pos_T firstpos; pos_T foundpos; @@ -12076,9 +12389,11 @@ f_searchpair(argvars, rettv) p_cpo = (char_u *)""; /* Get the three pattern arguments: start, middle, end. */ - spat = get_tv_string(&argvars[0]); - mpat = get_tv_string_buf(&argvars[1], nbuf1); - epat = get_tv_string_buf(&argvars[2], nbuf2); + spat = get_tv_string_chk(&argvars[0]); + mpat = get_tv_string_buf_chk(&argvars[1], nbuf1); + epat = get_tv_string_buf_chk(&argvars[2], nbuf2); + if (spat == NULL || mpat == NULL || epat == NULL) + goto theend; /* type error */ /* Make two search patterns: start/end (pat2, for in nested pairs) and * start/middle/end (pat3, for the top pair). */ @@ -12103,7 +12418,9 @@ f_searchpair(argvars, rettv) || argvars[4].v_type == VAR_UNKNOWN) skip = (char_u *)""; else - skip = get_tv_string_buf(&argvars[4], nbuf3); + skip = get_tv_string_buf_chk(&argvars[4], nbuf3); + if (skip == NULL) + goto theend; /* type error */ save_cursor = curwin->w_cursor; pos = curwin->w_cursor; @@ -12198,10 +12515,12 @@ f_server2client(argvars, rettv) { #ifdef FEAT_CLIENTSERVER char_u buf[NUMBUFLEN]; - char_u *server = get_tv_string(&argvars[0]); - char_u *reply = get_tv_string_buf(&argvars[1], buf); + char_u *server = get_tv_string_chk(&argvars[0]); + char_u *reply = get_tv_string_buf_chk(&argvars[1], buf); rettv->vval.v_number = -1; + if (server == NULL || reply == NULL) + return; if (check_restricted() || check_secure()) return; # ifdef FEAT_X11 @@ -12260,11 +12579,14 @@ f_setbufvar(argvars, rettv) typval_T *varp; char_u nbuf[NUMBUFLEN]; + rettv->vval.v_number = 0; + if (check_restricted() || check_secure()) return; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + varname = get_tv_string_chk(&argvars[1]); ++emsg_off; buf = get_buf_tv(&argvars[0]); - varname = get_tv_string(&argvars[1]); varp = &argvars[2]; if (buf != NULL && varname != NULL && varp != NULL) @@ -12279,9 +12601,17 @@ f_setbufvar(argvars, rettv) if (*varname == '&') { + long numval; + char_u *strval; + int error = FALSE; + ++varname; - set_option_value(varname, get_tv_number(varp), - get_tv_string_buf(varp, nbuf), OPT_LOCAL); + --emsg_off; + numval = get_tv_number_chk(varp, &error); + strval = get_tv_string_buf_chk(varp, nbuf); + ++emsg_off; + if (!error && strval != NULL) + set_option_value(varname, numval, strval, OPT_LOCAL); } else { @@ -12313,8 +12643,10 @@ f_setcmdpos(argvars, rettv) typval_T *argvars; typval_T *rettv; { - rettv->vval.v_number = set_cmdline_pos( - (int)get_tv_number(&argvars[0]) - 1); + int pos = (int)get_tv_number(&argvars[0]) - 1; + + if (pos >= 0) + rettv->vval.v_number = set_cmdline_pos(pos); } /* @@ -12339,7 +12671,7 @@ f_setline(argvars, rettv) li = l->lv_first; } else - line = get_tv_string(&argvars[1]); + line = get_tv_string_chk(&argvars[1]); rettv->vval.v_number = 0; /* OK */ for (;;) @@ -12349,12 +12681,12 @@ f_setline(argvars, rettv) /* list argument, get next string */ if (li == NULL) break; - line = get_tv_string(&li->li_tv); + line = get_tv_string_chk(&li->li_tv); li = li->li_next; } rettv->vval.v_number = 1; /* FAIL */ - if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) break; if (lnum <= curbuf->b_ml.ml_line_count) { @@ -12406,7 +12738,9 @@ f_setqflist(argvars, rettv) if (argvars[1].v_type == VAR_STRING) { - act = get_tv_string(&argvars[1]); + act = get_tv_string_chk(&argvars[1]); + if (act == NULL) + return; /* type error; errmsg already given */ if (*act == 'a' || *act == 'r') action = *act; } @@ -12428,6 +12762,7 @@ f_setreg(argvars, rettv) int regname; char_u *strregname; char_u *stropt; + char_u *strval; int append; char_u yank_type; long block_len; @@ -12436,10 +12771,12 @@ f_setreg(argvars, rettv) yank_type = MAUTO; append = FALSE; - strregname = get_tv_string(argvars); + strregname = get_tv_string_chk(argvars); rettv->vval.v_number = 1; /* FAIL is default */ - regname = (strregname == NULL ? '"' : *strregname); + if (strregname == NULL) + return; /* type error; errmsg already given */ + regname = *strregname; if (regname == 0 || regname == '@') regname = '"'; else if (regname == '=') @@ -12447,7 +12784,10 @@ f_setreg(argvars, rettv) if (argvars[2].v_type != VAR_UNKNOWN) { - for (stropt = get_tv_string(&argvars[2]); *stropt != NUL; ++stropt) + stropt = get_tv_string_chk(&argvars[2]); + if (stropt == NULL) + return; /* type error */ + for (; *stropt != NUL; ++stropt) switch (*stropt) { case 'a': case 'A': /* append */ @@ -12473,7 +12813,9 @@ f_setreg(argvars, rettv) } } - write_reg_contents_ex(regname, get_tv_string(&argvars[1]), -1, + strval = get_tv_string_chk(&argvars[1]); + if (strval != NULL) + write_reg_contents_ex(regname, strval, -1, append, yank_type, block_len); rettv->vval.v_number = 0; } @@ -12496,11 +12838,13 @@ f_setwinvar(argvars, rettv) typval_T *varp; char_u nbuf[NUMBUFLEN]; + rettv->vval.v_number = 0; + if (check_restricted() || check_secure()) return; - ++emsg_off; win = find_win_by_nr(&argvars[0]); - varname = get_tv_string(&argvars[1]); + varname = get_tv_string_chk(&argvars[1]); + ++emsg_off; varp = &argvars[2]; if (win != NULL && varname != NULL && varp != NULL) @@ -12514,9 +12858,17 @@ f_setwinvar(argvars, rettv) if (*varname == '&') { + long numval; + char_u *strval; + int error = FALSE; + ++varname; - set_option_value(varname, get_tv_number(varp), - get_tv_string_buf(varp, nbuf), OPT_LOCAL); + --emsg_off; + numval = get_tv_number_chk(varp, &error); + strval = get_tv_string_buf_chk(varp, nbuf); + ++emsg_off; + if (!error && strval != NULL) + set_option_value(varname, numval, strval, OPT_LOCAL); } else { @@ -12572,6 +12924,7 @@ static int static int item_compare_ic; static char_u *item_compare_func; +static int item_compare_func_err; #define ITEM_COMPARE_FAIL 999 /* @@ -12615,6 +12968,10 @@ item_compare2(s1, s2) typval_T argv[2]; int dummy; + /* shortcut after failure in previous call; compare all items equal */ + if (item_compare_func_err) + return 0; + /* copy the values. This is needed to be able to set v_lock to VAR_FIXED * in the copy without changing the original list items. */ copy_tv(&(*(listitem_T **)s1)->li_tv, &argv[0]); @@ -12629,7 +12986,10 @@ item_compare2(s1, s2) if (res == FAIL) res = ITEM_COMPARE_FAIL; else - res = get_tv_number(&rettv); + /* return value has wrong type */ + res = get_tv_number_chk(&rettv, &item_compare_func_err); + if (item_compare_func_err) + res = ITEM_COMPARE_FAIL; clear_tv(&rettv); return res; } @@ -12669,10 +13029,14 @@ f_sort(argvars, rettv) if (argvars[1].v_type != VAR_UNKNOWN) { if (argvars[1].v_type == VAR_FUNC) - item_compare_func = argvars[0].vval.v_string; + item_compare_func = argvars[1].vval.v_string; else { - i = get_tv_number(&argvars[1]); + int error = FALSE; + + i = get_tv_number_chk(&argvars[1], &error); + if (error) + return; /* type error; errmsg already given */ if (i == 1) item_compare_ic = TRUE; else @@ -12688,6 +13052,7 @@ f_sort(argvars, rettv) for (li = l->lv_first; li != NULL; li = li->li_next) ptrs[i++] = li; + item_compare_func_err = FALSE; /* test the compare function */ if (item_compare_func != NULL && item_compare2((void *)&ptrs[0], (void *)&ptrs[1]) @@ -12699,11 +13064,14 @@ f_sort(argvars, rettv) qsort((void *)ptrs, (size_t)len, sizeof(listitem_T *), item_compare_func == NULL ? item_compare : item_compare2); - /* Clear the List and append the items in the sorted order. */ - l->lv_first = l->lv_last = NULL; - l->lv_len = 0; - for (i = 0; i < len; ++i) - list_append(l, ptrs[i]); + if (!item_compare_func_err) + { + /* Clear the List and append the items in the sorted order. */ + l->lv_first = l->lv_last = NULL; + l->lv_len = 0; + for (i = 0; i < len; ++i) + list_append(l, ptrs[i]); + } } vim_free(ptrs); @@ -12726,6 +13094,7 @@ f_split(argvars, rettv) list_T *l; colnr_T col = 0; int keepempty = FALSE; + int typeerr = FALSE; /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ save_cpo = p_cpo; @@ -12734,9 +13103,11 @@ f_split(argvars, rettv) str = get_tv_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { - pat = get_tv_string_buf(&argvars[1], patbuf); + pat = get_tv_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) + typeerr = TRUE; if (argvars[2].v_type != VAR_UNKNOWN) - keepempty = get_tv_number(&argvars[2]); + keepempty = get_tv_number_chk(&argvars[2], &typeerr); } if (pat == NULL || *pat == NUL) pat = (char_u *)"[\\x01- ]\\+"; @@ -12747,6 +13118,8 @@ f_split(argvars, rettv) rettv->v_type = VAR_LIST; rettv->vval.v_list = l; ++l->lv_refcount; + if (typeerr) + return; regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) @@ -12873,14 +13246,18 @@ f_stridx(argvars, rettv) char_u *pos; int start_idx; - needle = get_tv_string(&argvars[1]); - save_haystack = haystack = get_tv_string_buf(&argvars[0], buf); + needle = get_tv_string_chk(&argvars[1]); + save_haystack = haystack = get_tv_string_buf_chk(&argvars[0], buf); rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) + return; /* type error; errmsg already given */ if (argvars[2].v_type != VAR_UNKNOWN) { - start_idx = get_tv_number(&argvars[2]); - if (start_idx >= (int)STRLEN(haystack)) + int error = FALSE; + + start_idx = get_tv_number_chk(&argvars[2], &error); + if (error || start_idx >= (int)STRLEN(haystack)) return; if (start_idx >= 0) haystack += start_idx; @@ -12932,12 +13309,15 @@ f_strpart(argvars, rettv) int n; int len; int slen; + int error = FALSE; p = get_tv_string(&argvars[0]); slen = (int)STRLEN(p); - n = get_tv_number(&argvars[1]); - if (argvars[2].v_type != VAR_UNKNOWN) + n = get_tv_number_chk(&argvars[1], &error); + if (error) + len = 0; + else if (argvars[2].v_type != VAR_UNKNOWN) len = get_tv_number(&argvars[2]); else len = slen - n; /* default len: all bytes that are available. */ @@ -12977,19 +13357,19 @@ f_strridx(argvars, rettv) char_u *lastmatch = NULL; int haystack_len, end_idx; - needle = get_tv_string(&argvars[1]); - haystack = get_tv_string_buf(&argvars[0], buf); + needle = get_tv_string_chk(&argvars[1]); + haystack = get_tv_string_buf_chk(&argvars[0], buf); haystack_len = STRLEN(haystack); + + rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) + return; /* type error; errmsg already given */ if (argvars[2].v_type != VAR_UNKNOWN) { /* Third argument: upper limit for index */ - end_idx = get_tv_number(&argvars[2]); + end_idx = get_tv_number_chk(&argvars[2], NULL); if (end_idx < 0) - { - /* can never find a match */ - rettv->vval.v_number = -1; - return; - } + return; /* can never find a match */ } else end_idx = haystack_len; @@ -13037,7 +13417,8 @@ f_submatch(argvars, rettv) typval_T *rettv; { rettv->v_type = VAR_STRING; - rettv->vval.v_string = reg_submatch((int)get_tv_number(&argvars[0])); + rettv->vval.v_string = + reg_submatch((int)get_tv_number_chk(&argvars[0], NULL)); } /* @@ -13052,12 +13433,16 @@ f_substitute(argvars, rettv) char_u subbuf[NUMBUFLEN]; char_u flagsbuf[NUMBUFLEN]; + char_u *str = get_tv_string_chk(&argvars[0]); + char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf); + char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf); + char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf); + rettv->v_type = VAR_STRING; - rettv->vval.v_string = do_string_sub( - get_tv_string(&argvars[0]), - get_tv_string_buf(&argvars[1], patbuf), - get_tv_string_buf(&argvars[2], subbuf), - get_tv_string_buf(&argvars[3], flagsbuf)); + if (str == NULL || pat == NULL || sub == NULL || flg == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = do_string_sub(str, pat, sub, flg); } /* @@ -13074,12 +13459,13 @@ f_synID(argvars, rettv) long lnum; long col; int trans; + int transerr; - lnum = get_tv_lnum(argvars); - col = get_tv_number(&argvars[1]) - 1; - trans = get_tv_number(&argvars[2]); + lnum = get_tv_lnum(argvars); /* -1 on type error */ + col = get_tv_number(&argvars[1]) - 1; /* -1 on type error */ + trans = get_tv_number_chk(&argvars[2], &transerr); - if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && col < (long)STRLEN(ml_get(lnum))) id = syn_get_id(lnum, (colnr_T)col, trans, NULL); #endif @@ -13236,7 +13622,9 @@ f_system(argvars, rettv) EMSG2(_(e_notopen), infile); goto done; } - p = get_tv_string_buf(&argvars[1], buf); + p = get_tv_string_buf_chk(&argvars[1], buf); + if (p == NULL) + goto done; /* type error; errmsg already given */ if (fwrite(p, STRLEN(p), 1, fd) != 1) err = TRUE; if (fclose(fd) != 0) @@ -13305,6 +13693,8 @@ f_taglist(argvars, rettv) tag_pattern = get_tv_string(&argvars[0]); rettv->vval.v_number = FALSE; + if (*tag_pattern == NUL) + return; l = list_alloc(); if (l != NULL) @@ -13407,39 +13797,8 @@ f_toupper(argvars, rettv) typval_T *argvars; typval_T *rettv; { - char_u *p; - - p = vim_strsave(get_tv_string(&argvars[0])); rettv->v_type = VAR_STRING; - rettv->vval.v_string = p; - - if (p != NULL) - while (*p != NUL) - { -#ifdef FEAT_MBYTE - int l; - - if (enc_utf8) - { - int c, uc; - - c = utf_ptr2char(p); - uc = utf_toupper(c); - l = utf_ptr2len_check(p); - /* TODO: reallocate string when byte count changes. */ - if (utf_char2len(uc) == l) - utf_char2bytes(uc, p); - p += l; - } - else if (has_mbyte && (l = (*mb_ptr2len_check)(p)) > 1) - p += l; /* skip multi-byte character */ - else -#endif - { - *p = TOUPPER_LOC(*p); /* note that toupper() can be a macro */ - p++; - } - } + rettv->vval.v_string = strup_save(get_tv_string(&argvars[0])); } /* @@ -13468,12 +13827,14 @@ f_tr(argvars, rettv) garray_T ga; instr = get_tv_string(&argvars[0]); - fromstr = get_tv_string_buf(&argvars[1], buf); - tostr = get_tv_string_buf(&argvars[2], buf2); + fromstr = get_tv_string_buf_chk(&argvars[1], buf); + tostr = get_tv_string_buf_chk(&argvars[2], buf2); /* Default return value: empty string. */ rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; + if (fromstr == NULL || tostr == NULL) + return; /* type error; errmsg already given */ ga_init2(&ga, (int)sizeof(char), 80); #ifdef FEAT_MBYTE @@ -13718,8 +14079,10 @@ f_winnr(argvars, rettv) if (argvars[0].v_type != VAR_UNKNOWN) { - arg = get_tv_string(&argvars[0]); - if (STRCMP(arg, "$") == 0) + arg = get_tv_string_chk(&argvars[0]); + if (arg == NULL) + nr = 0; /* type error; errmsg already given */ + else if (STRCMP(arg, "$") == 0) twin = lastwin; else if (STRCMP(arg, "#") == 0) { @@ -13806,9 +14169,11 @@ find_win_by_nr(vp) #endif int nr; - nr = get_tv_number(vp); + nr = get_tv_number_chk(vp, NULL); #ifdef FEAT_WINDOWS + if (nr < 0) + return NULL; if (nr == 0) return curwin; @@ -13906,7 +14271,9 @@ var2fpos(varp, lnum) static pos_T pos; pos_T *pp; - name = get_tv_string(varp); + name = get_tv_string_chk(varp); + if (name == NULL) + return NULL; if (name[0] == '.') /* cursor */ return &curwin->w_cursor; if (name[0] == '\'') /* mark */ @@ -14621,18 +14988,31 @@ init_tv(varp) /* * Get the number value of a variable. * If it is a String variable, uses vim_str2nr(). + * For incompatible types, return 0. + * get_tv_number_chk() is similar to get_tv_number(), but informs the + * caller of incompatible types: it sets *denote to TRUE if "denote" + * is not NULL or returns -1 otherwise. */ static long get_tv_number(varp) typval_T *varp; { + int error = FALSE; + + return get_tv_number_chk(varp, &error); /* return 0L on error */ +} + + static long +get_tv_number_chk(varp, denote) + typval_T *varp; + int *denote; +{ long n = 0L; switch (varp->v_type) { case VAR_NUMBER: - n = (long)(varp->vval.v_number); - break; + return (long)(varp->vval.v_number); case VAR_FUNC: EMSG(_("E703: Using a Funcref as a number")); break; @@ -14640,7 +15020,7 @@ get_tv_number(varp) if (varp->vval.v_string != NULL) vim_str2nr(varp->vval.v_string, NULL, NULL, TRUE, TRUE, &n, NULL); - break; + return n; case VAR_LIST: EMSG(_("E745: Using a List as a number")); break; @@ -14651,11 +15031,16 @@ get_tv_number(varp) EMSG2(_(e_intern2), "get_tv_number()"); break; } + if (denote == NULL) /* useful for values that must be unsigned */ + n = -1; + else + *denote = TRUE; return n; } /* * Get the lnum from the first argument. Also accepts ".", "$", etc. + * Returns -1 on error. */ static linenr_T get_tv_lnum(argvars) @@ -14664,7 +15049,7 @@ get_tv_lnum(argvars) typval_T rettv; linenr_T lnum; - lnum = get_tv_number(&argvars[0]); + lnum = get_tv_number_chk(&argvars[0], NULL); if (lnum == 0) /* no valid number, try using line() */ { rettv.v_type = VAR_NUMBER; @@ -14682,6 +15067,8 @@ get_tv_lnum(argvars) * get_tv_string_buf() uses a given buffer. * If the String variable has never been set, return an empty string. * Never returns NULL; + * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return + * NULL on error. */ static char_u * get_tv_string(varp) @@ -14697,6 +15084,25 @@ get_tv_string_buf(varp, buf) typval_T *varp; char_u *buf; { + char_u *res = get_tv_string_buf_chk(varp, buf); + + return res != NULL ? res : (char_u *)""; +} + + static char_u * +get_tv_string_chk(varp) + typval_T *varp; +{ + static char_u mybuf[NUMBUFLEN]; + + return get_tv_string_buf_chk(varp, mybuf); +} + + static char_u * +get_tv_string_buf_chk(varp, buf) + typval_T *varp; + char_u *buf; +{ switch (varp->v_type) { case VAR_NUMBER: @@ -14714,12 +15120,12 @@ get_tv_string_buf(varp, buf) case VAR_STRING: if (varp->vval.v_string != NULL) return varp->vval.v_string; - break; + return (char_u *)""; default: EMSG2(_(e_intern2), "get_tv_string_buf()"); break; } - return (char_u *)""; + return NULL; } /* diff --git a/src/ex_cmds.c b/src/ex_cmds.c index 9bef62db4..834614566 100644 --- a/src/ex_cmds.c +++ b/src/ex_cmds.c @@ -5349,13 +5349,13 @@ find_help_tags(arg, num_matches, matches, keep_lang) int i; static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*", "/*", "/\\*", "\"*", "/\\(\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", "/\\?", "/\\z(\\)", "\\=", ":s\\=", "[count]", "[quotex]", "[range]", "[pattern]", "\\|", "\\%$"}; static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star", "/star", "/\\\\star", "quotestar", "/\\\\(\\\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", "\\[count]", "\\[quotex]", "\\[range]", "\\[pattern]", "\\\\bar", "/\\\\%\\$"}; diff --git a/src/ex_docmd.c b/src/ex_docmd.c index 09e12bcc0..fbb0f5a78 100644 --- a/src/ex_docmd.c +++ b/src/ex_docmd.c @@ -8139,6 +8139,7 @@ theend: #if ((defined(FEAT_SESSION) || defined(FEAT_EVAL)) && defined(vim_mkdir)) \ || defined(PROTO) +/*ARGSUSED*/ int vim_mkdir_emsg(name, prot) char_u *name; diff --git a/src/ex_getln.c b/src/ex_getln.c index 5d04ad301..51b2f251c 100644 --- a/src/ex_getln.c +++ b/src/ex_getln.c @@ -2997,10 +2997,10 @@ nextwild(xp, type, options) v = OK; if (v == OK) { - vim_strncpy(&ccline.cmdbuff[ccline.cmdpos + difflen], - &ccline.cmdbuff[ccline.cmdpos], - ccline.cmdlen - ccline.cmdpos + 1); - STRNCPY(&ccline.cmdbuff[i], p2, STRLEN(p2)); + mch_memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], + &ccline.cmdbuff[ccline.cmdpos], + (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); + mch_memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); ccline.cmdlen += difflen; ccline.cmdpos += difflen; } diff --git a/src/gui_w32.c b/src/gui_w32.c index 8d0a4cef0..8046a8df0 100644 --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -556,7 +556,6 @@ _OnWindowPosChanged( static int _DuringSizing( - HWND hwnd, UINT fwSide, LPRECT lprc) { @@ -681,7 +680,7 @@ _WndProc( #endif case WM_SIZING: /* HANDLE_MSG doesn't seem to handle this one */ - return _DuringSizing(hwnd, (UINT)wParam, (LPRECT)lParam); + return _DuringSizing((UINT)wParam, (LPRECT)lParam); case WM_MOUSEWHEEL: _OnMouseWheel(hwnd, HIWORD(wParam)); @@ -842,12 +841,14 @@ gui_mch_set_parent(char *title) } } +#ifndef FEAT_OLE static void ole_error(char *arg) { EMSG2(_("E243: Argument not supported: \"-%s\"; Use the OLE version."), arg); } +#endif /* * Parse the GUI related command-line arguments. Any arguments used are @@ -1260,6 +1261,7 @@ get_work_area(RECT *spi_rect) /* * Set the size of the window to the given width and height in pixels. */ +/*ARGSUSED*/ void gui_mch_set_shellsize(int width, int height, int min_width, int min_height, int base_width, int base_height) @@ -2045,7 +2047,7 @@ gui_mch_draw_string( { int x; int offset; - const static int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 }; + static const int val[8] = {1, 0, 0, 0, 1, 2, 2, 2 }; y = FILL_Y(row + 1) - 1; for (x = FILL_X(col); x < FILL_X(col + len); ++x) @@ -2451,6 +2453,7 @@ gui_mch_menu_grey( * pressed, return that button's ID - IDCANCEL (2), which is the button's * number. */ +/*ARGSUSED*/ static LRESULT CALLBACK dialog_callback( HWND hwnd, @@ -4037,6 +4040,7 @@ delete_tooltip(beval) DestroyWindow(beval->balloon); } +/*ARGSUSED*/ static VOID CALLBACK BevalTimerProc(hwnd, uMsg, idEvent, dwTime) HWND hwnd; @@ -4078,6 +4082,7 @@ BevalTimerProc(hwnd, uMsg, idEvent, dwTime) } } +/*ARGSUSED*/ void gui_mch_disable_beval_area(beval) BalloonEval *beval; @@ -4087,6 +4092,7 @@ gui_mch_disable_beval_area(beval) // TRACE0("gui_mch_disable_beval_area }}}"); } +/*ARGSUSED*/ void gui_mch_enable_beval_area(beval) BalloonEval *beval; @@ -4162,6 +4168,7 @@ gui_mch_create_beval_area(target, mesg, mesgCB, clientData) return beval; } +/*ARGSUSED*/ static void Handle_WM_Notify(hwnd, pnmh) HWND hwnd; diff --git a/src/gui_w48.c b/src/gui_w48.c index 4729f9b1d..be2354681 100644 --- a/src/gui_w48.c +++ b/src/gui_w48.c @@ -314,7 +314,7 @@ static LOGFONT norm_logfont; static LRESULT _OnImeNotify(HWND hWnd, DWORD dwCommand, DWORD dwData); #endif -#ifdef DEBUG +#ifdef DEBUG_PRINT_ERROR /* * Print out the last Windows error message */ @@ -330,7 +330,7 @@ print_windows_error(void) TRACE1("Error: %s\n", lpMsgBuf); LocalFree(lpMsgBuf); } -#endif /* DEBUG */ +#endif /* * Cursor blink functions. @@ -445,6 +445,7 @@ gui_mch_start_blink(void) * Call-back routines. */ +/*ARGSUSED*/ static VOID CALLBACK _OnTimer( HWND hwnd, @@ -467,6 +468,7 @@ _OnTimer( s_wait_timer = 0; } +/*ARGSUSED*/ static void _OnDeadChar( HWND hwnd, @@ -555,6 +557,7 @@ char_to_string(int ch, char_u *string, int slen) /* * Key hit, add it to the input buffer. */ +/*ARGSUSED*/ static void _OnChar( HWND hwnd, @@ -577,6 +580,7 @@ _OnChar( /* * Alt-Key hit, add it to the input buffer. */ +/*ARGSUSED*/ static void _OnSysChar( HWND hwnd, @@ -656,6 +660,7 @@ _OnMouseEvent( gui_send_mouse_event(button, x, y, repeated_click, vim_modifiers); } +/*ARGSUSED*/ static void _OnMouseButtonDown( HWND hwnd, @@ -756,6 +761,7 @@ _OnMouseButtonDown( } } +/*ARGSUSED*/ static void _OnMouseMoveOrRelease( HWND hwnd, @@ -832,6 +838,7 @@ gui_mswin_find_menu( return pMenu; } +/*ARGSUSED*/ static void _OnMenu( HWND hwnd, @@ -1255,6 +1262,7 @@ gui_mch_get_font( * Return the name of font "font" in allocated memory. * Don't know how to get the actual name, thus use the provided name. */ +/*ARGSUSED*/ char_u * gui_mch_get_fontname(font, name) GuiFont font; @@ -1937,6 +1945,7 @@ gui_mch_enable_menu(int flag) #endif } +/*ARGSUSED*/ void gui_mch_set_menu_pos( int x, @@ -2291,6 +2300,7 @@ _OnEndSession(void) * Get this message when the user clicks on the cross in the top right corner * of a Windows95 window. */ +/*ARGSUSED*/ static void _OnClose( HWND hwnd) @@ -2344,6 +2354,7 @@ _OnPaint( } } +/*ARGSUSED*/ static void _OnSize( HWND hwnd, @@ -2588,6 +2599,7 @@ gui_mch_insert_lines( } +/*ARGSUSED*/ void gui_mch_exit(int rc) { @@ -2657,6 +2669,7 @@ logfont2name(LOGFONT lf) * Initialise vim to use the font with the given name. * Return FAIL if the font could not be loaded, OK otherwise. */ +/*ARGSUSED*/ int gui_mch_init_font(char_u *font_name, int fontset) { @@ -2761,6 +2774,7 @@ gui_mch_newfont() /* * Set the window title */ +/*ARGSUSED*/ void gui_mch_settitle( char_u *title, @@ -3162,6 +3176,7 @@ gui_mch_browse( } #endif /* FEAT_BROWSE */ +/*ARGSUSED*/ static void _OnDropFiles( HWND hwnd, @@ -3229,6 +3244,7 @@ _OnDropFiles( #endif } +/*ARGSUSED*/ static int _OnScroll( HWND hwnd, @@ -3361,6 +3377,7 @@ _OnScroll( * Return pointer to buffer in "tofree". * Returns zero when out of memory. */ +/*ARGSUSED*/ int get_cmd_args(char *prog, char *cmdline, char ***argvp, char **tofree) { @@ -3473,7 +3490,7 @@ get_cmd_args(char *prog, char *cmdline, char ***argvp, char **tofree) } } - if (pnew != NUL) + if (pnew != NULL) *pnew++ = NUL; while (*p == ' ' || *p == '\t') ++p; /* advance until a non-space */ diff --git a/src/if_cscope.c b/src/if_cscope.c index 7163c8a03..7653308f1 100644 --- a/src/if_cscope.c +++ b/src/if_cscope.c @@ -1226,11 +1226,13 @@ GetWin32Error() return msg; } #endif + /* * PRIVATE: cs_insert_filelist * * insert a new cscope database filename into the filelist */ +/*ARGSUSED*/ static int cs_insert_filelist(fname, ppath, flags, sb) char *fname; diff --git a/src/if_python.c b/src/if_python.c index b799047e9..8aca83518 100644 --- a/src/if_python.c +++ b/src/if_python.c @@ -384,10 +384,13 @@ static int initialised = 0; #if PYTHON_API_VERSION < 1007 /* Python 1.4 */ typedef PyObject PyThreadState; -#endif /* Python 1.4 */ +#endif -#ifndef PY_CAN_RECURSE +#ifdef PY_CAN_RECURSE +static PyGILState_STATE pygilstate = PyGILState_UNLOCKED; +#else static PyThreadState *saved_python_thread = NULL; +#endif /* * Suspend a thread of the Python interpreter, other threads are allowed to @@ -396,7 +399,11 @@ static PyThreadState *saved_python_thread = NULL; static void Python_SaveThread(void) { +#ifdef PY_CAN_RECURSE + PyGILState_Release(pygilstate); +#else saved_python_thread = PyEval_SaveThread(); +#endif } /* @@ -406,10 +413,13 @@ Python_SaveThread(void) static void Python_RestoreThread(void) { +#ifdef PY_CAN_RECURSE + pygilstate = PyGILState_Ensure(); +#else PyEval_RestoreThread(saved_python_thread); saved_python_thread = NULL; -} #endif +} /* * obtain a lock on the Vim data structures @@ -430,11 +440,17 @@ python_end() { #ifdef DYNAMIC_PYTHON if (hinstPython && Py_IsInitialized()) + { + Python_RestoreThread(); /* enter python */ Py_Finalize(); + } end_dynamic_python(); #else if (Py_IsInitialized()) + { + Python_RestoreThread(); /* enter python */ Py_Finalize(); + } #endif } @@ -470,11 +486,7 @@ Python_Init(void) goto fail; /* the first python thread is vim's, release the lock */ -#ifdef PY_CAN_RECURSE - PyEval_SaveThread(); -#else Python_SaveThread(); -#endif initialised = 1; } @@ -497,9 +509,7 @@ fail: static void DoPythonCommand(exarg_T *eap, const char *cmd) { -#ifdef PY_CAN_RECURSE - PyGILState_STATE pygilstate; -#else +#ifndef PY_CAN_RECURSE static int recursive = 0; #endif #if defined(MACOS) && !defined(MACOS_X_UNIX) @@ -544,19 +554,11 @@ DoPythonCommand(exarg_T *eap, const char *cmd) } #endif -#ifdef PY_CAN_RECURSE - pygilstate = PyGILState_Ensure(); -#else Python_RestoreThread(); /* enter python */ -#endif PyRun_SimpleString((char *)(cmd)); -#ifdef PY_CAN_RECURSE - PyGILState_Release(pygilstate); -#else Python_SaveThread(); /* leave python */ -#endif #if defined(HAVE_LOCALE_H) || defined(X_LOCALE) if (saved_locale != NULL) diff --git a/src/misc1.c b/src/misc1.c index d4dabe634..7209a1ae2 100644 --- a/src/misc1.c +++ b/src/misc1.c @@ -3137,6 +3137,27 @@ get_number(colon) return n; } +/* + * Ask the user to enter a number. + */ + int +prompt_for_number() +{ + int i; + + /* When using ":silent" assume that <CR> was entered. */ + MSG_PUTS(_("Choice number (<Enter> cancels): ")); + i = get_number(TRUE); + if (KeyTyped) /* don't call wait_return() now */ + { + msg_putchar('\n'); + cmdline_row = msg_row - 1; + need_wait_return = FALSE; + msg_didany = FALSE; + } + return i; +} + void msgmore(n) long n; diff --git a/src/misc2.c b/src/misc2.c index b3ab1f241..6775932c8 100644 --- a/src/misc2.c +++ b/src/misc2.c @@ -1082,6 +1082,69 @@ vim_strup(p) } } +#if defined(FEAT_EVAL) || defined(FEAT_SYN_HL) || defined(PROTO) +/* + * Make string "s" all upper-case and return it in allocated memory. + * Handles multi-byte characters as well as possible. + * Returns NULL when out of memory. + */ + char_u * +strup_save(orig) + char_u *orig; +{ + char_u *p; + char_u *res; + + res = p = vim_strsave(orig); + + if (res != NULL) + while (*p != NUL) + { +# ifdef FEAT_MBYTE + int l; + + if (enc_utf8) + { + int c, uc; + int nl; + char_u *s; + + c = utf_ptr2char(p); + uc = utf_toupper(c); + + /* Reallocate string when byte count changes. This is rare, + * thus it's OK to do another malloc()/free(). */ + l = utf_ptr2len_check(p); + nl = utf_char2len(uc); + if (nl != l) + { + s = alloc((unsigned)STRLEN(res) + 1 + nl - l); + if (s == NULL) + break; + mch_memmove(s, res, p - res); + STRCPY(s + (p - res) + nl, p + l); + p = s + (p - res); + vim_free(res); + res = s; + } + + utf_char2bytes(uc, p); + p += nl; + } + else if (has_mbyte && (l = (*mb_ptr2len_check)(p)) > 1) + p += l; /* skip multi-byte character */ + else +# endif + { + *p = TOUPPER_LOC(*p); /* note that toupper() can be a macro */ + p++; + } + } + + return res; +} +#endif + /* * copy a space a number of times */ @@ -1131,43 +1194,16 @@ del_trailing_spaces(ptr) } /* - * This is here because strncpy() does not guarantee successful results when - * the to and from strings overlap. It is only currently called from - * nextwild() which copies part of the command line to another part of the - * command line. This produced garbage when expanding files etc in the middle - * of the command line (on my terminal, anyway) -- webb. - * Note: strncpy() pads the remainder of the buffer with NUL bytes, - * vim_strncpy() doesn't do that. + * Like strncpy(), but always terminate the result with one NUL. */ void vim_strncpy(to, from, len) - char_u *to; - char_u *from; - int len; + char_u *to; + char_u *from; + int len; { - int i; - - if (to <= from) - { - while (len-- && *from) - *to++ = *from++; - if (len >= 0) - *to = *from; /* Copy NUL */ - } - else - { - for (i = 0; i < len; i++) - { - to++; - if (*from++ == NUL) - { - i++; - break; - } - } - for (; i > 0; i--) - *--to = *--from; - } + STRNCPY(to, from, len); + to[len] = NUL; } /* diff --git a/src/normal.c b/src/normal.c index e4bd3964d..fa5e355e7 100644 --- a/src/normal.c +++ b/src/normal.c @@ -4685,6 +4685,11 @@ dozet: spell_add_word(ptr, len, nchar == 'w'); } break; + + case '?': /* "z?": suggestions for a badly spelled word */ + if (!checkclearopq(cap->oap)) + spell_suggest(); + break; #endif default: clearopbeep(cap->oap); @@ -6106,7 +6111,7 @@ nv_brackets(cap) setpcmark(); for (n = 0; n < cap->count1; ++n) if (spell_move_to(cap->cmdchar == ']' ? FORWARD : BACKWARD, - cap->nchar == 's' ? TRUE : FALSE) == FAIL) + cap->nchar == 's' ? TRUE : FALSE, FALSE) == FAIL) { clearopbeep(cap->oap); break; diff --git a/src/option.c b/src/option.c index 14f8ade78..c0ea094f6 100644 --- a/src/option.c +++ b/src/option.c @@ -5683,20 +5683,32 @@ did_set_string_option(opt_idx, varp, new_value_alloced, oldval, errbuf, #endif #ifdef FEAT_SYN_HL - /* When 'spelllang' is set and there is a window for this buffer in which - * 'spell' is set load the wordlists. */ - else if (varp == &(curbuf->b_p_spl)) + /* When 'spelllang' or 'spellfile' is set and there is a window for this + * buffer in which 'spell' is set load the wordlists. */ + else if (varp == &(curbuf->b_p_spl) || varp == &(curbuf->b_p_spf)) { win_T *wp; + int l; - FOR_ALL_WINDOWS(wp) - if (wp->w_buffer == curbuf && wp->w_p_spell) - { - errmsg = did_set_spelllang(curbuf); + if (varp == &(curbuf->b_p_spf)) + { + l = STRLEN(curbuf->b_p_spf); + if (l > 0 && (l < 4 || STRCMP(curbuf->b_p_spf + l - 4, + ".add") != 0)) + errmsg = e_invarg; + } + + if (errmsg == NULL) + { + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer == curbuf && wp->w_p_spell) + { + errmsg = did_set_spelllang(curbuf); # ifdef FEAT_WINDOWS - break; + break; # endif - } + } + } } #endif diff --git a/src/os_mswin.c b/src/os_mswin.c index 8a47dd16f..b0c137fca 100644 --- a/src/os_mswin.c +++ b/src/os_mswin.c @@ -332,6 +332,7 @@ mch_settitle( * 2: Just restore icon (which we don't have) * 3: Restore title and icon (which we don't have) */ +/*ARGSUSED*/ void mch_restore_title( int which) @@ -370,6 +371,7 @@ mch_can_restore_icon() * When 'shellslash' set do it the other way around. * Return OK or FAIL. */ +/*ARGSUSED*/ int mch_FullName( char_u *fname, @@ -525,6 +527,7 @@ vim_stat(const char *name, struct stat *stp) } #if defined(FEAT_GUI_MSWIN) || defined(PROTO) +/*ARGSUSED*/ void mch_settmode(int tmode) { @@ -701,6 +704,7 @@ mch_chdir(char *path) * Switching off termcap mode is only allowed when Columns is 80, otherwise a * crash may result. It's always allowed on NT or when running the GUI. */ +/*ARGSUSED*/ int can_end_termcap_mode( int give_msg) @@ -732,6 +736,7 @@ mch_char_avail() /* * set screen mode, always fails. */ +/*ARGSUSED*/ int mch_screenmode( char_u *arg) @@ -1028,6 +1033,7 @@ typedef struct /* * Make vim the owner of the current selection. Return OK upon success. */ +/*ARGSUSED*/ int clip_mch_own_selection(VimClipboard *cbd) { @@ -1041,6 +1047,7 @@ clip_mch_own_selection(VimClipboard *cbd) /* * Make vim NOT the owner of the current selection. */ +/*ARGSUSED*/ void clip_mch_lose_selection(VimClipboard *cbd) { @@ -1228,7 +1235,6 @@ clip_mch_request_selection(VimClipboard *cbd) #ifdef FEAT_MBYTE HGLOBAL rawh = NULL; #endif - char_u *hMemStr = NULL; int str_size = 0; int maxlen; size_t n; @@ -1327,7 +1333,7 @@ clip_mch_request_selection(VimClipboard *cbd) { if ((hMem = GetClipboardData(CF_TEXT)) != NULL) { - str = hMemStr = (char_u *)GlobalLock(hMem); + str = (char_u *)GlobalLock(hMem); /* The length is either what our metadata says or the strlen(). * But limit it to the GlobalSize() for safety. */ @@ -1587,6 +1593,7 @@ clip_mch_set_selection(VimClipboard *cbd) /* * Debugging helper: expose the MCH_WRITE_DUMP stuff to other modules */ +/*ARGSUSED*/ void DumpPutS( const char *psz) @@ -1736,6 +1743,7 @@ swap_me(COLORREF colorref) return colorref; } +/*ARGSUSED*/ static BOOL CALLBACK PrintDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) { @@ -1798,6 +1806,7 @@ PrintDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) return FALSE; } +/*ARGSUSED*/ static BOOL CALLBACK AbortProc(HDC hdcPrn, int iCode) { @@ -2247,6 +2256,7 @@ mch_print_begin(prt_settings_T *psettings) return (ret > 0); } +/*ARGSUSED*/ void mch_print_end(prt_settings_T *psettings) { @@ -2935,8 +2945,7 @@ typedef struct HWND server; /* server window */ char_u *reply; /* reply string */ int expr_result; /* 0 for REPLY, 1 for RESULT 2 for error */ -} -reply_T; +} reply_T; static garray_T reply_list = {0, 0, sizeof(reply_T), 5, 0}; @@ -3186,6 +3195,7 @@ points_to_pixels(char_u *str, char_u **end, int vertical, int pprinter_dc) return pixels; } +/*ARGSUSED*/ static int CALLBACK font_enumproc( ENUMLOGFONT *elf, diff --git a/src/os_w32exe.c b/src/os_w32exe.c index ac9bf3c4a..8c48fb338 100644 --- a/src/os_w32exe.c +++ b/src/os_w32exe.c @@ -38,6 +38,7 @@ void _cdecl SaveInst(HINSTANCE hInst); void (_cdecl *pSaveInst)(HINSTANCE); #endif +/*ARGSUSED*/ int WINAPI WinMain( HINSTANCE hInstance, diff --git a/src/os_win32.c b/src/os_win32.c index a55f46c19..82a3ce0f9 100644 --- a/src/os_win32.c +++ b/src/os_win32.c @@ -300,8 +300,7 @@ dyn_libintl_init(char *libname) } /* The bind_textdomain_codeset() function is optional. */ - (FARPROC)dyn_libintl_bind_textdomain_codeset = - (FARPROC)GetProcAddress(hLibintlDLL, + dyn_libintl_bind_textdomain_codeset = (void *)GetProcAddress(hLibintlDLL, "bind_textdomain_codeset"); if (dyn_libintl_bind_textdomain_codeset == NULL) dyn_libintl_bind_textdomain_codeset = @@ -322,18 +321,21 @@ dyn_libintl_end() dyn_libintl_bind_textdomain_codeset = null_libintl_bind_textdomain_codeset; } +/*ARGSUSED*/ static char * null_libintl_gettext(const char *msgid) { return (char*)msgid; } +/*ARGSUSED*/ static char * null_libintl_bindtextdomain(const char *domainname, const char *dirname) { return NULL; } +/*ARGSUSED*/ static char * null_libintl_bind_textdomain_codeset(const char *domainname, const char *codeset) @@ -341,6 +343,7 @@ null_libintl_bind_textdomain_codeset(const char *domainname, return NULL; } +/*ARGSUSED*/ static char * null_libintl_textdomain(const char *domainname) { @@ -446,6 +449,7 @@ mch_windows95(void) static int old_num_windows; static int num_windows; +/*ARGSUSED*/ static BOOL CALLBACK win32ssynch_cb(HWND hwnd, LPARAM lparam) { @@ -762,6 +766,7 @@ decode_key_event( * For the GUI the mouse handling is in gui_w32.c. */ # ifdef FEAT_GUI_W32 +/*ARGSUSED*/ void mch_setmouse(int on) { @@ -2211,6 +2216,7 @@ mch_exit(int r) /* * Do we have an interactive window? */ +/*ARGSUSED*/ int mch_check_win( int argc, @@ -3444,6 +3450,7 @@ termcap_mode_end(void) #ifdef FEAT_GUI_W32 +/*ARGSUSED*/ void mch_write( char_u *s, @@ -4099,6 +4106,7 @@ mch_write( /* * Delay for half a second. */ +/*ARGSUSED*/ void mch_delay( long msec, @@ -4716,7 +4724,7 @@ copy_infostreams(char_u *from, char_u *to) /* Advance to the next stream. We might try seeking too far, * but BackupSeek() doesn't skip over stream borders, thus * that's OK. */ - (void)BackupSeek(sh, sid.Size.LowPart, sid.Size.u.HighPart, + (void)BackupSeek(sh, sid.Size.u.LowPart, sid.Size.u.HighPart, &lo, &hi, &context); } diff --git a/src/proto/misc1.pro b/src/proto/misc1.pro index 5a42c13d1..c2ae831fc 100644 --- a/src/proto/misc1.pro +++ b/src/proto/misc1.pro @@ -42,6 +42,7 @@ void change_warning __ARGS((int col)); int ask_yesno __ARGS((char_u *str, int direct)); int get_keystroke __ARGS((void)); int get_number __ARGS((int colon)); +int prompt_for_number __ARGS((void)); void msgmore __ARGS((long n)); void beep_flush __ARGS((void)); void vim_beep __ARGS((void)); diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro index 365f2ea95..38d50a985 100644 --- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -31,6 +31,7 @@ char_u *vim_strsave_escaped_ext __ARGS((char_u *string, char_u *esc_chars, int c char_u *vim_strsave_up __ARGS((char_u *string)); char_u *vim_strnsave_up __ARGS((char_u *string, int len)); void vim_strup __ARGS((char_u *p)); +char_u *strup_save __ARGS((char_u *orig)); void copy_spaces __ARGS((char_u *ptr, size_t count)); void copy_chars __ARGS((char_u *ptr, size_t count, int c)); void del_trailing_spaces __ARGS((char_u *ptr)); diff --git a/src/proto/spell.pro b/src/proto/spell.pro index 71d6dbc1e..c932b1f71 100644 --- a/src/proto/spell.pro +++ b/src/proto/spell.pro @@ -1,6 +1,6 @@ /* spell.c */ int spell_check __ARGS((win_T *wp, char_u *ptr, int *attrp)); -int spell_move_to __ARGS((int dir, int allwords)); +int spell_move_to __ARGS((int dir, int allwords, int curline)); char_u *did_set_spelllang __ARGS((buf_T *buf)); void spell_reload __ARGS((void)); void put_bytes __ARGS((FILE *fd, long_u nr, int len)); @@ -8,4 +8,5 @@ void ex_mkspell __ARGS((exarg_T *eap)); void ex_spell __ARGS((exarg_T *eap)); void spell_add_word __ARGS((char_u *word, int len, int bad)); void init_spell_chartab __ARGS((void)); +void spell_suggest __ARGS((void)); /* vim: set ft=c : */ diff --git a/src/spell.c b/src/spell.c index cbe2f9fc6..2b37f632f 100644 --- a/src/spell.c +++ b/src/spell.c @@ -29,12 +29,18 @@ */ /* + * Use this to let the score depend in how much a suggestion sounds like the + * bad word. It's quite slow and doesn't make the sorting much better.... + * #define SOUNDFOLD_SCORE + */ + +/* * Vim spell file format: <HEADER> <SUGGEST> <LWORDTREE> <KWORDTREE> * * <HEADER>: <fileID> <regioncnt> <regionname> ... * <charflagslen> <charflags> <fcharslen> <fchars> * - * <fileID> 10 bytes "VIMspell05" + * <fileID> 10 bytes "VIMspell06" * <regioncnt> 1 byte number of regions following (8 supported) * <regionname> 2 bytes Region name: ca, au, etc. Lower case. * First <regionname> is region 1. @@ -47,11 +53,41 @@ * <fchars> N bytes Folded characters, first one is for character 128. * * - * <SUGGEST> : <suggestlen> <more> ... + * <SUGGEST> : <repcount> <rep> ... + * <salflags> <salcount> <sal> ... + * <maplen> <mapstr> + * + * <repcount> 2 bytes number of <rep> items, MSB first. + * + * <rep> : <repfromlen> <repfrom> <reptolen> <repto> + * + * <repfromlen> 1 byte length of <repfrom> + * + * <repfrom> N bytes "from" part of replacement + * + * <reptolen> 1 byte length of <repto> + * + * <repto> N bytes "to" part of replacement + * + * <salflags> 1 byte flags for soundsalike conversion: + * SAL_F0LLOWUP + * SAL_COLLAPSE + * SAL_REM_ACCENTS + * + * <sal> : <salfromlen> <salfrom> <saltolen> <salto> + * + * <salfromlen> 1 byte length of <salfrom> * - * <suggestlen> 4 bytes Length of <SUGGEST> in bytes, excluding - * <suggestlen>. MSB first. - * <more> To be defined. + * <salfrom> N bytes "from" part of soundsalike + * + * <saltolen> 1 byte length of <salto> + * + * <salto> N bytes "to" part of soundsalike + * + * <maplen> 2 bytes length of <mapstr>, MSB first + * + * <mapstr> N bytes String with sequences of similar characters, + * separated by slashes. * * * <LWORDTREE>: <wordtree> @@ -90,9 +126,7 @@ * * <KWORDTREE>: <wordtree> * - * * All text characters are in 'encoding', but stored as single bytes. - * The region name is ASCII. */ #if defined(MSDOS) || defined(WIN16) || defined(WIN32) || defined(_WIN64) @@ -107,7 +141,9 @@ # include <fcntl.h> #endif -#define MAXWLEN 250 /* assume max. word len is this many bytes */ +#define MAXWLEN 250 /* Assume max. word len is this many bytes. + Some places assume a word length fits in a + byte, thus it can't be above 255. */ /* Flags used for a word. */ #define WF_REGION 0x01 /* region byte follows */ @@ -115,21 +151,23 @@ #define WF_ALLCAP 0x04 /* word must be all capitals */ #define WF_RARE 0x08 /* rare word */ #define WF_BANNED 0x10 /* bad word */ +#define WF_KEEPCAP 0x80 /* keep-case word */ -#define WF_KEEPCAP 0x100 /* keep-case word (not stored in file) */ +#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP) #define BY_NOFLAGS 0 /* end of word without flags or region */ #define BY_FLAGS 1 /* end of word, flag byte follows */ #define BY_INDEX 2 /* child is shared, index follows */ #define BY_SPECIAL BY_INDEX /* hightest special byte value */ -/* Info from "REP" entries in ".aff" file used in af_rep. - * TODO: This is not used yet. Either use it or remove it. */ -typedef struct repentry_S +/* Info from "REP" and "SAL" entries in ".aff" file used in si_rep, sl_rep, + * si_sal and sl_sal. + * One replacement: from "ft_from" to "ft_to". */ +typedef struct fromto_S { - char_u *re_from; - char_u *re_to; -} repentry_T; + char_u *ft_from; + char_u *ft_to; +} fromto_T; /* * Structure used to store words and other info for one language, loaded from @@ -152,22 +190,34 @@ struct slang_S slang_T *sl_next; /* next language */ char_u *sl_name; /* language name "en", "en.rare", "nl", etc. */ char_u *sl_fname; /* name of .spl file */ - int sl_add; /* TRUE if it's an addition. */ + int sl_add; /* TRUE if it's a .add file. */ char_u *sl_fbyts; /* case-folded word bytes */ int *sl_fidxs; /* case-folded word indexes */ char_u *sl_kbyts; /* keep-case word bytes */ int *sl_kidxs; /* keep-case word indexes */ - char_u *sl_try; /* "TRY" from .aff file TODO: not used */ - garray_T sl_rep; /* list of repentry_T entries from REP lines - * TODO not used */ char_u sl_regions[17]; /* table with up to 8 region names plus NUL */ - int sl_error; /* error while loading */ + + garray_T sl_rep; /* list of fromto_T entries from REP lines */ + short sl_rep_first[256]; /* indexes where byte first appears, -1 if + there is none */ + garray_T sl_sal; /* list of fromto_T entries from SAL lines */ + short sl_sal_first[256]; /* indexes where byte first appears, -1 if + there is none */ + int sl_followup; /* SAL followup */ + int sl_collapse; /* SAL collapse_result */ + int sl_rem_accents; /* SAL remove_accents */ + char_u *sl_map; /* string with similar chars from MAP lines */ }; /* First language that is loaded, start of the linked list of loaded * languages. */ static slang_T *first_lang = NULL; +/* Flags used in .spl file for soundsalike flags. */ +#define SAL_F0LLOWUP 1 +#define SAL_COLLAPSE 2 +#define SAL_REM_ACCENTS 4 + /* * Structure used in "b_langp", filled from 'spelllang'. */ @@ -188,16 +238,71 @@ typedef struct langp_S #define SP_LOCAL 2 #define SP_BAD 3 -#define VIMSPELLMAGIC "VIMspell05" /* string at start of Vim spell file */ +#define VIMSPELLMAGIC "VIMspell06" /* string at start of Vim spell file */ #define VIMSPELLMAGICL 10 /* + * Information used when looking for suggestions. + */ +typedef struct suginfo_S +{ + garray_T su_ga; /* suggestions, contains "suggest_T" */ + int su_maxscore; /* maximum score for adding to su_ga */ + int su_icase; /* accept words with wrong case */ + int su_icase_add; /* add matches while ignoring case */ + char_u *su_badptr; /* start of bad word in line */ + int su_badlen; /* length of detected bad word in line */ + char_u su_badword[MAXWLEN]; /* bad word truncated at su_badlen */ + char_u su_fbadword[MAXWLEN]; /* su_badword case-folded */ + hashtab_T su_banned; /* table with banned words */ +#ifdef SOUNDFOLD_SCORE + slang_T *su_slang; /* currently used slang_T */ + char_u su_salword[MAXWLEN]; /* soundfolded badword */ +#endif +} suginfo_T; + +/* One word suggestion. Used in "si_ga". */ +typedef struct suggest_S +{ + char_u *st_word; /* suggested word, allocated string */ + int st_orglen; /* length of replaced text */ + int st_score; /* lower is better */ +} suggest_T; + +#define SUG(sup, i) (((suggest_T *)(sup)->su_ga.ga_data)[i]) + +/* Number of suggestions displayed. */ +#define SUG_PROMPT_COUNT ((int)Rows - 2) + +/* Threshold for sorting and cleaning up suggestions. */ +#define SUG_CLEANUP_COUNT (SUG_PROMPT_COUNT + 50) + +/* score for various changes */ +#define SCORE_SPLIT 99 /* split bad word */ +#define SCORE_ICASE 52 /* slightly different case */ +#define SCORE_ALLCAP 120 /* need all-cap case */ +#define SCORE_REGION 70 /* word is for different region */ +#define SCORE_RARE 180 /* rare word */ + +/* score for edit distance */ +#define SCORE_SWAP 90 /* swap two characters */ +#define SCORE_SWAP3 110 /* swap two characters in three */ +#define SCORE_REP 87 /* REP replacement */ +#define SCORE_SUBST 93 /* substitute a character */ +#define SCORE_SIMILAR 33 /* substitute a similar character */ +#define SCORE_DEL 96 /* delete a character */ +#define SCORE_INS 94 /* insert a character */ + +#define SCORE_MAXINIT 350 /* Initial maximum score: higher == slower. + * 350 allows for about three changes. */ +#define SCORE_MAXMAX 999999 /* accept any score */ + +/* * Structure to store info for word matching. */ typedef struct matchinf_S { langp_T *mi_lp; /* info for language and region */ - slang_T *mi_slang; /* info for the language */ /* pointers to original text to be checked */ char_u *mi_word; /* start of word being checked */ @@ -248,23 +353,56 @@ static int set_spell_finish __ARGS((spelltab_T *new_st)); # define SPELL_ISWORDP(p) (spelltab.st_isw[*(p)]) #endif +/* + * Struct to keep the state at each level in spell_try_change(). + */ +typedef struct trystate_S +{ + int ts_state; /* state at this level, STATE_ */ + int ts_score; /* score */ + int ts_curi; /* index in list of child nodes */ + int ts_fidx; /* index in fword[], case-folded bad word */ + int ts_fidxtry; /* ts_fidx at which bytes may be changed */ + int ts_twordlen; /* valid length of tword[] */ + int ts_arridx; /* index in tree array, start of node */ + char_u ts_save_prewordlen; /* saved "prewordlen" */ + int ts_save_splitoff; /* su_splitoff saved here */ + int ts_save_badflags; /* badflags saved here */ +} trystate_T; + static slang_T *slang_alloc __ARGS((char_u *lang)); static void slang_free __ARGS((slang_T *lp)); static void slang_clear __ARGS((slang_T *lp)); static void find_word __ARGS((matchinf_T *mip, int keepcap)); +static int spell_valid_case __ARGS((int origflags, int treeflags)); static void spell_load_lang __ARGS((char_u *lang)); static char_u *spell_enc __ARGS((void)); static void spell_load_cb __ARGS((char_u *fname, void *cookie)); -static void spell_load_file __ARGS((char_u *fname, char_u *lang, slang_T *old_lp)); +static slang_T *spell_load_file __ARGS((char_u *fname, char_u *lang, slang_T *old_lp, int silent)); static int read_tree __ARGS((FILE *fd, char_u *byts, int *idxs, int maxidx, int startidx)); static int find_region __ARGS((char_u *rp, char_u *region)); static int captype __ARGS((char_u *word, char_u *end)); -static void spell_reload_one __ARGS((char_u *fname)); +static void spell_reload_one __ARGS((char_u *fname, int added_word)); static int set_spell_charflags __ARGS((char_u *flags, int cnt, char_u *upp)); static int set_spell_chartab __ARGS((char_u *fol, char_u *low, char_u *upp)); static void write_spell_chartab __ARGS((FILE *fd)); static int spell_isupper __ARGS((int c)); static int spell_casefold __ARGS((char_u *p, int len, char_u *buf, int buflen)); +static void onecap_copy __ARGS((char_u *word, int len, char_u *wcopy, int upper)); +static void spell_try_change __ARGS((suginfo_T *su)); +static int try_deeper __ARGS((suginfo_T *su, trystate_T *stack, int depth, int score_add)); +static void find_keepcap_word __ARGS((slang_T *slang, char_u *fword, char_u *kword)); +static void spell_try_soundalike __ARGS((suginfo_T *su)); +static void make_case_word __ARGS((char_u *fword, char_u *cword, int flags)); +static int similar_chars __ARGS((slang_T *slang, int c1, int c2)); +static void add_suggestion __ARGS((suginfo_T *su, char_u *goodword, int use_score)); +static void add_banned __ARGS((suginfo_T *su, char_u *word)); +static int was_banned __ARGS((suginfo_T *su, char_u *word)); +static void free_banned __ARGS((suginfo_T *su)); +static void cleanup_suggestions __ARGS((suginfo_T *su)); +static void spell_soundfold __ARGS((slang_T *slang, char_u *inword, char_u *res)); +static int spell_edit_score __ARGS((char_u *badword, char_u *goodword)); + static char *e_format = N_("E759: Format error in spell file"); @@ -274,6 +412,10 @@ static char *e_format = N_("E759: Format error in spell file"); * "*attrp" is set to the attributes for a badly spelled word. For a non-word * or when it's OK it remains unchanged. * This must only be called when 'spelllang' is not empty. + * + * "sug" is normally NULL. When looking for suggestions it points to + * suginfo_T. It's passed as a void pointer to keep the struct local. + * * Returns the length of the word in bytes, also when it's OK, so that the * caller can skip over the word. */ @@ -305,6 +447,7 @@ spell_check(wp, ptr, attrp) /* Find the end of the word. */ mi.mi_word = ptr; mi.mi_fend = ptr; + if (SPELL_ISWORDP(mi.mi_fend)) { /* Make case-folded copy of the characters until the next non-word @@ -313,18 +456,15 @@ spell_check(wp, ptr, attrp) { mb_ptr_adv(mi.mi_fend); } while (*mi.mi_fend != NUL && SPELL_ISWORDP(mi.mi_fend)); - - /* Check the caps type of the word. */ - mi.mi_capflags = captype(ptr, mi.mi_fend); } - else - /* No word characters, caps type is always zero. */ - mi.mi_capflags = 0; /* We always use the characters up to the next non-word character, * also for bad words. */ mi.mi_end = mi.mi_fend; - mi.mi_cend = mi.mi_fend; + + /* Check caps type later. */ + mi.mi_capflags = 0; + mi.mi_cend = NULL; /* Include one non-word character so that we can check for the * word end. */ @@ -349,7 +489,6 @@ spell_check(wp, ptr, attrp) /* Check for a matching word in case-folded words. */ find_word(&mi, FALSE); - /* Try keep-case words. */ find_word(&mi, TRUE); } @@ -563,17 +702,13 @@ find_word(mip, keepcap) /* Check that the word is in the required case. */ if (mip->mi_cend != mip->mi_word + wlen) { - /* mi_capflags was set for a different word - * length, need to do it again. */ + /* mi_capflags was set for a different word length, need + * to do it again. */ mip->mi_cend = mip->mi_word + wlen; - mip->mi_capflags = captype(mip->mi_word, - mip->mi_cend); + mip->mi_capflags = captype(mip->mi_word, mip->mi_cend); } - valid = (mip->mi_capflags == WF_ALLCAP - || ((flags & WF_ALLCAP) == 0 - && ((flags & WF_ONECAP) == 0 - || mip->mi_capflags == WF_ONECAP))); + valid = spell_valid_case(mip->mi_capflags, flags); } if (valid) @@ -617,15 +752,31 @@ find_word(mip, keepcap) } } +/* + * Check case flags for a word. Return TRUE if the word has the requested + * case. + */ + static int +spell_valid_case(origflags, treeflags) + int origflags; /* flags for the checked word. */ + int treeflags; /* flags for the word in the spell tree */ +{ + return (origflags == WF_ALLCAP + || ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0 + && ((treeflags & WF_ONECAP) == 0 || origflags == WF_ONECAP))); +} + /* * Move to next spell error. + * "curline" is TRUE for "z?": find word under/after cursor in the same line. * Return OK if found, FAIL otherwise. */ int -spell_move_to(dir, allwords) +spell_move_to(dir, allwords, curline) int dir; /* FORWARD or BACKWARD */ int allwords; /* TRUE for "[s" and "]s" */ + int curline; { linenr_T lnum; pos_T found_pos; @@ -680,7 +831,8 @@ spell_move_to(dir, allwords) if (dir == BACKWARD || lnum > curwin->w_cursor.lnum || (lnum == curwin->w_cursor.lnum - && (colnr_T)(p - line) + && (colnr_T)(curline ? p - line + len + : p - line) > curwin->w_cursor.col)) { if (has_syntax) @@ -722,6 +874,9 @@ spell_move_to(dir, allwords) break; } + if (curline) + return FAIL; /* only check cursor line */ + /* Advance to next line. */ if (dir == BACKWARD) { @@ -819,7 +974,8 @@ slang_alloc(lang) if (lp != NULL) { lp->sl_name = vim_strsave(lang); - ga_init2(&lp->sl_rep, sizeof(repentry_T), 4); + ga_init2(&lp->sl_rep, sizeof(fromto_T), 10); + ga_init2(&lp->sl_sal, sizeof(fromto_T), 10); } return lp; } @@ -844,6 +1000,10 @@ slang_free(lp) slang_clear(lp) slang_T *lp; { + garray_T *gap; + fromto_T *ftp; + int round; + vim_free(lp->sl_fbyts); lp->sl_fbyts = NULL; vim_free(lp->sl_kbyts); @@ -852,9 +1012,21 @@ slang_clear(lp) lp->sl_fidxs = NULL; vim_free(lp->sl_kidxs); lp->sl_kidxs = NULL; - ga_clear(&lp->sl_rep); - vim_free(lp->sl_try); - lp->sl_try = NULL; + + for (round = 1; round <= 2; ++round) + { + gap = round == 1 ? &lp->sl_rep : &lp->sl_sal; + while (gap->ga_len > 0) + { + ftp = &((fromto_T *)gap->ga_data)[--gap->ga_len]; + vim_free(ftp->ft_from); + vim_free(ftp->ft_to); + } + ga_clear(gap); + } + + vim_free(lp->sl_map); + lp->sl_map = NULL; } /* @@ -866,7 +1038,7 @@ spell_load_cb(fname, cookie) char_u *fname; void *cookie; /* points to the language name */ { - spell_load_file(fname, (char_u *)cookie, NULL); + (void)spell_load_file(fname, (char_u *)cookie, NULL, FALSE); } /* @@ -877,12 +1049,14 @@ spell_load_cb(fname, cookie) * the language name, "old_lp" is NULL. Will allocate an slang_T. * - To reload a spell file that was changed. "lang" is NULL and "old_lp" * points to the existing slang_T. + * Returns the slang_T the spell file was loaded into. NULL for error. */ - static void -spell_load_file(fname, lang, old_lp) + static slang_T * +spell_load_file(fname, lang, old_lp, silent) char_u *fname; char_u *lang; slang_T *old_lp; + int silent; /* no error if file doesn't exist */ { FILE *fd; char_u buf[MAXWLEN + 1]; @@ -895,11 +1069,22 @@ spell_load_file(fname, lang, old_lp) int cnt, ccnt; char_u *fol; slang_T *lp = NULL; + garray_T *gap; + fromto_T *ftp; + int rr; + short *first; fd = mch_fopen((char *)fname, "r"); if (fd == NULL) { - EMSG2(_(e_notopen), fname); + if (!silent) + EMSG2(_(e_notopen), fname); + else if (p_verbose > 2) + { + verbose_enter(); + smsg((char_u *)e_notopen, fname); + verbose_leave(); + } goto endFAIL; } if (p_verbose > 2) @@ -1000,12 +1185,88 @@ formerr: goto formerr; } - /* <SUGGEST> : <suggestlen> <more> ... */ - /* TODO, just skip this for now */ - i = (getc(fd) << 24) + (getc(fd) << 16) + (getc(fd) << 8) + getc(fd); - while (i-- > 0) - if (getc(fd) == EOF) /* <suggestlen> */ - goto truncerr; + /* <SUGGEST> : <repcount> <rep> ... + * <salflags> <salcount> <sal> ... + * <maplen> <mapstr> */ + for (round = 1; round <= 2; ++round) + { + if (round == 1) + { + gap = &lp->sl_rep; + first = lp->sl_rep_first; + } + else + { + gap = &lp->sl_sal; + first = lp->sl_sal_first; + + i = getc(fd); /* <salflags> */ + if (i & SAL_F0LLOWUP) + lp->sl_followup = TRUE; + if (i & SAL_COLLAPSE) + lp->sl_collapse = TRUE; + if (i & SAL_REM_ACCENTS) + lp->sl_rem_accents = TRUE; + } + + cnt = (getc(fd) << 8) + getc(fd); /* <repcount> or <salcount> */ + if (cnt < 0) + goto formerr; + + if (ga_grow(gap, cnt) == FAIL) + goto endFAIL; + for (; gap->ga_len < cnt; ++gap->ga_len) + { + /* <rep> : <repfromlen> <repfrom> <reptolen> <repto> */ + /* <sal> : <salfromlen> <salfrom> <saltolen> <salto> */ + ftp = &((fromto_T *)gap->ga_data)[gap->ga_len]; + for (rr = 1; rr <= 2; ++rr) + { + ccnt = getc(fd); + if (ccnt < 0) + { + if (rr == 2) + vim_free(ftp->ft_from); + goto formerr; + } + if ((p = alloc(ccnt + 1)) == NULL) + { + if (rr == 2) + vim_free(ftp->ft_from); + goto endFAIL; + } + for (i = 0; i < ccnt; ++i) + p[i] = getc(fd); /* <repfrom> or <salfrom> */ + p[i] = NUL; + if (rr == 1) + ftp->ft_from = p; + else + ftp->ft_to = p; + } + } + + /* Fill the first-index table. */ + for (i = 0; i < 256; ++i) + first[i] = -1; + for (i = 0; i < gap->ga_len; ++i) + { + ftp = &((fromto_T *)gap->ga_data)[i]; + if (first[*ftp->ft_from] == -1) + first[*ftp->ft_from] = i; + } + } + + cnt = (getc(fd) << 8) + getc(fd); /* <maplen> */ + if (cnt < 0) + goto formerr; + p = alloc(cnt + 1); + if (p == NULL) + goto endFAIL; + for (i = 0; i < cnt; ++i) + p[i] = getc(fd); /* <mapstr> */ + p[i] = NUL; + lp->sl_map = p; + /* round 1: <LWORDTREE> * round 2: <KWORDTREE> */ @@ -1063,13 +1324,18 @@ endFAIL: /* truncating the name signals the error to spell_load_lang() */ *lang = NUL; if (lp != NULL && old_lp == NULL) + { slang_free(lp); + lp = NULL; + } endOK: if (fd != NULL) fclose(fd); sourcing_name = save_sourcing_name; sourcing_lnum = save_sourcing_lnum; + + return lp; } /* @@ -1177,9 +1443,18 @@ did_set_spelllang(buf) slang_T *lp; int c; char_u lbuf[MAXWLEN + 1]; + char_u spf_name[MAXPATHL]; + int did_spf = FALSE; ga_init2(&ga, sizeof(langp_T), 2); + /* Get the name of the .spl file associated with 'spellfile'. */ + if (*buf->b_p_spf == NUL) + did_spf = TRUE; + else + vim_snprintf((char *)spf_name, sizeof(spf_name), "%s.spl", + buf->b_p_spf); + /* loop over comma separated languages. */ for (lang = buf->b_p_spl; *lang != NUL; lang = e) { @@ -1206,8 +1481,7 @@ did_set_spelllang(buf) if (lp == NULL) { /* Not found, load the language. */ - STRNCPY(lbuf, lang, e - lang); - lbuf[e - lang] = NUL; + vim_strncpy(lbuf, lang, e - lang); if (region != NULL) mch_memmove(lbuf + 2, lbuf + 5, e - lang - 4); spell_load_lang(lbuf); @@ -1247,12 +1521,38 @@ did_set_spelllang(buf) LANGP_ENTRY(ga, ga.ga_len)->lp_slang = lp; LANGP_ENTRY(ga, ga.ga_len)->lp_region = region_mask; ++ga.ga_len; + + /* Check if this is the 'spellfile' spell file. */ + if (fullpathcmp(spf_name, lp->sl_fname, FALSE) == FPC_SAME) + did_spf = TRUE; } if (*e == ',') ++e; } + /* + * Make sure the 'spellfile' file is loaded. It may be in 'runtimepath', + * then it's probably loaded above already. Otherwise load it here. + */ + if (!did_spf) + { + for (lp = first_lang; lp != NULL; lp = lp->sl_next) + if (fullpathcmp(spf_name, lp->sl_fname, FALSE) == FPC_SAME) + break; + if (lp == NULL) + { + vim_strncpy(lbuf, gettail(spf_name), 2); + lp = spell_load_file(spf_name, lbuf, NULL, TRUE); + } + if (lp != NULL && ga_grow(&ga, 1) == OK) + { + LANGP_ENTRY(ga, ga.ga_len)->lp_slang = lp; + LANGP_ENTRY(ga, ga.ga_len)->lp_region = REGION_ALL; + ++ga.ga_len; + } + } + /* Add a NULL entry to mark the end of the list. */ if (ga_grow(&ga, 1) == FAIL) { @@ -1292,7 +1592,7 @@ find_region(rp, region) } /* - * Return type of word: + * Return case type of word: * w word 0 * Word WF_ONECAP * W WORD WF_ALLCAP @@ -1301,7 +1601,7 @@ find_region(rp, region) static int captype(word, end) char_u *word; - char_u *end; + char_u *end; /* When NULL use up to NUL byte. */ { char_u *p; int c; @@ -1311,7 +1611,7 @@ captype(word, end) /* find first letter */ for (p = word; !SPELL_ISWORDP(p); mb_ptr_adv(p)) - if (p >= end) + if (end == NULL ? *p == NUL : p >= end) return 0; /* only non-word characters, illegal word */ #ifdef FEAT_MBYTE if (has_mbyte) @@ -1325,7 +1625,7 @@ captype(word, end) * Need to check all letters to find a word with mixed upper/lower. * But a word with an upper char only at start is a ONECAP. */ - for ( ; p < end; mb_ptr_adv(p)) + for ( ; end == NULL ? *p != NUL : p < end; mb_ptr_adv(p)) if (SPELL_ISWORDP(p)) { #ifdef FEAT_MBYTE @@ -1402,18 +1702,26 @@ spell_reload() * Reload the spell file "fname" if it's loaded. */ static void -spell_reload_one(fname) +spell_reload_one(fname, added_word) char_u *fname; + int added_word; /* invoked through "zg" */ { slang_T *lp; + int didit = FALSE; for (lp = first_lang; lp != NULL; lp = lp->sl_next) if (fullpathcmp(fname, lp->sl_fname, FALSE) == FPC_SAME) { slang_clear(lp); - spell_load_file(fname, NULL, lp); + (void)spell_load_file(fname, NULL, lp, FALSE); redraw_all_later(NOT_VALID); + didit = TRUE; } + + /* When "zg" was used and the file wasn't loaded yet, should redo + * 'spelllang' to get it loaded. */ + if (added_word && !didit) + did_set_spelllang(curbuf); } @@ -1429,12 +1737,10 @@ spell_reload_one(fname) typedef struct afffile_S { char_u *af_enc; /* "SET", normalized, alloc'ed string or NULL */ - char_u *af_try; /* "TRY" line in "af_enc" encoding */ int af_rar; /* RAR ID for rare word */ int af_kep; /* KEP ID for keep-case word */ hashtab_T af_pref; /* hashtable for prefixes, affheader_T */ hashtab_T af_suff; /* hashtable for suffixes, affheader_T */ - garray_T af_rep; /* list of repentry_T entries from REP lines */ } afffile_T; typedef struct affentry_S affentry_T; @@ -1510,9 +1816,18 @@ typedef struct spellinfo_S int si_region_count; /* number of regions supported (1 when there are no regions) */ char_u si_region_name[16]; /* region names (if count > 1) */ + + garray_T si_rep; /* list of fromto_T entries from REP lines */ + garray_T si_sal; /* list of fromto_T entries from SAL lines */ + int si_followup; /* soundsalike: ? */ + int si_collapse; /* soundsalike: ? */ + int si_rem_accents; /* soundsalike: remove accents */ + garray_T si_map; /* MAP info concatenated */ } spellinfo_T; static afffile_T *spell_read_aff __ARGS((char_u *fname, spellinfo_T *spin)); +static void add_fromto __ARGS((spellinfo_T *spin, garray_T *gap, char_u *from, char_u *to)); +static int sal_to_bool __ARGS((char_u *s)); static int has_non_ascii __ARGS((char_u *s)); static void spell_free_aff __ARGS((afffile_T *aff)); static int spell_read_dic __ARGS((char_u *fname, spellinfo_T *spin, afffile_T *affile)); @@ -1529,11 +1844,11 @@ static int node_compress __ARGS((wordnode_T *node, hashtab_T *ht, int *tot)); static int node_equal __ARGS((wordnode_T *n1, wordnode_T *n2)); static void write_vim_spell __ARGS((char_u *fname, spellinfo_T *spin)); static int put_tree __ARGS((FILE *fd, wordnode_T *node, int index, int regionmask)); -static void mkspell __ARGS((int fcount, char_u **fnames, int ascii, int overwrite, int verbose)); +static void mkspell __ARGS((int fcount, char_u **fnames, int ascii, int overwrite, int added_word)); static void init_spellfile __ARGS((void)); /* - * Read an affix ".aff" file. + * Read the affix file "fname". * Returns an afffile_T, NULL for complete failure. */ static afffile_T * @@ -1557,6 +1872,10 @@ spell_read_aff(fname, spin) char_u *fol = NULL; char_u *upp = NULL; static char *e_affname = N_("Affix name too long in %s line %d: %s"); + int do_rep; + int do_sal; + int do_map; + int found_map = FALSE; /* * Open the file. @@ -1578,6 +1897,15 @@ spell_read_aff(fname, spin) verbose_leave(); } + /* Only do REP lines when not done in another .aff file already. */ + do_rep = spin->si_rep.ga_len == 0; + + /* Only do SAL lines when not done in another .aff file already. */ + do_sal = spin->si_sal.ga_len == 0; + + /* Only do MAP lines when not done in another .aff file already. */ + do_map = spin->si_map.ga_len == 0; + /* * Allocate and init the afffile_T structure. */ @@ -1586,7 +1914,6 @@ spell_read_aff(fname, spin) return NULL; hash_init(&aff->af_pref); hash_init(&aff->af_suff); - ga_init2(&aff->af_rep, (int)sizeof(repentry_T), 20); /* * Read all the lines in the file one by one. @@ -1660,12 +1987,11 @@ spell_read_aff(fname, spin) } else if (STRCMP(items[0], "NOSPLITSUGS") == 0 && itemcnt == 1) { - /* ignored */ + /* ignored, we always split */ } - else if (STRCMP(items[0], "TRY") == 0 && itemcnt == 2 - && aff->af_try == NULL) + else if (STRCMP(items[0], "TRY") == 0 && itemcnt == 2) { - aff->af_try = getroom_save(&spin->si_blocks, items[1]); + /* ignored, we look in the tree for what chars may appear */ } else if (STRCMP(items[0], "RAR") == 0 && itemcnt == 2 && aff->af_rar == 0) @@ -1784,18 +2110,55 @@ spell_read_aff(fname, spin) upp = vim_strsave(items[1]); } else if (STRCMP(items[0], "REP") == 0 && itemcnt == 2) + { /* Ignore REP count */; + if (!isdigit(*items[1])) + smsg((char_u *)_("Expected REP count in %s line %d"), + fname, lnum); + } else if (STRCMP(items[0], "REP") == 0 && itemcnt == 3) { - repentry_T *rp; - /* REP item */ - if (ga_grow(&aff->af_rep, 1) == FAIL) - break; - rp = ((repentry_T *)aff->af_rep.ga_data) + aff->af_rep.ga_len; - rp->re_from = getroom_save(&spin->si_blocks, items[1]); - rp->re_to = getroom_save(&spin->si_blocks, items[2]); - ++aff->af_rep.ga_len; + if (do_rep) + add_fromto(spin, &spin->si_rep, items[1], items[2]); + } + else if (STRCMP(items[0], "MAP") == 0 && itemcnt == 2) + { + /* MAP item or count */ + if (!found_map) + { + /* First line contains the count. */ + found_map = TRUE; + if (!isdigit(*items[1])) + smsg((char_u *)_("Expected MAP count in %s line %d"), + fname, lnum); + } + else if (do_map) + { + /* We simply concatenate all the MAP strings, separated by + * slashes. */ + ga_concat(&spin->si_map, items[1]); + ga_append(&spin->si_map, '/'); + } + } + else if (STRCMP(items[0], "SAL") == 0 && itemcnt == 3) + { + if (do_sal) + { + /* SAL item (sounds-a-like) + * Either one of the known keys or a from-to pair. */ + if (STRCMP(items[1], "followup") == 0) + spin->si_followup = sal_to_bool(items[2]); + else if (STRCMP(items[1], "collapse_result") == 0) + spin->si_collapse = sal_to_bool(items[2]); + else if (STRCMP(items[1], "remove_accents") == 0) + spin->si_rem_accents = sal_to_bool(items[2]); + else + /* when "to" is "_" it means empty */ + add_fromto(spin, &spin->si_sal, items[1], + STRCMP(items[2], "_") == 0 ? (char_u *)"" + : items[2]); + } } else smsg((char_u *)_("Unrecognized item in %s line %d: %s"), @@ -1834,6 +2197,41 @@ spell_read_aff(fname, spin) } /* + * Add a from-to item to "gap". Used for REP and SAL items. + * They are stored case-folded. + */ + static void +add_fromto(spin, gap, from, to) + spellinfo_T *spin; + garray_T *gap; + char_u *from; + char_u *to; +{ + fromto_T *ftp; + char_u word[MAXWLEN]; + + if (ga_grow(gap, 1) == OK) + { + ftp = ((fromto_T *)gap->ga_data) + gap->ga_len; + (void)spell_casefold(from, STRLEN(from), word, MAXWLEN); + ftp->ft_from = getroom_save(&spin->si_blocks, word); + (void)spell_casefold(to, STRLEN(to), word, MAXWLEN); + ftp->ft_to = getroom_save(&spin->si_blocks, word); + ++gap->ga_len; + } +} + +/* + * Convert a boolean argument in a SAL line to TRUE or FALSE; + */ + static int +sal_to_bool(s) + char_u *s; +{ + return STRCMP(s, "1") == 0 || STRCMP(s, "true") == 0; +} + +/* * Return TRUE if string "s" contains a non-ASCII character (128 or higher). * When "s" is NULL FALSE is returned. */ @@ -1885,7 +2283,6 @@ spell_free_aff(aff) hash_clear(&aff->af_pref); hash_clear(&aff->af_suff); - ga_clear(&aff->af_rep); } /* @@ -2490,15 +2887,9 @@ store_word(word, spin, flags, region) char_u foldword[MAXWLEN]; int res; - if (flags & WF_KEEPCAP) - res = OK; /* keep-case specified, don't add as fold-case */ - else - { - (void)spell_casefold(word, len, foldword, MAXWLEN); - res = tree_add_word(foldword, spin->si_foldroot, - (ct == WF_KEEPCAP ? WF_ALLCAP : ct) | flags, - region, &spin->si_blocks); - } + (void)spell_casefold(word, len, foldword, MAXWLEN); + res = tree_add_word(foldword, spin->si_foldroot, ct | flags, + region, &spin->si_blocks); if (res == OK && (ct == WF_KEEPCAP || flags & WF_KEEPCAP)) res = tree_add_word(word, spin->si_keeproot, flags, @@ -2731,6 +3122,29 @@ put_bytes(fd, nr, len) putc((int)(nr >> (i * 8)), fd); } +static int +#ifdef __BORLANDC__ +_RTLENTRYF +#endif +rep_compare __ARGS((const void *s1, const void *s2)); + +/* + * Function given to qsort() to sort the REP items on "from" string. + */ + static int +#ifdef __BORLANDC__ +_RTLENTRYF +#endif +rep_compare(s1, s2) + const void *s1; + const void *s2; +{ + fromto_T *p1 = (fromto_T *)s1; + fromto_T *p2 = (fromto_T *)s2; + + return STRCMP(p1->ft_from, p2->ft_from); +} + /* * Write the Vim spell file "fname". */ @@ -2744,6 +3158,12 @@ write_vim_spell(fname, spin) int round; wordnode_T *tree; int nodecount; + int i; + int l; + garray_T *gap; + fromto_T *ftp; + char_u *p; + int rr; fd = mch_fopen((char *)fname, "w"); if (fd == NULL) @@ -2773,11 +3193,15 @@ write_vim_spell(fname, spin) regionmask = 0; } - /* Write the table with character flags and table for case folding. + /* + * Write the table with character flags and table for case folding. * <charflagslen> <charflags> <fcharlen> <fchars> * Skip this for ASCII, the table may conflict with the one used for - * 'encoding'. */ - if (spin->si_ascii) + * 'encoding'. + * Also skip this for an .add.spl file, the main spell file must contain + * the table (avoids that it conflicts). File is shorter too. + */ + if (spin->si_ascii || spin->si_add) { putc(0, fd); putc(0, fd); @@ -2786,16 +3210,56 @@ write_vim_spell(fname, spin) else write_spell_chartab(fd); + /* Sort the REP items. */ + qsort(spin->si_rep.ga_data, (size_t)spin->si_rep.ga_len, + sizeof(fromto_T), rep_compare); - /* <SUGGEST> : <suggestlen> <more> ... - * TODO. Only write a zero length for now. */ - put_bytes(fd, 0L, 4); /* <suggestlen> */ + /* <SUGGEST> : <repcount> <rep> ... + * <salflags> <salcount> <sal> ... + * <maplen> <mapstr> */ + for (round = 1; round <= 2; ++round) + { + if (round == 1) + gap = &spin->si_rep; + else + { + gap = &spin->si_sal; + + i = 0; + if (spin->si_followup) + i |= SAL_F0LLOWUP; + if (spin->si_collapse) + i |= SAL_COLLAPSE; + if (spin->si_rem_accents) + i |= SAL_REM_ACCENTS; + putc(i, fd); /* <salflags> */ + } - spin->si_memtot = 0; + put_bytes(fd, (long_u)gap->ga_len, 2); /* <repcount> or <salcount> */ + for (i = 0; i < gap->ga_len; ++i) + { + /* <rep> : <repfromlen> <repfrom> <reptolen> <repto> */ + /* <sal> : <salfromlen> <salfrom> <saltolen> <salto> */ + ftp = &((fromto_T *)gap->ga_data)[i]; + for (rr = 1; rr <= 2; ++rr) + { + p = rr == 1 ? ftp->ft_from : ftp->ft_to; + l = STRLEN(p); + putc(l, fd); + fwrite(p, l, (size_t)1, fd); + } + } + } + + put_bytes(fd, (long_u)spin->si_map.ga_len, 2); /* <maplen> */ + if (spin->si_map.ga_len > 0) /* <mapstr> */ + fwrite(spin->si_map.ga_data, (size_t)spin->si_map.ga_len, + (size_t)1, fd); /* * <LWORDTREE> <KWORDTREE> */ + spin->si_memtot = 0; for (round = 1; round <= 2; ++round) { tree = (round == 1) ? spin->si_foldroot : spin->si_keeproot; @@ -2941,7 +3405,7 @@ ex_mkspell(eap) /* Expand all the remaining arguments (e.g., $VIMRUNTIME). */ if (get_arglist_exp(arg, &fcount, &fnames) == OK) { - mkspell(fcount, fnames, ascii, eap->forceit, TRUE); + mkspell(fcount, fnames, ascii, eap->forceit, FALSE); FreeWild(fcount, fnames); } } @@ -2954,12 +3418,12 @@ ex_mkspell(eap) * and ".spl" is appended to make the output file name. */ static void -mkspell(fcount, fnames, ascii, overwrite, verbose) +mkspell(fcount, fnames, ascii, overwrite, added_word) int fcount; char_u **fnames; int ascii; /* -ascii argument given */ int overwrite; /* overwrite existing output file */ - int verbose; /* give progress messages */ + int added_word; /* invoked through "zg" */ { char_u fname[MAXPATHL]; char_u wfname[MAXPATHL]; @@ -2973,8 +3437,13 @@ mkspell(fcount, fnames, ascii, overwrite, verbose) spellinfo_T spin; vim_memset(&spin, 0, sizeof(spin)); - spin.si_verbose = verbose; + spin.si_verbose = !added_word; spin.si_ascii = ascii; + spin.si_followup = TRUE; + spin.si_rem_accents = TRUE; + ga_init2(&spin.si_rep, (int)sizeof(fromto_T), 20); + ga_init2(&spin.si_sal, (int)sizeof(fromto_T), 20); + ga_init2(&spin.si_map, (int)sizeof(char_u), 100); /* default: fnames[0] is output file, following are input files */ innames = &fnames[1]; @@ -2994,8 +3463,7 @@ mkspell(fcount, fnames, ascii, overwrite, verbose) else if (len > 4 && STRCMP(fnames[0] + len - 4, ".spl") == 0) { /* Name ends in ".spl", use as the file name. */ - STRNCPY(wfname, fnames[0], sizeof(wfname)); - wfname[sizeof(wfname) - 1] = NUL; + vim_strncpy(wfname, fnames[0], sizeof(wfname) - 1); } else /* Name should be language, make the file name from it. */ @@ -3119,13 +3587,13 @@ mkspell(fcount, fnames, ascii, overwrite, verbose) /* * Combine tails in the tree. */ - if (verbose || p_verbose > 2) + if (!added_word || p_verbose > 2) { - if (!verbose) + if (added_word) verbose_enter(); MSG(_("Compressing word tree...")); out_flush(); - if (!verbose) + if (added_word) verbose_leave(); } wordtree_compress(spin.si_foldroot, &spin); @@ -3137,36 +3605,39 @@ mkspell(fcount, fnames, ascii, overwrite, verbose) /* * Write the info in the spell file. */ - if (verbose || p_verbose > 2) + if (!added_word || p_verbose > 2) { - if (!verbose) + if (added_word) verbose_enter(); smsg((char_u *)_("Writing spell file %s..."), wfname); out_flush(); - if (!verbose) + if (added_word) verbose_leave(); } write_vim_spell(wfname, &spin); - if (verbose || p_verbose > 2) + if (!added_word || p_verbose > 2) { - if (!verbose) + if (added_word) verbose_enter(); MSG(_("Done!")); smsg((char_u *)_("Estimated runtime memory use: %d bytes"), spin.si_memtot); out_flush(); - if (!verbose) + if (added_word) verbose_leave(); } /* If the file is loaded need to reload it. */ - spell_reload_one(wfname); + spell_reload_one(wfname, added_word); } /* Free the allocated memory. */ free_blocks(spin.si_blocks); + ga_clear(&spin.si_rep); + ga_clear(&spin.si_sal); + ga_clear(&spin.si_map); /* Free the .aff file structures. */ for (i = 0; i < incount; ++i) @@ -3202,7 +3673,7 @@ spell_add_word(word, len, bad) if (*curbuf->b_p_spf == NUL) init_spellfile(); if (*curbuf->b_p_spf == NUL) - EMSG(_("E999: 'spellfile' is not set")); + EMSG(_("E764: 'spellfile' is not set")); else { /* Check that the user isn't editing the .add file somewhere. */ @@ -3225,11 +3696,13 @@ spell_add_word(word, len, bad) fclose(fd); /* Update the .add.spl file. */ - mkspell(1, &curbuf->b_p_spf, FALSE, TRUE, FALSE); + mkspell(1, &curbuf->b_p_spf, FALSE, TRUE, TRUE); /* If the .add file is edited somewhere, reload it. */ if (buf != NULL) buf_reload(buf); + + redraw_all_later(NOT_VALID); } } } @@ -3615,5 +4088,1591 @@ spell_casefold(p, len, buf, buflen) return OK; } +/* + * "z?": Find badly spelled word under or after the cursor. + * Give suggestions for the properly spelled word. + * This is based on the mechanisms of Aspell, but completely reimplemented. + */ + void +spell_suggest() +{ + char_u *line; + pos_T prev_cursor = curwin->w_cursor; + int attr; + char_u wcopy[MAXWLEN + 2]; + char_u *p; + int i; + int c; + suginfo_T sug; + suggest_T *stp; + + /* + * Find the start of the badly spelled word. + */ + if (spell_move_to(FORWARD, TRUE, TRUE) == FAIL) + { + beep_flush(); + return; + } + + /* + * Set the info in "sug". + */ + vim_memset(&sug, 0, sizeof(sug)); + ga_init2(&sug.su_ga, (int)sizeof(suggest_T), 10); + hash_init(&sug.su_banned); + line = ml_get_curline(); + sug.su_badptr = line + curwin->w_cursor.col; + sug.su_badlen = spell_check(curwin, sug.su_badptr, &attr); + if (sug.su_badlen >= MAXWLEN) + sug.su_badlen = MAXWLEN - 1; /* just in case */ + vim_strncpy(sug.su_badword, sug.su_badptr, sug.su_badlen); + (void)spell_casefold(sug.su_badptr, sug.su_badlen, + sug.su_fbadword, MAXWLEN); + + /* Ban the bad word itself. It may appear in another region. */ + add_banned(&sug, sug.su_badword); + + /* + * 1. Try inserting/deleting/swapping/changing a letter, use REP entries + * from the .aff file and inserting a space (split the word). + */ + /* Set a maximum score to limit the combination of operations that is + * tried. */ + sug.su_maxscore = SCORE_MAXINIT; + spell_try_change(&sug); + cleanup_suggestions(&sug); + + /* + * 2. Try finding sound-a-like words. + */ + /* Allow a higher score if we don't have many suggestions yet. */ + if (sug.su_maxscore == SCORE_MAXINIT) + sug.su_maxscore = SCORE_MAXMAX; + spell_try_soundalike(&sug); + + /* When CTRL-C was hit while searching do show the results. */ + if (got_int) + { + (void)vgetc(); + got_int = FALSE; + } + + if (sug.su_ga.ga_len == 0) + MSG(_("Sorry, no suggestions")); + else + { + /* Cleanup, sort the suggestions and truncate at SUG_PROMPT_COUNT. */ + cleanup_suggestions(&sug); + + /* List the suggestions. */ + msg_start(); + vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), + sug.su_badlen, sug.su_badptr); + msg_puts(IObuff); + msg_clr_eos(); + msg_putchar('\n'); + msg_scroll = TRUE; + for (i = 0; i < sug.su_ga.ga_len; ++i) + { + stp = &SUG(&sug, i); + + /* The suggested word may replace only part of the bad word, add + * the not replaced part. */ + STRCPY(wcopy, stp->st_word); + if (sug.su_badlen > stp->st_orglen) + vim_strncpy(wcopy + STRLEN(wcopy), + sug.su_badptr + stp->st_orglen, + sug.su_badlen - stp->st_orglen); + /* TODO: remove score */ + vim_snprintf((char *)IObuff, IOSIZE, _("%2d \"%s\" (%d)"), + i + 1, wcopy, stp->st_score); + msg_puts(IObuff); + lines_left = 3; /* avoid more prompt */ + msg_putchar('\n'); + } + + /* Ask for choice. */ + i = prompt_for_number(); + if (i > 0 && i <= sug.su_ga.ga_len && u_save_cursor()) + { + /* Replace the word. */ + stp = &SUG(&sug, i - 1); + p = alloc(STRLEN(line) - stp->st_orglen + STRLEN(stp->st_word) + 1); + if (p != NULL) + { + c = sug.su_badptr - line; + mch_memmove(p, line, c); + STRCPY(p + c, stp->st_word); + STRCAT(p, sug.su_badptr + stp->st_orglen); + ml_replace(curwin->w_cursor.lnum, p, FALSE); + curwin->w_cursor.col = c; + changed_bytes(curwin->w_cursor.lnum, c); + } + } + else + curwin->w_cursor = prev_cursor; + } + + /* Free the suggestions. */ + for (i = 0; i < sug.su_ga.ga_len; ++i) + vim_free(SUG(&sug, i).st_word); + ga_clear(&sug.su_ga); + + /* Free the banned words. */ + free_banned(&sug); +} + +/* + * Make a copy of "word[len]", with the first letter upper or lower cased, + * to "wcopy[MAXWLEN]". + */ + static void +onecap_copy(word, len, wcopy, upper) + char_u *word; + int len; + char_u *wcopy; + int upper; /* TRUE: first letter made upper case */ +{ + char_u *p; + int c; + int l; + + p = word; +#ifdef FEAT_MBYTE + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else +#endif + c = *p++; + if (upper) + c = MB_TOUPPER(c); + else + c = MB_TOLOWER(c); +#ifdef FEAT_MBYTE + if (has_mbyte) + l = mb_char2bytes(c, wcopy); + else +#endif + { + l = 1; + wcopy[0] = c; + } + vim_strncpy(wcopy + l, p, len - (p - word)); +} + +/* + * Make a copy of "word[len]" with all the letters upper cased into + * "wcopy[MAXWLEN]". + */ + static void +allcap_copy(word, wcopy) + char_u *word; + char_u *wcopy; +{ + char_u *s; + char_u *d; + int c; + + d = wcopy; + for (s = word; *s != NUL; ) + { +#ifdef FEAT_MBYTE + if (has_mbyte) + c = mb_ptr2char_adv(&s); + else +#endif + c = *s++; + + c = MB_TOUPPER(c); /* TODO: use spell toupper */ + +#ifdef FEAT_MBYTE + if (has_mbyte) + { + if (d - wcopy >= MAXWLEN - MB_MAXBYTES) + break; + d += mb_char2bytes(c, d); + } + else +#endif + { + if (d - wcopy >= MAXWLEN - 1) + break; + *d++ = c; + } + } + *d = NUL; +} + +/* + * Try finding suggestions by adding/removing/swapping letters. + */ + static void +spell_try_change(su) + suginfo_T *su; +{ + char_u fword[MAXWLEN]; /* copy of the bad word, case-folded */ + char_u tword[MAXWLEN]; /* good word collected so far */ + trystate_T stack[MAXWLEN]; + char_u preword[MAXWLEN * 3]; /* word found with proper case (appended + * to for word split) */ + char_u prewordlen = 0; /* length of word in "preword" */ + int splitoff = 0; /* index in tword after last split */ + trystate_T *sp; + int newscore; + langp_T *lp; + char_u *byts; + int *idxs; + int depth; + int c; + int n; + int flags; + int badflags; + garray_T *gap; + int arridx; + int len; + char_u *p; + fromto_T *ftp; + int fl, tl; + + /* get caps flags for bad word */ + badflags = captype(su->su_badptr, su->su_badptr + su->su_badlen); + + /* We make a copy of the case-folded bad word, so that we can modify it + * to find matches (esp. REP items). */ + STRCPY(fword, su->su_fbadword); + + /* + * At each node in the tree these states are tried: + */ +#define STATE_START 0 /* At start of node, check if word may end or + * split word. */ +#define STATE_SPLITUNDO 1 /* Undo word split. */ +#define STATE_ENDNUL 2 /* Past NUL bytes at start of the node. */ +#define STATE_PLAIN 3 /* Use each byte of the node. */ +#define STATE_DEL 4 /* Delete a byte from the bad word. */ +#define STATE_INS 5 /* Insert a byte in the bad word. */ +#define STATE_SWAP 6 /* Swap two bytes. */ +#define STATE_SWAP3A 7 /* Swap two bytes over three. */ +#define STATE_ROT3L 8 /* Rotate three bytes left */ +#define STATE_ROT3R 9 /* Rotate three bytes right */ +#define STATE_ROT_UNDO 10 /* undo rotating */ +#define STATE_REP_INI 11 /* Prepare for using REP items. */ +#define STATE_REP 12 /* Use matching REP items from the .aff file. */ +#define STATE_REP_UNDO 13 /* Undo a REP item replacement. */ +#define STATE_FINAL 99 /* End of this node. */ + + + for (lp = LANGP_ENTRY(curwin->w_buffer->b_langp, 0); + lp->lp_slang != NULL; ++lp) + { +#ifdef SOUNDFOLD_SCORE + su->su_slang = lp->lp_slang; + if (lp->lp_slang->sl_sal.ga_len > 0) + /* soundfold the bad word */ + spell_soundfold(lp->lp_slang, su->su_fbadword, su->su_salword); +#endif + + /* + * Go through the whole case-fold tree, try changes at each node. + * "tword[]" contains the word collected from nodes in the tree. + * "fword[]" the word we are trying to match with (initially the bad + * word). + */ + byts = lp->lp_slang->sl_fbyts; + idxs = lp->lp_slang->sl_fidxs; + + depth = 0; + stack[0].ts_state = STATE_START; + stack[0].ts_score = 0; + stack[0].ts_curi = 1; + stack[0].ts_fidx = 0; + stack[0].ts_fidxtry = 0; + stack[0].ts_twordlen = 0; + stack[0].ts_arridx = 0; + + while (depth >= 0 && !got_int) + { + sp = &stack[depth]; + switch (sp->ts_state) + { + case STATE_START: + /* + * Start of node: Deal with NUL bytes, which means + * tword[] may end here. + */ + arridx = sp->ts_arridx; /* current node in the tree */ + len = byts[arridx]; /* bytes in this node */ + arridx += sp->ts_curi; /* index of current byte */ + + if (sp->ts_curi > len || (c = byts[arridx]) != 0) + { + /* Past bytes in node and/or past NUL bytes. */ + sp->ts_state = STATE_ENDNUL; + break; + } + + /* + * End of word in tree. + */ + ++sp->ts_curi; /* eat one NUL byte */ + + flags = idxs[arridx]; + + /* + * Form the word with proper case in preword. + * If there is a word from a previous split, append. + */ + tword[sp->ts_twordlen] = NUL; + if (flags & WF_KEEPCAP) + /* Must find the word in the keep-case tree. */ + find_keepcap_word(lp->lp_slang, tword + splitoff, + preword + prewordlen); + else + /* Include badflags: if the badword is onecap or allcap + * use that for the goodword too. */ + make_case_word(tword + splitoff, + preword + prewordlen, flags | badflags); + + /* Don't use a banned word. It may appear again as a good + * word, thus remember it. */ + if (flags & WF_BANNED) + { + add_banned(su, preword + prewordlen); + break; + } + if (was_banned(su, preword + prewordlen)) + break; + + newscore = 0; + if ((flags & WF_REGION) + && (((unsigned)flags >> 8) & lp->lp_region) == 0) + newscore += SCORE_REGION; + if (flags & WF_RARE) + newscore += SCORE_RARE; + + if (!spell_valid_case(badflags, + captype(preword + prewordlen, NULL))) + newscore += SCORE_ICASE; + + if (fword[sp->ts_fidx] == 0) + { + /* The badword also ends: add suggestions, */ + add_suggestion(su, preword, sp->ts_score + newscore); + } + else if (sp->ts_fidx >= sp->ts_fidxtry) + { + /* The word in the tree ends but the badword + * continues: try inserting a space and check that a valid + * words starts at fword[sp->ts_fidx]. */ + if (try_deeper(su, stack, depth, newscore + SCORE_SPLIT)) + { + /* Save things to be restored at STATE_SPLITUNDO. */ + sp->ts_save_prewordlen = prewordlen; + sp->ts_save_badflags = badflags; + sp->ts_save_splitoff = splitoff; + + /* Append a space to preword. */ + STRCAT(preword, " "); + prewordlen = STRLEN(preword); + splitoff = sp->ts_twordlen; + /* TODO: when case-folding changed the number of bytes + * this doesn't work... */ + badflags = captype(su->su_badptr + sp->ts_fidx, + su->su_badptr + su->su_badlen); + + sp->ts_state = STATE_SPLITUNDO; + ++depth; + /* Restart at top of the tree. */ + stack[depth].ts_arridx = 0; + } + } + break; + + case STATE_SPLITUNDO: + /* Fixup the changes done for word split. */ + badflags = sp->ts_save_badflags; + splitoff = sp->ts_save_splitoff; + prewordlen = sp->ts_save_prewordlen; + + /* Continue looking for NUL bytes. */ + sp->ts_state = STATE_START; + break; + + case STATE_ENDNUL: + /* Past the NUL bytes in the node. */ + if (fword[sp->ts_fidx] == 0) + { + /* The badword ends, can't use the bytes in this node. */ + sp->ts_state = STATE_DEL; + break; + } + sp->ts_state = STATE_PLAIN; + /*FALLTHROUGH*/ + + case STATE_PLAIN: + /* + * Go over all possible bytes at this node, add each to + * tword[] and use child node. "ts_curi" is the index. + */ + arridx = sp->ts_arridx; + if (sp->ts_curi > byts[arridx]) + { + /* Done all bytes at this node, do next state. When still + * at already changed bytes skip the other tricks. */ + if (sp->ts_fidx >= sp->ts_fidxtry) + sp->ts_state = STATE_DEL; + else + sp->ts_state = STATE_FINAL; + } + else + { + arridx += sp->ts_curi++; + c = byts[arridx]; + + /* Normal byte, go one level deeper. If it's not equal to + * the byte in the bad word adjust the score. But don't + * even try when the byte was already changed. */ + if (c == fword[sp->ts_fidx]) + newscore = 0; + /* TODO: multi-byte characters */ + else if (lp->lp_slang->sl_map != NULL + && similar_chars(lp->lp_slang, + c, fword[sp->ts_fidx])) + newscore = SCORE_SIMILAR; + else + newscore = SCORE_SUBST; + if ((newscore == 0 || sp->ts_fidx >= sp->ts_fidxtry) + && try_deeper(su, stack, depth, newscore)) + { + ++depth; + ++stack[depth].ts_fidx; + tword[stack[depth].ts_twordlen++] = c; + stack[depth].ts_arridx = idxs[arridx]; + } + } + break; + + case STATE_DEL: + /* Try skipping one byte in the bad word (delete it). */ + sp->ts_state = STATE_INS; + sp->ts_curi = 1; + if (fword[sp->ts_fidx] != NUL + && try_deeper(su, stack, depth, SCORE_DEL)) + { + ++depth; + ++stack[depth].ts_fidx; + break; + } + /*FALLTHROUGH*/ + + case STATE_INS: + /* Insert one byte. Do this for each possible bytes at this + * node. */ + n = sp->ts_arridx; + if (sp->ts_curi > byts[n]) + { + /* Done all bytes at this node, do next state. */ + sp->ts_state = STATE_SWAP; + sp->ts_curi = 1; + } + else + { + /* Do one more byte at this node. */ + n += sp->ts_curi++; + c = byts[n]; + if (c != 0 && try_deeper(su, stack, depth, SCORE_INS)) + { + ++depth; + tword[stack[depth].ts_twordlen++] = c; + stack[depth].ts_arridx = idxs[n]; + } + } + break; + + case STATE_SWAP: + /* Swap two bytes: "12" -> "21". This means looking for the + * following byte at the current node and the current byte at + * its child node. We change "fword" here, it's changed back + * afterwards. TODO: should swap characters instead of bytes. + * */ + c = fword[sp->ts_fidx]; + if (c != NUL && fword[sp->ts_fidx + 1] != NUL + && try_deeper(su, stack, depth, SCORE_SWAP)) + { + sp->ts_state = STATE_SWAP3A; + ++depth; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = c; + stack[depth].ts_fidxtry = sp->ts_fidx + 2; + } + else + /* If this swap doesn't work then SWAP3 won't either. */ + sp->ts_state = STATE_REP_INI; + break; + + case STATE_SWAP3A: + /* First undo the STATE_SWAP swap: "21" -> "12". */ + c = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = c; + + /* Swap two bytes, skipping one: "123" -> "321". We change + * "fword" here, it's changed back afterwards. TODO: should + * swap characters instead of bytes. */ + c = fword[sp->ts_fidx]; + if (c != NUL && fword[sp->ts_fidx + 1] != NUL + && fword[sp->ts_fidx + 2] != NUL + && try_deeper(su, stack, depth, SCORE_SWAP3)) + { + sp->ts_state = STATE_ROT3L; + ++depth; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = c; + stack[depth].ts_fidxtry = sp->ts_fidx + 3; + } + else + sp->ts_state = STATE_REP_INI; + break; + + case STATE_ROT3L: + /* First undo STATE_SWAP3A: "321" -> "123" */ + c = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = c; + + /* Rotate three bytes left: "123" -> "231". We change + * "fword" here, it's changed back afterwards. TODO: should + * swap characters instead of bytes. */ + if (try_deeper(su, stack, depth, SCORE_SWAP3)) + { + sp->ts_state = STATE_ROT3R; + ++depth; + c = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = c; + stack[depth].ts_fidxtry = sp->ts_fidx + 3; + } + else + sp->ts_state = STATE_REP_INI; + break; + + case STATE_ROT3R: + /* First undo STATE_ROT3L: "231" -> "123" */ + c = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = c; + + /* Rotate three bytes right: "123" -> "312". We change + * "fword" here, it's changed back afterwards. TODO: should + * swap characters instead of bytes. */ + if (try_deeper(su, stack, depth, SCORE_SWAP3)) + { + sp->ts_state = STATE_ROT_UNDO; + ++depth; + c = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = c; + stack[depth].ts_fidxtry = sp->ts_fidx + 3; + } + else + sp->ts_state = STATE_REP_INI; + break; + + case STATE_ROT_UNDO: + /* Undo STATE_ROT3R: "312" -> "123" */ + c = fword[sp->ts_fidx]; + fword[sp->ts_fidx] = fword[sp->ts_fidx + 1]; + fword[sp->ts_fidx + 1] = fword[sp->ts_fidx + 2]; + fword[sp->ts_fidx + 2] = c; + /*FALLTHROUGH*/ + + case STATE_REP_INI: + /* Check if matching with REP items from the .aff file would + * work. Quickly skip if there are no REP items or the score + * is going to be too high anyway. */ + gap = &lp->lp_slang->sl_rep; + if (gap->ga_len == 0 + || sp->ts_score + SCORE_REP >= su->su_maxscore) + { + sp->ts_state = STATE_FINAL; + break; + } + + /* Use the first byte to quickly find the first entry that + * matches. If the index is -1 there is none. */ + sp->ts_curi = lp->lp_slang->sl_rep_first[fword[sp->ts_fidx]]; + if (sp->ts_curi < 0) + { + sp->ts_state = STATE_FINAL; + break; + } + + sp->ts_state = STATE_REP; + /*FALLTHROUGH*/ + + case STATE_REP: + /* Try matching with REP items from the .aff file. For each + * match replace the charactes and check if the resulting word + * is valid. */ + p = fword + sp->ts_fidx; + + gap = &lp->lp_slang->sl_rep; + while (sp->ts_curi < gap->ga_len) + { + ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; + if (*ftp->ft_from != *p) + { + /* past possible matching entries */ + sp->ts_curi = gap->ga_len; + break; + } + if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 + && try_deeper(su, stack, depth, SCORE_REP)) + { + /* Need to undo this afterwards. */ + sp->ts_state = STATE_REP_UNDO; + + /* Change the "from" to the "to" string. */ + ++depth; + fl = STRLEN(ftp->ft_from); + tl = STRLEN(ftp->ft_to); + if (fl != tl) + mch_memmove(p + tl, p + fl, STRLEN(p + fl) + 1); + mch_memmove(p, ftp->ft_to, tl); + stack[depth].ts_fidxtry = sp->ts_fidx + tl; + break; + } + } + + if (sp->ts_curi >= gap->ga_len) + /* No (more) matches. */ + sp->ts_state = STATE_FINAL; + + break; + + case STATE_REP_UNDO: + /* Undo a REP replacement and continue with the next one. */ + ftp = (fromto_T *)lp->lp_slang->sl_rep.ga_data + + sp->ts_curi - 1; + fl = STRLEN(ftp->ft_from); + tl = STRLEN(ftp->ft_to); + p = fword + sp->ts_fidx; + if (fl != tl) + mch_memmove(p + fl, p + tl, STRLEN(p + tl) + 1); + mch_memmove(p, ftp->ft_from, fl); + sp->ts_state = STATE_REP; + break; + + default: + /* Did all possible states at this level, go up one level. */ + --depth; + } + + line_breakcheck(); + } + } +} + +/* + * Try going one level deeper in the tree. + */ + static int +try_deeper(su, stack, depth, score_add) + suginfo_T *su; + trystate_T *stack; + int depth; + int score_add; +{ + int newscore; + + /* Refuse to go deeper if the scrore is getting too big. */ + newscore = stack[depth].ts_score + score_add; + if (newscore >= su->su_maxscore) + return FALSE; + + stack[depth + 1].ts_state = STATE_START; + stack[depth + 1].ts_score = newscore; + stack[depth + 1].ts_curi = 1; /* start just after length byte */ + stack[depth + 1].ts_fidx = stack[depth].ts_fidx; + stack[depth + 1].ts_fidxtry = stack[depth].ts_fidxtry; + stack[depth + 1].ts_twordlen = stack[depth].ts_twordlen; + stack[depth + 1].ts_arridx = stack[depth].ts_arridx; + return TRUE; +} + +/* + * "fword" is a good word with case folded. Find the matching keep-case + * words and put it in "kword". + * Theoretically there could be several keep-case words that result in the + * same case-folded word, but we only find one... + */ + static void +find_keepcap_word(slang, fword, kword) + slang_T *slang; + char_u *fword; + char_u *kword; +{ + char_u uword[MAXWLEN]; /* "fword" in upper-case */ + int depth; + int tryidx; + + /* The following arrays are used at each depth in the tree. */ + int arridx[MAXWLEN]; + int round[MAXWLEN]; + int fwordidx[MAXWLEN]; + int uwordidx[MAXWLEN]; + int kwordlen[MAXWLEN]; + + int flen, ulen; + int l; + int len; + int c; + unsigned lo, hi, m; + char_u *p; + char_u *byts = slang->sl_kbyts; /* array with bytes of the words */ + int *idxs = slang->sl_kidxs; /* array with indexes */ + + if (byts == NULL) + { + /* array is empty: "cannot happen" */ + *kword = NUL; + return; + } + + /* Make an all-cap version of "fword". */ + allcap_copy(fword, uword); + + /* + * Each character needs to be tried both case-folded and upper-case. + * All this gets very complicated if we keep in mind that changing case + * may change the byte length of a multi-byte character... + */ + depth = 0; + arridx[0] = 0; + round[0] = 0; + fwordidx[0] = 0; + uwordidx[0] = 0; + kwordlen[0] = 0; + while (depth >= 0) + { + if (fword[fwordidx[depth]] == NUL) + { + /* We are at the end of "fword". If the tree allows a word to end + * here we have found a match. */ + if (byts[arridx[depth] + 1] == 0) + { + kword[kwordlen[depth]] = NUL; + return; + } + + /* kword is getting too long, continue one level up */ + --depth; + } + else if (++round[depth] > 2) + { + /* tried both fold-case and upper-case character, continue one + * level up */ + --depth; + } + else + { + /* + * round[depth] == 1: Try using the folded-case character. + * round[depth] == 2: Try using the upper-case character. + */ +#ifdef FEAT_MBYTE + if (has_mbyte) + { + flen = mb_ptr2len_check(fword + fwordidx[depth]); + ulen = mb_ptr2len_check(uword + uwordidx[depth]); + } + else +#endif + ulen = flen = 1; + if (round[depth] == 1) + { + p = fword + fwordidx[depth]; + l = flen; + } + else + { + p = uword + uwordidx[depth]; + l = ulen; + } + + for (tryidx = arridx[depth]; l > 0; --l) + { + /* Perform a binary search in the list of accepted bytes. */ + len = byts[tryidx++]; + c = *p++; + lo = tryidx; + hi = tryidx + len - 1; + while (lo < hi) + { + m = (lo + hi) / 2; + if (byts[m] > c) + hi = m - 1; + else if (byts[m] < c) + lo = m + 1; + else + { + lo = hi = m; + break; + } + } + + /* Stop if there is no matching byte. */ + if (hi < lo || byts[lo] != c) + break; + + /* Continue at the child (if there is one). */ + tryidx = idxs[lo]; + } + + if (l == 0) + { + /* + * Found the matching char. Copy it to "kword" and go a + * level deeper. + */ + if (round[depth] == 1) + { + STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], + flen); + kwordlen[depth + 1] = kwordlen[depth] + flen; + } + else + { + STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], + ulen); + kwordlen[depth + 1] = kwordlen[depth] + ulen; + } + fwordidx[depth + 1] = fwordidx[depth] + flen; + uwordidx[depth + 1] = uwordidx[depth] + ulen; + + ++depth; + arridx[depth] = tryidx; + round[depth] = 0; + } + } + } + + /* Didn't find it: "cannot happen". */ + *kword = NUL; +} + +/* + * Find suggestions by comparing the word in a sound-a-like form. + */ + static void +spell_try_soundalike(su) + suginfo_T *su; +{ + char_u salword[MAXWLEN]; + char_u tword[MAXWLEN]; + char_u tfword[MAXWLEN]; + char_u tsalword[MAXWLEN]; + int arridx[MAXWLEN]; + int curi[MAXWLEN]; + langp_T *lp; + char_u *byts; + int *idxs; + int depth; + int c; + int n; + int round; + int flags; + + for (lp = LANGP_ENTRY(curwin->w_buffer->b_langp, 0); + lp->lp_slang != NULL; ++lp) + { + if (lp->lp_slang->sl_sal.ga_len > 0) + { + /* soundfold the bad word */ + spell_soundfold(lp->lp_slang, su->su_fbadword, salword); + + /* + * Go through the whole tree, soundfold each word and compare. + * round 1: use the case-folded tree. + * round 2: use the keep-case tree. + */ + for (round = 1; round <= 2; ++round) + { + if (round == 1) + { + byts = lp->lp_slang->sl_fbyts; + idxs = lp->lp_slang->sl_fidxs; + } + else + { + byts = lp->lp_slang->sl_kbyts; + idxs = lp->lp_slang->sl_kidxs; + } + + depth = 0; + arridx[0] = 0; + curi[0] = 1; + while (depth >= 0 && !got_int) + { + if (curi[depth] > byts[arridx[depth]]) + /* Done all bytes at this node, go up one level. */ + --depth; + else + { + /* Do one more byte at this node. */ + n = arridx[depth] + curi[depth]; + ++curi[depth]; + c = byts[n]; + if (c == 0) + { + /* End of word, deal with the word. */ + flags = idxs[n]; + if (round == 2 || (flags & WF_KEEPCAP) == 0) + { + tword[depth] = NUL; + if (round == 1) + spell_soundfold(lp->lp_slang, + tword, tsalword); + else + { + /* In keep-case tree need to case-fold the + * word. */ + (void)spell_casefold(tword, depth, + tfword, MAXWLEN); + spell_soundfold(lp->lp_slang, + tfword, tsalword); + } + + /* TODO: also compare with small changes + * (insert char, swap char, etc.) */ + if (STRCMP(salword, tsalword) == 0) + { + if (round == 1 && flags != 0) + { + char_u cword[MAXWLEN]; + + make_case_word(tword, cword, flags); + add_suggestion(su, cword, 0); + } + else + add_suggestion(su, tword, 0); + } + } + + /* Skip over other NUL bytes. */ + while (byts[n + 1] == 0) + { + ++n; + ++curi[depth]; + } + } + else + { + /* Normal char, go one level deeper. */ + tword[depth++] = c; + arridx[depth] = idxs[n]; + curi[depth] = 1; + } + } + } + line_breakcheck(); + } + } + } +} + +/* + * Copy "fword" to "cword", fixing according to "flags". + */ + static void +make_case_word(fword, cword, flags) + char_u *fword; + char_u *cword; + int flags; +{ + if (flags & WF_ALLCAP) + /* Make it all upper-case */ + allcap_copy(fword, cword); + else if (flags & WF_ONECAP) + /* Make the first letter upper-case */ + onecap_copy(fword, STRLEN(fword), cword, TRUE); + else + /* Use goodword as-is. */ + STRCPY(cword, fword); +} + +/* + * Return TRUE if "c1" and "c2" are similar characters according to the MAP + * lines in the .aff file. + */ + static int +similar_chars(slang, c1, c2) + slang_T *slang; + int c1; + int c2; +{ + char_u *p1; + char_u *p2; + + /* The similar characters are stored separated with slashes: + * "aaa/bbb/ccc/". Search for each character and if the next slash is the + * same one they are in the same MAP entry. */ + p1 = vim_strchr(slang->sl_map, c1); + if (p1 == NULL) + return FALSE; + p2 = vim_strchr(slang->sl_map, c2); + if (p2 == NULL) + return FALSE; + return vim_strchr(p1, '/') == vim_strchr(p2, '/'); +} + +/* + * Add a suggestion to the list of suggestions. + * Do not add a duplicate suggestion or suggestions with a bad score. + * When "use_score" is not zero it's used, otherwise the score is computed + * with spell_edit_score(). + */ + static void +add_suggestion(su, goodword, use_score) + suginfo_T *su; + char_u *goodword; + int use_score; +{ + suggest_T *stp; + int score; + int i; +#ifdef SOUNDFOLD_SCORE + char_u fword[MAXWLEN]; + char_u salword[MAXWLEN]; +#endif + + /* Check that the word wasn't banned. */ + if (was_banned(su, goodword)) + return; + + /* Compute the score and add the suggestion if it's good enough. */ + if (use_score != 0) + score = use_score; + else + score = spell_edit_score(su->su_badword, goodword); + + if (score <= su->su_maxscore) + { +#ifdef SOUNDFOLD_SCORE + /* Add to the score when the word sounds differently. + * This is slow... */ + if (su->su_slang->sl_sal.ga_len > 0) + { + (void)spell_casefold(goodword, STRLEN(goodword), fword, MAXWLEN); + spell_soundfold(su->su_slang, fword, salword); + score += spell_edit_score(su->su_salword, salword); + } +#endif + + /* Check if the word is already there. */ + stp = &SUG(su, 0); + for (i = su->su_ga.ga_len - 1; i >= 0; --i) + if (STRCMP(stp[i].st_word, goodword) == 0) + { + /* Found it. Remember the lowest score. */ + if (stp[i].st_score > score) + stp[i].st_score = score; + break; + } + + if (i < 0 && ga_grow(&su->su_ga, 1) == OK) + { + /* Add a suggestion. */ + stp = &SUG(su, su->su_ga.ga_len); + stp->st_word = vim_strsave(goodword); + if (stp->st_word != NULL) + { + stp->st_score = score; + stp->st_orglen = su->su_badlen; + ++su->su_ga.ga_len; + + /* If we have too many suggestions now, sort the list and keep + * the best suggestions. */ + if (su->su_ga.ga_len > SUG_CLEANUP_COUNT) + cleanup_suggestions(su); + } + } + } +} + +/* + * Add a word to be banned. + */ + static void +add_banned(su, word) + suginfo_T *su; + char_u *word; +{ + char_u *s = vim_strsave(word); + hash_T hash; + hashitem_T *hi; + + if (s != NULL) + { + hash = hash_hash(s); + hi = hash_lookup(&su->su_banned, s, hash); + if (HASHITEM_EMPTY(hi)) + hash_add_item(&su->su_banned, hi, s, hash); + } +} + +/* + * Return TRUE if a word appears in the list of banned words. + */ + static int +was_banned(su, word) + suginfo_T *su; + char_u *word; +{ + return !HASHITEM_EMPTY(hash_find(&su->su_banned, word)); +} + +/* + * Free the banned words in "su". + */ + static void +free_banned(su) + suginfo_T *su; +{ + int todo; + hashitem_T *hi; + + todo = su->su_banned.ht_used; + for (hi = su->su_banned.ht_array; todo > 0; ++hi) + { + if (!HASHITEM_EMPTY(hi)) + { + vim_free(hi->hi_key); + --todo; + } + } + hash_clear(&su->su_banned); +} + +static int +#ifdef __BORLANDC__ +_RTLENTRYF +#endif +sug_compare __ARGS((const void *s1, const void *s2)); + +/* + * Function given to qsort() to sort the suggestions on st_score. + */ + static int +#ifdef __BORLANDC__ +_RTLENTRYF +#endif +sug_compare(s1, s2) + const void *s1; + const void *s2; +{ + suggest_T *p1 = (suggest_T *)s1; + suggest_T *p2 = (suggest_T *)s2; + + return p1->st_score - p2->st_score; +} + +/* + * Cleanup the suggestions: + * - Sort on score. + * - Remove words that won't be displayed. + */ + static void +cleanup_suggestions(su) + suginfo_T *su; +{ + suggest_T *stp = &SUG(su, 0); + int i; + + /* Sort the list. */ + qsort(su->su_ga.ga_data, (size_t)su->su_ga.ga_len, + sizeof(suggest_T), sug_compare); + + /* Truncate the list to the number of suggestions that will be displayed. */ + if (su->su_ga.ga_len > SUG_PROMPT_COUNT) + { + for (i = SUG_PROMPT_COUNT; i < su->su_ga.ga_len; ++i) + vim_free(stp[i].st_word); + su->su_ga.ga_len = SUG_PROMPT_COUNT; + su->su_maxscore = stp[SUG_PROMPT_COUNT - 1].st_score; + } +} + +/* + * Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". + */ + static void +spell_soundfold(slang, inword, res) + slang_T *slang; + char_u *inword; + char_u *res; +{ + fromto_T *ftp; + char_u word[MAXWLEN]; +#ifdef FEAT_MBYTE + int l; +#endif + char_u *s; + char_u *t; + int i, j, z; + int n, k = 0; + int z0; + int k0; + int n0; + int c; + int pri; + int p0 = -333; + int c0; + + /* Remove accents, if wanted. + * We actually remove all non-word characters. */ + if (slang->sl_rem_accents) + { + t = word; + for (s = inword; *s != NUL; ) + { +#ifdef FEAT_MBYTE + if (has_mbyte) + { + l = mb_ptr2len_check(s); + if (SPELL_ISWORDP(s)) + { + mch_memmove(t, s, l); + t += l; + } + s += l; + } + else +#endif + { + if (SPELL_ISWORDP(s)) + *t++ = *s; + ++s; + } + } + *t = NUL; + } + else + STRCPY(word, inword); + + ftp = (fromto_T *)slang->sl_sal.ga_data; + + /* + * This comes from Aspell phonet.cpp. Converted from C++ to C. + * TODO: support for multi-byte chars. + */ + i = j = z = 0; + while ((c = word[i]) != NUL) + { + n = slang->sl_sal_first[c]; + z0 = 0; + + if (n >= 0) + { + /* check all rules for the same letter */ + while (ftp[n].ft_from[0] == c) + { + /* check whole string */ + k = 1; /* number of found letters */ + pri = 5; /* default priority */ + s = ftp[n].ft_from; + s++; /* important for (see below) "*(s-1)" */ + + /* Skip over normal letters that match with the word. */ + while (*s != NUL && word[i + k] == *s + && !vim_isdigit(*s) && strchr("(-<^$", *s) == NULL) + { + k++; + s++; + } + + if (*s == '(') + { + /* check alternate letters in "(..)" */ + for (t = s + 1; *t != ')' && *t != NUL; ++t) + if (*t == word[i + k]) + { + /* match */ + ++k; + for (s = t + 1; *s != NUL; ++s) + if (*s == ')') + { + ++s; + break; + } + break; + } + } + + p0 = *s; + k0 = k; + while (*s == '-' && k > 1) + { + k--; + s++; + } + if (*s == '<') + s++; + if (vim_isdigit(*s)) + { + /* determine priority */ + pri = *s - '0'; + s++; + } + if (*s == '^' && *(s + 1) == '^') + s++; + + if (*s == NUL + || (*s == '^' + && (i == 0 || !SPELL_ISWORDP(word + i - 1)) + && (*(s + 1) != '$' + || (!SPELL_ISWORDP(word + i + k0)))) + || (*s == '$' && i > 0 + && SPELL_ISWORDP(word + i - 1) + && (!SPELL_ISWORDP(word + i + k0)))) + { + /* search for followup rules, if: */ + /* followup and k > 1 and NO '-' in searchstring */ + c0 = word[i + k - 1]; + n0 = slang->sl_sal_first[c0]; + + if (slang->sl_followup && k > 1 && n0 >= 0 + && p0 != '-' && word[i + k] != NUL) + { + /* test follow-up rule for "word[i + k]" */ + while (ftp[n0].ft_from[0] == c0) + { + + /* check whole string */ + k0 = k; + p0 = 5; + s = ftp[n0].ft_from; + s++; + while (*s != NUL && word[i+k0] == *s + && !vim_isdigit(*s) + && strchr("(-<^$",*s) == NULL) + { + k0++; + s++; + } + if (*s == '(') + { + /* check alternate letters in "(..)" */ + for (t = s + 1; *t != ')' && *t != NUL; ++t) + if (*t == word[i + k0]) + { + /* match */ + ++k0; + for (s = t + 1; *s != NUL; ++s) + if (*s == ')') + { + ++s; + break; + } + break; + } + } + while (*s == '-') + { + /* "k0" gets NOT reduced */ + /* because "if (k0 == k)" */ + s++; + } + if (*s == '<') + s++; + if (vim_isdigit(*s)) + { + p0 = *s - '0'; + s++; + } + + if (*s == NUL + /* *s == '^' cuts */ + || (*s == '$' + && !SPELL_ISWORDP(word + i + k0))) + { + if (k0 == k) + { + /* this is just a piece of the string */ + ++n0; + continue; + } + + if (p0 < pri) + { + /* priority too low */ + ++n0; + continue; + } + /* rule fits; stop search */ + break; + } + ++n0; + } + + if (p0 >= pri && ftp[n0].ft_from[0] == c0) + { + ++n; + continue; + } + } + + /* replace string */ + s = ftp[n].ft_to; + p0 = (ftp[n].ft_from[0] != NUL + && vim_strchr(ftp[n].ft_from + 1, + '<') != NULL) ? 1 : 0; + if (p0 == 1 && z == 0) + { + /* rule with '<' is used */ + if (j > 0 && *s != NUL + && (res[j - 1] == c || res[j - 1] == *s)) + j--; + z0 = 1; + z = 1; + k0 = 0; + while (*s != NUL && word[i+k0] != NUL) + { + word[i + k0] = *s; + k0++; + s++; + } + if (k > k0) + mch_memmove(word + i + k0, word + i + k, + STRLEN(word + i + k) + 1); + + /* new "actual letter" */ + c = word[i]; + } + else + { + /* no '<' rule used */ + i += k - 1; + z = 0; + while (*s != NUL && s[1] != NUL && j < MAXWLEN) + { + if (j == 0 || res[j - 1] != *s) + { + res[j] = *s; + j++; + } + s++; + } + /* new "actual letter" */ + c = *s; + if (ftp[n].ft_from[0] != NUL + && strstr((char *)ftp[n].ft_from + 1, + "^^") != NULL) + { + if (c != NUL) + { + res[j] = c; + j++; + } + mch_memmove(word, word + i + 1, + STRLEN(word + i + 1) + 1); + i = 0; + z0 = 1; + } + } + break; + } + ++n; + } + } + + if (z0 == 0) + { + if (k && !p0 && j < MAXWLEN && c != NUL + && (!slang->sl_collapse || j == 0 || res[j - 1] != c)) + { + /* condense only double letters */ + res[j] = c; + j++; + } + + i++; + z = 0; + k = 0; + } + } + + res[j] = NUL; +} + +/* + * Compute the "edit distance" to turn "badword" into "goodword". The less + * deletes/inserts/swaps are required the lower the score. + * The algorithm comes from Aspell editdist.cpp, edit_distance(). + * TODO: make this work with multi-byte chars. + */ + static int +spell_edit_score(badword, goodword) + char_u *badword; + char_u *goodword; +{ + int *cnt; + int badlen, goodlen; + int j, i; + int t; + int bc, gc; + + /* We use "cnt" as an array: CNT(badword_idx, goodword_idx). */ +#define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] + badlen = STRLEN(badword) + 1; + goodlen = STRLEN(goodword) + 1; + cnt = (int *)lalloc((long_u)(sizeof(int) * (badlen + 1) * (goodlen + 1)), + TRUE); + if (cnt == 0) + return 0; + + CNT(0, 0) = 0; + for (j = 1; j <= goodlen; ++j) + CNT(0, j) = CNT(0, j - 1) + SCORE_DEL; + + for (i = 1; i <= badlen; ++i) + { + CNT(i, 0) = CNT(i - 1, 0) + SCORE_INS; + for (j = 1; j <= goodlen; ++j) + { + bc = badword[i - 1]; + gc = goodword[j - 1]; + if (bc == gc) + CNT(i, j) = CNT(i - 1, j - 1); + else + { + /* Use a better score when there is only a case difference. */ + if (spelltab.st_fold[bc] == spelltab.st_fold[gc]) + CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); + else + CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); + + if (i > 1 && j > 1 && bc == goodword[j - 2] + && badword[i - 2] == gc) + { + t = SCORE_SWAP + CNT(i - 2, j - 2); + if (t < CNT(i, j)) + CNT(i, j) = t; + } + t = SCORE_DEL + CNT(i - 1, j); + if (t < CNT(i, j)) + CNT(i, j) = t; + t = SCORE_INS + CNT(i, j - 1); + if (t < CNT(i, j)) + CNT(i, j) = t; + } + } + } + return CNT(badlen - 1, goodlen - 1); +} #endif /* FEAT_SYN_HL */ @@ -740,17 +740,8 @@ do_tag(tag, type, count, forceit, verbose) { /* * Ask to select a tag from the list. - * When using ":silent" assume that <CR> was entered. */ - MSG_PUTS(_("Enter nr of choice (<CR> to abort): ")); - i = get_number(TRUE); - if (KeyTyped) /* don't call wait_return() now */ - { - msg_putchar('\n'); - cmdline_row = msg_row - 1; - need_wait_return = FALSE; - msg_didany = FALSE; - } + i = prompt_for_number(); if (i <= 0 || i > num_matches || got_int) { /* no valid choice: don't change anything */ @@ -1680,6 +1680,7 @@ read_from_input_buf(buf, maxlen) return (int)maxlen; } +/*ARGSUSED*/ void fill_input_buf(exit_on_error) int exit_on_error; diff --git a/src/version.h b/src/version.h index c97e07806..94ec0ea7c 100644 --- a/src/version.h +++ b/src/version.h @@ -36,5 +36,5 @@ #define VIM_VERSION_NODOT "vim70aa" #define VIM_VERSION_SHORT "7.0aa" #define VIM_VERSION_MEDIUM "7.0aa ALPHA" -#define VIM_VERSION_LONG "VIM - Vi IMproved 7.0aa ALPHA (2005 Jun 8)" -#define VIM_VERSION_LONG_DATE "VIM - Vi IMproved 7.0aa ALPHA (2005 Jun 8, compiled " +#define VIM_VERSION_LONG "VIM - Vi IMproved 7.0aa ALPHA (2005 Jun 13)" +#define VIM_VERSION_LONG_DATE "VIM - Vi IMproved 7.0aa ALPHA (2005 Jun 13, compiled " |