diff options
Diffstat (limited to 'src/nvim/spell.c')
| -rw-r--r-- | src/nvim/spell.c | 3902 |
1 files changed, 125 insertions, 3777 deletions
diff --git a/src/nvim/spell.c b/src/nvim/spell.c index ceb35af4b8..1e44d328b3 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -56,18 +56,6 @@ // Use DEBUG_TRIEWALK to print the changes made in suggest_trie_walk() for a // specific word. -// Use this to adjust the score after finding suggestions, based on the -// suggested word sounding like the bad word. This is much faster than doing -// it for every possible suggestion. -// Disadvantage: When "the" is typed as "hte" it sounds quite different ("@" -// vs "ht") and goes down in the list. -// Used when 'spellsuggest' is set to "best". -#define RESCORE(word_score, sound_score) ((3 * (word_score) + (sound_score)) / 4) - -// Do the opposite: based on a maximum end score and a known sound score, -// compute the maximum word score that can be used. -#define MAXSCORE(word_score, sound_score) ((4 * (word_score) - (sound_score)) / 3) - #include <assert.h> #include <inttypes.h> #include <limits.h> @@ -84,15 +72,12 @@ #include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" +#include "nvim/drawscreen.h" #include "nvim/edit.h" -#include "nvim/eval.h" #include "nvim/ex_cmds.h" -#include "nvim/ex_cmds2.h" #include "nvim/ex_docmd.h" -#include "nvim/fileio.h" #include "nvim/func_attr.h" #include "nvim/garray.h" -#include "nvim/getchar.h" #include "nvim/hashtab.h" #include "nvim/input.h" #include "nvim/insexpand.h" @@ -108,20 +93,14 @@ #include "nvim/os_unix.h" #include "nvim/path.h" #include "nvim/regexp.h" -#include "nvim/screen.h" #include "nvim/search.h" #include "nvim/spell.h" #include "nvim/spellfile.h" +#include "nvim/spellsuggest.h" #include "nvim/strings.h" #include "nvim/syntax.h" -#include "nvim/ui.h" #include "nvim/undo.h" -// only used for su_badflags -#define WF_MIXCAP 0x20 // mix of upper and lower case: macaRONI - -#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP) - // Result values. Lower number is accepted over higher one. #define SP_BANNED (-1) #define SP_RARE 0 @@ -136,104 +115,6 @@ slang_T *first_lang = NULL; // file used for "zG" and "zW" char_u *int_wordlist = NULL; -typedef struct wordcount_S { - uint16_t wc_count; // nr of times word was seen - char_u wc_word[1]; // word, actually longer -} wordcount_T; - -#define WC_KEY_OFF offsetof(wordcount_T, wc_word) -#define HI2WC(hi) ((wordcount_T *)((hi)->hi_key - WC_KEY_OFF)) -#define MAXWORDCOUNT 0xffff - -// Information used when looking for suggestions. -typedef struct suginfo_S { - garray_T su_ga; // suggestions, contains "suggest_T" - int su_maxcount; // max. number of suggestions displayed - int su_maxscore; // maximum score for adding to su_ga - int su_sfmaxscore; // idem, for when doing soundfold words - garray_T su_sga; // like su_ga, sound-folded scoring - char_u *su_badptr; // start of bad word in line - int su_badlen; // length of detected bad word in line - int su_badflags; // caps flags for bad word - char_u su_badword[MAXWLEN]; // bad word truncated at su_badlen - char_u su_fbadword[MAXWLEN]; // su_badword case-folded - char_u su_sal_badword[MAXWLEN]; // su_badword soundfolded - hashtab_T su_banned; // table with banned words - slang_T *su_sallang; // default language for sound folding -} suginfo_T; - -// One word suggestion. Used in "si_ga". -typedef struct { - char_u *st_word; // suggested word, allocated string - int st_wordlen; // STRLEN(st_word) - int st_orglen; // length of replaced text - int st_score; // lower is better - int st_altscore; // used when st_score compares equal - bool st_salscore; // st_score is for soundalike - bool st_had_bonus; // bonus already included in score - slang_T *st_slang; // language used for sound folding -} suggest_T; - -#define SUG(ga, i) (((suggest_T *)(ga).ga_data)[i]) - -// True if a word appears in the list of banned words. -#define WAS_BANNED(su, word) (!HASHITEM_EMPTY(hash_find(&(su)->su_banned, word))) - -// Number of suggestions kept when cleaning up. We need to keep more than -// what is displayed, because when rescore_suggestions() is called the score -// may change and wrong suggestions may be removed later. -#define SUG_CLEAN_COUNT(su) ((su)->su_maxcount < \ - 130 ? 150 : (su)->su_maxcount + 20) - -// Threshold for sorting and cleaning up suggestions. Don't want to keep lots -// of suggestions that are not going to be displayed. -#define SUG_MAX_COUNT(su) (SUG_CLEAN_COUNT(su) + 50) - -// score for various changes -#define SCORE_SPLIT 149 // split bad word -#define SCORE_SPLIT_NO 249 // split bad word with NOSPLITSUGS -#define SCORE_ICASE 52 // slightly different case -#define SCORE_REGION 200 // word is for different region -#define SCORE_RARE 180 // rare word -#define SCORE_SWAP 75 // swap two characters -#define SCORE_SWAP3 110 // swap two characters in three -#define SCORE_REP 65 // REP replacement -#define SCORE_SUBST 93 // substitute a character -#define SCORE_SIMILAR 33 // substitute a similar character -#define SCORE_SUBCOMP 33 // substitute a composing character -#define SCORE_DEL 94 // delete a character -#define SCORE_DELDUP 66 // delete a duplicated character -#define SCORE_DELCOMP 28 // delete a composing character -#define SCORE_INS 96 // insert a character -#define SCORE_INSDUP 67 // insert a duplicate character -#define SCORE_INSCOMP 30 // insert a composing character -#define SCORE_NONWORD 103 // change non-word to word char - -#define SCORE_FILE 30 // suggestion from a file -#define SCORE_MAXINIT 350 // Initial maximum score: higher == slower. - // 350 allows for about three changes. - -#define SCORE_COMMON1 30 // subtracted for words seen before -#define SCORE_COMMON2 40 // subtracted for words often seen -#define SCORE_COMMON3 50 // subtracted for words very often seen -#define SCORE_THRES2 10 // word count threshold for COMMON2 -#define SCORE_THRES3 100 // word count threshold for COMMON3 - -// When trying changed soundfold words it becomes slow when trying more than -// two changes. With less than two changes it's slightly faster but we miss a -// few good suggestions. In rare cases we need to try three of four changes. -#define SCORE_SFMAX1 200 // maximum score for first try -#define SCORE_SFMAX2 300 // maximum score for second try -#define SCORE_SFMAX3 400 // maximum score for third try - -#define SCORE_BIG (SCORE_INS * 3) // big difference -#define SCORE_MAXMAX 999999 // accept any score -#define SCORE_LIMITMAX 350 // for spell_edit_score_limit() - -// for spell_edit_score_limit() we need to know the minimum value of -// SCORE_ICASE, SCORE_SWAP, SCORE_DEL, SCORE_SIMILAR and SCORE_INS -#define SCORE_EDIT_MIN SCORE_SIMILAR - // Structure to store info for word matching. typedef struct matchinf_S { langp_T *mi_lp; // info for language and region @@ -289,38 +170,10 @@ typedef struct syl_item_S { spelltab_T spelltab; int did_set_spelltab; -// structure used to store soundfolded words that add_sound_suggest() has -// handled already. -typedef struct { - int16_t sft_score; // lowest score used - char_u sft_word[1]; // soundfolded word, actually longer -} sftword_T; - -typedef struct { - int badi; - int goodi; - int score; -} limitscore_T; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "spell.c.generated.h" #endif -// values for ts_isdiff -#define DIFF_NONE 0 // no different byte (yet) -#define DIFF_YES 1 // different byte found -#define DIFF_INSERT 2 // inserting character - -// values for ts_flags -#define TSF_PREFIXOK 1 // already checked that prefix is OK -#define TSF_DIDSPLIT 2 // tried split at this point -#define TSF_DIDDEL 4 // did a delete, "ts_delidx" has index - -// special values ts_prefixdepth -#define PFD_NOPREFIX 0xff // not using prefixes -#define PFD_PREFIXTREE 0xfe // walking through the prefix tree -#define PFD_NOTSPECIAL 0xfd // highest value that's not special - // mode values for find_word #define FIND_FOLDWORD 0 // find word case-folded #define FIND_KEEPWORD 1 // find keep-case word @@ -331,8 +184,8 @@ typedef struct { char *e_format = N_("E759: Format error in spell file"); // Remember what "z?" replaced. -static char_u *repl_from = NULL; -static char_u *repl_to = NULL; +char_u *repl_from = NULL; +char_u *repl_to = NULL; /// Main spell-checking function. /// "ptr" points to a character that could be the start of a word. @@ -374,7 +227,7 @@ size_t spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, bool docou return 1; } - memset(&mi, 0, sizeof(matchinf_T)); + CLEAR_FIELD(mi); // A number is always OK. Also skip hexadecimal numbers 0xFF99 and // 0X99FF. But always do check spelling to find "3GPP" and "11 @@ -639,13 +492,13 @@ static void find_word(matchinf_T *mip, int mode) } endlen[endidxcnt] = wlen; endidx[endidxcnt++] = arridx++; - --len; + len--; // Skip over the zeros, there can be several flag/region // combinations. while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } if (len == 0) { break; // no children, word must end here @@ -683,8 +536,8 @@ static void find_word(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; // One space in the good word may stand for several spaces in the // checked word. @@ -696,8 +549,8 @@ static void find_word(matchinf_T *mip, int mode) if (ptr[wlen] != ' ' && ptr[wlen] != TAB) { break; } - ++wlen; - --flen; + wlen++; + flen--; } } } @@ -708,7 +561,7 @@ static void find_word(matchinf_T *mip, int mode) // Verify that one of the possible endings is valid. Try the longest // first. while (endidxcnt > 0) { - --endidxcnt; + endidxcnt--; arridx = endidx[endidxcnt]; wlen = endlen[endidxcnt]; @@ -1050,7 +903,7 @@ static void find_word(matchinf_T *mip, int mode) /// end of ptr[wlen] and the second part matches after it. /// /// @param gap &sl_comppat -static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) +bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) { char_u *p; int len; @@ -1072,7 +925,7 @@ static bool match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap) // Returns true if "flags" is a valid sequence of compound flags and "word" // does not have too many syllables. -static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) +bool can_compound(slang_T *slang, const char_u *word, const char_u *flags) FUNC_ATTR_NONNULL_ALL { char_u uflags[MAXWLEN * 2] = { 0 }; @@ -1101,37 +954,11 @@ static bool can_compound(slang_T *slang, const char_u *word, const char_u *flags return true; } -// Returns true when the sequence of flags in "compflags" plus "flag" can -// possibly form a valid compounded word. This also checks the COMPOUNDRULE -// lines if they don't contain wildcards. -static bool can_be_compound(trystate_T *sp, slang_T *slang, char_u *compflags, int flag) -{ - // If the flag doesn't appear in sl_compstartflags or sl_compallflags - // then it can't possibly compound. - if (!byte_in_str(sp->ts_complen == sp->ts_compsplit - ? slang->sl_compstartflags : slang->sl_compallflags, flag)) { - return false; - } - - // If there are no wildcards, we can check if the flags collected so far - // possibly can form a match with COMPOUNDRULE patterns. This only - // makes sense when we have two or more words. - if (slang->sl_comprules != NULL && sp->ts_complen > sp->ts_compsplit) { - compflags[sp->ts_complen] = (char_u)flag; - compflags[sp->ts_complen + 1] = NUL; - bool v = match_compoundrule(slang, compflags + sp->ts_compsplit); - compflags[sp->ts_complen] = NUL; - return v; - } - - return true; -} - // Returns true if the compound flags in compflags[] match the start of any // compound rule. This is used to stop trying a compound if the flags // collected so far can't possibly match any compound rule. // Caller must check that slang->sl_comprules is not NULL. -static bool match_compoundrule(slang_T *slang, char_u *compflags) +bool match_compoundrule(slang_T *slang, char_u *compflags) { char_u *p; int i; @@ -1154,7 +981,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) bool match = false; // compare against all the flags in [] - ++p; + p++; while (*p != ']' && *p != NUL) { if (*p++ == c) { match = true; @@ -1166,7 +993,7 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) } else if (*p != c) { break; // flag of word doesn't match flag in pattern } - ++p; + p++; } // Skip to the next "/", where the next pattern starts. @@ -1188,8 +1015,8 @@ static bool match_compoundrule(slang_T *slang, char_u *compflags) /// @param totprefcnt nr of prefix IDs /// @param arridx idx in sl_pidxs[] /// @param cond_req only use prefixes with a condition -static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, - bool cond_req) +int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, + bool cond_req) { int prefcnt; int pidx; @@ -1282,8 +1109,8 @@ static void find_prefix(matchinf_T *mip, int mode) mip->mi_prefarridx = arridx; mip->mi_prefcnt = len; while (len > 0 && byts[arridx] == 0) { - ++arridx; - --len; + arridx++; + len--; } mip->mi_prefcnt -= len; @@ -1332,8 +1159,8 @@ static void find_prefix(matchinf_T *mip, int mode) // Continue at the child (if there is one). arridx = idxs[lo]; - ++wlen; - --flen; + wlen++; + flen--; } } @@ -1368,7 +1195,7 @@ static int fold_more(matchinf_T *mip) /// /// @param wordflags Flags for the checked word. /// @param treeflags Flags for the word in the spell tree. -static bool spell_valid_case(int wordflags, int treeflags) +bool spell_valid_case(int wordflags, int treeflags) { return (wordflags == WF_ALLCAP && (treeflags & WF_FIXCAP) == 0) || ((treeflags & (WF_ALLCAP | WF_KEEPCAP)) == 0 @@ -1377,7 +1204,7 @@ static bool spell_valid_case(int wordflags, int treeflags) } // Returns true if spell checking is not enabled. -static bool no_spell_checking(win_T *wp) +bool no_spell_checking(win_T *wp) { if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL || GA_EMPTY(&wp->w_s->b_langp)) { @@ -1581,7 +1408,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att capcol = -1; } else { if (lnum < wp->w_buffer->b_ml.ml_line_count) { - ++lnum; + lnum++; } else if (!p_ws) { break; // at first line and 'nowrapscan' } else { @@ -1609,7 +1436,7 @@ size_t spell_move_to(win_T *wp, int dir, bool allwords, bool curline, hlf_T *att } // Capcol skips over the inserted space. - --capcol; + capcol--; // But after empty line check first word in next line if (empty_line) { @@ -1903,38 +1730,6 @@ void count_common_word(slang_T *lp, char_u *word, int len, uint8_t count) } } -/// Adjust the score of common words. -/// -/// @param split word was split, less bonus -static int score_wordcount_adj(slang_T *slang, int score, char_u *word, bool split) -{ - wordcount_T *wc; - int bonus; - int newscore; - - hashitem_T *hi = hash_find(&slang->sl_wordcount, (char *)word); - if (!HASHITEM_EMPTY(hi)) { - wc = HI2WC(hi); - if (wc->wc_count < SCORE_THRES2) { - bonus = SCORE_COMMON1; - } else if (wc->wc_count < SCORE_THRES3) { - bonus = SCORE_COMMON2; - } else { - bonus = SCORE_COMMON3; - } - if (split) { - newscore = score - bonus / 2; - } else { - newscore = score - bonus; - } - if (newscore < 0) { - return 0; - } - return newscore; - } - return score; -} - // Returns true if byte "n" appears in "str". // Like strchr() but independent of locale. bool byte_in_str(char_u *str, int n) @@ -2016,7 +1811,7 @@ static int count_syllables(slang_T *slang, const char_u *word) } } if (len != 0) { // found a match, count syllable - ++cnt; + cnt++; skip = false; } else { // No recognized syllable item, at least a syllable char then? @@ -2038,7 +1833,7 @@ static int count_syllables(slang_T *slang, const char_u *word) char *did_set_spelllang(win_T *wp) { garray_T ga; - char_u *splp; + char *splp; char_u *region; char_u region_cp[3]; bool filename; @@ -2050,7 +1845,7 @@ char *did_set_spelllang(win_T *wp) int len; char_u *p; int round; - char_u *spf; + char *spf; char_u *use_region = NULL; bool dont_use_region = false; bool nobreak = false; @@ -2080,9 +1875,9 @@ char *did_set_spelllang(win_T *wp) wp->w_s->b_cjk = 0; // Loop over comma separated language names. - for (splp = spl_copy; *splp != NUL;) { + for (splp = (char *)spl_copy; *splp != NUL;) { // Get one language name. - copy_option_part((char **)&splp, (char *)lang, MAXWLEN, ","); + copy_option_part(&splp, (char *)lang, MAXWLEN, ","); region = NULL; len = (int)STRLEN(lang); @@ -2204,8 +1999,8 @@ char *did_set_spelllang(win_T *wp) // round 1: load first name in 'spellfile'. // round 2: load second name in 'spellfile. // etc. - spf = curwin->w_s->b_p_spf; - for (round = 0; round == 0 || *spf != NUL; ++round) { + spf = (char *)curwin->w_s->b_p_spf; + for (round = 0; round == 0 || *spf != NUL; round++) { if (round == 0) { // Internal wordlist, if there is one. if (int_wordlist == NULL) { @@ -2214,7 +2009,7 @@ char *did_set_spelllang(win_T *wp) int_wordlist_spl(spf_name); } else { // One entry in 'spellfile'. - copy_option_part((char **)&spf, (char *)spf_name, MAXPATHL - 5, ","); + copy_option_part(&spf, (char *)spf_name, MAXPATHL - 5, ","); STRCAT(spf_name, ".spl"); // If it was already found above then skip it. @@ -2338,7 +2133,7 @@ theend: // Clear the midword characters for buffer "buf". static void clear_midword(win_T *wp) { - memset(wp->w_s->b_spell_ismw, 0, 256); + CLEAR_FIELD(wp->w_s->b_spell_ismw); XFREE_CLEAR(wp->w_s->b_spell_ismw_mb); } @@ -2444,51 +2239,6 @@ int captype(char_u *word, char_u *end) return 0; } -// Like captype() but for a KEEPCAP word add ONECAP if the word starts with a -// capital. So that make_case_word() can turn WOrd into Word. -// Add ALLCAP for "WOrD". -static int badword_captype(char_u *word, char_u *end) - FUNC_ATTR_NONNULL_ALL -{ - int flags = captype(word, end); - int c; - int l, u; - bool first; - char_u *p; - - if (flags & WF_KEEPCAP) { - // Count the number of UPPER and lower case letters. - l = u = 0; - first = false; - for (p = word; p < end; MB_PTR_ADV(p)) { - c = utf_ptr2char((char *)p); - if (SPELL_ISUPPER(c)) { - ++u; - if (p == word) { - first = true; - } - } else { - ++l; - } - } - - // If there are more UPPER than lower case letters suggest an - // ALLCAP word. Otherwise, if the first letter is UPPER then - // suggest ONECAP. Exception: "ALl" most likely should be "All", - // require three upper case letters. - if (u > l && u > 2) { - flags |= WF_ALLCAP; - } else if (first) { - flags |= WF_ONECAP; - } - - if (u >= 2 && l >= 2) { // maCARONI maCAroni - flags |= WF_MIXCAP; - } - } - return flags; -} - // Delete the internal wordlist and its .spl file. void spell_delete_wordlist(void) { @@ -2547,36 +2297,6 @@ void spell_reload(void) } } -// Opposite of offset2bytes(). -// "pp" points to the bytes and is advanced over it. -// Returns the offset. -static int bytes2offset(char_u **pp) -{ - char_u *p = *pp; - int nr; - int c; - - c = *p++; - if ((c & 0x80) == 0x00) { // 1 byte - nr = c - 1; - } else if ((c & 0xc0) == 0x80) { // 2 bytes - nr = (c & 0x3f) - 1; - nr = nr * 255 + (*p++ - 1); - } else if ((c & 0xe0) == 0xc0) { // 3 bytes - nr = (c & 0x1f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } else { // 4 bytes - nr = (c & 0x0f) - 1; - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - nr = nr * 255 + (*p++ - 1); - } - - *pp = p; - return nr; -} - // Open a spell buffer. This is a nameless buffer that is not in the buffer // list and only contains text lines. Can use a swapfile to reduce memory // use. @@ -2610,9 +2330,9 @@ void clear_spell_chartab(spelltab_T *sp) { int i; - // Init everything to false. - memset(sp->st_isw, false, sizeof(sp->st_isw)); - memset(sp->st_isu, false, sizeof(sp->st_isu)); + // Init everything to false (zero). + CLEAR_FIELD(sp->st_isw); + CLEAR_FIELD(sp->st_isu); for (i = 0; i < 256; i++) { sp->st_fold[i] = (char_u)i; @@ -2665,7 +2385,7 @@ void init_spell_chartab(void) /// Thus this only works properly when past the first character of the word. /// /// @param wp Buffer used. -static bool spell_iswordp(const char_u *p, const win_T *wp) +bool spell_iswordp(const char_u *p, const win_T *wp) FUNC_ATTR_NONNULL_ALL { int c; @@ -2783,298 +2503,9 @@ int spell_casefold(const win_T *wp, char_u *str, int len, char_u *buf, int bufle return OK; } -// values for sps_flags -#define SPS_BEST 1 -#define SPS_FAST 2 -#define SPS_DOUBLE 4 - -static int sps_flags = SPS_BEST; // flags from 'spellsuggest' -static int sps_limit = 9999; // max nr of suggestions given - -// Check the 'spellsuggest' option. Return FAIL if it's wrong. -// Sets "sps_flags" and "sps_limit". -int spell_check_sps(void) -{ - char_u *p; - char_u *s; - char_u buf[MAXPATHL]; - int f; - - sps_flags = 0; - sps_limit = 9999; - - for (p = p_sps; *p != NUL;) { - copy_option_part((char **)&p, (char *)buf, MAXPATHL, ","); - - f = 0; - if (ascii_isdigit(*buf)) { - s = buf; - sps_limit = getdigits_int((char **)&s, true, 0); - if (*s != NUL && !ascii_isdigit(*s)) { - f = -1; - } - } else if (STRCMP(buf, "best") == 0) { - f = SPS_BEST; - } else if (STRCMP(buf, "fast") == 0) { - f = SPS_FAST; - } else if (STRCMP(buf, "double") == 0) { - f = SPS_DOUBLE; - } else if (STRNCMP(buf, "expr:", 5) != 0 - && STRNCMP(buf, "file:", 5) != 0) { - f = -1; - } - - if (f == -1 || (sps_flags != 0 && f != 0)) { - sps_flags = SPS_BEST; - sps_limit = 9999; - return FAIL; - } - if (f != 0) { - sps_flags = f; - } - } - - if (sps_flags == 0) { - sps_flags = SPS_BEST; - } - - return OK; -} - -// "z=": Find badly spelled word under or after the cursor. -// Give suggestions for the properly spelled word. -// In Visual mode use the highlighted word as the bad word. -// When "count" is non-zero use that suggestion. -void spell_suggest(int count) -{ - char_u *line; - pos_T prev_cursor = curwin->w_cursor; - char_u wcopy[MAXWLEN + 2]; - char_u *p; - int c; - suginfo_T sug; - suggest_T *stp; - int mouse_used; - int need_cap; - int limit; - int selected = count; - int badlen = 0; - int msg_scroll_save = msg_scroll; - const int wo_spell_save = curwin->w_p_spell; - - if (!curwin->w_p_spell) { - did_set_spelllang(curwin); - curwin->w_p_spell = true; - } - - if (*curwin->w_s->b_p_spl == NUL) { - emsg(_(e_no_spell)); - return; - } - - if (VIsual_active) { - // Use the Visually selected text as the bad word. But reject - // a multi-line selection. - if (curwin->w_cursor.lnum != VIsual.lnum) { - vim_beep(BO_SPELL); - return; - } - badlen = (int)curwin->w_cursor.col - (int)VIsual.col; - if (badlen < 0) { - badlen = -badlen; - } else { - curwin->w_cursor.col = VIsual.col; - } - badlen++; - end_visual_mode(); - } else - // Find the start of the badly spelled word. - if (spell_move_to(curwin, FORWARD, true, true, NULL) == 0 - || curwin->w_cursor.col > prev_cursor.col) { - // No bad word or it starts after the cursor: use the word under the - // cursor. - curwin->w_cursor = prev_cursor; - line = get_cursor_line_ptr(); - p = line + curwin->w_cursor.col; - // Backup to before start of word. - while (p > line && spell_iswordp_nmw(p, curwin)) { - MB_PTR_BACK(line, p); - } - // Forward to start of word. - while (*p != NUL && !spell_iswordp_nmw(p, curwin)) { - MB_PTR_ADV(p); - } - - if (!spell_iswordp_nmw(p, curwin)) { // No word found. - beep_flush(); - return; - } - curwin->w_cursor.col = (colnr_T)(p - line); - } - - // Get the word and its length. - - // Figure out if the word should be capitalised. - need_cap = check_need_cap(curwin->w_cursor.lnum, curwin->w_cursor.col); - - // Make a copy of current line since autocommands may free the line. - line = vim_strsave(get_cursor_line_ptr()); - - // Get the list of suggestions. Limit to 'lines' - 2 or the number in - // 'spellsuggest', whatever is smaller. - if (sps_limit > Rows - 2) { - limit = Rows - 2; - } else { - limit = sps_limit; - } - spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit, - true, need_cap, true); - - if (GA_EMPTY(&sug.su_ga)) { - msg(_("Sorry, no suggestions")); - } else if (count > 0) { - if (count > sug.su_ga.ga_len) { - smsg(_("Sorry, only %" PRId64 " suggestions"), - (int64_t)sug.su_ga.ga_len); - } - } else { - // When 'rightleft' is set the list is drawn right-left. - cmdmsg_rl = curwin->w_p_rl; - if (cmdmsg_rl) { - msg_col = Columns - 1; - } - - // List the suggestions. - msg_start(); - msg_row = Rows - 1; // for when 'cmdheight' > 1 - lines_left = Rows; // avoid more prompt - vim_snprintf((char *)IObuff, IOSIZE, _("Change \"%.*s\" to:"), - sug.su_badlen, sug.su_badptr); - if (cmdmsg_rl && STRNCMP(IObuff, "Change", 6) == 0) { - // And now the rabbit from the high hat: Avoid showing the - // untranslated message rightleft. - vim_snprintf((char *)IObuff, IOSIZE, ":ot \"%.*s\" egnahC", - sug.su_badlen, sug.su_badptr); - } - msg_puts((const char *)IObuff); - msg_clr_eos(); - msg_putchar('\n'); - - msg_scroll = TRUE; - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // The suggested word may replace only part of the bad word, add - // the not replaced part. But only when it's not getting too long. - STRLCPY(wcopy, stp->st_word, MAXWLEN + 1); - int el = sug.su_badlen - stp->st_orglen; - if (el > 0 && stp->st_wordlen + el <= MAXWLEN) { - STRLCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen, el + 1); - } - vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1); - if (cmdmsg_rl) { - rl_mirror(IObuff); - } - msg_puts((const char *)IObuff); - - vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy); - msg_puts((const char *)IObuff); - - // The word may replace more than "su_badlen". - if (sug.su_badlen < stp->st_orglen) { - vim_snprintf((char *)IObuff, IOSIZE, _(" < \"%.*s\""), - stp->st_orglen, sug.su_badptr); - msg_puts((const char *)IObuff); - } - - if (p_verbose > 0) { - // Add the score. - if (sps_flags & (SPS_DOUBLE | SPS_BEST)) { - vim_snprintf((char *)IObuff, IOSIZE, " (%s%d - %d)", - stp->st_salscore ? "s " : "", - stp->st_score, stp->st_altscore); - } else { - vim_snprintf((char *)IObuff, IOSIZE, " (%d)", - stp->st_score); - } - if (cmdmsg_rl) { - // Mirror the numbers, but keep the leading space. - rl_mirror(IObuff + 1); - } - msg_advance(30); - msg_puts((const char *)IObuff); - } - msg_putchar('\n'); - } - - cmdmsg_rl = FALSE; - msg_col = 0; - // Ask for choice. - selected = prompt_for_number(&mouse_used); - - if (ui_has(kUIMessages)) { - ui_call_msg_clear(); - } - - if (mouse_used) { - selected -= lines_left; - } - lines_left = Rows; // avoid more prompt - // don't delay for 'smd' in normal_cmd() - msg_scroll = msg_scroll_save; - } - - if (selected > 0 && selected <= sug.su_ga.ga_len && u_save_cursor() == OK) { - // Save the from and to text for :spellrepall. - XFREE_CLEAR(repl_from); - XFREE_CLEAR(repl_to); - - stp = &SUG(sug.su_ga, selected - 1); - if (sug.su_badlen > stp->st_orglen) { - // Replacing less than "su_badlen", append the remainder to - // repl_to. - repl_from = vim_strnsave(sug.su_badptr, (size_t)sug.su_badlen); - vim_snprintf((char *)IObuff, IOSIZE, "%s%.*s", stp->st_word, - sug.su_badlen - stp->st_orglen, - sug.su_badptr + stp->st_orglen); - repl_to = vim_strsave(IObuff); - } else { - // Replacing su_badlen or more, use the whole word. - repl_from = vim_strnsave(sug.su_badptr, (size_t)stp->st_orglen); - repl_to = vim_strsave(stp->st_word); - } - - // Replace the word. - p = xmalloc(STRLEN(line) - (size_t)stp->st_orglen + (size_t)stp->st_wordlen + 1); - c = (int)(sug.su_badptr - line); - memmove(p, line, (size_t)c); - STRCPY(p + c, stp->st_word); - STRCAT(p, sug.su_badptr + stp->st_orglen); - - // For redo we use a change-word command. - ResetRedobuff(); - AppendToRedobuff("ciw"); - AppendToRedobuffLit((char *)p + c, - stp->st_wordlen + sug.su_badlen - stp->st_orglen); - AppendCharToRedobuff(ESC); - - // "p" may be freed here - ml_replace(curwin->w_cursor.lnum, (char *)p, false); - curwin->w_cursor.col = c; - - inserted_bytes(curwin->w_cursor.lnum, c, stp->st_orglen, stp->st_wordlen); - } else { - curwin->w_cursor = prev_cursor; - } - - spell_find_cleanup(&sug); - xfree(line); - curwin->w_p_spell = wo_spell_save; -} - // Check if the word at line "lnum" column "col" is required to start with a // capital. This uses 'spellcapcheck' of the current buffer. -static bool check_need_cap(linenr_T lnum, colnr_T col) +bool check_need_cap(linenr_T lnum, colnr_T col) { bool need_cap = false; char_u *line; @@ -3176,10 +2607,10 @@ void ex_spellrepall(exarg_T *eap) changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); if (curwin->w_cursor.lnum != prev_lnum) { - ++sub_nlines; + sub_nlines++; prev_lnum = curwin->w_cursor.lnum; } - ++sub_nsubs; + sub_nsubs++; } curwin->w_cursor.col += (colnr_T)STRLEN(repl_to); } @@ -3195,336 +2626,6 @@ void ex_spellrepall(exarg_T *eap) } } -/// Find spell suggestions for "word". Return them in the growarray "*gap" as -/// a list of allocated strings. -/// -/// @param maxcount maximum nr of suggestions -/// @param need_cap 'spellcapcheck' matched -void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, bool need_cap, bool interactive) -{ - suginfo_T sug; - suggest_T *stp; - char_u *wcopy; - - spell_find_suggest(word, 0, &sug, maxcount, false, need_cap, interactive); - - // Make room in "gap". - ga_init(gap, sizeof(char_u *), sug.su_ga.ga_len + 1); - ga_grow(gap, sug.su_ga.ga_len); - for (int i = 0; i < sug.su_ga.ga_len; ++i) { - stp = &SUG(sug.su_ga, i); - - // The suggested word may replace only part of "word", add the not - // replaced part. - wcopy = xmalloc((size_t)stp->st_wordlen + STRLEN(sug.su_badptr + stp->st_orglen) + 1); - STRCPY(wcopy, stp->st_word); - STRCPY(wcopy + stp->st_wordlen, sug.su_badptr + stp->st_orglen); - ((char_u **)gap->ga_data)[gap->ga_len++] = wcopy; - } - - spell_find_cleanup(&sug); -} - -/// Find spell suggestions for the word at the start of "badptr". -/// Return the suggestions in "su->su_ga". -/// The maximum number of suggestions is "maxcount". -/// Note: does use info for the current window. -/// This is based on the mechanisms of Aspell, but completely reimplemented. -/// -/// @param badlen length of bad word or 0 if unknown -/// @param banbadword don't include badword in suggestions -/// @param need_cap word should start with capital -static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, - bool banbadword, bool need_cap, bool interactive) -{ - hlf_T attr = HLF_COUNT; - char_u buf[MAXPATHL]; - char_u *p; - bool do_combine = false; - char_u *sps_copy; - static bool expr_busy = false; - int c; - langp_T *lp; - bool did_intern = false; - - // Set the info in "*su". - memset(su, 0, sizeof(suginfo_T)); - ga_init(&su->su_ga, (int)sizeof(suggest_T), 10); - ga_init(&su->su_sga, (int)sizeof(suggest_T), 10); - if (*badptr == NUL) { - return; - } - hash_init(&su->su_banned); - - su->su_badptr = badptr; - if (badlen != 0) { - su->su_badlen = badlen; - } else { - size_t tmplen = spell_check(curwin, su->su_badptr, &attr, NULL, false); - assert(tmplen <= INT_MAX); - su->su_badlen = (int)tmplen; - } - su->su_maxcount = maxcount; - su->su_maxscore = SCORE_MAXINIT; - - if (su->su_badlen >= MAXWLEN) { - su->su_badlen = MAXWLEN - 1; // just in case - } - STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword, - MAXWLEN); - - // TODO(vim): make this work if the case-folded text is longer than the - // original text. Currently an illegal byte causes wrong pointer - // computations. - su->su_fbadword[su->su_badlen] = NUL; - - // get caps flags for bad word - su->su_badflags = badword_captype(su->su_badptr, - su->su_badptr + su->su_badlen); - if (need_cap) { - su->su_badflags |= WF_ONECAP; - } - - // Find the default language for sound folding. We simply use the first - // one in 'spelllang' that supports sound folding. That's good for when - // using multiple files for one language, it's not that bad when mixing - // languages (e.g., "pl,en"). - for (int i = 0; i < curbuf->b_s.b_langp.ga_len; ++i) { - lp = LANGP_ENTRY(curbuf->b_s.b_langp, i); - if (lp->lp_sallang != NULL) { - su->su_sallang = lp->lp_sallang; - break; - } - } - - // Soundfold the bad word with the default sound folding, so that we don't - // have to do this many times. - if (su->su_sallang != NULL) { - spell_soundfold(su->su_sallang, su->su_fbadword, true, |