1 /* vi:set ts=8 sts=4 sw=4 noet: 2 * 3 * VIM - Vi IMproved by Bram Moolenaar 4 * 5 * Do ":help uganda" in Vim to read copying and usage conditions. 6 * Do ":help credits" in Vim to see a list of people who contributed. 7 * See README.txt for an overview of the Vim source code. 8 */ 9 10 /* 11 * spell.c: code for spell checking 12 * 13 * See spellfile.c for the Vim spell file format. 14 * 15 * The spell checking mechanism uses a tree (aka trie). Each node in the tree 16 * has a list of bytes that can appear (siblings). For each byte there is a 17 * pointer to the node with the byte that follows in the word (child). 18 * 19 * A NUL byte is used where the word may end. The bytes are sorted, so that 20 * binary searching can be used and the NUL bytes are at the start. The 21 * number of possible bytes is stored before the list of bytes. 22 * 23 * The tree uses two arrays: "byts" stores the characters, "idxs" stores 24 * either the next index or flags. The tree starts at index 0. For example, 25 * to lookup "vi" this sequence is followed: 26 * i = 0 27 * len = byts[i] 28 * n = where "v" appears in byts[i + 1] to byts[i + len] 29 * i = idxs[n] 30 * len = byts[i] 31 * n = where "i" appears in byts[i + 1] to byts[i + len] 32 * i = idxs[n] 33 * len = byts[i] 34 * find that byts[i + 1] is 0, idxs[i + 1] has flags for "vi". 35 * 36 * There are two word trees: one with case-folded words and one with words in 37 * original case. The second one is only used for keep-case words and is 38 * usually small. 39 * 40 * There is one additional tree for when not all prefixes are applied when 41 * generating the .spl file. This tree stores all the possible prefixes, as 42 * if they were words. At each word (prefix) end the prefix nr is stored, the 43 * following word must support this prefix nr. And the condition nr is 44 * stored, used to lookup the condition that the word must match with. 45 * 46 * Thanks to Olaf Seibert for providing an example implementation of this tree 47 * and the compression mechanism. 48 * LZ trie ideas: 49 * http://www.irb.hr/hr/home/ristov/papers/RistovLZtrieRevision1.pdf 50 * More papers: http://www-igm.univ-mlv.fr/~laporte/publi_en.html 51 * 52 * Matching involves checking the caps type: Onecap ALLCAP KeepCap. 53 * 54 * Why doesn't Vim use aspell/ispell/myspell/etc.? 55 * See ":help develop-spell". 56 */ 57 58 /* 59 * Use this to adjust the score after finding suggestions, based on the 60 * suggested word sounding like the bad word. This is much faster than doing 61 * it for every possible suggestion. 62 * Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" 63 * vs "ht") and goes down in the list. 64 * Used when 'spellsuggest' is set to "best". 65 */ 66 #define RESCORE(word_score, sound_score) ((3 * word_score + sound_score) / 4) 67 68 /* 69 * Do the opposite: based on a maximum end score and a known sound score, 70 * compute the maximum word score that can be used. 71 */ 72 #define MAXSCORE(word_score, sound_score) ((4 * word_score - sound_score) / 3) 73 74 #define IN_SPELL_C 75 #include "vim.h" 76 77 #if defined(FEAT_SPELL) || defined(PROTO) 78 79 #ifndef UNIX /* it's in os_unix.h for Unix */ 80 # include <time.h> /* for time_t */ 81 #endif 82 83 /* only used for su_badflags */ 84 #define WF_MIXCAP 0x20 /* mix of upper and lower case: macaRONI */ 85 86 #define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) 87 88 #define REGION_ALL 0xff /* word valid in all regions */ 89 90 #define VIMSUGMAGIC "VIMsug" /* string at start of Vim .sug file */ 91 #define VIMSUGMAGICL 6 92 #define VIMSUGVERSION 1 93 94 /* Result values. Lower number is accepted over higher one. */ 95 #define SP_BANNED -1 96 #define SP_OK 0 97 #define SP_RARE 1 98 #define SP_LOCAL 2 99 #define SP_BAD 3 100 101 typedef struct wordcount_S 102 { 103 short_u wc_count; /* nr of times word was seen */ 104 char_u wc_word[1]; /* word, actually longer */ 105 } wordcount_T; 106 107 #define WC_KEY_OFF offsetof(wordcount_T, wc_word) 108 #define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) 109 #define MAXWORDCOUNT 0xffff 110 111 /* 112 * Information used when looking for suggestions. 113 */ 114 typedef struct suginfo_S 115 { 116 garray_T su_ga; /* suggestions, contains "suggest_T" */ 117 int su_maxcount; /* max. number of suggestions displayed */ 118 int su_maxscore; /* maximum score for adding to su_ga */ 119 int su_sfmaxscore; /* idem, for when doing soundfold words */ 120 garray_T su_sga; /* like su_ga, sound-folded scoring */ 121 char_u *su_badptr; /* start of bad word in line */ 122 int su_badlen; /* length of detected bad word in line */ 123 int su_badflags; /* caps flags for bad word */ 124 char_u su_badword[MAXWLEN]; /* bad word truncated at su_badlen */ 125 char_u su_fbadword[MAXWLEN]; /* su_badword case-folded */ 126 char_u su_sal_badword[MAXWLEN]; /* su_badword soundfolded */ 127 hashtab_T su_banned; /* table with banned words */ 128 slang_T *su_sallang; /* default language for sound folding */ 129 } suginfo_T; 130 131 /* One word suggestion. Used in "si_ga". */ 132 typedef struct suggest_S 133 { 134 char_u *st_word; /* suggested word, allocated string */ 135 int st_wordlen; /* STRLEN(st_word) */ 136 int st_orglen; /* length of replaced text */ 137 int st_score; /* lower is better */ 138 int st_altscore; /* used when st_score compares equal */ 139 int st_salscore; /* st_score is for soundalike */ 140 int st_had_bonus; /* bonus already included in score */ 141 slang_T *st_slang; /* language used for sound folding */ 142 } suggest_T; 143 144 #define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) 145 146 /* TRUE if a word appears in the list of banned words. */ 147 #define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&su->su_banned, word))) 148 149 /* Number of suggestions kept when cleaning up. We need to keep more than 150 * what is displayed, because when rescore_suggestions() is called the score 151 * may change and wrong suggestions may be removed later. */ 152 #define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < 130 ? 150 : (su)->su_maxcount + 20) 153 154 /* Threshold for sorting and cleaning up suggestions. Don't want to keep lots 155 * of suggestions that are not going to be displayed. */ 156 #define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) 157 158 /* score for various changes */ 159 #define SCORE_SPLIT 149 /* split bad word */ 160 #define SCORE_SPLIT_NO 249 /* split bad word with NOSPLITSUGS */ 161 #define SCORE_ICASE 52 /* slightly different case */ 162 #define SCORE_REGION 200 /* word is for different region */ 163 #define SCORE_RARE 180 /* rare word */ 164 #define SCORE_SWAP 75 /* swap two characters */ 165 #define SCORE_SWAP3 110 /* swap two characters in three */ 166 #define SCORE_REP 65 /* REP replacement */ 167 #define SCORE_SUBST 93 /* substitute a character */ 168 #define SCORE_SIMILAR 33 /* substitute a similar character */ 169 #define SCORE_SUBCOMP 33 /* substitute a composing character */ 170 #define SCORE_DEL 94 /* delete a character */ 171 #define SCORE_DELDUP 66 /* delete a duplicated character */ 172 #define SCORE_DELCOMP 28 /* delete a composing character */ 173 #define SCORE_INS 96 /* insert a character */ 174 #define SCORE_INSDUP 67 /* insert a duplicate character */ 175 #define SCORE_INSCOMP 30 /* insert a composing character */ 176 #define SCORE_NONWORD 103 /* change non-word to word char */ 177 178 #define SCORE_FILE 30 /* suggestion from a file */ 179 #define SCORE_MAXINIT 350 /* Initial maximum score: higher == slower. 180 * 350 allows for about three changes. */ 181 182 #define SCORE_COMMON1 30 /* subtracted for words seen before */ 183 #define SCORE_COMMON2 40 /* subtracted for words often seen */ 184 #define SCORE_COMMON3 50 /* subtracted for words very often seen */ 185 #define SCORE_THRES2 10 /* word count threshold for COMMON2 */ 186 #define SCORE_THRES3 100 /* word count threshold for COMMON3 */ 187 188 /* When trying changed soundfold words it becomes slow when trying more than 189 * two changes. With less then two changes it's slightly faster but we miss a 190 * few good suggestions. In rare cases we need to try three of four changes. 191 */ 192 #define SCORE_SFMAX1 200 /* maximum score for first try */ 193 #define SCORE_SFMAX2 300 /* maximum score for second try */ 194 #define SCORE_SFMAX3 400 /* maximum score for third try */ 195 196 #define SCORE_BIG SCORE_INS * 3 /* big difference */ 197 #define SCORE_MAXMAX 999999 /* accept any score */ 198 #define SCORE_LIMITMAX 350 /* for spell_edit_score_limit() */ 199 200 /* for spell_edit_score_limit() we need to know the minimum value of 201 * SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS */ 202 #define SCORE_EDIT_MIN SCORE_SIMILAR 203 204 /* 205 * Structure to store info for word matching. 206 */ 207 typedef struct matchinf_S 208 { 209 langp_T *mi_lp; /* info for language and region */ 210 211 /* pointers to original text to be checked */ 212 char_u *mi_word; /* start of word being checked */ 213 char_u *mi_end; /* end of matching word so far */ 214 char_u *mi_fend; /* next char to be added to mi_fword */ 215 char_u *mi_cend; /* char after what was used for 216 mi_capflags */ 217 218 /* case-folded text */ 219 char_u mi_fword[MAXWLEN + 1]; /* mi_word case-folded */ 220 int mi_fwordlen; /* nr of valid bytes in mi_fword */ 221 222 /* for when checking word after a prefix */ 223 int mi_prefarridx; /* index in sl_pidxs with list of 224 affixID/condition */ 225 int mi_prefcnt; /* number of entries at mi_prefarridx */ 226 int mi_prefixlen; /* byte length of prefix */ 227 int mi_cprefixlen; /* byte length of prefix in original 228 case */ 229 230 /* for when checking a compound word */ 231 int mi_compoff; /* start of following word offset */ 232 char_u mi_compflags[MAXWLEN]; /* flags for compound words used */ 233 int mi_complen; /* nr of compound words used */ 234 int mi_compextra; /* nr of COMPOUNDROOT words */ 235 236 /* others */ 237 int mi_result; /* result so far: SP_BAD, SP_OK, etc. */ 238 int mi_capflags; /* WF_ONECAP WF_ALLCAP WF_KEEPCAP */ 239 win_T *mi_win; /* buffer being checked */ 240 241 /* for NOBREAK */ 242 int mi_result2; /* "mi_resul" without following word */ 243 char_u *mi_end2; /* "mi_end" without following word */ 244 } matchinf_T; 245 246 247 static int spell_iswordp(char_u *p, win_T *wp); 248 static int spell_mb_isword_class(int cl, win_T *wp); 249 250 /* 251 * For finding suggestions: At each node in the tree these states are tried: 252 */ 253 typedef enum 254 { 255 STATE_START = 0, /* At start of node check for NUL bytes (goodword 256 * ends); if badword ends there is a match, otherwise 257 * try splitting word. */ 258 STATE_NOPREFIX, /* try without prefix */ 259 STATE_SPLITUNDO, /* Undo splitting. */ 260 STATE_ENDNUL, /* Past NUL bytes at start of the node. */ 261 STATE_PLAIN, /* Use each byte of the node. */ 262 STATE_DEL, /* Delete a byte from the bad word. */ 263 STATE_INS_PREP, /* Prepare for inserting bytes. */ 264 STATE_INS, /* Insert a byte in the bad word. */ 265 STATE_SWAP, /* Swap two bytes. */ 266 STATE_UNSWAP, /* Undo swap two characters. */ 267 STATE_SWAP3, /* Swap two characters over three. */ 268 STATE_UNSWAP3, /* Undo Swap two characters over three. */ 269 STATE_UNROT3L, /* Undo rotate three characters left */ 270 STATE_UNROT3R, /* Undo rotate three characters right */ 271 STATE_REP_INI, /* Prepare for using REP items. */ 272 STATE_REP, /* Use matching REP items from the .aff file. */ 273 STATE_REP_UNDO, /* Undo a REP item replacement. */ 274 STATE_FINAL /* End of this node. */ 275 } state_T; 276 277 /* 278 * Struct to keep the state at each level in suggest_try_change(). 279 */ 280 typedef struct trystate_S 281 { 282 state_T ts_state; /* state at this level, STATE_ */ 283 int ts_score; /* score */ 284 idx_T ts_arridx; /* index in tree array, start of node */ 285 short ts_curi; /* index in list of child nodes */ 286 char_u ts_fidx; /* index in fword[], case-folded bad word */ 287 char_u ts_fidxtry; /* ts_fidx at which bytes may be changed */ 288 char_u ts_twordlen; /* valid length of tword[] */ 289 char_u ts_prefixdepth; /* stack depth for end of prefix or 290 * PFD_PREFIXTREE or PFD_NOPREFIX */ 291 char_u ts_flags; /* TSF_ flags */ 292 char_u ts_tcharlen; /* number of bytes in tword character */ 293 char_u ts_tcharidx; /* current byte index in tword character */ 294 char_u ts_isdiff; /* DIFF_ values */ 295 char_u ts_fcharstart; /* index in fword where badword char started */ 296 char_u ts_prewordlen; /* length of word in "preword[]" */ 297 char_u ts_splitoff; /* index in "tword" after last split */ 298 char_u ts_splitfidx; /* "ts_fidx" at word split */ 299 char_u ts_complen; /* nr of compound words used */ 300 char_u ts_compsplit; /* index for "compflags" where word was spit */ 301 char_u ts_save_badflags; /* su_badflags saved here */ 302 char_u ts_delidx; /* index in fword for char that was deleted, 303 valid when "ts_flags" has TSF_DIDDEL */ 304 } trystate_T; 305 306 /* values for ts_isdiff */ 307 #define DIFF_NONE 0 /* no different byte (yet) */ 308 #define DIFF_YES 1 /* different byte found */ 309 #define DIFF_INSERT 2 /* inserting character */ 310 311 /* values for ts_flags */ 312 #define TSF_PREFIXOK 1 /* already checked that prefix is OK */ 313 #define TSF_DIDSPLIT 2 /* tried split at this point */ 314 #define TSF_DIDDEL 4 /* did a delete, "ts_delidx" has index */ 315 316 /* special values ts_prefixdepth */ 317 #define PFD_NOPREFIX 0xff /* not using prefixes */ 318 #define PFD_PREFIXTREE 0xfe /* walking through the prefix tree */ 319 #define PFD_NOTSPECIAL 0xfd /* highest value that's not special */ 320 321 /* mode values for find_word */ 322 #define FIND_FOLDWORD 0 /* find word case-folded */ 323 #define FIND_KEEPWORD 1 /* find keep-case word */ 324 #define FIND_PREFIX 2 /* find word after prefix */ 325 #define FIND_COMPOUND 3 /* find case-folded compound word */ 326 #define FIND_KEEPCOMPOUND 4 /* find keep-case compound word */ 327 328 static void find_word(matchinf_T *mip, int mode); 329 static int match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap); 330 static int can_compound(slang_T *slang, char_u *word, char_u *flags); 331 static int match_compoundrule(slang_T *slang, char_u *compflags); 332 static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, int cond_req); 333 static void find_prefix(matchinf_T *mip, int mode); 334 static int fold_more(matchinf_T *mip); 335 static int spell_valid_case(int wordflags, int treeflags); 336 static void spell_load_cb(char_u *fname, void *cookie); 337 static int count_syllables(slang_T *slang, char_u *word); 338 static void clear_midword(win_T *buf); 339 static void use_midword(slang_T *lp, win_T *buf); 340 static int find_region(char_u *rp, char_u *region); 341 static int check_need_cap(linenr_T lnum, colnr_T col); 342 static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, int banbadword, int need_cap, int interactive); 343 #ifdef FEAT_EVAL 344 static void spell_suggest_expr(suginfo_T *su, char_u *expr); 345 #endif 346 static void spell_suggest_file(suginfo_T *su, char_u *fname); 347 static void spell_suggest_intern(suginfo_T *su, int interactive); 348 static void spell_find_cleanup(suginfo_T *su); 349 static void suggest_try_special(suginfo_T *su); 350 static void suggest_try_change(suginfo_T *su); 351 static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, int soundfold); 352 static void go_deeper(trystate_T *stack, int depth, int score_add); 353 static int nofold_len(char_u *fword, int flen, char_u *word); 354 static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword); 355 static void score_comp_sal(suginfo_T *su); 356 static void score_combine(suginfo_T *su); 357 static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound); 358 static void suggest_try_soundalike_prep(void); 359 static void suggest_try_soundalike(suginfo_T *su); 360 static void suggest_try_soundalike_finish(void); 361 static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp); 362 static int soundfold_find(slang_T *slang, char_u *word); 363 static void make_case_word(char_u *fword, char_u *cword, int flags); 364 static int similar_chars(slang_T *slang, int c1, int c2); 365 static void add_suggestion(suginfo_T *su, garray_T *gap, char_u *goodword, int badlen, int score, int altscore, int had_bonus, slang_T *slang, int maxsf); 366 static void check_suggestions(suginfo_T *su, garray_T *gap); 367 static void add_banned(suginfo_T *su, char_u *word); 368 static void rescore_suggestions(suginfo_T *su); 369 static void rescore_one(suginfo_T *su, suggest_T *stp); 370 static int cleanup_suggestions(garray_T *gap, int maxscore, int keep); 371 static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res); 372 static void spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res); 373 static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res); 374 static int soundalike_score(char_u *goodsound, char_u *badsound); 375 static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword); 376 static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit); 377 static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit); 378 static void dump_word(slang_T *slang, char_u *word, char_u *pat, int *dir, int round, int flags, linenr_T lnum); 379 static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, int *dir, int round, int flags, linenr_T startlnum); 380 381 382 /* Remember what "z?" replaced. */ 383 static char_u *repl_from = NULL; 384 static char_u *repl_to = NULL; 385 386 /* 387 * Main spell-checking function. 388 * "ptr" points to a character that could be the start of a word. 389 * "*attrp" is set to the highlight index for a badly spelled word. For a 390 * non-word or when it's OK it remains unchanged. 391 * This must only be called when 'spelllang' is not empty. 392 * 393 * "capcol" is used to check for a Capitalised word after the end of a 394 * sentence. If it's zero then perform the check. Return the column where to 395 * check next, or -1 when no sentence end was found. If it's NULL then don't 396 * worry. 397 * 398 * Returns the length of the word in bytes, also when it's OK, so that the 399 * caller can skip over the word. 400 */ 401 int 402 spell_check( 403 win_T *wp, /* current window */ 404 char_u *ptr, 405 hlf_T *attrp, 406 int *capcol, /* column to check for Capital */ 407 int docount) /* count good words */ 408 { 409 matchinf_T mi; /* Most things are put in "mi" so that it can 410 be passed to functions quickly. */ 411 int nrlen = 0; /* found a number first */ 412 int c; 413 int wrongcaplen = 0; 414 int lpi; 415 int count_word = docount; 416 417 /* A word never starts at a space or a control character. Return quickly 418 * then, skipping over the character. */ 419 if (*ptr <= ' ') 420 return 1; 421 422 /* Return here when loading language files failed. */ 423 if (wp->w_s->b_langp.ga_len == 0) 424 return 1; 425 426 vim_memset(&mi, 0, sizeof(matchinf_T)); 427 428 /* A number is always OK. Also skip hexadecimal numbers 0xFF99 and 429 * 0X99FF. But always do check spelling to find "3GPP" and "11 430 * julifeest". */ 431 if (*ptr >= '0' && *ptr <= '9') 432 { 433 if (*ptr == '0' && (ptr[1] == 'b' || ptr[1] == 'B')) 434 mi.mi_end = skipbin(ptr + 2); 435 else if (*ptr == '0' && (ptr[1] == 'x' || ptr[1] == 'X')) 436 mi.mi_end = skiphex(ptr + 2); 437 else 438 mi.mi_end = skipdigits(ptr); 439 nrlen = (int)(mi.mi_end - ptr); 440 } 441 442 /* Find the normal end of the word (until the next non-word character). */ 443 mi.mi_word = ptr; 444 mi.mi_fend = ptr; 445 if (spell_iswordp(mi.mi_fend, wp)) 446 { 447 do 448 { 449 MB_PTR_ADV(mi.mi_fend); 450 } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)); 451 452 if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) 453 { 454 /* Check word starting with capital letter. */ 455 c = PTR2CHAR(ptr); 456 if (!SPELL_ISUPPER(c)) 457 wrongcaplen = (int)(mi.mi_fend - ptr); 458 } 459 } 460 if (capcol != NULL) 461 *capcol = -1; 462 463 /* We always use the characters up to the next non-word character, 464 * also for bad words. */ 465 mi.mi_end = mi.mi_fend; 466 467 /* Check caps type later. */ 468 mi.mi_capflags = 0; 469 mi.mi_cend = NULL; 470 mi.mi_win = wp; 471 472 /* case-fold the word with one non-word character, so that we can check 473 * for the word end. */ 474 if (*mi.mi_fend != NUL) 475 MB_PTR_ADV(mi.mi_fend); 476 477 (void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, 478 MAXWLEN + 1); 479 mi.mi_fwordlen = (int)STRLEN(mi.mi_fword); 480 481 /* The word is bad unless we recognize it. */ 482 mi.mi_result = SP_BAD; 483 mi.mi_result2 = SP_BAD; 484 485 /* 486 * Loop over the languages specified in 'spelllang'. 487 * We check them all, because a word may be matched longer in another 488 * language. 489 */ 490 for (lpi = 0; lpi < wp->w_s->b_langp.ga_len; ++lpi) 491 { 492 mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, lpi); 493 494 /* If reloading fails the language is still in the list but everything 495 * has been cleared. */ 496 if (mi.mi_lp->lp_slang->sl_fidxs == NULL) 497 continue; 498 499 /* Check for a matching word in case-folded words. */ 500 find_word(&mi, FIND_FOLDWORD); 501 502 /* Check for a matching word in keep-case words. */ 503 find_word(&mi, FIND_KEEPWORD); 504 505 /* Check for matching prefixes. */ 506 find_prefix(&mi, FIND_FOLDWORD); 507 508 /* For a NOBREAK language, may want to use a word without a following 509 * word as a backup. */ 510 if (mi.mi_lp->lp_slang->sl_nobreak && mi.mi_result == SP_BAD 511 && mi.mi_result2 != SP_BAD) 512 { 513 mi.mi_result = mi.mi_result2; 514 mi.mi_end = mi.mi_end2; 515 } 516 517 /* Count the word in the first language where it's found to be OK. */ 518 if (count_word && mi.mi_result == SP_OK) 519 { 520 count_common_word(mi.mi_lp->lp_slang, ptr, 521 (int)(mi.mi_end - ptr), 1); 522 count_word = FALSE; 523 } 524 } 525 526 if (mi.mi_result != SP_OK) 527 { 528 /* If we found a number skip over it. Allows for "42nd". Do flag 529 * rare and local words, e.g., "3GPP". */ 530 if (nrlen > 0) 531 { 532 if (mi.mi_result == SP_BAD || mi.mi_result == SP_BANNED) 533 return nrlen; 534 } 535 536 /* When we are at a non-word character there is no error, just 537 * skip over the character (try looking for a word after it). */ 538 else if (!spell_iswordp_nmw(ptr, wp)) 539 { 540 if (capcol != NULL && wp->w_s->b_cap_prog != NULL) 541 { 542 regmatch_T regmatch; 543 int r; 544 545 /* Check for end of sentence. */ 546 regmatch.regprog = wp->w_s->b_cap_prog; 547 regmatch.rm_ic = FALSE; 548 r = vim_regexec(®match, ptr, 0); 549 wp->w_s->b_cap_prog = regmatch.regprog; 550 if (r) 551 *capcol = (int)(regmatch.endp[0] - ptr); 552 } 553 554 if (has_mbyte) 555 return (*mb_ptr2len)(ptr); 556 return 1; 557 } 558 else if (mi.mi_end == ptr) 559 /* Always include at least one character. Required for when there 560 * is a mixup in "midword". */ 561 MB_PTR_ADV(mi.mi_end); 562 else if (mi.mi_result == SP_BAD 563 && LANGP_ENTRY(wp->w_s->b_langp, 0)->lp_slang->sl_nobreak) 564 { 565 char_u *p, *fp; 566 int save_result = mi.mi_result; 567 568 /* First language in 'spelllang' is NOBREAK. Find first position 569 * at which any word would be valid. */ 570 mi.mi_lp = LANGP_ENTRY(wp->w_s->b_langp, 0); 571 if (mi.mi_lp->lp_slang->sl_fidxs != NULL) 572 { 573 p = mi.mi_word; 574 fp = mi.mi_fword; 575 for (;;) 576 { 577 MB_PTR_ADV(p); 578 MB_PTR_ADV(fp); 579 if (p >= mi.mi_end) 580 break; 581 mi.mi_compoff = (int)(fp - mi.mi_fword); 582 find_word(&mi, FIND_COMPOUND); 583 if (mi.mi_result != SP_BAD) 584 { 585 mi.mi_end = p; 586 break; 587 } 588 } 589 mi.mi_result = save_result; 590 } 591 } 592 593 if (mi.mi_result == SP_BAD || mi.mi_result == SP_BANNED) 594 *attrp = HLF_SPB; 595 else if (mi.mi_result == SP_RARE) 596 *attrp = HLF_SPR; 597 else 598 *attrp = HLF_SPL; 599 } 600 601 if (wrongcaplen > 0 && (mi.mi_result == SP_OK || mi.mi_result == SP_RARE)) 602 { 603 /* Report SpellCap only when the word isn't badly spelled. */ 604 *attrp = HLF_SPC; 605 return wrongcaplen; 606 } 607 608 return (int)(mi.mi_end - ptr); 609 } 610 611 /* 612 * Check if the word at "mip->mi_word" is in the tree. 613 * When "mode" is FIND_FOLDWORD check in fold-case word tree. 614 * When "mode" is FIND_KEEPWORD check in keep-case word tree. 615 * When "mode" is FIND_PREFIX check for word after prefix in fold-case word 616 * tree. 617 * 618 * For a match mip->mi_result is updated. 619 */ 620 static void 621 find_word(matchinf_T *mip, int mode) 622 { 623 idx_T arridx = 0; 624 int endlen[MAXWLEN]; /* length at possible word endings */ 625 idx_T endidx[MAXWLEN]; /* possible word endings */ 626 int endidxcnt = 0; 627 int len; 628 int wlen = 0; 629 int flen; 630 int c; 631 char_u *ptr; 632 idx_T lo, hi, m; 633 char_u *s; 634 char_u *p; 635 int res = SP_BAD; 636 slang_T *slang = mip->mi_lp->lp_slang; 637 unsigned flags; 638 char_u *byts; 639 idx_T *idxs; 640 int word_ends; 641 int prefix_found; 642 int nobreak_result; 643 644 if (mode == FIND_KEEPWORD || mode == FIND_KEEPCOMPOUND) 645 { 646 /* Check for word with matching case in keep-case tree. */ 647 ptr = mip->mi_word; 648 flen = 9999; /* no case folding, always enough bytes */ 649 byts = slang->sl_kbyts; 650 idxs = slang->sl_kidxs; 651 652 if (mode == FIND_KEEPCOMPOUND) 653 /* Skip over the previously found word(s). */ 654 wlen += mip->mi_compoff; 655 } 656 else 657 { 658 /* Check for case-folded in case-folded tree. */ 659 ptr = mip->mi_fword; 660 flen = mip->mi_fwordlen; /* available case-folded bytes */ 661 byts = slang->sl_fbyts; 662 idxs = slang->sl_fidxs; 663 664 if (mode == FIND_PREFIX) 665 { 666 /* Skip over the prefix. */ 667 wlen = mip->mi_prefixlen; 668 flen -= mip->mi_prefixlen; 669 } 670 else if (mode == FIND_COMPOUND) 671 { 672 /* Skip over the previously found word(s). */ 673 wlen = mip->mi_compoff; 674 flen -= mip->mi_compoff; 675 } 676 677 } 678 679 if (byts == NULL) 680 return; /* array is empty */ 681 682 /* 683 * Repeat advancing in the tree until: 684 * - there is a byte that doesn't match, 685 * - we reach the end of the tree, 686 * - or we reach the end of the line. 687 */ 688 for (;;) 689 { 690 if (flen <= 0 && *mip->mi_fend != NUL) 691 flen = fold_more(mip); 692 693 len = byts[arridx++]; 694 695 /* If the first possible byte is a zero the word could end here. 696 * Remember this index, we first check for the longest word. */ 697 if (byts[arridx] == 0) 698 { 699 if (endidxcnt == MAXWLEN) 700 { 701 /* Must be a corrupted spell file. */ 702 emsg(_(e_format)); 703 return; 704 } 705 endlen[endidxcnt] = wlen; 706 endidx[endidxcnt++] = arridx++; 707 --len; 708 709 /* Skip over the zeros, there can be several flag/region 710 * combinations. */ 711 while (len > 0 && byts[arridx] == 0) 712 { 713 ++arridx; 714 --len; 715 } 716 if (len == 0) 717 break; /* no children, word must end here */ 718 } 719 720 /* Stop looking at end of the line. */ 721 if (ptr[wlen] == NUL) 722 break; 723 724 /* Perform a binary search in the list of accepted bytes. */ 725 c = ptr[wlen]; 726 if (c == TAB) /* <Tab> is handled like <Space> */ 727 c = ' '; 728 lo = arridx; 729 hi = arridx + len - 1; 730 while (lo < hi) 731 { 732 m = (lo + hi) / 2; 733 if (byts[m] > c) 734 hi = m - 1; 735 else if (byts[m] < c) 736 lo = m + 1; 737 else 738 { 739 lo = hi = m; 740 break; 741 } 742 } 743 744 /* Stop if there is no matching byte. */ 745 if (hi < lo || byts[lo] != c) 746 break; 747 748 /* Continue at the child (if there is one). */ 749 arridx = idxs[lo]; 750 ++wlen; 751 --flen; 752 753 /* One space in the good word may stand for several spaces in the 754 * checked word. */ 755 if (c == ' ') 756 { 757 for (;;) 758 { 759 if (flen <= 0 && *mip->mi_fend != NUL) 760 flen = fold_more(mip); 761 if (ptr[wlen] != ' ' && ptr[wlen] != TAB) 762 break; 763 ++wlen; 764 --flen; 765 } 766 } 767 } 768 769 /* 770 * Verify that one of the possible endings is valid. Try the longest 771 * first. 772 */ 773 while (endidxcnt > 0) 774 { 775 --endidxcnt; 776 arridx = endidx[endidxcnt]; 777 wlen = endlen[endidxcnt]; 778 779 if ((*mb_head_off)(ptr, ptr + wlen) > 0) 780 continue; /* not at first byte of character */ 781 if (spell_iswordp(ptr + wlen, mip->mi_win)) 782 { 783 if (slang->sl_compprog == NULL && !slang->sl_nobreak) 784 continue; /* next char is a word character */ 785 word_ends = FALSE; 786 } 787 else 788 word_ends = TRUE; 789 /* The prefix flag is before compound flags. Once a valid prefix flag 790 * has been found we try compound flags. */ 791 prefix_found = FALSE; 792 793 if (mode != FIND_KEEPWORD && has_mbyte) 794 { 795 /* Compute byte length in original word, length may change 796 * when folding case. This can be slow, take a shortcut when the 797 * case-folded word is equal to the keep-case word. */ 798 p = mip->mi_word; 799 if (STRNCMP(ptr, p, wlen) != 0) 800 { 801 for (s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) 802 MB_PTR_ADV(p); 803 wlen = (int)(p - mip->mi_word); 804 } 805 } 806 807 /* Check flags and region. For FIND_PREFIX check the condition and 808 * prefix ID. 809 * Repeat this if there are more flags/region alternatives until there 810 * is a match. */ 811 res = SP_BAD; 812 for (len = byts[arridx - 1]; len > 0 && byts[arridx] == 0; 813 --len, ++arridx) 814 { 815 flags = idxs[arridx]; 816 817 /* For the fold-case tree check that the case of the checked word 818 * matches with what the word in the tree requires. 819 * For keep-case tree the case is always right. For prefixes we 820 * don't bother to check. */ 821 if (mode == FIND_FOLDWORD) 822 { 823 if (mip->mi_cend != mip->mi_word + wlen) 824 { 825 /* mi_capflags was set for a different word length, need 826 * to do it again. */ 827 mip->mi_cend = mip->mi_word + wlen; 828 mip->mi_capflags = captype(mip->mi_word, mip->mi_cend); 829 } 830 831 if (mip->mi_capflags == WF_KEEPCAP 832 || !spell_valid_case(mip->mi_capflags, flags)) 833 continue; 834 } 835 836 /* When mode is FIND_PREFIX the word must support the prefix: 837 * check the prefix ID and the condition. Do that for the list at 838 * mip->mi_prefarridx that find_prefix() filled. */ 839 else if (mode == FIND_PREFIX && !prefix_found) 840 { 841 c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx, 842 flags, 843 mip->mi_word + mip->mi_cprefixlen, slang, 844 FALSE); 845 if (c == 0) 846 continue; 847 848 /* Use the WF_RARE flag for a rare prefix. */ 849 if (c & WF_RAREPFX) 850 flags |= WF_RARE; 851 prefix_found = TRUE; 852 } 853 854 if (slang->sl_nobreak) 855 { 856 if ((mode == FIND_COMPOUND || mode == FIND_KEEPCOMPOUND) 857 && (flags & WF_BANNED) == 0) 858 { 859 /* NOBREAK: found a valid following word. That's all we 860 * need to know, so return. */ 861 mip->mi_result = SP_OK; 862 break; 863 } 864 } 865 866 else if ((mode == FIND_COMPOUND || mode == FIND_KEEPCOMPOUND 867 || !word_ends)) 868 { 869 /* If there is no compound flag or the word is shorter than 870 * COMPOUNDMIN reject it quickly. 871 * Makes you wonder why someone puts a compound flag on a word 872 * that's too short... Myspell compatibility requires this 873 * anyway. */ 874 if (((unsigned)flags >> 24) == 0 875 || wlen - mip->mi_compoff < slang->sl_compminlen) 876 continue; 877 /* For multi-byte chars check character length against 878 * COMPOUNDMIN. */ 879 if (has_mbyte 880 && slang->sl_compminlen > 0 881 && mb_charlen_len(mip->mi_word + mip->mi_compoff, 882 wlen - mip->mi_compoff) < slang->sl_compminlen) 883 continue; 884 885 /* Limit the number of compound words to COMPOUNDWORDMAX if no 886 * maximum for syllables is specified. */ 887 if (!word_ends && mip->mi_complen + mip->mi_compextra + 2 888 > slang->sl_compmax 889 && slang->sl_compsylmax == MAXWLEN) 890 continue; 891 892 /* Don't allow compounding on a side where an affix was added, 893 * unless COMPOUNDPERMITFLAG was used. */ 894 if (mip->mi_complen > 0 && (flags & WF_NOCOMPBEF)) 895 continue; 896 if (!word_ends && (flags & WF_NOCOMPAFT)) 897 continue; 898 899 /* Quickly check if compounding is possible with this flag. */ 900 if (!byte_in_str(mip->mi_complen == 0 901 ? slang->sl_compstartflags 902 : slang->sl_compallflags, 903 ((unsigned)flags >> 24))) 904 continue; 905 906 /* If there is a match with a CHECKCOMPOUNDPATTERN rule 907 * discard the compound word. */ 908 if (match_checkcompoundpattern(ptr, wlen, &slang->sl_comppat)) 909 continue; 910 911 if (mode == FIND_COMPOUND) 912 { 913 int capflags; 914 915 /* Need to check the caps type of the appended compound 916 * word. */ 917 if (has_mbyte && STRNCMP(ptr, mip->mi_word, 918 mip->mi_compoff) != 0) 919 { 920 /* case folding may have changed the length */ 921 p = mip->mi_word; 922 for (s = ptr; s < ptr + mip->mi_compoff; MB_PTR_ADV(s)) 923 MB_PTR_ADV(p); 924 } 925 else 926 p = mip->mi_word + mip->mi_compoff; 927 capflags = captype(p, mip->mi_word + wlen); 928 if (capflags == WF_KEEPCAP || (capflags == WF_ALLCAP 929 && (flags & WF_FIXCAP) != 0)) 930 continue; 931 932 if (capflags != WF_ALLCAP) 933 { 934 /* When the character before the word is a word 935 * character we do not accept a Onecap word. We do 936 * accept a no-caps word, even when the dictionary 937 * word specifies ONECAP. */ 938 MB_PTR_BACK(mip->mi_word, p); 939 if (spell_iswordp_nmw(p, mip->mi_win) 940 ? capflags == WF_ONECAP 941 : (flags & WF_ONECAP) != 0 942 && capflags != WF_ONECAP) 943 continue; 944 } 945 } 946 947 /* If the word ends the sequence of compound flags of the 948 * words must match with one of the COMPOUNDRULE items and 949 * the number of syllables must not be too large. */ 950 mip->mi_compflags[mip->mi_complen] = ((unsigned)flags >> 24); 951 mip->mi_compflags[mip->mi_complen + 1] = NUL; 952 if (word_ends) 953 { 954 char_u fword[MAXWLEN]; 955 956 if (slang->sl_compsylmax < MAXWLEN) 957 { 958 /* "fword" is only needed for checking syllables. */ 959 if (ptr == mip->mi_word) 960 (void)spell_casefold(ptr, wlen, fword, MAXWLEN); 961 else 962 vim_strncpy(fword, ptr, endlen[endidxcnt]); 963 } 964 if (!can_compound(slang, fword, mip->mi_compflags)) 965 continue; 966 } 967 else if (slang->sl_comprules != NULL 968 && !match_compoundrule(slang, mip->mi_compflags)) 969 /* The compound flags collected so far do not match any 970 * COMPOUNDRULE, discard the compounded word. */ 971 continue; 972 } 973 974 /* Check NEEDCOMPOUND: can't use word without compounding. */ 975 else if (flags & WF_NEEDCOMP) 976 continue; 977 978 nobreak_result = SP_OK; 979 980 if (!word_ends) 981 { 982 int save_result = mip->mi_result; 983 char_u *save_end = mip->mi_end; 984 langp_T *save_lp = mip->mi_lp; 985 int lpi; 986 987 /* Check that a valid word follows. If there is one and we 988 * are compounding, it will set "mi_result", thus we are 989 * always finished here. For NOBREAK we only check that a 990 * valid word follows. 991 * Recursive! */ 992 if (slang->sl_nobreak) 993 mip->mi_result = SP_BAD; 994 995 /* Find following word in case-folded tree. */ 996 mip->mi_compoff = endlen[endidxcnt]; 997 if (has_mbyte && mode == FIND_KEEPWORD) 998 { 999 /* Compute byte length in case-folded word from "wlen": 1000 * byte length in keep-case word. Length may change when 1001 * folding case. This can be slow, take a shortcut when 1002 * the case-folded word is equal to the keep-case word. */ 1003 p = mip->mi_fword; 1004 if (STRNCMP(ptr, p, wlen) != 0) 1005 { 1006 for (s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) 1007 MB_PTR_ADV(p); 1008 mip->mi_compoff = (int)(p - mip->mi_fword); 1009 } 1010 } 1011 #if 0 /* Disabled, see below */ 1012 c = mip->mi_compoff; 1013 #endif 1014 ++mip->mi_complen; 1015 if (flags & WF_COMPROOT) 1016 ++mip->mi_compextra; 1017 1018 /* For NOBREAK we need to try all NOBREAK languages, at least 1019 * to find the ".add" file(s). */ 1020 for (lpi = 0; lpi < mip->mi_win->w_s->b_langp.ga_len; ++lpi) 1021 { 1022 if (slang->sl_nobreak) 1023 { 1024 mip->mi_lp = LANGP_ENTRY(mip->mi_win->w_s->b_langp, lpi); 1025 if (mip->mi_lp->lp_slang->sl_fidxs == NULL 1026 || !mip->mi_lp->lp_slang->sl_nobreak) 1027 continue; 1028 } 1029 1030 find_word(mip, FIND_COMPOUND); 1031 1032 /* When NOBREAK any word that matches is OK. Otherwise we 1033 * need to find the longest match, thus try with keep-case 1034 * and prefix too. */ 1035 if (!slang->sl_nobreak || mip->mi_result == SP_BAD) 1036 { 1037 /* Find following word in keep-case tree. */ 1038 mip->mi_compoff = wlen; 1039 find_word(mip, FIND_KEEPCOMPOUND); 1040 1041 #if 0 /* Disabled, a prefix must not appear halfway a compound word, 1042 unless the COMPOUNDPERMITFLAG is used and then it can't be a 1043 postponed prefix. */ 1044 if (!slang->sl_nobreak || mip->mi_result == SP_BAD) 1045 { 1046 /* Check for following word with prefix. */ 1047 mip->mi_compoff = c; 1048 find_prefix(mip, FIND_COMPOUND); 1049 } 1050 #endif 1051 } 1052 1053 if (!slang->sl_nobreak) 1054 break; 1055 } 1056 --mip->mi_complen; 1057 if (flags & WF_COMPROOT) 1058 --mip->mi_compextra; 1059 mip->mi_lp = save_lp; 1060 1061 if (slang->sl_nobreak) 1062 { 1063 nobreak_result = mip->mi_result; 1064 mip->mi_result = save_result; 1065 mip->mi_end = save_end; 1066 } 1067 else 1068 { 1069 if (mip->mi_result == SP_OK) 1070 break; 1071 continue; 1072 } 1073 } 1074 1075 if (flags & WF_BANNED) 1076 res = SP_BANNED; 1077 else if (flags & WF_REGION) 1078 { 1079 /* Check region. */ 1080 if ((mip->mi_lp->lp_region & (flags >> 16)) != 0) 1081 res = SP_OK; 1082 else 1083 res = SP_LOCAL; 1084 } 1085 else if (flags & WF_RARE) 1086 res = SP_RARE; 1087 else 1088 res = SP_OK; 1089 1090 /* Always use the longest match and the best result. For NOBREAK 1091 * we separately keep the longest match without a following good 1092 * word as a fall-back. */ 1093 if (nobreak_result == SP_BAD) 1094 { 1095 if (mip->mi_result2 > res) 1096 { 1097 mip->mi_result2 = res; 1098 mip->mi_end2 = mip->mi_word + wlen; 1099 } 1100 else if (mip->mi_result2 == res 1101 && mip->mi_end2 < mip->mi_word + wlen) 1102 mip->mi_end2 = mip->mi_word + wlen; 1103 } 1104 else if (mip->mi_result > res) 1105 { 1106 mip->mi_result = res; 1107 mip->mi_end = mip->mi_word + wlen; 1108 } 1109 else if (mip->mi_result == res && mip->mi_end < mip->mi_word + wlen) 1110 mip->mi_end = mip->mi_word + wlen; 1111 1112 if (mip->mi_result == SP_OK) 1113 break; 1114 } 1115 1116 if (mip->mi_result == SP_OK) 1117 break; 1118 } 1119 } 1120 1121 /* 1122 * Return TRUE if there is a match between the word ptr[wlen] and 1123 * CHECKCOMPOUNDPATTERN rules, assuming that we will concatenate with another 1124 * word. 1125 * A match means that the first part of CHECKCOMPOUNDPATTERN matches at the 1126 * end of ptr[wlen] and the second part matches after it. 1127 */ 1128 static int 1129 match_checkcompoundpattern( 1130 char_u *ptr, 1131 int wlen, 1132 garray_T *gap) /* &sl_comppat */ 1133 { 1134 int i; 1135 char_u *p; 1136 int len; 1137 1138 for (i = 0; i + 1 < gap->ga_len; i += 2) 1139 { 1140 p = ((char_u **)gap->ga_data)[i + 1]; 1141 if (STRNCMP(ptr + wlen, p, STRLEN(p)) == 0) 1142 { 1143 /* Second part matches at start of following compound word, now 1144 * check if first part matches at end of previous word. */ 1145 p = ((char_u **)gap->ga_data)[i]; 1146 len = (int)STRLEN(p); 1147 if (len <= wlen && STRNCMP(ptr + wlen - len, p, len) == 0) 1148 return TRUE; 1149 } 1150 } 1151 return FALSE; 1152 } 1153 1154 /* 1155 * Return TRUE if "flags" is a valid sequence of compound flags and "word" 1156 * does not have too many syllables. 1157 */ 1158 static int 1159 can_compound(slang_T *slang, char_u *word, char_u *flags) 1160 { 1161 char_u uflags[MAXWLEN * 2]; 1162 int i; 1163 char_u *p; 1164 1165 if (slang->sl_compprog == NULL) 1166 return FALSE; 1167 if (enc_utf8) 1168 { 1169 /* Need to convert the single byte flags to utf8 characters. */ 1170 p = uflags; 1171 for (i = 0; flags[i] != NUL; ++i) 1172 p += utf_char2bytes(flags[i], p); 1173 *p = NUL; 1174 p = uflags; 1175 } 1176 else 1177 p = flags; 1178 if (!vim_regexec_prog(&slang->sl_compprog, FALSE, p, 0)) 1179 return FALSE; 1180 1181 /* Count the number of syllables. This may be slow, do it last. If there 1182 * are too many syllables AND the number of compound words is above 1183 * COMPOUNDWORDMAX then compounding is not allowed. */ 1184 if (slang->sl_compsylmax < MAXWLEN 1185 && count_syllables(slang, word) > slang->sl_compsylmax) 1186 return (int)STRLEN(flags) < slang->sl_compmax; 1187 return TRUE; 1188 } 1189 1190 /* 1191 * Return TRUE when the sequence of flags in "compflags" plus "flag" can 1192 * possibly form a valid compounded word. This also checks the COMPOUNDRULE 1193 * lines if they don't contain wildcards. 1194 */ 1195 static int 1196 can_be_compound( 1197 trystate_T *sp, 1198 slang_T *slang, 1199 char_u *compflags, 1200 int flag) 1201 { 1202 /* If the flag doesn't appear in sl_compstartflags or sl_compallflags 1203 * then it can't possibly compound. */ 1204 if (!byte_in_str(sp->ts_complen == sp->ts_compsplit 1205 ? slang->sl_compstartflags : slang->sl_compallflags, flag)) 1206 return FALSE; 1207 1208 /* If there are no wildcards, we can check if the flags collected so far 1209 * possibly can form a match with COMPOUNDRULE patterns. This only 1210 * makes sense when we have two or more words. */ 1211 if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) 1212 { 1213 int v; 1214 1215 compflags[sp->ts_complen] = flag; 1216 compflags[sp->ts_complen + 1] = NUL; 1217 v = match_compoundrule(slang, compflags + sp->ts_compsplit); 1218 compflags[sp->ts_complen] = NUL; 1219 return v; 1220 } 1221 1222 return TRUE; 1223 } 1224 1225 1226 /* 1227 * Return TRUE if the compound flags in compflags[] match the start of any 1228 * compound rule. This is used to stop trying a compound if the flags 1229 * collected so far can't possibly match any compound rule. 1230 * Caller must check that slang->sl_comprules is not NULL. 1231 */ 1232 static int 1233 match_compoundrule(slang_T *slang, char_u *compflags) 1234 { 1235 char_u *p; 1236 int i; 1237 int c; 1238 1239 /* loop over all the COMPOUNDRULE entries */ 1240 for (p = slang->sl_comprules; *p != NUL; ++p) 1241 { 1242 /* loop over the flags in the compound word we have made, match 1243 * them against the current rule entry */ 1244 for (i = 0; ; ++i) 1245 { 1246 c = compflags[i]; 1247 if (c == NUL) 1248 /* found a rule that matches for the flags we have so far */ 1249 return TRUE; 1250 if (*p == '/' || *p == NUL) 1251 break; /* end of rule, it's too short */ 1252 if (*p == '[') 1253 { 1254 int match = FALSE; 1255 1256 /* compare against all the flags in [] */ 1257 ++p; 1258 while (*p != ']' && *p != NUL) 1259 if (*p++ == c) 1260 match = TRUE; 1261 if (!match) 1262 break; /* none matches */ 1263 } 1264 else if (*p != c) 1265 break; /* flag of word doesn't match flag in pattern */ 1266 ++p; 1267 } 1268 1269 /* Skip to the next "/", where the next pattern starts. */ 1270 p = vim_strchr(p, '/'); 1271 if (p == NULL) 1272 break; 1273 } 1274 1275 /* Checked all the rules and none of them match the flags, so there 1276 * can't possibly be a compound starting with these flags. */ 1277 return FALSE; 1278 } 1279 1280 /* 1281 * Return non-zero if the prefix indicated by "arridx" matches with the prefix 1282 * ID in "flags" for the word "word". 1283 * The WF_RAREPFX flag is included in the return value for a rare prefix. 1284 */ 1285 static int 1286 valid_word_prefix( 1287 int totprefcnt, /* nr of prefix IDs */ 1288 int arridx, /* idx in sl_pidxs[] */ 1289 int flags, 1290 char_u *word, 1291 slang_T *slang, 1292 int cond_req) /* only use prefixes with a condition */ 1293 { 1294 int prefcnt; 1295 int pidx; 1296 regprog_T **rp; 1297 int prefid; 1298 1299 prefid = (unsigned)flags >> 24; 1300 for (prefcnt = totprefcnt - 1; prefcnt >= 0; --prefcnt) 1301 { 1302 pidx = slang->sl_pidxs[arridx + prefcnt]; 1303 1304 /* Check the prefix ID. */ 1305 if (prefid != (pidx & 0xff)) 1306 continue; 1307 1308 /* Check if the prefix doesn't combine and the word already has a 1309 * suffix. */ 1310 if ((flags & WF_HAS_AFF) && (pidx & WF_PFX_NC)) 1311 continue; 1312 1313 /* Check the condition, if there is one. The condition index is 1314 * stored in the two bytes above the prefix ID byte. */ 1315 rp = &slang->sl_prefprog[((unsigned)pidx >> 8) & 0xffff]; 1316 if (*rp != NULL) 1317 { 1318 if (!vim_regexec_prog(rp, FALSE, word, 0)) 1319 continue; 1320 } 1321 else if (cond_req) 1322 continue; 1323 1324 /* It's a match! Return the WF_ flags. */ 1325 return pidx; 1326 } 1327 return 0; 1328 } 1329 1330 /* 1331 * Check if the word at "mip->mi_word" has a matching prefix. 1332 * If it does, then check the following word. 1333 * 1334 * If "mode" is "FIND_COMPOUND" then do the same after another word, find a 1335 * prefix in a compound word. 1336 * 1337 * For a match mip->mi_result is updated. 1338 */ 1339 static void 1340 find_prefix(matchinf_T *mip, int mode) 1341 { 1342 idx_T arridx = 0; 1343 int len; 1344 int wlen = 0; 1345 int flen; 1346 int c; 1347 char_u *ptr; 1348 idx_T lo, hi, m; 1349 slang_T *slang = mip->mi_lp->lp_slang; 1350 char_u *byts; 1351 idx_T *idxs; 1352 1353 byts = slang->sl_pbyts; 1354 if (byts == NULL) 1355 return; /* array is empty */ 1356 1357 /* We use the case-folded word here, since prefixes are always 1358 * case-folded. */ 1359 ptr = mip->mi_fword; 1360 flen = mip->mi_fwordlen; /* available case-folded bytes */ 1361 if (mode == FIND_COMPOUND) 1362 { 1363 /* Skip over the previously found word(s). */ 1364 ptr += mip->mi_compoff; 1365 flen -= mip->mi_compoff; 1366 } 1367 idxs = slang->sl_pidxs; 1368 1369 /* 1370 * Repeat advancing in the tree until: 1371 * - there is a byte that doesn't match, 1372 * - we reach the end of the tree, 1373 * - or we reach the end of the line. 1374 */ 1375 for (;;) 1376 { 1377 if (flen == 0 && *mip->mi_fend != NUL) 1378 flen = fold_more(mip); 1379 1380 len = byts[arridx++]; 1381 1382 /* If the first possible byte is a zero the prefix could end here. 1383 * Check if the following word matches and supports the prefix. */ 1384 if (byts[arridx] == 0) 1385 { 1386 /* There can be several prefixes with different conditions. We 1387 * try them all, since we don't know which one will give the 1388 * longest match. The word is the same each time, pass the list 1389 * of possible prefixes to find_word(). */ 1390 mip->mi_prefarridx = arridx; 1391 mip->mi_prefcnt = len; 1392 while (len > 0 && byts[arridx] == 0) 1393 { 1394 ++arridx; 1395 --len; 1396 } 1397 mip->mi_prefcnt -= len; 1398 1399 /* Find the word that comes after the prefix. */ 1400 mip->mi_prefixlen = wlen; 1401 if (mode == FIND_COMPOUND) 1402 /* Skip over the previously found word(s). */ 1403 mip->mi_prefixlen += mip->mi_compoff; 1404 1405 if (has_mbyte) 1406 { 1407 /* Case-folded length may differ from original length. */ 1408 mip->mi_cprefixlen = nofold_len(mip->mi_fword, 1409 mip->mi_prefixlen, mip->mi_word); 1410 } 1411 else 1412 mip->mi_cprefixlen = mip->mi_prefixlen; 1413 find_word(mip, FIND_PREFIX); 1414 1415 1416 if (len == 0) 1417 break; /* no children, word must end here */ 1418 } 1419 1420 /* Stop looking at end of the line. */ 1421 if (ptr[wlen] == NUL) 1422 break; 1423 1424 /* Perform a binary search in the list of accepted bytes. */ 1425 c = ptr[wlen]; 1426 lo = arridx; 1427 hi = arridx + len - 1; 1428 while (lo < hi) 1429 { 1430 m = (lo + hi) / 2; 1431 if (byts[m] > c) 1432 hi = m - 1; 1433 else if (byts[m] < c) 1434 lo = m + 1; 1435 else 1436 { 1437 lo = hi = m; 1438 break; 1439 } 1440 } 1441 1442 /* Stop if there is no matching byte. */ 1443 if (hi < lo || byts[lo] != c) 1444 break; 1445 1446 /* Continue at the child (if there is one). */ 1447 arridx = idxs[lo]; 1448 ++wlen; 1449 --flen; 1450 } 1451 } 1452 1453 /* 1454 * Need to fold at least one more character. Do until next non-word character 1455 * for efficiency. Include the non-word character too. 1456 * Return the length of the folded chars in bytes. 1457 */ 1458 static int 1459 fold_more(matchinf_T *mip) 1460 { 1461 int flen; 1462 char_u *p; 1463 1464 p = mip->mi_fend; 1465 do 1466 { 1467 MB_PTR_ADV(mip->mi_fend); 1468 } while (*mip->mi_fend != NUL && spell_iswordp(mip->mi_fend, mip->mi_win)); 1469 1470 /* Include the non-word character so that we can check for the word end. */ 1471 if (*mip->mi_fend != NUL) 1472 MB_PTR_ADV(mip->mi_fend); 1473 1474 (void)spell_casefold(p, (int)(mip->mi_fend - p), 1475 mip->mi_fword + mip->mi_fwordlen, 1476 MAXWLEN - mip->mi_fwordlen); 1477 flen = (int)STRLEN(mip->mi_fword + mip->mi_fwordlen); 1478 mip->mi_fwordlen += flen; 1479 return flen; 1480 } 1481 1482 /* 1483 * Check case flags for a word. Return TRUE if the word has the requested 1484 * case. 1485 */ 1486 static int 1487 spell_valid_case( 1488 int wordflags, /* flags for the checked word. */ 1489 int treeflags) /* flags for the word in the spell tree */ 1490 { 1491 return ((wordflags == WF_ALLCAP && (treeflags & WF_FIXCAP) == 0) 1492 || ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0 1493 && ((treeflags & WF_ONECAP) == 0 1494 || (wordflags & WF_ONECAP) != 0))); 1495 } 1496 1497 /* 1498 * Return TRUE if spell checking is not enabled. 1499 */ 1500 static int 1501 no_spell_checking(win_T *wp) 1502 { 1503 if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL 1504 || wp->w_s->b_langp.ga_len == 0) 1505 { 1506 emsg(_("E756: Spell checking is not enabled")); 1507 return TRUE; 1508 } 1509 return FALSE; 1510 } 1511 1512 /* 1513 * Move to next spell error. 1514 * "curline" is FALSE for "[s", "]s", "[S" and "]S". 1515 * "curline" is TRUE to find word under/after cursor in the same line. 1516 * For Insert mode completion "dir" is BACKWARD and "curline" is TRUE: move 1517 * to after badly spelled word before the cursor. 1518 * Return 0 if not found, length of the badly spelled word otherwise. 1519 */ 1520 int 1521 spell_move_to( 1522 win_T *wp, 1523 int dir, /* FORWARD or BACKWARD */ 1524 int allwords, /* TRUE for "[s"/"]s", FALSE for "[S"/"]S" */ 1525 int curline, 1526 hlf_T *attrp) /* return: attributes of bad word or NULL 1527 (only when "dir" is FORWARD) */ 1528 { 1529 linenr_T lnum; 1530 pos_T found_pos; 1531 int found_len = 0; 1532 char_u *line; 1533 char_u *p; 1534 char_u *endp; 1535 hlf_T attr; 1536 int len; 1537 #ifdef FEAT_SYN_HL 1538 int has_syntax = syntax_present(wp); 1539 #endif 1540 int col; 1541 int can_spell; 1542 char_u *buf = NULL; 1543 int buflen = 0; 1544 int skip = 0; 1545 int capcol = -1; 1546 int found_one = FALSE; 1547 int wrapped = FALSE; 1548 1549 if (no_spell_checking(wp)) 1550 return 0; 1551 1552 /* 1553 * Start looking for bad word at the start of the line, because we can't 1554 * start halfway a word, we don't know where it starts or ends. 1555 * 1556 * When searching backwards, we continue in the line to find the last 1557 * bad word (in the cursor line: before the cursor). 1558 * 1559 * We concatenate the start of the next line, so that wrapped words work 1560 * (e.g. "et<line-break>cetera"). Doesn't work when searching backwards 1561 * though... 1562 */ 1563 lnum = wp->w_cursor.lnum; 1564 CLEAR_POS(&found_pos); 1565 1566 while (!got_int) 1567 { 1568 line = ml_get_buf(wp->w_buffer, lnum, FALSE); 1569 1570 len = (int)STRLEN(line); 1571 if (buflen < len + MAXWLEN + 2) 1572 { 1573 vim_free(buf); 1574 buflen = len + MAXWLEN + 2; 1575 buf = alloc(buflen); 1576 if (buf == NULL) 1577 break; 1578 } 1579 1580 /* In first line check first word for Capital. */ 1581 if (lnum == 1) 1582 capcol = 0; 1583 1584 /* For checking first word with a capital skip white space. */ 1585 if (capcol == 0) 1586 capcol = getwhitecols(line); 1587 else if (curline && wp == curwin) 1588 { 1589 /* For spellbadword(): check if first word needs a capital. */ 1590 col = getwhitecols(line); 1591 if (check_need_cap(lnum, col)) 1592 capcol = col; 1593 1594 /* Need to get the line again, may have looked at the previous 1595 * one. */ 1596 line = ml_get_buf(wp->w_buffer, lnum, FALSE); 1597 } 1598 1599 /* Copy the line into "buf" and append the start of the next line if 1600 * possible. */ 1601 STRCPY(buf, line); 1602 if (lnum < wp->w_buffer->b_ml.ml_line_count) 1603 spell_cat_line(buf + STRLEN(buf), 1604 ml_get_buf(wp->w_buffer, lnum + 1, FALSE), MAXWLEN); 1605 1606 p = buf + skip; 1607 endp = buf + len; 1608 while (p < endp) 1609 { 1610 /* When searching backward don't search after the cursor. Unless 1611 * we wrapped around the end of the buffer. */ 1612 if (dir == BACKWARD 1613 && lnum == wp->w_cursor.lnum 1614 && !wrapped 1615 && (colnr_T)(p - buf) >= wp->w_cursor.col) 1616 break; 1617 1618 /* start of word */ 1619 attr = HLF_COUNT; 1620 len = spell_check(wp, p, &attr, &capcol, FALSE); 1621 1622 if (attr != HLF_COUNT) 1623 { 1624 /* We found a bad word. Check the attribute. */ 1625 if (allwords || attr == HLF_SPB) 1626 { 1627 /* When searching forward only accept a bad word after 1628 * the cursor. */ 1629 if (dir == BACKWARD 1630 || lnum != wp->w_cursor.lnum 1631 || (lnum == wp->w_cursor.lnum 1632 && (wrapped 1633 || (colnr_T)(curline ? p - buf + len 1634 : p - buf) 1635 > wp->w_cursor.col))) 1636 { 1637 #ifdef FEAT_SYN_HL 1638 if (has_syntax) 1639 { 1640 col = (int)(p - buf); 1641 (void)syn_get_id(wp, lnum, (colnr_T)col, 1642 FALSE, &can_spell, FALSE); 1643 if (!can_spell) 1644 attr = HLF_COUNT; 1645 } 1646 else 1647 #endif 1648 can_spell = TRUE; 1649 1650 if (can_spell) 1651 { 1652 found_one = TRUE; 1653 found_pos.lnum = lnum; 1654 found_pos.col = (int)(p - buf); 1655 found_pos.coladd = 0; 1656 if (dir == FORWARD) 1657 { 1658 /* No need to search further. */ 1659 wp->w_cursor = found_pos; 1660 vim_free(buf); 1661 if (attrp != NULL) 1662 *attrp = attr; 1663 return len; 1664 } 1665 else if (curline) 1666 /* Insert mode completion: put cursor after 1667 * the bad word. */ 1668 found_pos.col += len; 1669 found_len = len; 1670 } 1671 } 1672 else 1673 found_one = TRUE; 1674 } 1675 } 1676 1677 /* advance to character after the word */ 1678 p += len; 1679 capcol -= len; 1680 } 1681 1682 if (dir == BACKWARD && found_pos.lnum != 0) 1683 { 1684 /* Use the last match in the line (before the cursor). */ 1685 wp->w_cursor = found_pos; 1686 vim_free(buf); 1687 return found_len; 1688 } 1689 1690 if (curline) 1691 break; /* only check cursor line */ 1692 1693 /* If we are back at the starting line and searched it again there 1694 * is no match, give up. */ 1695 if (lnum == wp->w_cursor.lnum && wrapped) 1696 break; 1697 1698 /* Advance to next line. */ 1699 if (dir == BACKWARD) 1700 { 1701 if (lnum > 1) 1702 --lnum; 1703 else if (!p_ws) 1704 break; /* at first line and 'nowrapscan' */ 1705 else 1706 { 1707 /* Wrap around to the end of the buffer. May search the 1708 * starting line again and accept the last match. */ 1709 lnum = wp->w_buffer->b_ml.ml_line_count; 1710 wrapped = TRUE; 1711 if (!shortmess(SHM_SEARCH)) 1712 give_warning((char_u *)_(top_bot_msg), TRUE); 1713 } 1714 capcol = -1; 1715 } 1716 else 1717 { 1718 if (lnum < wp->w_buffer->b_ml.ml_line_count) 1719 ++lnum; 1720 else if (!p_ws) 1721 break; /* at first line and 'nowrapscan' */ 1722 else 1723 { 1724 /* Wrap around to the start of the buffer. May search the 1725 * starting line again and accept the first match. */ 1726 lnum = 1; 1727 wrapped = TRUE; 1728 if (!shortmess(SHM_SEARCH)) 1729 give_warning((char_u *)_(bot_top_msg), TRUE); 1730 } 1731 1732 /* If we are back at the starting line and there is no match then 1733 * give up. */ 1734 if (lnum == wp->w_cursor.lnum && !found_one) 1735 break; 1736 1737 /* Skip the characters at the start of the next line that were 1738 * included in a match crossing line boundaries. */ 1739 if (attr == HLF_COUNT) 1740 skip = (int)(p - endp); 1741 else 1742 skip = 0; 1743 1744 /* Capcol skips over the inserted space. */ 1745 --capcol; 1746 1747 /* But after empty line check first word in next line */ 1748 if (*skipwhite(line) == NUL) 1749 capcol = 0; 1750 } 1751 1752 line_breakcheck(); 1753 } 1754 1755 vim_free(buf); 1756 return 0; 1757 } 1758 1759 /* 1760 * For spell checking: concatenate the start of the following line "line" into 1761 * "buf", blanking-out special characters. Copy less then "maxlen" bytes. 1762 * Keep the blanks at the start of the next line, this is used in win_line() 1763 * to skip those bytes if the word was OK. 1764 */ 1765 void 1766 spell_cat_line(char_u *buf, char_u *line, int maxlen) 1767 { 1768 char_u *p; 1769 int n; 1770 1771 p = skipwhite(line); 1772 while (vim_strchr((char_u *)"*#/\"\t", *p) != NULL) 1773 p = skipwhite(p + 1); 1774 1775 if (*p != NUL) 1776 { 1777 /* Only worth concatenating if there is something else than spaces to 1778 * concatenate. */ 1779 n = (int)(p - line) + 1; 1780 if (n < maxlen - 1) 1781 { 1782 vim_memset(buf, ' ', n); 1783 vim_strncpy(buf + n, p, maxlen - 1 - n); 1784 } 1785 } 1786 } 1787 1788 /* 1789 * Structure used for the cookie argument of do_in_runtimepath(). 1790 */ 1791 typedef struct spelload_S 1792 { 1793 char_u sl_lang[MAXWLEN + 1]; /* language name */ 1794 slang_T *sl_slang; /* resulting slang_T struct */ 1795 int sl_nobreak; /* NOBREAK language found */ 1796 } spelload_T; 1797 1798 /* 1799 * Load word list(s) for "lang" from Vim spell file(s). 1800 * "lang" must be the language without the region: e.g., "en". 1801 */ 1802 static void 1803 spell_load_lang(char_u *lang) 1804 { 1805 char_u fname_enc[85]; 1806 int r; 1807 spelload_T sl; 1808 int round; 1809 1810 /* Copy the language name to pass it to spell_load_cb() as a cookie. 1811 * It's truncated when an error is detected. */ 1812 STRCPY(sl.sl_lang, lang); 1813 sl.sl_slang = NULL; 1814 sl.sl_nobreak = FALSE; 1815 1816 /* We may retry when no spell file is found for the language, an 1817 * autocommand may load it then. */ 1818 for (round = 1; round <= 2; ++round) 1819 { 1820 /* 1821 * Find the first spell file for "lang" in 'runtimepath' and load it. 1822 */ 1823 vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, 1824 #ifdef VMS 1825 "spell/%s_%s.spl", 1826 #else 1827 "spell/%s.%s.spl", 1828 #endif 1829 lang, spell_enc()); 1830 r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); 1831 1832 if (r == FAIL && *sl.sl_lang != NUL) 1833 { 1834 /* Try loading the ASCII version. */ 1835 vim_snprintf((char *)fname_enc, sizeof(fname_enc) - 5, 1836 #ifdef VMS 1837 "spell/%s_ascii.spl", 1838 #else 1839 "spell/%s.ascii.spl", 1840 #endif 1841 lang); 1842 r = do_in_runtimepath(fname_enc, 0, spell_load_cb, &sl); 1843 1844 if (r == FAIL && *sl.sl_lang != NUL && round == 1 1845 && apply_autocmds(EVENT_SPELLFILEMISSING, lang, 1846 curbuf->b_fname, FALSE, curbuf)) 1847 continue; 1848 break; 1849 } 1850 break; 1851 } 1852 1853 if (r == FAIL) 1854 { 1855 smsg( 1856 #ifdef VMS 1857 _("Warning: Cannot find word list \"%s_%s.spl\" or \"%s_ascii.spl\""), 1858 #else 1859 _("Warning: Cannot find word list \"%s.%s.spl\" or \"%s.ascii.spl\""), 1860 #endif 1861 lang, spell_enc(), lang); 1862 } 1863 else if (sl.sl_slang != NULL) 1864 { 1865 /* At least one file was loaded, now load ALL the additions. */ 1866 STRCPY(fname_enc + STRLEN(fname_enc) - 3, "add.spl"); 1867 do_in_runtimepath(fname_enc, DIP_ALL, spell_load_cb, &sl); 1868 } 1869 } 1870 1871 /* 1872 * Return the encoding used for spell checking: Use 'encoding', except that we 1873 * use "latin1" for "latin9". And limit to 60 characters (just in case). 1874 */ 1875 char_u * 1876 spell_enc(void) 1877 { 1878 1879 if (STRLEN(p_enc) < 60 && STRCMP(p_enc, "iso-8859-15") != 0) 1880 return p_enc; 1881 return (char_u *)"latin1"; 1882 } 1883 1884 /* 1885 * Get the name of the .spl file for the internal wordlist into 1886 * "fname[MAXPATHL]". 1887 */ 1888 static void 1889 int_wordlist_spl(char_u *fname) 1890 { 1891 vim_snprintf((char *)fname, MAXPATHL, SPL_FNAME_TMPL, 1892 int_wordlist, spell_enc()); 1893 } 1894 1895 /* 1896 * Allocate a new slang_T for language "lang". "lang" can be NULL. 1897 * Caller must fill "sl_next". 1898 */ 1899 slang_T * 1900 slang_alloc(char_u *lang) 1901 { 1902 slang_T *lp; 1903 1904 lp = (slang_T *)alloc_clear(sizeof(slang_T)); 1905 if (lp != NULL) 1906 { 1907 if (lang != NULL) 1908 lp->sl_name = vim_strsave(lang); 1909 ga_init2(&lp->sl_rep, sizeof(fromto_T), 10); 1910 ga_init2(&lp->sl_repsal, sizeof(fromto_T), 10); 1911 lp->sl_compmax = MAXWLEN; 1912 lp->sl_compsylmax = MAXWLEN; 1913 hash_init(&lp->sl_wordcount); 1914 } 1915 1916 return lp; 1917 } 1918 1919 /* 1920 * Free the contents of an slang_T and the structure itself. 1921 */ 1922 void 1923 slang_free(slang_T *lp) 1924 { 1925 vim_free(lp->sl_name); 1926 vim_free(lp->sl_fname); 1927 slang_clear(lp); 1928 vim_free(lp); 1929 } 1930 1931 /* 1932 * Clear an slang_T so that the file can be reloaded. 1933 */ 1934 void 1935 slang_clear(slang_T *lp) 1936 { 1937 garray_T *gap; 1938 fromto_T *ftp; 1939 salitem_T *smp; 1940 int i; 1941 int round; 1942 1943 VIM_CLEAR(lp->sl_fbyts); 1944 VIM_CLEAR(lp->sl_kbyts); 1945 VIM_CLEAR(lp->sl_pbyts); 1946 1947 VIM_CLEAR(lp->sl_fidxs); 1948 VIM_CLEAR(lp->sl_kidxs); 1949 VIM_CLEAR(lp->sl_pidxs); 1950 1951 for (round = 1; round <= 2; ++round) 1952 { 1953 gap = round == 1 ? &lp->sl_rep : &lp->sl_repsal; 1954 while (gap->ga_len > 0) 1955 { 1956 ftp = &((fromto_T *)gap->ga_data)[--gap->ga_len]; 1957 vim_free(ftp->ft_from); 1958 vim_free(ftp->ft_to); 1959 } 1960 ga_clear(gap); 1961 } 1962 1963 gap = &lp->sl_sal; 1964 if (lp->sl_sofo) 1965 { 1966 /* "ga_len" is set to 1 without adding an item for latin1 */ 1967 if (gap->ga_data != NULL) 1968 /* SOFOFROM and SOFOTO items: free lists of wide characters. */ 1969 for (i = 0; i < gap->ga_len; ++i) 1970 vim_free(((int **)gap->ga_data)[i]); 1971 } 1972 else 1973 /* SAL items: free salitem_T items */ 1974 while (gap->ga_len > 0) 1975 { 1976 smp = &((salitem_T *)gap->ga_data)[--gap->ga_len]; 1977 vim_free(smp->sm_lead); 1978 /* Don't free sm_oneof and sm_rules, they point into sm_lead. */ 1979 vim_free(smp->sm_to); 1980 vim_free(smp->sm_lead_w); 1981 vim_free(smp->sm_oneof_w); 1982 vim_free(smp->sm_to_w); 1983 } 1984 ga_clear(gap); 1985 1986 for (i = 0; i < lp->sl_prefixcnt; ++i) 1987 vim_regfree(lp->sl_prefprog[i]); 1988 lp->sl_prefixcnt = 0; 1989 VIM_CLEAR(lp->sl_prefprog); 1990 1991 VIM_CLEAR(lp->sl_info); 1992 1993 VIM_CLEAR(lp->sl_midword); 1994 1995 vim_regfree(lp->sl_compprog); 1996 lp->sl_compprog = NULL; 1997 VIM_CLEAR(lp->sl_comprules); 1998 VIM_CLEAR(lp->sl_compstartflags); 1999 VIM_CLEAR(lp->sl_compallflags); 2000 2001 VIM_CLEAR(lp->sl_syllable); 2002 ga_clear(&lp->sl_syl_items); 2003 2004 ga_clear_strings(&lp->sl_comppat); 2005 2006 hash_clear_all(&lp->sl_wordcount, WC_KEY_OFF); 2007 hash_init(&lp->sl_wordcount); 2008 2009 hash_clear_all(&lp->sl_map_hash, 0); 2010 2011 /* Clear info from .sug file. */ 2012 slang_clear_sug(lp); 2013 2014 lp->sl_compmax = MAXWLEN; 2015 lp->sl_compminlen = 0; 2016 lp->sl_compsylmax = MAXWLEN; 2017 lp->sl_regions[0] = NUL; 2018 } 2019 2020 /* 2021 * Clear the info from the .sug file in "lp". 2022 */ 2023 void 2024 slang_clear_sug(slang_T *lp) 2025 { 2026 VIM_CLEAR(lp->sl_sbyts); 2027 VIM_CLEAR(lp->sl_sidxs); 2028 close_spellbuf(lp->sl_sugbuf); 2029 lp->sl_sugbuf = NULL; 2030 lp->sl_sugloaded = FALSE; 2031 lp->sl_sugtime = 0; 2032 } 2033 2034 /* 2035 * Load one spell file and store the info into a slang_T. 2036 * Invoked through do_in_runtimepath(). 2037 */ 2038 static void 2039 spell_load_cb(char_u *fname, void *cookie) 2040 { 2041 spelload_T *slp = (spelload_T *)cookie; 2042 slang_T *slang; 2043 2044 slang = spell_load_file(fname, slp->sl_lang, NULL, FALSE); 2045 if (slang != NULL) 2046 { 2047 /* When a previously loaded file has NOBREAK also use it for the 2048 * ".add" files. */ 2049 if (slp->sl_nobreak && slang->sl_add) 2050 slang->sl_nobreak = TRUE; 2051 else if (slang->sl_nobreak) 2052 slp->sl_nobreak = TRUE; 2053 2054 slp->sl_slang = slang; 2055 } 2056 } 2057 2058 2059 /* 2060 * Add a word to the hashtable of common words. 2061 * If it's already there then the counter is increased. 2062 */ 2063 void 2064 count_common_word( 2065 slang_T *lp, 2066 char_u *word, 2067 int len, /* word length, -1 for upto NUL */ 2068 int count) /* 1 to count once, 10 to init */ 2069 { 2070 hash_T hash; 2071 hashitem_T *hi; 2072 wordcount_T *wc; 2073 char_u buf[MAXWLEN]; 2074 char_u *p; 2075 2076 if (len == -1) 2077 p = word; 2078 else 2079 { 2080 vim_strncpy(buf, word, len); 2081 p = buf; 2082 } 2083 2084 hash = hash_hash(p); 2085 hi = hash_lookup(&lp->sl_wordcount, p, hash); 2086 if (HASHITEM_EMPTY(hi)) 2087 { 2088 wc = (wordcount_T *)alloc((unsigned)(sizeof(wordcount_T) + STRLEN(p))); 2089 if (wc == NULL) 2090 return; 2091 STRCPY(wc->wc_word, p); 2092 wc->wc_count = count; 2093 hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash); 2094 } 2095 else 2096 { 2097 wc = HI2WC(hi); 2098 if ((wc->wc_count += count) < (unsigned)count) /* check for overflow */ 2099 wc->wc_count = MAXWORDCOUNT; 2100 } 2101 } 2102 2103 /* 2104 * Adjust the score of common words. 2105 */ 2106 static int 2107 score_wordcount_adj( 2108 slang_T *slang, 2109 int score, 2110 char_u *word, 2111 int split) /* word was split, less bonus */ 2112 { 2113 hashitem_T *hi; 2114 wordcount_T *wc; 2115 int bonus; 2116 int newscore; 2117 2118 hi = hash_find(&slang->sl_wordcount, word); 2119 if (!HASHITEM_EMPTY(hi)) 2120 { 2121 wc = HI2WC(hi); 2122 if (wc->wc_count < SCORE_THRES2) 2123 bonus = SCORE_COMMON1; 2124 else if (wc->wc_count < SCORE_THRES3) 2125 bonus = SCORE_COMMON2; 2126 else 2127 bonus = SCORE_COMMON3; 2128 if (split) 2129 newscore = score - bonus / 2; 2130 else 2131 newscore = score - bonus; 2132 if (newscore < 0) 2133 return 0; 2134 return newscore; 2135 } 2136 return score; 2137 } 2138 2139 2140 /* 2141 * Return TRUE if byte "n" appears in "str". 2142 * Like strchr() but independent of locale. 2143 */ 2144 int 2145 byte_in_str(char_u *str, int n) 2146 { 2147 char_u *p; 2148 2149 for (p = str; *p != NUL; ++p) 2150 if (*p == n) 2151 return TRUE; 2152 return FALSE; 2153 } 2154 2155 #define SY_MAXLEN 30 2156 typedef struct syl_item_S 2157 { 2158 char_u sy_chars[SY_MAXLEN]; /* the sequence of chars */ 2159 int sy_len; 2160 } syl_item_T; 2161 2162 /* 2163 * Truncate "slang->sl_syllable" at the first slash and put the following items 2164 * in "slang->sl_syl_items". 2165 */ 2166 int 2167 init_syl_tab(slang_T *slang) 2168 { 2169 char_u *p; 2170 char_u *s; 2171 int l; 2172 syl_item_T *syl; 2173 2174 ga_init2(&slang->sl_syl_items, sizeof(syl_item_T), 4); 2175 p = vim_strchr(slang->sl_syllable, '/'); 2176 while (p != NULL) 2177 { 2178 *p++ = NUL; 2179 if (*p == NUL) /* trailing slash */ 2180 break; 2181 s = p; 2182 p = vim_strchr(p, '/'); 2183 if (p == NULL) 2184 l = (int)STRLEN(s); 2185 else 2186 l = (int)(p - s); 2187 if (l >= SY_MAXLEN) 2188 return SP_FORMERROR; 2189 if (ga_grow(&slang->sl_syl_items, 1) == FAIL) 2190 return SP_OTHERERROR; 2191 syl = ((syl_item_T *)slang->sl_syl_items.ga_data) 2192 + slang->sl_syl_items.ga_len++; 2193 vim_strncpy(syl->sy_chars, s, l); 2194 syl->sy_len = l; 2195 } 2196 return OK; 2197 } 2198 2199 /* 2200 * Count the number of syllables in "word". 2201 * When "word" contains spaces the syllables after the last space are counted. 2202 * Returns zero if syllables are not defines. 2203 */ 2204 static int 2205 count_syllables(slang_T *slang, char_u *word) 2206 { 2207 int cnt = 0; 2208 int skip = FALSE; 2209 char_u *p; 2210 int len; 2211 int i; 2212 syl_item_T *syl; 2213 int c; 2214 2215 if (slang->sl_syllable == NULL) 2216 return 0; 2217 2218 for (p = word; *p != NUL; p += len) 2219 { 2220 /* When running into a space reset counter. */ 2221 if (*p == ' ') 2222 { 2223 len = 1; 2224 cnt = 0; 2225 continue; 2226 } 2227 2228 /* Find longest match of syllable items. */ 2229 len = 0; 2230 for (i = 0; i < slang->sl_syl_items.ga_len; ++i) 2231 { 2232 syl = ((syl_item_T *)slang->sl_syl_items.ga_data) + i; 2233 if (syl->sy_len > len 2234 && STRNCMP(p, syl->sy_chars, syl->sy_len) == 0) 2235 len = syl->sy_len; 2236 } 2237 if (len != 0) /* found a match, count syllable */ 2238 { 2239 ++cnt; 2240 skip = FALSE; 2241 } 2242 else 2243 { 2244 /* No recognized syllable item, at least a syllable char then? */ 2245 c = mb_ptr2char(p); 2246 len = (*mb_ptr2len)(p); 2247 if (vim_strchr(slang->sl_syllable, c) == NULL) 2248 skip = FALSE; /* No, search for next syllable */ 2249 else if (!skip) 2250 { 2251 ++cnt; /* Yes, count it */ 2252 skip = TRUE; /* don't count following syllable chars */ 2253 } 2254 } 2255 } 2256 return cnt; 2257 } 2258 2259 /* 2260 * Parse 'spelllang' and set w_s->b_langp accordingly. 2261 * Returns NULL if it's OK, an error message otherwise. 2262 */ 2263 char * 2264 did_set_spelllang(win_T *wp) 2265 { 2266 garray_T ga; 2267 char_u *splp; 2268 char_u *region; 2269 char_u region_cp[3]; 2270 int filename; 2271 int region_mask; 2272 slang_T *slang; 2273 int c; 2274 char_u lang[MAXWLEN + 1]; 2275 char_u spf_name[MAXPATHL]; 2276 int len; 2277 char_u *p; 2278 int round; 2279 char_u *spf; 2280 char_u *use_region = NULL; 2281 int dont_use_region = FALSE; 2282 int nobreak = FALSE; 2283 int i, j; 2284 langp_T *lp, *lp2; 2285 static int recursive = FALSE; 2286 char *ret_msg = NULL; 2287 char_u *spl_copy; 2288 bufref_T bufref; 2289 2290 set_bufref(&bufref, wp->w_buffer); 2291 2292 /* We don't want to do this recursively. May happen when a language is 2293 * not available and the SpellFileMissing autocommand opens a new buffer 2294 * in which 'spell' is set. */ 2295 if (recursive) 2296 return NULL; 2297 recursive = TRUE; 2298 2299 ga_init2(&ga, sizeof(langp_T), 2); 2300 clear_midword(wp); 2301 2302 /* Make a copy of 'spelllang', the SpellFileMissing autocommands may change 2303 * it under our fingers. */ 2304 spl_copy = vim_strsave(wp->w_s->b_p_spl); 2305 if (spl_copy == NULL) 2306 goto theend; 2307 2308 wp->w_s->b_cjk = 0; 2309 2310 /* Loop over comma separated language names. */ 2311 for (splp = spl_copy; *splp != NUL; ) 2312 { 2313 /* Get one language name. */ 2314 copy_option_part(&splp, lang, MAXWLEN, ","); 2315 region = NULL; 2316 len = (int)STRLEN(lang); 2317 2318 if (STRCMP(lang, "cjk") == 0) 2319 { 2320 wp->w_s->b_cjk = 1; 2321 continue; 2322 } 2323 2324 /* If the name ends in ".spl" use it as the name of the spell file. 2325 * If there is a region name let "region" point to it and remove it 2326 * from the name. */ 2327 if (len > 4 && fnamecmp(lang + len - 4, ".spl") == 0) 2328 { 2329 filename = TRUE; 2330 2331 /* Locate a region and remove it from the file name. */ 2332 p = vim_strchr(gettail(lang), '_'); 2333 if (p != NULL && ASCII_ISALPHA(p[1]) && ASCII_ISALPHA(p[2]) 2334 && !ASCII_ISALPHA(p[3])) 2335 { 2336 vim_strncpy(region_cp, p + 1, 2); 2337 mch_memmove(p, p + 3, len - (p - lang) - 2); 2338 region = region_cp; 2339 } 2340 else 2341 dont_use_region = TRUE; 2342 2343 /* Check if we loaded this language before. */ 2344 for (slang = first_lang; slang != NULL; slang = slang->sl_next) 2345 if (fullpathcmp(lang, slang->sl_fname, FALSE) == FPC_SAME) 2346 break; 2347 } 2348 else 2349 { 2350 filename = FALSE; 2351 if (len > 3 && lang[len - 3] == '_') 2352 { 2353 region = lang + len - 2; 2354 len -= 3; 2355 lang[len] = NUL; 2356 } 2357 else 2358 dont_use_region = TRUE; 2359 2360 /* Check if we loaded this language before. */ 2361 for (slang = first_lang; slang != NULL; slang = slang->sl_next) 2362 if (STRICMP(lang, slang->sl_name) == 0) 2363 break; 2364 } 2365 2366 if (region != NULL) 2367 { 2368 /* If the region differs from what was used before then don't 2369 * use it for 'spellfile'. */ 2370 if (use_region != NULL && STRCMP(region, use_region) != 0) 2371 dont_use_region = TRUE; 2372 use_region = region; 2373 } 2374 2375 /* If not found try loading the language now. */ 2376 if (slang == NULL) 2377 { 2378 if (filename) 2379 (void)spell_load_file(lang, lang, NULL, FALSE); 2380 else 2381 { 2382 spell_load_lang(lang); 2383 /* SpellFileMissing autocommands may do anything, including 2384 * destroying the buffer we are using... */ 2385 if (!bufref_valid(&bufref)) 2386 { 2387 ret_msg = N_("E797: SpellFileMissing autocommand deleted buffer"); 2388 goto theend; 2389 } 2390 } 2391 } 2392 2393 /* 2394 * Loop over the languages, there can be several files for "lang". 2395 */ 2396 for (slang = first_lang; slang != NULL; slang = slang->sl_next) 2397 if (filename ? fullpathcmp(lang, slang->sl_fname, FALSE) == FPC_SAME 2398 : STRICMP(lang, slang->sl_name) == 0) 2399 { 2400 region_mask = REGION_ALL; 2401 if (!filename && region != NULL) 2402 { 2403 /* find region in sl_regions */ 2404 c = find_region(slang->sl_regions, region); 2405 if (c == REGION_ALL) 2406 { 2407 if (slang->sl_add) 2408 { 2409 if (*slang->sl_regions != NUL) 2410 /* This addition file is for other regions. */ 2411 region_mask = 0; 2412 } 2413 else 2414 /* This is probably an error. Give a warning and 2415 * accept the words anyway. */ 2416 smsg(_("Warning: region %s not supported"), 2417 region); 2418 } 2419 else 2420 region_mask = 1 << c; 2421 } 2422 2423 if (region_mask != 0) 2424 { 2425 if (ga_grow(&ga, 1) == FAIL) 2426 { 2427 ga_clear(&ga); 2428 ret_msg = e_outofmem; 2429 goto theend; 2430 } 2431 LANGP_ENTRY(ga, ga.ga_len)->lp_slang = slang; 2432 LANGP_ENTRY(ga, ga.ga_len)->lp_region = region_mask; 2433 ++ga.ga_len; 2434 use_midword(slang, wp); 2435 if (slang->sl_nobreak) 2436 nobreak = TRUE; 2437 } 2438 } 2439 } 2440 2441 /* round 0: load int_wordlist, if possible. 2442 * round 1: load first name in 'spellfile'. 2443 * round 2: load second name in 'spellfile. 2444 * etc. */ 2445 spf = curwin->w_s->b_p_spf; 2446 for (round = 0; round == 0 || *spf != NUL; ++round) 2447 { 2448 if (round == 0) 2449 { 2450 /* Internal wordlist, if there is one. */ 2451 if (int_wordlist == NULL) 2452 continue; 2453 int_wordlist_spl(spf_name); 2454 } 2455 else 2456 { 2457 /* One entry in 'spellfile'. */ 2458 copy_option_part(&spf, spf_name, MAXPATHL - 5, ","); 2459 STRCAT(spf_name, ".spl"); 2460 2461 /* If it was already found above then skip it. */ 2462 for (c = 0; c < ga.ga_len; ++c) 2463 { 2464 p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname; 2465 if (p != NULL && fullpathcmp(spf_name, p, FALSE) == FPC_SAME) 2466 break; 2467 } 2468 if (c < ga.ga_len) 2469 continue; 2470 } 2471 2472 /* Check if it was loaded already. */ 2473 for (slang = first_lang; slang != NULL; slang = slang->sl_next) 2474 if (fullpathcmp(spf_name, slang->sl_fname, FALSE) == FPC_SAME) 2475 break; 2476 if (slang == NULL) 2477 { 2478 /* Not loaded, try loading it now. The language name includes the 2479 * region name, the region is ignored otherwise. for int_wordlist 2480 * use an arbitrary name. */ 2481 if (round == 0) 2482 STRCPY(lang, "internal wordlist"); 2483 else 2484 { 2485 vim_strncpy(lang, gettail(spf_name), MAXWLEN); 2486 p = vim_strchr(lang, '.'); 2487 if (p != NULL) 2488 *p = NUL; /* truncate at ".encoding.add" */ 2489 } 2490 slang = spell_load_file(spf_name, lang, NULL, TRUE); 2491 2492 /* If one of the languages has NOBREAK we assume the addition 2493 * files also have this. */ 2494 if (slang != NULL && nobreak) 2495 slang->sl_nobreak = TRUE; 2496 } 2497 if (slang != NULL && ga_grow(&ga, 1) == OK) 2498 { 2499 region_mask = REGION_ALL; 2500 if (use_region != NULL && !dont_use_region) 2501 { 2502 /* find region in sl_regions */ 2503 c = find_region(slang->sl_regions, use_region); 2504 if (c != REGION_ALL) 2505 region_mask = 1 << c; 2506 else if (*slang->sl_regions != NUL) 2507 /* This spell file is for other regions. */ 2508 region_mask = 0; 2509 } 2510 2511 if (region_mask != 0) 2512 { 2513 LANGP_ENTRY(ga, ga.ga_len)->lp_slang = slang; 2514 LANGP_ENTRY(ga, ga.ga_len)->lp_sallang = NULL; 2515 LANGP_ENTRY(ga, ga.ga_len)->lp_replang = NULL; 2516 LANGP_ENTRY(ga, ga.ga_len)->lp_region = region_mask; 2517 ++ga.ga_len; 2518 use_midword(slang, wp); 2519 } 2520 } 2521 } 2522 2523 /* Everything is fine, store the new b_langp value. */ 2524 ga_clear(&wp->w_s->b_langp); 2525 wp->w_s->b_langp = ga; 2526 2527 /* For each language figure out what language to use for sound folding and 2528 * REP items. If the language doesn't support it itself use another one 2529 * with the same name. E.g. for "en-math" use "en". */ 2530 for (i = 0; i < ga.ga_len; ++i) 2531 { 2532 lp = LANGP_ENTRY(ga, i); 2533 2534 /* sound folding */ 2535 if (lp->lp_slang->sl_sal.ga_len > 0) 2536 /* language does sound folding itself */ 2537 lp->lp_sallang = lp->lp_slang; 2538 else 2539 /* find first similar language that does sound folding */ 2540 for (j = 0; j < ga.ga_len; ++j) 2541 { 2542 lp2 = LANGP_ENTRY(ga, j); 2543 if (lp2->lp_slang->sl_sal.ga_len > 0 2544 && STRNCMP(lp->lp_slang->sl_name, 2545 lp2->lp_slang->sl_name, 2) == 0) 2546 { 2547 lp->lp_sallang = lp2->lp_slang; 2548 break; 2549 } 2550 } 2551 2552 /* REP items */ 2553 if (lp->lp_slang->sl_rep.ga_len > 0) 2554 /* language has REP items itself */ 2555 lp->lp_replang = lp->lp_slang; 2556 else 2557 /* find first similar language that has REP items */ 2558 for (j = 0; j < ga.ga_len; ++j) 2559 { 2560 lp2 = LANGP_ENTRY(ga, j); 2561 if (lp2->lp_slang->sl_rep.ga_len > 0 2562 && STRNCMP(lp->lp_slang->sl_name, 2563 lp2->lp_slang->sl_name, 2) == 0) 2564 { 2565 lp->lp_replang = lp2->lp_slang; 2566 break; 2567 } 2568 } 2569 } 2570 2571 theend: 2572 vim_free(spl_copy); 2573 recursive = FALSE; 2574 redraw_win_later(wp, NOT_VALID); 2575 return ret_msg; 2576 } 2577 2578 /* 2579 * Clear the midword characters for buffer "buf". 2580 */ 2581 static void 2582 clear_midword(win_T *wp) 2583 { 2584 vim_memset(wp->w_s->b_spell_ismw, 0, 256); 2585 VIM_CLEAR(wp->w_s->b_spell_ismw_mb); 2586 } 2587 2588 /* 2589 * Use the "sl_midword" field of language "lp" for buffer "buf". 2590 * They add up to any currently used midword characters. 2591 */ 2592 static void 2593 use_midword(slang_T *lp, win_T *wp) 2594 { 2595 char_u *p; 2596 2597 if (lp->sl_midword == NULL) /* there aren't any */ 2598 return; 2599 2600 for (p = lp->sl_midword; *p != NUL; ) 2601 if (has_mbyte) 2602 { 2603 int c, l, n; 2604 char_u *bp; 2605 2606 c = mb_ptr2char(p); 2607 l = (*mb_ptr2len)(p); 2608 if (c < 256 && l <= 2) 2609 wp->w_s->b_spell_ismw[c] = TRUE; 2610 else if (wp->w_s->b_spell_ismw_mb == NULL) 2611 /* First multi-byte char in "b_spell_ismw_mb". */ 2612 wp->w_s->b_spell_ismw_mb = vim_strnsave(p, l); 2613 else 2614 { 2615 /* Append multi-byte chars to "b_spell_ismw_mb". */ 2616 n = (int)STRLEN(wp->w_s->b_spell_ismw_mb); 2617 bp = vim_strnsave(wp->w_s->b_spell_ismw_mb, n + l); 2618 if (bp != NULL) 2619 { 2620 vim_free(wp->w_s->b_spell_ismw_mb); 2621 wp->w_s->b_spell_ismw_mb = bp; 2622 vim_strncpy(bp + n, p, l); 2623 } 2624 } 2625 p += l; 2626 } 2627 else 2628 wp->w_s->b_spell_ismw[*p++] = TRUE; 2629 } 2630 2631 /* 2632 * Find the region "region[2]" in "rp" (points to "sl_regions"). 2633 * Each region is simply stored as the two characters of its name. 2634 * Returns the index if found (first is 0), REGION_ALL if not found. 2635 */ 2636 static int 2637 find_region(char_u *rp, char_u *region) 2638 { 2639 int i; 2640 2641 for (i = 0; ; i += 2) 2642 { 2643 if (rp[i] == NUL) 2644 return REGION_ALL; 2645 if (rp[i] == region[0] && rp[i + 1] == region[1]) 2646 break; 2647 } 2648 return i / 2; 2649 } 2650 2651 /* 2652 * Return case type of word: 2653 * w word 0 2654 * Word WF_ONECAP 2655 * W WORD WF_ALLCAP 2656 * WoRd wOrd WF_KEEPCAP 2657 */ 2658 int 2659 captype( 2660 char_u *word, 2661 char_u *end) /* When NULL use up to NUL byte. */ 2662 { 2663 char_u *p; 2664 int c; 2665 int firstcap; 2666 int allcap; 2667 int past_second = FALSE; /* past second word char */ 2668 2669 /* find first letter */ 2670 for (p = word; !spell_iswordp_nmw(p, curwin); MB_PTR_ADV(p)) 2671 if (end == NULL ? *p == NUL : p >= end) 2672 return 0; /* only non-word characters, illegal word */ 2673 if (has_mbyte) 2674 c = mb_ptr2char_adv(&p); 2675 else 2676 c = *p++; 2677 firstcap = allcap = SPELL_ISUPPER(c); 2678 2679 /* 2680 * Need to check all letters to find a word with mixed upper/lower. 2681 * But a word with an upper char only at start is a ONECAP. 2682 */ 2683 for ( ; end == NULL ? *p != NUL : p < end; MB_PTR_ADV(p)) 2684 if (spell_iswordp_nmw(p, curwin)) 2685 { 2686 c = PTR2CHAR(p); 2687 if (!SPELL_ISUPPER(c)) 2688 { 2689 /* UUl -> KEEPCAP */ 2690 if (past_second && allcap) 2691 return WF_KEEPCAP; 2692 allcap = FALSE; 2693 } 2694 else if (!allcap) 2695 /* UlU -> KEEPCAP */ 2696 return WF_KEEPCAP; 2697 past_second = TRUE; 2698 } 2699 2700 if (allcap) 2701 return WF_ALLCAP; 2702 if (firstcap) 2703 return WF_ONECAP; 2704 return 0; 2705 } 2706 2707 /* 2708 * Like captype() but for a KEEPCAP word add ONECAP if the word starts with a 2709 * capital. So that make_case_word() can turn WOrd into Word. 2710 * Add ALLCAP for "WOrD". 2711 */ 2712 static int 2713 badword_captype(char_u *word, char_u *end) 2714 { 2715 int flags = captype(word, end); 2716 int c; 2717 int l, u; 2718 int first; 2719 char_u *p; 2720 2721 if (flags & WF_KEEPCAP) 2722 { 2723 /* Count the number of UPPER and lower case letters. */ 2724 l = u = 0; 2725 first = FALSE; 2726 for (p = word; p < end; MB_PTR_ADV(p)) 2727 { 2728 c = PTR2CHAR(p); 2729 if (SPELL_ISUPPER(c)) 2730 { 2731 ++u; 2732 if (p == word) 2733 first = TRUE; 2734 } 2735 else 2736 ++l; 2737 } 2738 2739 /* If there are more UPPER than lower case letters suggest an 2740 * ALLCAP word. Otherwise, if the first letter is UPPER then 2741 * suggest ONECAP. Exception: "ALl" most likely should be "All", 2742 * require three upper case letters. */ 2743 if (u > l && u > 2) 2744 flags |= WF_ALLCAP; 2745 else if (first) 2746 flags |= WF_ONECAP; 2747 2748 if (u >= 2 && l >= 2) /* maCARONI maCAroni */ 2749 flags |= WF_MIXCAP; 2750 } 2751 return flags; 2752 } 2753 2754 /* 2755 * Delete the internal wordlist and its .spl file. 2756 */ 2757 void 2758 spell_delete_wordlist(void) 2759 { 2760 char_u fname[MAXPATHL]; 2761 2762 if (int_wordlist != NULL) 2763 { 2764 mch_remove(int_wordlist); 2765 int_wordlist_spl(fname); 2766 mch_remove(fname); 2767 VIM_CLEAR(int_wordlist); 2768 } 2769 } 2770 2771 /* 2772 * Free all languages. 2773 */ 2774 void 2775 spell_free_all(void) 2776 { 2777 slang_T *slang; 2778 buf_T *buf; 2779 2780 /* Go through all buffers and handle 'spelllang'. <VN> */ 2781 FOR_ALL_BUFFERS(buf) 2782 ga_clear(&buf->b_s.b_langp); 2783 2784 while (first_lang != NULL) 2785 { 2786 slang = first_lang; 2787 first_lang = slang->sl_next; 2788 slang_free(slang); 2789 } 2790 2791 spell_delete_wordlist(); 2792 2793 VIM_CLEAR(repl_to); 2794 VIM_CLEAR(repl_from); 2795 } 2796 2797 /* 2798 * Clear all spelling tables and reload them. 2799 * Used after 'encoding' is set and when ":mkspell" was used. 2800 */ 2801 void 2802 spell_reload(void) 2803 { 2804 win_T *wp; 2805 2806 /* Initialize the table for spell_iswordp(). */ 2807 init_spell_chartab(); 2808 2809 /* Unload all allocated memory. */ 2810 spell_free_all(); 2811 2812 /* Go through all buffers and handle 'spelllang'. */ 2813 FOR_ALL_WINDOWS(wp) 2814 { 2815 /* Only load the wordlists when 'spelllang' is set and there is a 2816 * window for this buffer in which 'spell' is set. */ 2817 if (*wp->w_s->b_p_spl != NUL) 2818 { 2819 if (wp->w_p_spell) 2820 { 2821 (void)did_set_spelllang(wp); 2822 break; 2823 } 2824 } 2825 } 2826 } 2827 2828 /* 2829 * Opposite of offset2bytes(). 2830 * "pp" points to the bytes and is advanced over it. 2831 * Returns the offset. 2832 */ 2833 static int 2834 bytes2offset(char_u **pp) 2835 { 2836 char_u *p = *pp; 2837 int nr; 2838 int c; 2839 2840 c = *p++; 2841 if ((c & 0x80) == 0x00) /* 1 byte */ 2842 { 2843 nr = c - 1; 2844 } 2845 else if ((c & 0xc0) == 0x80) /* 2 bytes */ 2846 { 2847 nr = (c & 0x3f) - 1; 2848 nr = nr * 255 + (*p++ - 1); 2849 } 2850 else if ((c & 0xe0) == 0xc0) /* 3 bytes */ 2851 { 2852 nr = (c & 0x1f) - 1; 2853 nr = nr * 255 + (*p++ - 1); 2854 nr = nr * 255 + (*p++ - 1); 2855 } 2856 else /* 4 bytes */ 2857 { 2858 nr = (c & 0x0f) - 1; 2859 nr = nr * 255 + (*p++ - 1); 2860 nr = nr * 255 + (*p++ - 1); 2861 nr = nr * 255 + (*p++ - 1); 2862 } 2863 2864 *pp = p; 2865 return nr; 2866 } 2867 2868 2869 /* 2870 * Open a spell buffer. This is a nameless buffer that is not in the buffer 2871 * list and only contains text lines. Can use a swapfile to reduce memory 2872 * use. 2873 * Most other fields are invalid! Esp. watch out for string options being 2874 * NULL and there is no undo info. 2875 * Returns NULL when out of memory. 2876 */ 2877 buf_T * 2878 open_spellbuf(void) 2879 { 2880 buf_T *buf; 2881 2882 buf = (buf_T *)alloc_clear(sizeof(buf_T)); 2883 if (buf != NULL) 2884 { 2885 buf->b_spell = TRUE; 2886 buf->b_p_swf = TRUE; /* may create a swap file */ 2887 #ifdef FEAT_CRYPT 2888 buf->b_p_key = empty_option; 2889 #endif 2890 ml_open(buf); 2891 ml_open_file(buf); /* create swap file now */ 2892 } 2893 return buf; 2894 } 2895 2896 /* 2897 * Close the buffer used for spell info. 2898 */ 2899 void 2900 close_spellbuf(buf_T *buf) 2901 { 2902 if (buf != NULL) 2903 { 2904 ml_close(buf, TRUE); 2905 vim_free(buf); 2906 } 2907 } 2908 2909 /* 2910 * Init the chartab used for spelling for ASCII. 2911 * EBCDIC is not supported! 2912 */ 2913 void 2914 clear_spell_chartab(spelltab_T *sp) 2915 { 2916 int i; 2917 2918 /* Init everything to FALSE. */ 2919 vim_memset(sp->st_isw, FALSE, sizeof(sp->st_isw)); 2920 vim_memset(sp->st_isu, FALSE, sizeof(sp->st_isu)); 2921 for (i = 0; i < 256; ++i) 2922 { 2923 sp->st_fold[i] = i; 2924 sp->st_upper[i] = i; 2925 } 2926 2927 /* We include digits. A word shouldn't start with a digit, but handling 2928 * that is done separately. */ 2929 for (i = '0'; i <= '9'; ++i) 2930 sp->st_isw[i] = TRUE; 2931 for (i = 'A'; i <= 'Z'; ++i) 2932 { 2933 sp->st_isw[i] = TRUE; 2934 sp->st_isu[i] = TRUE; 2935 sp->st_fold[i] = i + 0x20; 2936 } 2937 for (i = 'a'; i <= 'z'; ++i) 2938 { 2939 sp->st_isw[i] = TRUE; 2940 sp->st_upper[i] = i - 0x20; 2941 } 2942 } 2943 2944 /* 2945 * Init the chartab used for spelling. Only depends on 'encoding'. 2946 * Called once while starting up and when 'encoding' changes. 2947 * The default is to use isalpha(), but the spell file should define the word 2948 * characters to make it possible that 'encoding' differs from the current 2949 * locale. For utf-8 we don't use isalpha() but our own functions. 2950 */ 2951 void 2952 init_spell_chartab(void) 2953 { 2954 int i; 2955 2956 did_set_spelltab = FALSE; 2957 clear_spell_chartab(&spelltab); 2958 if (enc_dbcs) 2959 { 2960 /* DBCS: assume double-wide characters are word characters. */ 2961 for (i = 128; i <= 255; ++i) 2962 if (MB_BYTE2LEN(i) == 2) 2963 spelltab.st_isw[i] = TRUE; 2964 } 2965 else if (enc_utf8) 2966 { 2967 for (i = 128; i < 256; ++i) 2968 { 2969 int f = utf_fold(i); 2970 int u = utf_toupper(i); 2971 2972 spelltab.st_isu[i] = utf_isupper(i); 2973 spelltab.st_isw[i] = spelltab.st_isu[i] || utf_islower(i); 2974 /* The folded/upper-cased value is different between latin1 and 2975 * utf8 for 0xb5, causing E763 for no good reason. Use the latin1 2976 * value for utf-8 to avoid this. */ 2977 spelltab.st_fold[i] = (f < 256) ? f : i; 2978 spelltab.st_upper[i] = (u < 256) ? u : i; 2979 } 2980 } 2981 else 2982 { 2983 /* Rough guess: use locale-dependent library functions. */ 2984 for (i = 128; i < 256; ++i) 2985 { 2986 if (MB_ISUPPER(i)) 2987 { 2988 spelltab.st_isw[i] = TRUE; 2989 spelltab.st_isu[i] = TRUE; 2990 spelltab.st_fold[i] = MB_TOLOWER(i); 2991 } 2992 else if (MB_ISLOWER(i)) 2993 { 2994 spelltab.st_isw[i] = TRUE; 2995 spelltab.st_upper[i] = MB_TOUPPER(i); 2996 } 2997 } 2998 } 2999 } 3000 3001 3002 /* 3003 * Return TRUE if "p" points to a word character. 3004 * As a special case we see "midword" characters as word character when it is 3005 * followed by a word character. This finds they'there but not 'they there'. 3006 * Thus this only works properly when past the first character of the word. 3007 */ 3008 static int 3009 spell_iswordp( 3010 char_u *p, 3011 win_T *wp) /* buffer used */ 3012 { 3013 char_u *s; 3014 int l; 3015 int c; 3016 3017 if (has_mbyte) 3018 { 3019 l = MB_PTR2LEN(p); 3020 s = p; 3021 if (l == 1) 3022 { 3023 /* be quick for ASCII */ 3024 if (wp->w_s->b_spell_ismw[*p]) 3025 s = p + 1; /* skip a mid-word character */ 3026 } 3027 else 3028 { 3029 c = mb_ptr2char(p); 3030 if (c < 256 ? wp->w_s->b_spell_ismw[c] 3031 : (wp->w_s->b_spell_ismw_mb != NULL 3032 && vim_strchr(wp->w_s->b_spell_ismw_mb, c) != NULL)) 3033 s = p + l; 3034 } 3035 3036 c = mb_ptr2char(s); 3037 if (c > 255) 3038 return spell_mb_isword_class(mb_get_class(s), wp); 3039 return spelltab.st_isw[c]; 3040 } 3041 3042 return spelltab.st_isw[wp->w_s->b_spell_ismw[*p] ? p[1] : p[0]]; 3043 } 3044 3045 /* 3046 * Return TRUE if "p" points to a word character. 3047 * Unlike spell_iswordp() this doesn't check for "midword" characters. 3048 */ 3049 int 3050 spell_iswordp_nmw(char_u *p, win_T *wp) 3051 { 3052 int c; 3053 3054 if (has_mbyte) 3055 { 3056 c = mb_ptr2char(p); 3057 if (c > 255) 3058 return spell_mb_isword_class(mb_get_class(p), wp); 3059 return spelltab.st_isw[c]; 3060 } 3061 return spelltab.st_isw[*p]; 3062 } 3063 3064 /* 3065 * Return TRUE if word class indicates a word character. 3066 * Only for characters above 255. 3067 * Unicode subscript and superscript are not considered word characters. 3068 * See also dbcs_class() and utf_class() in mbyte.c. 3069 */ 3070 static int 3071 spell_mb_isword_class(int cl, win_T *wp) 3072 { 3073 if (wp->w_s->b_cjk) 3074 /* East Asian characters are not considered word characters. */ 3075 return cl == 2 || cl == 0x2800; 3076 return cl >= 2 && cl != 0x2070 && cl != 0x2080; 3077 } 3078 3079 /* 3080 * Return TRUE if "p" points to a word character. 3081 * Wide version of spell_iswordp(). 3082 */ 3083 static int 3084 spell_iswordp_w(int *p, win_T *wp) 3085 { 3086 int *s; 3087 3088 if (*p < 256 ? wp->w_s->b_spell_ismw[*p] 3089 : (wp->w_s->b_spell_ismw_mb != NULL 3090 && vim_strchr(wp->w_s->b_spell_ismw_mb, *p) != NULL)) 3091 s = p + 1; 3092 else 3093 s = p; 3094 3095 if (*s > 255) 3096 { 3097 if (enc_utf8) 3098 return spell_mb_isword_class(utf_class(*s), wp); 3099 if (enc_dbcs) 3100 return spell_mb_isword_class( 3101 dbcs_class((unsigned)*s >> 8, *s & 0xff), wp); 3102 return 0; 3103 } 3104 return spelltab.st_isw[*s]; 3105 } 3106 3107 /* 3108 * Case-fold "str[len]" into "buf[buflen]". The result is NUL terminated. 3109 * Uses the character definitions from the .spl file. 3110 * When using a multi-byte 'encoding' the length may change! 3111 * Returns FAIL when something wrong. 3112 */ 3113 int 3114 spell_casefold( 3115 char_u *str, 3116 int len, 3117 char_u *buf, 3118 int buflen) 3119 { 3120 int i; 3121 3122 if (len >= buflen) 3123 { 3124 buf[0] = NUL; 3125 return FAIL; /* result will not fit */ 3126 } 3127 3128 if (has_mbyte) 3129 { 3130 int outi = 0; 3131 char_u *p; 3132 int c; 3133 3134 /* Fold one character at a time. */ 3135 for (p = str; p < str + len; ) 3136 { 3137 if (outi + MB_MAXBYTES > buflen) 3138 { 3139 buf[outi] = NUL; 3140 return FAIL; 3141 } 3142 c = mb_cptr2char_adv(&p); 3143 outi += mb_char2bytes(SPELL_TOFOLD(c), buf + outi); 3144 } 3145 buf[outi] = NUL; 3146 } 3147 else 3148 { 3149 /* Be quick for non-multibyte encodings. */ 3150 for (i = 0; i < len; ++i) 3151 buf[i] = spelltab.st_fold[str[i]]; 3152 buf[i] = NUL; 3153 } 3154 3155 return OK; 3156 } 3157 3158 /* values for sps_flags */ 3159 #define SPS_BEST 1 3160 #define SPS_FAST 2 3161 #define SPS_DOUBLE 4 3162 3163 static int sps_flags = SPS_BEST; /* flags from 'spellsuggest' */ 3164 static int sps_limit = 9999; /* max nr of suggestions given */ 3165 3166 /* 3167 * Check the 'spellsuggest' option. Return FAIL if it's wrong. 3168 * Sets "sps_flags" and "sps_limit". 3169 */ 3170 int 3171 spell_check_sps(void) 3172 { 3173 char_u *p; 3174 char_u *s; 3175 char_u buf[MAXPATHL]; 3176 int f; 3177 3178 sps_flags = 0; 3179 sps_limit = 9999; 3180 3181 for (p = p_sps; *p != NUL; ) 3182 { 3183 copy_option_part(&p, buf, MAXPATHL, ","); 3184 3185 f = 0; 3186 if (VIM_ISDIGIT(*buf)) 3187 { 3188 s = buf; 3189 sps_limit = getdigits(&s); 3190 if (*s != NUL && !VIM_ISDIGIT(*s)) 3191 f = -1; 3192 } 3193 else if (STRCMP(buf, "best") == 0) 3194 f = SPS_BEST; 3195 else if (STRCMP(buf, "fast") == 0) 3196 f = SPS_FAST; 3197 else if (STRCMP(buf, "double") == 0) 3198 f = SPS_DOUBLE; 3199 else if (STRNCMP(buf, "expr:", 5) != 0 3200 && STRNCMP(buf, "file:", 5) != 0) 3201 f = -1; 3202 3203 if (f == -1 || (sps_flags != 0 && f != 0)) 3204 { 3205 sps_flags = SPS_BEST; 3206 sps_limit = 9999; 3207 return FAIL; 3208 } 3209 if (f != 0) 3210 sps_flags = f; 3211 } 3212 3213 if (sps_flags == 0) 3214 sps_flags = SPS_BEST; 3215 3216 return OK; 3217 } 3218 3219 /* 3220 * "z=": Find badly spelled word under or after the cursor. 3221 * Give suggestions for the properly spelled word. 3222 * In Visual mode use the highlighted word as the bad word. 3223 * When "count" is non-zero use that suggestion. 3224 */ 3225 void 3226 spell_suggest(int count) 3227 { 3228 char_u *line; 3229 pos_T prev_cursor = curwin->w_cursor; 3230 char_u wcopy[MAXWLEN + 2]; 3231 char_u *p; 3232 int i; 3233 int c; 3234 suginfo_T sug; 3235 suggest_T *stp; 3236 int mouse_used; 3237 int need_cap; 3238 int limit; 3239 int selected = count; 3240 int badlen = 0; 3241 int msg_scroll_save = msg_scroll; 3242 3243 if (no_spell_checking(curwin)) 3244 return; 3245 3246 if (VIsual_active) 3247 { 3248 /* Use the Visually selected text as the bad word. But reject 3249 * a multi-line selection. */ 3250 if (curwin->w_cursor.lnum != VIsual.lnum) 3251 { 3252 vim_beep(BO_SPELL); 3253 return; 3254 } 3255 badlen = (int)curwin->w_cursor.col - (int)VIsual.col; 3256 if (badlen < 0) 3257 badlen = -badlen; 3258 else 3259 curwin->w_cursor.col = VIsual.col; 3260 ++badlen; 3261 end_visual_mode(); 3262 } 3263 /* Find the start of the badly spelled word. */ 3264 else if (spell_move_to(curwin, FORWARD, TRUE, TRUE, NULL) == 0 3265 || curwin->w_cursor.col > prev_cursor.col) 3266 { 3267 /* No bad word or it starts after the cursor: use the word under the 3268 * cursor. */ 3269 curwin->w_cursor = prev_cursor; 3270 line = ml_get_curline(); 3271 p = line + curwin->w_cursor.col; 3272 /* Backup to before start of word. */ 3273 while (p > line && spell_iswordp_nmw(p, curwin)) 3274 MB_PTR_BACK(line, p); 3275 /* Forward to start of word. */ 3276 while (*p != NUL && !spell_iswordp_nmw(p, curwin)) 3277 MB_PTR_ADV(p); 3278 3279 if (!spell_iswordp_nmw(p, curwin)) /* No word found. */ 3280 { 3281 beep_flush(); 3282 return; 3283 } 3284 curwin->w_cursor.col = (colnr_T)(p - line); 3285 } 3286 3287 /* Get the word and its length. */ 3288 3289 /* Figure out if the word should be capitalised. */ 3290 need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); 3291 3292 /* Make a copy of current line since autocommands may free the line. */ 3293 line = vim_strsave(ml_get_curline()); 3294 if (line == NULL) 3295 goto skip; 3296 3297 /* Get the list of suggestions. Limit to 'lines' - 2 or the number in 3298 * 'spellsuggest', whatever is smaller. */ 3299 if (sps_limit > (int)Rows - 2) 3300 limit = (int)Rows - 2; 3301 else 3302 limit = sps_limit; 3303 spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, 3304 TRUE, need_cap, TRUE); 3305 3306 if (sug.su_ga.ga_len == 0) 3307 msg(_("Sorry, no suggestions")); 3308 else if (count > 0) 3309 { 3310 if (count > sug.su_ga.ga_len) 3311 smsg(_("Sorry, only %ld suggestions"), 3312 (long)sug.su_ga.ga_len); 3313 } 3314 else 3315 { 3316 VIM_CLEAR(repl_from); 3317 VIM_CLEAR(repl_to); 3318 3319 #ifdef FEAT_RIGHTLEFT 3320 /* When 'rightleft' is set the list is drawn right-left. */ 3321 cmdmsg_rl = curwin->w_p_rl; 3322 if (cmdmsg_rl) 3323 msg_col = Columns - 1; 3324 #endif 3325 3326 /* List the suggestions. */ 3327 msg_start(); 3328 msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ 3329 lines_left = Rows; /* avoid more prompt */ 3330 vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), 3331 sug.su_badlen, sug.su_badptr); 3332 #ifdef FEAT_RIGHTLEFT 3333 if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) 3334 { 3335 /* And now the rabbit from the high hat: Avoid showing the 3336 * untranslated message rightleft. */ 3337 vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", 3338 sug.su_badlen, sug.su_badptr); 3339 } 3340 #endif 3341 msg_puts((char *)IObuff); 3342 msg_clr_eos(); 3343 msg_putchar('\n'); 3344 3345 msg_scroll = TRUE; 3346 for (i = 0; i < sug.su_ga.ga_len; ++i) 3347 { 3348 stp = &SUG(sug.su_ga, i); 3349 3350 /* The suggested word may replace only part of the bad word, add 3351 * the not replaced part. */ 3352 vim_strncpy(wcopy, stp->st_word, MAXWLEN); 3353 if (sug.su_badlen > stp->st_orglen) 3354 vim_strncpy(wcopy + stp->st_wordlen, 3355 sug.su_badptr + stp->st_orglen, 3356 sug.su_badlen - stp->st_orglen); 3357 vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); 3358 #ifdef FEAT_RIGHTLEFT 3359 if (cmdmsg_rl) 3360 rl_mirror(IObuff); 3361 #endif 3362 msg_puts((char *)IObuff); 3363 3364 vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); 3365 msg_puts((char *)IObuff); 3366 3367 /* The word may replace more than "su_badlen". */ 3368 if (sug.su_badlen < stp->st_orglen) 3369 { 3370 vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), 3371 stp->st_orglen, sug.su_badptr); 3372 msg_puts((char *)IObuff); 3373 } 3374 3375 if (p_verbose > 0) 3376 { 3377 /* Add the score. */ 3378 if (sps_flags & (SPS_DOUBLE | SPS_BEST)) 3379 vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", 3380 stp->st_salscore ? "s " : "", 3381 stp->st_score, stp->st_altscore); 3382 else 3383 vim_snprintf((char *)IObuff, IOSIZE, " (%d)", 3384 stp->st_score); 3385 #ifdef FEAT_RIGHTLEFT 3386 if (cmdmsg_rl) 3387 /* Mirror the numbers, but keep the leading space. */ 3388 rl_mirror(IObuff + 1); 3389 #endif 3390 msg_advance(30); 3391 msg_puts((char *)IObuff); 3392 } 3393 msg_putchar('\n'); 3394 } 3395 3396 #ifdef FEAT_RIGHTLEFT 3397 cmdmsg_rl = FALSE; 3398 msg_col = 0; 3399 #endif 3400 /* Ask for choice. */ 3401 selected = prompt_for_number(&mouse_used); 3402 if (mouse_used) 3403 selected -= lines_left; 3404 lines_left = Rows; /* avoid more prompt */ 3405 /* don't delay for 'smd' in normal_cmd() */ 3406 msg_scroll = msg_scroll_save; 3407 } 3408 3409 if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) 3410 { 3411 /* Save the from and to text for :spellrepall. */ 3412 stp = &SUG(sug.su_ga, selected - 1); 3413 if (sug.su_badlen > stp->st_orglen) 3414 { 3415 /* Replacing less than "su_badlen", append the remainder to 3416 * repl_to. */ 3417 repl_from = vim_strnsave(sug.su_badptr, sug.su_badlen); 3418 vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, 3419 sug.su_badlen - stp->st_orglen, 3420 sug.su_badptr + stp->st_orglen); 3421 repl_to = vim_strsave(IObuff); 3422 } 3423 else 3424 { 3425 /* Replacing su_badlen or more, use the whole word. */ 3426 repl_from = vim_strnsave(sug.su_badptr, stp->st_orglen); 3427 repl_to = vim_strsave(stp->st_word); 3428 } 3429 3430 /* Replace the word. */ 3431 p = alloc((unsigned)STRLEN(line) - stp->st_orglen 3432 + stp->st_wordlen + 1); 3433 if (p != NULL) 3434 { 3435 c = (int)(sug.su_badptr - line); 3436 mch_memmove(p, line, c); 3437 STRCPY(p + c, stp->st_word); 3438 STRCAT(p, sug.su_badptr + stp->st_orglen); 3439 ml_replace(curwin->w_cursor.lnum, p, FALSE); 3440 curwin->w_cursor.col = c; 3441 3442 /* For redo we use a change-word command. */ 3443 ResetRedobuff(); 3444 AppendToRedobuff((char_u *)"ciw"); 3445 AppendToRedobuffLit(p + c, 3446 stp->st_wordlen + sug.su_badlen - stp->st_orglen); 3447 AppendCharToRedobuff(ESC); 3448 3449 /* After this "p" may be invalid. */ 3450 changed_bytes(curwin->w_cursor.lnum, c); 3451 } 3452 } 3453 else 3454 curwin->w_cursor = prev_cursor; 3455 3456 spell_find_cleanup(&sug); 3457 skip: 3458 vim_free(line); 3459 } 3460 3461 /* 3462 * Check if the word at line "lnum" column "col" is required to start with a 3463 * capital. This uses 'spellcapcheck' of the current buffer. 3464 */ 3465 static int 3466 check_need_cap(linenr_T lnum, colnr_T col) 3467 { 3468 int need_cap = FALSE; 3469 char_u *line; 3470 char_u *line_copy = NULL; 3471 char_u *p; 3472 colnr_T endcol; 3473 regmatch_T regmatch; 3474 3475 if (curwin->w_s->b_cap_prog == NULL) 3476 return FALSE; 3477 3478 line = ml_get_curline(); 3479 endcol = 0; 3480 if (getwhitecols(line) >= (int)col) 3481 { 3482 /* At start of line, check if previous line is empty or sentence 3483 * ends there. */ 3484 if (lnum == 1) 3485 need_cap = TRUE; 3486 else 3487 { 3488 line = ml_get(lnum - 1); 3489 if (*skipwhite(line) == NUL) 3490 need_cap = TRUE; 3491 else 3492 { 3493 /* Append a space in place of the line break. */ 3494 line_copy = concat_str(line, (char_u *)" "); 3495 line = line_copy; 3496 endcol = (colnr_T)STRLEN(line); 3497 } 3498 } 3499 } 3500 else 3501 endcol = col; 3502 3503 if (endcol > 0) 3504 { 3505 /* Check if sentence ends before the bad word. */ 3506 regmatch.regprog = curwin->w_s->b_cap_prog; 3507 regmatch.rm_ic = FALSE; 3508 p = line + endcol; 3509 for (;;) 3510 { 3511 MB_PTR_BACK(line, p); 3512 if (p == line || spell_iswordp_nmw(p, curwin)) 3513 break; 3514 if (vim_regexec(®match, p, 0) 3515 && regmatch.endp[0] == line + endcol) 3516 { 3517 need_cap = TRUE; 3518 break; 3519 } 3520 } 3521 curwin->w_s->b_cap_prog = regmatch.regprog; 3522 } 3523 3524 vim_free(line_copy); 3525 3526 return need_cap; 3527 } 3528 3529 3530 /* 3531 * ":spellrepall" 3532 */ 3533 void 3534 ex_spellrepall(exarg_T *eap UNUSED) 3535 { 3536 pos_T pos = curwin->w_cursor; 3537 char_u *frompat; 3538 int addlen; 3539 char_u *line; 3540 char_u *p; 3541 int save_ws = p_ws; 3542 linenr_T prev_lnum = 0; 3543 3544 if (repl_from == NULL || repl_to == NULL) 3545 { 3546 emsg(_("E752: No previous spell replacement")); 3547 return; 3548 } 3549 addlen = (int)(STRLEN(repl_to) - STRLEN(repl_from)); 3550 3551 frompat = alloc((unsigned)STRLEN(repl_from) + 7); 3552 if (frompat == NULL) 3553 return; 3554 sprintf((char *)frompat, "\\V\\<%s\\>", repl_from); 3555 p_ws = FALSE; 3556 3557 sub_nsubs = 0; 3558 sub_nlines = 0; 3559 curwin->w_cursor.lnum = 0; 3560 while (!got_int) 3561 { 3562 if (do_search(NULL, '/', frompat, 1L, SEARCH_KEEP, NULL, NULL) == 0 3563 || u_save_cursor() == FAIL) 3564 break; 3565 3566 /* Only replace when the right word isn't there yet. This happens 3567 * when changing "etc" to "etc.". */ 3568 line = ml_get_curline(); 3569 if (addlen <= 0 || STRNCMP(line + curwin->w_cursor.col, 3570 repl_to, STRLEN(repl_to)) != 0) 3571 { 3572 p = alloc((unsigned)STRLEN(line) + addlen + 1); 3573 if (p == NULL) 3574 break; 3575 mch_memmove(p, line, curwin->w_cursor.col); 3576 STRCPY(p + curwin->w_cursor.col, repl_to); 3577 STRCAT(p, line + curwin->w_cursor.col + STRLEN(repl_from)); 3578 ml_replace(curwin->w_cursor.lnum, p, FALSE); 3579 changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); 3580 3581 if (curwin->w_cursor.lnum != prev_lnum) 3582 { 3583 ++sub_nlines; 3584 prev_lnum = curwin->w_cursor.lnum; 3585 } 3586 ++sub_nsubs; 3587 } 3588 curwin->w_cursor.col += (colnr_T)STRLEN(repl_to); 3589 } 3590 3591 p_ws = save_ws; 3592 curwin->w_cursor = pos; 3593 vim_free(frompat); 3594 3595 if (sub_nsubs == 0) 3596 semsg(_("E753: Not found: %s"), repl_from); 3597 else 3598 do_sub_msg(FALSE); 3599 } 3600 3601 /* 3602 * Find spell suggestions for "word". Return them in the growarray "*gap" as 3603 * a list of allocated strings. 3604 */ 3605 void 3606 spell_suggest_list( 3607 garray_T *gap, 3608 char_u *word, 3609 int maxcount, /* maximum nr of suggestions */ 3610 int need_cap, /* 'spellcapcheck' matched */ 3611 int interactive) 3612 { 3613 suginfo_T sug; 3614 int i; 3615 suggest_T *stp; 3616 char_u *wcopy; 3617 3618 spell_find_suggest(word, 0, &sug, maxcount, FALSE, need_cap, interactive); 3619 3620 /* Make room in "gap". */ 3621 ga_init2(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); 3622 if (ga_grow(gap, sug.su_ga.ga_len) == OK) 3623 { 3624 for (i = 0; i < sug.su_ga.ga_len; ++i) 3625 { 3626 stp = &SUG(sug.su_ga, i); 3627 3628 /* The suggested word may replace only part of "word", add the not 3629 * replaced part. */ 3630 wcopy = alloc(stp->st_wordlen 3631 + (unsigned)STRLEN(sug.su_badptr + stp->st_orglen) + 1); 3632 if (wcopy == NULL) 3633 break; 3634 STRCPY(wcopy, stp->st_word); 3635 STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); 3636 ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; 3637 } 3638 } 3639 3640 spell_find_cleanup(&sug); 3641 } 3642 3643 /* 3644 * Find spell suggestions for the word at the start of "badptr". 3645 * Return the suggestions in "su->su_ga". 3646 * The maximum number of suggestions is "maxcount". 3647 * Note: does use info for the current window. 3648 * This is based on the mechanisms of Aspell, but completely reimplemented. 3649 */ 3650 static void 3651 spell_find_suggest( 3652 char_u *badptr, 3653 int badlen, /* length of bad word or 0 if unknown */ 3654 suginfo_T *su, 3655 int maxcount, 3656 int banbadword, /* don't include badword in suggestions */ 3657 int need_cap, /* word should start with capital */ 3658 int interactive) 3659 { 3660 hlf_T attr = HLF_COUNT; 3661 char_u buf[MAXPATHL]; 3662 char_u *p; 3663 int do_combine = FALSE; 3664 char_u *sps_copy; 3665 #ifdef FEAT_EVAL 3666 static int expr_busy = FALSE; 3667 #endif 3668 int c; 3669 int i; 3670 langp_T *lp; 3671 3672 /* 3673 * Set the info in "*su". 3674 */ 3675 vim_memset(su, 0, sizeof(suginfo_T)); 3676 ga_init2(&su->su_ga, (int)sizeof(suggest_T), 10); 3677 ga_init2(&su->su_sga, (int)sizeof(suggest_T), 10); 3678 if (*badptr == NUL) 3679 return; 3680 hash_init(&su->su_banned); 3681 3682 su->su_badptr = badptr; 3683 if (badlen != 0) 3684 su->su_badlen = badlen; 3685 else 3686 su->su_badlen = spell_check(curwin, su->su_badptr, &attr, NULL, FALSE); 3687 su->su_maxcount = maxcount; 3688 su->su_maxscore = SCORE_MAXINIT; 3689 3690 if (su->su_badlen >= MAXWLEN) 3691 su->su_badlen = MAXWLEN - 1; /* just in case */ 3692 vim_strncpy(su->su_badword, su->su_badptr, su->su_badlen); 3693 (void)spell_casefold(su->su_badptr, su->su_badlen, 3694 su->su_fbadword, MAXWLEN); 3695 /* TODO: make this work if the case-folded text is longer than the original 3696 * text. Currently an illegal byte causes wrong pointer computations. */ 3697 su->su_fbadword[su->su_badlen] = NUL; 3698 3699 /* get caps flags for bad word */ 3700 su->su_badflags = badword_captype(su->su_badptr, 3701 su->su_badptr + su->su_badlen); 3702 if (need_cap) 3703 su->su_badflags |= WF_ONECAP; 3704 3705 /* Find the default language for sound folding. We simply use the first 3706 * one in 'spelllang' that supports sound folding. That's good for when 3707 * using multiple files for one language, it's not that bad when mixing 3708 * languages (e.g., "pl,en"). */ 3709 for (i = 0; i < curbuf->b_s.b_langp.ga_len; ++i) 3710 { 3711 lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); 3712 if (lp->lp_sallang != NULL) 3713 { 3714 su->su_sallang = lp->lp_sallang; 3715 break; 3716 } 3717 } 3718 3719 /* Soundfold the bad word with the default sound folding, so that we don't 3720 * have to do this many times. */ 3721 if (su->su_sallang != NULL) 3722 spell_soundfold(su->su_sallang, su->su_fbadword, TRUE, 3723 su->su_sal_badword); 3724 3725 /* If the word is not capitalised and spell_check() doesn't consider the 3726 * word to be bad then it might need to be capitalised. Add a suggestion 3727 * for that. */ 3728 c = PTR2CHAR(su->su_badptr); 3729 if (!SPELL_ISUPPER(c) && attr == HLF_COUNT) 3730 { 3731 make_case_word(su->su_badword, buf, WF_ONECAP); 3732 add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE, 3733 0, TRUE, su->su_sallang, FALSE); 3734 } 3735 3736 /* Ban the bad word itself. It may appear in another region. */ 3737 if (banbadword) 3738 add_banned(su, su->su_badword); 3739 3740 /* Make a copy of 'spellsuggest', because the expression may change it. */ 3741 sps_copy = vim_strsave(p_sps); 3742 if (sps_copy == NULL) 3743 return; 3744 3745 /* Loop over the items in 'spellsuggest'. */ 3746 for (p = sps_copy; *p != NUL; ) 3747 { 3748 copy_option_part(&p, buf, MAXPATHL, ","); 3749 3750 if (STRNCMP(buf, "expr:", 5) == 0) 3751 { 3752 #ifdef FEAT_EVAL 3753 /* Evaluate an expression. Skip this when called recursively, 3754 * when using spellsuggest() in the expression. */ 3755 if (!expr_busy) 3756 { 3757 expr_busy = TRUE; 3758 spell_suggest_expr(su, buf + 5); 3759 expr_busy = FALSE; 3760 } 3761 #endif 3762 } 3763 else if (STRNCMP(buf, "file:", 5) == 0) 3764 /* Use list of suggestions in a file. */ 3765 spell_suggest_file(su, buf + 5); 3766 else 3767 { 3768 /* Use internal method. */ 3769 spell_suggest_intern(su, interactive); 3770 if (sps_flags & SPS_DOUBLE) 3771 do_combine = TRUE; 3772 } 3773 } 3774 3775 vim_free(sps_copy); 3776 3777 if (do_combine) 3778 /* Combine the two list of suggestions. This must be done last, 3779 * because sorting changes the order again. */ 3780 score_combine(su); 3781 } 3782 3783 #ifdef FEAT_EVAL 3784 /* 3785 * Find suggestions by evaluating expression "expr". 3786 */ 3787 static void 3788 spell_suggest_expr(suginfo_T *su, char_u *expr) 3789 { 3790 list_T *list; 3791 listitem_T *li; 3792 int score; 3793 char_u *p; 3794 3795 /* The work is split up in a few parts to avoid having to export 3796 * suginfo_T. 3797 * First evaluate the expression and get the resulting list. */ 3798 list = eval_spell_expr(su->su_badword, expr); 3799 if (list != NULL) 3800 { 3801 /* Loop over the items in the list. */ 3802 for (li = list->lv_first; li != NULL; li = li->li_next) 3803 if (li->li_tv.v_type == VAR_LIST) 3804 { 3805 /* Get the word and the score from the items. */ 3806 score = get_spellword(li->li_tv.vval.v_list, &p); 3807 if (score >= 0 && score <= su->su_maxscore) 3808 add_suggestion(su, &su->su_ga, p, su->su_badlen, 3809 score, 0, TRUE, su->su_sallang, FALSE); 3810 } 3811 list_unref(list); 3812 } 3813 3814 /* Remove bogus suggestions, sort and truncate at "maxcount". */ 3815 check_suggestions(su, &su->su_ga); 3816 (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); 3817 } 3818 #endif 3819 3820 /* 3821 * Find suggestions in file "fname". Used for "file:" in 'spellsuggest'. 3822 */ 3823 static void 3824 spell_suggest_file(suginfo_T *su, char_u *fname) 3825 { 3826 FILE *fd; 3827 char_u line[MAXWLEN * 2]; 3828 char_u *p; 3829 int len; 3830 char_u cword[MAXWLEN]; 3831 3832 /* Open the file. */ 3833 fd = mch_fopen((char *)fname, "r"); 3834 if (fd == NULL) 3835 { 3836 semsg(_(e_notopen), fname); 3837 return; 3838 } 3839 3840 /* Read it line by line. */ 3841 while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int) 3842 { 3843 line_breakcheck(); 3844 3845 p = vim_strchr(line, '/'); 3846 if (p == NULL) 3847 continue; /* No Tab found, just skip the line. */ 3848 *p++ = NUL; 3849 if (STRICMP(su->su_badword, line) == 0) 3850 { 3851 /* Match! Isolate the good word, until CR or NL. */ 3852 for (len = 0; p[len] >= ' '; ++len) 3853 ; 3854 p[len] = NUL; 3855 3856 /* If the suggestion doesn't have specific case duplicate the case 3857 * of the bad word. */ 3858 if (captype(p, NULL) == 0) 3859 { 3860 make_case_word(p, cword, su->su_badflags); 3861 p = cword; 3862 } 3863 3864 add_suggestion(su, &su->su_ga, p, su->su_badlen, 3865 SCORE_FILE, 0, TRUE, su->su_sallang, FALSE); 3866 } 3867 } 3868 3869 fclose(fd); 3870 3871 /* Remove bogus suggestions, sort and truncate at "maxcount". */ 3872 check_suggestions(su, &su->su_ga); 3873 (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); 3874 } 3875 3876 /* 3877 * Find suggestions for the internal method indicated by "sps_flags". 3878 */ 3879 static void 3880 spell_suggest_intern(suginfo_T *su, int interactive) 3881 { 3882 /* 3883 * Load the .sug file(s) that are available and not done yet. 3884 */ 3885 suggest_load_files(); 3886 3887 /* 3888 * 1. Try special cases, such as repeating a word: "the the" -> "the". 3889 * 3890 * Set a maximum score to limit the combination of operations that is 3891 * tried. 3892 */ 3893 suggest_try_special(su); 3894 3895 /* 3896 * 2. Try inserting/deleting/swapping/changing a letter, use REP entries 3897 * from the .aff file and inserting a space (split the word). 3898 */ 3899 suggest_try_change(su); 3900 3901 /* For the resulting top-scorers compute the sound-a-like score. */ 3902 if (sps_flags & SPS_DOUBLE) 3903 score_comp_sal(su); 3904 3905 /* 3906 * 3. Try finding sound-a-like words. 3907 */ 3908 if ((sps_flags & SPS_FAST) == 0) 3909 { 3910 if (sps_flags & SPS_BEST) 3911 /* Adjust the word score for the suggestions found so far for how 3912 * they sounds like. */ 3913 rescore_suggestions(su); 3914 3915 /* 3916 * While going through the soundfold tree "su_maxscore" is the score 3917 * for the soundfold word, limits the changes that are being tried, 3918 * and "su_sfmaxscore" the rescored score, which is set by 3919 * cleanup_suggestions(). 3920 * First find words with a small edit distance, because this is much 3921 * faster and often already finds the top-N suggestions. If we didn't 3922 * find many suggestions try again with a higher edit distance. 3923 * "sl_sounddone" is used to avoid doing the same word twice. 3924 */ 3925 suggest_try_soundalike_prep(); 3926 su->su_maxscore = SCORE_SFMAX1; 3927 su->su_sfmaxscore = SCORE_MAXINIT * 3; 3928 suggest_try_soundalike(su); 3929 if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) 3930 { 3931 /* We didn't find enough matches, try again, allowing more 3932 * changes to the soundfold word. */ 3933 su->su_maxscore = SCORE_SFMAX2; 3934 suggest_try_soundalike(su); 3935 if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su)) 3936 { 3937 /* Still didn't find enough matches, try again, allowing even 3938 * more changes to the soundfold word. */ 3939 su->su_maxscore = SCORE_SFMAX3; 3940 suggest_try_soundalike(su); 3941 } 3942 } 3943 su->su_maxscore = su->su_sfmaxscore; 3944 suggest_try_soundalike_finish(); 3945 } 3946 3947 /* When CTRL-C was hit while searching do show the results. Only clear 3948 * got_int when using a command, not for spellsuggest(). */ 3949 ui_breakcheck(); 3950 if (interactive && got_int) 3951 { 3952 (void)vgetc(); 3953 got_int = FALSE; 3954 } 3955 3956 if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0) 3957 { 3958 if (sps_flags & SPS_BEST) 3959 /* Adjust the word score for how it sounds like. */ 3960 rescore_suggestions(su); 3961 3962 /* Remove bogus suggestions, sort and truncate at "maxcount". */ 3963 check_suggestions(su, &su->su_ga); 3964 (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); 3965 } 3966 } 3967 3968 /* 3969 * Free the info put in "*su" by spell_find_suggest(). 3970 */ 3971 static void 3972 spell_find_cleanup(suginfo_T *su) 3973 { 3974 int i; 3975 3976 /* Free the suggestions. */ 3977 for (i = 0; i < su->su_ga.ga_len; ++i) 3978 vim_free(SUG(su->su_ga, i).st_word); 3979 ga_clear(&su->su_ga); 3980 for (i = 0; i < su->su_sga.ga_len; ++i) 3981 vim_free(SUG(su->su_sga, i).st_word); 3982 ga_clear(&su->su_sga); 3983 3984 /* Free the banned words. */ 3985 hash_clear_all(&su->su_banned, 0); 3986 } 3987 3988 /* 3989 * Make a copy of "word", with the first letter upper or lower cased, to 3990 * "wcopy[MAXWLEN]". "word" must not be empty. 3991 * The result is NUL terminated. 3992 */ 3993 void 3994 onecap_copy( 3995 char_u *word, 3996 char_u *wcopy, 3997 int upper) /* TRUE: first letter made upper case */ 3998 { 3999 char_u *p; 4000 int c; 4001 int l; 4002 4003 p = word; 4004 if (has_mbyte) 4005 c = mb_cptr2char_adv(&p); 4006 else 4007 c = *p++; 4008 if (upper) 4009 c = SPELL_TOUPPER(c); 4010 else 4011 c = SPELL_TOFOLD(c); 4012 if (has_mbyte) 4013 l = mb_char2bytes(c, wcopy); 4014 else 4015 { 4016 l = 1; 4017 wcopy[0] = c; 4018 } 4019 vim_strncpy(wcopy + l, p, MAXWLEN - l - 1); 4020 } 4021 4022 /* 4023 * Make a copy of "word" with all the letters upper cased into 4024 * "wcopy[MAXWLEN]". The result is NUL terminated. 4025 */ 4026 static void 4027 allcap_copy(char_u *word, char_u *wcopy) 4028 { 4029 char_u *s; 4030 char_u *d; 4031 int c; 4032 4033 d = wcopy; 4034 for (s = word; *s != NUL; ) 4035 { 4036 if (has_mbyte) 4037 c = mb_cptr2char_adv(&s); 4038 else 4039 c = *s++; 4040 4041 /* We only change 0xdf to SS when we are certain latin1 is used. It 4042 * would cause weird errors in other 8-bit encodings. */ 4043 if (enc_latin1like && c == 0xdf) 4044 { 4045 c = 'S'; 4046 if (d - wcopy >= MAXWLEN - 1) 4047 break; 4048 *d++ = c; 4049 } 4050 else 4051 c = SPELL_TOUPPER(c); 4052 4053 if (has_mbyte) 4054 { 4055 if (d - wcopy >= MAXWLEN - MB_MAXBYTES) 4056 break; 4057 d += mb_char2bytes(c, d); 4058 } 4059 else 4060 { 4061 if (d - wcopy >= MAXWLEN - 1) 4062 break; 4063 *d++ = c; 4064 } 4065 } 4066 *d = NUL; 4067 } 4068 4069 /* 4070 * Try finding suggestions by recognizing specific situations. 4071 */ 4072 static void 4073 suggest_try_special(suginfo_T *su) 4074 { 4075 char_u *p; 4076 size_t len; 4077 int c; 4078 char_u word[MAXWLEN]; 4079 4080 /* 4081 * Recognize a word that is repeated: "the the". 4082 */ 4083 p = skiptowhite(su->su_fbadword); 4084 len = p - su->su_fbadword; 4085 p = skipwhite(p); 4086 if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0) 4087 { 4088 /* Include badflags: if the badword is onecap or allcap 4089 * use that for the goodword too: "The the" -> "The". */ 4090 c = su->su_fbadword[len]; 4091 su->su_fbadword[len] = NUL; 4092 make_case_word(su->su_fbadword, word, su->su_badflags); 4093 su->su_fbadword[len] = c; 4094 4095 /* Give a soundalike score of 0, compute the score as if deleting one 4096 * character. */ 4097 add_suggestion(su, &su->su_ga, word, su->su_badlen, 4098 RESCORE(SCORE_REP, 0), 0, TRUE, su->su_sallang, FALSE); 4099 } 4100 } 4101 4102 /* 4103 * Change the 0 to 1 to measure how much time is spent in each state. 4104 * Output is dumped in "suggestprof". 4105 */ 4106 #if 0 4107 # define SUGGEST_PROFILE 4108 proftime_T current; 4109 proftime_T total; 4110 proftime_T times[STATE_FINAL + 1]; 4111 long counts[STATE_FINAL + 1]; 4112 4113 static void 4114 prof_init(void) 4115 { 4116 for (int i = 0; i <= STATE_FINAL; ++i) 4117 { 4118 profile_zero(×[i]); 4119 counts[i] = 0; 4120 } 4121 profile_start(¤t); 4122 profile_start(&total); 4123 } 4124 4125 /* call before changing state */ 4126 static void 4127 prof_store(state_T state) 4128 { 4129 profile_end(¤t); 4130 profile_add(×[state], ¤t); 4131 ++counts[state]; 4132 profile_start(¤t); 4133 } 4134 # define PROF_STORE(state) prof_store(state); 4135 4136 static void 4137 prof_report(char *name) 4138 { 4139 FILE *fd = fopen("suggestprof", "a"); 4140 4141 profile_end(&total); 4142 fprintf(fd, "-----------------------\n"); 4143 fprintf(fd, "%s: %s\n", name, profile_msg(&total)); 4144 for (int i = 0; i <= STATE_FINAL; ++i) 4145 fprintf(fd, "%d: %s (%ld)\n", i, profile_msg(×[i]), counts[i]); 4146 fclose(fd); 4147 } 4148 #else 4149 # define PROF_STORE(state) 4150 #endif 4151 4152 /* 4153 * Try finding suggestions by adding/removing/swapping letters. 4154 */ 4155 static void 4156 suggest_try_change(suginfo_T *su) 4157 { 4158 char_u fword[MAXWLEN]; /* copy of the bad word, case-folded */ 4159 int n; 4160 char_u *p; 4161 int lpi; 4162 langp_T *lp; 4163 4164 /* We make a copy of the case-folded bad word, so that we can modify it 4165 * to find matches (esp. REP items). Append some more text, changing 4166 * chars after the bad word may help. */ 4167 STRCPY(fword, su->su_fbadword); 4168 n = (int)STRLEN(fword); 4169 p = su->su_badptr + su->su_badlen; 4170 (void)spell_casefold(p, (int)STRLEN(p), fword + n, MAXWLEN - n); 4171 4172 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 4173 { 4174 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 4175 4176 /* If reloading a spell file fails it's still in the list but 4177 * everything has been cleared. */ 4178 if (lp->lp_slang->sl_fbyts == NULL) 4179 continue; 4180 4181 /* Try it for this language. Will add possible suggestions. */ 4182 #ifdef SUGGEST_PROFILE 4183 prof_init(); 4184 #endif 4185 suggest_trie_walk(su, lp, fword, FALSE); 4186 #ifdef SUGGEST_PROFILE 4187 prof_report("try_change"); 4188 #endif 4189 } 4190 } 4191 4192 /* Check the maximum score, if we go over it we won't try this change. */ 4193 #define TRY_DEEPER(su, stack, depth, add) \ 4194 (stack[depth].ts_score + (add) < su->su_maxscore) 4195 4196 /* 4197 * Try finding suggestions by adding/removing/swapping letters. 4198 * 4199 * This uses a state machine. At each node in the tree we try various 4200 * operations. When trying if an operation works "depth" is increased and the 4201 * stack[] is used to store info. This allows combinations, thus insert one 4202 * character, replace one and delete another. The number of changes is 4203 * limited by su->su_maxscore. 4204 * 4205 * After implementing this I noticed an article by Kemal Oflazer that 4206 * describes something similar: "Error-tolerant Finite State Recognition with 4207 * Applications to Morphological Analysis and Spelling Correction" (1996). 4208 * The implementation in the article is simplified and requires a stack of 4209 * unknown depth. The implementation here only needs a stack depth equal to 4210 * the length of the word. 4211 * 4212 * This is also used for the sound-folded word, "soundfold" is TRUE then. 4213 * The mechanism is the same, but we find a match with a sound-folded word 4214 * that comes from one or more original words. Each of these words may be 4215 * added, this is done by add_sound_suggest(). 4216 * Don't use: 4217 * the prefix tree or the keep-case tree 4218 * "su->su_badlen" 4219 * anything to do with upper and lower case 4220 * anything to do with word or non-word characters ("spell_iswordp()") 4221 * banned words 4222 * word flags (rare, region, compounding) 4223 * word splitting for now 4224 * "similar_chars()" 4225 * use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep" 4226 */ 4227 static void 4228 suggest_trie_walk( 4229 suginfo_T *su, 4230 langp_T *lp, 4231 char_u *fword, 4232 int soundfold) 4233 { 4234 char_u tword[MAXWLEN]; /* good word collected so far */ 4235 trystate_T stack[MAXWLEN]; 4236 char_u preword[MAXWLEN * 3]; /* word found with proper case; 4237 * concatenation of prefix compound 4238 * words and split word. NUL terminated 4239 * when going deeper but not when coming 4240 * back. */ 4241 char_u compflags[MAXWLEN]; /* compound flags, one for each word */ 4242 trystate_T *sp; 4243 int newscore; 4244 int score; 4245 char_u *byts, *fbyts, *pbyts; 4246 idx_T *idxs, *fidxs, *pidxs; 4247 int depth; 4248 int c, c2, c3; 4249 int n = 0; 4250 int flags; 4251 garray_T *gap; 4252 idx_T arridx; 4253 int len; 4254 char_u *p; 4255 fromto_T *ftp; 4256 int fl = 0, tl; 4257 int repextra = 0; /* extra bytes in fword[] from REP item */ 4258 slang_T *slang = lp->lp_slang; 4259 int fword_ends; 4260 int goodword_ends; 4261 #ifdef DEBUG_TRIEWALK 4262 /* Stores the name of the change made at each level. */ 4263 char_u changename[MAXWLEN][80]; 4264 #endif 4265 int breakcheckcount = 1000; 4266 int compound_ok; 4267 4268 /* 4269 * Go through the whole case-fold tree, try changes at each node. 4270 * "tword[]" contains the word collected from nodes in the tree. 4271 * "fword[]" the word we are trying to match with (initially the bad 4272 * word). 4273 */ 4274 depth = 0; 4275 sp = &stack[0]; 4276 vim_memset(sp, 0, sizeof(trystate_T)); 4277 sp->ts_curi = 1; 4278 4279 if (soundfold) 4280 { 4281 /* Going through the soundfold tree. */ 4282 byts = fbyts = slang->sl_sbyts; 4283 idxs = fidxs = slang->sl_sidxs; 4284 pbyts = NULL; 4285 pidxs = NULL; 4286 sp->ts_prefixdepth = PFD_NOPREFIX; 4287 sp->ts_state = STATE_START; 4288 } 4289 else 4290 { 4291 /* 4292 * When there are postponed prefixes we need to use these first. At 4293 * the end of the prefix we continue in the case-fold tree. 4294 */ 4295 fbyts = slang->sl_fbyts; 4296 fidxs = slang->sl_fidxs; 4297 pbyts = slang->sl_pbyts; 4298 pidxs = slang->sl_pidxs; 4299 if (pbyts != NULL) 4300 { 4301 byts = pbyts; 4302 idxs = pidxs; 4303 sp->ts_prefixdepth = PFD_PREFIXTREE; 4304 sp->ts_state = STATE_NOPREFIX; /* try without prefix first */ 4305 } 4306 else 4307 { 4308 byts = fbyts; 4309 idxs = fidxs; 4310 sp->ts_prefixdepth = PFD_NOPREFIX; 4311 sp->ts_state = STATE_START; 4312 } 4313 } 4314 4315 /* 4316 * Loop to find all suggestions. At each round we either: 4317 * - For the current state try one operation, advance "ts_curi", 4318 * increase "depth". 4319 * - When a state is done go to the next, set "ts_state". 4320 * - When all states are tried decrease "depth". 4321 */ 4322 while (depth >= 0 && !got_int) 4323 { 4324 sp = &stack[depth]; 4325 switch (sp->ts_state) 4326 { 4327 case STATE_START: 4328 case STATE_NOPREFIX: 4329 /* 4330 * Start of node: Deal with NUL bytes, which means 4331 * tword[] may end here. 4332 */ 4333 arridx = sp->ts_arridx; /* current node in the tree */ 4334 len = byts[arridx]; /* bytes in this node */ 4335 arridx += sp->ts_curi; /* index of current byte */ 4336 4337 if (sp->ts_prefixdepth == PFD_PREFIXTREE) 4338 { 4339 /* Skip over the NUL bytes, we use them later. */ 4340 for (n = 0; n < len && byts[arridx + n] == 0; ++n) 4341 ; 4342 sp->ts_curi += n; 4343 4344 /* Always past NUL bytes now. */ 4345 n = (int)sp->ts_state; 4346 PROF_STORE(sp->ts_state) 4347 sp->ts_state = STATE_ENDNUL; 4348 sp->ts_save_badflags = su->su_badflags; 4349 4350 /* At end of a prefix or at start of prefixtree: check for 4351 * following word. */ 4352 if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX) 4353 { 4354 /* Set su->su_badflags to the caps type at this position. 4355 * Use the caps type until here for the prefix itself. */ 4356 if (has_mbyte) 4357 n = nofold_len(fword, sp->ts_fidx, su->su_badptr); 4358 else 4359 n = sp->ts_fidx; 4360 flags = badword_captype(su->su_badptr, su->su_badptr + n); 4361 su->su_badflags = badword_captype(su->su_badptr + n, 4362 su->su_badptr + su->su_badlen); 4363 #ifdef DEBUG_TRIEWALK 4364 sprintf(changename[depth], "prefix"); 4365 #endif 4366 go_deeper(stack, depth, 0); 4367 ++depth; 4368 sp = &stack[depth]; 4369 sp->ts_prefixdepth = depth - 1; 4370 byts = fbyts; 4371 idxs = fidxs; 4372 sp->ts_arridx = 0; 4373 4374 /* Move the prefix to preword[] with the right case 4375 * and make find_keepcap_word() works. */ 4376 tword[sp->ts_twordlen] = NUL; 4377 make_case_word(tword + sp->ts_splitoff, 4378 preword + sp->ts_prewordlen, flags); 4379 sp->ts_prewordlen = (char_u)STRLEN(preword); 4380 sp->ts_splitoff = sp->ts_twordlen; 4381 } 4382 break; 4383 } 4384 4385 if (sp->ts_curi > len || byts[arridx] != 0) 4386 { 4387 /* Past bytes in node and/or past NUL bytes. */ 4388 PROF_STORE(sp->ts_state) 4389 sp->ts_state = STATE_ENDNUL; 4390 sp->ts_save_badflags = su->su_badflags; 4391 break; 4392 } 4393 4394 /* 4395 * End of word in tree. 4396 */ 4397 ++sp->ts_curi; /* eat one NUL byte */ 4398 4399 flags = (int)idxs[arridx]; 4400 4401 /* Skip words with the NOSUGGEST flag. */ 4402 if (flags & WF_NOSUGGEST) 4403 break; 4404 4405 fword_ends = (fword[sp->ts_fidx] == NUL 4406 || (soundfold 4407 ? VIM_ISWHITE(fword[sp->ts_fidx]) 4408 : !spell_iswordp(fword + sp->ts_fidx, curwin))); 4409 tword[sp->ts_twordlen] = NUL; 4410 4411 if (sp->ts_prefixdepth <= PFD_NOTSPECIAL 4412 && (sp->ts_flags & TSF_PREFIXOK) == 0) 4413 { 4414 /* There was a prefix before the word. Check that the prefix 4415 * can be used with this word. */ 4416 /* Count the length of the NULs in the prefix. If there are 4417 * none this must be the first try without a prefix. */ 4418 n = stack[sp->ts_prefixdepth].ts_arridx; 4419 len = pbyts[n++]; 4420 for (c = 0; c < len && pbyts[n + c] == 0; ++c) 4421 ; 4422 if (c > 0) 4423 { 4424 c = valid_word_prefix(c, n, flags, 4425 tword + sp->ts_splitoff, slang, FALSE); 4426 if (c == 0) 4427 break; 4428 4429 /* Use the WF_RARE flag for a rare prefix. */ 4430 if (c & WF_RAREPFX) 4431 flags |= WF_RARE; 4432 4433 /* Tricky: when checking for both prefix and compounding 4434 * we run into the prefix flag first. 4435 * Remember that it's OK, so that we accept the prefix 4436 * when arriving at a compound flag. */ 4437 sp->ts_flags |= TSF_PREFIXOK; 4438 } 4439 } 4440 4441 /* Check NEEDCOMPOUND: can't use word without compounding. Do try 4442 * appending another compound word below. */ 4443 if (sp->ts_complen == sp->ts_compsplit && fword_ends 4444 && (flags & WF_NEEDCOMP)) 4445 goodword_ends = FALSE; 4446 else 4447 goodword_ends = TRUE; 4448 4449 p = NULL; 4450 compound_ok = TRUE; 4451 if (sp->ts_complen > sp->ts_compsplit) 4452 { 4453 if (slang->sl_nobreak) 4454 { 4455 /* There was a word before this word. When there was no 4456 * change in this word (it was correct) add the first word 4457 * as a suggestion. If this word was corrected too, we 4458 * need to check if a correct word follows. */ 4459 if (sp->ts_fidx - sp->ts_splitfidx 4460 == sp->ts_twordlen - sp->ts_splitoff 4461 && STRNCMP(fword + sp->ts_splitfidx, 4462 tword + sp->ts_splitoff, 4463 sp->ts_fidx - sp->ts_splitfidx) == 0) 4464 { 4465 preword[sp->ts_prewordlen] = NUL; 4466 newscore = score_wordcount_adj(slang, sp->ts_score, 4467 preword + sp->ts_prewordlen, 4468 sp->ts_prewordlen > 0); 4469 /* Add the suggestion if the score isn't too bad. */ 4470 if (newscore <= su->su_maxscore) 4471 add_suggestion(su, &su->su_ga, preword, 4472 sp->ts_splitfidx - repextra, 4473 newscore, 0, FALSE, 4474 lp->lp_sallang, FALSE); 4475 break; 4476 } 4477 } 4478 else 4479 { 4480 /* There was a compound word before this word. If this 4481 * word does not support compounding then give up 4482 * (splitting is tried for the word without compound 4483 * flag). */ 4484 if (((unsigned)flags >> 24) == 0 4485 || sp->ts_twordlen - sp->ts_splitoff 4486 < slang->sl_compminlen) 4487 break; 4488 /* For multi-byte chars check character length against 4489 * COMPOUNDMIN. */ 4490 if (has_mbyte 4491 && slang->sl_compminlen > 0 4492 && mb_charlen(tword + sp->ts_splitoff) 4493 < slang->sl_compminlen) 4494 break; 4495 4496 compflags[sp->ts_complen] = ((unsigned)flags >> 24); 4497 compflags[sp->ts_complen + 1] = NUL; 4498 vim_strncpy(preword + sp->ts_prewordlen, 4499 tword + sp->ts_splitoff, 4500 sp->ts_twordlen - sp->ts_splitoff); 4501 4502 /* Verify CHECKCOMPOUNDPATTERN rules. */ 4503 if (match_checkcompoundpattern(preword, sp->ts_prewordlen, 4504 &slang->sl_comppat)) 4505 compound_ok = FALSE; 4506 4507 if (compound_ok) 4508 { 4509 p = preword; 4510 while (*skiptowhite(p) != NUL) 4511 p = skipwhite(skiptowhite(p)); 4512 if (fword_ends && !can_compound(slang, p, 4513 compflags + sp->ts_compsplit)) 4514 /* Compound is not allowed. But it may still be 4515 * possible if we add another (short) word. */ 4516 compound_ok = FALSE; 4517 } 4518 4519 /* Get pointer to last char of previous word. */ 4520 p = preword + sp->ts_prewordlen; 4521 MB_PTR_BACK(preword, p); 4522 } 4523 } 4524 4525 /* 4526 * Form the word with proper case in preword. 4527 * If there is a word from a previous split, append. 4528 * For the soundfold tree don't change the case, simply append. 4529 */ 4530 if (soundfold) 4531 STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff); 4532 else if (flags & WF_KEEPCAP) 4533 /* Must find the word in the keep-case tree. */ 4534 find_keepcap_word(slang, tword + sp->ts_splitoff, 4535 preword + sp->ts_prewordlen); 4536 else 4537 { 4538 /* Include badflags: If the badword is onecap or allcap 4539 * use that for the goodword too. But if the badword is 4540 * allcap and it's only one char long use onecap. */ 4541 c = su->su_badflags; 4542 if ((c & WF_ALLCAP) 4543 && su->su_badlen == (*mb_ptr2len)(su->su_badptr)) 4544 c = WF_ONECAP; 4545 c |= flags; 4546 4547 /* When appending a compound word after a word character don't 4548 * use Onecap. */ 4549 if (p != NULL && spell_iswordp_nmw(p, curwin)) 4550 c &= ~WF_ONECAP; 4551 make_case_word(tword + sp->ts_splitoff, 4552 preword + sp->ts_prewordlen, c); 4553 } 4554 4555 if (!soundfold) 4556 { 4557 /* Don't use a banned word. It may appear again as a good 4558 * word, thus remember it. */ 4559 if (flags & WF_BANNED) 4560 { 4561 add_banned(su, preword + sp->ts_prewordlen); 4562 break; 4563 } 4564 if ((sp->ts_complen == sp->ts_compsplit 4565 && WAS_BANNED(su, preword + sp->ts_prewordlen)) 4566 || WAS_BANNED(su, preword)) 4567 { 4568 if (slang->sl_compprog == NULL) 4569 break; 4570 /* the word so far was banned but we may try compounding */ 4571 goodword_ends = FALSE; 4572 } 4573 } 4574 4575 newscore = 0; 4576 if (!soundfold) /* soundfold words don't have flags */ 4577 { 4578 if ((flags & WF_REGION) 4579 && (((unsigned)flags >> 16) & lp->lp_region) == 0) 4580 newscore += SCORE_REGION; 4581 if (flags & WF_RARE) 4582 newscore += SCORE_RARE; 4583 4584 if (!spell_valid_case(su->su_badflags, 4585 captype(preword + sp->ts_prewordlen, NULL))) 4586 newscore += SCORE_ICASE; 4587 } 4588 4589 /* TODO: how about splitting in the soundfold tree? */ 4590 if (fword_ends 4591 && goodword_ends 4592 && sp->ts_fidx >= sp->ts_fidxtry 4593 && compound_ok) 4594 { 4595 /* The badword also ends: add suggestions. */ 4596 #ifdef DEBUG_TRIEWALK 4597 if (soundfold && STRCMP(preword, "smwrd") == 0) 4598 { 4599 int j; 4600 4601 /* print the stack of changes that brought us here */ 4602 smsg("------ %s -------", fword); 4603 for (j = 0; j < depth; ++j) 4604 smsg("%s", changename[j]); 4605 } 4606 #endif 4607 if (soundfold) 4608 { 4609 /* For soundfolded words we need to find the original 4610 * words, the edit distance and then add them. */ 4611 add_sound_suggest(su, preword, sp->ts_score, lp); 4612 } 4613 else if (sp->ts_fidx > 0) 4614 { 4615 /* Give a penalty when changing non-word char to word 4616 * char, e.g., "thes," -> "these". */ 4617 p = fword + sp->ts_fidx; 4618 MB_PTR_BACK(fword, p); 4619 if (!spell_iswordp(p, curwin)) 4620 { 4621 p = preword + STRLEN(preword); 4622 MB_PTR_BACK(preword, p); 4623 if (spell_iswordp(p, curwin)) 4624 newscore += SCORE_NONWORD; 4625 } 4626 4627 /* Give a bonus to words seen before. */ 4628 score = score_wordcount_adj(slang, 4629 sp->ts_score + newscore, 4630 preword + sp->ts_prewordlen, 4631 sp->ts_prewordlen > 0); 4632 4633 /* Add the suggestion if the score isn't too bad. */ 4634 if (score <= su->su_maxscore) 4635 { 4636 add_suggestion(su, &su->su_ga, preword, 4637 sp->ts_fidx - repextra, 4638 score, 0, FALSE, lp->lp_sallang, FALSE); 4639 4640 if (su->su_badflags & WF_MIXCAP) 4641 { 4642 /* We really don't know if the word should be 4643 * upper or lower case, add both. */ 4644 c = captype(preword, NULL); 4645 if (c == 0 || c == WF_ALLCAP) 4646 { 4647 make_case_word(tword + sp->ts_splitoff, 4648 preword + sp->ts_prewordlen, 4649 c == 0 ? WF_ALLCAP : 0); 4650 4651 add_suggestion(su, &su->su_ga, preword, 4652 sp->ts_fidx - repextra, 4653 score + SCORE_ICASE, 0, FALSE, 4654 lp->lp_sallang, FALSE); 4655 } 4656 } 4657 } 4658 } 4659 } 4660 4661 /* 4662 * Try word split and/or compounding. 4663 */ 4664 if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends) 4665 /* Don't split halfway a character. */ 4666 && (!has_mbyte || sp->ts_tcharlen == 0)) 4667 { 4668 int try_compound; 4669 int try_split; 4670 4671 /* If past the end of the bad word don't try a split. 4672 * Otherwise try changing the next word. E.g., find 4673 * suggestions for "the the" where the second "the" is 4674 * different. It's done like a split. 4675 * TODO: word split for soundfold words */ 4676 try_split = (sp->ts_fidx - repextra < su->su_badlen) 4677 && !soundfold; 4678 4679 /* Get here in several situations: 4680 * 1. The word in the tree ends: 4681 * If the word allows compounding try that. Otherwise try 4682 * a split by inserting a space. For both check that a 4683 * valid words starts at fword[sp->ts_fidx]. 4684 * For NOBREAK do like compounding to be able to check if 4685 * the next word is valid. 4686 * 2. The badword does end, but it was due to a change (e.g., 4687 * a swap). No need to split, but do check that the 4688 * following word is valid. 4689 * 3. The badword and the word in the tree end. It may still 4690 * be possible to compound another (short) word. 4691 */ 4692 try_compound = FALSE; 4693 if (!soundfold 4694 && !slang->sl_nocompoundsugs 4695 && slang->sl_compprog != NULL 4696 && ((unsigned)flags >> 24) != 0 4697 && sp->ts_twordlen - sp->ts_splitoff 4698 >= slang->sl_compminlen 4699 && (!has_mbyte 4700 || slang->sl_compminlen == 0 4701 || mb_charlen(tword + sp->ts_splitoff) 4702 >= slang->sl_compminlen) 4703 && (slang->sl_compsylmax < MAXWLEN 4704 || sp->ts_complen + 1 - sp->ts_compsplit 4705 < slang->sl_compmax) 4706 && (can_be_compound(sp, slang, 4707 compflags, ((unsigned)flags >> 24)))) 4708 4709 { 4710 try_compound = TRUE; 4711 compflags[sp->ts_complen] = ((unsigned)flags >> 24); 4712 compflags[sp->ts_complen + 1] = NUL; 4713 } 4714 4715 /* For NOBREAK we never try splitting, it won't make any word 4716 * valid. */ 4717 if (slang->sl_nobreak && !slang->sl_nocompoundsugs) 4718 try_compound = TRUE; 4719 4720 /* If we could add a compound word, and it's also possible to 4721 * split at this point, do the split first and set 4722 * TSF_DIDSPLIT to avoid doing it again. */ 4723 else if (!fword_ends 4724 && try_compound 4725 && (sp->ts_flags & TSF_DIDSPLIT) == 0) 4726 { 4727 try_compound = FALSE; 4728 sp->ts_flags |= TSF_DIDSPLIT; 4729 --sp->ts_curi; /* do the same NUL again */ 4730 compflags[sp->ts_complen] = NUL; 4731 } 4732 else 4733 sp->ts_flags &= ~TSF_DIDSPLIT; 4734 4735 if (try_split || try_compound) 4736 { 4737 if (!try_compound && (!fword_ends || !goodword_ends)) 4738 { 4739 /* If we're going to split need to check that the 4740 * words so far are valid for compounding. If there 4741 * is only one word it must not have the NEEDCOMPOUND 4742 * flag. */ 4743 if (sp->ts_complen == sp->ts_compsplit 4744 && (flags & WF_NEEDCOMP)) 4745 break; 4746 p = preword; 4747 while (*skiptowhite(p) != NUL) 4748 p = skipwhite(skiptowhite(p)); 4749 if (sp->ts_complen > sp->ts_compsplit 4750 && !can_compound(slang, p, 4751 compflags + sp->ts_compsplit)) 4752 break; 4753 4754 if (slang->sl_nosplitsugs) 4755 newscore += SCORE_SPLIT_NO; 4756 else 4757 newscore += SCORE_SPLIT; 4758 4759 /* Give a bonus to words seen before. */ 4760 newscore = score_wordcount_adj(slang, newscore, 4761 preword + sp->ts_prewordlen, TRUE); 4762 } 4763 4764 if (TRY_DEEPER(su, stack, depth, newscore)) 4765 { 4766 go_deeper(stack, depth, newscore); 4767 #ifdef DEBUG_TRIEWALK 4768 if (!try_compound && !fword_ends) 4769 sprintf(changename[depth], "%.*s-%s: split", 4770 sp->ts_twordlen, tword, fword + sp->ts_fidx); 4771 else 4772 sprintf(changename[depth], "%.*s-%s: compound", 4773 sp->ts_twordlen, tword, fword + sp->ts_fidx); 4774 #endif 4775 /* Save things to be restored at STATE_SPLITUNDO. */ 4776 sp->ts_save_badflags = su->su_badflags; 4777 PROF_STORE(sp->ts_state) 4778 sp->ts_state = STATE_SPLITUNDO; 4779 4780 ++depth; 4781 sp = &stack[depth]; 4782 4783 /* Append a space to preword when splitting. */ 4784 if (!try_compound && !fword_ends) 4785 STRCAT(preword, " "); 4786 sp->ts_prewordlen = (char_u)STRLEN(preword); 4787 sp->ts_splitoff = sp->ts_twordlen; 4788 sp->ts_splitfidx = sp->ts_fidx; 4789 4790 /* If the badword has a non-word character at this 4791 * position skip it. That means replacing the 4792 * non-word character with a space. Always skip a 4793 * character when the word ends. But only when the 4794 * good word can end. */ 4795 if (((!try_compound && !spell_iswordp_nmw(fword 4796 + sp->ts_fidx, 4797 curwin)) 4798 || fword_ends) 4799 && fword[sp->ts_fidx] != NUL 4800 && goodword_ends) 4801 { 4802 int l; 4803 4804 l = MB_PTR2LEN(fword + sp->ts_fidx); 4805 if (fword_ends) 4806 { 4807 /* Copy the skipped character to preword. */ 4808 mch_memmove(preword + sp->ts_prewordlen, 4809 fword + sp->ts_fidx, l); 4810 sp->ts_prewordlen += l; 4811 preword[sp->ts_prewordlen] = NUL; 4812 } 4813 else 4814 sp->ts_score -= SCORE_SPLIT - SCORE_SUBST; 4815 sp->ts_fidx += l; 4816 } 4817 4818 /* When compounding include compound flag in 4819 * compflags[] (already set above). When splitting we 4820 * may start compounding over again. */ 4821 if (try_compound) 4822 ++sp->ts_complen; 4823 else 4824 sp->ts_compsplit = sp->ts_complen; 4825 sp->ts_prefixdepth = PFD_NOPREFIX; 4826 4827 /* set su->su_badflags to the caps type at this 4828 * position */ 4829 if (has_mbyte) 4830 n = nofold_len(fword, sp->ts_fidx, su->su_badptr); 4831 else 4832 n = sp->ts_fidx; 4833 su->su_badflags = badword_captype(su->su_badptr + n, 4834 su->su_badptr + su->su_badlen); 4835 4836 /* Restart at top of the tree. */ 4837 sp->ts_arridx = 0; 4838 4839 /* If there are postponed prefixes, try these too. */ 4840 if (pbyts != NULL) 4841 { 4842 byts = pbyts; 4843 idxs = pidxs; 4844 sp->ts_prefixdepth = PFD_PREFIXTREE; 4845 PROF_STORE(sp->ts_state) 4846 sp->ts_state = STATE_NOPREFIX; 4847 } 4848 } 4849 } 4850 } 4851 break; 4852 4853 case STATE_SPLITUNDO: 4854 /* Undo the changes done for word split or compound word. */ 4855 su->su_badflags = sp->ts_save_badflags; 4856 4857 /* Continue looking for NUL bytes. */ 4858 PROF_STORE(sp->ts_state) 4859 sp->ts_state = STATE_START; 4860 4861 /* In case we went into the prefix tree. */ 4862 byts = fbyts; 4863 idxs = fidxs; 4864 break; 4865 4866 case STATE_ENDNUL: 4867 /* Past the NUL bytes in the node. */ 4868 su->su_badflags = sp->ts_save_badflags; 4869 if (fword[sp->ts_fidx] == NUL && sp->ts_tcharlen == 0) 4870 { 4871 /* The badword ends, can't use STATE_PLAIN. */ 4872 PROF_STORE(sp->ts_state) 4873 sp->ts_state = STATE_DEL; 4874 break; 4875 } 4876 PROF_STORE(sp->ts_state) 4877 sp->ts_state = STATE_PLAIN; 4878 /* FALLTHROUGH */ 4879 4880 case STATE_PLAIN: 4881 /* 4882 * Go over all possible bytes at this node, add each to tword[] 4883 * and use child node. "ts_curi" is the index. 4884 */ 4885 arridx = sp->ts_arridx; 4886 if (sp->ts_curi > byts[arridx]) 4887 { 4888 /* Done all bytes at this node, do next state. When still at 4889 * already changed bytes skip the other tricks. */ 4890 PROF_STORE(sp->ts_state) 4891 if (sp->ts_fidx >= sp->ts_fidxtry) 4892 sp->ts_state = STATE_DEL; 4893 else 4894 sp->ts_state = STATE_FINAL; 4895 } 4896 else 4897 { 4898 arridx += sp->ts_curi++; 4899 c = byts[arridx]; 4900 4901 /* Normal byte, go one level deeper. If it's not equal to the 4902 * byte in the bad word adjust the score. But don't even try 4903 * when the byte was already changed. And don't try when we 4904 * just deleted this byte, accepting it is always cheaper than 4905 * delete + substitute. */ 4906 if (c == fword[sp->ts_fidx] 4907 || (sp->ts_tcharlen > 0 && sp->ts_isdiff != DIFF_NONE)) 4908 newscore = 0; 4909 else 4910 newscore = SCORE_SUBST; 4911 if ((newscore == 0 4912 || (sp->ts_fidx >= sp->ts_fidxtry 4913 && ((sp->ts_flags & TSF_DIDDEL) == 0 4914 || c != fword[sp->ts_delidx]))) 4915 && TRY_DEEPER(su, stack, depth, newscore)) 4916 { 4917 go_deeper(stack, depth, newscore); 4918 #ifdef DEBUG_TRIEWALK 4919 if (newscore > 0) 4920 sprintf(changename[depth], "%.*s-%s: subst %c to %c", 4921 sp->ts_twordlen, tword, fword + sp->ts_fidx, 4922 fword[sp->ts_fidx], c); 4923 else 4924 sprintf(changename[depth], "%.*s-%s: accept %c", 4925 sp->ts_twordlen, tword, fword + sp->ts_fidx, 4926 fword[sp->ts_fidx]); 4927 #endif 4928 ++depth; 4929 sp = &stack[depth]; 4930 ++sp->ts_fidx; 4931 tword[sp->ts_twordlen++] = c; 4932 sp->ts_arridx = idxs[arridx]; 4933 if (newscore == SCORE_SUBST) 4934 sp->ts_isdiff = DIFF_YES; 4935 if (has_mbyte) 4936 { 4937 /* Multi-byte characters are a bit complicated to 4938 * handle: They differ when any of the bytes differ 4939 * and then their length may also differ. */ 4940 if (sp->ts_tcharlen == 0) 4941 { 4942 /* First byte. */ 4943 sp->ts_tcharidx = 0; 4944 sp->ts_tcharlen = MB_BYTE2LEN(c); 4945 sp->ts_fcharstart = sp->ts_fidx - 1; 4946 sp->ts_isdiff = (newscore != 0) 4947 ? DIFF_YES : DIFF_NONE; 4948 } 4949 else if (sp->ts_isdiff == DIFF_INSERT) 4950 /* When inserting trail bytes don't advance in the 4951 * bad word. */ 4952 --sp->ts_fidx; 4953 if (++sp->ts_tcharidx == sp->ts_tcharlen) 4954 { 4955 /* Last byte of character. */ 4956 if (sp->ts_isdiff == DIFF_YES) 4957 { 4958 /* Correct ts_fidx for the byte length of the 4959 * character (we didn't check that before). */ 4960 sp->ts_fidx = sp->ts_fcharstart 4961 + MB_PTR2LEN( 4962 fword + sp->ts_fcharstart); 4963 /* For changing a composing character adjust 4964 * the score from SCORE_SUBST to 4965 * SCORE_SUBCOMP. */ 4966 if (enc_utf8 4967 && utf_iscomposing( 4968 utf_ptr2char(tword 4969 + sp->ts_twordlen 4970 - sp->ts_tcharlen)) 4971 && utf_iscomposing( 4972 utf_ptr2char(fword 4973 + sp->ts_fcharstart))) 4974 sp->ts_score -= 4975 SCORE_SUBST - SCORE_SUBCOMP; 4976 4977 /* For a similar character adjust score from 4978 * SCORE_SUBST to SCORE_SIMILAR. */ 4979 else if (!soundfold 4980 && slang->sl_has_map 4981 && similar_chars(slang, 4982 mb_ptr2char(tword 4983 + sp->ts_twordlen 4984 - sp->ts_tcharlen), 4985 mb_ptr2char(fword 4986 + sp->ts_fcharstart))) 4987 sp->ts_score -= 4988 SCORE_SUBST - SCORE_SIMILAR; 4989 } 4990 else if (sp->ts_isdiff == DIFF_INSERT 4991 && sp->ts_twordlen > sp->ts_tcharlen) 4992 { 4993 p = tword + sp->ts_twordlen - sp->ts_tcharlen; 4994 c = mb_ptr2char(p); 4995 if (enc_utf8 && utf_iscomposing(c)) 4996 { 4997 /* Inserting a composing char doesn't 4998 * count that much. */ 4999 sp->ts_score -= SCORE_INS - SCORE_INSCOMP; 5000 } 5001 else 5002 { 5003 /* If the previous character was the same, 5004 * thus doubling a character, give a bonus 5005 * to the score. Also for the soundfold 5006 * tree (might seem illogical but does 5007 * give better scores). */ 5008 MB_PTR_BACK(tword, p); 5009 if (c == mb_ptr2char(p)) 5010 sp->ts_score -= SCORE_INS 5011 - SCORE_INSDUP; 5012 } 5013 } 5014 5015 /* Starting a new char, reset the length. */ 5016 sp->ts_tcharlen = 0; 5017 } 5018 } 5019 else 5020 { 5021 /* If we found a similar char adjust the score. 5022 * We do this after calling go_deeper() because 5023 * it's slow. */ 5024 if (newscore != 0 5025 && !soundfold 5026 && slang->sl_has_map 5027 && similar_chars(slang, 5028 c, fword[sp->ts_fidx - 1])) 5029 sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR; 5030 } 5031 } 5032 } 5033 break; 5034 5035 case STATE_DEL: 5036 /* When past the first byte of a multi-byte char don't try 5037 * delete/insert/swap a character. */ 5038 if (has_mbyte && sp->ts_tcharlen > 0) 5039 { 5040 PROF_STORE(sp->ts_state) 5041 sp->ts_state = STATE_FINAL; 5042 break; 5043 } 5044 /* 5045 * Try skipping one character in the bad word (delete it). 5046 */ 5047 PROF_STORE(sp->ts_state) 5048 sp->ts_state = STATE_INS_PREP; 5049 sp->ts_curi = 1; 5050 if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*') 5051 /* Deleting a vowel at the start of a word counts less, see 5052 * soundalike_score(). */ 5053 newscore = 2 * SCORE_DEL / 3; 5054 else 5055 newscore = SCORE_DEL; 5056 if (fword[sp->ts_fidx] != NUL 5057 && TRY_DEEPER(su, stack, depth, newscore)) 5058 { 5059 go_deeper(stack, depth, newscore); 5060 #ifdef DEBUG_TRIEWALK 5061 sprintf(changename[depth], "%.*s-%s: delete %c", 5062 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5063 fword[sp->ts_fidx]); 5064 #endif 5065 ++depth; 5066 5067 /* Remember what character we deleted, so that we can avoid 5068 * inserting it again. */ 5069 stack[depth].ts_flags |= TSF_DIDDEL; 5070 stack[depth].ts_delidx = sp->ts_fidx; 5071 5072 /* Advance over the character in fword[]. Give a bonus to the 5073 * score if the same character is following "nn" -> "n". It's 5074 * a bit illogical for soundfold tree but it does give better 5075 * results. */ 5076 if (has_mbyte) 5077 { 5078 c = mb_ptr2char(fword + sp->ts_fidx); 5079 stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); 5080 if (enc_utf8 && utf_iscomposing(c)) 5081 stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; 5082 else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) 5083 stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; 5084 } 5085 else 5086 { 5087 ++stack[depth].ts_fidx; 5088 if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1]) 5089 stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; 5090 } 5091 break; 5092 } 5093 /* FALLTHROUGH */ 5094 5095 case STATE_INS_PREP: 5096 if (sp->ts_flags & TSF_DIDDEL) 5097 { 5098 /* If we just deleted a byte then inserting won't make sense, 5099 * a substitute is always cheaper. */ 5100 PROF_STORE(sp->ts_state) 5101 sp->ts_state = STATE_SWAP; 5102 break; 5103 } 5104 5105 /* skip over NUL bytes */ 5106 n = sp->ts_arridx; 5107 for (;;) 5108 { 5109 if (sp->ts_curi > byts[n]) 5110 { 5111 /* Only NUL bytes at this node, go to next state. */ 5112 PROF_STORE(sp->ts_state) 5113 sp->ts_state = STATE_SWAP; 5114 break; 5115 } 5116 if (byts[n + sp->ts_curi] != NUL) 5117 { 5118 /* Found a byte to insert. */ 5119 PROF_STORE(sp->ts_state) 5120 sp->ts_state = STATE_INS; 5121 break; 5122 } 5123 ++sp->ts_curi; 5124 } 5125 break; 5126 5127 /* FALLTHROUGH */ 5128 5129 case STATE_INS: 5130 /* Insert one byte. Repeat this for each possible byte at this 5131 * node. */ 5132 n = sp->ts_arridx; 5133 if (sp->ts_curi > byts[n]) 5134 { 5135 /* Done all bytes at this node, go to next state. */ 5136 PROF_STORE(sp->ts_state) 5137 sp->ts_state = STATE_SWAP; 5138 break; 5139 } 5140 5141 /* Do one more byte at this node, but: 5142 * - Skip NUL bytes. 5143 * - Skip the byte if it's equal to the byte in the word, 5144 * accepting that byte is always better. 5145 */ 5146 n += sp->ts_curi++; 5147 c = byts[n]; 5148 if (soundfold && sp->ts_twordlen == 0 && c == '*') 5149 /* Inserting a vowel at the start of a word counts less, 5150 * see soundalike_score(). */ 5151 newscore = 2 * SCORE_INS / 3; 5152 else 5153 newscore = SCORE_INS; 5154 if (c != fword[sp->ts_fidx] 5155 && TRY_DEEPER(su, stack, depth, newscore)) 5156 { 5157 go_deeper(stack, depth, newscore); 5158 #ifdef DEBUG_TRIEWALK 5159 sprintf(changename[depth], "%.*s-%s: insert %c", 5160 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5161 c); 5162 #endif 5163 ++depth; 5164 sp = &stack[depth]; 5165 tword[sp->ts_twordlen++] = c; 5166 sp->ts_arridx = idxs[n]; 5167 if (has_mbyte) 5168 { 5169 fl = MB_BYTE2LEN(c); 5170 if (fl > 1) 5171 { 5172 /* There are following bytes for the same character. 5173 * We must find all bytes before trying 5174 * delete/insert/swap/etc. */ 5175 sp->ts_tcharlen = fl; 5176 sp->ts_tcharidx = 1; 5177 sp->ts_isdiff = DIFF_INSERT; 5178 } 5179 } 5180 else 5181 fl = 1; 5182 if (fl == 1) 5183 { 5184 /* If the previous character was the same, thus doubling a 5185 * character, give a bonus to the score. Also for 5186 * soundfold words (illogical but does give a better 5187 * score). */ 5188 if (sp->ts_twordlen >= 2 5189 && tword[sp->ts_twordlen - 2] == c) 5190 sp->ts_score -= SCORE_INS - SCORE_INSDUP; 5191 } 5192 } 5193 break; 5194 5195 case STATE_SWAP: 5196 /* 5197 * Swap two bytes in the bad word: "12" -> "21". 5198 * We change "fword" here, it's changed back afterwards at 5199 * STATE_UNSWAP. 5200 */ 5201 p = fword + sp->ts_fidx; 5202 c = *p; 5203 if (c == NUL) 5204 { 5205 /* End of word, can't swap or replace. */ 5206 PROF_STORE(sp->ts_state) 5207 sp->ts_state = STATE_FINAL; 5208 break; 5209 } 5210 5211 /* Don't swap if the first character is not a word character. 5212 * SWAP3 etc. also don't make sense then. */ 5213 if (!soundfold && !spell_iswordp(p, curwin)) 5214 { 5215 PROF_STORE(sp->ts_state) 5216 sp->ts_state = STATE_REP_INI; 5217 break; 5218 } 5219 5220 if (has_mbyte) 5221 { 5222 n = MB_CPTR2LEN(p); 5223 c = mb_ptr2char(p); 5224 if (p[n] == NUL) 5225 c2 = NUL; 5226 else if (!soundfold && !spell_iswordp(p + n, curwin)) 5227 c2 = c; /* don't swap non-word char */ 5228 else 5229 c2 = mb_ptr2char(p + n); 5230 } 5231 else 5232 { 5233 if (p[1] == NUL) 5234 c2 = NUL; 5235 else if (!soundfold && !spell_iswordp(p + 1, curwin)) 5236 c2 = c; /* don't swap non-word char */ 5237 else 5238 c2 = p[1]; 5239 } 5240 5241 /* When the second character is NUL we can't swap. */ 5242 if (c2 == NUL) 5243 { 5244 PROF_STORE(sp->ts_state) 5245 sp->ts_state = STATE_REP_INI; 5246 break; 5247 } 5248 5249 /* When characters are identical, swap won't do anything. 5250 * Also get here if the second char is not a word character. */ 5251 if (c == c2) 5252 { 5253 PROF_STORE(sp->ts_state) 5254 sp->ts_state = STATE_SWAP3; 5255 break; 5256 } 5257 if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP)) 5258 { 5259 go_deeper(stack, depth, SCORE_SWAP); 5260 #ifdef DEBUG_TRIEWALK 5261 sprintf(changename[depth], "%.*s-%s: swap %c and %c", 5262 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5263 c, c2); 5264 #endif 5265 PROF_STORE(sp->ts_state) 5266 sp->ts_state = STATE_UNSWAP; 5267 ++depth; 5268 if (has_mbyte) 5269 { 5270 fl = mb_char2len(c2); 5271 mch_memmove(p, p + n, fl); 5272 mb_char2bytes(c, p + fl); 5273 stack[depth].ts_fidxtry = sp->ts_fidx + n + fl; 5274 } 5275 else 5276 { 5277 p[0] = c2; 5278 p[1] = c; 5279 stack[depth].ts_fidxtry = sp->ts_fidx + 2; 5280 } 5281 } 5282 else 5283 { 5284 /* If this swap doesn't work then SWAP3 won't either. */ 5285 PROF_STORE(sp->ts_state) 5286 sp->ts_state = STATE_REP_INI; 5287 } 5288 break; 5289 5290 case STATE_UNSWAP: 5291 /* Undo the STATE_SWAP swap: "21" -> "12". */ 5292 p = fword + sp->ts_fidx; 5293 if (has_mbyte) 5294 { 5295 n = MB_PTR2LEN(p); 5296 c = mb_ptr2char(p + n); 5297 mch_memmove(p + MB_PTR2LEN(p + n), p, n); 5298 mb_char2bytes(c, p); 5299 } 5300 else 5301 { 5302 c = *p; 5303 *p = p[1]; 5304 p[1] = c; 5305 } 5306 /* FALLTHROUGH */ 5307 5308 case STATE_SWAP3: 5309 /* Swap two bytes, skipping one: "123" -> "321". We change 5310 * "fword" here, it's changed back afterwards at STATE_UNSWAP3. */ 5311 p = fword + sp->ts_fidx; 5312 if (has_mbyte) 5313 { 5314 n = MB_CPTR2LEN(p); 5315 c = mb_ptr2char(p); 5316 fl = MB_CPTR2LEN(p + n); 5317 c2 = mb_ptr2char(p + n); 5318 if (!soundfold && !spell_iswordp(p + n + fl, curwin)) 5319 c3 = c; /* don't swap non-word char */ 5320 else 5321 c3 = mb_ptr2char(p + n + fl); 5322 } 5323 else 5324 { 5325 c = *p; 5326 c2 = p[1]; 5327 if (!soundfold && !spell_iswordp(p + 2, curwin)) 5328 c3 = c; /* don't swap non-word char */ 5329 else 5330 c3 = p[2]; 5331 } 5332 5333 /* When characters are identical: "121" then SWAP3 result is 5334 * identical, ROT3L result is same as SWAP: "211", ROT3L result is 5335 * same as SWAP on next char: "112". Thus skip all swapping. 5336 * Also skip when c3 is NUL. 5337 * Also get here when the third character is not a word character. 5338 * Second character may any char: "a.b" -> "b.a" */ 5339 if (c == c3 || c3 == NUL) 5340 { 5341 PROF_STORE(sp->ts_state) 5342 sp->ts_state = STATE_REP_INI; 5343 break; 5344 } 5345 if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) 5346 { 5347 go_deeper(stack, depth, SCORE_SWAP3); 5348 #ifdef DEBUG_TRIEWALK 5349 sprintf(changename[depth], "%.*s-%s: swap3 %c and %c", 5350 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5351 c, c3); 5352 #endif 5353 PROF_STORE(sp->ts_state) 5354 sp->ts_state = STATE_UNSWAP3; 5355 ++depth; 5356 if (has_mbyte) 5357 { 5358 tl = mb_char2len(c3); 5359 mch_memmove(p, p + n + fl, tl); 5360 mb_char2bytes(c2, p + tl); 5361 mb_char2bytes(c, p + fl + tl); 5362 stack[depth].ts_fidxtry = sp->ts_fidx + n + fl + tl; 5363 } 5364 else 5365 { 5366 p[0] = p[2]; 5367 p[2] = c; 5368 stack[depth].ts_fidxtry = sp->ts_fidx + 3; 5369 } 5370 } 5371 else 5372 { 5373 PROF_STORE(sp->ts_state) 5374 sp->ts_state = STATE_REP_INI; 5375 } 5376 break; 5377 5378 case STATE_UNSWAP3: 5379 /* Undo STATE_SWAP3: "321" -> "123" */ 5380 p = fword + sp->ts_fidx; 5381 if (has_mbyte) 5382 { 5383 n = MB_PTR2LEN(p); 5384 c2 = mb_ptr2char(p + n); 5385 fl = MB_PTR2LEN(p + n); 5386 c = mb_ptr2char(p + n + fl); 5387 tl = MB_PTR2LEN(p + n + fl); 5388 mch_memmove(p + fl + tl, p, n); 5389 mb_char2bytes(c, p); 5390 mb_char2bytes(c2, p + tl); 5391 p = p + tl; 5392 } 5393 else 5394 { 5395 c = *p; 5396 *p = p[2]; 5397 p[2] = c; 5398 ++p; 5399 } 5400 5401 if (!soundfold && !spell_iswordp(p, curwin)) 5402 { 5403 /* Middle char is not a word char, skip the rotate. First and 5404 * third char were already checked at swap and swap3. */ 5405 PROF_STORE(sp->ts_state) 5406 sp->ts_state = STATE_REP_INI; 5407 break; 5408 } 5409 5410 /* Rotate three characters left: "123" -> "231". We change 5411 * "fword" here, it's changed back afterwards at STATE_UNROT3L. */ 5412 if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) 5413 { 5414 go_deeper(stack, depth, SCORE_SWAP3); 5415 #ifdef DEBUG_TRIEWALK 5416 p = fword + sp->ts_fidx; 5417 sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c", 5418 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5419 p[0], p[1], p[2]); 5420 #endif 5421 PROF_STORE(sp->ts_state) 5422 sp->ts_state = STATE_UNROT3L; 5423 ++depth; 5424 p = fword + sp->ts_fidx; 5425 if (has_mbyte) 5426 { 5427 n = MB_CPTR2LEN(p); 5428 c = mb_ptr2char(p); 5429 fl = MB_CPTR2LEN(p + n); 5430 fl += MB_CPTR2LEN(p + n + fl); 5431 mch_memmove(p, p + n, fl); 5432 mb_char2bytes(c, p + fl); 5433 stack[depth].ts_fidxtry = sp->ts_fidx + n + fl; 5434 } 5435 else 5436 { 5437 c = *p; 5438 *p = p[1]; 5439 p[1] = p[2]; 5440 p[2] = c; 5441 stack[depth].ts_fidxtry = sp->ts_fidx + 3; 5442 } 5443 } 5444 else 5445 { 5446 PROF_STORE(sp->ts_state) 5447 sp->ts_state = STATE_REP_INI; 5448 } 5449 break; 5450 5451 case STATE_UNROT3L: 5452 /* Undo ROT3L: "231" -> "123" */ 5453 p = fword + sp->ts_fidx; 5454 if (has_mbyte) 5455 { 5456 n = MB_PTR2LEN(p); 5457 n += MB_PTR2LEN(p + n); 5458 c = mb_ptr2char(p + n); 5459 tl = MB_PTR2LEN(p + n); 5460 mch_memmove(p + tl, p, n); 5461 mb_char2bytes(c, p); 5462 } 5463 else 5464 { 5465 c = p[2]; 5466 p[2] = p[1]; 5467 p[1] = *p; 5468 *p = c; 5469 } 5470 5471 /* Rotate three bytes right: "123" -> "312". We change "fword" 5472 * here, it's changed back afterwards at STATE_UNROT3R. */ 5473 if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3)) 5474 { 5475 go_deeper(stack, depth, SCORE_SWAP3); 5476 #ifdef DEBUG_TRIEWALK 5477 p = fword + sp->ts_fidx; 5478 sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c", 5479 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5480 p[0], p[1], p[2]); 5481 #endif 5482 PROF_STORE(sp->ts_state) 5483 sp->ts_state = STATE_UNROT3R; 5484 ++depth; 5485 p = fword + sp->ts_fidx; 5486 if (has_mbyte) 5487 { 5488 n = MB_CPTR2LEN(p); 5489 n += MB_CPTR2LEN(p + n); 5490 c = mb_ptr2char(p + n); 5491 tl = MB_CPTR2LEN(p + n); 5492 mch_memmove(p + tl, p, n); 5493 mb_char2bytes(c, p); 5494 stack[depth].ts_fidxtry = sp->ts_fidx + n + tl; 5495 } 5496 else 5497 { 5498 c = p[2]; 5499 p[2] = p[1]; 5500 p[1] = *p; 5501 *p = c; 5502 stack[depth].ts_fidxtry = sp->ts_fidx + 3; 5503 } 5504 } 5505 else 5506 { 5507 PROF_STORE(sp->ts_state) 5508 sp->ts_state = STATE_REP_INI; 5509 } 5510 break; 5511 5512 case STATE_UNROT3R: 5513 /* Undo ROT3R: "312" -> "123" */ 5514 p = fword + sp->ts_fidx; 5515 if (has_mbyte) 5516 { 5517 c = mb_ptr2char(p); 5518 tl = MB_PTR2LEN(p); 5519 n = MB_PTR2LEN(p + tl); 5520 n += MB_PTR2LEN(p + tl + n); 5521 mch_memmove(p, p + tl, n); 5522 mb_char2bytes(c, p + n); 5523 } 5524 else 5525 { 5526 c = *p; 5527 *p = p[1]; 5528 p[1] = p[2]; 5529 p[2] = c; 5530 } 5531 /* FALLTHROUGH */ 5532 5533 case STATE_REP_INI: 5534 /* Check if matching with REP items from the .aff file would work. 5535 * Quickly skip if: 5536 * - there are no REP items and we are not in the soundfold trie 5537 * - the score is going to be too high anyway 5538 * - already applied a REP item or swapped here */ 5539 if ((lp->lp_replang == NULL && !soundfold) 5540 || sp->ts_score + SCORE_REP >= su->su_maxscore 5541 || sp->ts_fidx < sp->ts_fidxtry) 5542 { 5543 PROF_STORE(sp->ts_state) 5544 sp->ts_state = STATE_FINAL; 5545 break; 5546 } 5547 5548 /* Use the first byte to quickly find the first entry that may 5549 * match. If the index is -1 there is none. */ 5550 if (soundfold) 5551 sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]]; 5552 else 5553 sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]]; 5554 5555 if (sp->ts_curi < 0) 5556 { 5557 PROF_STORE(sp->ts_state) 5558 sp->ts_state = STATE_FINAL; 5559 break; 5560 } 5561 5562 PROF_STORE(sp->ts_state) 5563 sp->ts_state = STATE_REP; 5564 /* FALLTHROUGH */ 5565 5566 case STATE_REP: 5567 /* Try matching with REP items from the .aff file. For each match 5568 * replace the characters and check if the resulting word is 5569 * valid. */ 5570 p = fword + sp->ts_fidx; 5571 5572 if (soundfold) 5573 gap = &slang->sl_repsal; 5574 else 5575 gap = &lp->lp_replang->sl_rep; 5576 while (sp->ts_curi < gap->ga_len) 5577 { 5578 ftp = (fromto_T *)gap->ga_data + sp->ts_curi++; 5579 if (*ftp->ft_from != *p) 5580 { 5581 /* past possible matching entries */ 5582 sp->ts_curi = gap->ga_len; 5583 break; 5584 } 5585 if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0 5586 && TRY_DEEPER(su, stack, depth, SCORE_REP)) 5587 { 5588 go_deeper(stack, depth, SCORE_REP); 5589 #ifdef DEBUG_TRIEWALK 5590 sprintf(changename[depth], "%.*s-%s: replace %s with %s", 5591 sp->ts_twordlen, tword, fword + sp->ts_fidx, 5592 ftp->ft_from, ftp->ft_to); 5593 #endif 5594 /* Need to undo this afterwards. */ 5595 PROF_STORE(sp->ts_state) 5596 sp->ts_state = STATE_REP_UNDO; 5597 5598 /* Change the "from" to the "to" string. */ 5599 ++depth; 5600 fl = (int)STRLEN(ftp->ft_from); 5601 tl = (int)STRLEN(ftp->ft_to); 5602 if (fl != tl) 5603 { 5604 STRMOVE(p + tl, p + fl); 5605 repextra += tl - fl; 5606 } 5607 mch_memmove(p, ftp->ft_to, tl); 5608 stack[depth].ts_fidxtry = sp->ts_fidx + tl; 5609 stack[depth].ts_tcharlen = 0; 5610 break; 5611 } 5612 } 5613 5614 if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP) 5615 { 5616 /* No (more) matches. */ 5617 PROF_STORE(sp->ts_state) 5618 sp->ts_state = STATE_FINAL; 5619 } 5620 5621 break; 5622 5623 case STATE_REP_UNDO: 5624 /* Undo a REP replacement and continue with the next one. */ 5625 if (soundfold) 5626 gap = &slang->sl_repsal; 5627 else 5628 gap = &lp->lp_replang->sl_rep; 5629 ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1; 5630 fl = (int)STRLEN(ftp->ft_from); 5631 tl = (int)STRLEN(ftp->ft_to); 5632 p = fword + sp->ts_fidx; 5633 if (fl != tl) 5634 { 5635 STRMOVE(p + fl, p + tl); 5636 repextra -= tl - fl; 5637 } 5638 mch_memmove(p, ftp->ft_from, fl); 5639 PROF_STORE(sp->ts_state) 5640 sp->ts_state = STATE_REP; 5641 break; 5642 5643 default: 5644 /* Did all possible states at this level, go up one level. */ 5645 --depth; 5646 5647 if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE) 5648 { 5649 /* Continue in or go back to the prefix tree. */ 5650 byts = pbyts; 5651 idxs = pidxs; 5652 } 5653 5654 /* Don't check for CTRL-C too often, it takes time. */ 5655 if (--breakcheckcount == 0) 5656 { 5657 ui_breakcheck(); 5658 breakcheckcount = 1000; 5659 } 5660 } 5661 } 5662 } 5663 5664 5665 /* 5666 * Go one level deeper in the tree. 5667 */ 5668 static void 5669 go_deeper(trystate_T *stack, int depth, int score_add) 5670 { 5671 stack[depth + 1] = stack[depth]; 5672 stack[depth + 1].ts_state = STATE_START; 5673 stack[depth + 1].ts_score = stack[depth].ts_score + score_add; 5674 stack[depth + 1].ts_curi = 1; /* start just after length byte */ 5675 stack[depth + 1].ts_flags = 0; 5676 } 5677 5678 /* 5679 * Case-folding may change the number of bytes: Count nr of chars in 5680 * fword[flen] and return the byte length of that many chars in "word". 5681 */ 5682 static int 5683 nofold_len(char_u *fword, int flen, char_u *word) 5684 { 5685 char_u *p; 5686 int i = 0; 5687 5688 for (p = fword; p < fword + flen; MB_PTR_ADV(p)) 5689 ++i; 5690 for (p = word; i > 0; MB_PTR_ADV(p)) 5691 --i; 5692 return (int)(p - word); 5693 } 5694 5695 /* 5696 * "fword" is a good word with case folded. Find the matching keep-case 5697 * words and put it in "kword". 5698 * Theoretically there could be several keep-case words that result in the 5699 * same case-folded word, but we only find one... 5700 */ 5701 static void 5702 find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword) 5703 { 5704 char_u uword[MAXWLEN]; /* "fword" in upper-case */ 5705 int depth; 5706 idx_T tryidx; 5707 5708 /* The following arrays are used at each depth in the tree. */ 5709 idx_T arridx[MAXWLEN]; 5710 int round[MAXWLEN]; 5711 int fwordidx[MAXWLEN]; 5712 int uwordidx[MAXWLEN]; 5713 int kwordlen[MAXWLEN]; 5714 5715 int flen, ulen; 5716 int l; 5717 int len; 5718 int c; 5719 idx_T lo, hi, m; 5720 char_u *p; 5721 char_u *byts = slang->sl_kbyts; /* array with bytes of the words */ 5722 idx_T *idxs = slang->sl_kidxs; /* array with indexes */ 5723 5724 if (byts == NULL) 5725 { 5726 /* array is empty: "cannot happen" */ 5727 *kword = NUL; 5728 return; 5729 } 5730 5731 /* Make an all-cap version of "fword". */ 5732 allcap_copy(fword, uword); 5733 5734 /* 5735 * Each character needs to be tried both case-folded and upper-case. 5736 * All this gets very complicated if we keep in mind that changing case 5737 * may change the byte length of a multi-byte character... 5738 */ 5739 depth = 0; 5740 arridx[0] = 0; 5741 round[0] = 0; 5742 fwordidx[0] = 0; 5743 uwordidx[0] = 0; 5744 kwordlen[0] = 0; 5745 while (depth >= 0) 5746 { 5747 if (fword[fwordidx[depth]] == NUL) 5748 { 5749 /* We are at the end of "fword". If the tree allows a word to end 5750 * here we have found a match. */ 5751 if (byts[arridx[depth] + 1] == 0) 5752 { 5753 kword[kwordlen[depth]] = NUL; 5754 return; 5755 } 5756 5757 /* kword is getting too long, continue one level up */ 5758 --depth; 5759 } 5760 else if (++round[depth] > 2) 5761 { 5762 /* tried both fold-case and upper-case character, continue one 5763 * level up */ 5764 --depth; 5765 } 5766 else 5767 { 5768 /* 5769 * round[depth] == 1: Try using the folded-case character. 5770 * round[depth] == 2: Try using the upper-case character. 5771 */ 5772 if (has_mbyte) 5773 { 5774 flen = MB_CPTR2LEN(fword + fwordidx[depth]); 5775 ulen = MB_CPTR2LEN(uword + uwordidx[depth]); 5776 } 5777 else 5778 ulen = flen = 1; 5779 if (round[depth] == 1) 5780 { 5781 p = fword + fwordidx[depth]; 5782 l = flen; 5783 } 5784 else 5785 { 5786 p = uword + uwordidx[depth]; 5787 l = ulen; 5788 } 5789 5790 for (tryidx = arridx[depth]; l > 0; --l) 5791 { 5792 /* Perform a binary search in the list of accepted bytes. */ 5793 len = byts[tryidx++]; 5794 c = *p++; 5795 lo = tryidx; 5796 hi = tryidx + len - 1; 5797 while (lo < hi) 5798 { 5799 m = (lo + hi) / 2; 5800 if (byts[m] > c) 5801 hi = m - 1; 5802 else if (byts[m] < c) 5803 lo = m + 1; 5804 else 5805 { 5806 lo = hi = m; 5807 break; 5808 } 5809 } 5810 5811 /* Stop if there is no matching byte. */ 5812 if (hi < lo || byts[lo] != c) 5813 break; 5814 5815 /* Continue at the child (if there is one). */ 5816 tryidx = idxs[lo]; 5817 } 5818 5819 if (l == 0) 5820 { 5821 /* 5822 * Found the matching char. Copy it to "kword" and go a 5823 * level deeper. 5824 */ 5825 if (round[depth] == 1) 5826 { 5827 STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth], 5828 flen); 5829 kwordlen[depth + 1] = kwordlen[depth] + flen; 5830 } 5831 else 5832 { 5833 STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth], 5834 ulen); 5835 kwordlen[depth + 1] = kwordlen[depth] + ulen; 5836 } 5837 fwordidx[depth + 1] = fwordidx[depth] + flen; 5838 uwordidx[depth + 1] = uwordidx[depth] + ulen; 5839 5840 ++depth; 5841 arridx[depth] = tryidx; 5842 round[depth] = 0; 5843 } 5844 } 5845 } 5846 5847 /* Didn't find it: "cannot happen". */ 5848 *kword = NUL; 5849 } 5850 5851 /* 5852 * Compute the sound-a-like score for suggestions in su->su_ga and add them to 5853 * su->su_sga. 5854 */ 5855 static void 5856 score_comp_sal(suginfo_T *su) 5857 { 5858 langp_T *lp; 5859 char_u badsound[MAXWLEN]; 5860 int i; 5861 suggest_T *stp; 5862 suggest_T *sstp; 5863 int score; 5864 int lpi; 5865 5866 if (ga_grow(&su->su_sga, su->su_ga.ga_len) == FAIL) 5867 return; 5868 5869 /* Use the sound-folding of the first language that supports it. */ 5870 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 5871 { 5872 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 5873 if (lp->lp_slang->sl_sal.ga_len > 0) 5874 { 5875 /* soundfold the bad word */ 5876 spell_soundfold(lp->lp_slang, su->su_fbadword, TRUE, badsound); 5877 5878 for (i = 0; i < su->su_ga.ga_len; ++i) 5879 { 5880 stp = &SUG(su->su_ga, i); 5881 5882 /* Case-fold the suggested word, sound-fold it and compute the 5883 * sound-a-like score. */ 5884 score = stp_sal_score(stp, su, lp->lp_slang, badsound); 5885 if (score < SCORE_MAXMAX) 5886 { 5887 /* Add the suggestion. */ 5888 sstp = &SUG(su->su_sga, su->su_sga.ga_len); 5889 sstp->st_word = vim_strsave(stp->st_word); 5890 if (sstp->st_word != NULL) 5891 { 5892 sstp->st_wordlen = stp->st_wordlen; 5893 sstp->st_score = score; 5894 sstp->st_altscore = 0; 5895 sstp->st_orglen = stp->st_orglen; 5896 ++su->su_sga.ga_len; 5897 } 5898 } 5899 } 5900 break; 5901 } 5902 } 5903 } 5904 5905 /* 5906 * Combine the list of suggestions in su->su_ga and su->su_sga. 5907 * They are entwined. 5908 */ 5909 static void 5910 score_combine(suginfo_T *su) 5911 { 5912 int i; 5913 int j; 5914 garray_T ga; 5915 garray_T *gap; 5916 langp_T *lp; 5917 suggest_T *stp; 5918 char_u *p; 5919 char_u badsound[MAXWLEN]; 5920 int round; 5921 int lpi; 5922 slang_T *slang = NULL; 5923 5924 /* Add the alternate score to su_ga. */ 5925 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 5926 { 5927 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 5928 if (lp->lp_slang->sl_sal.ga_len > 0) 5929 { 5930 /* soundfold the bad word */ 5931 slang = lp->lp_slang; 5932 spell_soundfold(slang, su->su_fbadword, TRUE, badsound); 5933 5934 for (i = 0; i < su->su_ga.ga_len; ++i) 5935 { 5936 stp = &SUG(su->su_ga, i); 5937 stp->st_altscore = stp_sal_score(stp, su, slang, badsound); 5938 if (stp->st_altscore == SCORE_MAXMAX) 5939 stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4; 5940 else 5941 stp->st_score = (stp->st_score * 3 5942 + stp->st_altscore) / 4; 5943 stp->st_salscore = FALSE; 5944 } 5945 break; 5946 } 5947 } 5948 5949 if (slang == NULL) /* Using "double" without sound folding. */ 5950 { 5951 (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, 5952 su->su_maxcount); 5953 return; 5954 } 5955 5956 /* Add the alternate score to su_sga. */ 5957 for (i = 0; i < su->su_sga.ga_len; ++i) 5958 { 5959 stp = &SUG(su->su_sga, i); 5960 stp->st_altscore = spell_edit_score(slang, 5961 su->su_badword, stp->st_word); 5962 if (stp->st_score == SCORE_MAXMAX) 5963 stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8; 5964 else 5965 stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8; 5966 stp->st_salscore = TRUE; 5967 } 5968 5969 /* Remove bad suggestions, sort the suggestions and truncate at "maxcount" 5970 * for both lists. */ 5971 check_suggestions(su, &su->su_ga); 5972 (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount); 5973 check_suggestions(su, &su->su_sga); 5974 (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount); 5975 5976 ga_init2(&ga, (int)sizeof(suginfo_T), 1); 5977 if (ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len) == FAIL) 5978 return; 5979 5980 stp = &SUG(ga, 0); 5981 for (i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i) 5982 { 5983 /* round 1: get a suggestion from su_ga 5984 * round 2: get a suggestion from su_sga */ 5985 for (round = 1; round <= 2; ++round) 5986 { 5987 gap = round == 1 ? &su->su_ga : &su->su_sga; 5988 if (i < gap->ga_len) 5989 { 5990 /* Don't add a word if it's already there. */ 5991 p = SUG(*gap, i).st_word; 5992 for (j = 0; j < ga.ga_len; ++j) 5993 if (STRCMP(stp[j].st_word, p) == 0) 5994 break; 5995 if (j == ga.ga_len) 5996 stp[ga.ga_len++] = SUG(*gap, i); 5997 else 5998 vim_free(p); 5999 } 6000 } 6001 } 6002 6003 ga_clear(&su->su_ga); 6004 ga_clear(&su->su_sga); 6005 6006 /* Truncate the list to the number of suggestions that will be displayed. */ 6007 if (ga.ga_len > su->su_maxcount) 6008 { 6009 for (i = su->su_maxcount; i < ga.ga_len; ++i) 6010 vim_free(stp[i].st_word); 6011 ga.ga_len = su->su_maxcount; 6012 } 6013 6014 su->su_ga = ga; 6015 } 6016 6017 /* 6018 * For the goodword in "stp" compute the soundalike score compared to the 6019 * badword. 6020 */ 6021 static int 6022 stp_sal_score( 6023 suggest_T *stp, 6024 suginfo_T *su, 6025 slang_T *slang, 6026 char_u *badsound) /* sound-folded badword */ 6027 { 6028 char_u *p; 6029 char_u *pbad; 6030 char_u *pgood; 6031 char_u badsound2[MAXWLEN]; 6032 char_u fword[MAXWLEN]; 6033 char_u goodsound[MAXWLEN]; 6034 char_u goodword[MAXWLEN]; 6035 int lendiff; 6036 6037 lendiff = (int)(su->su_badlen - stp->st_orglen); 6038 if (lendiff >= 0) 6039 pbad = badsound; 6040 else 6041 { 6042 /* soundfold the bad word with more characters following */ 6043 (void)spell_casefold(su->su_badptr, stp->st_orglen, fword, MAXWLEN); 6044 6045 /* When joining two words the sound often changes a lot. E.g., "t he" 6046 * sounds like "t h" while "the" sounds like "@". Avoid that by 6047 * removing the space. Don't do it when the good word also contains a 6048 * space. */ 6049 if (VIM_ISWHITE(su->su_badptr[su->su_badlen]) 6050 && *skiptowhite(stp->st_word) == NUL) 6051 for (p = fword; *(p = skiptowhite(p)) != NUL; ) 6052 STRMOVE(p, p + 1); 6053 6054 spell_soundfold(slang, fword, TRUE, badsound2); 6055 pbad = badsound2; 6056 } 6057 6058 if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN) 6059 { 6060 /* Add part of the bad word to the good word, so that we soundfold 6061 * what replaces the bad word. */ 6062 STRCPY(goodword, stp->st_word); 6063 vim_strncpy(goodword + stp->st_wordlen, 6064 su->su_badptr + su->su_badlen - lendiff, lendiff); 6065 pgood = goodword; 6066 } 6067 else 6068 pgood = stp->st_word; 6069 6070 /* Sound-fold the word and compute the score for the difference. */ 6071 spell_soundfold(slang, pgood, FALSE, goodsound); 6072 6073 return soundalike_score(goodsound, pbad); 6074 } 6075 6076 /* structure used to store soundfolded words that add_sound_suggest() has 6077 * handled already. */ 6078 typedef struct 6079 { 6080 short sft_score; /* lowest score used */ 6081 char_u sft_word[1]; /* soundfolded word, actually longer */ 6082 } sftword_T; 6083 6084 static sftword_T dumsft; 6085 #define HIKEY2SFT(p) ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft))) 6086 #define HI2SFT(hi) HIKEY2SFT((hi)->hi_key) 6087 6088 /* 6089 * Prepare for calling suggest_try_soundalike(). 6090 */ 6091 static void 6092 suggest_try_soundalike_prep(void) 6093 { 6094 langp_T *lp; 6095 int lpi; 6096 slang_T *slang; 6097 6098 /* Do this for all languages that support sound folding and for which a 6099 * .sug file has been loaded. */ 6100 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 6101 { 6102 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 6103 slang = lp->lp_slang; 6104 if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL) 6105 /* prepare the hashtable used by add_sound_suggest() */ 6106 hash_init(&slang->sl_sounddone); 6107 } 6108 } 6109 6110 /* 6111 * Find suggestions by comparing the word in a sound-a-like form. 6112 * Note: This doesn't support postponed prefixes. 6113 */ 6114 static void 6115 suggest_try_soundalike(suginfo_T *su) 6116 { 6117 char_u salword[MAXWLEN]; 6118 langp_T *lp; 6119 int lpi; 6120 slang_T *slang; 6121 6122 /* Do this for all languages that support sound folding and for which a 6123 * .sug file has been loaded. */ 6124 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 6125 { 6126 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 6127 slang = lp->lp_slang; 6128 if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL) 6129 { 6130 /* soundfold the bad word */ 6131 spell_soundfold(slang, su->su_fbadword, TRUE, salword); 6132 6133 /* try all kinds of inserts/deletes/swaps/etc. */ 6134 /* TODO: also soundfold the next words, so that we can try joining 6135 * and splitting */ 6136 #ifdef SUGGEST_PROFILE 6137 prof_init(); 6138 #endif 6139 suggest_trie_walk(su, lp, salword, TRUE); 6140 #ifdef SUGGEST_PROFILE 6141 prof_report("soundalike"); 6142 #endif 6143 } 6144 } 6145 } 6146 6147 /* 6148 * Finish up after calling suggest_try_soundalike(). 6149 */ 6150 static void 6151 suggest_try_soundalike_finish(void) 6152 { 6153 langp_T *lp; 6154 int lpi; 6155 slang_T *slang; 6156 int todo; 6157 hashitem_T *hi; 6158 6159 /* Do this for all languages that support sound folding and for which a 6160 * .sug file has been loaded. */ 6161 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 6162 { 6163 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 6164 slang = lp->lp_slang; 6165 if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL) 6166 { 6167 /* Free the info about handled words. */ 6168 todo = (int)slang->sl_sounddone.ht_used; 6169 for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi) 6170 if (!HASHITEM_EMPTY(hi)) 6171 { 6172 vim_free(HI2SFT(hi)); 6173 --todo; 6174 } 6175 6176 /* Clear the hashtable, it may also be used by another region. */ 6177 hash_clear(&slang->sl_sounddone); 6178 hash_init(&slang->sl_sounddone); 6179 } 6180 } 6181 } 6182 6183 /* 6184 * A match with a soundfolded word is found. Add the good word(s) that 6185 * produce this soundfolded word. 6186 */ 6187 static void 6188 add_sound_suggest( 6189 suginfo_T *su, 6190 char_u *goodword, 6191 int score, /* soundfold score */ 6192 langp_T *lp) 6193 { 6194 slang_T *slang = lp->lp_slang; /* language for sound folding */ 6195 int sfwordnr; 6196 char_u *nrline; 6197 int orgnr; 6198 char_u theword[MAXWLEN]; 6199 int i; 6200 int wlen; 6201 char_u *byts; 6202 idx_T *idxs; 6203 int n; 6204 int wordcount; 6205 int wc; 6206 int goodscore; 6207 hash_T hash; 6208 hashitem_T *hi; 6209 sftword_T *sft; 6210 int bc, gc; 6211 int limit; 6212 6213 /* 6214 * It's very well possible that the same soundfold word is found several 6215 * times with different scores. Since the following is quite slow only do 6216 * the words that have a better score than before. Use a hashtable to 6217 * remember the words that have been done. 6218 */ 6219 hash = hash_hash(goodword); 6220 hi = hash_lookup(&slang->sl_sounddone, goodword, hash); 6221 if (HASHITEM_EMPTY(hi)) 6222 { 6223 sft = (sftword_T *)alloc((unsigned)(sizeof(sftword_T) 6224 + STRLEN(goodword))); 6225 if (sft != NULL) 6226 { 6227 sft->sft_score = score; 6228 STRCPY(sft->sft_word, goodword); 6229 hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash); 6230 } 6231 } 6232 else 6233 { 6234 sft = HI2SFT(hi); 6235 if (score >= sft->sft_score) 6236 return; 6237 sft->sft_score = score; 6238 } 6239 6240 /* 6241 * Find the word nr in the soundfold tree. 6242 */ 6243 sfwordnr = soundfold_find(slang, goodword); 6244 if (sfwordnr < 0) 6245 { 6246 internal_error("add_sound_suggest()"); 6247 return; 6248 } 6249 6250 /* 6251 * go over the list of good words that produce this soundfold word 6252 */ 6253 nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)(sfwordnr + 1), FALSE); 6254 orgnr = 0; 6255 while (*nrline != NUL) 6256 { 6257 /* The wordnr was stored in a minimal nr of bytes as an offset to the 6258 * previous wordnr. */ 6259 orgnr += bytes2offset(&nrline); 6260 6261 byts = slang->sl_fbyts; 6262 idxs = slang->sl_fidxs; 6263 6264 /* Lookup the word "orgnr" one of the two tries. */ 6265 n = 0; 6266 wordcount = 0; 6267 for (wlen = 0; wlen < MAXWLEN - 3; ++wlen) 6268 { 6269 i = 1; 6270 if (wordcount == orgnr && byts[n + 1] == NUL) 6271 break; /* found end of word */ 6272 6273 if (byts[n + 1] == NUL) 6274 ++wordcount; 6275 6276 /* skip over the NUL bytes */ 6277 for ( ; byts[n + i] == NUL; ++i) 6278 if (i > byts[n]) /* safety check */ 6279 { 6280 STRCPY(theword + wlen, "BAD"); 6281 wlen += 3; 6282 goto badword; 6283 } 6284 6285 /* One of the siblings must have the word. */ 6286 for ( ; i < byts[n]; ++i) 6287 { 6288 wc = idxs[idxs[n + i]]; /* nr of words under this byte */ 6289 if (wordcount + wc > orgnr) 6290 break; 6291 wordcount += wc; 6292 } 6293 6294 theword[wlen] = byts[n + i]; 6295 n = idxs[n + i]; 6296 } 6297 badword: 6298 theword[wlen] = NUL; 6299 6300 /* Go over the possible flags and regions. */ 6301 for (; i <= byts[n] && byts[n + i] == NUL; ++i) 6302 { 6303 char_u cword[MAXWLEN]; 6304 char_u *p; 6305 int flags = (int)idxs[n + i]; 6306 6307 /* Skip words with the NOSUGGEST flag */ 6308 if (flags & WF_NOSUGGEST) 6309 continue; 6310 6311 if (flags & WF_KEEPCAP) 6312 { 6313 /* Must find the word in the keep-case tree. */ 6314 find_keepcap_word(slang, theword, cword); 6315 p = cword; 6316 } 6317 else 6318 { 6319 flags |= su->su_badflags; 6320 if ((flags & WF_CAPMASK) != 0) 6321 { 6322 /* Need to fix case according to "flags". */ 6323 make_case_word(theword, cword, flags); 6324 p = cword; 6325 } 6326 else 6327 p = theword; 6328 } 6329 6330 /* Add the suggestion. */ 6331 if (sps_flags & SPS_DOUBLE) 6332 { 6333 /* Add the suggestion if the score isn't too bad. */ 6334 if (score <= su->su_maxscore) 6335 add_suggestion(su, &su->su_sga, p, su->su_badlen, 6336 score, 0, FALSE, slang, FALSE); 6337 } 6338 else 6339 { 6340 /* Add a penalty for words in another region. */ 6341 if ((flags & WF_REGION) 6342 && (((unsigned)flags >> 16) & lp->lp_region) == 0) 6343 goodscore = SCORE_REGION; 6344 else 6345 goodscore = 0; 6346 6347 /* Add a small penalty for changing the first letter from 6348 * lower to upper case. Helps for "tath" -> "Kath", which is 6349 * less common than "tath" -> "path". Don't do it when the 6350 * letter is the same, that has already been counted. */ 6351 gc = PTR2CHAR(p); 6352 if (SPELL_ISUPPER(gc)) 6353 { 6354 bc = PTR2CHAR(su->su_badword); 6355 if (!SPELL_ISUPPER(bc) 6356 && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc)) 6357 goodscore += SCORE_ICASE / 2; 6358 } 6359 6360 /* Compute the score for the good word. This only does letter 6361 * insert/delete/swap/replace. REP items are not considered, 6362 * which may make the score a bit higher. 6363 * Use a limit for the score to make it work faster. Use 6364 * MAXSCORE(), because RESCORE() will change the score. 6365 * If the limit is very high then the iterative method is 6366 * inefficient, using an array is quicker. */ 6367 limit = MAXSCORE(su->su_sfmaxscore - goodscore, score); 6368 if (limit > SCORE_LIMITMAX) 6369 goodscore += spell_edit_score(slang, su->su_badword, p); 6370 else 6371 goodscore += spell_edit_score_limit(slang, su->su_badword, 6372 p, limit); 6373 6374 /* When going over the limit don't bother to do the rest. */ 6375 if (goodscore < SCORE_MAXMAX) 6376 { 6377 /* Give a bonus to words seen before. */ 6378 goodscore = score_wordcount_adj(slang, goodscore, p, FALSE); 6379 6380 /* Add the suggestion if the score isn't too bad. */ 6381 goodscore = RESCORE(goodscore, score); 6382 if (goodscore <= su->su_sfmaxscore) 6383 add_suggestion(su, &su->su_ga, p, su->su_badlen, 6384 goodscore, score, TRUE, slang, TRUE); 6385 } 6386 } 6387 } 6388 /* smsg("word %s (%d): %s (%d)", sftword, sftnr, theword, orgnr); */ 6389 } 6390 } 6391 6392 /* 6393 * Find word "word" in fold-case tree for "slang" and return the word number. 6394 */ 6395 static int 6396 soundfold_find(slang_T *slang, char_u *word) 6397 { 6398 idx_T arridx = 0; 6399 int len; 6400 int wlen = 0; 6401 int c; 6402 char_u *ptr = word; 6403 char_u *byts; 6404 idx_T *idxs; 6405 int wordnr = 0; 6406 6407 byts = slang->sl_sbyts; 6408 idxs = slang->sl_sidxs; 6409 6410 for (;;) 6411 { 6412 /* First byte is the number of possible bytes. */ 6413 len = byts[arridx++]; 6414 6415 /* If the first possible byte is a zero the word could end here. 6416 * If the word ends we found the word. If not skip the NUL bytes. */ 6417 c = ptr[wlen]; 6418 if (byts[arridx] == NUL) 6419 { 6420 if (c == NUL) 6421 break; 6422 6423 /* Skip over the zeros, there can be several. */ 6424 while (len > 0 && byts[arridx] == NUL) 6425 { 6426 ++arridx; 6427 --len; 6428 } 6429 if (len == 0) 6430 return -1; /* no children, word should have ended here */ 6431 ++wordnr; 6432 } 6433 6434 /* If the word ends we didn't find it. */ 6435 if (c == NUL) 6436 return -1; 6437 6438 /* Perform a binary search in the list of accepted bytes. */ 6439 if (c == TAB) /* <Tab> is handled like <Space> */ 6440 c = ' '; 6441 while (byts[arridx] < c) 6442 { 6443 /* The word count is in the first idxs[] entry of the child. */ 6444 wordnr += idxs[idxs[arridx]]; 6445 ++arridx; 6446 if (--len == 0) /* end of the bytes, didn't find it */ 6447 return -1; 6448 } 6449 if (byts[arridx] != c) /* didn't find the byte */ 6450 return -1; 6451 6452 /* Continue at the child (if there is one). */ 6453 arridx = idxs[arridx]; 6454 ++wlen; 6455 6456 /* One space in the good word may stand for several spaces in the 6457 * checked word. */ 6458 if (c == ' ') 6459 while (ptr[wlen] == ' ' || ptr[wlen] == TAB) 6460 ++wlen; 6461 } 6462 6463 return wordnr; 6464 } 6465 6466 /* 6467 * Copy "fword" to "cword", fixing case according to "flags". 6468 */ 6469 static void 6470 make_case_word(char_u *fword, char_u *cword, int flags) 6471 { 6472 if (flags & WF_ALLCAP) 6473 /* Make it all upper-case */ 6474 allcap_copy(fword, cword); 6475 else if (flags & WF_ONECAP) 6476 /* Make the first letter upper-case */ 6477 onecap_copy(fword, cword, TRUE); 6478 else 6479 /* Use goodword as-is. */ 6480 STRCPY(cword, fword); 6481 } 6482 6483 6484 /* 6485 * Return TRUE if "c1" and "c2" are similar characters according to the MAP 6486 * lines in the .aff file. 6487 */ 6488 static int 6489 similar_chars(slang_T *slang, int c1, int c2) 6490 { 6491 int m1, m2; 6492 char_u buf[MB_MAXBYTES + 1]; 6493 hashitem_T *hi; 6494 6495 if (c1 >= 256) 6496 { 6497 buf[mb_char2bytes(c1, buf)] = 0; 6498 hi = hash_find(&slang->sl_map_hash, buf); 6499 if (HASHITEM_EMPTY(hi)) 6500 m1 = 0; 6501 else 6502 m1 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1); 6503 } 6504 else 6505 m1 = slang->sl_map_array[c1]; 6506 if (m1 == 0) 6507 return FALSE; 6508 6509 6510 if (c2 >= 256) 6511 { 6512 buf[mb_char2bytes(c2, buf)] = 0; 6513 hi = hash_find(&slang->sl_map_hash, buf); 6514 if (HASHITEM_EMPTY(hi)) 6515 m2 = 0; 6516 else 6517 m2 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1); 6518 } 6519 else 6520 m2 = slang->sl_map_array[c2]; 6521 6522 return m1 == m2; 6523 } 6524 6525 /* 6526 * Add a suggestion to the list of suggestions. 6527 * For a suggestion that is already in the list the lowest score is remembered. 6528 */ 6529 static void 6530 add_suggestion( 6531 suginfo_T *su, 6532 garray_T *gap, /* either su_ga or su_sga */ 6533 char_u *goodword, 6534 int badlenarg, /* len of bad word replaced with "goodword" */ 6535 int score, 6536 int altscore, 6537 int had_bonus, /* value for st_had_bonus */ 6538 slang_T *slang, /* language for sound folding */ 6539 int maxsf) /* su_maxscore applies to soundfold score, 6540 su_sfmaxscore to the total score. */ 6541 { 6542 int goodlen; /* len of goodword changed */ 6543 int badlen; /* len of bad word changed */ 6544 suggest_T *stp; 6545 suggest_T new_sug; 6546 int i; 6547 char_u *pgood, *pbad; 6548 6549 /* Minimize "badlen" for consistency. Avoids that changing "the the" to 6550 * "thee the" is added next to changing the first "the" the "thee". */ 6551 pgood = goodword + STRLEN(goodword); 6552 pbad = su->su_badptr + badlenarg; 6553 for (;;) 6554 { 6555 goodlen = (int)(pgood - goodword); 6556 badlen = (int)(pbad - su->su_badptr); 6557 if (goodlen <= 0 || badlen <= 0) 6558 break; 6559 MB_PTR_BACK(goodword, pgood); 6560 MB_PTR_BACK(su->su_badptr, pbad); 6561 if (has_mbyte) 6562 { 6563 if (mb_ptr2char(pgood) != mb_ptr2char(pbad)) 6564 break; 6565 } 6566 else if (*pgood != *pbad) 6567 break; 6568 } 6569 6570 if (badlen == 0 && goodlen == 0) 6571 /* goodword doesn't change anything; may happen for "the the" changing 6572 * the first "the" to itself. */ 6573 return; 6574 6575 if (gap->ga_len == 0) 6576 i = -1; 6577 else 6578 { 6579 /* Check if the word is already there. Also check the length that is 6580 * being replaced "thes," -> "these" is a different suggestion from 6581 * "thes" -> "these". */ 6582 stp = &SUG(*gap, 0); 6583 for (i = gap->ga_len; --i >= 0; ++stp) 6584 if (stp->st_wordlen == goodlen 6585 && stp->st_orglen == badlen 6586 && STRNCMP(stp->st_word, goodword, goodlen) == 0) 6587 { 6588 /* 6589 * Found it. Remember the word with the lowest score. 6590 */ 6591 if (stp->st_slang == NULL) 6592 stp->st_slang = slang; 6593 6594 new_sug.st_score = score; 6595 new_sug.st_altscore = altscore; 6596 new_sug.st_had_bonus = had_bonus; 6597 6598 if (stp->st_had_bonus != had_bonus) 6599 { 6600 /* Only one of the two had the soundalike score computed. 6601 * Need to do that for the other one now, otherwise the 6602 * scores can't be compared. This happens because 6603 * suggest_try_change() doesn't compute the soundalike 6604 * word to keep it fast, while some special methods set 6605 * the soundalike score to zero. */ 6606 if (had_bonus) 6607 rescore_one(su, stp); 6608 else 6609 { 6610 new_sug.st_word = stp->st_word; 6611 new_sug.st_wordlen = stp->st_wordlen; 6612 new_sug.st_slang = stp->st_slang; 6613 new_sug.st_orglen = badlen; 6614 rescore_one(su, &new_sug); 6615 } 6616 } 6617 6618 if (stp->st_score > new_sug.st_score) 6619 { 6620 stp->st_score = new_sug.st_score; 6621 stp->st_altscore = new_sug.st_altscore; 6622 stp->st_had_bonus = new_sug.st_had_bonus; 6623 } 6624 break; 6625 } 6626 } 6627 6628 if (i < 0 && ga_grow(gap, 1) == OK) 6629 { 6630 /* Add a suggestion. */ 6631 stp = &SUG(*gap, gap->ga_len); 6632 stp->st_word = vim_strnsave(goodword, goodlen); 6633 if (stp->st_word != NULL) 6634 { 6635 stp->st_wordlen = goodlen; 6636 stp->st_score = score; 6637 stp->st_altscore = altscore; 6638 stp->st_had_bonus = had_bonus; 6639 stp->st_orglen = badlen; 6640 stp->st_slang = slang; 6641 ++gap->ga_len; 6642 6643 /* If we have too many suggestions now, sort the list and keep 6644 * the best suggestions. */ 6645 if (gap->ga_len > SUG_MAX_COUNT(su)) 6646 { 6647 if (maxsf) 6648 su->su_sfmaxscore = cleanup_suggestions(gap, 6649 su->su_sfmaxscore, SUG_CLEAN_COUNT(su)); 6650 else 6651 su->su_maxscore = cleanup_suggestions(gap, 6652 su->su_maxscore, SUG_CLEAN_COUNT(su)); 6653 } 6654 } 6655 } 6656 } 6657 6658 /* 6659 * Suggestions may in fact be flagged as errors. Esp. for banned words and 6660 * for split words, such as "the the". Remove these from the list here. 6661 */ 6662 static void 6663 check_suggestions( 6664 suginfo_T *su, 6665 garray_T *gap) /* either su_ga or su_sga */ 6666 { 6667 suggest_T *stp; 6668 int i; 6669 char_u longword[MAXWLEN + 1]; 6670 int len; 6671 hlf_T attr; 6672 6673 stp = &SUG(*gap, 0); 6674 for (i = gap->ga_len - 1; i >= 0; --i) 6675 { 6676 /* Need to append what follows to check for "the the". */ 6677 vim_strncpy(longword, stp[i].st_word, MAXWLEN); 6678 len = stp[i].st_wordlen; 6679 vim_strncpy(longword + len, su->su_badptr + stp[i].st_orglen, 6680 MAXWLEN - len); 6681 attr = HLF_COUNT; 6682 (void)spell_check(curwin, longword, &attr, NULL, FALSE); 6683 if (attr != HLF_COUNT) 6684 { 6685 /* Remove this entry. */ 6686 vim_free(stp[i].st_word); 6687 --gap->ga_len; 6688 if (i < gap->ga_len) 6689 mch_memmove(stp + i, stp + i + 1, 6690 sizeof(suggest_T) * (gap->ga_len - i)); 6691 } 6692 } 6693 } 6694 6695 6696 /* 6697 * Add a word to be banned. 6698 */ 6699 static void 6700 add_banned( 6701 suginfo_T *su, 6702 char_u *word) 6703 { 6704 char_u *s; 6705 hash_T hash; 6706 hashitem_T *hi; 6707 6708 hash = hash_hash(word); 6709 hi = hash_lookup(&su->su_banned, word, hash); 6710 if (HASHITEM_EMPTY(hi)) 6711 { 6712 s = vim_strsave(word); 6713 if (s != NULL) 6714 hash_add_item(&su->su_banned, hi, s, hash); 6715 } 6716 } 6717 6718 /* 6719 * Recompute the score for all suggestions if sound-folding is possible. This 6720 * is slow, thus only done for the final results. 6721 */ 6722 static void 6723 rescore_suggestions(suginfo_T *su) 6724 { 6725 int i; 6726 6727 if (su->su_sallang != NULL) 6728 for (i = 0; i < su->su_ga.ga_len; ++i) 6729 rescore_one(su, &SUG(su->su_ga, i)); 6730 } 6731 6732 /* 6733 * Recompute the score for one suggestion if sound-folding is possible. 6734 */ 6735 static void 6736 rescore_one(suginfo_T *su, suggest_T *stp) 6737 { 6738 slang_T *slang = stp->st_slang; 6739 char_u sal_badword[MAXWLEN]; 6740 char_u *p; 6741 6742 /* Only rescore suggestions that have no sal score yet and do have a 6743 * language. */ 6744 if (slang != NULL && slang->sl_sal.ga_len > 0 && !stp->st_had_bonus) 6745 { 6746 if (slang == su->su_sallang) 6747 p = su->su_sal_badword; 6748 else 6749 { 6750 spell_soundfold(slang, su->su_fbadword, TRUE, sal_badword); 6751 p = sal_badword; 6752 } 6753 6754 stp->st_altscore = stp_sal_score(stp, su, slang, p); 6755 if (stp->st_altscore == SCORE_MAXMAX) 6756 stp->st_altscore = SCORE_BIG; 6757 stp->st_score = RESCORE(stp->st_score, stp->st_altscore); 6758 stp->st_had_bonus = TRUE; 6759 } 6760 } 6761 6762 static int 6763 #ifdef __BORLANDC__ 6764 _RTLENTRYF 6765 #endif 6766 sug_compare(const void *s1, const void *s2); 6767 6768 /* 6769 * Function given to qsort() to sort the suggestions on st_score. 6770 * First on "st_score", then "st_altscore" then alphabetically. 6771 */ 6772 static int 6773 #ifdef __BORLANDC__ 6774 _RTLENTRYF 6775 #endif 6776 sug_compare(const void *s1, const void *s2) 6777 { 6778 suggest_T *p1 = (suggest_T *)s1; 6779 suggest_T *p2 = (suggest_T *)s2; 6780 int n = p1->st_score - p2->st_score; 6781 6782 if (n == 0) 6783 { 6784 n = p1->st_altscore - p2->st_altscore; 6785 if (n == 0) 6786 n = STRICMP(p1->st_word, p2->st_word); 6787 } 6788 return n; 6789 } 6790 6791 /* 6792 * Cleanup the suggestions: 6793 * - Sort on score. 6794 * - Remove words that won't be displayed. 6795 * Returns the maximum score in the list or "maxscore" unmodified. 6796 */ 6797 static int 6798 cleanup_suggestions( 6799 garray_T *gap, 6800 int maxscore, 6801 int keep) /* nr of suggestions to keep */ 6802 { 6803 suggest_T *stp = &SUG(*gap, 0); 6804 int i; 6805 6806 /* Sort the list. */ 6807 qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare); 6808 6809 /* Truncate the list to the number of suggestions that will be displayed. */ 6810 if (gap->ga_len > keep) 6811 { 6812 for (i = keep; i < gap->ga_len; ++i) 6813 vim_free(stp[i].st_word); 6814 gap->ga_len = keep; 6815 return stp[keep - 1].st_score; 6816 } 6817 return maxscore; 6818 } 6819 6820 #if defined(FEAT_EVAL) || defined(PROTO) 6821 /* 6822 * Soundfold a string, for soundfold(). 6823 * Result is in allocated memory, NULL for an error. 6824 */ 6825 char_u * 6826 eval_soundfold(char_u *word) 6827 { 6828 langp_T *lp; 6829 char_u sound[MAXWLEN]; 6830 int lpi; 6831 6832 if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) 6833 /* Use the sound-folding of the first language that supports it. */ 6834 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 6835 { 6836 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 6837 if (lp->lp_slang->sl_sal.ga_len > 0) 6838 { 6839 /* soundfold the word */ 6840 spell_soundfold(lp->lp_slang, word, FALSE, sound); 6841 return vim_strsave(sound); 6842 } 6843 } 6844 6845 /* No language with sound folding, return word as-is. */ 6846 return vim_strsave(word); 6847 } 6848 #endif 6849 6850 /* 6851 * Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". 6852 * 6853 * There are many ways to turn a word into a sound-a-like representation. The 6854 * oldest is Soundex (1918!). A nice overview can be found in "Approximate 6855 * swedish name matching - survey and test of different algorithms" by Klas 6856 * Erikson. 6857 * 6858 * We support two methods: 6859 * 1. SOFOFROM/SOFOTO do a simple character mapping. 6860 * 2. SAL items define a more advanced sound-folding (and much slower). 6861 */ 6862 void 6863 spell_soundfold( 6864 slang_T *slang, 6865 char_u *inword, 6866 int folded, /* "inword" is already case-folded */ 6867 char_u *res) 6868 { 6869 char_u fword[MAXWLEN]; 6870 char_u *word; 6871 6872 if (slang->sl_sofo) 6873 /* SOFOFROM and SOFOTO used */ 6874 spell_soundfold_sofo(slang, inword, res); 6875 else 6876 { 6877 /* SAL items used. Requires the word to be case-folded. */ 6878 if (folded) 6879 word = inword; 6880 else 6881 { 6882 (void)spell_casefold(inword, (int)STRLEN(inword), fword, MAXWLEN); 6883 word = fword; 6884 } 6885 6886 if (has_mbyte) 6887 spell_soundfold_wsal(slang, word, res); 6888 else 6889 spell_soundfold_sal(slang, word, res); 6890 } 6891 } 6892 6893 /* 6894 * Perform sound folding of "inword" into "res" according to SOFOFROM and 6895 * SOFOTO lines. 6896 */ 6897 static void 6898 spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res) 6899 { 6900 char_u *s; 6901 int ri = 0; 6902 int c; 6903 6904 if (has_mbyte) 6905 { 6906 int prevc = 0; 6907 int *ip; 6908 6909 /* The sl_sal_first[] table contains the translation for chars up to 6910 * 255, sl_sal the rest. */ 6911 for (s = inword; *s != NUL; ) 6912 { 6913 c = mb_cptr2char_adv(&s); 6914 if (enc_utf8 ? utf_class(c) == 0 : VIM_ISWHITE(c)) 6915 c = ' '; 6916 else if (c < 256) 6917 c = slang->sl_sal_first[c]; 6918 else 6919 { 6920 ip = ((int **)slang->sl_sal.ga_data)[c & 0xff]; 6921 if (ip == NULL) /* empty list, can't match */ 6922 c = NUL; 6923 else 6924 for (;;) /* find "c" in the list */ 6925 { 6926 if (*ip == 0) /* not found */ 6927 { 6928 c = NUL; 6929 break; 6930 } 6931 if (*ip == c) /* match! */ 6932 { 6933 c = ip[1]; 6934 break; 6935 } 6936 ip += 2; 6937 } 6938 } 6939 6940 if (c != NUL && c != prevc) 6941 { 6942 ri += mb_char2bytes(c, res + ri); 6943 if (ri + MB_MAXBYTES > MAXWLEN) 6944 break; 6945 prevc = c; 6946 } 6947 } 6948 } 6949 else 6950 { 6951 /* The sl_sal_first[] table contains the translation. */ 6952 for (s = inword; (c = *s) != NUL; ++s) 6953 { 6954 if (VIM_ISWHITE(c)) 6955 c = ' '; 6956 else 6957 c = slang->sl_sal_first[c]; 6958 if (c != NUL && (ri == 0 || res[ri - 1] != c)) 6959 res[ri++] = c; 6960 } 6961 } 6962 6963 res[ri] = NUL; 6964 } 6965 6966 static void 6967 spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res) 6968 { 6969 salitem_T *smp; 6970 char_u word[MAXWLEN]; 6971 char_u *s = inword; 6972 char_u *t; 6973 char_u *pf; 6974 int i, j, z; 6975 int reslen; 6976 int n, k = 0; 6977 int z0; 6978 int k0; 6979 int n0; 6980 int c; 6981 int pri; 6982 int p0 = -333; 6983 int c0; 6984 6985 /* Remove accents, if wanted. We actually remove all non-word characters. 6986 * But keep white space. We need a copy, the word may be changed here. */ 6987 if (slang->sl_rem_accents) 6988 { 6989 t = word; 6990 while (*s != NUL) 6991 { 6992 if (VIM_ISWHITE(*s)) 6993 { 6994 *t++ = ' '; 6995 s = skipwhite(s); 6996 } 6997 else 6998 { 6999 if (spell_iswordp_nmw(s, curwin)) 7000 *t++ = *s; 7001 ++s; 7002 } 7003 } 7004 *t = NUL; 7005 } 7006 else 7007 vim_strncpy(word, s, MAXWLEN - 1); 7008 7009 smp = (salitem_T *)slang->sl_sal.ga_data; 7010 7011 /* 7012 * This comes from Aspell phonet.cpp. Converted from C++ to C. 7013 * Changed to keep spaces. 7014 */ 7015 i = reslen = z = 0; 7016 while ((c = word[i]) != NUL) 7017 { 7018 /* Start with the first rule that has the character in the word. */ 7019 n = slang->sl_sal_first[c]; 7020 z0 = 0; 7021 7022 if (n >= 0) 7023 { 7024 /* check all rules for the same letter */ 7025 for (; (s = smp[n].sm_lead)[0] == c; ++n) 7026 { 7027 /* Quickly skip entries that don't match the word. Most 7028 * entries are less then three chars, optimize for that. */ 7029 k = smp[n].sm_leadlen; 7030 if (k > 1) 7031 { 7032 if (word[i + 1] != s[1]) 7033 continue; 7034 if (k > 2) 7035 { 7036 for (j = 2; j < k; ++j) 7037 if (word[i + j] != s[j]) 7038 break; 7039 if (j < k) 7040 continue; 7041 } 7042 } 7043 7044 if ((pf = smp[n].sm_oneof) != NULL) 7045 { 7046 /* Check for match with one of the chars in "sm_oneof". */ 7047 while (*pf != NUL && *pf != word[i + k]) 7048 ++pf; 7049 if (*pf == NUL) 7050 continue; 7051 ++k; 7052 } 7053 s = smp[n].sm_rules; 7054 pri = 5; /* default priority */ 7055 7056 p0 = *s; 7057 k0 = k; 7058 while (*s == '-' && k > 1) 7059 { 7060 k--; 7061 s++; 7062 } 7063 if (*s == '<') 7064 s++; 7065 if (VIM_ISDIGIT(*s)) 7066 { 7067 /* determine priority */ 7068 pri = *s - '0'; 7069 s++; 7070 } 7071 if (*s == '^' && *(s + 1) == '^') 7072 s++; 7073 7074 if (*s == NUL 7075 || (*s == '^' 7076 && (i == 0 || !(word[i - 1] == ' ' 7077 || spell_iswordp(word + i - 1, curwin))) 7078 && (*(s + 1) != '$' 7079 || (!spell_iswordp(word + i + k0, curwin)))) 7080 || (*s == '$' && i > 0 7081 && spell_iswordp(word + i - 1, curwin) 7082 && (!spell_iswordp(word + i + k0, curwin)))) 7083 { 7084 /* search for followup rules, if: */ 7085 /* followup and k > 1 and NO '-' in searchstring */ 7086 c0 = word[i + k - 1]; 7087 n0 = slang->sl_sal_first[c0]; 7088 7089 if (slang->sl_followup && k > 1 && n0 >= 0 7090 && p0 != '-' && word[i + k] != NUL) 7091 { 7092 /* test follow-up rule for "word[i + k]" */ 7093 for ( ; (s = smp[n0].sm_lead)[0] == c0; ++n0) 7094 { 7095 /* Quickly skip entries that don't match the word. 7096 * */ 7097 k0 = smp[n0].sm_leadlen; 7098 if (k0 > 1) 7099 { 7100 if (word[i + k] != s[1]) 7101 continue; 7102 if (k0 > 2) 7103 { 7104 pf = word + i + k + 1; 7105 for (j = 2; j < k0; ++j) 7106 if (*pf++ != s[j]) 7107 break; 7108 if (j < k0) 7109 continue; 7110 } 7111 } 7112 k0 += k - 1; 7113 7114 if ((pf = smp[n0].sm_oneof) != NULL) 7115 { 7116 /* Check for match with one of the chars in 7117 * "sm_oneof". */ 7118 while (*pf != NUL && *pf != word[i + k0]) 7119 ++pf; 7120 if (*pf == NUL) 7121 continue; 7122 ++k0; 7123 } 7124 7125 p0 = 5; 7126 s = smp[n0].sm_rules; 7127 while (*s == '-') 7128 { 7129 /* "k0" gets NOT reduced because 7130 * "if (k0 == k)" */ 7131 s++; 7132 } 7133 if (*s == '<') 7134 s++; 7135 if (VIM_ISDIGIT(*s)) 7136 { 7137 p0 = *s - '0'; 7138 s++; 7139 } 7140 7141 if (*s == NUL 7142 /* *s == '^' cuts */ 7143 || (*s == '$' 7144 && !spell_iswordp(word + i + k0, 7145 curwin))) 7146 { 7147 if (k0 == k) 7148 /* this is just a piece of the string */ 7149 continue; 7150 7151 if (p0 < pri) 7152 /* priority too low */ 7153 continue; 7154 /* rule fits; stop search */ 7155 break; 7156 } 7157 } 7158 7159 if (p0 >= pri && smp[n0].sm_lead[0] == c0) 7160 continue; 7161 } 7162 7163 /* replace string */ 7164 s = smp[n].sm_to; 7165 if (s == NULL) 7166 s = (char_u *)""; 7167 pf = smp[n].sm_rules; 7168 p0 = (vim_strchr(pf, '<') != NULL) ? 1 : 0; 7169 if (p0 == 1 && z == 0) 7170 { 7171 /* rule with '<' is used */ 7172 if (reslen > 0 && *s != NUL && (res[reslen - 1] == c 7173 || res[reslen - 1] == *s)) 7174 reslen--; 7175 z0 = 1; 7176 z = 1; 7177 k0 = 0; 7178 while (*s != NUL && word[i + k0] != NUL) 7179 { 7180 word[i + k0] = *s; 7181 k0++; 7182 s++; 7183 } 7184 if (k > k0) 7185 STRMOVE(word + i + k0, word + i + k); 7186 7187 /* new "actual letter" */ 7188 c = word[i]; 7189 } 7190 else 7191 { 7192 /* no '<' rule used */ 7193 i += k - 1; 7194 z = 0; 7195 while (*s != NUL && s[1] != NUL && reslen < MAXWLEN) 7196 { 7197 if (reslen == 0 || res[reslen - 1] != *s) 7198 res[reslen++] = *s; 7199 s++; 7200 } 7201 /* new "actual letter" */ 7202 c = *s; 7203 if (strstr((char *)pf, "^^") != NULL) 7204 { 7205 if (c != NUL) 7206 res[reslen++] = c; 7207 STRMOVE(word, word + i + 1); 7208 i = 0; 7209 z0 = 1; 7210 } 7211 } 7212 break; 7213 } 7214 } 7215 } 7216 else if (VIM_ISWHITE(c)) 7217 { 7218 c = ' '; 7219 k = 1; 7220 } 7221 7222 if (z0 == 0) 7223 { 7224 if (k && !p0 && reslen < MAXWLEN && c != NUL 7225 && (!slang->sl_collapse || reslen == 0 7226 || res[reslen - 1] != c)) 7227 /* condense only double letters */ 7228 res[reslen++] = c; 7229 7230 i++; 7231 z = 0; 7232 k = 0; 7233 } 7234 } 7235 7236 res[reslen] = NUL; 7237 } 7238 7239 /* 7240 * Turn "inword" into its sound-a-like equivalent in "res[MAXWLEN]". 7241 * Multi-byte version of spell_soundfold(). 7242 */ 7243 static void 7244 spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res) 7245 { 7246 salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data; 7247 int word[MAXWLEN]; 7248 int wres[MAXWLEN]; 7249 int l; 7250 char_u *s; 7251 int *ws; 7252 char_u *t; 7253 int *pf; 7254 int i, j, z; 7255 int reslen; 7256 int n, k = 0; 7257 int z0; 7258 int k0; 7259 int n0; 7260 int c; 7261 int pri; 7262 int p0 = -333; 7263 int c0; 7264 int did_white = FALSE; 7265 int wordlen; 7266 7267 7268 /* 7269 * Convert the multi-byte string to a wide-character string. 7270 * Remove accents, if wanted. We actually remove all non-word characters. 7271 * But keep white space. 7272 */ 7273 wordlen = 0; 7274 for (s = inword; *s != NUL; ) 7275 { 7276 t = s; 7277 c = mb_cptr2char_adv(&s); 7278 if (slang->sl_rem_accents) 7279 { 7280 if (enc_utf8 ? utf_class(c) == 0 : VIM_ISWHITE(c)) 7281 { 7282 if (did_white) 7283 continue; 7284 c = ' '; 7285 did_white = TRUE; 7286 } 7287 else 7288 { 7289 did_white = FALSE; 7290 if (!spell_iswordp_nmw(t, curwin)) 7291 continue; 7292 } 7293 } 7294 word[wordlen++] = c; 7295 } 7296 word[wordlen] = NUL; 7297 7298 /* 7299 * This algorithm comes from Aspell phonet.cpp. 7300 * Converted from C++ to C. Added support for multi-byte chars. 7301 * Changed to keep spaces. 7302 */ 7303 i = reslen = z = 0; 7304 while ((c = word[i]) != NUL) 7305 { 7306 /* Start with the first rule that has the character in the word. */ 7307 n = slang->sl_sal_first[c & 0xff]; 7308 z0 = 0; 7309 7310 if (n >= 0) 7311 { 7312 /* Check all rules for the same index byte. 7313 * If c is 0x300 need extra check for the end of the array, as 7314 * (c & 0xff) is NUL. */ 7315 for (; ((ws = smp[n].sm_lead_w)[0] & 0xff) == (c & 0xff) 7316 && ws[0] != NUL; ++n) 7317 { 7318 /* Quickly skip entries that don't match the word. Most 7319 * entries are less then three chars, optimize for that. */ 7320 if (c != ws[0]) 7321 continue; 7322 k = smp[n].sm_leadlen; 7323 if (k > 1) 7324 { 7325 if (word[i + 1] != ws[1]) 7326 continue; 7327 if (k > 2) 7328 { 7329 for (j = 2; j < k; ++j) 7330 if (word[i + j] != ws[j]) 7331 break; 7332 if (j < k) 7333 continue; 7334 } 7335 } 7336 7337 if ((pf = smp[n].sm_oneof_w) != NULL) 7338 { 7339 /* Check for match with one of the chars in "sm_oneof". */ 7340 while (*pf != NUL && *pf != word[i + k]) 7341 ++pf; 7342 if (*pf == NUL) 7343 continue; 7344 ++k; 7345 } 7346 s = smp[n].sm_rules; 7347 pri = 5; /* default priority */ 7348 7349 p0 = *s; 7350 k0 = k; 7351 while (*s == '-' && k > 1) 7352 { 7353 k--; 7354 s++; 7355 } 7356 if (*s == '<') 7357 s++; 7358 if (VIM_ISDIGIT(*s)) 7359 { 7360 /* determine priority */ 7361 pri = *s - '0'; 7362 s++; 7363 } 7364 if (*s == '^' && *(s + 1) == '^') 7365 s++; 7366 7367 if (*s == NUL 7368 || (*s == '^' 7369 && (i == 0 || !(word[i - 1] == ' ' 7370 || spell_iswordp_w(word + i - 1, curwin))) 7371 && (*(s + 1) != '$' 7372 || (!spell_iswordp_w(word + i + k0, curwin)))) 7373 || (*s == '$' && i > 0 7374 && spell_iswordp_w(word + i - 1, curwin) 7375 && (!spell_iswordp_w(word + i + k0, curwin)))) 7376 { 7377 /* search for followup rules, if: */ 7378 /* followup and k > 1 and NO '-' in searchstring */ 7379 c0 = word[i + k - 1]; 7380 n0 = slang->sl_sal_first[c0 & 0xff]; 7381 7382 if (slang->sl_followup && k > 1 && n0 >= 0 7383 && p0 != '-' && word[i + k] != NUL) 7384 { 7385 /* Test follow-up rule for "word[i + k]"; loop over 7386 * all entries with the same index byte. */ 7387 for ( ; ((ws = smp[n0].sm_lead_w)[0] & 0xff) 7388 == (c0 & 0xff); ++n0) 7389 { 7390 /* Quickly skip entries that don't match the word. 7391 */ 7392 if (c0 != ws[0]) 7393 continue; 7394 k0 = smp[n0].sm_leadlen; 7395 if (k0 > 1) 7396 { 7397 if (word[i + k] != ws[1]) 7398 continue; 7399 if (k0 > 2) 7400 { 7401 pf = word + i + k + 1; 7402 for (j = 2; j < k0; ++j) 7403 if (*pf++ != ws[j]) 7404 break; 7405 if (j < k0) 7406 continue; 7407 } 7408 } 7409 k0 += k - 1; 7410 7411 if ((pf = smp[n0].sm_oneof_w) != NULL) 7412 { 7413 /* Check for match with one of the chars in 7414 * "sm_oneof". */ 7415 while (*pf != NUL && *pf != word[i + k0]) 7416 ++pf; 7417 if (*pf == NUL) 7418 continue; 7419 ++k0; 7420 } 7421 7422 p0 = 5; 7423 s = smp[n0].sm_rules; 7424 while (*s == '-') 7425 { 7426 /* "k0" gets NOT reduced because 7427 * "if (k0 == k)" */ 7428 s++; 7429 } 7430 if (*s == '<') 7431 s++; 7432 if (VIM_ISDIGIT(*s)) 7433 { 7434 p0 = *s - '0'; 7435 s++; 7436 } 7437 7438 if (*s == NUL 7439 /* *s == '^' cuts */ 7440 || (*s == '$' 7441 && !spell_iswordp_w(word + i + k0, 7442 curwin))) 7443 { 7444 if (k0 == k) 7445 /* this is just a piece of the string */ 7446 continue; 7447 7448 if (p0 < pri) 7449 /* priority too low */ 7450 continue; 7451 /* rule fits; stop search */ 7452 break; 7453 } 7454 } 7455 7456 if (p0 >= pri && (smp[n0].sm_lead_w[0] & 0xff) 7457 == (c0 & 0xff)) 7458 continue; 7459 } 7460 7461 /* replace string */ 7462 ws = smp[n].sm_to_w; 7463 s = smp[n].sm_rules; 7464 p0 = (vim_strchr(s, '<') != NULL) ? 1 : 0; 7465 if (p0 == 1 && z == 0) 7466 { 7467 /* rule with '<' is used */ 7468 if (reslen > 0 && ws != NULL && *ws != NUL 7469 && (wres[reslen - 1] == c 7470 || wres[reslen - 1] == *ws)) 7471 reslen--; 7472 z0 = 1; 7473 z = 1; 7474 k0 = 0; 7475 if (ws != NULL) 7476 while (*ws != NUL && word[i + k0] != NUL) 7477 { 7478 word[i + k0] = *ws; 7479 k0++; 7480 ws++; 7481 } 7482 if (k > k0) 7483 mch_memmove(word + i + k0, word + i + k, 7484 sizeof(int) * (wordlen - (i + k) + 1)); 7485 7486 /* new "actual letter" */ 7487 c = word[i]; 7488 } 7489 else 7490 { 7491 /* no '<' rule used */ 7492 i += k - 1; 7493 z = 0; 7494 if (ws != NULL) 7495 while (*ws != NUL && ws[1] != NUL 7496 && reslen < MAXWLEN) 7497 { 7498 if (reslen == 0 || wres[reslen - 1] != *ws) 7499 wres[reslen++] = *ws; 7500 ws++; 7501 } 7502 /* new "actual letter" */ 7503 if (ws == NULL) 7504 c = NUL; 7505 else 7506 c = *ws; 7507 if (strstr((char *)s, "^^") != NULL) 7508 { 7509 if (c != NUL) 7510 wres[reslen++] = c; 7511 mch_memmove(word, word + i + 1, 7512 sizeof(int) * (wordlen - (i + 1) + 1)); 7513 i = 0; 7514 z0 = 1; 7515 } 7516 } 7517 break; 7518 } 7519 } 7520 } 7521 else if (VIM_ISWHITE(c)) 7522 { 7523 c = ' '; 7524 k = 1; 7525 } 7526 7527 if (z0 == 0) 7528 { 7529 if (k && !p0 && reslen < MAXWLEN && c != NUL 7530 && (!slang->sl_collapse || reslen == 0 7531 || wres[reslen - 1] != c)) 7532 /* condense only double letters */ 7533 wres[reslen++] = c; 7534 7535 i++; 7536 z = 0; 7537 k = 0; 7538 } 7539 } 7540 7541 /* Convert wide characters in "wres" to a multi-byte string in "res". */ 7542 l = 0; 7543 for (n = 0; n < reslen; ++n) 7544 { 7545 l += mb_char2bytes(wres[n], res + l); 7546 if (l + MB_MAXBYTES > MAXWLEN) 7547 break; 7548 } 7549 res[l] = NUL; 7550 } 7551 7552 /* 7553 * Compute a score for two sound-a-like words. 7554 * This permits up to two inserts/deletes/swaps/etc. to keep things fast. 7555 * Instead of a generic loop we write out the code. That keeps it fast by 7556 * avoiding checks that will not be possible. 7557 */ 7558 static int 7559 soundalike_score( 7560 char_u *goodstart, /* sound-folded good word */ 7561 char_u *badstart) /* sound-folded bad word */ 7562 { 7563 char_u *goodsound = goodstart; 7564 char_u *badsound = badstart; 7565 int goodlen; 7566 int badlen; 7567 int n; 7568 char_u *pl, *ps; 7569 char_u *pl2, *ps2; 7570 int score = 0; 7571 7572 /* Adding/inserting "*" at the start (word starts with vowel) shouldn't be 7573 * counted so much, vowels halfway the word aren't counted at all. */ 7574 if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound) 7575 { 7576 if ((badsound[0] == NUL && goodsound[1] == NUL) 7577 || (goodsound[0] == NUL && badsound[1] == NUL)) 7578 /* changing word with vowel to word without a sound */ 7579 return SCORE_DEL; 7580 if (badsound[0] == NUL || goodsound[0] == NUL) 7581 /* more than two changes */ 7582 return SCORE_MAXMAX; 7583 7584 if (badsound[1] == goodsound[1] 7585 || (badsound[1] != NUL 7586 && goodsound[1] != NUL 7587 && badsound[2] == goodsound[2])) 7588 { 7589 /* handle like a substitute */ 7590 } 7591 else 7592 { 7593 score = 2 * SCORE_DEL / 3; 7594 if (*badsound == '*') 7595 ++badsound; 7596 else 7597 ++goodsound; 7598 } 7599 } 7600 7601 goodlen = (int)STRLEN(goodsound); 7602 badlen = (int)STRLEN(badsound); 7603 7604 /* Return quickly if the lengths are too different to be fixed by two 7605 * changes. */ 7606 n = goodlen - badlen; 7607 if (n < -2 || n > 2) 7608 return SCORE_MAXMAX; 7609 7610 if (n > 0) 7611 { 7612 pl = goodsound; /* goodsound is longest */ 7613 ps = badsound; 7614 } 7615 else 7616 { 7617 pl = badsound; /* badsound is longest */ 7618 ps = goodsound; 7619 } 7620 7621 /* Skip over the identical part. */ 7622 while (*pl == *ps && *pl != NUL) 7623 { 7624 ++pl; 7625 ++ps; 7626 } 7627 7628 switch (n) 7629 { 7630 case -2: 7631 case 2: 7632 /* 7633 * Must delete two characters from "pl". 7634 */ 7635 ++pl; /* first delete */ 7636 while (*pl == *ps) 7637 { 7638 ++pl; 7639 ++ps; 7640 } 7641 /* strings must be equal after second delete */ 7642 if (STRCMP(pl + 1, ps) == 0) 7643 return score + SCORE_DEL * 2; 7644 7645 /* Failed to compare. */ 7646 break; 7647 7648 case -1: 7649 case 1: 7650 /* 7651 * Minimal one delete from "pl" required. 7652 */ 7653 7654 /* 1: delete */ 7655 pl2 = pl + 1; 7656 ps2 = ps; 7657 while (*pl2 == *ps2) 7658 { 7659 if (*pl2 == NUL) /* reached the end */ 7660 return score + SCORE_DEL; 7661 ++pl2; 7662 ++ps2; 7663 } 7664 7665 /* 2: delete then swap, then rest must be equal */ 7666 if (pl2[0] == ps2[1] && pl2[1] == ps2[0] 7667 && STRCMP(pl2 + 2, ps2 + 2) == 0) 7668 return score + SCORE_DEL + SCORE_SWAP; 7669 7670 /* 3: delete then substitute, then the rest must be equal */ 7671 if (STRCMP(pl2 + 1, ps2 + 1) == 0) 7672 return score + SCORE_DEL + SCORE_SUBST; 7673 7674 /* 4: first swap then delete */ 7675 if (pl[0] == ps[1] && pl[1] == ps[0]) 7676 { 7677 pl2 = pl + 2; /* swap, skip two chars */ 7678 ps2 = ps + 2; 7679 while (*pl2 == *ps2) 7680 { 7681 ++pl2; 7682 ++ps2; 7683 } 7684 /* delete a char and then strings must be equal */ 7685 if (STRCMP(pl2 + 1, ps2) == 0) 7686 return score + SCORE_SWAP + SCORE_DEL; 7687 } 7688 7689 /* 5: first substitute then delete */ 7690 pl2 = pl + 1; /* substitute, skip one char */ 7691 ps2 = ps + 1; 7692 while (*pl2 == *ps2) 7693 { 7694 ++pl2; 7695 ++ps2; 7696 } 7697 /* delete a char and then strings must be equal */ 7698 if (STRCMP(pl2 + 1, ps2) == 0) 7699 return score + SCORE_SUBST + SCORE_DEL; 7700 7701 /* Failed to compare. */ 7702 break; 7703 7704 case 0: 7705 /* 7706 * Lengths are equal, thus changes must result in same length: An 7707 * insert is only possible in combination with a delete. 7708 * 1: check if for identical strings 7709 */ 7710 if (*pl == NUL) 7711 return score; 7712 7713 /* 2: swap */ 7714 if (pl[0] == ps[1] && pl[1] == ps[0]) 7715 { 7716 pl2 = pl + 2; /* swap, skip two chars */ 7717 ps2 = ps + 2; 7718 while (*pl2 == *ps2) 7719 { 7720 if (*pl2 == NUL) /* reached the end */ 7721 return score + SCORE_SWAP; 7722 ++pl2; 7723 ++ps2; 7724 } 7725 /* 3: swap and swap again */ 7726 if (pl2[0] == ps2[1] && pl2[1] == ps2[0] 7727 && STRCMP(pl2 + 2, ps2 + 2) == 0) 7728 return score + SCORE_SWAP + SCORE_SWAP; 7729 7730 /* 4: swap and substitute */ 7731 if (STRCMP(pl2 + 1, ps2 + 1) == 0) 7732 return score + SCORE_SWAP + SCORE_SUBST; 7733 } 7734 7735 /* 5: substitute */ 7736 pl2 = pl + 1; 7737 ps2 = ps + 1; 7738 while (*pl2 == *ps2) 7739 { 7740 if (*pl2 == NUL) /* reached the end */ 7741 return score + SCORE_SUBST; 7742 ++pl2; 7743 ++ps2; 7744 } 7745 7746 /* 6: substitute and swap */ 7747 if (pl2[0] == ps2[1] && pl2[1] == ps2[0] 7748 && STRCMP(pl2 + 2, ps2 + 2) == 0) 7749 return score + SCORE_SUBST + SCORE_SWAP; 7750 7751 /* 7: substitute and substitute */ 7752 if (STRCMP(pl2 + 1, ps2 + 1) == 0) 7753 return score + SCORE_SUBST + SCORE_SUBST; 7754 7755 /* 8: insert then delete */ 7756 pl2 = pl; 7757 ps2 = ps + 1; 7758 while (*pl2 == *ps2) 7759 { 7760 ++pl2; 7761 ++ps2; 7762 } 7763 if (STRCMP(pl2 + 1, ps2) == 0) 7764 return score + SCORE_INS + SCORE_DEL; 7765 7766 /* 9: delete then insert */ 7767 pl2 = pl + 1; 7768 ps2 = ps; 7769 while (*pl2 == *ps2) 7770 { 7771 ++pl2; 7772 ++ps2; 7773 } 7774 if (STRCMP(pl2, ps2 + 1) == 0) 7775 return score + SCORE_INS + SCORE_DEL; 7776 7777 /* Failed to compare. */ 7778 break; 7779 } 7780 7781 return SCORE_MAXMAX; 7782 } 7783 7784 /* 7785 * Compute the "edit distance" to turn "badword" into "goodword". The less 7786 * deletes/inserts/substitutes/swaps are required the lower the score. 7787 * 7788 * The algorithm is described by Du and Chang, 1992. 7789 * The implementation of the algorithm comes from Aspell editdist.cpp, 7790 * edit_distance(). It has been converted from C++ to C and modified to 7791 * support multi-byte characters. 7792 */ 7793 static int 7794 spell_edit_score( 7795 slang_T *slang, 7796 char_u *badword, 7797 char_u *goodword) 7798 { 7799 int *cnt; 7800 int badlen, goodlen; /* lengths including NUL */ 7801 int j, i; 7802 int t; 7803 int bc, gc; 7804 int pbc, pgc; 7805 char_u *p; 7806 int wbadword[MAXWLEN]; 7807 int wgoodword[MAXWLEN]; 7808 7809 if (has_mbyte) 7810 { 7811 /* Get the characters from the multi-byte strings and put them in an 7812 * int array for easy access. */ 7813 for (p = badword, badlen = 0; *p != NUL; ) 7814 wbadword[badlen++] = mb_cptr2char_adv(&p); 7815 wbadword[badlen++] = 0; 7816 for (p = goodword, goodlen = 0; *p != NUL; ) 7817 wgoodword[goodlen++] = mb_cptr2char_adv(&p); 7818 wgoodword[goodlen++] = 0; 7819 } 7820 else 7821 { 7822 badlen = (int)STRLEN(badword) + 1; 7823 goodlen = (int)STRLEN(goodword) + 1; 7824 } 7825 7826 /* We use "cnt" as an array: CNT(badword_idx, goodword_idx). */ 7827 #define CNT(a, b) cnt[(a) + (b) * (badlen + 1)] 7828 cnt = (int *)lalloc((long_u)(sizeof(int) * (badlen + 1) * (goodlen + 1)), 7829 TRUE); 7830 if (cnt == NULL) 7831 return 0; /* out of memory */ 7832 7833 CNT(0, 0) = 0; 7834 for (j = 1; j <= goodlen; ++j) 7835 CNT(0, j) = CNT(0, j - 1) + SCORE_INS; 7836 7837 for (i = 1; i <= badlen; ++i) 7838 { 7839 CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL; 7840 for (j = 1; j <= goodlen; ++j) 7841 { 7842 if (has_mbyte) 7843 { 7844 bc = wbadword[i - 1]; 7845 gc = wgoodword[j - 1]; 7846 } 7847 else 7848 { 7849 bc = badword[i - 1]; 7850 gc = goodword[j - 1]; 7851 } 7852 if (bc == gc) 7853 CNT(i, j) = CNT(i - 1, j - 1); 7854 else 7855 { 7856 /* Use a better score when there is only a case difference. */ 7857 if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) 7858 CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1); 7859 else 7860 { 7861 /* For a similar character use SCORE_SIMILAR. */ 7862 if (slang != NULL 7863 && slang->sl_has_map 7864 && similar_chars(slang, gc, bc)) 7865 CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1); 7866 else 7867 CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1); 7868 } 7869 7870 if (i > 1 && j > 1) 7871 { 7872 if (has_mbyte) 7873 { 7874 pbc = wbadword[i - 2]; 7875 pgc = wgoodword[j - 2]; 7876 } 7877 else 7878 { 7879 pbc = badword[i - 2]; 7880 pgc = goodword[j - 2]; 7881 } 7882 if (bc == pgc && pbc == gc) 7883 { 7884 t = SCORE_SWAP + CNT(i - 2, j - 2); 7885 if (t < CNT(i, j)) 7886 CNT(i, j) = t; 7887 } 7888 } 7889 t = SCORE_DEL + CNT(i - 1, j); 7890 if (t < CNT(i, j)) 7891 CNT(i, j) = t; 7892 t = SCORE_INS + CNT(i, j - 1); 7893 if (t < CNT(i, j)) 7894 CNT(i, j) = t; 7895 } 7896 } 7897 } 7898 7899 i = CNT(badlen - 1, goodlen - 1); 7900 vim_free(cnt); 7901 return i; 7902 } 7903 7904 typedef struct 7905 { 7906 int badi; 7907 int goodi; 7908 int score; 7909 } limitscore_T; 7910 7911 /* 7912 * Like spell_edit_score(), but with a limit on the score to make it faster. 7913 * May return SCORE_MAXMAX when the score is higher than "limit". 7914 * 7915 * This uses a stack for the edits still to be tried. 7916 * The idea comes from Aspell leditdist.cpp. Rewritten in C and added support 7917 * for multi-byte characters. 7918 */ 7919 static int 7920 spell_edit_score_limit( 7921 slang_T *slang, 7922 char_u *badword, 7923 char_u *goodword, 7924 int limit) 7925 { 7926 limitscore_T stack[10]; /* allow for over 3 * 2 edits */ 7927 int stackidx; 7928 int bi, gi; 7929 int bi2, gi2; 7930 int bc, gc; 7931 int score; 7932 int score_off; 7933 int minscore; 7934 int round; 7935 7936 /* Multi-byte characters require a bit more work, use a different function 7937 * to avoid testing "has_mbyte" quite often. */ 7938 if (has_mbyte) 7939 return spell_edit_score_limit_w(slang, badword, goodword, limit); 7940 7941 /* 7942 * The idea is to go from start to end over the words. So long as 7943 * characters are equal just continue, this always gives the lowest score. 7944 * When there is a difference try several alternatives. Each alternative 7945 * increases "score" for the edit distance. Some of the alternatives are 7946 * pushed unto a stack and tried later, some are tried right away. At the 7947 * end of the word the score for one alternative is known. The lowest 7948 * possible score is stored in "minscore". 7949 */ 7950 stackidx = 0; 7951 bi = 0; 7952 gi = 0; 7953 score = 0; 7954 minscore = limit + 1; 7955 7956 for (;;) 7957 { 7958 /* Skip over an equal part, score remains the same. */ 7959 for (;;) 7960 { 7961 bc = badword[bi]; 7962 gc = goodword[gi]; 7963 if (bc != gc) /* stop at a char that's different */ 7964 break; 7965 if (bc == NUL) /* both words end */ 7966 { 7967 if (score < minscore) 7968 minscore = score; 7969 goto pop; /* do next alternative */ 7970 } 7971 ++bi; 7972 ++gi; 7973 } 7974 7975 if (gc == NUL) /* goodword ends, delete badword chars */ 7976 { 7977 do 7978 { 7979 if ((score += SCORE_DEL) >= minscore) 7980 goto pop; /* do next alternative */ 7981 } while (badword[++bi] != NUL); 7982 minscore = score; 7983 } 7984 else if (bc == NUL) /* badword ends, insert badword chars */ 7985 { 7986 do 7987 { 7988 if ((score += SCORE_INS) >= minscore) 7989 goto pop; /* do next alternative */ 7990 } while (goodword[++gi] != NUL); 7991 minscore = score; 7992 } 7993 else /* both words continue */ 7994 { 7995 /* If not close to the limit, perform a change. Only try changes 7996 * that may lead to a lower score than "minscore". 7997 * round 0: try deleting a char from badword 7998 * round 1: try inserting a char in badword */ 7999 for (round = 0; round <= 1; ++round) 8000 { 8001 score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); 8002 if (score_off < minscore) 8003 { 8004 if (score_off + SCORE_EDIT_MIN >= minscore) 8005 { 8006 /* Near the limit, rest of the words must match. We 8007 * can check that right now, no need to push an item 8008 * onto the stack. */ 8009 bi2 = bi + 1 - round; 8010 gi2 = gi + round; 8011 while (goodword[gi2] == badword[bi2]) 8012 { 8013 if (goodword[gi2] == NUL) 8014 { 8015 minscore = score_off; 8016 break; 8017 } 8018 ++bi2; 8019 ++gi2; 8020 } 8021 } 8022 else 8023 { 8024 /* try deleting/inserting a character later */ 8025 stack[stackidx].badi = bi + 1 - round; 8026 stack[stackidx].goodi = gi + round; 8027 stack[stackidx].score = score_off; 8028 ++stackidx; 8029 } 8030 } 8031 } 8032 8033 if (score + SCORE_SWAP < minscore) 8034 { 8035 /* If swapping two characters makes a match then the 8036 * substitution is more expensive, thus there is no need to 8037 * try both. */ 8038 if (gc == badword[bi + 1] && bc == goodword[gi + 1]) 8039 { 8040 /* Swap two characters, that is: skip them. */ 8041 gi += 2; 8042 bi += 2; 8043 score += SCORE_SWAP; 8044 continue; 8045 } 8046 } 8047 8048 /* Substitute one character for another which is the same 8049 * thing as deleting a character from both goodword and badword. 8050 * Use a better score when there is only a case difference. */ 8051 if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) 8052 score += SCORE_ICASE; 8053 else 8054 { 8055 /* For a similar character use SCORE_SIMILAR. */ 8056 if (slang != NULL 8057 && slang->sl_has_map 8058 && similar_chars(slang, gc, bc)) 8059 score += SCORE_SIMILAR; 8060 else 8061 score += SCORE_SUBST; 8062 } 8063 8064 if (score < minscore) 8065 { 8066 /* Do the substitution. */ 8067 ++gi; 8068 ++bi; 8069 continue; 8070 } 8071 } 8072 pop: 8073 /* 8074 * Get here to try the next alternative, pop it from the stack. 8075 */ 8076 if (stackidx == 0) /* stack is empty, finished */ 8077 break; 8078 8079 /* pop an item from the stack */ 8080 --stackidx; 8081 gi = stack[stackidx].goodi; 8082 bi = stack[stackidx].badi; 8083 score = stack[stackidx].score; 8084 } 8085 8086 /* When the score goes over "limit" it may actually be much higher. 8087 * Return a very large number to avoid going below the limit when giving a 8088 * bonus. */ 8089 if (minscore > limit) 8090 return SCORE_MAXMAX; 8091 return minscore; 8092 } 8093 8094 /* 8095 * Multi-byte version of spell_edit_score_limit(). 8096 * Keep it in sync with the above! 8097 */ 8098 static int 8099 spell_edit_score_limit_w( 8100 slang_T *slang, 8101 char_u *badword, 8102 char_u *goodword, 8103 int limit) 8104 { 8105 limitscore_T stack[10]; /* allow for over 3 * 2 edits */ 8106 int stackidx; 8107 int bi, gi; 8108 int bi2, gi2; 8109 int bc, gc; 8110 int score; 8111 int score_off; 8112 int minscore; 8113 int round; 8114 char_u *p; 8115 int wbadword[MAXWLEN]; 8116 int wgoodword[MAXWLEN]; 8117 8118 /* Get the characters from the multi-byte strings and put them in an 8119 * int array for easy access. */ 8120 bi = 0; 8121 for (p = badword; *p != NUL; ) 8122 wbadword[bi++] = mb_cptr2char_adv(&p); 8123 wbadword[bi++] = 0; 8124 gi = 0; 8125 for (p = goodword; *p != NUL; ) 8126 wgoodword[gi++] = mb_cptr2char_adv(&p); 8127 wgoodword[gi++] = 0; 8128 8129 /* 8130 * The idea is to go from start to end over the words. So long as 8131 * characters are equal just continue, this always gives the lowest score. 8132 * When there is a difference try several alternatives. Each alternative 8133 * increases "score" for the edit distance. Some of the alternatives are 8134 * pushed unto a stack and tried later, some are tried right away. At the 8135 * end of the word the score for one alternative is known. The lowest 8136 * possible score is stored in "minscore". 8137 */ 8138 stackidx = 0; 8139 bi = 0; 8140 gi = 0; 8141 score = 0; 8142 minscore = limit + 1; 8143 8144 for (;;) 8145 { 8146 /* Skip over an equal part, score remains the same. */ 8147 for (;;) 8148 { 8149 bc = wbadword[bi]; 8150 gc = wgoodword[gi]; 8151 8152 if (bc != gc) /* stop at a char that's different */ 8153 break; 8154 if (bc == NUL) /* both words end */ 8155 { 8156 if (score < minscore) 8157 minscore = score; 8158 goto pop; /* do next alternative */ 8159 } 8160 ++bi; 8161 ++gi; 8162 } 8163 8164 if (gc == NUL) /* goodword ends, delete badword chars */ 8165 { 8166 do 8167 { 8168 if ((score += SCORE_DEL) >= minscore) 8169 goto pop; /* do next alternative */ 8170 } while (wbadword[++bi] != NUL); 8171 minscore = score; 8172 } 8173 else if (bc == NUL) /* badword ends, insert badword chars */ 8174 { 8175 do 8176 { 8177 if ((score += SCORE_INS) >= minscore) 8178 goto pop; /* do next alternative */ 8179 } while (wgoodword[++gi] != NUL); 8180 minscore = score; 8181 } 8182 else /* both words continue */ 8183 { 8184 /* If not close to the limit, perform a change. Only try changes 8185 * that may lead to a lower score than "minscore". 8186 * round 0: try deleting a char from badword 8187 * round 1: try inserting a char in badword */ 8188 for (round = 0; round <= 1; ++round) 8189 { 8190 score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS); 8191 if (score_off < minscore) 8192 { 8193 if (score_off + SCORE_EDIT_MIN >= minscore) 8194 { 8195 /* Near the limit, rest of the words must match. We 8196 * can check that right now, no need to push an item 8197 * onto the stack. */ 8198 bi2 = bi + 1 - round; 8199 gi2 = gi + round; 8200 while (wgoodword[gi2] == wbadword[bi2]) 8201 { 8202 if (wgoodword[gi2] == NUL) 8203 { 8204 minscore = score_off; 8205 break; 8206 } 8207 ++bi2; 8208 ++gi2; 8209 } 8210 } 8211 else 8212 { 8213 /* try deleting a character from badword later */ 8214 stack[stackidx].badi = bi + 1 - round; 8215 stack[stackidx].goodi = gi + round; 8216 stack[stackidx].score = score_off; 8217 ++stackidx; 8218 } 8219 } 8220 } 8221 8222 if (score + SCORE_SWAP < minscore) 8223 { 8224 /* If swapping two characters makes a match then the 8225 * substitution is more expensive, thus there is no need to 8226 * try both. */ 8227 if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1]) 8228 { 8229 /* Swap two characters, that is: skip them. */ 8230 gi += 2; 8231 bi += 2; 8232 score += SCORE_SWAP; 8233 continue; 8234 } 8235 } 8236 8237 /* Substitute one character for another which is the same 8238 * thing as deleting a character from both goodword and badword. 8239 * Use a better score when there is only a case difference. */ 8240 if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc)) 8241 score += SCORE_ICASE; 8242 else 8243 { 8244 /* For a similar character use SCORE_SIMILAR. */ 8245 if (slang != NULL 8246 && slang->sl_has_map 8247 && similar_chars(slang, gc, bc)) 8248 score += SCORE_SIMILAR; 8249 else 8250 score += SCORE_SUBST; 8251 } 8252 8253 if (score < minscore) 8254 { 8255 /* Do the substitution. */ 8256 ++gi; 8257 ++bi; 8258 continue; 8259 } 8260 } 8261 pop: 8262 /* 8263 * Get here to try the next alternative, pop it from the stack. 8264 */ 8265 if (stackidx == 0) /* stack is empty, finished */ 8266 break; 8267 8268 /* pop an item from the stack */ 8269 --stackidx; 8270 gi = stack[stackidx].goodi; 8271 bi = stack[stackidx].badi; 8272 score = stack[stackidx].score; 8273 } 8274 8275 /* When the score goes over "limit" it may actually be much higher. 8276 * Return a very large number to avoid going below the limit when giving a 8277 * bonus. */ 8278 if (minscore > limit) 8279 return SCORE_MAXMAX; 8280 return minscore; 8281 } 8282 8283 /* 8284 * ":spellinfo" 8285 */ 8286 void 8287 ex_spellinfo(exarg_T *eap UNUSED) 8288 { 8289 int lpi; 8290 langp_T *lp; 8291 char_u *p; 8292 8293 if (no_spell_checking(curwin)) 8294 return; 8295 8296 msg_start(); 8297 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len && !got_int; ++lpi) 8298 { 8299 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 8300 msg_puts("file: "); 8301 msg_puts((char *)lp->lp_slang->sl_fname); 8302 msg_putchar('\n'); 8303 p = lp->lp_slang->sl_info; 8304 if (p != NULL) 8305 { 8306 msg_puts((char *)p); 8307 msg_putchar('\n'); 8308 } 8309 } 8310 msg_end(); 8311 } 8312 8313 #define DUMPFLAG_KEEPCASE 1 /* round 2: keep-case tree */ 8314 #define DUMPFLAG_COUNT 2 /* include word count */ 8315 #define DUMPFLAG_ICASE 4 /* ignore case when finding matches */ 8316 #define DUMPFLAG_ONECAP 8 /* pattern starts with capital */ 8317 #define DUMPFLAG_ALLCAP 16 /* pattern is all capitals */ 8318 8319 /* 8320 * ":spelldump" 8321 */ 8322 void 8323 ex_spelldump(exarg_T *eap) 8324 { 8325 char_u *spl; 8326 long dummy; 8327 8328 if (no_spell_checking(curwin)) 8329 return; 8330 get_option_value((char_u*)"spl", &dummy, &spl, OPT_LOCAL); 8331 8332 /* Create a new empty buffer in a new window. */ 8333 do_cmdline_cmd((char_u *)"new"); 8334 8335 /* enable spelling locally in the new window */ 8336 set_option_value((char_u*)"spell", TRUE, (char_u*)"", OPT_LOCAL); 8337 set_option_value((char_u*)"spl", dummy, spl, OPT_LOCAL); 8338 vim_free(spl); 8339 8340 if (!BUFEMPTY()) 8341 return; 8342 8343 spell_dump_compl(NULL, 0, NULL, eap->forceit ? DUMPFLAG_COUNT : 0); 8344 8345 /* Delete the empty line that we started with. */ 8346 if (curbuf->b_ml.ml_line_count > 1) 8347 ml_delete(curbuf->b_ml.ml_line_count, FALSE); 8348 8349 redraw_later(NOT_VALID); 8350 } 8351 8352 /* 8353 * Go through all possible words and: 8354 * 1. When "pat" is NULL: dump a list of all words in the current buffer. 8355 * "ic" and "dir" are not used. 8356 * 2. When "pat" is not NULL: add matching words to insert mode completion. 8357 */ 8358 void 8359 spell_dump_compl( 8360 char_u *pat, /* leading part of the word */ 8361 int ic, /* ignore case */ 8362 int *dir, /* direction for adding matches */ 8363 int dumpflags_arg) /* DUMPFLAG_* */ 8364 { 8365 langp_T *lp; 8366 slang_T *slang; 8367 idx_T arridx[MAXWLEN]; 8368 int curi[MAXWLEN]; 8369 char_u word[MAXWLEN]; 8370 int c; 8371 char_u *byts; 8372 idx_T *idxs; 8373 linenr_T lnum = 0; 8374 int round; 8375 int depth; 8376 int n; 8377 int flags; 8378 char_u *region_names = NULL; /* region names being used */ 8379 int do_region = TRUE; /* dump region names and numbers */ 8380 char_u *p; 8381 int lpi; 8382 int dumpflags = dumpflags_arg; 8383 int patlen; 8384 8385 /* When ignoring case or when the pattern starts with capital pass this on 8386 * to dump_word(). */ 8387 if (pat != NULL) 8388 { 8389 if (ic) 8390 dumpflags |= DUMPFLAG_ICASE; 8391 else 8392 { 8393 n = captype(pat, NULL); 8394 if (n == WF_ONECAP) 8395 dumpflags |= DUMPFLAG_ONECAP; 8396 else if (n == WF_ALLCAP && (int)STRLEN(pat) > mb_ptr2len(pat)) 8397 dumpflags |= DUMPFLAG_ALLCAP; 8398 } 8399 } 8400 8401 /* Find out if we can support regions: All languages must support the same 8402 * regions or none at all. */ 8403 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 8404 { 8405 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 8406 p = lp->lp_slang->sl_regions; 8407 if (p[0] != 0) 8408 { 8409 if (region_names == NULL) /* first language with regions */ 8410 region_names = p; 8411 else if (STRCMP(region_names, p) != 0) 8412 { 8413 do_region = FALSE; /* region names are different */ 8414 break; 8415 } 8416 } 8417 } 8418 8419 if (do_region && region_names != NULL) 8420 { 8421 if (pat == NULL) 8422 { 8423 vim_snprintf((char *)IObuff, IOSIZE, "/regions=%s", region_names); 8424 ml_append(lnum++, IObuff, (colnr_T)0, FALSE); 8425 } 8426 } 8427 else 8428 do_region = FALSE; 8429 8430 /* 8431 * Loop over all files loaded for the entries in 'spelllang'. 8432 */ 8433 for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi) 8434 { 8435 lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi); 8436 slang = lp->lp_slang; 8437 if (slang->sl_fbyts == NULL) /* reloading failed */ 8438 continue; 8439 8440 if (pat == NULL) 8441 { 8442 vim_snprintf((char *)IObuff, IOSIZE, "# file: %s", slang->sl_fname); 8443 ml_append(lnum++, IObuff, (colnr_T)0, FALSE); 8444 } 8445 8446 /* When matching with a pattern and there are no prefixes only use 8447 * parts of the tree that match "pat". */ 8448 if (pat != NULL && slang->sl_pbyts == NULL) 8449 patlen = (int)STRLEN(pat); 8450 else 8451 patlen = -1; 8452 8453 /* round 1: case-folded tree 8454 * round 2: keep-case tree */ 8455 for (round = 1; round <= 2; ++round) 8456 { 8457 if (round == 1) 8458 { 8459 dumpflags &= ~DUMPFLAG_KEEPCASE; 8460 byts = slang->sl_fbyts; 8461 idxs = slang->sl_fidxs; 8462 } 8463 else 8464 { 8465 dumpflags |= DUMPFLAG_KEEPCASE; 8466 byts = slang->sl_kbyts; 8467 idxs = slang->sl_kidxs; 8468 } 8469 if (byts == NULL) 8470 continue; /* array is empty */ 8471 8472 depth = 0; 8473 arridx[0] = 0; 8474 curi[0] = 1; 8475 while (depth >= 0 && !got_int 8476 && (pat == NULL || !compl_interrupted)) 8477 { 8478 if (curi[depth] > byts[arridx[depth]]) 8479 { 8480 /* Done all bytes at this node, go up one level. */ 8481 --depth; 8482 line_breakcheck(); 8483 ins_compl_check_keys(50, FALSE); 8484 } 8485 else 8486 { 8487 /* Do one more byte at this node. */ 8488 n = arridx[depth] + curi[depth]; 8489 ++curi[depth]; 8490 c = byts[n]; 8491 if (c == 0) 8492 { 8493 /* End of word, deal with the word. 8494 * Don't use keep-case words in the fold-case tree, 8495 * they will appear in the keep-case tree. 8496 * Only use the word when the region matches. */ 8497 flags = (int)idxs[n]; 8498 if ((round == 2 || (flags & WF_KEEPCAP) == 0) 8499 && (flags & WF_NEEDCOMP) == 0 8500 && (do_region 8501 || (flags & WF_REGION) == 0 8502 || (((unsigned)flags >> 16) 8503 & lp->lp_region) != 0)) 8504 { 8505 word[depth] = NUL; 8506 if (!do_region) 8507 flags &= ~WF_REGION; 8508 8509 /* Dump the basic word if there is no prefix or 8510 * when it's the first one. */ 8511 c = (unsigned)flags >> 24; 8512 if (c == 0 || curi[depth] == 2) 8513 { 8514 dump_word(slang, word, pat, dir, 8515 dumpflags, flags, lnum); 8516 if (pat == NULL) 8517 ++lnum; 8518 } 8519 8520 /* Apply the prefix, if there is one. */ 8521 if (c != 0) 8522 lnum = dump_prefixes(slang, word, pat, dir, 8523 dumpflags, flags, lnum); 8524 } 8525 } 8526 else 8527 { 8528 /* Normal char, go one level deeper. */ 8529 word[depth++] = c; 8530 arridx[depth] = idxs[n]; 8531 curi[depth] = 1; 8532 8533 /* Check if this characters matches with the pattern. 8534 * If not skip the whole tree below it. 8535 * Always ignore case here, dump_word() will check 8536 * proper case later. This isn't exactly right when 8537 * length changes for multi-byte characters with 8538 * ignore case... */ 8539 if (depth <= patlen 8540 && MB_STRNICMP(word, pat, depth) != 0) 8541 --depth; 8542 } 8543 } 8544 } 8545 } 8546 } 8547 } 8548 8549 /* 8550 * Dump one word: apply case modifications and append a line to the buffer. 8551 * When "lnum" is zero add insert mode completion. 8552 */ 8553 static void 8554 dump_word( 8555 slang_T *slang, 8556 char_u *word, 8557 char_u *pat, 8558 int *dir, 8559 int dumpflags, 8560 int wordflags, 8561 linenr_T lnum) 8562 { 8563 int keepcap = FALSE; 8564 char_u *p; 8565 char_u *tw; 8566 char_u cword[MAXWLEN]; 8567 char_u badword[MAXWLEN + 10]; 8568 int i; 8569 int flags = wordflags; 8570 8571 if (dumpflags & DUMPFLAG_ONECAP) 8572 flags |= WF_ONECAP; 8573 if (dumpflags & DUMPFLAG_ALLCAP) 8574 flags |= WF_ALLCAP; 8575 8576 if ((dumpflags & DUMPFLAG_KEEPCASE) == 0 && (flags & WF_CAPMASK) != 0) 8577 { 8578 /* Need to fix case according to "flags". */ 8579 make_case_word(word, cword, flags); 8580 p = cword; 8581 } 8582 else 8583 { 8584 p = word; 8585 if ((dumpflags & DUMPFLAG_KEEPCASE) 8586 && ((captype(word, NULL) & WF_KEEPCAP) == 0 8587 || (flags & WF_FIXCAP) != 0)) 8588 keepcap = TRUE; 8589 } 8590 tw = p; 8591 8592 if (pat == NULL) 8593 { 8594 /* Add flags and regions after a slash. */ 8595 if ((flags & (WF_BANNED | WF_RARE | WF_REGION)) || keepcap) 8596 { 8597 STRCPY(badword, p); 8598 STRCAT(badword, "/"); 8599 if (keepcap) 8600 STRCAT(badword, "="); 8601 if (flags & WF_BANNED) 8602 STRCAT(badword, "!"); 8603 else if (flags & WF_RARE) 8604 STRCAT(badword, "?"); 8605 if (flags & WF_REGION) 8606 for (i = 0; i < 7; ++i) 8607 if (flags & (0x10000 << i)) 8608 sprintf((char *)badword + STRLEN(badword), "%d", i + 1); 8609 p = badword; 8610 } 8611 8612 if (dumpflags & DUMPFLAG_COUNT) 8613 { 8614 hashitem_T *hi; 8615 8616 /* Include the word count for ":spelldump!". */ 8617 hi = hash_find(&slang->sl_wordcount, tw); 8618 if (!HASHITEM_EMPTY(hi)) 8619 { 8620 vim_snprintf((char *)IObuff, IOSIZE, "%s\t%d", 8621 tw, HI2WC(hi)->wc_count); 8622 p = IObuff; 8623 } 8624 } 8625 8626 ml_append(lnum, p, (colnr_T)0, FALSE); 8627 } 8628 else if (((dumpflags & DUMPFLAG_ICASE) 8629 ? MB_STRNICMP(p, pat, STRLEN(pat)) == 0 8630 : STRNCMP(p, pat, STRLEN(pat)) == 0) 8631 && ins_compl_add_infercase(p, (int)STRLEN(p), 8632 p_ic, NULL, *dir, 0) == OK) 8633 /* if dir was BACKWARD then honor it just once */ 8634 *dir = FORWARD; 8635 } 8636 8637 /* 8638 * For ":spelldump": Find matching prefixes for "word". Prepend each to 8639 * "word" and append a line to the buffer. 8640 * When "lnum" is zero add insert mode completion. 8641 * Return the updated line number. 8642 */ 8643 static linenr_T 8644 dump_prefixes( 8645 slang_T *slang, 8646 char_u *word, /* case-folded word */ 8647 char_u *pat, 8648 int *dir, 8649 int dumpflags, 8650 int flags, /* flags with prefix ID */ 8651 linenr_T startlnum) 8652 { 8653 idx_T arridx[MAXWLEN]; 8654 int curi[MAXWLEN]; 8655 char_u prefix[MAXWLEN]; 8656 char_u word_up[MAXWLEN]; 8657 int has_word_up = FALSE; 8658 int c; 8659 char_u *byts; 8660 idx_T *idxs; 8661 linenr_T lnum = startlnum; 8662 int depth; 8663 int n; 8664 int len; 8665 int i; 8666 8667 /* If the word starts with a lower-case letter make the word with an 8668 * upper-case letter in word_up[]. */ 8669 c = PTR2CHAR(word); 8670 if (SPELL_TOUPPER(c) != c) 8671 { 8672 onecap_copy(word, word_up, TRUE); 8673 has_word_up = TRUE; 8674 } 8675 8676 byts = slang->sl_pbyts; 8677 idxs = slang->sl_pidxs; 8678 if (byts != NULL) /* array not is empty */ 8679 { 8680 /* 8681 * Loop over all prefixes, building them byte-by-byte in prefix[]. 8682 * When at the end of a prefix check that it supports "flags". 8683 */ 8684 depth = 0; 8685 arridx[0] = 0; 8686 curi[0] = 1; 8687 while (depth >= 0 && !got_int) 8688 { 8689 n = arridx[depth]; 8690 len = byts[n]; 8691 if (curi[depth] > len) 8692 { 8693 /* Done all bytes at this node, go up one level. */ 8694 --depth; 8695 line_breakcheck(); 8696 } 8697 else 8698 { 8699 /* Do one more byte at this node. */ 8700 n += curi[depth]; 8701 ++curi[depth]; 8702 c = byts[n]; 8703 if (c == 0) 8704 { 8705 /* End of prefix, find out how many IDs there are. */ 8706 for (i = 1; i < len; ++i) 8707 if (byts[n + i] != 0) 8708 break; 8709 curi[depth] += i - 1; 8710 8711 c = valid_word_prefix(i, n, flags, word, slang, FALSE); 8712 if (c != 0) 8713 { 8714 vim_strncpy(prefix + depth, word, MAXWLEN - depth - 1); 8715 dump_word(slang, prefix, pat, dir, dumpflags, 8716 (c & WF_RAREPFX) ? (flags | WF_RARE) 8717 : flags, lnum); 8718 if (lnum != 0) 8719 ++lnum; 8720 } 8721 8722 /* Check for prefix that matches the word when the 8723 * first letter is upper-case, but only if the prefix has 8724 * a condition. */ 8725 if (has_word_up) 8726 { 8727 c = valid_word_prefix(i, n, flags, word_up, slang, 8728 TRUE); 8729 if (c != 0) 8730 { 8731 vim_strncpy(prefix + depth, word_up, 8732 MAXWLEN - depth - 1); 8733 dump_word(slang, prefix, pat, dir, dumpflags, 8734 (c & WF_RAREPFX) ? (flags | WF_RARE) 8735 : flags, lnum); 8736 if (lnum != 0) 8737 ++lnum; 8738 } 8739 } 8740 } 8741 else 8742 { 8743 /* Normal char, go one level deeper. */ 8744 prefix[depth++] = c; 8745 arridx[depth] = idxs[n]; 8746 curi[depth] = 1; 8747 } 8748 } 8749 } 8750 } 8751 8752 return lnum; 8753 } 8754 8755 /* 8756 * Move "p" to the end of word "start". 8757 * Uses the spell-checking word characters. 8758 */ 8759 char_u * 8760 spell_to_word_end(char_u *start, win_T *win) 8761 { 8762 char_u *p = start; 8763 8764 while (*p != NUL && spell_iswordp(p, win)) 8765 MB_PTR_ADV(p); 8766 return p; 8767 } 8768 8769 #if defined(FEAT_INS_EXPAND) || defined(PROTO) 8770 /* 8771 * For Insert mode completion CTRL-X s: 8772 * Find start of the word in front of column "startcol". 8773 * We don't check if it is badly spelled, with completion we can only change 8774 * the word in front of the cursor. 8775 * Returns the column number of the word. 8776 */ 8777 int 8778 spell_word_start(int startcol) 8779 { 8780 char_u *line; 8781 char_u *p; 8782 int col = 0; 8783 8784 if (no_spell_checking(curwin)) 8785 return startcol; 8786 8787 /* Find a word character before "startcol". */ 8788 line = ml_get_curline(); 8789 for (p = line + startcol; p > line; ) 8790 { 8791 MB_PTR_BACK(line, p); 8792 if (spell_iswordp_nmw(p, curwin)) 8793 break; 8794 } 8795 8796 /* Go back to start of the word. */ 8797 while (p > line) 8798 { 8799 col = (int)(p - line); 8800 MB_PTR_BACK(line, p); 8801 if (!spell_iswordp(p, curwin)) 8802 break; 8803 col = 0; 8804 } 8805 8806 return col; 8807 } 8808 8809 /* 8810 * Need to check for 'spellcapcheck' now, the word is removed before 8811 * expand_spelling() is called. Therefore the ugly global variable. 8812 */ 8813 static int spell_expand_need_cap; 8814 8815 void 8816 spell_expand_check_cap(colnr_T col) 8817 { 8818 spell_expand_need_cap = check_need_cap(curwin->w_cursor.lnum, col); 8819 } 8820 8821 /* 8822 * Get list of spelling suggestions. 8823 * Used for Insert mode completion CTRL-X ?. 8824 * Returns the number of matches. The matches are in "matchp[]", array of 8825 * allocated strings. 8826 */ 8827 int 8828 expand_spelling( 8829 linenr_T lnum UNUSED, 8830 char_u *pat, 8831 char_u ***matchp) 8832 { 8833 garray_T ga; 8834 8835 spell_suggest_list(&ga, pat, 100, spell_expand_need_cap, TRUE); 8836 *matchp = ga.ga_data; 8837 return ga.ga_len; 8838 } 8839 #endif 8840 8841 #endif /* FEAT_SPELL */ 8842