]> granicus.if.org Git - vim/commitdiff
patch 8.1.2081: the spell.c file is too big v8.1.2081
authorBram Moolenaar <Bram@vim.org>
Fri, 27 Sep 2019 10:41:56 +0000 (12:41 +0200)
committerBram Moolenaar <Bram@vim.org>
Fri, 27 Sep 2019 10:41:56 +0000 (12:41 +0200)
Problem:    The spell.c file is too big.
Solution:   Move the code for spell suggestions to a separate file. (Yegappan
            Lakshmanan, closes #4988)

14 files changed:
Filelist
src/Make_cyg_ming.mak
src/Make_morph.mak
src/Make_mvc.mak
src/Make_vms.mms
src/Makefile
src/README.md
src/proto.h
src/proto/spell.pro
src/proto/spellsuggest.pro [new file with mode: 0644]
src/spell.c
src/spell.h
src/spellsuggest.c [new file with mode: 0644]
src/version.c

index ff9d43954de1144fad879a29f136f8f6b28dc8b8..fa7934427e32ad8d92b97aaccccc1d28d3ecd566 100644 (file)
--- a/Filelist
+++ b/Filelist
@@ -111,6 +111,7 @@ SRC_ALL =   \
                src/spell.c \
                src/spell.h \
                src/spellfile.c \
+               src/spellsuggest.c \
                src/structs.h \
                src/syntax.c \
                src/tag.c \
@@ -255,6 +256,7 @@ SRC_ALL =   \
                src/proto/sound.pro \
                src/proto/spell.pro \
                src/proto/spellfile.pro \
+               src/proto/spellsuggest.pro \
                src/proto/syntax.pro \
                src/proto/tag.pro \
                src/proto/term.pro \
index 3bc57aa44cba1db3922b4ae00e6cd0b262c62c10..521ec3a35e277cb865250bfa9425e07f4be96b9e 100644 (file)
@@ -778,6 +778,7 @@ OBJ = \
        $(OUTDIR)/sign.o \
        $(OUTDIR)/spell.o \
        $(OUTDIR)/spellfile.o \
+       $(OUTDIR)/spellsuggest.o \
        $(OUTDIR)/syntax.o \
        $(OUTDIR)/tag.o \
        $(OUTDIR)/term.o \
index 0f3cd4ebfd1cfa51b196ae7ff860beec19fceebd..9c070c8e268dd765224d50b9f921424147ac71f3 100644 (file)
@@ -94,6 +94,7 @@ SRC = arabic.c                                                \
        sign.c                                                  \
        spell.c                                                 \
        spellfile.c                                             \
+       spellsuggest.c                                          \
        syntax.c                                                \
        tag.c                                                   \
        term.c                                                  \
index 3ad3547c1e803db562db83333834f53731783921..9146c7934e9eee3225cdb04d3c4a83c7ecf4bf78 100644 (file)
@@ -785,6 +785,7 @@ OBJ = \
        $(OUTDIR)\sign.obj \
        $(OUTDIR)\spell.obj \
        $(OUTDIR)\spellfile.obj \
+       $(OUTDIR)\spellsuggest.obj \
        $(OUTDIR)\syntax.obj \
        $(OUTDIR)\tag.obj \
        $(OUTDIR)\term.obj \
@@ -1670,6 +1671,8 @@ $(OUTDIR)/spell.obj:      $(OUTDIR) spell.c  $(INCL)
 
 $(OUTDIR)/spellfile.obj:       $(OUTDIR) spellfile.c  $(INCL)
 
+$(OUTDIR)/spellsuggest.obj:    $(OUTDIR) spellsuggest.c  $(INCL)
+
 $(OUTDIR)/syntax.obj:  $(OUTDIR) syntax.c  $(INCL)
 
 $(OUTDIR)/tag.obj:     $(OUTDIR) tag.c  $(INCL)
@@ -1852,6 +1855,7 @@ proto.h: \
        proto/sign.pro \
        proto/spell.pro \
        proto/spellfile.pro \
+       proto/spellsuggest.pro \
        proto/syntax.pro \
        proto/tag.pro \
        proto/term.pro \
index b8e5085bb6f0a723658531a8db69e479e65b3fce..3b45b7885a9e290b2d9c34b8072d00936a09c9f6 100644 (file)
@@ -320,7 +320,8 @@ SRC =       arabic.c arglist.c autocmd.c beval.c blob.c blowfish.c buffer.c \
        ops.c \
        option.c optionstr.c popupmnu.c popupwin.c profiler.c quickfix.c \
        regexp.c register.c scriptfile.c \
-       search.c session.c sha256.c sign.c spell.c spellfile.c syntax.c tag.c \
+       search.c session.c sha256.c sign.c spell.c spellfile.c spellsuggest.c \
+       syntax.c tag.c \
        term.c termlib.c testing.c textprop.c ui.c undo.c usercmd.c \
        userfunc.c version.c viminfo.c screen.c window.c os_unix.c os_vms.c \
        pathdef.c \
@@ -342,7 +343,8 @@ OBJ =       arabic.obj arglist.obj autocmd.obj beval.obj blob.obj blowfish.obj \
        optionstr.obj popupmnu.obj popupwin.obj profiler.obj quickfix.obj \
        regexp.obj register.obj scriptfile.obj \
        search.obj session.obj sha256.obj sign.obj spell.obj spellfile.obj \
-       syntax.obj tag.obj term.obj termlib.obj testing.obj textprop.obj \
+       spellsuggest.obj syntax.obj tag.obj term.obj termlib.obj testing.obj \
+       textprop.obj \
        ui.obj undo.obj usercmd.obj userfunc.obj screen.obj version.obj \
        viminfo.obj window.obj os_unix.obj os_vms.obj pathdef.obj if_mzsch.obj \
        $(GUI_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(TCL_OBJ) \
@@ -791,6 +793,10 @@ spellfile.obj : spellfile.c vim.h [.auto]config.h feature.h os_unix.h \
  ascii.h keymap.h term.h macros.h option.h structs.h \
  regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+spellsuggest.obj : spellsuggest.c vim.h [.auto]config.h feature.h os_unix.h \
+ ascii.h keymap.h term.h macros.h option.h structs.h \
+ regexp.h gui.h beval.h [.proto]gui_beval.pro alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 syntax.obj : syntax.c vim.h [.auto]config.h feature.h os_unix.h \
  ascii.h keymap.h term.h macros.h structs.h regexp.h \
  gui.h beval.h [.proto]gui_beval.pro option.h ex_cmds.h proto.h \
index 9462404adfc0710096568628448247259ff7d542..0f949a89123af2fdd4f1c6efe7cdb1cea80304d9 100644 (file)
@@ -1654,6 +1654,7 @@ BASIC_SRC = \
        sound.c \
        spell.c \
        spellfile.c \
+       spellsuggest.c \
        syntax.c \
        tag.c \
        term.c \
@@ -1789,6 +1790,7 @@ OBJ_COMMON = \
        objects/sound.o \
        objects/spell.o \
        objects/spellfile.o \
+       objects/spellsuggest.o \
        objects/syntax.o \
        objects/tag.o \
        objects/term.o \
@@ -1949,6 +1951,7 @@ PRO_AUTO = \
        sound.pro \
        spell.pro \
        spellfile.pro \
+       spellsuggest.pro \
        syntax.pro \
        tag.pro \
        term.pro \
@@ -3386,6 +3389,9 @@ objects/spell.o: spell.c
 objects/spellfile.o: spellfile.c
        $(CCC) -o $@ spellfile.c
 
+objects/spellsuggest.o: spellsuggest.c
+       $(CCC) -o $@ spellsuggest.c
+
 objects/syntax.o: syntax.c
        $(CCC) -o $@ syntax.c
 
@@ -3881,6 +3887,10 @@ objects/spellfile.o: spellfile.c vim.h protodef.h auto/config.h feature.h \
  os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
  proto.h globals.h
+objects/spellsuggest.o: spellsuggest.c vim.h protodef.h auto/config.h feature.h \
+ os_unix.h auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
+ proto.h globals.h
 objects/syntax.o: syntax.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h term.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h alloc.h ex_cmds.h spell.h \
index b5ee8b57184107f42fddc753183ba316f51793de..b08f0b493103ccfcfaa458f6afa268f35dfcb50b 100644 (file)
@@ -69,7 +69,9 @@ screen.c      | lower level screen functions
 search.c       | pattern searching
 session.c      | sessions and views
 sign.c         | signs
-spell.c                | spell checking
+spell.c                | spell checking core
+spellfile.c    | spell file handling
+spellsuggest.c | spell correction suggestions
 syntax.c       | syntax and other highlighting
 tag.c          | tags
 term.c         | terminal handling, termcap codes
index ae4c63a1c7d53214dcb8b59f21183a375b06ce8a..6d3b83deabad5fde63bdc835fe858797973fc542 100644 (file)
@@ -202,6 +202,7 @@ void qsort(void *base, size_t elm_count, size_t elm_size, int (*cmp)(const void
 # include "sound.pro"
 # include "spell.pro"
 # include "spellfile.pro"
+# include "spellsuggest.pro"
 # include "syntax.pro"
 # include "tag.pro"
 # include "term.pro"
index 6114412585e40690c3d27a024b3199159126f0be..461b5e10eaf7106743477156eea550f7c83dd8f0 100644 (file)
@@ -1,5 +1,11 @@
 /* spell.c */
 int spell_check(win_T *wp, char_u *ptr, hlf_T *attrp, int *capcol, int docount);
+int match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap);
+int can_compound(slang_T *slang, char_u *word, char_u *flags);
+int match_compoundrule(slang_T *slang, char_u *compflags);
+int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, int cond_req);
+int spell_valid_case(int wordflags, int treeflags);
+int no_spell_checking(win_T *wp);
 int spell_move_to(win_T *wp, int dir, int allwords, int curline, hlf_T *attrp);
 void spell_cat_line(char_u *buf, char_u *line, int maxlen);
 char_u *spell_enc(void);
@@ -19,12 +25,15 @@ buf_T *open_spellbuf(void);
 void close_spellbuf(buf_T *buf);
 void clear_spell_chartab(spelltab_T *sp);
 void init_spell_chartab(void);
+int spell_iswordp(char_u *p, win_T *wp);
+int spell_iswordp_nmw(char_u *p, win_T *wp);
 int spell_casefold(char_u *str, int len, char_u *buf, int buflen);
-int spell_check_sps(void);
-void spell_suggest(int count);
+int check_need_cap(linenr_T lnum, colnr_T col);
 void ex_spellrepall(exarg_T *eap);
-void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, int need_cap, int interactive);
 void onecap_copy(char_u *word, char_u *wcopy, int upper);
+void allcap_copy(char_u *word, char_u *wcopy);
+int nofold_len(char_u *fword, int flen, char_u *word);
+void make_case_word(char_u *fword, char_u *cword, int flags);
 char_u *eval_soundfold(char_u *word);
 void spell_soundfold(slang_T *slang, char_u *inword, int folded, char_u *res);
 void ex_spellinfo(exarg_T *eap);
diff --git a/src/proto/spellsuggest.pro b/src/proto/spellsuggest.pro
new file mode 100644 (file)
index 0000000..745d407
--- /dev/null
@@ -0,0 +1,5 @@
+/* spellsuggest.c */
+int spell_check_sps(void);
+void spell_suggest(int count);
+void spell_suggest_list(garray_T *gap, char_u *word, int maxcount, int need_cap, int interactive);
+/* vim: set ft=c : */
index 4f4959e301332fb1cfa1e7ce19651d07ef311725..de478a3ed0664b39b23563a616b123137f828a7e 100644 (file)
  * See ":help develop-spell".
  */
 
-/*
- * 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)
-
 #define IN_SPELL_C
 #include "vim.h"
 
 # include <time.h>     /* for time_t */
 #endif
 
-/* 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)
-
 #define REGION_ALL 0xff                /* word valid in all regions */
 
 #define VIMSUGMAGIC "VIMsug"   /* string at start of Vim .sug file */
 #define SP_LOCAL       2
 #define SP_BAD         3
 
-typedef struct wordcount_S
-{
-    short_u    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 suggest_S
-{
-    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 */
-    int                st_salscore;    /* st_score is for soundalike */
-    int                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 then 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.
  */
@@ -244,80 +120,8 @@ typedef struct matchinf_S
 } matchinf_T;
 
 
-static int spell_iswordp(char_u *p, win_T *wp);
 static int spell_mb_isword_class(int cl, win_T *wp);
 
-/*
- * For finding suggestions: At each node in the tree these states are tried:
- */
-typedef enum
-{
-    STATE_START = 0,   /* At start of node check for NUL bytes (goodword
-                        * ends); if badword ends there is a match, otherwise
-                        * try splitting word. */
-    STATE_NOPREFIX,    /* try without prefix */
-    STATE_SPLITUNDO,   /* Undo splitting. */
-    STATE_ENDNUL,      /* Past NUL bytes at start of the node. */
-    STATE_PLAIN,       /* Use each byte of the node. */
-    STATE_DEL,         /* Delete a byte from the bad word. */
-    STATE_INS_PREP,    /* Prepare for inserting bytes. */
-    STATE_INS,         /* Insert a byte in the bad word. */
-    STATE_SWAP,                /* Swap two bytes. */
-    STATE_UNSWAP,      /* Undo swap two characters. */
-    STATE_SWAP3,       /* Swap two characters over three. */
-    STATE_UNSWAP3,     /* Undo Swap two characters over three. */
-    STATE_UNROT3L,     /* Undo rotate three characters left */
-    STATE_UNROT3R,     /* Undo rotate three characters right */
-    STATE_REP_INI,     /* Prepare for using REP items. */
-    STATE_REP,         /* Use matching REP items from the .aff file. */
-    STATE_REP_UNDO,    /* Undo a REP item replacement. */
-    STATE_FINAL                /* End of this node. */
-} state_T;
-
-/*
- * Struct to keep the state at each level in suggest_try_change().
- */
-typedef struct trystate_S
-{
-    state_T    ts_state;       /* state at this level, STATE_ */
-    int                ts_score;       /* score */
-    idx_T      ts_arridx;      /* index in tree array, start of node */
-    short      ts_curi;        /* index in list of child nodes */
-    char_u     ts_fidx;        /* index in fword[], case-folded bad word */
-    char_u     ts_fidxtry;     /* ts_fidx at which bytes may be changed */
-    char_u     ts_twordlen;    /* valid length of tword[] */
-    char_u     ts_prefixdepth; /* stack depth for end of prefix or
-                                * PFD_PREFIXTREE or PFD_NOPREFIX */
-    char_u     ts_flags;       /* TSF_ flags */
-    char_u     ts_tcharlen;    /* number of bytes in tword character */
-    char_u     ts_tcharidx;    /* current byte index in tword character */
-    char_u     ts_isdiff;      /* DIFF_ values */
-    char_u     ts_fcharstart;  /* index in fword where badword char started */
-    char_u     ts_prewordlen;  /* length of word in "preword[]" */
-    char_u     ts_splitoff;    /* index in "tword" after last split */
-    char_u     ts_splitfidx;   /* "ts_fidx" at word split */
-    char_u     ts_complen;     /* nr of compound words used */
-    char_u     ts_compsplit;   /* index for "compflags" where word was spit */
-    char_u     ts_save_badflags;   /* su_badflags saved here */
-    char_u     ts_delidx;      /* index in fword for char that was deleted,
-                                  valid when "ts_flags" has TSF_DIDDEL */
-} trystate_T;
-
-/* 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 */
@@ -326,64 +130,19 @@ typedef struct trystate_S
 #define FIND_KEEPCOMPOUND   4  /* find keep-case compound word */
 
 static void find_word(matchinf_T *mip, int mode);
-static int match_checkcompoundpattern(char_u *ptr, int wlen, garray_T *gap);
-static int can_compound(slang_T *slang, char_u *word, char_u *flags);
-static int match_compoundrule(slang_T *slang, char_u *compflags);
-static int valid_word_prefix(int totprefcnt, int arridx, int flags, char_u *word, slang_T *slang, int cond_req);
 static void find_prefix(matchinf_T *mip, int mode);
 static int fold_more(matchinf_T *mip);
-static int spell_valid_case(int wordflags, int treeflags);
 static void spell_load_cb(char_u *fname, void *cookie);
 static int count_syllables(slang_T *slang, char_u *word);
 static void clear_midword(win_T *buf);
 static void use_midword(slang_T *lp, win_T *buf);
 static int find_region(char_u *rp, char_u *region);
-static int spell_iswordp_nmw(char_u *p, win_T *wp);
-static int check_need_cap(linenr_T lnum, colnr_T col);
-static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, int banbadword, int need_cap, int interactive);
-#ifdef FEAT_EVAL
-static void spell_suggest_expr(suginfo_T *su, char_u *expr);
-#endif
-static void spell_suggest_file(suginfo_T *su, char_u *fname);
-static void spell_suggest_intern(suginfo_T *su, int interactive);
-static void spell_find_cleanup(suginfo_T *su);
-static void suggest_try_special(suginfo_T *su);
-static void suggest_try_change(suginfo_T *su);
-static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, int soundfold);
-static void go_deeper(trystate_T *stack, int depth, int score_add);
-static int nofold_len(char_u *fword, int flen, char_u *word);
-static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword);
-static void score_comp_sal(suginfo_T *su);
-static void score_combine(suginfo_T *su);
-static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound);
-static void suggest_try_soundalike_prep(void);
-static void suggest_try_soundalike(suginfo_T *su);
-static void suggest_try_soundalike_finish(void);
-static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp);
-static int soundfold_find(slang_T *slang, char_u *word);
-static void make_case_word(char_u *fword, char_u *cword, int flags);
-static int similar_chars(slang_T *slang, int c1, int c2);
-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);
-static void check_suggestions(suginfo_T *su, garray_T *gap);
-static void add_banned(suginfo_T *su, char_u *word);
-static void rescore_suggestions(suginfo_T *su);
-static void rescore_one(suginfo_T *su, suggest_T *stp);
-static int cleanup_suggestions(garray_T *gap, int maxscore, int keep);
 static void spell_soundfold_sofo(slang_T *slang, char_u *inword, char_u *res);
 static void spell_soundfold_sal(slang_T *slang, char_u *inword, char_u *res);
 static void spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res);
-static int soundalike_score(char_u *goodsound, char_u *badsound);
-static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword);
-static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit);
-static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit);
 static void dump_word(slang_T *slang, char_u *word, char_u *pat, int *dir, int round, int flags, linenr_T lnum);
 static linenr_T dump_prefixes(slang_T *slang, char_u *word, char_u *pat, int *dir, int round, int flags, linenr_T startlnum);
 
-
-/* Remember what "z?" replaced. */
-static char_u  *repl_from = NULL;
-static char_u  *repl_to = NULL;
-
 /*
  * Main spell-checking function.
  * "ptr" points to a character that could be the start of a word.
@@ -1125,7 +884,7 @@ find_word(matchinf_T *mip, int mode)
  * A match means that the first part of CHECKCOMPOUNDPATTERN matches at the
  * end of ptr[wlen] and the second part matches after it.
  */
-    static int
+    int
 match_checkcompoundpattern(
     char_u     *ptr,
     int                wlen,
@@ -1155,7 +914,7 @@ match_checkcompoundpattern(
  * Return TRUE if "flags" is a valid sequence of compound flags and "word"
  * does not have too many syllables.
  */
-    static int
+    int
 can_compound(slang_T *slang, char_u *word, char_u *flags)
 {
     char_u     uflags[MAXWLEN * 2];
@@ -1187,49 +946,13 @@ can_compound(slang_T *slang, char_u *word, char_u *flags)
     return TRUE;
 }
 
-/*
- * Return 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 int
-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)
-    {
-       int v;
-
-       compflags[sp->ts_complen] = flag;
-       compflags[sp->ts_complen + 1] = NUL;
-       v = match_compoundrule(slang, compflags + sp->ts_compsplit);
-       compflags[sp->ts_complen] = NUL;
-       return v;
-    }
-
-    return TRUE;
-}
-
-
 /*
  * Return 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 int
+    int
 match_compoundrule(slang_T *slang, char_u *compflags)
 {
     char_u     *p;
@@ -1282,7 +1005,7 @@ match_compoundrule(slang_T *slang, char_u *compflags)
  * ID in "flags" for the word "word".
  * The WF_RAREPFX flag is included in the return value for a rare prefix.
  */
-    static int
+    int
 valid_word_prefix(
     int                totprefcnt,     /* nr of prefix IDs */
     int                arridx,         /* idx in sl_pidxs[] */
@@ -1482,7 +1205,7 @@ fold_more(matchinf_T *mip)
  * Check case flags for a word.  Return TRUE if the word has the requested
  * case.
  */
-    static int
+    int
 spell_valid_case(
     int            wordflags,      /* flags for the checked word. */
     int            treeflags)      /* flags for the word in the spell tree */
@@ -1496,7 +1219,7 @@ spell_valid_case(
 /*
  * Return TRUE if spell checking is not enabled.
  */
-    static int
+    int
 no_spell_checking(win_T *wp)
 {
     if (!wp->w_p_spell || *wp->w_s->b_p_spl == NUL
@@ -2101,43 +1824,6 @@ count_common_word(
     }
 }
 
-/*
- * Adjust the score of common words.
- */
-    static int
-score_wordcount_adj(
-    slang_T    *slang,
-    int                score,
-    char_u     *word,
-    int                split)      /* word was split, less bonus */
-{
-    hashitem_T *hi;
-    wordcount_T        *wc;
-    int                bonus;
-    int                newscore;
-
-    hi = hash_find(&slang->sl_wordcount, 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;
-}
-
-
 /*
  * Return TRUE if byte "n" appears in "str".
  * Like strchr() but independent of locale.
@@ -2711,53 +2397,6 @@ captype(
     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)
-{
-    int                flags = captype(word, end);
-    int                c;
-    int                l, u;
-    int                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 = PTR2CHAR(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.
  */
@@ -2832,47 +2471,6 @@ 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
@@ -3012,7 +2610,7 @@ init_spell_chartab(void)
  * followed by a word character.  This finds they'there but not 'they there'.
  * Thus this only works properly when past the first character of the word.
  */
-    static int
+    int
 spell_iswordp(
     char_u     *p,
     win_T      *wp)        /* buffer used */
@@ -3053,7 +2651,7 @@ spell_iswordp(
  * Return TRUE if "p" points to a word character.
  * Unlike spell_iswordp() this doesn't check for "midword" characters.
  */
-    static int
+    int
 spell_iswordp_nmw(char_u *p, win_T *wp)
 {
     int                c;
@@ -3162,313 +2760,11 @@ spell_casefold(
     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(&p, buf, MAXPATHL, ",");
-
-       f = 0;
-       if (VIM_ISDIGIT(*buf))
-       {
-           s = buf;
-           sps_limit = getdigits(&s);
-           if (*s != NUL && !VIM_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                i;
-    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;
-
-    if (no_spell_checking(curwin))
-       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();
-    }
-    /* Find the start of the badly spelled word. */
-    else 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 = ml_get_curline();
-       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(ml_get_curline());
-    if (line == NULL)
-       goto skip;
-
-    /* Get the list of suggestions.  Limit to 'lines' - 2 or the number in
-     * 'spellsuggest', whatever is smaller. */
-    if (sps_limit > (int)Rows - 2)
-       limit = (int)Rows - 2;
-    else
-       limit = sps_limit;
-    spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
-                                                       TRUE, need_cap, TRUE);
-
-    if (sug.su_ga.ga_len == 0)
-       msg(_("Sorry, no suggestions"));
-    else if (count > 0)
-    {
-       if (count > sug.su_ga.ga_len)
-           smsg(_("Sorry, only %ld suggestions"),
-                                                     (long)sug.su_ga.ga_len);
-    }
-    else
-    {
-       VIM_CLEAR(repl_from);
-       VIM_CLEAR(repl_to);
-
-#ifdef FEAT_RIGHTLEFT
-       /* When 'rightleft' is set the list is drawn right-left. */
-       cmdmsg_rl = curwin->w_p_rl;
-       if (cmdmsg_rl)
-           msg_col = Columns - 1;
-#endif
-
-       /* 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);
-#ifdef FEAT_RIGHTLEFT
-       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);
-       }
-#endif
-       msg_puts((char *)IObuff);
-       msg_clr_eos();
-       msg_putchar('\n');
-
-       msg_scroll = TRUE;
-       for (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. */
-           vim_strncpy(wcopy, stp->st_word, MAXWLEN);
-           if (sug.su_badlen > stp->st_orglen)
-               vim_strncpy(wcopy + stp->st_wordlen,
-                                              sug.su_badptr + stp->st_orglen,
-                                             sug.su_badlen - stp->st_orglen);
-           vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1);
-#ifdef FEAT_RIGHTLEFT
-           if (cmdmsg_rl)
-               rl_mirror(IObuff);
-#endif
-           msg_puts((char *)IObuff);
-
-           vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy);
-           msg_puts((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((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);
-#ifdef FEAT_RIGHTLEFT
-               if (cmdmsg_rl)
-                   /* Mirror the numbers, but keep the leading space. */
-                   rl_mirror(IObuff + 1);
-#endif
-               msg_advance(30);
-               msg_puts((char *)IObuff);
-           }
-           msg_putchar('\n');
-       }
-
-#ifdef FEAT_RIGHTLEFT
-       cmdmsg_rl = FALSE;
-       msg_col = 0;
-#endif
-       /* Ask for choice. */
-       selected = prompt_for_number(&mouse_used);
-       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. */
-       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, 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, stp->st_orglen);
-           repl_to = vim_strsave(stp->st_word);
-       }
-
-       /* Replace the word. */
-       p = alloc(STRLEN(line) - stp->st_orglen + stp->st_wordlen + 1);
-       if (p != NULL)
-       {
-           c = (int)(sug.su_badptr - line);
-           mch_memmove(p, line, c);
-           STRCPY(p + c, stp->st_word);
-           STRCAT(p, sug.su_badptr + stp->st_orglen);
-           ml_replace(curwin->w_cursor.lnum, p, FALSE);
-           curwin->w_cursor.col = c;
-
-           /* For redo we use a change-word command. */
-           ResetRedobuff();
-           AppendToRedobuff((char_u *)"ciw");
-           AppendToRedobuffLit(p + c,
-                           stp->st_wordlen + sug.su_badlen - stp->st_orglen);
-           AppendCharToRedobuff(ESC);
-
-           /* After this "p" may be invalid. */
-           changed_bytes(curwin->w_cursor.lnum, c);
-       }
-    }
-    else
-       curwin->w_cursor = prev_cursor;
-
-    spell_find_cleanup(&sug);
-skip:
-    vim_free(line);
-}
-
 /*
  * Check if the word at line "lnum" column "col" is required to start with a
  * capital.  This uses 'spellcapcheck' of the current buffer.
  */
-    static int
+    int
 check_need_cap(linenr_T lnum, colnr_T col)
 {
     int                need_cap = FALSE;
@@ -3605,435 +2901,48 @@ ex_spellrepall(exarg_T *eap UNUSED)
 }
 
 /*
- * Find spell suggestions for "word".  Return them in the growarray "*gap" as
- * a list of allocated strings.
+ * Make a copy of "word", with the first letter upper or lower cased, to
+ * "wcopy[MAXWLEN]".  "word" must not be empty.
+ * The result is NUL terminated.
  */
     void
-spell_suggest_list(
-    garray_T   *gap,
+onecap_copy(
     char_u     *word,
-    int                maxcount,       /* maximum nr of suggestions */
-    int                need_cap,       /* 'spellcapcheck' matched */
-    int                interactive)
+    char_u     *wcopy,
+    int                upper)      /* TRUE: first letter made upper case */
 {
-    suginfo_T  sug;
-    int                i;
-    suggest_T  *stp;
-    char_u     *wcopy;
-
-    spell_find_suggest(word, 0, &sug, maxcount, FALSE, need_cap, interactive);
+    char_u     *p;
+    int                c;
+    int                l;
 
-    /* Make room in "gap". */
-    ga_init2(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
-    if (ga_grow(gap, sug.su_ga.ga_len) == OK)
+    p = word;
+    if (has_mbyte)
+       c = mb_cptr2char_adv(&p);
+    else
+       c = *p++;
+    if (upper)
+       c = SPELL_TOUPPER(c);
+    else
+       c = SPELL_TOFOLD(c);
+    if (has_mbyte)
+       l = mb_char2bytes(c, wcopy);
+    else
     {
-       for (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 = alloc(stp->st_wordlen
-                     + (unsigned)STRLEN(sug.su_badptr + stp->st_orglen) + 1);
-           if (wcopy == NULL)
-               break;
-           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;
-       }
+       l = 1;
+       wcopy[0] = c;
     }
-
-    spell_find_cleanup(&sug);
+    vim_strncpy(wcopy + l, p, MAXWLEN - l - 1);
 }
 
 /*
- * 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.
+ * Make a copy of "word" with all the letters upper cased into
+ * "wcopy[MAXWLEN]".  The result is NUL terminated.
  */
-    static void
-spell_find_suggest(
-    char_u     *badptr,
-    int                badlen,         /* length of bad word or 0 if unknown */
-    suginfo_T  *su,
-    int                maxcount,
-    int                banbadword,     /* don't include badword in suggestions */
-    int                need_cap,       /* word should start with capital */
-    int                interactive)
+    void
+allcap_copy(char_u *word, char_u *wcopy)
 {
-    hlf_T      attr = HLF_COUNT;
-    char_u     buf[MAXPATHL];
-    char_u     *p;
-    int                do_combine = FALSE;
-    char_u     *sps_copy;
-#ifdef FEAT_EVAL
-    static int expr_busy = FALSE;
-#endif
-    int                c;
-    int                i;
-    langp_T    *lp;
-
-    /*
-     * Set the info in "*su".
-     */
-    vim_memset(su, 0, sizeof(suginfo_T));
-    ga_init2(&su->su_ga, (int)sizeof(suggest_T), 10);
-    ga_init2(&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
-       su->su_badlen = spell_check(curwin, su->su_badptr, &attr, NULL, FALSE);
-    su->su_maxcount = maxcount;
-    su->su_maxscore = SCORE_MAXINIT;
-
-    if (su->su_badlen >= MAXWLEN)
-       su->su_badlen = MAXWLEN - 1;    /* just in case */
-    vim_strncpy(su->su_badword, su->su_badptr, su->su_badlen);
-    (void)spell_casefold(su->su_badptr, su->su_badlen,
-                                                   su->su_fbadword, MAXWLEN);
-    /* TODO: 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 (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,
-                                                         su->su_sal_badword);
-
-    /* If the word is not capitalised and spell_check() doesn't consider the
-     * word to be bad then it might need to be capitalised.  Add a suggestion
-     * for that. */
-    c = PTR2CHAR(su->su_badptr);
-    if (!SPELL_ISUPPER(c) && attr == HLF_COUNT)
-    {
-       make_case_word(su->su_badword, buf, WF_ONECAP);
-       add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
-                                             0, TRUE, su->su_sallang, FALSE);
-    }
-
-    /* Ban the bad word itself.  It may appear in another region. */
-    if (banbadword)
-       add_banned(su, su->su_badword);
-
-    /* Make a copy of 'spellsuggest', because the expression may change it. */
-    sps_copy = vim_strsave(p_sps);
-    if (sps_copy == NULL)
-       return;
-
-    /* Loop over the items in 'spellsuggest'. */
-    for (p = sps_copy; *p != NUL; )
-    {
-       copy_option_part(&p, buf, MAXPATHL, ",");
-
-       if (STRNCMP(buf, "expr:", 5) == 0)
-       {
-#ifdef FEAT_EVAL
-           /* Evaluate an expression.  Skip this when called recursively,
-            * when using spellsuggest() in the expression. */
-           if (!expr_busy)
-           {
-               expr_busy = TRUE;
-               spell_suggest_expr(su, buf + 5);
-               expr_busy = FALSE;
-           }
-#endif
-       }
-       else if (STRNCMP(buf, "file:", 5) == 0)
-           /* Use list of suggestions in a file. */
-           spell_suggest_file(su, buf + 5);
-       else
-       {
-           /* Use internal method. */
-           spell_suggest_intern(su, interactive);
-           if (sps_flags & SPS_DOUBLE)
-               do_combine = TRUE;
-       }
-    }
-
-    vim_free(sps_copy);
-
-    if (do_combine)
-       /* Combine the two list of suggestions.  This must be done last,
-        * because sorting changes the order again. */
-       score_combine(su);
-}
-
-#ifdef FEAT_EVAL
-/*
- * Find suggestions by evaluating expression "expr".
- */
-    static void
-spell_suggest_expr(suginfo_T *su, char_u *expr)
-{
-    list_T     *list;
-    listitem_T *li;
-    int                score;
-    char_u     *p;
-
-    /* The work is split up in a few parts to avoid having to export
-     * suginfo_T.
-     * First evaluate the expression and get the resulting list. */
-    list = eval_spell_expr(su->su_badword, expr);
-    if (list != NULL)
-    {
-       /* Loop over the items in the list. */
-       for (li = list->lv_first; li != NULL; li = li->li_next)
-           if (li->li_tv.v_type == VAR_LIST)
-           {
-               /* Get the word and the score from the items. */
-               score = get_spellword(li->li_tv.vval.v_list, &p);
-               if (score >= 0 && score <= su->su_maxscore)
-                   add_suggestion(su, &su->su_ga, p, su->su_badlen,
-                                      score, 0, TRUE, su->su_sallang, FALSE);
-           }
-       list_unref(list);
-    }
-
-    /* Remove bogus suggestions, sort and truncate at "maxcount". */
-    check_suggestions(su, &su->su_ga);
-    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-}
-#endif
-
-/*
- * Find suggestions in file "fname".  Used for "file:" in 'spellsuggest'.
- */
-    static void
-spell_suggest_file(suginfo_T *su, char_u *fname)
-{
-    FILE       *fd;
-    char_u     line[MAXWLEN * 2];
-    char_u     *p;
-    int                len;
-    char_u     cword[MAXWLEN];
-
-    /* Open the file. */
-    fd = mch_fopen((char *)fname, "r");
-    if (fd == NULL)
-    {
-       semsg(_(e_notopen), fname);
-       return;
-    }
-
-    /* Read it line by line. */
-    while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int)
-    {
-       line_breakcheck();
-
-       p = vim_strchr(line, '/');
-       if (p == NULL)
-           continue;       /* No Tab found, just skip the line. */
-       *p++ = NUL;
-       if (STRICMP(su->su_badword, line) == 0)
-       {
-           /* Match!  Isolate the good word, until CR or NL. */
-           for (len = 0; p[len] >= ' '; ++len)
-               ;
-           p[len] = NUL;
-
-           /* If the suggestion doesn't have specific case duplicate the case
-            * of the bad word. */
-           if (captype(p, NULL) == 0)
-           {
-               make_case_word(p, cword, su->su_badflags);
-               p = cword;
-           }
-
-           add_suggestion(su, &su->su_ga, p, su->su_badlen,
-                                 SCORE_FILE, 0, TRUE, su->su_sallang, FALSE);
-       }
-    }
-
-    fclose(fd);
-
-    /* Remove bogus suggestions, sort and truncate at "maxcount". */
-    check_suggestions(su, &su->su_ga);
-    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-}
-
-/*
- * Find suggestions for the internal method indicated by "sps_flags".
- */
-    static void
-spell_suggest_intern(suginfo_T *su, int interactive)
-{
-    /*
-     * Load the .sug file(s) that are available and not done yet.
-     */
-    suggest_load_files();
-
-    /*
-     * 1. Try special cases, such as repeating a word: "the the" -> "the".
-     *
-     * Set a maximum score to limit the combination of operations that is
-     * tried.
-     */
-    suggest_try_special(su);
-
-    /*
-     * 2. Try inserting/deleting/swapping/changing a letter, use REP entries
-     *    from the .aff file and inserting a space (split the word).
-     */
-    suggest_try_change(su);
-
-    /* For the resulting top-scorers compute the sound-a-like score. */
-    if (sps_flags & SPS_DOUBLE)
-       score_comp_sal(su);
-
-    /*
-     * 3. Try finding sound-a-like words.
-     */
-    if ((sps_flags & SPS_FAST) == 0)
-    {
-       if (sps_flags & SPS_BEST)
-           /* Adjust the word score for the suggestions found so far for how
-            * they sounds like. */
-           rescore_suggestions(su);
-
-       /*
-        * While going through the soundfold tree "su_maxscore" is the score
-        * for the soundfold word, limits the changes that are being tried,
-        * and "su_sfmaxscore" the rescored score, which is set by
-        * cleanup_suggestions().
-        * First find words with a small edit distance, because this is much
-        * faster and often already finds the top-N suggestions.  If we didn't
-        * find many suggestions try again with a higher edit distance.
-        * "sl_sounddone" is used to avoid doing the same word twice.
-        */
-       suggest_try_soundalike_prep();
-       su->su_maxscore = SCORE_SFMAX1;
-       su->su_sfmaxscore = SCORE_MAXINIT * 3;
-       suggest_try_soundalike(su);
-       if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su))
-       {
-           /* We didn't find enough matches, try again, allowing more
-            * changes to the soundfold word. */
-           su->su_maxscore = SCORE_SFMAX2;
-           suggest_try_soundalike(su);
-           if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su))
-           {
-               /* Still didn't find enough matches, try again, allowing even
-                * more changes to the soundfold word. */
-               su->su_maxscore = SCORE_SFMAX3;
-               suggest_try_soundalike(su);
-           }
-       }
-       su->su_maxscore = su->su_sfmaxscore;
-       suggest_try_soundalike_finish();
-    }
-
-    /* When CTRL-C was hit while searching do show the results.  Only clear
-     * got_int when using a command, not for spellsuggest(). */
-    ui_breakcheck();
-    if (interactive && got_int)
-    {
-       (void)vgetc();
-       got_int = FALSE;
-    }
-
-    if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0)
-    {
-       if (sps_flags & SPS_BEST)
-           /* Adjust the word score for how it sounds like. */
-           rescore_suggestions(su);
-
-       /* Remove bogus suggestions, sort and truncate at "maxcount". */
-       check_suggestions(su, &su->su_ga);
-       (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-    }
-}
-
-/*
- * Free the info put in "*su" by spell_find_suggest().
- */
-    static void
-spell_find_cleanup(suginfo_T *su)
-{
-    int                i;
-
-    /* Free the suggestions. */
-    for (i = 0; i < su->su_ga.ga_len; ++i)
-       vim_free(SUG(su->su_ga, i).st_word);
-    ga_clear(&su->su_ga);
-    for (i = 0; i < su->su_sga.ga_len; ++i)
-       vim_free(SUG(su->su_sga, i).st_word);
-    ga_clear(&su->su_sga);
-
-    /* Free the banned words. */
-    hash_clear_all(&su->su_banned, 0);
-}
-
-/*
- * Make a copy of "word", with the first letter upper or lower cased, to
- * "wcopy[MAXWLEN]".  "word" must not be empty.
- * The result is NUL terminated.
- */
-    void
-onecap_copy(
-    char_u     *word,
-    char_u     *wcopy,
-    int                upper)      /* TRUE: first letter made upper case */
-{
-    char_u     *p;
-    int                c;
-    int                l;
-
-    p = word;
-    if (has_mbyte)
-       c = mb_cptr2char_adv(&p);
-    else
-       c = *p++;
-    if (upper)
-       c = SPELL_TOUPPER(c);
-    else
-       c = SPELL_TOFOLD(c);
-    if (has_mbyte)
-       l = mb_char2bytes(c, wcopy);
-    else
-    {
-       l = 1;
-       wcopy[0] = c;
-    }
-    vim_strncpy(wcopy + l, p, MAXWLEN - l - 1);
-}
-
-/*
- * Make a copy of "word" with all the letters upper cased into
- * "wcopy[MAXWLEN]".  The result is NUL terminated.
- */
-    static void
-allcap_copy(char_u *word, char_u *wcopy)
-{
-    char_u     *s;
-    char_u     *d;
+    char_u     *s;
+    char_u     *d;
     int                c;
 
     d = wcopy;
@@ -4073,2746 +2982,37 @@ allcap_copy(char_u *word, char_u *wcopy)
 }
 
 /*
- * Try finding suggestions by recognizing specific situations.
- */
-    static void
-suggest_try_special(suginfo_T *su)
-{
-    char_u     *p;
-    size_t     len;
-    int                c;
-    char_u     word[MAXWLEN];
-
-    /*
-     * Recognize a word that is repeated: "the the".
-     */
-    p = skiptowhite(su->su_fbadword);
-    len = p - su->su_fbadword;
-    p = skipwhite(p);
-    if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0)
-    {
-       /* Include badflags: if the badword is onecap or allcap
-        * use that for the goodword too: "The the" -> "The". */
-       c = su->su_fbadword[len];
-       su->su_fbadword[len] = NUL;
-       make_case_word(su->su_fbadword, word, su->su_badflags);
-       su->su_fbadword[len] = c;
-
-       /* Give a soundalike score of 0, compute the score as if deleting one
-        * character. */
-       add_suggestion(su, &su->su_ga, word, su->su_badlen,
-                      RESCORE(SCORE_REP, 0), 0, TRUE, su->su_sallang, FALSE);
-    }
-}
-
-/*
- * Change the 0 to 1 to measure how much time is spent in each state.
- * Output is dumped in "suggestprof".
- */
-#if 0
-# define SUGGEST_PROFILE
-proftime_T current;
-proftime_T total;
-proftime_T times[STATE_FINAL + 1];
-long counts[STATE_FINAL + 1];
-
-    static void
-prof_init(void)
-{
-    for (int i = 0; i <= STATE_FINAL; ++i)
-    {
-       profile_zero(&times[i]);
-       counts[i] = 0;
-    }
-    profile_start(&current);
-    profile_start(&total);
-}
-
-/* call before changing state */
-    static void
-prof_store(state_T state)
-{
-    profile_end(&current);
-    profile_add(&times[state], &current);
-    ++counts[state];
-    profile_start(&current);
-}
-# define PROF_STORE(state) prof_store(state);
-
-    static void
-prof_report(char *name)
-{
-    FILE *fd = fopen("suggestprof", "a");
-
-    profile_end(&total);
-    fprintf(fd, "-----------------------\n");
-    fprintf(fd, "%s: %s\n", name, profile_msg(&total));
-    for (int i = 0; i <= STATE_FINAL; ++i)
-       fprintf(fd, "%d: %s (%ld)\n", i, profile_msg(&times[i]), counts[i]);
-    fclose(fd);
-}
-#else
-# define PROF_STORE(state)
-#endif
-
-/*
- * Try finding suggestions by adding/removing/swapping letters.
+ * Case-folding may change the number of bytes: Count nr of chars in
+ * fword[flen] and return the byte length of that many chars in "word".
  */
-    static void
-suggest_try_change(suginfo_T *su)
+    int
+nofold_len(char_u *fword, int flen, char_u *word)
 {
-    char_u     fword[MAXWLEN];     /* copy of the bad word, case-folded */
-    int                n;
     char_u     *p;
-    int                lpi;
-    langp_T    *lp;
-
-    /* We make a copy of the case-folded bad word, so that we can modify it
-     * to find matches (esp. REP items).  Append some more text, changing
-     * chars after the bad word may help. */
-    STRCPY(fword, su->su_fbadword);
-    n = (int)STRLEN(fword);
-    p = su->su_badptr + su->su_badlen;
-    (void)spell_casefold(p, (int)STRLEN(p), fword + n, MAXWLEN - n);
-
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-
-       /* If reloading a spell file fails it's still in the list but
-        * everything has been cleared. */
-       if (lp->lp_slang->sl_fbyts == NULL)
-           continue;
+    int                i = 0;
 
-       /* Try it for this language.  Will add possible suggestions. */
-#ifdef SUGGEST_PROFILE
-       prof_init();
-#endif
-       suggest_trie_walk(su, lp, fword, FALSE);
-#ifdef SUGGEST_PROFILE
-       prof_report("try_change");
-#endif
-    }
+    for (p = fword; p < fword + flen; MB_PTR_ADV(p))
+       ++i;
+    for (p = word; i > 0; MB_PTR_ADV(p))
+       --i;
+    return (int)(p - word);
 }
 
-/* Check the maximum score, if we go over it we won't try this change. */
-#define TRY_DEEPER(su, stack, depth, add) \
-               (stack[depth].ts_score + (add) < su->su_maxscore)
-
 /*
- * Try finding suggestions by adding/removing/swapping letters.
- *
- * This uses a state machine.  At each node in the tree we try various
- * operations.  When trying if an operation works "depth" is increased and the
- * stack[] is used to store info.  This allows combinations, thus insert one
- * character, replace one and delete another.  The number of changes is
- * limited by su->su_maxscore.
- *
- * After implementing this I noticed an article by Kemal Oflazer that
- * describes something similar: "Error-tolerant Finite State Recognition with
- * Applications to Morphological Analysis and Spelling Correction" (1996).
- * The implementation in the article is simplified and requires a stack of
- * unknown depth.  The implementation here only needs a stack depth equal to
- * the length of the word.
- *
- * This is also used for the sound-folded word, "soundfold" is TRUE then.
- * The mechanism is the same, but we find a match with a sound-folded word
- * that comes from one or more original words.  Each of these words may be
- * added, this is done by add_sound_suggest().
- * Don't use:
- *     the prefix tree or the keep-case tree
- *     "su->su_badlen"
- *     anything to do with upper and lower case
- *     anything to do with word or non-word characters ("spell_iswordp()")
- *     banned words
- *     word flags (rare, region, compounding)
- *     word splitting for now
- *     "similar_chars()"
- *     use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep"
+ * Copy "fword" to "cword", fixing case according to "flags".
  */
-    static void
-suggest_trie_walk(
-    suginfo_T  *su,
-    langp_T    *lp,
-    char_u     *fword,
-    int                soundfold)
+    void
+make_case_word(char_u *fword, char_u *cword, int flags)
 {
-    char_u     tword[MAXWLEN];     /* good word collected so far */
-    trystate_T stack[MAXWLEN];
-    char_u     preword[MAXWLEN * 3]; /* word found with proper case;
-                                      * concatenation of prefix compound
-                                      * words and split word.  NUL terminated
-                                      * when going deeper but not when coming
-                                      * back. */
-    char_u     compflags[MAXWLEN];     /* compound flags, one for each word */
-    trystate_T *sp;
-    int                newscore;
-    int                score;
-    char_u     *byts, *fbyts, *pbyts;
-    idx_T      *idxs, *fidxs, *pidxs;
-    int                depth;
-    int                c, c2, c3;
-    int                n = 0;
-    int                flags;
-    garray_T   *gap;
-    idx_T      arridx;
-    int                len;
-    char_u     *p;
-    fromto_T   *ftp;
-    int                fl = 0, tl;
-    int                repextra = 0;       /* extra bytes in fword[] from REP item */
-    slang_T    *slang = lp->lp_slang;
-    int                fword_ends;
-    int                goodword_ends;
-#ifdef DEBUG_TRIEWALK
-    /* Stores the name of the change made at each level. */
-    char_u     changename[MAXWLEN][80];
-#endif
-    int                breakcheckcount = 1000;
-    int                compound_ok;
-
-    /*
-     * Go through the whole case-fold tree, try changes at each node.
-     * "tword[]" contains the word collected from nodes in the tree.
-     * "fword[]" the word we are trying to match with (initially the bad
-     * word).
-     */
-    depth = 0;
-    sp = &stack[0];
-    vim_memset(sp, 0, sizeof(trystate_T));
-    sp->ts_curi = 1;
-
-    if (soundfold)
-    {
-       /* Going through the soundfold tree. */
-       byts = fbyts = slang->sl_sbyts;
-       idxs = fidxs = slang->sl_sidxs;
-       pbyts = NULL;
-       pidxs = NULL;
-       sp->ts_prefixdepth = PFD_NOPREFIX;
-       sp->ts_state = STATE_START;
-    }
-    else
-    {
-       /*
-        * When there are postponed prefixes we need to use these first.  At
-        * the end of the prefix we continue in the case-fold tree.
-        */
-       fbyts = slang->sl_fbyts;
-       fidxs = slang->sl_fidxs;
-       pbyts = slang->sl_pbyts;
-       pidxs = slang->sl_pidxs;
-       if (pbyts != NULL)
-       {
-           byts = pbyts;
-           idxs = pidxs;
-           sp->ts_prefixdepth = PFD_PREFIXTREE;
-           sp->ts_state = STATE_NOPREFIX;      /* try without prefix first */
-       }
-       else
-       {
-           byts = fbyts;
-           idxs = fidxs;
-           sp->ts_prefixdepth = PFD_NOPREFIX;
-           sp->ts_state = STATE_START;
-       }
-    }
-
-    /*
-     * Loop to find all suggestions.  At each round we either:
-     * - For the current state try one operation, advance "ts_curi",
-     *   increase "depth".
-     * - When a state is done go to the next, set "ts_state".
-     * - When all states are tried decrease "depth".
-     */
-    while (depth >= 0 && !got_int)
-    {
-       sp = &stack[depth];
-       switch (sp->ts_state)
-       {
-       case STATE_START:
-       case STATE_NOPREFIX:
-           /*
-            * Start of node: Deal with NUL bytes, which means
-            * tword[] may end here.
-            */
-           arridx = sp->ts_arridx;         /* current node in the tree */
-           len = byts[arridx];             /* bytes in this node */
-           arridx += sp->ts_curi;          /* index of current byte */
-
-           if (sp->ts_prefixdepth == PFD_PREFIXTREE)
-           {
-               /* Skip over the NUL bytes, we use them later. */
-               for (n = 0; n < len && byts[arridx + n] == 0; ++n)
-                   ;
-               sp->ts_curi += n;
-
-               /* Always past NUL bytes now. */
-               n = (int)sp->ts_state;
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_ENDNUL;
-               sp->ts_save_badflags = su->su_badflags;
-
-               /* At end of a prefix or at start of prefixtree: check for
-                * following word. */
-               if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX)
-               {
-                   /* Set su->su_badflags to the caps type at this position.
-                    * Use the caps type until here for the prefix itself. */
-                   if (has_mbyte)
-                       n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
-                   else
-                       n = sp->ts_fidx;
-                   flags = badword_captype(su->su_badptr, su->su_badptr + n);
-                   su->su_badflags = badword_captype(su->su_badptr + n,
-                                              su->su_badptr + su->su_badlen);
-#ifdef DEBUG_TRIEWALK
-                   sprintf(changename[depth], "prefix");
-#endif
-                   go_deeper(stack, depth, 0);
-                   ++depth;
-                   sp = &stack[depth];
-                   sp->ts_prefixdepth = depth - 1;
-                   byts = fbyts;
-                   idxs = fidxs;
-                   sp->ts_arridx = 0;
-
-                   /* Move the prefix to preword[] with the right case
-                    * and make find_keepcap_word() works. */
-                   tword[sp->ts_twordlen] = NUL;
-                   make_case_word(tword + sp->ts_splitoff,
-                                         preword + sp->ts_prewordlen, flags);
-                   sp->ts_prewordlen = (char_u)STRLEN(preword);
-                   sp->ts_splitoff = sp->ts_twordlen;
-               }
-               break;
-           }
-
-           if (sp->ts_curi > len || byts[arridx] != 0)
-           {
-               /* Past bytes in node and/or past NUL bytes. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_ENDNUL;
-               sp->ts_save_badflags = su->su_badflags;
-               break;
-           }
-
-           /*
-            * End of word in tree.
-            */
-           ++sp->ts_curi;              /* eat one NUL byte */
-
-           flags = (int)idxs[arridx];
-
-           /* Skip words with the NOSUGGEST flag. */
-           if (flags & WF_NOSUGGEST)
-               break;
-
-           fword_ends = (fword[sp->ts_fidx] == NUL
-                          || (soundfold
-                              ? VIM_ISWHITE(fword[sp->ts_fidx])
-                              : !spell_iswordp(fword + sp->ts_fidx, curwin)));
-           tword[sp->ts_twordlen] = NUL;
-
-           if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
-                                       && (sp->ts_flags & TSF_PREFIXOK) == 0)
-           {
-               /* There was a prefix before the word.  Check that the prefix
-                * can be used with this word. */
-               /* Count the length of the NULs in the prefix.  If there are
-                * none this must be the first try without a prefix.  */
-               n = stack[sp->ts_prefixdepth].ts_arridx;
-               len = pbyts[n++];
-               for (c = 0; c < len && pbyts[n + c] == 0; ++c)
-                   ;
-               if (c > 0)
-               {
-                   c = valid_word_prefix(c, n, flags,
-                                      tword + sp->ts_splitoff, slang, FALSE);
-                   if (c == 0)
-                       break;
-
-                   /* Use the WF_RARE flag for a rare prefix. */
-                   if (c & WF_RAREPFX)
-                       flags |= WF_RARE;
-
-                   /* Tricky: when checking for both prefix and compounding
-                    * we run into the prefix flag first.
-                    * Remember that it's OK, so that we accept the prefix
-                    * when arriving at a compound flag. */
-                   sp->ts_flags |= TSF_PREFIXOK;
-               }
-           }
-
-           /* Check NEEDCOMPOUND: can't use word without compounding.  Do try
-            * appending another compound word below. */
-           if (sp->ts_complen == sp->ts_compsplit && fword_ends
-                                                    && (flags & WF_NEEDCOMP))
-               goodword_ends = FALSE;
-           else
-               goodword_ends = TRUE;
-
-           p = NULL;
-           compound_ok = TRUE;
-           if (sp->ts_complen > sp->ts_compsplit)
-           {
-               if (slang->sl_nobreak)
-               {
-                   /* There was a word before this word.  When there was no
-                    * change in this word (it was correct) add the first word
-                    * as a suggestion.  If this word was corrected too, we
-                    * need to check if a correct word follows. */
-                   if (sp->ts_fidx - sp->ts_splitfidx
-                                         == sp->ts_twordlen - sp->ts_splitoff
-                           && STRNCMP(fword + sp->ts_splitfidx,
-                                       tword + sp->ts_splitoff,
-                                        sp->ts_fidx - sp->ts_splitfidx) == 0)
-                   {
-                       preword[sp->ts_prewordlen] = NUL;
-                       newscore = score_wordcount_adj(slang, sp->ts_score,
-                                                preword + sp->ts_prewordlen,
-                                                sp->ts_prewordlen > 0);
-                       /* Add the suggestion if the score isn't too bad. */
-                       if (newscore <= su->su_maxscore)
-                           add_suggestion(su, &su->su_ga, preword,
-                                   sp->ts_splitfidx - repextra,
-                                   newscore, 0, FALSE,
-                                   lp->lp_sallang, FALSE);
-                       break;
-                   }
-               }
-               else
-               {
-                   /* There was a compound word before this word.  If this
-                    * word does not support compounding then give up
-                    * (splitting is tried for the word without compound
-                    * flag). */
-                   if (((unsigned)flags >> 24) == 0
-                           || sp->ts_twordlen - sp->ts_splitoff
-                                                      < slang->sl_compminlen)
-                       break;
-                   /* For multi-byte chars check character length against
-                    * COMPOUNDMIN. */
-                   if (has_mbyte
-                           && slang->sl_compminlen > 0
-                           && mb_charlen(tword + sp->ts_splitoff)
-                                                      < slang->sl_compminlen)
-                       break;
-
-                   compflags[sp->ts_complen] = ((unsigned)flags >> 24);
-                   compflags[sp->ts_complen + 1] = NUL;
-                   vim_strncpy(preword + sp->ts_prewordlen,
-                           tword + sp->ts_splitoff,
-                           sp->ts_twordlen - sp->ts_splitoff);
-
-                   /* Verify CHECKCOMPOUNDPATTERN  rules. */
-                   if (match_checkcompoundpattern(preword,  sp->ts_prewordlen,
-                                                         &slang->sl_comppat))
-                       compound_ok = FALSE;
-
-                   if (compound_ok)
-                   {
-                       p = preword;
-                       while (*skiptowhite(p) != NUL)
-                           p = skipwhite(skiptowhite(p));
-                       if (fword_ends && !can_compound(slang, p,
-                                               compflags + sp->ts_compsplit))
-                           /* Compound is not allowed.  But it may still be
-                            * possible if we add another (short) word. */
-                           compound_ok = FALSE;
-                   }
-
-                   /* Get pointer to last char of previous word. */
-                   p = preword + sp->ts_prewordlen;
-                   MB_PTR_BACK(preword, p);
-               }
-           }
-
-           /*
-            * Form the word with proper case in preword.
-            * If there is a word from a previous split, append.
-            * For the soundfold tree don't change the case, simply append.
-            */
-           if (soundfold)
-               STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
-           else if (flags & WF_KEEPCAP)
-               /* Must find the word in the keep-case tree. */
-               find_keepcap_word(slang, tword + sp->ts_splitoff,
-                                                preword + sp->ts_prewordlen);
-           else
-           {
-               /* Include badflags: If the badword is onecap or allcap
-                * use that for the goodword too.  But if the badword is
-                * allcap and it's only one char long use onecap. */
-               c = su->su_badflags;
-               if ((c & WF_ALLCAP)
-                       && su->su_badlen == (*mb_ptr2len)(su->su_badptr))
-                   c = WF_ONECAP;
-               c |= flags;
-
-               /* When appending a compound word after a word character don't
-                * use Onecap. */
-               if (p != NULL && spell_iswordp_nmw(p, curwin))
-                   c &= ~WF_ONECAP;
-               make_case_word(tword + sp->ts_splitoff,
-                                             preword + sp->ts_prewordlen, c);
-           }
-
-           if (!soundfold)
-           {
-               /* Don't use a banned word.  It may appear again as a good
-                * word, thus remember it. */
-               if (flags & WF_BANNED)
-               {
-                   add_banned(su, preword + sp->ts_prewordlen);
-                   break;
-               }
-               if ((sp->ts_complen == sp->ts_compsplit
-                           && WAS_BANNED(su, preword + sp->ts_prewordlen))
-                                                  || WAS_BANNED(su, preword))
-               {
-                   if (slang->sl_compprog == NULL)
-                       break;
-                   /* the word so far was banned but we may try compounding */
-                   goodword_ends = FALSE;
-               }
-           }
-
-           newscore = 0;
-           if (!soundfold)     /* soundfold words don't have flags */
-           {
-               if ((flags & WF_REGION)
-                           && (((unsigned)flags >> 16) & lp->lp_region) == 0)
-                   newscore += SCORE_REGION;
-               if (flags & WF_RARE)
-                   newscore += SCORE_RARE;
-
-               if (!spell_valid_case(su->su_badflags,
-                                 captype(preword + sp->ts_prewordlen, NULL)))
-                   newscore += SCORE_ICASE;
-           }
-
-           /* TODO: how about splitting in the soundfold tree? */
-           if (fword_ends
-                   && goodword_ends
-                   && sp->ts_fidx >= sp->ts_fidxtry
-                   && compound_ok)
-           {
-               /* The badword also ends: add suggestions. */
-#ifdef DEBUG_TRIEWALK
-               if (soundfold && STRCMP(preword, "smwrd") == 0)
-               {
-                   int     j;
-
-                   /* print the stack of changes that brought us here */
-                   smsg("------ %s -------", fword);
-                   for (j = 0; j < depth; ++j)
-                       smsg("%s", changename[j]);
-               }
-#endif
-               if (soundfold)
-               {
-                   /* For soundfolded words we need to find the original
-                    * words, the edit distance and then add them. */
-                   add_sound_suggest(su, preword, sp->ts_score, lp);
-               }
-               else if (sp->ts_fidx > 0)
-               {
-                   /* Give a penalty when changing non-word char to word
-                    * char, e.g., "thes," -> "these". */
-                   p = fword + sp->ts_fidx;
-                   MB_PTR_BACK(fword, p);
-                   if (!spell_iswordp(p, curwin))
-                   {
-                       p = preword + STRLEN(preword);
-                       MB_PTR_BACK(preword, p);
-                       if (spell_iswordp(p, curwin))
-                           newscore += SCORE_NONWORD;
-                   }
-
-                   /* Give a bonus to words seen before. */
-                   score = score_wordcount_adj(slang,
-                                               sp->ts_score + newscore,
-                                               preword + sp->ts_prewordlen,
-                                               sp->ts_prewordlen > 0);
-
-                   /* Add the suggestion if the score isn't too bad. */
-                   if (score <= su->su_maxscore)
-                   {
-                       add_suggestion(su, &su->su_ga, preword,
-                                   sp->ts_fidx - repextra,
-                                   score, 0, FALSE, lp->lp_sallang, FALSE);
-
-                       if (su->su_badflags & WF_MIXCAP)
-                       {
-                           /* We really don't know if the word should be
-                            * upper or lower case, add both. */
-                           c = captype(preword, NULL);
-                           if (c == 0 || c == WF_ALLCAP)
-                           {
-                               make_case_word(tword + sp->ts_splitoff,
-                                             preword + sp->ts_prewordlen,
-                                                     c == 0 ? WF_ALLCAP : 0);
-
-                               add_suggestion(su, &su->su_ga, preword,
-                                       sp->ts_fidx - repextra,
-                                       score + SCORE_ICASE, 0, FALSE,
-                                       lp->lp_sallang, FALSE);
-                           }
-                       }
-                   }
-               }
-           }
-
-           /*
-            * Try word split and/or compounding.
-            */
-           if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends)
-                   /* Don't split halfway a character. */
-                   && (!has_mbyte || sp->ts_tcharlen == 0))
-           {
-               int     try_compound;
-               int     try_split;
-
-               /* If past the end of the bad word don't try a split.
-                * Otherwise try changing the next word.  E.g., find
-                * suggestions for "the the" where the second "the" is
-                * different.  It's done like a split.
-                * TODO: word split for soundfold words */
-               try_split = (sp->ts_fidx - repextra < su->su_badlen)
-                                                               && !soundfold;
-
-               /* Get here in several situations:
-                * 1. The word in the tree ends:
-                *    If the word allows compounding try that.  Otherwise try
-                *    a split by inserting a space.  For both check that a
-                *    valid words starts at fword[sp->ts_fidx].
-                *    For NOBREAK do like compounding to be able to check if
-                *    the next word is valid.
-                * 2. The badword does end, but it was due to a change (e.g.,
-                *    a swap).  No need to split, but do check that the
-                *    following word is valid.
-                * 3. The badword and the word in the tree end.  It may still
-                *    be possible to compound another (short) word.
-                */
-               try_compound = FALSE;
-               if (!soundfold
-                       && !slang->sl_nocompoundsugs
-                       && slang->sl_compprog != NULL
-                       && ((unsigned)flags >> 24) != 0
-                       && sp->ts_twordlen - sp->ts_splitoff
-                                                      >= slang->sl_compminlen
-                       && (!has_mbyte
-                           || slang->sl_compminlen == 0
-                           || mb_charlen(tword + sp->ts_splitoff)
-                                                     >= slang->sl_compminlen)
-                       && (slang->sl_compsylmax < MAXWLEN
-                           || sp->ts_complen + 1 - sp->ts_compsplit
-                                                         < slang->sl_compmax)
-                       && (can_be_compound(sp, slang,
-                                        compflags, ((unsigned)flags >> 24))))
-
-               {
-                   try_compound = TRUE;
-                   compflags[sp->ts_complen] = ((unsigned)flags >> 24);
-                   compflags[sp->ts_complen + 1] = NUL;
-               }
-
-               /* For NOBREAK we never try splitting, it won't make any word
-                * valid. */
-               if (slang->sl_nobreak && !slang->sl_nocompoundsugs)
-                   try_compound = TRUE;
-
-               /* If we could add a compound word, and it's also possible to
-                * split at this point, do the split first and set
-                * TSF_DIDSPLIT to avoid doing it again. */
-               else if (!fword_ends
-                       && try_compound
-                       && (sp->ts_flags & TSF_DIDSPLIT) == 0)
-               {
-                   try_compound = FALSE;
-                   sp->ts_flags |= TSF_DIDSPLIT;
-                   --sp->ts_curi;          /* do the same NUL again */
-                   compflags[sp->ts_complen] = NUL;
-               }
-               else
-                   sp->ts_flags &= ~TSF_DIDSPLIT;
-
-               if (try_split || try_compound)
-               {
-                   if (!try_compound && (!fword_ends || !goodword_ends))
-                   {
-                       /* If we're going to split need to check that the
-                        * words so far are valid for compounding.  If there
-                        * is only one word it must not have the NEEDCOMPOUND
-                        * flag. */
-                       if (sp->ts_complen == sp->ts_compsplit
-                                                    && (flags & WF_NEEDCOMP))
-                           break;
-                       p = preword;
-                       while (*skiptowhite(p) != NUL)
-                           p = skipwhite(skiptowhite(p));
-                       if (sp->ts_complen > sp->ts_compsplit
-                               && !can_compound(slang, p,
-                                               compflags + sp->ts_compsplit))
-                           break;
-
-                       if (slang->sl_nosplitsugs)
-                           newscore += SCORE_SPLIT_NO;
-                       else
-                           newscore += SCORE_SPLIT;
-
-                       /* Give a bonus to words seen before. */
-                       newscore = score_wordcount_adj(slang, newscore,
-                                          preword + sp->ts_prewordlen, TRUE);
-                   }
-
-                   if (TRY_DEEPER(su, stack, depth, newscore))
-                   {
-                       go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
-                       if (!try_compound && !fword_ends)
-                           sprintf(changename[depth], "%.*s-%s: split",
-                                sp->ts_twordlen, tword, fword + sp->ts_fidx);
-                       else
-                           sprintf(changename[depth], "%.*s-%s: compound",
-                                sp->ts_twordlen, tword, fword + sp->ts_fidx);
-#endif
-                       /* Save things to be restored at STATE_SPLITUNDO. */
-                       sp->ts_save_badflags = su->su_badflags;
-                       PROF_STORE(sp->ts_state)
-                       sp->ts_state = STATE_SPLITUNDO;
-
-                       ++depth;
-                       sp = &stack[depth];
-
-                       /* Append a space to preword when splitting. */
-                       if (!try_compound && !fword_ends)
-                           STRCAT(preword, " ");
-                       sp->ts_prewordlen = (char_u)STRLEN(preword);
-                       sp->ts_splitoff = sp->ts_twordlen;
-                       sp->ts_splitfidx = sp->ts_fidx;
-
-                       /* If the badword has a non-word character at this
-                        * position skip it.  That means replacing the
-                        * non-word character with a space.  Always skip a
-                        * character when the word ends.  But only when the
-                        * good word can end. */
-                       if (((!try_compound && !spell_iswordp_nmw(fword
-                                                              + sp->ts_fidx,
-                                                              curwin))
-                                   || fword_ends)
-                               && fword[sp->ts_fidx] != NUL
-                               && goodword_ends)
-                       {
-                           int     l;
-
-                           l = MB_PTR2LEN(fword + sp->ts_fidx);
-                           if (fword_ends)
-                           {
-                               /* Copy the skipped character to preword. */
-                               mch_memmove(preword + sp->ts_prewordlen,
-                                                     fword + sp->ts_fidx, l);
-                               sp->ts_prewordlen += l;
-                               preword[sp->ts_prewordlen] = NUL;
-                           }
-                           else
-                               sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
-                           sp->ts_fidx += l;
-                       }
-
-                       /* When compounding include compound flag in
-                        * compflags[] (already set above).  When splitting we
-                        * may start compounding over again.  */
-                       if (try_compound)
-                           ++sp->ts_complen;
-                       else
-                           sp->ts_compsplit = sp->ts_complen;
-                       sp->ts_prefixdepth = PFD_NOPREFIX;
-
-                       /* set su->su_badflags to the caps type at this
-                        * position */
-                       if (has_mbyte)
-                           n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
-                       else
-                           n = sp->ts_fidx;
-                       su->su_badflags = badword_captype(su->su_badptr + n,
-                                              su->su_badptr + su->su_badlen);
-
-                       /* Restart at top of the tree. */
-                       sp->ts_arridx = 0;
-
-                       /* If there are postponed prefixes, try these too. */
-                       if (pbyts != NULL)
-                       {
-                           byts = pbyts;
-                           idxs = pidxs;
-                           sp->ts_prefixdepth = PFD_PREFIXTREE;
-                           PROF_STORE(sp->ts_state)
-                           sp->ts_state = STATE_NOPREFIX;
-                       }
-                   }
-               }
-           }
-           break;
-
-       case STATE_SPLITUNDO:
-           /* Undo the changes done for word split or compound word. */
-           su->su_badflags = sp->ts_save_badflags;
-
-           /* Continue looking for NUL bytes. */
-           PROF_STORE(sp->ts_state)
-           sp->ts_state = STATE_START;
-
-           /* In case we went into the prefix tree. */
-           byts = fbyts;
-           idxs = fidxs;
-           break;
-
-       case STATE_ENDNUL:
-           /* Past the NUL bytes in the node. */
-           su->su_badflags = sp->ts_save_badflags;
-           if (fword[sp->ts_fidx] == NUL && sp->ts_tcharlen == 0)
-           {
-               /* The badword ends, can't use STATE_PLAIN. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_DEL;
-               break;
-           }
-           PROF_STORE(sp->ts_state)
-           sp->ts_state = STATE_PLAIN;
-           /* FALLTHROUGH */
-
-       case STATE_PLAIN:
-           /*
-            * Go over all possible bytes at this node, add each to tword[]
-            * and use child node.  "ts_curi" is the index.
-            */
-           arridx = sp->ts_arridx;
-           if (sp->ts_curi > byts[arridx])
-           {
-               /* Done all bytes at this node, do next state.  When still at
-                * already changed bytes skip the other tricks. */
-               PROF_STORE(sp->ts_state)
-               if (sp->ts_fidx >= sp->ts_fidxtry)
-                   sp->ts_state = STATE_DEL;
-               else
-                   sp->ts_state = STATE_FINAL;
-           }
-           else
-           {
-               arridx += sp->ts_curi++;
-               c = byts[arridx];
-
-               /* Normal byte, go one level deeper.  If it's not equal to the
-                * byte in the bad word adjust the score.  But don't even try
-                * when the byte was already changed.  And don't try when we
-                * just deleted this byte, accepting it is always cheaper than
-                * delete + substitute. */
-               if (c == fword[sp->ts_fidx]
-                       || (sp->ts_tcharlen > 0 && sp->ts_isdiff != DIFF_NONE))
-                   newscore = 0;
-               else
-                   newscore = SCORE_SUBST;
-               if ((newscore == 0
-                           || (sp->ts_fidx >= sp->ts_fidxtry
-                               && ((sp->ts_flags & TSF_DIDDEL) == 0
-                                   || c != fword[sp->ts_delidx])))
-                       && TRY_DEEPER(su, stack, depth, newscore))
-               {
-                   go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
-                   if (newscore > 0)
-                       sprintf(changename[depth], "%.*s-%s: subst %c to %c",
-                               sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                               fword[sp->ts_fidx], c);
-                   else
-                       sprintf(changename[depth], "%.*s-%s: accept %c",
-                               sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                               fword[sp->ts_fidx]);
-#endif
-                   ++depth;
-                   sp = &stack[depth];
-                   ++sp->ts_fidx;
-                   tword[sp->ts_twordlen++] = c;
-                   sp->ts_arridx = idxs[arridx];
-                   if (newscore == SCORE_SUBST)
-                       sp->ts_isdiff = DIFF_YES;
-                   if (has_mbyte)
-                   {
-                       /* Multi-byte characters are a bit complicated to
-                        * handle: They differ when any of the bytes differ
-                        * and then their length may also differ. */
-                       if (sp->ts_tcharlen == 0)
-                       {
-                           /* First byte. */
-                           sp->ts_tcharidx = 0;
-                           sp->ts_tcharlen = MB_BYTE2LEN(c);
-                           sp->ts_fcharstart = sp->ts_fidx - 1;
-                           sp->ts_isdiff = (newscore != 0)
-                                                      ? DIFF_YES : DIFF_NONE;
-                       }
-                       else if (sp->ts_isdiff == DIFF_INSERT)
-                           /* When inserting trail bytes don't advance in the
-                            * bad word. */
-                           --sp->ts_fidx;
-                       if (++sp->ts_tcharidx == sp->ts_tcharlen)
-                       {
-                           /* Last byte of character. */
-                           if (sp->ts_isdiff == DIFF_YES)
-                           {
-                               /* Correct ts_fidx for the byte length of the
-                                * character (we didn't check that before). */
-                               sp->ts_fidx = sp->ts_fcharstart
-                                           + MB_PTR2LEN(
-                                                   fword + sp->ts_fcharstart);
-                               /* For changing a composing character adjust
-                                * the score from SCORE_SUBST to
-                                * SCORE_SUBCOMP. */
-                               if (enc_utf8
-                                       && utf_iscomposing(
-                                           utf_ptr2char(tword
-                                               + sp->ts_twordlen
-                                                          - sp->ts_tcharlen))
-                                       && utf_iscomposing(
-                                           utf_ptr2char(fword
-                                                       + sp->ts_fcharstart)))
-                                   sp->ts_score -=
-                                                 SCORE_SUBST - SCORE_SUBCOMP;
-
-                               /* For a similar character adjust score from
-                                * SCORE_SUBST to SCORE_SIMILAR. */
-                               else if (!soundfold
-                                       && slang->sl_has_map
-                                       && similar_chars(slang,
-                                           mb_ptr2char(tword
-                                               + sp->ts_twordlen
-                                                          - sp->ts_tcharlen),
-                                           mb_ptr2char(fword
-                                                       + sp->ts_fcharstart)))
-                                   sp->ts_score -=
-                                                 SCORE_SUBST - SCORE_SIMILAR;
-                           }
-                           else if (sp->ts_isdiff == DIFF_INSERT
-                                        && sp->ts_twordlen > sp->ts_tcharlen)
-                           {
-                               p = tword + sp->ts_twordlen - sp->ts_tcharlen;
-                               c = mb_ptr2char(p);
-                               if (enc_utf8 && utf_iscomposing(c))
-                               {
-                                   /* Inserting a composing char doesn't
-                                    * count that much. */
-                                   sp->ts_score -= SCORE_INS - SCORE_INSCOMP;
-                               }
-                               else
-                               {
-                                   /* If the previous character was the same,
-                                    * thus doubling a character, give a bonus
-                                    * to the score.  Also for the soundfold
-                                    * tree (might seem illogical but does
-                                    * give better scores). */
-                                   MB_PTR_BACK(tword, p);
-                                   if (c == mb_ptr2char(p))
-                                       sp->ts_score -= SCORE_INS
-                                                              - SCORE_INSDUP;
-                               }
-                           }
-
-                           /* Starting a new char, reset the length. */
-                           sp->ts_tcharlen = 0;
-                       }
-                   }
-                   else
-                   {
-                       /* If we found a similar char adjust the score.
-                        * We do this after calling go_deeper() because
-                        * it's slow. */
-                       if (newscore != 0
-                               && !soundfold
-                               && slang->sl_has_map
-                               && similar_chars(slang,
-                                                  c, fword[sp->ts_fidx - 1]))
-                           sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR;
-                   }
-               }
-           }
-           break;
-
-       case STATE_DEL:
-           /* When past the first byte of a multi-byte char don't try
-            * delete/insert/swap a character. */
-           if (has_mbyte && sp->ts_tcharlen > 0)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_FINAL;
-               break;
-           }
-           /*
-            * Try skipping one character in the bad word (delete it).
-            */
-           PROF_STORE(sp->ts_state)
-           sp->ts_state = STATE_INS_PREP;
-           sp->ts_curi = 1;
-           if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*')
-               /* Deleting a vowel at the start of a word counts less, see
-                * soundalike_score(). */
-               newscore = 2 * SCORE_DEL / 3;
-           else
-               newscore = SCORE_DEL;
-           if (fword[sp->ts_fidx] != NUL
-                                   && TRY_DEEPER(su, stack, depth, newscore))
-           {
-               go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
-               sprintf(changename[depth], "%.*s-%s: delete %c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       fword[sp->ts_fidx]);
-#endif
-               ++depth;
-
-               /* Remember what character we deleted, so that we can avoid
-                * inserting it again. */
-               stack[depth].ts_flags |= TSF_DIDDEL;
-               stack[depth].ts_delidx = sp->ts_fidx;
-
-               /* Advance over the character in fword[].  Give a bonus to the
-                * score if the same character is following "nn" -> "n".  It's
-                * a bit illogical for soundfold tree but it does give better
-                * results. */
-               if (has_mbyte)
-               {
-                   c = mb_ptr2char(fword + sp->ts_fidx);
-                   stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx);
-                   if (enc_utf8 && utf_iscomposing(c))
-                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
-                   else if (c == mb_ptr2char(fword + stack[depth].ts_fidx))
-                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
-               }
-               else
-               {
-                   ++stack[depth].ts_fidx;
-                   if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1])
-                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
-               }
-               break;
-           }
-           /* FALLTHROUGH */
-
-       case STATE_INS_PREP:
-           if (sp->ts_flags & TSF_DIDDEL)
-           {
-               /* If we just deleted a byte then inserting won't make sense,
-                * a substitute is always cheaper. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_SWAP;
-               break;
-           }
-
-           /* skip over NUL bytes */
-           n = sp->ts_arridx;
-           for (;;)
-           {
-               if (sp->ts_curi > byts[n])
-               {
-                   /* Only NUL bytes at this node, go to next state. */
-                   PROF_STORE(sp->ts_state)
-                   sp->ts_state = STATE_SWAP;
-                   break;
-               }
-               if (byts[n + sp->ts_curi] != NUL)
-               {
-                   /* Found a byte to insert. */
-                   PROF_STORE(sp->ts_state)
-                   sp->ts_state = STATE_INS;
-                   break;
-               }
-               ++sp->ts_curi;
-           }
-           break;
-
-           /* FALLTHROUGH */
-
-       case STATE_INS:
-           /* Insert one byte.  Repeat this for each possible byte at this
-            * node. */
-           n = sp->ts_arridx;
-           if (sp->ts_curi > byts[n])
-           {
-               /* Done all bytes at this node, go to next state. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_SWAP;
-               break;
-           }
-
-           /* Do one more byte at this node, but:
-            * - Skip NUL bytes.
-            * - Skip the byte if it's equal to the byte in the word,
-            *   accepting that byte is always better.
-            */
-           n += sp->ts_curi++;
-           c = byts[n];
-           if (soundfold && sp->ts_twordlen == 0 && c == '*')
-               /* Inserting a vowel at the start of a word counts less,
-                * see soundalike_score(). */
-               newscore = 2 * SCORE_INS / 3;
-           else
-               newscore = SCORE_INS;
-           if (c != fword[sp->ts_fidx]
-                                   && TRY_DEEPER(su, stack, depth, newscore))
-           {
-               go_deeper(stack, depth, newscore);
-#ifdef DEBUG_TRIEWALK
-               sprintf(changename[depth], "%.*s-%s: insert %c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       c);
-#endif
-               ++depth;
-               sp = &stack[depth];
-               tword[sp->ts_twordlen++] = c;
-               sp->ts_arridx = idxs[n];
-               if (has_mbyte)
-               {
-                   fl = MB_BYTE2LEN(c);
-                   if (fl > 1)
-                   {
-                       /* There are following bytes for the same character.
-                        * We must find all bytes before trying
-                        * delete/insert/swap/etc. */
-                       sp->ts_tcharlen = fl;
-                       sp->ts_tcharidx = 1;
-                       sp->ts_isdiff = DIFF_INSERT;
-                   }
-               }
-               else
-                   fl = 1;
-               if (fl == 1)
-               {
-                   /* If the previous character was the same, thus doubling a
-                    * character, give a bonus to the score.  Also for
-                    * soundfold words (illogical but does give a better
-                    * score). */
-                   if (sp->ts_twordlen >= 2
-                                          && tword[sp->ts_twordlen - 2] == c)
-                       sp->ts_score -= SCORE_INS - SCORE_INSDUP;
-               }
-           }
-           break;
-
-       case STATE_SWAP:
-           /*
-            * Swap two bytes in the bad word: "12" -> "21".
-            * We change "fword" here, it's changed back afterwards at
-            * STATE_UNSWAP.
-            */
-           p = fword + sp->ts_fidx;
-           c = *p;
-           if (c == NUL)
-           {
-               /* End of word, can't swap or replace. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_FINAL;
-               break;
-           }
-
-           /* Don't swap if the first character is not a word character.
-            * SWAP3 etc. also don't make sense then. */
-           if (!soundfold && !spell_iswordp(p, curwin))
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-               break;
-           }
-
-           if (has_mbyte)
-           {
-               n = MB_CPTR2LEN(p);
-               c = mb_ptr2char(p);
-               if (p[n] == NUL)
-                   c2 = NUL;
-               else if (!soundfold && !spell_iswordp(p + n, curwin))
-                   c2 = c; /* don't swap non-word char */
-               else
-                   c2 = mb_ptr2char(p + n);
-           }
-           else
-           {
-               if (p[1] == NUL)
-                   c2 = NUL;
-               else if (!soundfold && !spell_iswordp(p + 1, curwin))
-                   c2 = c; /* don't swap non-word char */
-               else
-                   c2 = p[1];
-           }
-
-           /* When the second character is NUL we can't swap. */
-           if (c2 == NUL)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-               break;
-           }
-
-           /* When characters are identical, swap won't do anything.
-            * Also get here if the second char is not a word character. */
-           if (c == c2)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_SWAP3;
-               break;
-           }
-           if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP))
-           {
-               go_deeper(stack, depth, SCORE_SWAP);
-#ifdef DEBUG_TRIEWALK
-               sprintf(changename[depth], "%.*s-%s: swap %c and %c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       c, c2);
-#endif
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_UNSWAP;
-               ++depth;
-               if (has_mbyte)
-               {
-                   fl = mb_char2len(c2);
-                   mch_memmove(p, p + n, fl);
-                   mb_char2bytes(c, p + fl);
-                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl;
-               }
-               else
-               {
-                   p[0] = c2;
-                   p[1] = c;
-                   stack[depth].ts_fidxtry = sp->ts_fidx + 2;
-               }
-           }
-           else
-           {
-               /* If this swap doesn't work then SWAP3 won't either. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-           }
-           break;
-
-       case STATE_UNSWAP:
-           /* Undo the STATE_SWAP swap: "21" -> "12". */
-           p = fword + sp->ts_fidx;
-           if (has_mbyte)
-           {
-               n = MB_PTR2LEN(p);
-               c = mb_ptr2char(p + n);
-               mch_memmove(p + MB_PTR2LEN(p + n), p, n);
-               mb_char2bytes(c, p);
-           }
-           else
-           {
-               c = *p;
-               *p = p[1];
-               p[1] = c;
-           }
-           /* FALLTHROUGH */
-
-       case STATE_SWAP3:
-           /* Swap two bytes, skipping one: "123" -> "321".  We change
-            * "fword" here, it's changed back afterwards at STATE_UNSWAP3. */
-           p = fword + sp->ts_fidx;
-           if (has_mbyte)
-           {
-               n = MB_CPTR2LEN(p);
-               c = mb_ptr2char(p);
-               fl = MB_CPTR2LEN(p + n);
-               c2 = mb_ptr2char(p + n);
-               if (!soundfold && !spell_iswordp(p + n + fl, curwin))
-                   c3 = c;     /* don't swap non-word char */
-               else
-                   c3 = mb_ptr2char(p + n + fl);
-           }
-           else
-           {
-               c = *p;
-               c2 = p[1];
-               if (!soundfold && !spell_iswordp(p + 2, curwin))
-                   c3 = c;     /* don't swap non-word char */
-               else
-                   c3 = p[2];
-           }
-
-           /* When characters are identical: "121" then SWAP3 result is
-            * identical, ROT3L result is same as SWAP: "211", ROT3L result is
-            * same as SWAP on next char: "112".  Thus skip all swapping.
-            * Also skip when c3 is NUL.
-            * Also get here when the third character is not a word character.
-            * Second character may any char: "a.b" -> "b.a" */
-           if (c == c3 || c3 == NUL)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-               break;
-           }
-           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
-           {
-               go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
-               sprintf(changename[depth], "%.*s-%s: swap3 %c and %c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       c, c3);
-#endif
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_UNSWAP3;
-               ++depth;
-               if (has_mbyte)
-               {
-                   tl = mb_char2len(c3);
-                   mch_memmove(p, p + n + fl, tl);
-                   mb_char2bytes(c2, p + tl);
-                   mb_char2bytes(c, p + fl + tl);
-                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl + tl;
-               }
-               else
-               {
-                   p[0] = p[2];
-                   p[2] = c;
-                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
-               }
-           }
-           else
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-           }
-           break;
-
-       case STATE_UNSWAP3:
-           /* Undo STATE_SWAP3: "321" -> "123" */
-           p = fword + sp->ts_fidx;
-           if (has_mbyte)
-           {
-               n = MB_PTR2LEN(p);
-               c2 = mb_ptr2char(p + n);
-               fl = MB_PTR2LEN(p + n);
-               c = mb_ptr2char(p + n + fl);
-               tl = MB_PTR2LEN(p + n + fl);
-               mch_memmove(p + fl + tl, p, n);
-               mb_char2bytes(c, p);
-               mb_char2bytes(c2, p + tl);
-               p = p + tl;
-           }
-           else
-           {
-               c = *p;
-               *p = p[2];
-               p[2] = c;
-               ++p;
-           }
-
-           if (!soundfold && !spell_iswordp(p, curwin))
-           {
-               /* Middle char is not a word char, skip the rotate.  First and
-                * third char were already checked at swap and swap3. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-               break;
-           }
-
-           /* Rotate three characters left: "123" -> "231".  We change
-            * "fword" here, it's changed back afterwards at STATE_UNROT3L. */
-           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
-           {
-               go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
-               p = fword + sp->ts_fidx;
-               sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       p[0], p[1], p[2]);
-#endif
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_UNROT3L;
-               ++depth;
-               p = fword + sp->ts_fidx;
-               if (has_mbyte)
-               {
-                   n = MB_CPTR2LEN(p);
-                   c = mb_ptr2char(p);
-                   fl = MB_CPTR2LEN(p + n);
-                   fl += MB_CPTR2LEN(p + n + fl);
-                   mch_memmove(p, p + n, fl);
-                   mb_char2bytes(c, p + fl);
-                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl;
-               }
-               else
-               {
-                   c = *p;
-                   *p = p[1];
-                   p[1] = p[2];
-                   p[2] = c;
-                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
-               }
-           }
-           else
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-           }
-           break;
-
-       case STATE_UNROT3L:
-           /* Undo ROT3L: "231" -> "123" */
-           p = fword + sp->ts_fidx;
-           if (has_mbyte)
-           {
-               n = MB_PTR2LEN(p);
-               n += MB_PTR2LEN(p + n);
-               c = mb_ptr2char(p + n);
-               tl = MB_PTR2LEN(p + n);
-               mch_memmove(p + tl, p, n);
-               mb_char2bytes(c, p);
-           }
-           else
-           {
-               c = p[2];
-               p[2] = p[1];
-               p[1] = *p;
-               *p = c;
-           }
-
-           /* Rotate three bytes right: "123" -> "312".  We change "fword"
-            * here, it's changed back afterwards at STATE_UNROT3R. */
-           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
-           {
-               go_deeper(stack, depth, SCORE_SWAP3);
-#ifdef DEBUG_TRIEWALK
-               p = fword + sp->ts_fidx;
-               sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c",
-                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                       p[0], p[1], p[2]);
-#endif
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_UNROT3R;
-               ++depth;
-               p = fword + sp->ts_fidx;
-               if (has_mbyte)
-               {
-                   n = MB_CPTR2LEN(p);
-                   n += MB_CPTR2LEN(p + n);
-                   c = mb_ptr2char(p + n);
-                   tl = MB_CPTR2LEN(p + n);
-                   mch_memmove(p + tl, p, n);
-                   mb_char2bytes(c, p);
-                   stack[depth].ts_fidxtry = sp->ts_fidx + n + tl;
-               }
-               else
-               {
-                   c = p[2];
-                   p[2] = p[1];
-                   p[1] = *p;
-                   *p = c;
-                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
-               }
-           }
-           else
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_REP_INI;
-           }
-           break;
-
-       case STATE_UNROT3R:
-           /* Undo ROT3R: "312" -> "123" */
-           p = fword + sp->ts_fidx;
-           if (has_mbyte)
-           {
-               c = mb_ptr2char(p);
-               tl = MB_PTR2LEN(p);
-               n = MB_PTR2LEN(p + tl);
-               n += MB_PTR2LEN(p + tl + n);
-               mch_memmove(p, p + tl, n);
-               mb_char2bytes(c, p + n);
-           }
-           else
-           {
-               c = *p;
-               *p = p[1];
-               p[1] = p[2];
-               p[2] = c;
-           }
-           /* FALLTHROUGH */
-
-       case STATE_REP_INI:
-           /* Check if matching with REP items from the .aff file would work.
-            * Quickly skip if:
-            * - there are no REP items and we are not in the soundfold trie
-            * - the score is going to be too high anyway
-            * - already applied a REP item or swapped here  */
-           if ((lp->lp_replang == NULL && !soundfold)
-                   || sp->ts_score + SCORE_REP >= su->su_maxscore
-                   || sp->ts_fidx < sp->ts_fidxtry)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_FINAL;
-               break;
-           }
-
-           /* Use the first byte to quickly find the first entry that may
-            * match.  If the index is -1 there is none. */
-           if (soundfold)
-               sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]];
-           else
-               sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]];
-
-           if (sp->ts_curi < 0)
-           {
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_FINAL;
-               break;
-           }
-
-           PROF_STORE(sp->ts_state)
-           sp->ts_state = STATE_REP;
-           /* FALLTHROUGH */
-
-       case STATE_REP:
-           /* Try matching with REP items from the .aff file.  For each match
-            * replace the characters and check if the resulting word is
-            * valid. */
-           p = fword + sp->ts_fidx;
-
-           if (soundfold)
-               gap = &slang->sl_repsal;
-           else
-               gap = &lp->lp_replang->sl_rep;
-           while (sp->ts_curi < gap->ga_len)
-           {
-               ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
-               if (*ftp->ft_from != *p)
-               {
-                   /* past possible matching entries */
-                   sp->ts_curi = gap->ga_len;
-                   break;
-               }
-               if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0
-                       && TRY_DEEPER(su, stack, depth, SCORE_REP))
-               {
-                   go_deeper(stack, depth, SCORE_REP);
-#ifdef DEBUG_TRIEWALK
-                   sprintf(changename[depth], "%.*s-%s: replace %s with %s",
-                           sp->ts_twordlen, tword, fword + sp->ts_fidx,
-                           ftp->ft_from, ftp->ft_to);
-#endif
-                   /* Need to undo this afterwards. */
-                   PROF_STORE(sp->ts_state)
-                   sp->ts_state = STATE_REP_UNDO;
-
-                   /* Change the "from" to the "to" string. */
-                   ++depth;
-                   fl = (int)STRLEN(ftp->ft_from);
-                   tl = (int)STRLEN(ftp->ft_to);
-                   if (fl != tl)
-                   {
-                       STRMOVE(p + tl, p + fl);
-                       repextra += tl - fl;
-                   }
-                   mch_memmove(p, ftp->ft_to, tl);
-                   stack[depth].ts_fidxtry = sp->ts_fidx + tl;
-                   stack[depth].ts_tcharlen = 0;
-                   break;
-               }
-           }
-
-           if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP)
-           {
-               /* No (more) matches. */
-               PROF_STORE(sp->ts_state)
-               sp->ts_state = STATE_FINAL;
-           }
-
-           break;
-
-       case STATE_REP_UNDO:
-           /* Undo a REP replacement and continue with the next one. */
-           if (soundfold)
-               gap = &slang->sl_repsal;
-           else
-               gap = &lp->lp_replang->sl_rep;
-           ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
-           fl = (int)STRLEN(ftp->ft_from);
-           tl = (int)STRLEN(ftp->ft_to);
-           p = fword + sp->ts_fidx;
-           if (fl != tl)
-           {
-               STRMOVE(p + fl, p + tl);
-               repextra -= tl - fl;
-           }
-           mch_memmove(p, ftp->ft_from, fl);
-           PROF_STORE(sp->ts_state)
-           sp->ts_state = STATE_REP;
-           break;
-
-       default:
-           /* Did all possible states at this level, go up one level. */
-           --depth;
-
-           if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE)
-           {
-               /* Continue in or go back to the prefix tree. */
-               byts = pbyts;
-               idxs = pidxs;
-           }
-
-           /* Don't check for CTRL-C too often, it takes time. */
-           if (--breakcheckcount == 0)
-           {
-               ui_breakcheck();
-               breakcheckcount = 1000;
-           }
-       }
-    }
-}
-
-
-/*
- * Go one level deeper in the tree.
- */
-    static void
-go_deeper(trystate_T *stack, int depth, int score_add)
-{
-    stack[depth + 1] = stack[depth];
-    stack[depth + 1].ts_state = STATE_START;
-    stack[depth + 1].ts_score = stack[depth].ts_score + score_add;
-    stack[depth + 1].ts_curi = 1;      /* start just after length byte */
-    stack[depth + 1].ts_flags = 0;
-}
-
-/*
- * Case-folding may change the number of bytes: Count nr of chars in
- * fword[flen] and return the byte length of that many chars in "word".
- */
-    static int
-nofold_len(char_u *fword, int flen, char_u *word)
-{
-    char_u     *p;
-    int                i = 0;
-
-    for (p = fword; p < fword + flen; MB_PTR_ADV(p))
-       ++i;
-    for (p = word; i > 0; MB_PTR_ADV(p))
-       --i;
-    return (int)(p - word);
-}
-
-/*
- * "fword" is a good word with case folded.  Find the matching keep-case
- * words and put it in "kword".
- * Theoretically there could be several keep-case words that result in the
- * same case-folded word, but we only find one...
- */
-    static void
-find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword)
-{
-    char_u     uword[MAXWLEN];         /* "fword" in upper-case */
-    int                depth;
-    idx_T      tryidx;
-
-    /* The following arrays are used at each depth in the tree. */
-    idx_T      arridx[MAXWLEN];
-    int                round[MAXWLEN];
-    int                fwordidx[MAXWLEN];
-    int                uwordidx[MAXWLEN];
-    int                kwordlen[MAXWLEN];
-
-    int                flen, ulen;
-    int                l;
-    int                len;
-    int                c;
-    idx_T      lo, hi, m;
-    char_u     *p;
-    char_u     *byts = slang->sl_kbyts;    /* array with bytes of the words */
-    idx_T      *idxs = slang->sl_kidxs;    /* array with indexes */
-
-    if (byts == NULL)
-    {
-       /* array is empty: "cannot happen" */
-       *kword = NUL;
-       return;
-    }
-
-    /* Make an all-cap version of "fword". */
-    allcap_copy(fword, uword);
-
-    /*
-     * Each character needs to be tried both case-folded and upper-case.
-     * All this gets very complicated if we keep in mind that changing case
-     * may change the byte length of a multi-byte character...
-     */
-    depth = 0;
-    arridx[0] = 0;
-    round[0] = 0;
-    fwordidx[0] = 0;
-    uwordidx[0] = 0;
-    kwordlen[0] = 0;
-    while (depth >= 0)
-    {
-       if (fword[fwordidx[depth]] == NUL)
-       {
-           /* We are at the end of "fword".  If the tree allows a word to end
-            * here we have found a match. */
-           if (byts[arridx[depth] + 1] == 0)
-           {
-               kword[kwordlen[depth]] = NUL;
-               return;
-           }
-
-           /* kword is getting too long, continue one level up */
-           --depth;
-       }
-       else if (++round[depth] > 2)
-       {
-           /* tried both fold-case and upper-case character, continue one
-            * level up */
-           --depth;
-       }
-       else
-       {
-           /*
-            * round[depth] == 1: Try using the folded-case character.
-            * round[depth] == 2: Try using the upper-case character.
-            */
-           if (has_mbyte)
-           {
-               flen = MB_CPTR2LEN(fword + fwordidx[depth]);
-               ulen = MB_CPTR2LEN(uword + uwordidx[depth]);
-           }
-           else
-               ulen = flen = 1;
-           if (round[depth] == 1)
-           {
-               p = fword + fwordidx[depth];
-               l = flen;
-           }
-           else
-           {
-               p = uword + uwordidx[depth];
-               l = ulen;
-           }
-
-           for (tryidx = arridx[depth]; l > 0; --l)
-           {
-               /* Perform a binary search in the list of accepted bytes. */
-               len = byts[tryidx++];
-               c = *p++;
-               lo = tryidx;
-               hi = tryidx + len - 1;
-               while (lo < hi)
-               {
-                   m = (lo + hi) / 2;
-                   if (byts[m] > c)
-                       hi = m - 1;
-                   else if (byts[m] < c)
-                       lo = m + 1;
-                   else
-                   {
-                       lo = hi = m;
-                       break;
-                   }
-               }
-
-               /* Stop if there is no matching byte. */
-               if (hi < lo || byts[lo] != c)
-                   break;
-
-               /* Continue at the child (if there is one). */
-               tryidx = idxs[lo];
-           }
-
-           if (l == 0)
-           {
-               /*
-                * Found the matching char.  Copy it to "kword" and go a
-                * level deeper.
-                */
-               if (round[depth] == 1)
-               {
-                   STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth],
-                                                                       flen);
-                   kwordlen[depth + 1] = kwordlen[depth] + flen;
-               }
-               else
-               {
-                   STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth],
-                                                                       ulen);
-                   kwordlen[depth + 1] = kwordlen[depth] + ulen;
-               }
-               fwordidx[depth + 1] = fwordidx[depth] + flen;
-               uwordidx[depth + 1] = uwordidx[depth] + ulen;
-
-               ++depth;
-               arridx[depth] = tryidx;
-               round[depth] = 0;
-           }
-       }
-    }
-
-    /* Didn't find it: "cannot happen". */
-    *kword = NUL;
-}
-
-/*
- * Compute the sound-a-like score for suggestions in su->su_ga and add them to
- * su->su_sga.
- */
-    static void
-score_comp_sal(suginfo_T *su)
-{
-    langp_T    *lp;
-    char_u     badsound[MAXWLEN];
-    int                i;
-    suggest_T   *stp;
-    suggest_T   *sstp;
-    int                score;
-    int                lpi;
-
-    if (ga_grow(&su->su_sga, su->su_ga.ga_len) == FAIL)
-       return;
-
-    /* Use the sound-folding of the first language that supports it. */
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-       if (lp->lp_slang->sl_sal.ga_len > 0)
-       {
-           /* soundfold the bad word */
-           spell_soundfold(lp->lp_slang, su->su_fbadword, TRUE, badsound);
-
-           for (i = 0; i < su->su_ga.ga_len; ++i)
-           {
-               stp = &SUG(su->su_ga, i);
-
-               /* Case-fold the suggested word, sound-fold it and compute the
-                * sound-a-like score. */
-               score = stp_sal_score(stp, su, lp->lp_slang, badsound);
-               if (score < SCORE_MAXMAX)
-               {
-                   /* Add the suggestion. */
-                   sstp = &SUG(su->su_sga, su->su_sga.ga_len);
-                   sstp->st_word = vim_strsave(stp->st_word);
-                   if (sstp->st_word != NULL)
-                   {
-                       sstp->st_wordlen = stp->st_wordlen;
-                       sstp->st_score = score;
-                       sstp->st_altscore = 0;
-                       sstp->st_orglen = stp->st_orglen;
-                       ++su->su_sga.ga_len;
-                   }
-               }
-           }
-           break;
-       }
-    }
-}
-
-/*
- * Combine the list of suggestions in su->su_ga and su->su_sga.
- * They are entwined.
- */
-    static void
-score_combine(suginfo_T *su)
-{
-    int                i;
-    int                j;
-    garray_T   ga;
-    garray_T   *gap;
-    langp_T    *lp;
-    suggest_T  *stp;
-    char_u     *p;
-    char_u     badsound[MAXWLEN];
-    int                round;
-    int                lpi;
-    slang_T    *slang = NULL;
-
-    /* Add the alternate score to su_ga. */
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-       if (lp->lp_slang->sl_sal.ga_len > 0)
-       {
-           /* soundfold the bad word */
-           slang = lp->lp_slang;
-           spell_soundfold(slang, su->su_fbadword, TRUE, badsound);
-
-           for (i = 0; i < su->su_ga.ga_len; ++i)
-           {
-               stp = &SUG(su->su_ga, i);
-               stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
-               if (stp->st_altscore == SCORE_MAXMAX)
-                   stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
-               else
-                   stp->st_score = (stp->st_score * 3
-                                                 + stp->st_altscore) / 4;
-               stp->st_salscore = FALSE;
-           }
-           break;
-       }
-    }
-
-    if (slang == NULL) /* Using "double" without sound folding. */
-    {
-       (void)cleanup_suggestions(&su->su_ga, su->su_maxscore,
-                                                            su->su_maxcount);
-       return;
-    }
-
-    /* Add the alternate score to su_sga. */
-    for (i = 0; i < su->su_sga.ga_len; ++i)
-    {
-       stp = &SUG(su->su_sga, i);
-       stp->st_altscore = spell_edit_score(slang,
-                                               su->su_badword, stp->st_word);
-       if (stp->st_score == SCORE_MAXMAX)
-           stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
-       else
-           stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8;
-       stp->st_salscore = TRUE;
-    }
-
-    /* Remove bad suggestions, sort the suggestions and truncate at "maxcount"
-     * for both lists. */
-    check_suggestions(su, &su->su_ga);
-    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
-    check_suggestions(su, &su->su_sga);
-    (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount);
-
-    ga_init2(&ga, (int)sizeof(suginfo_T), 1);
-    if (ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len) == FAIL)
-       return;
-
-    stp = &SUG(ga, 0);
-    for (i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i)
-    {
-       /* round 1: get a suggestion from su_ga
-        * round 2: get a suggestion from su_sga */
-       for (round = 1; round <= 2; ++round)
-       {
-           gap = round == 1 ? &su->su_ga : &su->su_sga;
-           if (i < gap->ga_len)
-           {
-               /* Don't add a word if it's already there. */
-               p = SUG(*gap, i).st_word;
-               for (j = 0; j < ga.ga_len; ++j)
-                   if (STRCMP(stp[j].st_word, p) == 0)
-                       break;
-               if (j == ga.ga_len)
-                   stp[ga.ga_len++] = SUG(*gap, i);
-               else
-                   vim_free(p);
-           }
-       }
-    }
-
-    ga_clear(&su->su_ga);
-    ga_clear(&su->su_sga);
-
-    /* Truncate the list to the number of suggestions that will be displayed. */
-    if (ga.ga_len > su->su_maxcount)
-    {
-       for (i = su->su_maxcount; i < ga.ga_len; ++i)
-           vim_free(stp[i].st_word);
-       ga.ga_len = su->su_maxcount;
-    }
-
-    su->su_ga = ga;
-}
-
-/*
- * For the goodword in "stp" compute the soundalike score compared to the
- * badword.
- */
-    static int
-stp_sal_score(
-    suggest_T  *stp,
-    suginfo_T  *su,
-    slang_T    *slang,
-    char_u     *badsound)      /* sound-folded badword */
-{
-    char_u     *p;
-    char_u     *pbad;
-    char_u     *pgood;
-    char_u     badsound2[MAXWLEN];
-    char_u     fword[MAXWLEN];
-    char_u     goodsound[MAXWLEN];
-    char_u     goodword[MAXWLEN];
-    int                lendiff;
-
-    lendiff = (int)(su->su_badlen - stp->st_orglen);
-    if (lendiff >= 0)
-       pbad = badsound;
-    else
-    {
-       /* soundfold the bad word with more characters following */
-       (void)spell_casefold(su->su_badptr, stp->st_orglen, fword, MAXWLEN);
-
-       /* When joining two words the sound often changes a lot.  E.g., "t he"
-        * sounds like "t h" while "the" sounds like "@".  Avoid that by
-        * removing the space.  Don't do it when the good word also contains a
-        * space. */
-       if (VIM_ISWHITE(su->su_badptr[su->su_badlen])
-                                        && *skiptowhite(stp->st_word) == NUL)
-           for (p = fword; *(p = skiptowhite(p)) != NUL; )
-               STRMOVE(p, p + 1);
-
-       spell_soundfold(slang, fword, TRUE, badsound2);
-       pbad = badsound2;
-    }
-
-    if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN)
-    {
-       /* Add part of the bad word to the good word, so that we soundfold
-        * what replaces the bad word. */
-       STRCPY(goodword, stp->st_word);
-       vim_strncpy(goodword + stp->st_wordlen,
-                           su->su_badptr + su->su_badlen - lendiff, lendiff);
-       pgood = goodword;
-    }
-    else
-       pgood = stp->st_word;
-
-    /* Sound-fold the word and compute the score for the difference. */
-    spell_soundfold(slang, pgood, FALSE, goodsound);
-
-    return soundalike_score(goodsound, pbad);
-}
-
-/* structure used to store soundfolded words that add_sound_suggest() has
- * handled already. */
-typedef struct
-{
-    short      sft_score;      /* lowest score used */
-    char_u     sft_word[1];    /* soundfolded word, actually longer */
-} sftword_T;
-
-static sftword_T dumsft;
-#define HIKEY2SFT(p)  ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft)))
-#define HI2SFT(hi)     HIKEY2SFT((hi)->hi_key)
-
-/*
- * Prepare for calling suggest_try_soundalike().
- */
-    static void
-suggest_try_soundalike_prep(void)
-{
-    langp_T    *lp;
-    int                lpi;
-    slang_T    *slang;
-
-    /* Do this for all languages that support sound folding and for which a
-     * .sug file has been loaded. */
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-       slang = lp->lp_slang;
-       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
-           /* prepare the hashtable used by add_sound_suggest() */
-           hash_init(&slang->sl_sounddone);
-    }
-}
-
-/*
- * Find suggestions by comparing the word in a sound-a-like form.
- * Note: This doesn't support postponed prefixes.
- */
-    static void
-suggest_try_soundalike(suginfo_T *su)
-{
-    char_u     salword[MAXWLEN];
-    langp_T    *lp;
-    int                lpi;
-    slang_T    *slang;
-
-    /* Do this for all languages that support sound folding and for which a
-     * .sug file has been loaded. */
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-       slang = lp->lp_slang;
-       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
-       {
-           /* soundfold the bad word */
-           spell_soundfold(slang, su->su_fbadword, TRUE, salword);
-
-           /* try all kinds of inserts/deletes/swaps/etc. */
-           /* TODO: also soundfold the next words, so that we can try joining
-            * and splitting */
-#ifdef SUGGEST_PROFILE
-       prof_init();
-#endif
-           suggest_trie_walk(su, lp, salword, TRUE);
-#ifdef SUGGEST_PROFILE
-       prof_report("soundalike");
-#endif
-       }
-    }
-}
-
-/*
- * Finish up after calling suggest_try_soundalike().
- */
-    static void
-suggest_try_soundalike_finish(void)
-{
-    langp_T    *lp;
-    int                lpi;
-    slang_T    *slang;
-    int                todo;
-    hashitem_T *hi;
-
-    /* Do this for all languages that support sound folding and for which a
-     * .sug file has been loaded. */
-    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
-    {
-       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
-       slang = lp->lp_slang;
-       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
-       {
-           /* Free the info about handled words. */
-           todo = (int)slang->sl_sounddone.ht_used;
-           for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi)
-               if (!HASHITEM_EMPTY(hi))
-               {
-                   vim_free(HI2SFT(hi));
-                   --todo;
-               }
-
-           /* Clear the hashtable, it may also be used by another region. */
-           hash_clear(&slang->sl_sounddone);
-           hash_init(&slang->sl_sounddone);
-       }
-    }
-}
-
-/*
- * A match with a soundfolded word is found.  Add the good word(s) that
- * produce this soundfolded word.
- */
-    static void
-add_sound_suggest(
-    suginfo_T  *su,
-    char_u     *goodword,
-    int                score,          /* soundfold score  */
-    langp_T    *lp)
-{
-    slang_T    *slang = lp->lp_slang;  /* language for sound folding */
-    int                sfwordnr;
-    char_u     *nrline;
-    int                orgnr;
-    char_u     theword[MAXWLEN];
-    int                i;
-    int                wlen;
-    char_u     *byts;
-    idx_T      *idxs;
-    int                n;
-    int                wordcount;
-    int                wc;
-    int                goodscore;
-    hash_T     hash;
-    hashitem_T  *hi;
-    sftword_T  *sft;
-    int                bc, gc;
-    int                limit;
-
-    /*
-     * It's very well possible that the same soundfold word is found several
-     * times with different scores.  Since the following is quite slow only do
-     * the words that have a better score than before.  Use a hashtable to
-     * remember the words that have been done.
-     */
-    hash = hash_hash(goodword);
-    hi = hash_lookup(&slang->sl_sounddone, goodword, hash);
-    if (HASHITEM_EMPTY(hi))
-    {
-       sft = alloc(sizeof(sftword_T) + STRLEN(goodword));
-       if (sft != NULL)
-       {
-           sft->sft_score = score;
-           STRCPY(sft->sft_word, goodword);
-           hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash);
-       }
-    }
-    else
-    {
-       sft = HI2SFT(hi);
-       if (score >= sft->sft_score)
-           return;
-       sft->sft_score = score;
-    }
-
-    /*
-     * Find the word nr in the soundfold tree.
-     */
-    sfwordnr = soundfold_find(slang, goodword);
-    if (sfwordnr < 0)
-    {
-       internal_error("add_sound_suggest()");
-       return;
-    }
-
-    /*
-     * go over the list of good words that produce this soundfold word
-     */
-    nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)(sfwordnr + 1), FALSE);
-    orgnr = 0;
-    while (*nrline != NUL)
-    {
-       /* The wordnr was stored in a minimal nr of bytes as an offset to the
-        * previous wordnr. */
-       orgnr += bytes2offset(&nrline);
-
-       byts = slang->sl_fbyts;
-       idxs = slang->sl_fidxs;
-
-       /* Lookup the word "orgnr" one of the two tries. */
-       n = 0;
-       wordcount = 0;
-       for (wlen = 0; wlen < MAXWLEN - 3; ++wlen)
-       {
-           i = 1;
-           if (wordcount == orgnr && byts[n + 1] == NUL)
-               break;  /* found end of word */
-
-           if (byts[n + 1] == NUL)
-               ++wordcount;
-
-           /* skip over the NUL bytes */
-           for ( ; byts[n + i] == NUL; ++i)
-               if (i > byts[n])        /* safety check */
-               {
-                   STRCPY(theword + wlen, "BAD");
-                   wlen += 3;
-                   goto badword;
-               }
-
-           /* One of the siblings must have the word. */
-           for ( ; i < byts[n]; ++i)
-           {
-               wc = idxs[idxs[n + i]]; /* nr of words under this byte */
-               if (wordcount + wc > orgnr)
-                   break;
-               wordcount += wc;
-           }
-
-           theword[wlen] = byts[n + i];
-           n = idxs[n + i];
-       }
-badword:
-       theword[wlen] = NUL;
-
-       /* Go over the possible flags and regions. */
-       for (; i <= byts[n] && byts[n + i] == NUL; ++i)
-       {
-           char_u      cword[MAXWLEN];
-           char_u      *p;
-           int         flags = (int)idxs[n + i];
-
-           /* Skip words with the NOSUGGEST flag */
-           if (flags & WF_NOSUGGEST)
-               continue;
-
-           if (flags & WF_KEEPCAP)
-           {
-               /* Must find the word in the keep-case tree. */
-               find_keepcap_word(slang, theword, cword);
-               p = cword;
-           }
-           else
-           {
-               flags |= su->su_badflags;
-               if ((flags & WF_CAPMASK) != 0)
-               {
-                   /* Need to fix case according to "flags". */
-                   make_case_word(theword, cword, flags);
-                   p = cword;
-               }
-               else
-                   p = theword;
-           }
-
-           /* Add the suggestion. */
-           if (sps_flags & SPS_DOUBLE)
-           {
-               /* Add the suggestion if the score isn't too bad. */
-               if (score <= su->su_maxscore)
-                   add_suggestion(su, &su->su_sga, p, su->su_badlen,
-                                              score, 0, FALSE, slang, FALSE);
-           }
-           else
-           {
-               /* Add a penalty for words in another region. */
-               if ((flags & WF_REGION)
-                           && (((unsigned)flags >> 16) & lp->lp_region) == 0)
-                   goodscore = SCORE_REGION;
-               else
-                   goodscore = 0;
-
-               /* Add a small penalty for changing the first letter from
-                * lower to upper case.  Helps for "tath" -> "Kath", which is
-                * less common than "tath" -> "path".  Don't do it when the
-                * letter is the same, that has already been counted. */
-               gc = PTR2CHAR(p);
-               if (SPELL_ISUPPER(gc))
-               {
-                   bc = PTR2CHAR(su->su_badword);
-                   if (!SPELL_ISUPPER(bc)
-                                     && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc))
-                       goodscore += SCORE_ICASE / 2;
-               }
-
-               /* Compute the score for the good word.  This only does letter
-                * insert/delete/swap/replace.  REP items are not considered,
-                * which may make the score a bit higher.
-                * Use a limit for the score to make it work faster.  Use
-                * MAXSCORE(), because RESCORE() will change the score.
-                * If the limit is very high then the iterative method is
-                * inefficient, using an array is quicker. */
-               limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
-               if (limit > SCORE_LIMITMAX)
-                   goodscore += spell_edit_score(slang, su->su_badword, p);
-               else
-                   goodscore += spell_edit_score_limit(slang, su->su_badword,
-                                                                   p, limit);
-
-               /* When going over the limit don't bother to do the rest. */
-               if (goodscore < SCORE_MAXMAX)
-               {
-                   /* Give a bonus to words seen before. */
-                   goodscore = score_wordcount_adj(slang, goodscore, p, FALSE);
-
-                   /* Add the suggestion if the score isn't too bad. */
-                   goodscore = RESCORE(goodscore, score);
-                   if (goodscore <= su->su_sfmaxscore)
-                       add_suggestion(su, &su->su_ga, p, su->su_badlen,
-                                        goodscore, score, TRUE, slang, TRUE);
-               }
-           }
-       }
-       /* smsg("word %s (%d): %s (%d)", sftword, sftnr, theword, orgnr); */
-    }
-}
-
-/*
- * Find word "word" in fold-case tree for "slang" and return the word number.
- */
-    static int
-soundfold_find(slang_T *slang, char_u *word)
-{
-    idx_T      arridx = 0;
-    int                len;
-    int                wlen = 0;
-    int                c;
-    char_u     *ptr = word;
-    char_u     *byts;
-    idx_T      *idxs;
-    int                wordnr = 0;
-
-    byts = slang->sl_sbyts;
-    idxs = slang->sl_sidxs;
-
-    for (;;)
-    {
-       /* First byte is the number of possible bytes. */
-       len = byts[arridx++];
-
-       /* If the first possible byte is a zero the word could end here.
-        * If the word ends we found the word.  If not skip the NUL bytes. */
-       c = ptr[wlen];
-       if (byts[arridx] == NUL)
-       {
-           if (c == NUL)
-               break;
-
-           /* Skip over the zeros, there can be several. */
-           while (len > 0 && byts[arridx] == NUL)
-           {
-               ++arridx;
-               --len;
-           }
-           if (len == 0)
-               return -1;    /* no children, word should have ended here */
-           ++wordnr;
-       }
-
-       /* If the word ends we didn't find it. */
-       if (c == NUL)
-           return -1;
-
-       /* Perform a binary search in the list of accepted bytes. */
-       if (c == TAB)       /* <Tab> is handled like <Space> */
-           c = ' ';
-       while (byts[arridx] < c)
-       {
-           /* The word count is in the first idxs[] entry of the child. */
-           wordnr += idxs[idxs[arridx]];
-           ++arridx;
-           if (--len == 0)     /* end of the bytes, didn't find it */
-               return -1;
-       }
-       if (byts[arridx] != c)  /* didn't find the byte */
-           return -1;
-
-       /* Continue at the child (if there is one). */
-       arridx = idxs[arridx];
-       ++wlen;
-
-       /* One space in the good word may stand for several spaces in the
-        * checked word. */
-       if (c == ' ')
-           while (ptr[wlen] == ' ' || ptr[wlen] == TAB)
-               ++wlen;
-    }
-
-    return wordnr;
-}
-
-/*
- * Copy "fword" to "cword", fixing case according to "flags".
- */
-    static void
-make_case_word(char_u *fword, char_u *cword, int flags)
-{
-    if (flags & WF_ALLCAP)
-       /* Make it all upper-case */
-       allcap_copy(fword, cword);
-    else if (flags & WF_ONECAP)
-       /* Make the first letter upper-case */
-       onecap_copy(fword, cword, TRUE);
-    else
-       /* Use goodword as-is. */
-       STRCPY(cword, fword);
-}
-
-
-/*
- * Return TRUE if "c1" and "c2" are similar characters according to the MAP
- * lines in the .aff file.
- */
-    static int
-similar_chars(slang_T *slang, int c1, int c2)
-{
-    int                m1, m2;
-    char_u     buf[MB_MAXBYTES + 1];
-    hashitem_T  *hi;
-
-    if (c1 >= 256)
-    {
-       buf[mb_char2bytes(c1, buf)] = 0;
-       hi = hash_find(&slang->sl_map_hash, buf);
-       if (HASHITEM_EMPTY(hi))
-           m1 = 0;
-       else
-           m1 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1);
-    }
-    else
-       m1 = slang->sl_map_array[c1];
-    if (m1 == 0)
-       return FALSE;
-
-
-    if (c2 >= 256)
-    {
-       buf[mb_char2bytes(c2, buf)] = 0;
-       hi = hash_find(&slang->sl_map_hash, buf);
-       if (HASHITEM_EMPTY(hi))
-           m2 = 0;
-       else
-           m2 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1);
-    }
-    else
-       m2 = slang->sl_map_array[c2];
-
-    return m1 == m2;
-}
-
-/*
- * Add a suggestion to the list of suggestions.
- * For a suggestion that is already in the list the lowest score is remembered.
- */
-    static void
-add_suggestion(
-    suginfo_T  *su,
-    garray_T   *gap,           /* either su_ga or su_sga */
-    char_u     *goodword,
-    int                badlenarg,      /* len of bad word replaced with "goodword" */
-    int                score,
-    int                altscore,
-    int                had_bonus,      /* value for st_had_bonus */
-    slang_T    *slang,         /* language for sound folding */
-    int                maxsf)          /* su_maxscore applies to soundfold score,
-                                  su_sfmaxscore to the total score. */
-{
-    int                goodlen;        /* len of goodword changed */
-    int                badlen;         /* len of bad word changed */
-    suggest_T   *stp;
-    suggest_T   new_sug;
-    int                i;
-    char_u     *pgood, *pbad;
-
-    /* Minimize "badlen" for consistency.  Avoids that changing "the the" to
-     * "thee the" is added next to changing the first "the" the "thee".  */
-    pgood = goodword + STRLEN(goodword);
-    pbad = su->su_badptr + badlenarg;
-    for (;;)
-    {
-       goodlen = (int)(pgood - goodword);
-       badlen = (int)(pbad - su->su_badptr);
-       if (goodlen <= 0 || badlen <= 0)
-           break;
-       MB_PTR_BACK(goodword, pgood);
-       MB_PTR_BACK(su->su_badptr, pbad);
-       if (has_mbyte)
-       {
-           if (mb_ptr2char(pgood) != mb_ptr2char(pbad))
-               break;
-       }
-       else if (*pgood != *pbad)
-               break;
-    }
-
-    if (badlen == 0 && goodlen == 0)
-       /* goodword doesn't change anything; may happen for "the the" changing
-        * the first "the" to itself. */
-       return;
-
-    if (gap->ga_len == 0)
-       i = -1;
-    else
-    {
-       /* Check if the word is already there.  Also check the length that is
-        * being replaced "thes," -> "these" is a different suggestion from
-        * "thes" -> "these". */
-       stp = &SUG(*gap, 0);
-       for (i = gap->ga_len; --i >= 0; ++stp)
-           if (stp->st_wordlen == goodlen
-                   && stp->st_orglen == badlen
-                   && STRNCMP(stp->st_word, goodword, goodlen) == 0)
-           {
-               /*
-                * Found it.  Remember the word with the lowest score.
-                */
-               if (stp->st_slang == NULL)
-                   stp->st_slang = slang;
-
-               new_sug.st_score = score;
-               new_sug.st_altscore = altscore;
-               new_sug.st_had_bonus = had_bonus;
-
-               if (stp->st_had_bonus != had_bonus)
-               {
-                   /* Only one of the two had the soundalike score computed.
-                    * Need to do that for the other one now, otherwise the
-                    * scores can't be compared.  This happens because
-                    * suggest_try_change() doesn't compute the soundalike
-                    * word to keep it fast, while some special methods set
-                    * the soundalike score to zero. */
-                   if (had_bonus)
-                       rescore_one(su, stp);
-                   else
-                   {
-                       new_sug.st_word = stp->st_word;
-                       new_sug.st_wordlen = stp->st_wordlen;
-                       new_sug.st_slang = stp->st_slang;
-                       new_sug.st_orglen = badlen;
-                       rescore_one(su, &new_sug);
-                   }
-               }
-
-               if (stp->st_score > new_sug.st_score)
-               {
-                   stp->st_score = new_sug.st_score;
-                   stp->st_altscore = new_sug.st_altscore;
-                   stp->st_had_bonus = new_sug.st_had_bonus;
-               }
-               break;
-           }
-    }
-
-    if (i < 0 && ga_grow(gap, 1) == OK)
-    {
-       /* Add a suggestion. */
-       stp = &SUG(*gap, gap->ga_len);
-       stp->st_word = vim_strnsave(goodword, goodlen);
-       if (stp->st_word != NULL)
-       {
-           stp->st_wordlen = goodlen;
-           stp->st_score = score;
-           stp->st_altscore = altscore;
-           stp->st_had_bonus = had_bonus;
-           stp->st_orglen = badlen;
-           stp->st_slang = slang;
-           ++gap->ga_len;
-
-           /* If we have too many suggestions now, sort the list and keep
-            * the best suggestions. */
-           if (gap->ga_len > SUG_MAX_COUNT(su))
-           {
-               if (maxsf)
-                   su->su_sfmaxscore = cleanup_suggestions(gap,
-                                     su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
-               else
-                   su->su_maxscore = cleanup_suggestions(gap,
-                                       su->su_maxscore, SUG_CLEAN_COUNT(su));
-           }
-       }
-    }
-}
-
-/*
- * Suggestions may in fact be flagged as errors.  Esp. for banned words and
- * for split words, such as "the the".  Remove these from the list here.
- */
-    static void
-check_suggestions(
-    suginfo_T  *su,
-    garray_T   *gap)               /* either su_ga or su_sga */
-{
-    suggest_T   *stp;
-    int                i;
-    char_u     longword[MAXWLEN + 1];
-    int                len;
-    hlf_T      attr;
-
-    stp = &SUG(*gap, 0);
-    for (i = gap->ga_len - 1; i >= 0; --i)
-    {
-       /* Need to append what follows to check for "the the". */
-       vim_strncpy(longword, stp[i].st_word, MAXWLEN);
-       len = stp[i].st_wordlen;
-       vim_strncpy(longword + len, su->su_badptr + stp[i].st_orglen,
-                                                              MAXWLEN - len);
-       attr = HLF_COUNT;
-       (void)spell_check(curwin, longword, &attr, NULL, FALSE);
-       if (attr != HLF_COUNT)
-       {
-           /* Remove this entry. */
-           vim_free(stp[i].st_word);
-           --gap->ga_len;
-           if (i < gap->ga_len)
-               mch_memmove(stp + i, stp + i + 1,
-                                      sizeof(suggest_T) * (gap->ga_len - i));
-       }
-    }
-}
-
-
-/*
- * Add a word to be banned.
- */
-    static void
-add_banned(
-    suginfo_T  *su,
-    char_u     *word)
-{
-    char_u     *s;
-    hash_T     hash;
-    hashitem_T *hi;
-
-    hash = hash_hash(word);
-    hi = hash_lookup(&su->su_banned, word, hash);
-    if (HASHITEM_EMPTY(hi))
-    {
-       s = vim_strsave(word);
-       if (s != NULL)
-           hash_add_item(&su->su_banned, hi, s, hash);
-    }
-}
-
-/*
- * Recompute the score for all suggestions if sound-folding is possible.  This
- * is slow, thus only done for the final results.
- */
-    static void
-rescore_suggestions(suginfo_T *su)
-{
-    int                i;
-
-    if (su->su_sallang != NULL)
-       for (i = 0; i < su->su_ga.ga_len; ++i)
-           rescore_one(su, &SUG(su->su_ga, i));
-}
-
-/*
- * Recompute the score for one suggestion if sound-folding is possible.
- */
-    static void
-rescore_one(suginfo_T *su, suggest_T *stp)
-{
-    slang_T    *slang = stp->st_slang;
-    char_u     sal_badword[MAXWLEN];
-    char_u     *p;
-
-    /* Only rescore suggestions that have no sal score yet and do have a
-     * language. */
-    if (slang != NULL && slang->sl_sal.ga_len > 0 && !stp->st_had_bonus)
-    {
-       if (slang == su->su_sallang)
-           p = su->su_sal_badword;
-       else
-       {
-           spell_soundfold(slang, su->su_fbadword, TRUE, sal_badword);
-           p = sal_badword;
-       }
-
-       stp->st_altscore = stp_sal_score(stp, su, slang, p);
-       if (stp->st_altscore == SCORE_MAXMAX)
-           stp->st_altscore = SCORE_BIG;
-       stp->st_score = RESCORE(stp->st_score, stp->st_altscore);
-       stp->st_had_bonus = TRUE;
-    }
-}
-
-static int sug_compare(const void *s1, const void *s2);
-
-/*
- * Function given to qsort() to sort the suggestions on st_score.
- * First on "st_score", then "st_altscore" then alphabetically.
- */
-    static int
-sug_compare(const void *s1, const void *s2)
-{
-    suggest_T  *p1 = (suggest_T *)s1;
-    suggest_T  *p2 = (suggest_T *)s2;
-    int                n = p1->st_score - p2->st_score;
-
-    if (n == 0)
-    {
-       n = p1->st_altscore - p2->st_altscore;
-       if (n == 0)
-           n = STRICMP(p1->st_word, p2->st_word);
-    }
-    return n;
-}
-
-/*
- * Cleanup the suggestions:
- * - Sort on score.
- * - Remove words that won't be displayed.
- * Returns the maximum score in the list or "maxscore" unmodified.
- */
-    static int
-cleanup_suggestions(
-    garray_T   *gap,
-    int                maxscore,
-    int                keep)           /* nr of suggestions to keep */
-{
-    suggest_T   *stp = &SUG(*gap, 0);
-    int                i;
-
-    /* Sort the list. */
-    qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
-
-    /* Truncate the list to the number of suggestions that will be displayed. */
-    if (gap->ga_len > keep)
-    {
-       for (i = keep; i < gap->ga_len; ++i)
-           vim_free(stp[i].st_word);
-       gap->ga_len = keep;
-       return stp[keep - 1].st_score;
-    }
-    return maxscore;
+    if (flags & WF_ALLCAP)
+       /* Make it all upper-case */
+       allcap_copy(fword, cword);
+    else if (flags & WF_ONECAP)
+       /* Make the first letter upper-case */
+       onecap_copy(fword, cword, TRUE);
+    else
+       /* Use goodword as-is. */
+       STRCPY(cword, fword);
 }
 
 #if defined(FEAT_EVAL) || defined(PROTO)
@@ -7547,736 +3747,6 @@ spell_soundfold_wsal(slang_T *slang, char_u *inword, char_u *res)
     res[l] = NUL;
 }
 
-/*
- * Compute a score for two sound-a-like words.
- * This permits up to two inserts/deletes/swaps/etc. to keep things fast.
- * Instead of a generic loop we write out the code.  That keeps it fast by
- * avoiding checks that will not be possible.
- */
-    static int
-soundalike_score(
-    char_u     *goodstart,     /* sound-folded good word */
-    char_u     *badstart)      /* sound-folded bad word */
-{
-    char_u     *goodsound = goodstart;
-    char_u     *badsound = badstart;
-    int                goodlen;
-    int                badlen;
-    int                n;
-    char_u     *pl, *ps;
-    char_u     *pl2, *ps2;
-    int                score = 0;
-
-    /* Adding/inserting "*" at the start (word starts with vowel) shouldn't be
-     * counted so much, vowels halfway the word aren't counted at all. */
-    if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound)
-    {
-       if ((badsound[0] == NUL && goodsound[1] == NUL)
-           || (goodsound[0] == NUL && badsound[1] == NUL))
-           /* changing word with vowel to word without a sound */
-           return SCORE_DEL;
-       if (badsound[0] == NUL || goodsound[0] == NUL)
-           /* more than two changes */
-           return SCORE_MAXMAX;
-
-       if (badsound[1] == goodsound[1]
-               || (badsound[1] != NUL
-                   && goodsound[1] != NUL
-                   && badsound[2] == goodsound[2]))
-       {
-           /* handle like a substitute */
-       }
-       else
-       {
-           score = 2 * SCORE_DEL / 3;
-           if (*badsound == '*')
-               ++badsound;
-           else
-               ++goodsound;
-       }
-    }
-
-    goodlen = (int)STRLEN(goodsound);
-    badlen = (int)STRLEN(badsound);
-
-    /* Return quickly if the lengths are too different to be fixed by two
-     * changes. */
-    n = goodlen - badlen;
-    if (n < -2 || n > 2)
-       return SCORE_MAXMAX;
-
-    if (n > 0)
-    {
-       pl = goodsound;     /* goodsound is longest */
-       ps = badsound;
-    }
-    else
-    {
-       pl = badsound;      /* badsound is longest */
-       ps = goodsound;
-    }
-
-    /* Skip over the identical part. */
-    while (*pl == *ps && *pl != NUL)
-    {
-       ++pl;
-       ++ps;
-    }
-
-    switch (n)
-    {
-       case -2:
-       case 2:
-           /*
-            * Must delete two characters from "pl".
-            */
-           ++pl;       /* first delete */
-           while (*pl == *ps)
-           {
-               ++pl;
-               ++ps;
-           }
-           /* strings must be equal after second delete */
-           if (STRCMP(pl + 1, ps) == 0)
-               return score + SCORE_DEL * 2;
-
-           /* Failed to compare. */
-           break;
-
-       case -1:
-       case 1:
-           /*
-            * Minimal one delete from "pl" required.
-            */
-
-           /* 1: delete */
-           pl2 = pl + 1;
-           ps2 = ps;
-           while (*pl2 == *ps2)
-           {
-               if (*pl2 == NUL)        /* reached the end */
-                   return score + SCORE_DEL;
-               ++pl2;
-               ++ps2;
-           }
-
-           /* 2: delete then swap, then rest must be equal */
-           if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
-                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
-               return score + SCORE_DEL + SCORE_SWAP;
-
-           /* 3: delete then substitute, then the rest must be equal */
-           if (STRCMP(pl2 + 1, ps2 + 1) == 0)
-               return score + SCORE_DEL + SCORE_SUBST;
-
-           /* 4: first swap then delete */
-           if (pl[0] == ps[1] && pl[1] == ps[0])
-           {
-               pl2 = pl + 2;       /* swap, skip two chars */
-               ps2 = ps + 2;
-               while (*pl2 == *ps2)
-               {
-                   ++pl2;
-                   ++ps2;
-               }
-               /* delete a char and then strings must be equal */
-               if (STRCMP(pl2 + 1, ps2) == 0)
-                   return score + SCORE_SWAP + SCORE_DEL;
-           }
-
-           /* 5: first substitute then delete */
-           pl2 = pl + 1;           /* substitute, skip one char */
-           ps2 = ps + 1;
-           while (*pl2 == *ps2)
-           {
-               ++pl2;
-               ++ps2;
-           }
-           /* delete a char and then strings must be equal */
-           if (STRCMP(pl2 + 1, ps2) == 0)
-               return score + SCORE_SUBST + SCORE_DEL;
-
-           /* Failed to compare. */
-           break;
-
-       case 0:
-           /*
-            * Lengths are equal, thus changes must result in same length: An
-            * insert is only possible in combination with a delete.
-            * 1: check if for identical strings
-            */
-           if (*pl == NUL)
-               return score;
-
-           /* 2: swap */
-           if (pl[0] == ps[1] && pl[1] == ps[0])
-           {
-               pl2 = pl + 2;       /* swap, skip two chars */
-               ps2 = ps + 2;
-               while (*pl2 == *ps2)
-               {
-                   if (*pl2 == NUL)    /* reached the end */
-                       return score + SCORE_SWAP;
-                   ++pl2;
-                   ++ps2;
-               }
-               /* 3: swap and swap again */
-               if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
-                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
-                   return score + SCORE_SWAP + SCORE_SWAP;
-
-               /* 4: swap and substitute */
-               if (STRCMP(pl2 + 1, ps2 + 1) == 0)
-                   return score + SCORE_SWAP + SCORE_SUBST;
-           }
-
-           /* 5: substitute */
-           pl2 = pl + 1;
-           ps2 = ps + 1;
-           while (*pl2 == *ps2)
-           {
-               if (*pl2 == NUL)        /* reached the end */
-                   return score + SCORE_SUBST;
-               ++pl2;
-               ++ps2;
-           }
-
-           /* 6: substitute and swap */
-           if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
-                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
-               return score + SCORE_SUBST + SCORE_SWAP;
-
-           /* 7: substitute and substitute */
-           if (STRCMP(pl2 + 1, ps2 + 1) == 0)
-               return score + SCORE_SUBST + SCORE_SUBST;
-
-           /* 8: insert then delete */
-           pl2 = pl;
-           ps2 = ps + 1;
-           while (*pl2 == *ps2)
-           {
-               ++pl2;
-               ++ps2;
-           }
-           if (STRCMP(pl2 + 1, ps2) == 0)
-               return score + SCORE_INS + SCORE_DEL;
-
-           /* 9: delete then insert */
-           pl2 = pl + 1;
-           ps2 = ps;
-           while (*pl2 == *ps2)
-           {
-               ++pl2;
-               ++ps2;
-           }
-           if (STRCMP(pl2, ps2 + 1) == 0)
-               return score + SCORE_INS + SCORE_DEL;
-
-           /* Failed to compare. */
-           break;
-    }
-
-    return SCORE_MAXMAX;
-}
-
-/*
- * Compute the "edit distance" to turn "badword" into "goodword".  The less
- * deletes/inserts/substitutes/swaps are required the lower the score.
- *
- * The algorithm is described by Du and Chang, 1992.
- * The implementation of the algorithm comes from Aspell editdist.cpp,
- * edit_distance().  It has been converted from C++ to C and modified to
- * support multi-byte characters.
- */
-    static int
-spell_edit_score(
-    slang_T    *slang,
-    char_u     *badword,
-    char_u     *goodword)
-{
-    int                *cnt;
-    int                badlen, goodlen;        /* lengths including NUL */
-    int                j, i;
-    int                t;
-    int                bc, gc;
-    int                pbc, pgc;
-    char_u     *p;
-    int                wbadword[MAXWLEN];
-    int                wgoodword[MAXWLEN];
-
-    if (has_mbyte)
-    {
-       /* Get the characters from the multi-byte strings and put them in an
-        * int array for easy access. */
-       for (p = badword, badlen = 0; *p != NUL; )
-           wbadword[badlen++] = mb_cptr2char_adv(&p);
-       wbadword[badlen++] = 0;
-       for (p = goodword, goodlen = 0; *p != NUL; )
-           wgoodword[goodlen++] = mb_cptr2char_adv(&p);
-       wgoodword[goodlen++] = 0;
-    }
-    else
-    {
-       badlen = (int)STRLEN(badword) + 1;
-       goodlen = (int)STRLEN(goodword) + 1;
-    }
-
-    /* We use "cnt" as an array: CNT(badword_idx, goodword_idx). */
-#define CNT(a, b)   cnt[(a) + (b) * (badlen + 1)]
-    cnt = ALLOC_MULT(int, (badlen + 1) * (goodlen + 1));
-    if (cnt == NULL)
-       return 0;       /* out of memory */
-
-    CNT(0, 0) = 0;
-    for (j = 1; j <= goodlen; ++j)
-       CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
-
-    for (i = 1; i <= badlen; ++i)
-    {
-       CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
-       for (j = 1; j <= goodlen; ++j)
-       {
-           if (has_mbyte)
-           {
-               bc = wbadword[i - 1];
-               gc = wgoodword[j - 1];
-           }
-           else
-           {
-               bc = badword[i - 1];
-               gc = goodword[j - 1];
-           }
-           if (bc == gc)
-               CNT(i, j) = CNT(i - 1, j - 1);
-           else
-           {
-               /* Use a better score when there is only a case difference. */
-               if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
-                   CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1);
-               else
-               {
-                   /* For a similar character use SCORE_SIMILAR. */
-                   if (slang != NULL
-                           && slang->sl_has_map
-                           && similar_chars(slang, gc, bc))
-                       CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1);
-                   else
-                       CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1);
-               }
-
-               if (i > 1 && j > 1)
-               {
-                   if (has_mbyte)
-                   {
-                       pbc = wbadword[i - 2];
-                       pgc = wgoodword[j - 2];
-                   }
-                   else
-                   {
-                       pbc = badword[i - 2];
-                       pgc = goodword[j - 2];
-                   }
-                   if (bc == pgc && pbc == gc)
-                   {
-                       t = SCORE_SWAP + CNT(i - 2, j - 2);
-                       if (t < CNT(i, j))
-                           CNT(i, j) = t;
-                   }
-               }
-               t = SCORE_DEL + CNT(i - 1, j);
-               if (t < CNT(i, j))
-                   CNT(i, j) = t;
-               t = SCORE_INS + CNT(i, j - 1);
-               if (t < CNT(i, j))
-                   CNT(i, j) = t;
-           }
-       }
-    }
-
-    i = CNT(badlen - 1, goodlen - 1);
-    vim_free(cnt);
-    return i;
-}
-
-typedef struct
-{
-    int                badi;
-    int                goodi;
-    int                score;
-} limitscore_T;
-
-/*
- * Like spell_edit_score(), but with a limit on the score to make it faster.
- * May return SCORE_MAXMAX when the score is higher than "limit".
- *
- * This uses a stack for the edits still to be tried.
- * The idea comes from Aspell leditdist.cpp.  Rewritten in C and added support
- * for multi-byte characters.
- */
-    static int
-spell_edit_score_limit(
-    slang_T    *slang,
-    char_u     *badword,
-    char_u     *goodword,
-    int                limit)
-{
-    limitscore_T    stack[10];         /* allow for over 3 * 2 edits */
-    int                    stackidx;
-    int                    bi, gi;
-    int                    bi2, gi2;
-    int                    bc, gc;
-    int                    score;
-    int                    score_off;
-    int                    minscore;
-    int                    round;
-
-    /* Multi-byte characters require a bit more work, use a different function
-     * to avoid testing "has_mbyte" quite often. */
-    if (has_mbyte)
-       return spell_edit_score_limit_w(slang, badword, goodword, limit);
-
-    /*
-     * The idea is to go from start to end over the words.  So long as
-     * characters are equal just continue, this always gives the lowest score.
-     * When there is a difference try several alternatives.  Each alternative
-     * increases "score" for the edit distance.  Some of the alternatives are
-     * pushed unto a stack and tried later, some are tried right away.  At the
-     * end of the word the score for one alternative is known.  The lowest
-     * possible score is stored in "minscore".
-     */
-    stackidx = 0;
-    bi = 0;
-    gi = 0;
-    score = 0;
-    minscore = limit + 1;
-
-    for (;;)
-    {
-       /* Skip over an equal part, score remains the same. */
-       for (;;)
-       {
-           bc = badword[bi];
-           gc = goodword[gi];
-           if (bc != gc)       /* stop at a char that's different */
-               break;
-           if (bc == NUL)      /* both words end */
-           {
-               if (score < minscore)
-                   minscore = score;
-               goto pop;       /* do next alternative */
-           }
-           ++bi;
-           ++gi;
-       }
-
-       if (gc == NUL)    /* goodword ends, delete badword chars */
-       {
-           do
-           {
-               if ((score += SCORE_DEL) >= minscore)
-                   goto pop;       /* do next alternative */
-           } while (badword[++bi] != NUL);
-           minscore = score;
-       }
-       else if (bc == NUL) /* badword ends, insert badword chars */
-       {
-           do
-           {
-               if ((score += SCORE_INS) >= minscore)
-                   goto pop;       /* do next alternative */
-           } while (goodword[++gi] != NUL);
-           minscore = score;
-       }
-       else                    /* both words continue */
-       {
-           /* If not close to the limit, perform a change.  Only try changes
-            * that may lead to a lower score than "minscore".
-            * round 0: try deleting a char from badword
-            * round 1: try inserting a char in badword */
-           for (round = 0; round <= 1; ++round)
-           {
-               score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
-               if (score_off < minscore)
-               {
-                   if (score_off + SCORE_EDIT_MIN >= minscore)
-                   {
-                       /* Near the limit, rest of the words must match.  We
-                        * can check that right now, no need to push an item
-                        * onto the stack. */
-                       bi2 = bi + 1 - round;
-                       gi2 = gi + round;
-                       while (goodword[gi2] == badword[bi2])
-                       {
-                           if (goodword[gi2] == NUL)
-                           {
-                               minscore = score_off;
-                               break;
-                           }
-                           ++bi2;
-                           ++gi2;
-                       }
-                   }
-                   else
-                   {
-                       /* try deleting/inserting a character later */
-                       stack[stackidx].badi = bi + 1 - round;
-                       stack[stackidx].goodi = gi + round;
-                       stack[stackidx].score = score_off;
-                       ++stackidx;
-                   }
-               }
-           }
-
-           if (score + SCORE_SWAP < minscore)
-           {
-               /* If swapping two characters makes a match then the
-                * substitution is more expensive, thus there is no need to
-                * try both. */
-               if (gc == badword[bi + 1] && bc == goodword[gi + 1])
-               {
-                   /* Swap two characters, that is: skip them. */
-                   gi += 2;
-                   bi += 2;
-                   score += SCORE_SWAP;
-                   continue;
-               }
-           }
-
-           /* Substitute one character for another which is the same
-            * thing as deleting a character from both goodword and badword.
-            * Use a better score when there is only a case difference. */
-           if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
-               score += SCORE_ICASE;
-           else
-           {
-               /* For a similar character use SCORE_SIMILAR. */
-               if (slang != NULL
-                       && slang->sl_has_map
-                       && similar_chars(slang, gc, bc))
-                   score += SCORE_SIMILAR;
-               else
-                   score += SCORE_SUBST;
-           }
-
-           if (score < minscore)
-           {
-               /* Do the substitution. */
-               ++gi;
-               ++bi;
-               continue;
-           }
-       }
-pop:
-       /*
-        * Get here to try the next alternative, pop it from the stack.
-        */
-       if (stackidx == 0)              /* stack is empty, finished */
-           break;
-
-       /* pop an item from the stack */
-       --stackidx;
-       gi = stack[stackidx].goodi;
-       bi = stack[stackidx].badi;
-       score = stack[stackidx].score;
-    }
-
-    /* When the score goes over "limit" it may actually be much higher.
-     * Return a very large number to avoid going below the limit when giving a
-     * bonus. */
-    if (minscore > limit)
-       return SCORE_MAXMAX;
-    return minscore;
-}
-
-/*
- * Multi-byte version of spell_edit_score_limit().
- * Keep it in sync with the above!
- */
-    static int
-spell_edit_score_limit_w(
-    slang_T    *slang,
-    char_u     *badword,
-    char_u     *goodword,
-    int                limit)
-{
-    limitscore_T    stack[10];         /* allow for over 3 * 2 edits */
-    int                    stackidx;
-    int                    bi, gi;
-    int                    bi2, gi2;
-    int                    bc, gc;
-    int                    score;
-    int                    score_off;
-    int                    minscore;
-    int                    round;
-    char_u         *p;
-    int                    wbadword[MAXWLEN];
-    int                    wgoodword[MAXWLEN];
-
-    /* Get the characters from the multi-byte strings and put them in an
-     * int array for easy access. */
-    bi = 0;
-    for (p = badword; *p != NUL; )
-       wbadword[bi++] = mb_cptr2char_adv(&p);
-    wbadword[bi++] = 0;
-    gi = 0;
-    for (p = goodword; *p != NUL; )
-       wgoodword[gi++] = mb_cptr2char_adv(&p);
-    wgoodword[gi++] = 0;
-
-    /*
-     * The idea is to go from start to end over the words.  So long as
-     * characters are equal just continue, this always gives the lowest score.
-     * When there is a difference try several alternatives.  Each alternative
-     * increases "score" for the edit distance.  Some of the alternatives are
-     * pushed unto a stack and tried later, some are tried right away.  At the
-     * end of the word the score for one alternative is known.  The lowest
-     * possible score is stored in "minscore".
-     */
-    stackidx = 0;
-    bi = 0;
-    gi = 0;
-    score = 0;
-    minscore = limit + 1;
-
-    for (;;)
-    {
-       /* Skip over an equal part, score remains the same. */
-       for (;;)
-       {
-           bc = wbadword[bi];
-           gc = wgoodword[gi];
-
-           if (bc != gc)       /* stop at a char that's different */
-               break;
-           if (bc == NUL)      /* both words end */
-           {
-               if (score < minscore)
-                   minscore = score;
-               goto pop;       /* do next alternative */
-           }
-           ++bi;
-           ++gi;
-       }
-
-       if (gc == NUL)    /* goodword ends, delete badword chars */
-       {
-           do
-           {
-               if ((score += SCORE_DEL) >= minscore)
-                   goto pop;       /* do next alternative */
-           } while (wbadword[++bi] != NUL);
-           minscore = score;
-       }
-       else if (bc == NUL) /* badword ends, insert badword chars */
-       {
-           do
-           {
-               if ((score += SCORE_INS) >= minscore)
-                   goto pop;       /* do next alternative */
-           } while (wgoodword[++gi] != NUL);
-           minscore = score;
-       }
-       else                    /* both words continue */
-       {
-           /* If not close to the limit, perform a change.  Only try changes
-            * that may lead to a lower score than "minscore".
-            * round 0: try deleting a char from badword
-            * round 1: try inserting a char in badword */
-           for (round = 0; round <= 1; ++round)
-           {
-               score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
-               if (score_off < minscore)
-               {
-                   if (score_off + SCORE_EDIT_MIN >= minscore)
-                   {
-                       /* Near the limit, rest of the words must match.  We
-                        * can check that right now, no need to push an item
-                        * onto the stack. */
-                       bi2 = bi + 1 - round;
-                       gi2 = gi + round;
-                       while (wgoodword[gi2] == wbadword[bi2])
-                       {
-                           if (wgoodword[gi2] == NUL)
-                           {
-                               minscore = score_off;
-                               break;
-                           }
-                           ++bi2;
-                           ++gi2;
-                       }
-                   }
-                   else
-                   {
-                       /* try deleting a character from badword later */
-                       stack[stackidx].badi = bi + 1 - round;
-                       stack[stackidx].goodi = gi + round;
-                       stack[stackidx].score = score_off;
-                       ++stackidx;
-                   }
-               }
-           }
-
-           if (score + SCORE_SWAP < minscore)
-           {
-               /* If swapping two characters makes a match then the
-                * substitution is more expensive, thus there is no need to
-                * try both. */
-               if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1])
-               {
-                   /* Swap two characters, that is: skip them. */
-                   gi += 2;
-                   bi += 2;
-                   score += SCORE_SWAP;
-                   continue;
-               }
-           }
-
-           /* Substitute one character for another which is the same
-            * thing as deleting a character from both goodword and badword.
-            * Use a better score when there is only a case difference. */
-           if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
-               score += SCORE_ICASE;
-           else
-           {
-               /* For a similar character use SCORE_SIMILAR. */
-               if (slang != NULL
-                       && slang->sl_has_map
-                       && similar_chars(slang, gc, bc))
-                   score += SCORE_SIMILAR;
-               else
-                   score += SCORE_SUBST;
-           }
-
-           if (score < minscore)
-           {
-               /* Do the substitution. */
-               ++gi;
-               ++bi;
-               continue;
-           }
-       }
-pop:
-       /*
-        * Get here to try the next alternative, pop it from the stack.
-        */
-       if (stackidx == 0)              /* stack is empty, finished */
-           break;
-
-       /* pop an item from the stack */
-       --stackidx;
-       gi = stack[stackidx].goodi;
-       bi = stack[stackidx].badi;
-       score = stack[stackidx].score;
-    }
-
-    /* When the score goes over "limit" it may actually be much higher.
-     * Return a very large number to avoid going below the limit when giving a
-     * bonus. */
-    if (minscore > limit)
-       return SCORE_MAXMAX;
-    return minscore;
-}
-
 /*
  * ":spellinfo"
  */
index 380e5ecc0028bdaf74c5188c62847fd9cf47dd9a..dc102bc507f4fdb448cb604d82d8cf01ce0a9177 100644 (file)
@@ -151,6 +151,8 @@ struct slang_S
 #define WF_FIXCAP   0x40       /* keep-case word, allcap not allowed */
 #define WF_KEEPCAP  0x80       /* keep-case word */
 
+#define WF_CAPMASK (WF_ONECAP | WF_ALLCAP | WF_KEEPCAP | WF_FIXCAP)
+
 /* for <flags2>, shifted up one byte to be used in wn_flags */
 #define WF_HAS_AFF  0x0100     /* word includes affix */
 #define WF_NEEDCOMP 0x0200     /* word only valid in compound */
@@ -302,4 +304,18 @@ SPELL_EXTERN int     did_set_spelltab;
 #define SPELL_ADD_GOOD 0
 #define SPELL_ADD_BAD  1
 #define SPELL_ADD_RARE 2
+
+typedef struct wordcount_S
+{
+    short_u    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
+
+/* Remember what "z?" replaced. */
+SPELL_EXTERN char_u    *repl_from SPELL_INIT(= NULL);
+SPELL_EXTERN char_u    *repl_to SPELL_INIT(= NULL);
 #endif
diff --git a/src/spellsuggest.c b/src/spellsuggest.c
new file mode 100644 (file)
index 0000000..41f4a73
--- /dev/null
@@ -0,0 +1,4452 @@
+/* vi:set ts=8 sts=4 sw=4 noet:
+ *
+ * VIM - Vi IMproved   by Bram Moolenaar
+ *
+ * Do ":help uganda"  in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ * See README.txt for an overview of the Vim source code.
+ */
+
+/*
+ * spellsuggest.c: functions for spelling suggestions
+ */
+
+#include "vim.h"
+
+#if defined(FEAT_SPELL) || defined(PROTO)
+
+/*
+ * 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)
+
+// only used for su_badflags
+#define WF_MIXCAP   0x20       // mix of upper and lower case: macaRONI
+
+/*
+ * 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 suggest_S
+{
+    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
+    int                st_salscore;    // st_score is for soundalike
+    int                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 then 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
+
+/*
+ * For finding suggestions: At each node in the tree these states are tried:
+ */
+typedef enum
+{
+    STATE_START = 0,   // At start of node check for NUL bytes (goodword
+                       // ends); if badword ends there is a match, otherwise
+                       // try splitting word.
+    STATE_NOPREFIX,    // try without prefix
+    STATE_SPLITUNDO,   // Undo splitting.
+    STATE_ENDNUL,      // Past NUL bytes at start of the node.
+    STATE_PLAIN,       // Use each byte of the node.
+    STATE_DEL,         // Delete a byte from the bad word.
+    STATE_INS_PREP,    // Prepare for inserting bytes.
+    STATE_INS,         // Insert a byte in the bad word.
+    STATE_SWAP,                // Swap two bytes.
+    STATE_UNSWAP,      // Undo swap two characters.
+    STATE_SWAP3,       // Swap two characters over three.
+    STATE_UNSWAP3,     // Undo Swap two characters over three.
+    STATE_UNROT3L,     // Undo rotate three characters left
+    STATE_UNROT3R,     // Undo rotate three characters right
+    STATE_REP_INI,     // Prepare for using REP items.
+    STATE_REP,         // Use matching REP items from the .aff file.
+    STATE_REP_UNDO,    // Undo a REP item replacement.
+    STATE_FINAL                // End of this node.
+} state_T;
+
+/*
+ * Struct to keep the state at each level in suggest_try_change().
+ */
+typedef struct trystate_S
+{
+    state_T    ts_state;       // state at this level, STATE_
+    int                ts_score;       // score
+    idx_T      ts_arridx;      // index in tree array, start of node
+    short      ts_curi;        // index in list of child nodes
+    char_u     ts_fidx;        // index in fword[], case-folded bad word
+    char_u     ts_fidxtry;     // ts_fidx at which bytes may be changed
+    char_u     ts_twordlen;    // valid length of tword[]
+    char_u     ts_prefixdepth; // stack depth for end of prefix or
+                               // PFD_PREFIXTREE or PFD_NOPREFIX
+    char_u     ts_flags;       // TSF_ flags
+    char_u     ts_tcharlen;    // number of bytes in tword character
+    char_u     ts_tcharidx;    // current byte index in tword character
+    char_u     ts_isdiff;      // DIFF_ values
+    char_u     ts_fcharstart;  // index in fword where badword char started
+    char_u     ts_prewordlen;  // length of word in "preword[]"
+    char_u     ts_splitoff;    // index in "tword" after last split
+    char_u     ts_splitfidx;   // "ts_fidx" at word split
+    char_u     ts_complen;     // nr of compound words used
+    char_u     ts_compsplit;   // index for "compflags" where word was spit
+    char_u     ts_save_badflags;   // su_badflags saved here
+    char_u     ts_delidx;      // index in fword for char that was deleted,
+                               // valid when "ts_flags" has TSF_DIDDEL
+} trystate_T;
+
+// 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
+
+static void spell_find_suggest(char_u *badptr, int badlen, suginfo_T *su, int maxcount, int banbadword, int need_cap, int interactive);
+#ifdef FEAT_EVAL
+static void spell_suggest_expr(suginfo_T *su, char_u *expr);
+#endif
+static void spell_suggest_file(suginfo_T *su, char_u *fname);
+static void spell_suggest_intern(suginfo_T *su, int interactive);
+static void spell_find_cleanup(suginfo_T *su);
+static void suggest_try_special(suginfo_T *su);
+static void suggest_try_change(suginfo_T *su);
+static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, int soundfold);
+static void go_deeper(trystate_T *stack, int depth, int score_add);
+static void find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword);
+static void score_comp_sal(suginfo_T *su);
+static void score_combine(suginfo_T *su);
+static int stp_sal_score(suggest_T *stp, suginfo_T *su, slang_T *slang, char_u *badsound);
+static void suggest_try_soundalike_prep(void);
+static void suggest_try_soundalike(suginfo_T *su);
+static void suggest_try_soundalike_finish(void);
+static void add_sound_suggest(suginfo_T *su, char_u *goodword, int score, langp_T *lp);
+static int soundfold_find(slang_T *slang, char_u *word);
+static int similar_chars(slang_T *slang, int c1, int c2);
+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);
+static void check_suggestions(suginfo_T *su, garray_T *gap);
+static void add_banned(suginfo_T *su, char_u *word);
+static void rescore_suggestions(suginfo_T *su);
+static void rescore_one(suginfo_T *su, suggest_T *stp);
+static int cleanup_suggestions(garray_T *gap, int maxscore, int keep);
+static int soundalike_score(char_u *goodsound, char_u *badsound);
+static int spell_edit_score(slang_T *slang, char_u *badword, char_u *goodword);
+static int spell_edit_score_limit(slang_T *slang, char_u *badword, char_u *goodword, int limit);
+static int spell_edit_score_limit_w(slang_T *slang, char_u *badword, char_u *goodword, int limit);
+
+/*
+ * Return 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 int
+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)
+    {
+       int v;
+
+       compflags[sp->ts_complen] = flag;
+       compflags[sp->ts_complen + 1] = NUL;
+       v = match_compoundrule(slang, compflags + sp->ts_compsplit);
+       compflags[sp->ts_complen] = NUL;
+       return v;
+    }
+
+    return TRUE;
+}
+
+/*
+ * Adjust the score of common words.
+ */
+    static int
+score_wordcount_adj(
+    slang_T    *slang,
+    int                score,
+    char_u     *word,
+    int                split)      // word was split, less bonus
+{
+    hashitem_T *hi;
+    wordcount_T        *wc;
+    int                bonus;
+    int                newscore;
+
+    hi = hash_find(&slang->sl_wordcount, 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;
+}
+
+/*
+ * 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)
+{
+    int                flags = captype(word, end);
+    int                c;
+    int                l, u;
+    int                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 = PTR2CHAR(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;
+}
+
+/*
+ * 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;
+}
+
+// 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(&p, buf, MAXPATHL, ",");
+
+       f = 0;
+       if (VIM_ISDIGIT(*buf))
+       {
+           s = buf;
+           sps_limit = getdigits(&s);
+           if (*s != NUL && !VIM_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                i;
+    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;
+
+    if (no_spell_checking(curwin))
+       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();
+    }
+    // Find the start of the badly spelled word.
+    else 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 = ml_get_curline();
+       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(ml_get_curline());
+    if (line == NULL)
+       goto skip;
+
+    // Get the list of suggestions.  Limit to 'lines' - 2 or the number in
+    // 'spellsuggest', whatever is smaller.
+    if (sps_limit > (int)Rows - 2)
+       limit = (int)Rows - 2;
+    else
+       limit = sps_limit;
+    spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
+                                                       TRUE, need_cap, TRUE);
+
+    if (sug.su_ga.ga_len == 0)
+       msg(_("Sorry, no suggestions"));
+    else if (count > 0)
+    {
+       if (count > sug.su_ga.ga_len)
+           smsg(_("Sorry, only %ld suggestions"),
+                                                     (long)sug.su_ga.ga_len);
+    }
+    else
+    {
+       VIM_CLEAR(repl_from);
+       VIM_CLEAR(repl_to);
+
+#ifdef FEAT_RIGHTLEFT
+       // When 'rightleft' is set the list is drawn right-left.
+       cmdmsg_rl = curwin->w_p_rl;
+       if (cmdmsg_rl)
+           msg_col = Columns - 1;
+#endif
+
+       // 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);
+#ifdef FEAT_RIGHTLEFT
+       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);
+       }
+#endif
+       msg_puts((char *)IObuff);
+       msg_clr_eos();
+       msg_putchar('\n');
+
+       msg_scroll = TRUE;
+       for (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.
+           vim_strncpy(wcopy, stp->st_word, MAXWLEN);
+           if (sug.su_badlen > stp->st_orglen)
+               vim_strncpy(wcopy + stp->st_wordlen,
+                                              sug.su_badptr + stp->st_orglen,
+                                             sug.su_badlen - stp->st_orglen);
+           vim_snprintf((char *)IObuff, IOSIZE, "%2d", i + 1);
+#ifdef FEAT_RIGHTLEFT
+           if (cmdmsg_rl)
+               rl_mirror(IObuff);
+#endif
+           msg_puts((char *)IObuff);
+
+           vim_snprintf((char *)IObuff, IOSIZE, " \"%s\"", wcopy);
+           msg_puts((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((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);
+#ifdef FEAT_RIGHTLEFT
+               if (cmdmsg_rl)
+                   // Mirror the numbers, but keep the leading space.
+                   rl_mirror(IObuff + 1);
+#endif
+               msg_advance(30);
+               msg_puts((char *)IObuff);
+           }
+           msg_putchar('\n');
+       }
+
+#ifdef FEAT_RIGHTLEFT
+       cmdmsg_rl = FALSE;
+       msg_col = 0;
+#endif
+       // Ask for choice.
+       selected = prompt_for_number(&mouse_used);
+       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.
+       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, 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, stp->st_orglen);
+           repl_to = vim_strsave(stp->st_word);
+       }
+
+       // Replace the word.
+       p = alloc(STRLEN(line) - stp->st_orglen + stp->st_wordlen + 1);
+       if (p != NULL)
+       {
+           c = (int)(sug.su_badptr - line);
+           mch_memmove(p, line, c);
+           STRCPY(p + c, stp->st_word);
+           STRCAT(p, sug.su_badptr + stp->st_orglen);
+           ml_replace(curwin->w_cursor.lnum, p, FALSE);
+           curwin->w_cursor.col = c;
+
+           // For redo we use a change-word command.
+           ResetRedobuff();
+           AppendToRedobuff((char_u *)"ciw");
+           AppendToRedobuffLit(p + c,
+                           stp->st_wordlen + sug.su_badlen - stp->st_orglen);
+           AppendCharToRedobuff(ESC);
+
+           // After this "p" may be invalid.
+           changed_bytes(curwin->w_cursor.lnum, c);
+       }
+    }
+    else
+       curwin->w_cursor = prev_cursor;
+
+    spell_find_cleanup(&sug);
+skip:
+    vim_free(line);
+}
+
+/*
+ * Find spell suggestions for "word".  Return them in the growarray "*gap" as
+ * a list of allocated strings.
+ */
+    void
+spell_suggest_list(
+    garray_T   *gap,
+    char_u     *word,
+    int                maxcount,       // maximum nr of suggestions
+    int                need_cap,       // 'spellcapcheck' matched
+    int                interactive)
+{
+    suginfo_T  sug;
+    int                i;
+    suggest_T  *stp;
+    char_u     *wcopy;
+
+    spell_find_suggest(word, 0, &sug, maxcount, FALSE, need_cap, interactive);
+
+    // Make room in "gap".
+    ga_init2(gap, sizeof(char_u *), sug.su_ga.ga_len + 1);
+    if (ga_grow(gap, sug.su_ga.ga_len) == OK)
+    {
+       for (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 = alloc(stp->st_wordlen
+                     + (unsigned)STRLEN(sug.su_badptr + stp->st_orglen) + 1);
+           if (wcopy == NULL)
+               break;
+           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.
+ */
+    static void
+spell_find_suggest(
+    char_u     *badptr,
+    int                badlen,         // length of bad word or 0 if unknown
+    suginfo_T  *su,
+    int                maxcount,
+    int                banbadword,     // don't include badword in suggestions
+    int                need_cap,       // word should start with capital
+    int                interactive)
+{
+    hlf_T      attr = HLF_COUNT;
+    char_u     buf[MAXPATHL];
+    char_u     *p;
+    int                do_combine = FALSE;
+    char_u     *sps_copy;
+#ifdef FEAT_EVAL
+    static int expr_busy = FALSE;
+#endif
+    int                c;
+    int                i;
+    langp_T    *lp;
+
+    // Set the info in "*su".
+    vim_memset(su, 0, sizeof(suginfo_T));
+    ga_init2(&su->su_ga, (int)sizeof(suggest_T), 10);
+    ga_init2(&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
+       su->su_badlen = spell_check(curwin, su->su_badptr, &attr, NULL, FALSE);
+    su->su_maxcount = maxcount;
+    su->su_maxscore = SCORE_MAXINIT;
+
+    if (su->su_badlen >= MAXWLEN)
+       su->su_badlen = MAXWLEN - 1;    // just in case
+    vim_strncpy(su->su_badword, su->su_badptr, su->su_badlen);
+    (void)spell_casefold(su->su_badptr, su->su_badlen,
+                                                   su->su_fbadword, MAXWLEN);
+    // TODO: 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 (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,
+                                                         su->su_sal_badword);
+
+    // If the word is not capitalised and spell_check() doesn't consider the
+    // word to be bad then it might need to be capitalised.  Add a suggestion
+    // for that.
+    c = PTR2CHAR(su->su_badptr);
+    if (!SPELL_ISUPPER(c) && attr == HLF_COUNT)
+    {
+       make_case_word(su->su_badword, buf, WF_ONECAP);
+       add_suggestion(su, &su->su_ga, buf, su->su_badlen, SCORE_ICASE,
+                                             0, TRUE, su->su_sallang, FALSE);
+    }
+
+    // Ban the bad word itself.  It may appear in another region.
+    if (banbadword)
+       add_banned(su, su->su_badword);
+
+    // Make a copy of 'spellsuggest', because the expression may change it.
+    sps_copy = vim_strsave(p_sps);
+    if (sps_copy == NULL)
+       return;
+
+    // Loop over the items in 'spellsuggest'.
+    for (p = sps_copy; *p != NUL; )
+    {
+       copy_option_part(&p, buf, MAXPATHL, ",");
+
+       if (STRNCMP(buf, "expr:", 5) == 0)
+       {
+#ifdef FEAT_EVAL
+           // Evaluate an expression.  Skip this when called recursively,
+           // when using spellsuggest() in the expression.
+           if (!expr_busy)
+           {
+               expr_busy = TRUE;
+               spell_suggest_expr(su, buf + 5);
+               expr_busy = FALSE;
+           }
+#endif
+       }
+       else if (STRNCMP(buf, "file:", 5) == 0)
+           // Use list of suggestions in a file.
+           spell_suggest_file(su, buf + 5);
+       else
+       {
+           // Use internal method.
+           spell_suggest_intern(su, interactive);
+           if (sps_flags & SPS_DOUBLE)
+               do_combine = TRUE;
+       }
+    }
+
+    vim_free(sps_copy);
+
+    if (do_combine)
+       // Combine the two list of suggestions.  This must be done last,
+       // because sorting changes the order again.
+       score_combine(su);
+}
+
+#ifdef FEAT_EVAL
+/*
+ * Find suggestions by evaluating expression "expr".
+ */
+    static void
+spell_suggest_expr(suginfo_T *su, char_u *expr)
+{
+    list_T     *list;
+    listitem_T *li;
+    int                score;
+    char_u     *p;
+
+    // The work is split up in a few parts to avoid having to export
+    // suginfo_T.
+    // First evaluate the expression and get the resulting list.
+    list = eval_spell_expr(su->su_badword, expr);
+    if (list != NULL)
+    {
+       // Loop over the items in the list.
+       for (li = list->lv_first; li != NULL; li = li->li_next)
+           if (li->li_tv.v_type == VAR_LIST)
+           {
+               // Get the word and the score from the items.
+               score = get_spellword(li->li_tv.vval.v_list, &p);
+               if (score >= 0 && score <= su->su_maxscore)
+                   add_suggestion(su, &su->su_ga, p, su->su_badlen,
+                                      score, 0, TRUE, su->su_sallang, FALSE);
+           }
+       list_unref(list);
+    }
+
+    // Remove bogus suggestions, sort and truncate at "maxcount".
+    check_suggestions(su, &su->su_ga);
+    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+}
+#endif
+
+/*
+ * Find suggestions in file "fname".  Used for "file:" in 'spellsuggest'.
+ */
+    static void
+spell_suggest_file(suginfo_T *su, char_u *fname)
+{
+    FILE       *fd;
+    char_u     line[MAXWLEN * 2];
+    char_u     *p;
+    int                len;
+    char_u     cword[MAXWLEN];
+
+    // Open the file.
+    fd = mch_fopen((char *)fname, "r");
+    if (fd == NULL)
+    {
+       semsg(_(e_notopen), fname);
+       return;
+    }
+
+    // Read it line by line.
+    while (!vim_fgets(line, MAXWLEN * 2, fd) && !got_int)
+    {
+       line_breakcheck();
+
+       p = vim_strchr(line, '/');
+       if (p == NULL)
+           continue;       // No Tab found, just skip the line.
+       *p++ = NUL;
+       if (STRICMP(su->su_badword, line) == 0)
+       {
+           // Match!  Isolate the good word, until CR or NL.
+           for (len = 0; p[len] >= ' '; ++len)
+               ;
+           p[len] = NUL;
+
+           // If the suggestion doesn't have specific case duplicate the case
+           // of the bad word.
+           if (captype(p, NULL) == 0)
+           {
+               make_case_word(p, cword, su->su_badflags);
+               p = cword;
+           }
+
+           add_suggestion(su, &su->su_ga, p, su->su_badlen,
+                                 SCORE_FILE, 0, TRUE, su->su_sallang, FALSE);
+       }
+    }
+
+    fclose(fd);
+
+    // Remove bogus suggestions, sort and truncate at "maxcount".
+    check_suggestions(su, &su->su_ga);
+    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+}
+
+/*
+ * Find suggestions for the internal method indicated by "sps_flags".
+ */
+    static void
+spell_suggest_intern(suginfo_T *su, int interactive)
+{
+    // Load the .sug file(s) that are available and not done yet.
+    suggest_load_files();
+
+    // 1. Try special cases, such as repeating a word: "the the" -> "the".
+    //
+    // Set a maximum score to limit the combination of operations that is
+    // tried.
+    suggest_try_special(su);
+
+    // 2. Try inserting/deleting/swapping/changing a letter, use REP entries
+    //    from the .aff file and inserting a space (split the word).
+    suggest_try_change(su);
+
+    // For the resulting top-scorers compute the sound-a-like score.
+    if (sps_flags & SPS_DOUBLE)
+       score_comp_sal(su);
+
+    // 3. Try finding sound-a-like words.
+    if ((sps_flags & SPS_FAST) == 0)
+    {
+       if (sps_flags & SPS_BEST)
+           // Adjust the word score for the suggestions found so far for how
+           // they sounds like.
+           rescore_suggestions(su);
+
+       // While going through the soundfold tree "su_maxscore" is the score
+       // for the soundfold word, limits the changes that are being tried,
+       // and "su_sfmaxscore" the rescored score, which is set by
+       // cleanup_suggestions().
+       // First find words with a small edit distance, because this is much
+       // faster and often already finds the top-N suggestions.  If we didn't
+       // find many suggestions try again with a higher edit distance.
+       // "sl_sounddone" is used to avoid doing the same word twice.
+       suggest_try_soundalike_prep();
+       su->su_maxscore = SCORE_SFMAX1;
+       su->su_sfmaxscore = SCORE_MAXINIT * 3;
+       suggest_try_soundalike(su);
+       if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su))
+       {
+           // We didn't find enough matches, try again, allowing more
+           // changes to the soundfold word.
+           su->su_maxscore = SCORE_SFMAX2;
+           suggest_try_soundalike(su);
+           if (su->su_ga.ga_len < SUG_CLEAN_COUNT(su))
+           {
+               // Still didn't find enough matches, try again, allowing even
+               // more changes to the soundfold word.
+               su->su_maxscore = SCORE_SFMAX3;
+               suggest_try_soundalike(su);
+           }
+       }
+       su->su_maxscore = su->su_sfmaxscore;
+       suggest_try_soundalike_finish();
+    }
+
+    // When CTRL-C was hit while searching do show the results.  Only clear
+    // got_int when using a command, not for spellsuggest().
+    ui_breakcheck();
+    if (interactive && got_int)
+    {
+       (void)vgetc();
+       got_int = FALSE;
+    }
+
+    if ((sps_flags & SPS_DOUBLE) == 0 && su->su_ga.ga_len != 0)
+    {
+       if (sps_flags & SPS_BEST)
+           // Adjust the word score for how it sounds like.
+           rescore_suggestions(su);
+
+       // Remove bogus suggestions, sort and truncate at "maxcount".
+       check_suggestions(su, &su->su_ga);
+       (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+    }
+}
+
+/*
+ * Free the info put in "*su" by spell_find_suggest().
+ */
+    static void
+spell_find_cleanup(suginfo_T *su)
+{
+    int                i;
+
+    // Free the suggestions.
+    for (i = 0; i < su->su_ga.ga_len; ++i)
+       vim_free(SUG(su->su_ga, i).st_word);
+    ga_clear(&su->su_ga);
+    for (i = 0; i < su->su_sga.ga_len; ++i)
+       vim_free(SUG(su->su_sga, i).st_word);
+    ga_clear(&su->su_sga);
+
+    // Free the banned words.
+    hash_clear_all(&su->su_banned, 0);
+}
+
+/*
+ * Try finding suggestions by recognizing specific situations.
+ */
+    static void
+suggest_try_special(suginfo_T *su)
+{
+    char_u     *p;
+    size_t     len;
+    int                c;
+    char_u     word[MAXWLEN];
+
+    // Recognize a word that is repeated: "the the".
+    p = skiptowhite(su->su_fbadword);
+    len = p - su->su_fbadword;
+    p = skipwhite(p);
+    if (STRLEN(p) == len && STRNCMP(su->su_fbadword, p, len) == 0)
+    {
+       // Include badflags: if the badword is onecap or allcap
+       // use that for the goodword too: "The the" -> "The".
+       c = su->su_fbadword[len];
+       su->su_fbadword[len] = NUL;
+       make_case_word(su->su_fbadword, word, su->su_badflags);
+       su->su_fbadword[len] = c;
+
+       // Give a soundalike score of 0, compute the score as if deleting one
+       // character.
+       add_suggestion(su, &su->su_ga, word, su->su_badlen,
+                      RESCORE(SCORE_REP, 0), 0, TRUE, su->su_sallang, FALSE);
+    }
+}
+
+/*
+ * Change the 0 to 1 to measure how much time is spent in each state.
+ * Output is dumped in "suggestprof".
+ */
+#if 0
+# define SUGGEST_PROFILE
+proftime_T current;
+proftime_T total;
+proftime_T times[STATE_FINAL + 1];
+long counts[STATE_FINAL + 1];
+
+    static void
+prof_init(void)
+{
+    for (int i = 0; i <= STATE_FINAL; ++i)
+    {
+       profile_zero(&times[i]);
+       counts[i] = 0;
+    }
+    profile_start(&current);
+    profile_start(&total);
+}
+
+// call before changing state
+    static void
+prof_store(state_T state)
+{
+    profile_end(&current);
+    profile_add(&times[state], &current);
+    ++counts[state];
+    profile_start(&current);
+}
+# define PROF_STORE(state) prof_store(state);
+
+    static void
+prof_report(char *name)
+{
+    FILE *fd = fopen("suggestprof", "a");
+
+    profile_end(&total);
+    fprintf(fd, "-----------------------\n");
+    fprintf(fd, "%s: %s\n", name, profile_msg(&total));
+    for (int i = 0; i <= STATE_FINAL; ++i)
+       fprintf(fd, "%d: %s (%ld)\n", i, profile_msg(&times[i]), counts[i]);
+    fclose(fd);
+}
+#else
+# define PROF_STORE(state)
+#endif
+
+/*
+ * Try finding suggestions by adding/removing/swapping letters.
+ */
+    static void
+suggest_try_change(suginfo_T *su)
+{
+    char_u     fword[MAXWLEN];     // copy of the bad word, case-folded
+    int                n;
+    char_u     *p;
+    int                lpi;
+    langp_T    *lp;
+
+    // We make a copy of the case-folded bad word, so that we can modify it
+    // to find matches (esp. REP items).  Append some more text, changing
+    // chars after the bad word may help.
+    STRCPY(fword, su->su_fbadword);
+    n = (int)STRLEN(fword);
+    p = su->su_badptr + su->su_badlen;
+    (void)spell_casefold(p, (int)STRLEN(p), fword + n, MAXWLEN - n);
+
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+
+       // If reloading a spell file fails it's still in the list but
+       // everything has been cleared.
+       if (lp->lp_slang->sl_fbyts == NULL)
+           continue;
+
+       // Try it for this language.  Will add possible suggestions.
+#ifdef SUGGEST_PROFILE
+       prof_init();
+#endif
+       suggest_trie_walk(su, lp, fword, FALSE);
+#ifdef SUGGEST_PROFILE
+       prof_report("try_change");
+#endif
+    }
+}
+
+// Check the maximum score, if we go over it we won't try this change.
+#define TRY_DEEPER(su, stack, depth, add) \
+               (stack[depth].ts_score + (add) < su->su_maxscore)
+
+/*
+ * Try finding suggestions by adding/removing/swapping letters.
+ *
+ * This uses a state machine.  At each node in the tree we try various
+ * operations.  When trying if an operation works "depth" is increased and the
+ * stack[] is used to store info.  This allows combinations, thus insert one
+ * character, replace one and delete another.  The number of changes is
+ * limited by su->su_maxscore.
+ *
+ * After implementing this I noticed an article by Kemal Oflazer that
+ * describes something similar: "Error-tolerant Finite State Recognition with
+ * Applications to Morphological Analysis and Spelling Correction" (1996).
+ * The implementation in the article is simplified and requires a stack of
+ * unknown depth.  The implementation here only needs a stack depth equal to
+ * the length of the word.
+ *
+ * This is also used for the sound-folded word, "soundfold" is TRUE then.
+ * The mechanism is the same, but we find a match with a sound-folded word
+ * that comes from one or more original words.  Each of these words may be
+ * added, this is done by add_sound_suggest().
+ * Don't use:
+ *     the prefix tree or the keep-case tree
+ *     "su->su_badlen"
+ *     anything to do with upper and lower case
+ *     anything to do with word or non-word characters ("spell_iswordp()")
+ *     banned words
+ *     word flags (rare, region, compounding)
+ *     word splitting for now
+ *     "similar_chars()"
+ *     use "slang->sl_repsal" instead of "lp->lp_replang->sl_rep"
+ */
+    static void
+suggest_trie_walk(
+    suginfo_T  *su,
+    langp_T    *lp,
+    char_u     *fword,
+    int                soundfold)
+{
+    char_u     tword[MAXWLEN];     // good word collected so far
+    trystate_T stack[MAXWLEN];
+    char_u     preword[MAXWLEN * 3]; // word found with proper case;
+                                     // concatenation of prefix compound
+                                     // words and split word.  NUL terminated
+                                     // when going deeper but not when coming
+                                     // back.
+    char_u     compflags[MAXWLEN];     // compound flags, one for each word
+    trystate_T *sp;
+    int                newscore;
+    int                score;
+    char_u     *byts, *fbyts, *pbyts;
+    idx_T      *idxs, *fidxs, *pidxs;
+    int                depth;
+    int                c, c2, c3;
+    int                n = 0;
+    int                flags;
+    garray_T   *gap;
+    idx_T      arridx;
+    int                len;
+    char_u     *p;
+    fromto_T   *ftp;
+    int                fl = 0, tl;
+    int                repextra = 0;       // extra bytes in fword[] from REP item
+    slang_T    *slang = lp->lp_slang;
+    int                fword_ends;
+    int                goodword_ends;
+#ifdef DEBUG_TRIEWALK
+    // Stores the name of the change made at each level.
+    char_u     changename[MAXWLEN][80];
+#endif
+    int                breakcheckcount = 1000;
+    int                compound_ok;
+
+    // Go through the whole case-fold tree, try changes at each node.
+    // "tword[]" contains the word collected from nodes in the tree.
+    // "fword[]" the word we are trying to match with (initially the bad
+    // word).
+    depth = 0;
+    sp = &stack[0];
+    vim_memset(sp, 0, sizeof(trystate_T));
+    sp->ts_curi = 1;
+
+    if (soundfold)
+    {
+       // Going through the soundfold tree.
+       byts = fbyts = slang->sl_sbyts;
+       idxs = fidxs = slang->sl_sidxs;
+       pbyts = NULL;
+       pidxs = NULL;
+       sp->ts_prefixdepth = PFD_NOPREFIX;
+       sp->ts_state = STATE_START;
+    }
+    else
+    {
+       // When there are postponed prefixes we need to use these first.  At
+       // the end of the prefix we continue in the case-fold tree.
+       fbyts = slang->sl_fbyts;
+       fidxs = slang->sl_fidxs;
+       pbyts = slang->sl_pbyts;
+       pidxs = slang->sl_pidxs;
+       if (pbyts != NULL)
+       {
+           byts = pbyts;
+           idxs = pidxs;
+           sp->ts_prefixdepth = PFD_PREFIXTREE;
+           sp->ts_state = STATE_NOPREFIX;      // try without prefix first
+       }
+       else
+       {
+           byts = fbyts;
+           idxs = fidxs;
+           sp->ts_prefixdepth = PFD_NOPREFIX;
+           sp->ts_state = STATE_START;
+       }
+    }
+
+    // Loop to find all suggestions.  At each round we either:
+    // - For the current state try one operation, advance "ts_curi",
+    //   increase "depth".
+    // - When a state is done go to the next, set "ts_state".
+    // - When all states are tried decrease "depth".
+    while (depth >= 0 && !got_int)
+    {
+       sp = &stack[depth];
+       switch (sp->ts_state)
+       {
+       case STATE_START:
+       case STATE_NOPREFIX:
+           // Start of node: Deal with NUL bytes, which means
+           // tword[] may end here.
+           arridx = sp->ts_arridx;         // current node in the tree
+           len = byts[arridx];             // bytes in this node
+           arridx += sp->ts_curi;          // index of current byte
+
+           if (sp->ts_prefixdepth == PFD_PREFIXTREE)
+           {
+               // Skip over the NUL bytes, we use them later.
+               for (n = 0; n < len && byts[arridx + n] == 0; ++n)
+                   ;
+               sp->ts_curi += n;
+
+               // Always past NUL bytes now.
+               n = (int)sp->ts_state;
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_ENDNUL;
+               sp->ts_save_badflags = su->su_badflags;
+
+               // At end of a prefix or at start of prefixtree: check for
+               // following word.
+               if (byts[arridx] == 0 || n == (int)STATE_NOPREFIX)
+               {
+                   // Set su->su_badflags to the caps type at this position.
+                   // Use the caps type until here for the prefix itself.
+                   if (has_mbyte)
+                       n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
+                   else
+                       n = sp->ts_fidx;
+                   flags = badword_captype(su->su_badptr, su->su_badptr + n);
+                   su->su_badflags = badword_captype(su->su_badptr + n,
+                                              su->su_badptr + su->su_badlen);
+#ifdef DEBUG_TRIEWALK
+                   sprintf(changename[depth], "prefix");
+#endif
+                   go_deeper(stack, depth, 0);
+                   ++depth;
+                   sp = &stack[depth];
+                   sp->ts_prefixdepth = depth - 1;
+                   byts = fbyts;
+                   idxs = fidxs;
+                   sp->ts_arridx = 0;
+
+                   // Move the prefix to preword[] with the right case
+                   // and make find_keepcap_word() works.
+                   tword[sp->ts_twordlen] = NUL;
+                   make_case_word(tword + sp->ts_splitoff,
+                                         preword + sp->ts_prewordlen, flags);
+                   sp->ts_prewordlen = (char_u)STRLEN(preword);
+                   sp->ts_splitoff = sp->ts_twordlen;
+               }
+               break;
+           }
+
+           if (sp->ts_curi > len || byts[arridx] != 0)
+           {
+               // Past bytes in node and/or past NUL bytes.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_ENDNUL;
+               sp->ts_save_badflags = su->su_badflags;
+               break;
+           }
+
+           // End of word in tree.
+           ++sp->ts_curi;              // eat one NUL byte
+
+           flags = (int)idxs[arridx];
+
+           // Skip words with the NOSUGGEST flag.
+           if (flags & WF_NOSUGGEST)
+               break;
+
+           fword_ends = (fword[sp->ts_fidx] == NUL
+                          || (soundfold
+                              ? VIM_ISWHITE(fword[sp->ts_fidx])
+                              : !spell_iswordp(fword + sp->ts_fidx, curwin)));
+           tword[sp->ts_twordlen] = NUL;
+
+           if (sp->ts_prefixdepth <= PFD_NOTSPECIAL
+                                       && (sp->ts_flags & TSF_PREFIXOK) == 0)
+           {
+               // There was a prefix before the word.  Check that the prefix
+               // can be used with this word.
+               // Count the length of the NULs in the prefix.  If there are
+               // none this must be the first try without a prefix.
+               n = stack[sp->ts_prefixdepth].ts_arridx;
+               len = pbyts[n++];
+               for (c = 0; c < len && pbyts[n + c] == 0; ++c)
+                   ;
+               if (c > 0)
+               {
+                   c = valid_word_prefix(c, n, flags,
+                                      tword + sp->ts_splitoff, slang, FALSE);
+                   if (c == 0)
+                       break;
+
+                   // Use the WF_RARE flag for a rare prefix.
+                   if (c & WF_RAREPFX)
+                       flags |= WF_RARE;
+
+                   // Tricky: when checking for both prefix and compounding
+                   // we run into the prefix flag first.
+                   // Remember that it's OK, so that we accept the prefix
+                   // when arriving at a compound flag.
+                   sp->ts_flags |= TSF_PREFIXOK;
+               }
+           }
+
+           // Check NEEDCOMPOUND: can't use word without compounding.  Do try
+           // appending another compound word below.
+           if (sp->ts_complen == sp->ts_compsplit && fword_ends
+                                                    && (flags & WF_NEEDCOMP))
+               goodword_ends = FALSE;
+           else
+               goodword_ends = TRUE;
+
+           p = NULL;
+           compound_ok = TRUE;
+           if (sp->ts_complen > sp->ts_compsplit)
+           {
+               if (slang->sl_nobreak)
+               {
+                   // There was a word before this word.  When there was no
+                   // change in this word (it was correct) add the first word
+                   // as a suggestion.  If this word was corrected too, we
+                   // need to check if a correct word follows.
+                   if (sp->ts_fidx - sp->ts_splitfidx
+                                         == sp->ts_twordlen - sp->ts_splitoff
+                           && STRNCMP(fword + sp->ts_splitfidx,
+                                       tword + sp->ts_splitoff,
+                                        sp->ts_fidx - sp->ts_splitfidx) == 0)
+                   {
+                       preword[sp->ts_prewordlen] = NUL;
+                       newscore = score_wordcount_adj(slang, sp->ts_score,
+                                                preword + sp->ts_prewordlen,
+                                                sp->ts_prewordlen > 0);
+                       // Add the suggestion if the score isn't too bad.
+                       if (newscore <= su->su_maxscore)
+                           add_suggestion(su, &su->su_ga, preword,
+                                   sp->ts_splitfidx - repextra,
+                                   newscore, 0, FALSE,
+                                   lp->lp_sallang, FALSE);
+                       break;
+                   }
+               }
+               else
+               {
+                   // There was a compound word before this word.  If this
+                   // word does not support compounding then give up
+                   // (splitting is tried for the word without compound
+                   // flag).
+                   if (((unsigned)flags >> 24) == 0
+                           || sp->ts_twordlen - sp->ts_splitoff
+                                                      < slang->sl_compminlen)
+                       break;
+                   // For multi-byte chars check character length against
+                   // COMPOUNDMIN.
+                   if (has_mbyte
+                           && slang->sl_compminlen > 0
+                           && mb_charlen(tword + sp->ts_splitoff)
+                                                      < slang->sl_compminlen)
+                       break;
+
+                   compflags[sp->ts_complen] = ((unsigned)flags >> 24);
+                   compflags[sp->ts_complen + 1] = NUL;
+                   vim_strncpy(preword + sp->ts_prewordlen,
+                           tword + sp->ts_splitoff,
+                           sp->ts_twordlen - sp->ts_splitoff);
+
+                   // Verify CHECKCOMPOUNDPATTERN  rules.
+                   if (match_checkcompoundpattern(preword,  sp->ts_prewordlen,
+                                                         &slang->sl_comppat))
+                       compound_ok = FALSE;
+
+                   if (compound_ok)
+                   {
+                       p = preword;
+                       while (*skiptowhite(p) != NUL)
+                           p = skipwhite(skiptowhite(p));
+                       if (fword_ends && !can_compound(slang, p,
+                                               compflags + sp->ts_compsplit))
+                           // Compound is not allowed.  But it may still be
+                           // possible if we add another (short) word.
+                           compound_ok = FALSE;
+                   }
+
+                   // Get pointer to last char of previous word.
+                   p = preword + sp->ts_prewordlen;
+                   MB_PTR_BACK(preword, p);
+               }
+           }
+
+           // Form the word with proper case in preword.
+           // If there is a word from a previous split, append.
+           // For the soundfold tree don't change the case, simply append.
+           if (soundfold)
+               STRCPY(preword + sp->ts_prewordlen, tword + sp->ts_splitoff);
+           else if (flags & WF_KEEPCAP)
+               // Must find the word in the keep-case tree.
+               find_keepcap_word(slang, tword + sp->ts_splitoff,
+                                                preword + sp->ts_prewordlen);
+           else
+           {
+               // Include badflags: If the badword is onecap or allcap
+               // use that for the goodword too.  But if the badword is
+               // allcap and it's only one char long use onecap.
+               c = su->su_badflags;
+               if ((c & WF_ALLCAP)
+                       && su->su_badlen == (*mb_ptr2len)(su->su_badptr))
+                   c = WF_ONECAP;
+               c |= flags;
+
+               // When appending a compound word after a word character don't
+               // use Onecap.
+               if (p != NULL && spell_iswordp_nmw(p, curwin))
+                   c &= ~WF_ONECAP;
+               make_case_word(tword + sp->ts_splitoff,
+                                             preword + sp->ts_prewordlen, c);
+           }
+
+           if (!soundfold)
+           {
+               // Don't use a banned word.  It may appear again as a good
+               // word, thus remember it.
+               if (flags & WF_BANNED)
+               {
+                   add_banned(su, preword + sp->ts_prewordlen);
+                   break;
+               }
+               if ((sp->ts_complen == sp->ts_compsplit
+                           && WAS_BANNED(su, preword + sp->ts_prewordlen))
+                                                  || WAS_BANNED(su, preword))
+               {
+                   if (slang->sl_compprog == NULL)
+                       break;
+                   // the word so far was banned but we may try compounding
+                   goodword_ends = FALSE;
+               }
+           }
+
+           newscore = 0;
+           if (!soundfold)     // soundfold words don't have flags
+           {
+               if ((flags & WF_REGION)
+                           && (((unsigned)flags >> 16) & lp->lp_region) == 0)
+                   newscore += SCORE_REGION;
+               if (flags & WF_RARE)
+                   newscore += SCORE_RARE;
+
+               if (!spell_valid_case(su->su_badflags,
+                                 captype(preword + sp->ts_prewordlen, NULL)))
+                   newscore += SCORE_ICASE;
+           }
+
+           // TODO: how about splitting in the soundfold tree?
+           if (fword_ends
+                   && goodword_ends
+                   && sp->ts_fidx >= sp->ts_fidxtry
+                   && compound_ok)
+           {
+               // The badword also ends: add suggestions.
+#ifdef DEBUG_TRIEWALK
+               if (soundfold && STRCMP(preword, "smwrd") == 0)
+               {
+                   int     j;
+
+                   // print the stack of changes that brought us here
+                   smsg("------ %s -------", fword);
+                   for (j = 0; j < depth; ++j)
+                       smsg("%s", changename[j]);
+               }
+#endif
+               if (soundfold)
+               {
+                   // For soundfolded words we need to find the original
+                   // words, the edit distance and then add them.
+                   add_sound_suggest(su, preword, sp->ts_score, lp);
+               }
+               else if (sp->ts_fidx > 0)
+               {
+                   // Give a penalty when changing non-word char to word
+                   // char, e.g., "thes," -> "these".
+                   p = fword + sp->ts_fidx;
+                   MB_PTR_BACK(fword, p);
+                   if (!spell_iswordp(p, curwin))
+                   {
+                       p = preword + STRLEN(preword);
+                       MB_PTR_BACK(preword, p);
+                       if (spell_iswordp(p, curwin))
+                           newscore += SCORE_NONWORD;
+                   }
+
+                   // Give a bonus to words seen before.
+                   score = score_wordcount_adj(slang,
+                                               sp->ts_score + newscore,
+                                               preword + sp->ts_prewordlen,
+                                               sp->ts_prewordlen > 0);
+
+                   // Add the suggestion if the score isn't too bad.
+                   if (score <= su->su_maxscore)
+                   {
+                       add_suggestion(su, &su->su_ga, preword,
+                                   sp->ts_fidx - repextra,
+                                   score, 0, FALSE, lp->lp_sallang, FALSE);
+
+                       if (su->su_badflags & WF_MIXCAP)
+                       {
+                           // We really don't know if the word should be
+                           // upper or lower case, add both.
+                           c = captype(preword, NULL);
+                           if (c == 0 || c == WF_ALLCAP)
+                           {
+                               make_case_word(tword + sp->ts_splitoff,
+                                             preword + sp->ts_prewordlen,
+                                                     c == 0 ? WF_ALLCAP : 0);
+
+                               add_suggestion(su, &su->su_ga, preword,
+                                       sp->ts_fidx - repextra,
+                                       score + SCORE_ICASE, 0, FALSE,
+                                       lp->lp_sallang, FALSE);
+                           }
+                       }
+                   }
+               }
+           }
+
+           // Try word split and/or compounding.
+           if ((sp->ts_fidx >= sp->ts_fidxtry || fword_ends)
+                   // Don't split halfway a character.
+                   && (!has_mbyte || sp->ts_tcharlen == 0))
+           {
+               int     try_compound;
+               int     try_split;
+
+               // If past the end of the bad word don't try a split.
+               // Otherwise try changing the next word.  E.g., find
+               // suggestions for "the the" where the second "the" is
+               // different.  It's done like a split.
+               // TODO: word split for soundfold words
+               try_split = (sp->ts_fidx - repextra < su->su_badlen)
+                                                               && !soundfold;
+
+               // Get here in several situations:
+               // 1. The word in the tree ends:
+               //    If the word allows compounding try that.  Otherwise try
+               //    a split by inserting a space.  For both check that a
+               //    valid words starts at fword[sp->ts_fidx].
+               //    For NOBREAK do like compounding to be able to check if
+               //    the next word is valid.
+               // 2. The badword does end, but it was due to a change (e.g.,
+               //    a swap).  No need to split, but do check that the
+               //    following word is valid.
+               // 3. The badword and the word in the tree end.  It may still
+               //    be possible to compound another (short) word.
+               try_compound = FALSE;
+               if (!soundfold
+                       && !slang->sl_nocompoundsugs
+                       && slang->sl_compprog != NULL
+                       && ((unsigned)flags >> 24) != 0
+                       && sp->ts_twordlen - sp->ts_splitoff
+                                                      >= slang->sl_compminlen
+                       && (!has_mbyte
+                           || slang->sl_compminlen == 0
+                           || mb_charlen(tword + sp->ts_splitoff)
+                                                     >= slang->sl_compminlen)
+                       && (slang->sl_compsylmax < MAXWLEN
+                           || sp->ts_complen + 1 - sp->ts_compsplit
+                                                         < slang->sl_compmax)
+                       && (can_be_compound(sp, slang,
+                                        compflags, ((unsigned)flags >> 24))))
+
+               {
+                   try_compound = TRUE;
+                   compflags[sp->ts_complen] = ((unsigned)flags >> 24);
+                   compflags[sp->ts_complen + 1] = NUL;
+               }
+
+               // For NOBREAK we never try splitting, it won't make any word
+               // valid.
+               if (slang->sl_nobreak && !slang->sl_nocompoundsugs)
+                   try_compound = TRUE;
+
+               // If we could add a compound word, and it's also possible to
+               // split at this point, do the split first and set
+               // TSF_DIDSPLIT to avoid doing it again.
+               else if (!fword_ends
+                       && try_compound
+                       && (sp->ts_flags & TSF_DIDSPLIT) == 0)
+               {
+                   try_compound = FALSE;
+                   sp->ts_flags |= TSF_DIDSPLIT;
+                   --sp->ts_curi;          // do the same NUL again
+                   compflags[sp->ts_complen] = NUL;
+               }
+               else
+                   sp->ts_flags &= ~TSF_DIDSPLIT;
+
+               if (try_split || try_compound)
+               {
+                   if (!try_compound && (!fword_ends || !goodword_ends))
+                   {
+                       // If we're going to split need to check that the
+                       // words so far are valid for compounding.  If there
+                       // is only one word it must not have the NEEDCOMPOUND
+                       // flag.
+                       if (sp->ts_complen == sp->ts_compsplit
+                                                    && (flags & WF_NEEDCOMP))
+                           break;
+                       p = preword;
+                       while (*skiptowhite(p) != NUL)
+                           p = skipwhite(skiptowhite(p));
+                       if (sp->ts_complen > sp->ts_compsplit
+                               && !can_compound(slang, p,
+                                               compflags + sp->ts_compsplit))
+                           break;
+
+                       if (slang->sl_nosplitsugs)
+                           newscore += SCORE_SPLIT_NO;
+                       else
+                           newscore += SCORE_SPLIT;
+
+                       // Give a bonus to words seen before.
+                       newscore = score_wordcount_adj(slang, newscore,
+                                          preword + sp->ts_prewordlen, TRUE);
+                   }
+
+                   if (TRY_DEEPER(su, stack, depth, newscore))
+                   {
+                       go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+                       if (!try_compound && !fword_ends)
+                           sprintf(changename[depth], "%.*s-%s: split",
+                                sp->ts_twordlen, tword, fword + sp->ts_fidx);
+                       else
+                           sprintf(changename[depth], "%.*s-%s: compound",
+                                sp->ts_twordlen, tword, fword + sp->ts_fidx);
+#endif
+                       // Save things to be restored at STATE_SPLITUNDO.
+                       sp->ts_save_badflags = su->su_badflags;
+                       PROF_STORE(sp->ts_state)
+                       sp->ts_state = STATE_SPLITUNDO;
+
+                       ++depth;
+                       sp = &stack[depth];
+
+                       // Append a space to preword when splitting.
+                       if (!try_compound && !fword_ends)
+                           STRCAT(preword, " ");
+                       sp->ts_prewordlen = (char_u)STRLEN(preword);
+                       sp->ts_splitoff = sp->ts_twordlen;
+                       sp->ts_splitfidx = sp->ts_fidx;
+
+                       // If the badword has a non-word character at this
+                       // position skip it.  That means replacing the
+                       // non-word character with a space.  Always skip a
+                       // character when the word ends.  But only when the
+                       // good word can end.
+                       if (((!try_compound && !spell_iswordp_nmw(fword
+                                                              + sp->ts_fidx,
+                                                              curwin))
+                                   || fword_ends)
+                               && fword[sp->ts_fidx] != NUL
+                               && goodword_ends)
+                       {
+                           int     l;
+
+                           l = MB_PTR2LEN(fword + sp->ts_fidx);
+                           if (fword_ends)
+                           {
+                               // Copy the skipped character to preword.
+                               mch_memmove(preword + sp->ts_prewordlen,
+                                                     fword + sp->ts_fidx, l);
+                               sp->ts_prewordlen += l;
+                               preword[sp->ts_prewordlen] = NUL;
+                           }
+                           else
+                               sp->ts_score -= SCORE_SPLIT - SCORE_SUBST;
+                           sp->ts_fidx += l;
+                       }
+
+                       // When compounding include compound flag in
+                       // compflags[] (already set above).  When splitting we
+                       // may start compounding over again.
+                       if (try_compound)
+                           ++sp->ts_complen;
+                       else
+                           sp->ts_compsplit = sp->ts_complen;
+                       sp->ts_prefixdepth = PFD_NOPREFIX;
+
+                       // set su->su_badflags to the caps type at this
+                       // position
+                       if (has_mbyte)
+                           n = nofold_len(fword, sp->ts_fidx, su->su_badptr);
+                       else
+                           n = sp->ts_fidx;
+                       su->su_badflags = badword_captype(su->su_badptr + n,
+                                              su->su_badptr + su->su_badlen);
+
+                       // Restart at top of the tree.
+                       sp->ts_arridx = 0;
+
+                       // If there are postponed prefixes, try these too.
+                       if (pbyts != NULL)
+                       {
+                           byts = pbyts;
+                           idxs = pidxs;
+                           sp->ts_prefixdepth = PFD_PREFIXTREE;
+                           PROF_STORE(sp->ts_state)
+                           sp->ts_state = STATE_NOPREFIX;
+                       }
+                   }
+               }
+           }
+           break;
+
+       case STATE_SPLITUNDO:
+           // Undo the changes done for word split or compound word.
+           su->su_badflags = sp->ts_save_badflags;
+
+           // Continue looking for NUL bytes.
+           PROF_STORE(sp->ts_state)
+           sp->ts_state = STATE_START;
+
+           // In case we went into the prefix tree.
+           byts = fbyts;
+           idxs = fidxs;
+           break;
+
+       case STATE_ENDNUL:
+           // Past the NUL bytes in the node.
+           su->su_badflags = sp->ts_save_badflags;
+           if (fword[sp->ts_fidx] == NUL && sp->ts_tcharlen == 0)
+           {
+               // The badword ends, can't use STATE_PLAIN.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_DEL;
+               break;
+           }
+           PROF_STORE(sp->ts_state)
+           sp->ts_state = STATE_PLAIN;
+           // FALLTHROUGH
+
+       case STATE_PLAIN:
+           // Go over all possible bytes at this node, add each to tword[]
+           // and use child node.  "ts_curi" is the index.
+           arridx = sp->ts_arridx;
+           if (sp->ts_curi > byts[arridx])
+           {
+               // Done all bytes at this node, do next state.  When still at
+               // already changed bytes skip the other tricks.
+               PROF_STORE(sp->ts_state)
+               if (sp->ts_fidx >= sp->ts_fidxtry)
+                   sp->ts_state = STATE_DEL;
+               else
+                   sp->ts_state = STATE_FINAL;
+           }
+           else
+           {
+               arridx += sp->ts_curi++;
+               c = byts[arridx];
+
+               // Normal byte, go one level deeper.  If it's not equal to the
+               // byte in the bad word adjust the score.  But don't even try
+               // when the byte was already changed.  And don't try when we
+               // just deleted this byte, accepting it is always cheaper than
+               // delete + substitute.
+               if (c == fword[sp->ts_fidx]
+                       || (sp->ts_tcharlen > 0 && sp->ts_isdiff != DIFF_NONE))
+                   newscore = 0;
+               else
+                   newscore = SCORE_SUBST;
+               if ((newscore == 0
+                           || (sp->ts_fidx >= sp->ts_fidxtry
+                               && ((sp->ts_flags & TSF_DIDDEL) == 0
+                                   || c != fword[sp->ts_delidx])))
+                       && TRY_DEEPER(su, stack, depth, newscore))
+               {
+                   go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+                   if (newscore > 0)
+                       sprintf(changename[depth], "%.*s-%s: subst %c to %c",
+                               sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                               fword[sp->ts_fidx], c);
+                   else
+                       sprintf(changename[depth], "%.*s-%s: accept %c",
+                               sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                               fword[sp->ts_fidx]);
+#endif
+                   ++depth;
+                   sp = &stack[depth];
+                   ++sp->ts_fidx;
+                   tword[sp->ts_twordlen++] = c;
+                   sp->ts_arridx = idxs[arridx];
+                   if (newscore == SCORE_SUBST)
+                       sp->ts_isdiff = DIFF_YES;
+                   if (has_mbyte)
+                   {
+                       // Multi-byte characters are a bit complicated to
+                       // handle: They differ when any of the bytes differ
+                       // and then their length may also differ.
+                       if (sp->ts_tcharlen == 0)
+                       {
+                           // First byte.
+                           sp->ts_tcharidx = 0;
+                           sp->ts_tcharlen = MB_BYTE2LEN(c);
+                           sp->ts_fcharstart = sp->ts_fidx - 1;
+                           sp->ts_isdiff = (newscore != 0)
+                                                      ? DIFF_YES : DIFF_NONE;
+                       }
+                       else if (sp->ts_isdiff == DIFF_INSERT)
+                           // When inserting trail bytes don't advance in the
+                           // bad word.
+                           --sp->ts_fidx;
+                       if (++sp->ts_tcharidx == sp->ts_tcharlen)
+                       {
+                           // Last byte of character.
+                           if (sp->ts_isdiff == DIFF_YES)
+                           {
+                               // Correct ts_fidx for the byte length of the
+                               // character (we didn't check that before).
+                               sp->ts_fidx = sp->ts_fcharstart
+                                           + MB_PTR2LEN(
+                                                   fword + sp->ts_fcharstart);
+                               // For changing a composing character adjust
+                               // the score from SCORE_SUBST to
+                               // SCORE_SUBCOMP.
+                               if (enc_utf8
+                                       && utf_iscomposing(
+                                           utf_ptr2char(tword
+                                               + sp->ts_twordlen
+                                                          - sp->ts_tcharlen))
+                                       && utf_iscomposing(
+                                           utf_ptr2char(fword
+                                                       + sp->ts_fcharstart)))
+                                   sp->ts_score -=
+                                                 SCORE_SUBST - SCORE_SUBCOMP;
+
+                               // For a similar character adjust score from
+                               // SCORE_SUBST to SCORE_SIMILAR.
+                               else if (!soundfold
+                                       && slang->sl_has_map
+                                       && similar_chars(slang,
+                                           mb_ptr2char(tword
+                                               + sp->ts_twordlen
+                                                          - sp->ts_tcharlen),
+                                           mb_ptr2char(fword
+                                                       + sp->ts_fcharstart)))
+                                   sp->ts_score -=
+                                                 SCORE_SUBST - SCORE_SIMILAR;
+                           }
+                           else if (sp->ts_isdiff == DIFF_INSERT
+                                        && sp->ts_twordlen > sp->ts_tcharlen)
+                           {
+                               p = tword + sp->ts_twordlen - sp->ts_tcharlen;
+                               c = mb_ptr2char(p);
+                               if (enc_utf8 && utf_iscomposing(c))
+                               {
+                                   // Inserting a composing char doesn't
+                                   // count that much.
+                                   sp->ts_score -= SCORE_INS - SCORE_INSCOMP;
+                               }
+                               else
+                               {
+                                   // If the previous character was the same,
+                                   // thus doubling a character, give a bonus
+                                   // to the score.  Also for the soundfold
+                                   // tree (might seem illogical but does
+                                   // give better scores).
+                                   MB_PTR_BACK(tword, p);
+                                   if (c == mb_ptr2char(p))
+                                       sp->ts_score -= SCORE_INS
+                                                              - SCORE_INSDUP;
+                               }
+                           }
+
+                           // Starting a new char, reset the length.
+                           sp->ts_tcharlen = 0;
+                       }
+                   }
+                   else
+                   {
+                       // If we found a similar char adjust the score.
+                       // We do this after calling go_deeper() because
+                       // it's slow.
+                       if (newscore != 0
+                               && !soundfold
+                               && slang->sl_has_map
+                               && similar_chars(slang,
+                                                  c, fword[sp->ts_fidx - 1]))
+                           sp->ts_score -= SCORE_SUBST - SCORE_SIMILAR;
+                   }
+               }
+           }
+           break;
+
+       case STATE_DEL:
+           // When past the first byte of a multi-byte char don't try
+           // delete/insert/swap a character.
+           if (has_mbyte && sp->ts_tcharlen > 0)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_FINAL;
+               break;
+           }
+           // Try skipping one character in the bad word (delete it).
+           PROF_STORE(sp->ts_state)
+           sp->ts_state = STATE_INS_PREP;
+           sp->ts_curi = 1;
+           if (soundfold && sp->ts_fidx == 0 && fword[sp->ts_fidx] == '*')
+               // Deleting a vowel at the start of a word counts less, see
+               // soundalike_score().
+               newscore = 2 * SCORE_DEL / 3;
+           else
+               newscore = SCORE_DEL;
+           if (fword[sp->ts_fidx] != NUL
+                                   && TRY_DEEPER(su, stack, depth, newscore))
+           {
+               go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+               sprintf(changename[depth], "%.*s-%s: delete %c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       fword[sp->ts_fidx]);
+#endif
+               ++depth;
+
+               // Remember what character we deleted, so that we can avoid
+               // inserting it again.
+               stack[depth].ts_flags |= TSF_DIDDEL;
+               stack[depth].ts_delidx = sp->ts_fidx;
+
+               // Advance over the character in fword[].  Give a bonus to the
+               // score if the same character is following "nn" -> "n".  It's
+               // a bit illogical for soundfold tree but it does give better
+               // results.
+               if (has_mbyte)
+               {
+                   c = mb_ptr2char(fword + sp->ts_fidx);
+                   stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx);
+                   if (enc_utf8 && utf_iscomposing(c))
+                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
+                   else if (c == mb_ptr2char(fword + stack[depth].ts_fidx))
+                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
+               }
+               else
+               {
+                   ++stack[depth].ts_fidx;
+                   if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1])
+                       stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
+               }
+               break;
+           }
+           // FALLTHROUGH
+
+       case STATE_INS_PREP:
+           if (sp->ts_flags & TSF_DIDDEL)
+           {
+               // If we just deleted a byte then inserting won't make sense,
+               // a substitute is always cheaper.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_SWAP;
+               break;
+           }
+
+           // skip over NUL bytes
+           n = sp->ts_arridx;
+           for (;;)
+           {
+               if (sp->ts_curi > byts[n])
+               {
+                   // Only NUL bytes at this node, go to next state.
+                   PROF_STORE(sp->ts_state)
+                   sp->ts_state = STATE_SWAP;
+                   break;
+               }
+               if (byts[n + sp->ts_curi] != NUL)
+               {
+                   // Found a byte to insert.
+                   PROF_STORE(sp->ts_state)
+                   sp->ts_state = STATE_INS;
+                   break;
+               }
+               ++sp->ts_curi;
+           }
+           break;
+
+           // FALLTHROUGH
+
+       case STATE_INS:
+           // Insert one byte.  Repeat this for each possible byte at this
+           // node.
+           n = sp->ts_arridx;
+           if (sp->ts_curi > byts[n])
+           {
+               // Done all bytes at this node, go to next state.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_SWAP;
+               break;
+           }
+
+           // Do one more byte at this node, but:
+           // - Skip NUL bytes.
+           // - Skip the byte if it's equal to the byte in the word,
+           //   accepting that byte is always better.
+           n += sp->ts_curi++;
+           c = byts[n];
+           if (soundfold && sp->ts_twordlen == 0 && c == '*')
+               // Inserting a vowel at the start of a word counts less,
+               // see soundalike_score().
+               newscore = 2 * SCORE_INS / 3;
+           else
+               newscore = SCORE_INS;
+           if (c != fword[sp->ts_fidx]
+                                   && TRY_DEEPER(su, stack, depth, newscore))
+           {
+               go_deeper(stack, depth, newscore);
+#ifdef DEBUG_TRIEWALK
+               sprintf(changename[depth], "%.*s-%s: insert %c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       c);
+#endif
+               ++depth;
+               sp = &stack[depth];
+               tword[sp->ts_twordlen++] = c;
+               sp->ts_arridx = idxs[n];
+               if (has_mbyte)
+               {
+                   fl = MB_BYTE2LEN(c);
+                   if (fl > 1)
+                   {
+                       // There are following bytes for the same character.
+                       // We must find all bytes before trying
+                       // delete/insert/swap/etc.
+                       sp->ts_tcharlen = fl;
+                       sp->ts_tcharidx = 1;
+                       sp->ts_isdiff = DIFF_INSERT;
+                   }
+               }
+               else
+                   fl = 1;
+               if (fl == 1)
+               {
+                   // If the previous character was the same, thus doubling a
+                   // character, give a bonus to the score.  Also for
+                   // soundfold words (illogical but does give a better
+                   // score).
+                   if (sp->ts_twordlen >= 2
+                                          && tword[sp->ts_twordlen - 2] == c)
+                       sp->ts_score -= SCORE_INS - SCORE_INSDUP;
+               }
+           }
+           break;
+
+       case STATE_SWAP:
+           // Swap two bytes in the bad word: "12" -> "21".
+           // We change "fword" here, it's changed back afterwards at
+           // STATE_UNSWAP.
+           p = fword + sp->ts_fidx;
+           c = *p;
+           if (c == NUL)
+           {
+               // End of word, can't swap or replace.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_FINAL;
+               break;
+           }
+
+           // Don't swap if the first character is not a word character.
+           // SWAP3 etc. also don't make sense then.
+           if (!soundfold && !spell_iswordp(p, curwin))
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+               break;
+           }
+
+           if (has_mbyte)
+           {
+               n = MB_CPTR2LEN(p);
+               c = mb_ptr2char(p);
+               if (p[n] == NUL)
+                   c2 = NUL;
+               else if (!soundfold && !spell_iswordp(p + n, curwin))
+                   c2 = c; // don't swap non-word char
+               else
+                   c2 = mb_ptr2char(p + n);
+           }
+           else
+           {
+               if (p[1] == NUL)
+                   c2 = NUL;
+               else if (!soundfold && !spell_iswordp(p + 1, curwin))
+                   c2 = c; // don't swap non-word char
+               else
+                   c2 = p[1];
+           }
+
+           // When the second character is NUL we can't swap.
+           if (c2 == NUL)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+               break;
+           }
+
+           // When characters are identical, swap won't do anything.
+           // Also get here if the second char is not a word character.
+           if (c == c2)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_SWAP3;
+               break;
+           }
+           if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP))
+           {
+               go_deeper(stack, depth, SCORE_SWAP);
+#ifdef DEBUG_TRIEWALK
+               sprintf(changename[depth], "%.*s-%s: swap %c and %c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       c, c2);
+#endif
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_UNSWAP;
+               ++depth;
+               if (has_mbyte)
+               {
+                   fl = mb_char2len(c2);
+                   mch_memmove(p, p + n, fl);
+                   mb_char2bytes(c, p + fl);
+                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl;
+               }
+               else
+               {
+                   p[0] = c2;
+                   p[1] = c;
+                   stack[depth].ts_fidxtry = sp->ts_fidx + 2;
+               }
+           }
+           else
+           {
+               // If this swap doesn't work then SWAP3 won't either.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+           }
+           break;
+
+       case STATE_UNSWAP:
+           // Undo the STATE_SWAP swap: "21" -> "12".
+           p = fword + sp->ts_fidx;
+           if (has_mbyte)
+           {
+               n = MB_PTR2LEN(p);
+               c = mb_ptr2char(p + n);
+               mch_memmove(p + MB_PTR2LEN(p + n), p, n);
+               mb_char2bytes(c, p);
+           }
+           else
+           {
+               c = *p;
+               *p = p[1];
+               p[1] = c;
+           }
+           // FALLTHROUGH
+
+       case STATE_SWAP3:
+           // Swap two bytes, skipping one: "123" -> "321".  We change
+           // "fword" here, it's changed back afterwards at STATE_UNSWAP3.
+           p = fword + sp->ts_fidx;
+           if (has_mbyte)
+           {
+               n = MB_CPTR2LEN(p);
+               c = mb_ptr2char(p);
+               fl = MB_CPTR2LEN(p + n);
+               c2 = mb_ptr2char(p + n);
+               if (!soundfold && !spell_iswordp(p + n + fl, curwin))
+                   c3 = c;     // don't swap non-word char
+               else
+                   c3 = mb_ptr2char(p + n + fl);
+           }
+           else
+           {
+               c = *p;
+               c2 = p[1];
+               if (!soundfold && !spell_iswordp(p + 2, curwin))
+                   c3 = c;     // don't swap non-word char
+               else
+                   c3 = p[2];
+           }
+
+           // When characters are identical: "121" then SWAP3 result is
+           // identical, ROT3L result is same as SWAP: "211", ROT3L result is
+           // same as SWAP on next char: "112".  Thus skip all swapping.
+           // Also skip when c3 is NUL.
+           // Also get here when the third character is not a word character.
+           // Second character may any char: "a.b" -> "b.a"
+           if (c == c3 || c3 == NUL)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+               break;
+           }
+           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
+           {
+               go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+               sprintf(changename[depth], "%.*s-%s: swap3 %c and %c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       c, c3);
+#endif
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_UNSWAP3;
+               ++depth;
+               if (has_mbyte)
+               {
+                   tl = mb_char2len(c3);
+                   mch_memmove(p, p + n + fl, tl);
+                   mb_char2bytes(c2, p + tl);
+                   mb_char2bytes(c, p + fl + tl);
+                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl + tl;
+               }
+               else
+               {
+                   p[0] = p[2];
+                   p[2] = c;
+                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
+               }
+           }
+           else
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+           }
+           break;
+
+       case STATE_UNSWAP3:
+           // Undo STATE_SWAP3: "321" -> "123"
+           p = fword + sp->ts_fidx;
+           if (has_mbyte)
+           {
+               n = MB_PTR2LEN(p);
+               c2 = mb_ptr2char(p + n);
+               fl = MB_PTR2LEN(p + n);
+               c = mb_ptr2char(p + n + fl);
+               tl = MB_PTR2LEN(p + n + fl);
+               mch_memmove(p + fl + tl, p, n);
+               mb_char2bytes(c, p);
+               mb_char2bytes(c2, p + tl);
+               p = p + tl;
+           }
+           else
+           {
+               c = *p;
+               *p = p[2];
+               p[2] = c;
+               ++p;
+           }
+
+           if (!soundfold && !spell_iswordp(p, curwin))
+           {
+               // Middle char is not a word char, skip the rotate.  First and
+               // third char were already checked at swap and swap3.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+               break;
+           }
+
+           // Rotate three characters left: "123" -> "231".  We change
+           // "fword" here, it's changed back afterwards at STATE_UNROT3L.
+           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
+           {
+               go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+               p = fword + sp->ts_fidx;
+               sprintf(changename[depth], "%.*s-%s: rotate left %c%c%c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       p[0], p[1], p[2]);
+#endif
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_UNROT3L;
+               ++depth;
+               p = fword + sp->ts_fidx;
+               if (has_mbyte)
+               {
+                   n = MB_CPTR2LEN(p);
+                   c = mb_ptr2char(p);
+                   fl = MB_CPTR2LEN(p + n);
+                   fl += MB_CPTR2LEN(p + n + fl);
+                   mch_memmove(p, p + n, fl);
+                   mb_char2bytes(c, p + fl);
+                   stack[depth].ts_fidxtry = sp->ts_fidx + n + fl;
+               }
+               else
+               {
+                   c = *p;
+                   *p = p[1];
+                   p[1] = p[2];
+                   p[2] = c;
+                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
+               }
+           }
+           else
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+           }
+           break;
+
+       case STATE_UNROT3L:
+           // Undo ROT3L: "231" -> "123"
+           p = fword + sp->ts_fidx;
+           if (has_mbyte)
+           {
+               n = MB_PTR2LEN(p);
+               n += MB_PTR2LEN(p + n);
+               c = mb_ptr2char(p + n);
+               tl = MB_PTR2LEN(p + n);
+               mch_memmove(p + tl, p, n);
+               mb_char2bytes(c, p);
+           }
+           else
+           {
+               c = p[2];
+               p[2] = p[1];
+               p[1] = *p;
+               *p = c;
+           }
+
+           // Rotate three bytes right: "123" -> "312".  We change "fword"
+           // here, it's changed back afterwards at STATE_UNROT3R.
+           if (TRY_DEEPER(su, stack, depth, SCORE_SWAP3))
+           {
+               go_deeper(stack, depth, SCORE_SWAP3);
+#ifdef DEBUG_TRIEWALK
+               p = fword + sp->ts_fidx;
+               sprintf(changename[depth], "%.*s-%s: rotate right %c%c%c",
+                       sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                       p[0], p[1], p[2]);
+#endif
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_UNROT3R;
+               ++depth;
+               p = fword + sp->ts_fidx;
+               if (has_mbyte)
+               {
+                   n = MB_CPTR2LEN(p);
+                   n += MB_CPTR2LEN(p + n);
+                   c = mb_ptr2char(p + n);
+                   tl = MB_CPTR2LEN(p + n);
+                   mch_memmove(p + tl, p, n);
+                   mb_char2bytes(c, p);
+                   stack[depth].ts_fidxtry = sp->ts_fidx + n + tl;
+               }
+               else
+               {
+                   c = p[2];
+                   p[2] = p[1];
+                   p[1] = *p;
+                   *p = c;
+                   stack[depth].ts_fidxtry = sp->ts_fidx + 3;
+               }
+           }
+           else
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_REP_INI;
+           }
+           break;
+
+       case STATE_UNROT3R:
+           // Undo ROT3R: "312" -> "123"
+           p = fword + sp->ts_fidx;
+           if (has_mbyte)
+           {
+               c = mb_ptr2char(p);
+               tl = MB_PTR2LEN(p);
+               n = MB_PTR2LEN(p + tl);
+               n += MB_PTR2LEN(p + tl + n);
+               mch_memmove(p, p + tl, n);
+               mb_char2bytes(c, p + n);
+           }
+           else
+           {
+               c = *p;
+               *p = p[1];
+               p[1] = p[2];
+               p[2] = c;
+           }
+           // FALLTHROUGH
+
+       case STATE_REP_INI:
+           // Check if matching with REP items from the .aff file would work.
+           // Quickly skip if:
+           // - there are no REP items and we are not in the soundfold trie
+           // - the score is going to be too high anyway
+           // - already applied a REP item or swapped here
+           if ((lp->lp_replang == NULL && !soundfold)
+                   || sp->ts_score + SCORE_REP >= su->su_maxscore
+                   || sp->ts_fidx < sp->ts_fidxtry)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_FINAL;
+               break;
+           }
+
+           // Use the first byte to quickly find the first entry that may
+           // match.  If the index is -1 there is none.
+           if (soundfold)
+               sp->ts_curi = slang->sl_repsal_first[fword[sp->ts_fidx]];
+           else
+               sp->ts_curi = lp->lp_replang->sl_rep_first[fword[sp->ts_fidx]];
+
+           if (sp->ts_curi < 0)
+           {
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_FINAL;
+               break;
+           }
+
+           PROF_STORE(sp->ts_state)
+           sp->ts_state = STATE_REP;
+           // FALLTHROUGH
+
+       case STATE_REP:
+           // Try matching with REP items from the .aff file.  For each match
+           // replace the characters and check if the resulting word is
+           // valid.
+           p = fword + sp->ts_fidx;
+
+           if (soundfold)
+               gap = &slang->sl_repsal;
+           else
+               gap = &lp->lp_replang->sl_rep;
+           while (sp->ts_curi < gap->ga_len)
+           {
+               ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
+               if (*ftp->ft_from != *p)
+               {
+                   // past possible matching entries
+                   sp->ts_curi = gap->ga_len;
+                   break;
+               }
+               if (STRNCMP(ftp->ft_from, p, STRLEN(ftp->ft_from)) == 0
+                       && TRY_DEEPER(su, stack, depth, SCORE_REP))
+               {
+                   go_deeper(stack, depth, SCORE_REP);
+#ifdef DEBUG_TRIEWALK
+                   sprintf(changename[depth], "%.*s-%s: replace %s with %s",
+                           sp->ts_twordlen, tword, fword + sp->ts_fidx,
+                           ftp->ft_from, ftp->ft_to);
+#endif
+                   // Need to undo this afterwards.
+                   PROF_STORE(sp->ts_state)
+                   sp->ts_state = STATE_REP_UNDO;
+
+                   // Change the "from" to the "to" string.
+                   ++depth;
+                   fl = (int)STRLEN(ftp->ft_from);
+                   tl = (int)STRLEN(ftp->ft_to);
+                   if (fl != tl)
+                   {
+                       STRMOVE(p + tl, p + fl);
+                       repextra += tl - fl;
+                   }
+                   mch_memmove(p, ftp->ft_to, tl);
+                   stack[depth].ts_fidxtry = sp->ts_fidx + tl;
+                   stack[depth].ts_tcharlen = 0;
+                   break;
+               }
+           }
+
+           if (sp->ts_curi >= gap->ga_len && sp->ts_state == STATE_REP)
+           {
+               // No (more) matches.
+               PROF_STORE(sp->ts_state)
+               sp->ts_state = STATE_FINAL;
+           }
+
+           break;
+
+       case STATE_REP_UNDO:
+           // Undo a REP replacement and continue with the next one.
+           if (soundfold)
+               gap = &slang->sl_repsal;
+           else
+               gap = &lp->lp_replang->sl_rep;
+           ftp = (fromto_T *)gap->ga_data + sp->ts_curi - 1;
+           fl = (int)STRLEN(ftp->ft_from);
+           tl = (int)STRLEN(ftp->ft_to);
+           p = fword + sp->ts_fidx;
+           if (fl != tl)
+           {
+               STRMOVE(p + fl, p + tl);
+               repextra -= tl - fl;
+           }
+           mch_memmove(p, ftp->ft_from, fl);
+           PROF_STORE(sp->ts_state)
+           sp->ts_state = STATE_REP;
+           break;
+
+       default:
+           // Did all possible states at this level, go up one level.
+           --depth;
+
+           if (depth >= 0 && stack[depth].ts_prefixdepth == PFD_PREFIXTREE)
+           {
+               // Continue in or go back to the prefix tree.
+               byts = pbyts;
+               idxs = pidxs;
+           }
+
+           // Don't check for CTRL-C too often, it takes time.
+           if (--breakcheckcount == 0)
+           {
+               ui_breakcheck();
+               breakcheckcount = 1000;
+           }
+       }
+    }
+}
+
+
+/*
+ * Go one level deeper in the tree.
+ */
+    static void
+go_deeper(trystate_T *stack, int depth, int score_add)
+{
+    stack[depth + 1] = stack[depth];
+    stack[depth + 1].ts_state = STATE_START;
+    stack[depth + 1].ts_score = stack[depth].ts_score + score_add;
+    stack[depth + 1].ts_curi = 1;      // start just after length byte
+    stack[depth + 1].ts_flags = 0;
+}
+
+/*
+ * "fword" is a good word with case folded.  Find the matching keep-case
+ * words and put it in "kword".
+ * Theoretically there could be several keep-case words that result in the
+ * same case-folded word, but we only find one...
+ */
+    static void
+find_keepcap_word(slang_T *slang, char_u *fword, char_u *kword)
+{
+    char_u     uword[MAXWLEN];         // "fword" in upper-case
+    int                depth;
+    idx_T      tryidx;
+
+    // The following arrays are used at each depth in the tree.
+    idx_T      arridx[MAXWLEN];
+    int                round[MAXWLEN];
+    int                fwordidx[MAXWLEN];
+    int                uwordidx[MAXWLEN];
+    int                kwordlen[MAXWLEN];
+
+    int                flen, ulen;
+    int                l;
+    int                len;
+    int                c;
+    idx_T      lo, hi, m;
+    char_u     *p;
+    char_u     *byts = slang->sl_kbyts;    // array with bytes of the words
+    idx_T      *idxs = slang->sl_kidxs;    // array with indexes
+
+    if (byts == NULL)
+    {
+       // array is empty: "cannot happen"
+       *kword = NUL;
+       return;
+    }
+
+    // Make an all-cap version of "fword".
+    allcap_copy(fword, uword);
+
+    // Each character needs to be tried both case-folded and upper-case.
+    // All this gets very complicated if we keep in mind that changing case
+    // may change the byte length of a multi-byte character...
+    depth = 0;
+    arridx[0] = 0;
+    round[0] = 0;
+    fwordidx[0] = 0;
+    uwordidx[0] = 0;
+    kwordlen[0] = 0;
+    while (depth >= 0)
+    {
+       if (fword[fwordidx[depth]] == NUL)
+       {
+           // We are at the end of "fword".  If the tree allows a word to end
+           // here we have found a match.
+           if (byts[arridx[depth] + 1] == 0)
+           {
+               kword[kwordlen[depth]] = NUL;
+               return;
+           }
+
+           // kword is getting too long, continue one level up
+           --depth;
+       }
+       else if (++round[depth] > 2)
+       {
+           // tried both fold-case and upper-case character, continue one
+           // level up
+           --depth;
+       }
+       else
+       {
+           // round[depth] == 1: Try using the folded-case character.
+           // round[depth] == 2: Try using the upper-case character.
+           if (has_mbyte)
+           {
+               flen = MB_CPTR2LEN(fword + fwordidx[depth]);
+               ulen = MB_CPTR2LEN(uword + uwordidx[depth]);
+           }
+           else
+               ulen = flen = 1;
+           if (round[depth] == 1)
+           {
+               p = fword + fwordidx[depth];
+               l = flen;
+           }
+           else
+           {
+               p = uword + uwordidx[depth];
+               l = ulen;
+           }
+
+           for (tryidx = arridx[depth]; l > 0; --l)
+           {
+               // Perform a binary search in the list of accepted bytes.
+               len = byts[tryidx++];
+               c = *p++;
+               lo = tryidx;
+               hi = tryidx + len - 1;
+               while (lo < hi)
+               {
+                   m = (lo + hi) / 2;
+                   if (byts[m] > c)
+                       hi = m - 1;
+                   else if (byts[m] < c)
+                       lo = m + 1;
+                   else
+                   {
+                       lo = hi = m;
+                       break;
+                   }
+               }
+
+               // Stop if there is no matching byte.
+               if (hi < lo || byts[lo] != c)
+                   break;
+
+               // Continue at the child (if there is one).
+               tryidx = idxs[lo];
+           }
+
+           if (l == 0)
+           {
+               // Found the matching char.  Copy it to "kword" and go a
+               // level deeper.
+               if (round[depth] == 1)
+               {
+                   STRNCPY(kword + kwordlen[depth], fword + fwordidx[depth],
+                                                                       flen);
+                   kwordlen[depth + 1] = kwordlen[depth] + flen;
+               }
+               else
+               {
+                   STRNCPY(kword + kwordlen[depth], uword + uwordidx[depth],
+                                                                       ulen);
+                   kwordlen[depth + 1] = kwordlen[depth] + ulen;
+               }
+               fwordidx[depth + 1] = fwordidx[depth] + flen;
+               uwordidx[depth + 1] = uwordidx[depth] + ulen;
+
+               ++depth;
+               arridx[depth] = tryidx;
+               round[depth] = 0;
+           }
+       }
+    }
+
+    // Didn't find it: "cannot happen".
+    *kword = NUL;
+}
+
+/*
+ * Compute the sound-a-like score for suggestions in su->su_ga and add them to
+ * su->su_sga.
+ */
+    static void
+score_comp_sal(suginfo_T *su)
+{
+    langp_T    *lp;
+    char_u     badsound[MAXWLEN];
+    int                i;
+    suggest_T   *stp;
+    suggest_T   *sstp;
+    int                score;
+    int                lpi;
+
+    if (ga_grow(&su->su_sga, su->su_ga.ga_len) == FAIL)
+       return;
+
+    // Use the sound-folding of the first language that supports it.
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+       if (lp->lp_slang->sl_sal.ga_len > 0)
+       {
+           // soundfold the bad word
+           spell_soundfold(lp->lp_slang, su->su_fbadword, TRUE, badsound);
+
+           for (i = 0; i < su->su_ga.ga_len; ++i)
+           {
+               stp = &SUG(su->su_ga, i);
+
+               // Case-fold the suggested word, sound-fold it and compute the
+               // sound-a-like score.
+               score = stp_sal_score(stp, su, lp->lp_slang, badsound);
+               if (score < SCORE_MAXMAX)
+               {
+                   // Add the suggestion.
+                   sstp = &SUG(su->su_sga, su->su_sga.ga_len);
+                   sstp->st_word = vim_strsave(stp->st_word);
+                   if (sstp->st_word != NULL)
+                   {
+                       sstp->st_wordlen = stp->st_wordlen;
+                       sstp->st_score = score;
+                       sstp->st_altscore = 0;
+                       sstp->st_orglen = stp->st_orglen;
+                       ++su->su_sga.ga_len;
+                   }
+               }
+           }
+           break;
+       }
+    }
+}
+
+/*
+ * Combine the list of suggestions in su->su_ga and su->su_sga.
+ * They are entwined.
+ */
+    static void
+score_combine(suginfo_T *su)
+{
+    int                i;
+    int                j;
+    garray_T   ga;
+    garray_T   *gap;
+    langp_T    *lp;
+    suggest_T  *stp;
+    char_u     *p;
+    char_u     badsound[MAXWLEN];
+    int                round;
+    int                lpi;
+    slang_T    *slang = NULL;
+
+    // Add the alternate score to su_ga.
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+       if (lp->lp_slang->sl_sal.ga_len > 0)
+       {
+           // soundfold the bad word
+           slang = lp->lp_slang;
+           spell_soundfold(slang, su->su_fbadword, TRUE, badsound);
+
+           for (i = 0; i < su->su_ga.ga_len; ++i)
+           {
+               stp = &SUG(su->su_ga, i);
+               stp->st_altscore = stp_sal_score(stp, su, slang, badsound);
+               if (stp->st_altscore == SCORE_MAXMAX)
+                   stp->st_score = (stp->st_score * 3 + SCORE_BIG) / 4;
+               else
+                   stp->st_score = (stp->st_score * 3
+                                                 + stp->st_altscore) / 4;
+               stp->st_salscore = FALSE;
+           }
+           break;
+       }
+    }
+
+    if (slang == NULL) // Using "double" without sound folding.
+    {
+       (void)cleanup_suggestions(&su->su_ga, su->su_maxscore,
+                                                            su->su_maxcount);
+       return;
+    }
+
+    // Add the alternate score to su_sga.
+    for (i = 0; i < su->su_sga.ga_len; ++i)
+    {
+       stp = &SUG(su->su_sga, i);
+       stp->st_altscore = spell_edit_score(slang,
+                                               su->su_badword, stp->st_word);
+       if (stp->st_score == SCORE_MAXMAX)
+           stp->st_score = (SCORE_BIG * 7 + stp->st_altscore) / 8;
+       else
+           stp->st_score = (stp->st_score * 7 + stp->st_altscore) / 8;
+       stp->st_salscore = TRUE;
+    }
+
+    // Remove bad suggestions, sort the suggestions and truncate at "maxcount"
+    // for both lists.
+    check_suggestions(su, &su->su_ga);
+    (void)cleanup_suggestions(&su->su_ga, su->su_maxscore, su->su_maxcount);
+    check_suggestions(su, &su->su_sga);
+    (void)cleanup_suggestions(&su->su_sga, su->su_maxscore, su->su_maxcount);
+
+    ga_init2(&ga, (int)sizeof(suginfo_T), 1);
+    if (ga_grow(&ga, su->su_ga.ga_len + su->su_sga.ga_len) == FAIL)
+       return;
+
+    stp = &SUG(ga, 0);
+    for (i = 0; i < su->su_ga.ga_len || i < su->su_sga.ga_len; ++i)
+    {
+       // round 1: get a suggestion from su_ga
+       // round 2: get a suggestion from su_sga
+       for (round = 1; round <= 2; ++round)
+       {
+           gap = round == 1 ? &su->su_ga : &su->su_sga;
+           if (i < gap->ga_len)
+           {
+               // Don't add a word if it's already there.
+               p = SUG(*gap, i).st_word;
+               for (j = 0; j < ga.ga_len; ++j)
+                   if (STRCMP(stp[j].st_word, p) == 0)
+                       break;
+               if (j == ga.ga_len)
+                   stp[ga.ga_len++] = SUG(*gap, i);
+               else
+                   vim_free(p);
+           }
+       }
+    }
+
+    ga_clear(&su->su_ga);
+    ga_clear(&su->su_sga);
+
+    // Truncate the list to the number of suggestions that will be displayed.
+    if (ga.ga_len > su->su_maxcount)
+    {
+       for (i = su->su_maxcount; i < ga.ga_len; ++i)
+           vim_free(stp[i].st_word);
+       ga.ga_len = su->su_maxcount;
+    }
+
+    su->su_ga = ga;
+}
+
+/*
+ * For the goodword in "stp" compute the soundalike score compared to the
+ * badword.
+ */
+    static int
+stp_sal_score(
+    suggest_T  *stp,
+    suginfo_T  *su,
+    slang_T    *slang,
+    char_u     *badsound)      // sound-folded badword
+{
+    char_u     *p;
+    char_u     *pbad;
+    char_u     *pgood;
+    char_u     badsound2[MAXWLEN];
+    char_u     fword[MAXWLEN];
+    char_u     goodsound[MAXWLEN];
+    char_u     goodword[MAXWLEN];
+    int                lendiff;
+
+    lendiff = (int)(su->su_badlen - stp->st_orglen);
+    if (lendiff >= 0)
+       pbad = badsound;
+    else
+    {
+       // soundfold the bad word with more characters following
+       (void)spell_casefold(su->su_badptr, stp->st_orglen, fword, MAXWLEN);
+
+       // When joining two words the sound often changes a lot.  E.g., "t he"
+       // sounds like "t h" while "the" sounds like "@".  Avoid that by
+       // removing the space.  Don't do it when the good word also contains a
+       // space.
+       if (VIM_ISWHITE(su->su_badptr[su->su_badlen])
+                                        && *skiptowhite(stp->st_word) == NUL)
+           for (p = fword; *(p = skiptowhite(p)) != NUL; )
+               STRMOVE(p, p + 1);
+
+       spell_soundfold(slang, fword, TRUE, badsound2);
+       pbad = badsound2;
+    }
+
+    if (lendiff > 0 && stp->st_wordlen + lendiff < MAXWLEN)
+    {
+       // Add part of the bad word to the good word, so that we soundfold
+       // what replaces the bad word.
+       STRCPY(goodword, stp->st_word);
+       vim_strncpy(goodword + stp->st_wordlen,
+                           su->su_badptr + su->su_badlen - lendiff, lendiff);
+       pgood = goodword;
+    }
+    else
+       pgood = stp->st_word;
+
+    // Sound-fold the word and compute the score for the difference.
+    spell_soundfold(slang, pgood, FALSE, goodsound);
+
+    return soundalike_score(goodsound, pbad);
+}
+
+// structure used to store soundfolded words that add_sound_suggest() has
+// handled already.
+typedef struct
+{
+    short      sft_score;      // lowest score used
+    char_u     sft_word[1];    // soundfolded word, actually longer
+} sftword_T;
+
+static sftword_T dumsft;
+#define HIKEY2SFT(p)  ((sftword_T *)(p - (dumsft.sft_word - (char_u *)&dumsft)))
+#define HI2SFT(hi)     HIKEY2SFT((hi)->hi_key)
+
+/*
+ * Prepare for calling suggest_try_soundalike().
+ */
+    static void
+suggest_try_soundalike_prep(void)
+{
+    langp_T    *lp;
+    int                lpi;
+    slang_T    *slang;
+
+    // Do this for all languages that support sound folding and for which a
+    // .sug file has been loaded.
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+       slang = lp->lp_slang;
+       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
+           // prepare the hashtable used by add_sound_suggest()
+           hash_init(&slang->sl_sounddone);
+    }
+}
+
+/*
+ * Find suggestions by comparing the word in a sound-a-like form.
+ * Note: This doesn't support postponed prefixes.
+ */
+    static void
+suggest_try_soundalike(suginfo_T *su)
+{
+    char_u     salword[MAXWLEN];
+    langp_T    *lp;
+    int                lpi;
+    slang_T    *slang;
+
+    // Do this for all languages that support sound folding and for which a
+    // .sug file has been loaded.
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+       slang = lp->lp_slang;
+       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
+       {
+           // soundfold the bad word
+           spell_soundfold(slang, su->su_fbadword, TRUE, salword);
+
+           // try all kinds of inserts/deletes/swaps/etc.
+           // TODO: also soundfold the next words, so that we can try joining
+           // and splitting
+#ifdef SUGGEST_PROFILE
+       prof_init();
+#endif
+           suggest_trie_walk(su, lp, salword, TRUE);
+#ifdef SUGGEST_PROFILE
+       prof_report("soundalike");
+#endif
+       }
+    }
+}
+
+/*
+ * Finish up after calling suggest_try_soundalike().
+ */
+    static void
+suggest_try_soundalike_finish(void)
+{
+    langp_T    *lp;
+    int                lpi;
+    slang_T    *slang;
+    int                todo;
+    hashitem_T *hi;
+
+    // Do this for all languages that support sound folding and for which a
+    // .sug file has been loaded.
+    for (lpi = 0; lpi < curwin->w_s->b_langp.ga_len; ++lpi)
+    {
+       lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+       slang = lp->lp_slang;
+       if (slang->sl_sal.ga_len > 0 && slang->sl_sbyts != NULL)
+       {
+           // Free the info about handled words.
+           todo = (int)slang->sl_sounddone.ht_used;
+           for (hi = slang->sl_sounddone.ht_array; todo > 0; ++hi)
+               if (!HASHITEM_EMPTY(hi))
+               {
+                   vim_free(HI2SFT(hi));
+                   --todo;
+               }
+
+           // Clear the hashtable, it may also be used by another region.
+           hash_clear(&slang->sl_sounddone);
+           hash_init(&slang->sl_sounddone);
+       }
+    }
+}
+
+/*
+ * A match with a soundfolded word is found.  Add the good word(s) that
+ * produce this soundfolded word.
+ */
+    static void
+add_sound_suggest(
+    suginfo_T  *su,
+    char_u     *goodword,
+    int                score,          // soundfold score
+    langp_T    *lp)
+{
+    slang_T    *slang = lp->lp_slang;  // language for sound folding
+    int                sfwordnr;
+    char_u     *nrline;
+    int                orgnr;
+    char_u     theword[MAXWLEN];
+    int                i;
+    int                wlen;
+    char_u     *byts;
+    idx_T      *idxs;
+    int                n;
+    int                wordcount;
+    int                wc;
+    int                goodscore;
+    hash_T     hash;
+    hashitem_T  *hi;
+    sftword_T  *sft;
+    int                bc, gc;
+    int                limit;
+
+    // It's very well possible that the same soundfold word is found several
+    // times with different scores.  Since the following is quite slow only do
+    // the words that have a better score than before.  Use a hashtable to
+    // remember the words that have been done.
+    hash = hash_hash(goodword);
+    hi = hash_lookup(&slang->sl_sounddone, goodword, hash);
+    if (HASHITEM_EMPTY(hi))
+    {
+       sft = alloc(sizeof(sftword_T) + STRLEN(goodword));
+       if (sft != NULL)
+       {
+           sft->sft_score = score;
+           STRCPY(sft->sft_word, goodword);
+           hash_add_item(&slang->sl_sounddone, hi, sft->sft_word, hash);
+       }
+    }
+    else
+    {
+       sft = HI2SFT(hi);
+       if (score >= sft->sft_score)
+           return;
+       sft->sft_score = score;
+    }
+
+    // Find the word nr in the soundfold tree.
+    sfwordnr = soundfold_find(slang, goodword);
+    if (sfwordnr < 0)
+    {
+       internal_error("add_sound_suggest()");
+       return;
+    }
+
+    // go over the list of good words that produce this soundfold word
+    nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)(sfwordnr + 1), FALSE);
+    orgnr = 0;
+    while (*nrline != NUL)
+    {
+       // The wordnr was stored in a minimal nr of bytes as an offset to the
+       // previous wordnr.
+       orgnr += bytes2offset(&nrline);
+
+       byts = slang->sl_fbyts;
+       idxs = slang->sl_fidxs;
+
+       // Lookup the word "orgnr" one of the two tries.
+       n = 0;
+       wordcount = 0;
+       for (wlen = 0; wlen < MAXWLEN - 3; ++wlen)
+       {
+           i = 1;
+           if (wordcount == orgnr && byts[n + 1] == NUL)
+               break;  // found end of word
+
+           if (byts[n + 1] == NUL)
+               ++wordcount;
+
+           // skip over the NUL bytes
+           for ( ; byts[n + i] == NUL; ++i)
+               if (i > byts[n])        // safety check
+               {
+                   STRCPY(theword + wlen, "BAD");
+                   wlen += 3;
+                   goto badword;
+               }
+
+           // One of the siblings must have the word.
+           for ( ; i < byts[n]; ++i)
+           {
+               wc = idxs[idxs[n + i]]; // nr of words under this byte
+               if (wordcount + wc > orgnr)
+                   break;
+               wordcount += wc;
+           }
+
+           theword[wlen] = byts[n + i];
+           n = idxs[n + i];
+       }
+badword:
+       theword[wlen] = NUL;
+
+       // Go over the possible flags and regions.
+       for (; i <= byts[n] && byts[n + i] == NUL; ++i)
+       {
+           char_u      cword[MAXWLEN];
+           char_u      *p;
+           int         flags = (int)idxs[n + i];
+
+           // Skip words with the NOSUGGEST flag
+           if (flags & WF_NOSUGGEST)
+               continue;
+
+           if (flags & WF_KEEPCAP)
+           {
+               // Must find the word in the keep-case tree.
+               find_keepcap_word(slang, theword, cword);
+               p = cword;
+           }
+           else
+           {
+               flags |= su->su_badflags;
+               if ((flags & WF_CAPMASK) != 0)
+               {
+                   // Need to fix case according to "flags".
+                   make_case_word(theword, cword, flags);
+                   p = cword;
+               }
+               else
+                   p = theword;
+           }
+
+           // Add the suggestion.
+           if (sps_flags & SPS_DOUBLE)
+           {
+               // Add the suggestion if the score isn't too bad.
+               if (score <= su->su_maxscore)
+                   add_suggestion(su, &su->su_sga, p, su->su_badlen,
+                                              score, 0, FALSE, slang, FALSE);
+           }
+           else
+           {
+               // Add a penalty for words in another region.
+               if ((flags & WF_REGION)
+                           && (((unsigned)flags >> 16) & lp->lp_region) == 0)
+                   goodscore = SCORE_REGION;
+               else
+                   goodscore = 0;
+
+               // Add a small penalty for changing the first letter from
+               // lower to upper case.  Helps for "tath" -> "Kath", which is
+               // less common than "tath" -> "path".  Don't do it when the
+               // letter is the same, that has already been counted.
+               gc = PTR2CHAR(p);
+               if (SPELL_ISUPPER(gc))
+               {
+                   bc = PTR2CHAR(su->su_badword);
+                   if (!SPELL_ISUPPER(bc)
+                                     && SPELL_TOFOLD(bc) != SPELL_TOFOLD(gc))
+                       goodscore += SCORE_ICASE / 2;
+               }
+
+               // Compute the score for the good word.  This only does letter
+               // insert/delete/swap/replace.  REP items are not considered,
+               // which may make the score a bit higher.
+               // Use a limit for the score to make it work faster.  Use
+               // MAXSCORE(), because RESCORE() will change the score.
+               // If the limit is very high then the iterative method is
+               // inefficient, using an array is quicker.
+               limit = MAXSCORE(su->su_sfmaxscore - goodscore, score);
+               if (limit > SCORE_LIMITMAX)
+                   goodscore += spell_edit_score(slang, su->su_badword, p);
+               else
+                   goodscore += spell_edit_score_limit(slang, su->su_badword,
+                                                                   p, limit);
+
+               // When going over the limit don't bother to do the rest.
+               if (goodscore < SCORE_MAXMAX)
+               {
+                   // Give a bonus to words seen before.
+                   goodscore = score_wordcount_adj(slang, goodscore, p, FALSE);
+
+                   // Add the suggestion if the score isn't too bad.
+                   goodscore = RESCORE(goodscore, score);
+                   if (goodscore <= su->su_sfmaxscore)
+                       add_suggestion(su, &su->su_ga, p, su->su_badlen,
+                                        goodscore, score, TRUE, slang, TRUE);
+               }
+           }
+       }
+       // smsg("word %s (%d): %s (%d)", sftword, sftnr, theword, orgnr);
+    }
+}
+
+/*
+ * Find word "word" in fold-case tree for "slang" and return the word number.
+ */
+    static int
+soundfold_find(slang_T *slang, char_u *word)
+{
+    idx_T      arridx = 0;
+    int                len;
+    int                wlen = 0;
+    int                c;
+    char_u     *ptr = word;
+    char_u     *byts;
+    idx_T      *idxs;
+    int                wordnr = 0;
+
+    byts = slang->sl_sbyts;
+    idxs = slang->sl_sidxs;
+
+    for (;;)
+    {
+       // First byte is the number of possible bytes.
+       len = byts[arridx++];
+
+       // If the first possible byte is a zero the word could end here.
+       // If the word ends we found the word.  If not skip the NUL bytes.
+       c = ptr[wlen];
+       if (byts[arridx] == NUL)
+       {
+           if (c == NUL)
+               break;
+
+           // Skip over the zeros, there can be several.
+           while (len > 0 && byts[arridx] == NUL)
+           {
+               ++arridx;
+               --len;
+           }
+           if (len == 0)
+               return -1;    // no children, word should have ended here
+           ++wordnr;
+       }
+
+       // If the word ends we didn't find it.
+       if (c == NUL)
+           return -1;
+
+       // Perform a binary search in the list of accepted bytes.
+       if (c == TAB)       // <Tab> is handled like <Space>
+           c = ' ';
+       while (byts[arridx] < c)
+       {
+           // The word count is in the first idxs[] entry of the child.
+           wordnr += idxs[idxs[arridx]];
+           ++arridx;
+           if (--len == 0)     // end of the bytes, didn't find it
+               return -1;
+       }
+       if (byts[arridx] != c)  // didn't find the byte
+           return -1;
+
+       // Continue at the child (if there is one).
+       arridx = idxs[arridx];
+       ++wlen;
+
+       // One space in the good word may stand for several spaces in the
+       // checked word.
+       if (c == ' ')
+           while (ptr[wlen] == ' ' || ptr[wlen] == TAB)
+               ++wlen;
+    }
+
+    return wordnr;
+}
+
+/*
+ * Return TRUE if "c1" and "c2" are similar characters according to the MAP
+ * lines in the .aff file.
+ */
+    static int
+similar_chars(slang_T *slang, int c1, int c2)
+{
+    int                m1, m2;
+    char_u     buf[MB_MAXBYTES + 1];
+    hashitem_T  *hi;
+
+    if (c1 >= 256)
+    {
+       buf[mb_char2bytes(c1, buf)] = 0;
+       hi = hash_find(&slang->sl_map_hash, buf);
+       if (HASHITEM_EMPTY(hi))
+           m1 = 0;
+       else
+           m1 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1);
+    }
+    else
+       m1 = slang->sl_map_array[c1];
+    if (m1 == 0)
+       return FALSE;
+
+
+    if (c2 >= 256)
+    {
+       buf[mb_char2bytes(c2, buf)] = 0;
+       hi = hash_find(&slang->sl_map_hash, buf);
+       if (HASHITEM_EMPTY(hi))
+           m2 = 0;
+       else
+           m2 = mb_ptr2char(hi->hi_key + STRLEN(hi->hi_key) + 1);
+    }
+    else
+       m2 = slang->sl_map_array[c2];
+
+    return m1 == m2;
+}
+
+/*
+ * Add a suggestion to the list of suggestions.
+ * For a suggestion that is already in the list the lowest score is remembered.
+ */
+    static void
+add_suggestion(
+    suginfo_T  *su,
+    garray_T   *gap,           // either su_ga or su_sga
+    char_u     *goodword,
+    int                badlenarg,      // len of bad word replaced with "goodword"
+    int                score,
+    int                altscore,
+    int                had_bonus,      // value for st_had_bonus
+    slang_T    *slang,         // language for sound folding
+    int                maxsf)          // su_maxscore applies to soundfold score,
+                               // su_sfmaxscore to the total score.
+{
+    int                goodlen;        // len of goodword changed
+    int                badlen;         // len of bad word changed
+    suggest_T   *stp;
+    suggest_T   new_sug;
+    int                i;
+    char_u     *pgood, *pbad;
+
+    // Minimize "badlen" for consistency.  Avoids that changing "the the" to
+    // "thee the" is added next to changing the first "the" the "thee".
+    pgood = goodword + STRLEN(goodword);
+    pbad = su->su_badptr + badlenarg;
+    for (;;)
+    {
+       goodlen = (int)(pgood - goodword);
+       badlen = (int)(pbad - su->su_badptr);
+       if (goodlen <= 0 || badlen <= 0)
+           break;
+       MB_PTR_BACK(goodword, pgood);
+       MB_PTR_BACK(su->su_badptr, pbad);
+       if (has_mbyte)
+       {
+           if (mb_ptr2char(pgood) != mb_ptr2char(pbad))
+               break;
+       }
+       else if (*pgood != *pbad)
+               break;
+    }
+
+    if (badlen == 0 && goodlen == 0)
+       // goodword doesn't change anything; may happen for "the the" changing
+       // the first "the" to itself.
+       return;
+
+    if (gap->ga_len == 0)
+       i = -1;
+    else
+    {
+       // Check if the word is already there.  Also check the length that is
+       // being replaced "thes," -> "these" is a different suggestion from
+       // "thes" -> "these".
+       stp = &SUG(*gap, 0);
+       for (i = gap->ga_len; --i >= 0; ++stp)
+           if (stp->st_wordlen == goodlen
+                   && stp->st_orglen == badlen
+                   && STRNCMP(stp->st_word, goodword, goodlen) == 0)
+           {
+               // Found it.  Remember the word with the lowest score.
+               if (stp->st_slang == NULL)
+                   stp->st_slang = slang;
+
+               new_sug.st_score = score;
+               new_sug.st_altscore = altscore;
+               new_sug.st_had_bonus = had_bonus;
+
+               if (stp->st_had_bonus != had_bonus)
+               {
+                   // Only one of the two had the soundalike score computed.
+                   // Need to do that for the other one now, otherwise the
+                   // scores can't be compared.  This happens because
+                   // suggest_try_change() doesn't compute the soundalike
+                   // word to keep it fast, while some special methods set
+                   // the soundalike score to zero.
+                   if (had_bonus)
+                       rescore_one(su, stp);
+                   else
+                   {
+                       new_sug.st_word = stp->st_word;
+                       new_sug.st_wordlen = stp->st_wordlen;
+                       new_sug.st_slang = stp->st_slang;
+                       new_sug.st_orglen = badlen;
+                       rescore_one(su, &new_sug);
+                   }
+               }
+
+               if (stp->st_score > new_sug.st_score)
+               {
+                   stp->st_score = new_sug.st_score;
+                   stp->st_altscore = new_sug.st_altscore;
+                   stp->st_had_bonus = new_sug.st_had_bonus;
+               }
+               break;
+           }
+    }
+
+    if (i < 0 && ga_grow(gap, 1) == OK)
+    {
+       // Add a suggestion.
+       stp = &SUG(*gap, gap->ga_len);
+       stp->st_word = vim_strnsave(goodword, goodlen);
+       if (stp->st_word != NULL)
+       {
+           stp->st_wordlen = goodlen;
+           stp->st_score = score;
+           stp->st_altscore = altscore;
+           stp->st_had_bonus = had_bonus;
+           stp->st_orglen = badlen;
+           stp->st_slang = slang;
+           ++gap->ga_len;
+
+           // If we have too many suggestions now, sort the list and keep
+           // the best suggestions.
+           if (gap->ga_len > SUG_MAX_COUNT(su))
+           {
+               if (maxsf)
+                   su->su_sfmaxscore = cleanup_suggestions(gap,
+                                     su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
+               else
+                   su->su_maxscore = cleanup_suggestions(gap,
+                                       su->su_maxscore, SUG_CLEAN_COUNT(su));
+           }
+       }
+    }
+}
+
+/*
+ * Suggestions may in fact be flagged as errors.  Esp. for banned words and
+ * for split words, such as "the the".  Remove these from the list here.
+ */
+    static void
+check_suggestions(
+    suginfo_T  *su,
+    garray_T   *gap)               // either su_ga or su_sga
+{
+    suggest_T   *stp;
+    int                i;
+    char_u     longword[MAXWLEN + 1];
+    int                len;
+    hlf_T      attr;
+
+    stp = &SUG(*gap, 0);
+    for (i = gap->ga_len - 1; i >= 0; --i)
+    {
+       // Need to append what follows to check for "the the".
+       vim_strncpy(longword, stp[i].st_word, MAXWLEN);
+       len = stp[i].st_wordlen;
+       vim_strncpy(longword + len, su->su_badptr + stp[i].st_orglen,
+                                                              MAXWLEN - len);
+       attr = HLF_COUNT;
+       (void)spell_check(curwin, longword, &attr, NULL, FALSE);
+       if (attr != HLF_COUNT)
+       {
+           // Remove this entry.
+           vim_free(stp[i].st_word);
+           --gap->ga_len;
+           if (i < gap->ga_len)
+               mch_memmove(stp + i, stp + i + 1,
+                                      sizeof(suggest_T) * (gap->ga_len - i));
+       }
+    }
+}
+
+
+/*
+ * Add a word to be banned.
+ */
+    static void
+add_banned(
+    suginfo_T  *su,
+    char_u     *word)
+{
+    char_u     *s;
+    hash_T     hash;
+    hashitem_T *hi;
+
+    hash = hash_hash(word);
+    hi = hash_lookup(&su->su_banned, word, hash);
+    if (HASHITEM_EMPTY(hi))
+    {
+       s = vim_strsave(word);
+       if (s != NULL)
+           hash_add_item(&su->su_banned, hi, s, hash);
+    }
+}
+
+/*
+ * Recompute the score for all suggestions if sound-folding is possible.  This
+ * is slow, thus only done for the final results.
+ */
+    static void
+rescore_suggestions(suginfo_T *su)
+{
+    int                i;
+
+    if (su->su_sallang != NULL)
+       for (i = 0; i < su->su_ga.ga_len; ++i)
+           rescore_one(su, &SUG(su->su_ga, i));
+}
+
+/*
+ * Recompute the score for one suggestion if sound-folding is possible.
+ */
+    static void
+rescore_one(suginfo_T *su, suggest_T *stp)
+{
+    slang_T    *slang = stp->st_slang;
+    char_u     sal_badword[MAXWLEN];
+    char_u     *p;
+
+    // Only rescore suggestions that have no sal score yet and do have a
+    // language.
+    if (slang != NULL && slang->sl_sal.ga_len > 0 && !stp->st_had_bonus)
+    {
+       if (slang == su->su_sallang)
+           p = su->su_sal_badword;
+       else
+       {
+           spell_soundfold(slang, su->su_fbadword, TRUE, sal_badword);
+           p = sal_badword;
+       }
+
+       stp->st_altscore = stp_sal_score(stp, su, slang, p);
+       if (stp->st_altscore == SCORE_MAXMAX)
+           stp->st_altscore = SCORE_BIG;
+       stp->st_score = RESCORE(stp->st_score, stp->st_altscore);
+       stp->st_had_bonus = TRUE;
+    }
+}
+
+static int sug_compare(const void *s1, const void *s2);
+
+/*
+ * Function given to qsort() to sort the suggestions on st_score.
+ * First on "st_score", then "st_altscore" then alphabetically.
+ */
+    static int
+sug_compare(const void *s1, const void *s2)
+{
+    suggest_T  *p1 = (suggest_T *)s1;
+    suggest_T  *p2 = (suggest_T *)s2;
+    int                n = p1->st_score - p2->st_score;
+
+    if (n == 0)
+    {
+       n = p1->st_altscore - p2->st_altscore;
+       if (n == 0)
+           n = STRICMP(p1->st_word, p2->st_word);
+    }
+    return n;
+}
+
+/*
+ * Cleanup the suggestions:
+ * - Sort on score.
+ * - Remove words that won't be displayed.
+ * Returns the maximum score in the list or "maxscore" unmodified.
+ */
+    static int
+cleanup_suggestions(
+    garray_T   *gap,
+    int                maxscore,
+    int                keep)           // nr of suggestions to keep
+{
+    suggest_T   *stp = &SUG(*gap, 0);
+    int                i;
+
+    // Sort the list.
+    qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
+
+    // Truncate the list to the number of suggestions that will be displayed.
+    if (gap->ga_len > keep)
+    {
+       for (i = keep; i < gap->ga_len; ++i)
+           vim_free(stp[i].st_word);
+       gap->ga_len = keep;
+       return stp[keep - 1].st_score;
+    }
+    return maxscore;
+}
+
+/*
+ * Compute a score for two sound-a-like words.
+ * This permits up to two inserts/deletes/swaps/etc. to keep things fast.
+ * Instead of a generic loop we write out the code.  That keeps it fast by
+ * avoiding checks that will not be possible.
+ */
+    static int
+soundalike_score(
+    char_u     *goodstart,     // sound-folded good word
+    char_u     *badstart)      // sound-folded bad word
+{
+    char_u     *goodsound = goodstart;
+    char_u     *badsound = badstart;
+    int                goodlen;
+    int                badlen;
+    int                n;
+    char_u     *pl, *ps;
+    char_u     *pl2, *ps2;
+    int                score = 0;
+
+    // Adding/inserting "*" at the start (word starts with vowel) shouldn't be
+    // counted so much, vowels halfway the word aren't counted at all.
+    if ((*badsound == '*' || *goodsound == '*') && *badsound != *goodsound)
+    {
+       if ((badsound[0] == NUL && goodsound[1] == NUL)
+           || (goodsound[0] == NUL && badsound[1] == NUL))
+           // changing word with vowel to word without a sound
+           return SCORE_DEL;
+       if (badsound[0] == NUL || goodsound[0] == NUL)
+           // more than two changes
+           return SCORE_MAXMAX;
+
+       if (badsound[1] == goodsound[1]
+               || (badsound[1] != NUL
+                   && goodsound[1] != NUL
+                   && badsound[2] == goodsound[2]))
+       {
+           // handle like a substitute
+       }
+       else
+       {
+           score = 2 * SCORE_DEL / 3;
+           if (*badsound == '*')
+               ++badsound;
+           else
+               ++goodsound;
+       }
+    }
+
+    goodlen = (int)STRLEN(goodsound);
+    badlen = (int)STRLEN(badsound);
+
+    // Return quickly if the lengths are too different to be fixed by two
+    // changes.
+    n = goodlen - badlen;
+    if (n < -2 || n > 2)
+       return SCORE_MAXMAX;
+
+    if (n > 0)
+    {
+       pl = goodsound;     // goodsound is longest
+       ps = badsound;
+    }
+    else
+    {
+       pl = badsound;      // badsound is longest
+       ps = goodsound;
+    }
+
+    // Skip over the identical part.
+    while (*pl == *ps && *pl != NUL)
+    {
+       ++pl;
+       ++ps;
+    }
+
+    switch (n)
+    {
+       case -2:
+       case 2:
+           // Must delete two characters from "pl".
+           ++pl;       // first delete
+           while (*pl == *ps)
+           {
+               ++pl;
+               ++ps;
+           }
+           // strings must be equal after second delete
+           if (STRCMP(pl + 1, ps) == 0)
+               return score + SCORE_DEL * 2;
+
+           // Failed to compare.
+           break;
+
+       case -1:
+       case 1:
+           // Minimal one delete from "pl" required.
+
+           // 1: delete
+           pl2 = pl + 1;
+           ps2 = ps;
+           while (*pl2 == *ps2)
+           {
+               if (*pl2 == NUL)        // reached the end
+                   return score + SCORE_DEL;
+               ++pl2;
+               ++ps2;
+           }
+
+           // 2: delete then swap, then rest must be equal
+           if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
+               return score + SCORE_DEL + SCORE_SWAP;
+
+           // 3: delete then substitute, then the rest must be equal
+           if (STRCMP(pl2 + 1, ps2 + 1) == 0)
+               return score + SCORE_DEL + SCORE_SUBST;
+
+           // 4: first swap then delete
+           if (pl[0] == ps[1] && pl[1] == ps[0])
+           {
+               pl2 = pl + 2;       // swap, skip two chars
+               ps2 = ps + 2;
+               while (*pl2 == *ps2)
+               {
+                   ++pl2;
+                   ++ps2;
+               }
+               // delete a char and then strings must be equal
+               if (STRCMP(pl2 + 1, ps2) == 0)
+                   return score + SCORE_SWAP + SCORE_DEL;
+           }
+
+           // 5: first substitute then delete
+           pl2 = pl + 1;           // substitute, skip one char
+           ps2 = ps + 1;
+           while (*pl2 == *ps2)
+           {
+               ++pl2;
+               ++ps2;
+           }
+           // delete a char and then strings must be equal
+           if (STRCMP(pl2 + 1, ps2) == 0)
+               return score + SCORE_SUBST + SCORE_DEL;
+
+           // Failed to compare.
+           break;
+
+       case 0:
+           // Lengths are equal, thus changes must result in same length: An
+           // insert is only possible in combination with a delete.
+           // 1: check if for identical strings
+           if (*pl == NUL)
+               return score;
+
+           // 2: swap
+           if (pl[0] == ps[1] && pl[1] == ps[0])
+           {
+               pl2 = pl + 2;       // swap, skip two chars
+               ps2 = ps + 2;
+               while (*pl2 == *ps2)
+               {
+                   if (*pl2 == NUL)    // reached the end
+                       return score + SCORE_SWAP;
+                   ++pl2;
+                   ++ps2;
+               }
+               // 3: swap and swap again
+               if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
+                   return score + SCORE_SWAP + SCORE_SWAP;
+
+               // 4: swap and substitute
+               if (STRCMP(pl2 + 1, ps2 + 1) == 0)
+                   return score + SCORE_SWAP + SCORE_SUBST;
+           }
+
+           // 5: substitute
+           pl2 = pl + 1;
+           ps2 = ps + 1;
+           while (*pl2 == *ps2)
+           {
+               if (*pl2 == NUL)        // reached the end
+                   return score + SCORE_SUBST;
+               ++pl2;
+               ++ps2;
+           }
+
+           // 6: substitute and swap
+           if (pl2[0] == ps2[1] && pl2[1] == ps2[0]
+                                            && STRCMP(pl2 + 2, ps2 + 2) == 0)
+               return score + SCORE_SUBST + SCORE_SWAP;
+
+           // 7: substitute and substitute
+           if (STRCMP(pl2 + 1, ps2 + 1) == 0)
+               return score + SCORE_SUBST + SCORE_SUBST;
+
+           // 8: insert then delete
+           pl2 = pl;
+           ps2 = ps + 1;
+           while (*pl2 == *ps2)
+           {
+               ++pl2;
+               ++ps2;
+           }
+           if (STRCMP(pl2 + 1, ps2) == 0)
+               return score + SCORE_INS + SCORE_DEL;
+
+           // 9: delete then insert
+           pl2 = pl + 1;
+           ps2 = ps;
+           while (*pl2 == *ps2)
+           {
+               ++pl2;
+               ++ps2;
+           }
+           if (STRCMP(pl2, ps2 + 1) == 0)
+               return score + SCORE_INS + SCORE_DEL;
+
+           // Failed to compare.
+           break;
+    }
+
+    return SCORE_MAXMAX;
+}
+
+/*
+ * Compute the "edit distance" to turn "badword" into "goodword".  The less
+ * deletes/inserts/substitutes/swaps are required the lower the score.
+ *
+ * The algorithm is described by Du and Chang, 1992.
+ * The implementation of the algorithm comes from Aspell editdist.cpp,
+ * edit_distance().  It has been converted from C++ to C and modified to
+ * support multi-byte characters.
+ */
+    static int
+spell_edit_score(
+    slang_T    *slang,
+    char_u     *badword,
+    char_u     *goodword)
+{
+    int                *cnt;
+    int                badlen, goodlen;        // lengths including NUL
+    int                j, i;
+    int                t;
+    int                bc, gc;
+    int                pbc, pgc;
+    char_u     *p;
+    int                wbadword[MAXWLEN];
+    int                wgoodword[MAXWLEN];
+
+    if (has_mbyte)
+    {
+       // Get the characters from the multi-byte strings and put them in an
+       // int array for easy access.
+       for (p = badword, badlen = 0; *p != NUL; )
+           wbadword[badlen++] = mb_cptr2char_adv(&p);
+       wbadword[badlen++] = 0;
+       for (p = goodword, goodlen = 0; *p != NUL; )
+           wgoodword[goodlen++] = mb_cptr2char_adv(&p);
+       wgoodword[goodlen++] = 0;
+    }
+    else
+    {
+       badlen = (int)STRLEN(badword) + 1;
+       goodlen = (int)STRLEN(goodword) + 1;
+    }
+
+    // We use "cnt" as an array: CNT(badword_idx, goodword_idx).
+#define CNT(a, b)   cnt[(a) + (b) * (badlen + 1)]
+    cnt = ALLOC_MULT(int, (badlen + 1) * (goodlen + 1));
+    if (cnt == NULL)
+       return 0;       // out of memory
+
+    CNT(0, 0) = 0;
+    for (j = 1; j <= goodlen; ++j)
+       CNT(0, j) = CNT(0, j - 1) + SCORE_INS;
+
+    for (i = 1; i <= badlen; ++i)
+    {
+       CNT(i, 0) = CNT(i - 1, 0) + SCORE_DEL;
+       for (j = 1; j <= goodlen; ++j)
+       {
+           if (has_mbyte)
+           {
+               bc = wbadword[i - 1];
+               gc = wgoodword[j - 1];
+           }
+           else
+           {
+               bc = badword[i - 1];
+               gc = goodword[j - 1];
+           }
+           if (bc == gc)
+               CNT(i, j) = CNT(i - 1, j - 1);
+           else
+           {
+               // Use a better score when there is only a case difference.
+               if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
+                   CNT(i, j) = SCORE_ICASE + CNT(i - 1, j - 1);
+               else
+               {
+                   // For a similar character use SCORE_SIMILAR.
+                   if (slang != NULL
+                           && slang->sl_has_map
+                           && similar_chars(slang, gc, bc))
+                       CNT(i, j) = SCORE_SIMILAR + CNT(i - 1, j - 1);
+                   else
+                       CNT(i, j) = SCORE_SUBST + CNT(i - 1, j - 1);
+               }
+
+               if (i > 1 && j > 1)
+               {
+                   if (has_mbyte)
+                   {
+                       pbc = wbadword[i - 2];
+                       pgc = wgoodword[j - 2];
+                   }
+                   else
+                   {
+                       pbc = badword[i - 2];
+                       pgc = goodword[j - 2];
+                   }
+                   if (bc == pgc && pbc == gc)
+                   {
+                       t = SCORE_SWAP + CNT(i - 2, j - 2);
+                       if (t < CNT(i, j))
+                           CNT(i, j) = t;
+                   }
+               }
+               t = SCORE_DEL + CNT(i - 1, j);
+               if (t < CNT(i, j))
+                   CNT(i, j) = t;
+               t = SCORE_INS + CNT(i, j - 1);
+               if (t < CNT(i, j))
+                   CNT(i, j) = t;
+           }
+       }
+    }
+
+    i = CNT(badlen - 1, goodlen - 1);
+    vim_free(cnt);
+    return i;
+}
+
+typedef struct
+{
+    int                badi;
+    int                goodi;
+    int                score;
+} limitscore_T;
+
+/*
+ * Like spell_edit_score(), but with a limit on the score to make it faster.
+ * May return SCORE_MAXMAX when the score is higher than "limit".
+ *
+ * This uses a stack for the edits still to be tried.
+ * The idea comes from Aspell leditdist.cpp.  Rewritten in C and added support
+ * for multi-byte characters.
+ */
+    static int
+spell_edit_score_limit(
+    slang_T    *slang,
+    char_u     *badword,
+    char_u     *goodword,
+    int                limit)
+{
+    limitscore_T    stack[10];         // allow for over 3 * 2 edits
+    int                    stackidx;
+    int                    bi, gi;
+    int                    bi2, gi2;
+    int                    bc, gc;
+    int                    score;
+    int                    score_off;
+    int                    minscore;
+    int                    round;
+
+    // Multi-byte characters require a bit more work, use a different function
+    // to avoid testing "has_mbyte" quite often.
+    if (has_mbyte)
+       return spell_edit_score_limit_w(slang, badword, goodword, limit);
+
+    // The idea is to go from start to end over the words.  So long as
+    // characters are equal just continue, this always gives the lowest score.
+    // When there is a difference try several alternatives.  Each alternative
+    // increases "score" for the edit distance.  Some of the alternatives are
+    // pushed unto a stack and tried later, some are tried right away.  At the
+    // end of the word the score for one alternative is known.  The lowest
+    // possible score is stored in "minscore".
+    stackidx = 0;
+    bi = 0;
+    gi = 0;
+    score = 0;
+    minscore = limit + 1;
+
+    for (;;)
+    {
+       // Skip over an equal part, score remains the same.
+       for (;;)
+       {
+           bc = badword[bi];
+           gc = goodword[gi];
+           if (bc != gc)       // stop at a char that's different
+               break;
+           if (bc == NUL)      // both words end
+           {
+               if (score < minscore)
+                   minscore = score;
+               goto pop;       // do next alternative
+           }
+           ++bi;
+           ++gi;
+       }
+
+       if (gc == NUL)    // goodword ends, delete badword chars
+       {
+           do
+           {
+               if ((score += SCORE_DEL) >= minscore)
+                   goto pop;       // do next alternative
+           } while (badword[++bi] != NUL);
+           minscore = score;
+       }
+       else if (bc == NUL) // badword ends, insert badword chars
+       {
+           do
+           {
+               if ((score += SCORE_INS) >= minscore)
+                   goto pop;       // do next alternative
+           } while (goodword[++gi] != NUL);
+           minscore = score;
+       }
+       else                    // both words continue
+       {
+           // If not close to the limit, perform a change.  Only try changes
+           // that may lead to a lower score than "minscore".
+           // round 0: try deleting a char from badword
+           // round 1: try inserting a char in badword
+           for (round = 0; round <= 1; ++round)
+           {
+               score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
+               if (score_off < minscore)
+               {
+                   if (score_off + SCORE_EDIT_MIN >= minscore)
+                   {
+                       // Near the limit, rest of the words must match.  We
+                       // can check that right now, no need to push an item
+                       // onto the stack.
+                       bi2 = bi + 1 - round;
+                       gi2 = gi + round;
+                       while (goodword[gi2] == badword[bi2])
+                       {
+                           if (goodword[gi2] == NUL)
+                           {
+                               minscore = score_off;
+                               break;
+                           }
+                           ++bi2;
+                           ++gi2;
+                       }
+                   }
+                   else
+                   {
+                       // try deleting/inserting a character later
+                       stack[stackidx].badi = bi + 1 - round;
+                       stack[stackidx].goodi = gi + round;
+                       stack[stackidx].score = score_off;
+                       ++stackidx;
+                   }
+               }
+           }
+
+           if (score + SCORE_SWAP < minscore)
+           {
+               // If swapping two characters makes a match then the
+               // substitution is more expensive, thus there is no need to
+               // try both.
+               if (gc == badword[bi + 1] && bc == goodword[gi + 1])
+               {
+                   // Swap two characters, that is: skip them.
+                   gi += 2;
+                   bi += 2;
+                   score += SCORE_SWAP;
+                   continue;
+               }
+           }
+
+           // Substitute one character for another which is the same
+           // thing as deleting a character from both goodword and badword.
+           // Use a better score when there is only a case difference.
+           if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
+               score += SCORE_ICASE;
+           else
+           {
+               // For a similar character use SCORE_SIMILAR.
+               if (slang != NULL
+                       && slang->sl_has_map
+                       && similar_chars(slang, gc, bc))
+                   score += SCORE_SIMILAR;
+               else
+                   score += SCORE_SUBST;
+           }
+
+           if (score < minscore)
+           {
+               // Do the substitution.
+               ++gi;
+               ++bi;
+               continue;
+           }
+       }
+pop:
+       // Get here to try the next alternative, pop it from the stack.
+       if (stackidx == 0)              // stack is empty, finished
+           break;
+
+       // pop an item from the stack
+       --stackidx;
+       gi = stack[stackidx].goodi;
+       bi = stack[stackidx].badi;
+       score = stack[stackidx].score;
+    }
+
+    // When the score goes over "limit" it may actually be much higher.
+    // Return a very large number to avoid going below the limit when giving a
+    // bonus.
+    if (minscore > limit)
+       return SCORE_MAXMAX;
+    return minscore;
+}
+
+/*
+ * Multi-byte version of spell_edit_score_limit().
+ * Keep it in sync with the above!
+ */
+    static int
+spell_edit_score_limit_w(
+    slang_T    *slang,
+    char_u     *badword,
+    char_u     *goodword,
+    int                limit)
+{
+    limitscore_T    stack[10];         // allow for over 3 * 2 edits
+    int                    stackidx;
+    int                    bi, gi;
+    int                    bi2, gi2;
+    int                    bc, gc;
+    int                    score;
+    int                    score_off;
+    int                    minscore;
+    int                    round;
+    char_u         *p;
+    int                    wbadword[MAXWLEN];
+    int                    wgoodword[MAXWLEN];
+
+    // Get the characters from the multi-byte strings and put them in an
+    // int array for easy access.
+    bi = 0;
+    for (p = badword; *p != NUL; )
+       wbadword[bi++] = mb_cptr2char_adv(&p);
+    wbadword[bi++] = 0;
+    gi = 0;
+    for (p = goodword; *p != NUL; )
+       wgoodword[gi++] = mb_cptr2char_adv(&p);
+    wgoodword[gi++] = 0;
+
+    // The idea is to go from start to end over the words.  So long as
+    // characters are equal just continue, this always gives the lowest score.
+    // When there is a difference try several alternatives.  Each alternative
+    // increases "score" for the edit distance.  Some of the alternatives are
+    // pushed unto a stack and tried later, some are tried right away.  At the
+    // end of the word the score for one alternative is known.  The lowest
+    // possible score is stored in "minscore".
+    stackidx = 0;
+    bi = 0;
+    gi = 0;
+    score = 0;
+    minscore = limit + 1;
+
+    for (;;)
+    {
+       // Skip over an equal part, score remains the same.
+       for (;;)
+       {
+           bc = wbadword[bi];
+           gc = wgoodword[gi];
+
+           if (bc != gc)       // stop at a char that's different
+               break;
+           if (bc == NUL)      // both words end
+           {
+               if (score < minscore)
+                   minscore = score;
+               goto pop;       // do next alternative
+           }
+           ++bi;
+           ++gi;
+       }
+
+       if (gc == NUL)    // goodword ends, delete badword chars
+       {
+           do
+           {
+               if ((score += SCORE_DEL) >= minscore)
+                   goto pop;       // do next alternative
+           } while (wbadword[++bi] != NUL);
+           minscore = score;
+       }
+       else if (bc == NUL) // badword ends, insert badword chars
+       {
+           do
+           {
+               if ((score += SCORE_INS) >= minscore)
+                   goto pop;       // do next alternative
+           } while (wgoodword[++gi] != NUL);
+           minscore = score;
+       }
+       else                    // both words continue
+       {
+           // If not close to the limit, perform a change.  Only try changes
+           // that may lead to a lower score than "minscore".
+           // round 0: try deleting a char from badword
+           // round 1: try inserting a char in badword
+           for (round = 0; round <= 1; ++round)
+           {
+               score_off = score + (round == 0 ? SCORE_DEL : SCORE_INS);
+               if (score_off < minscore)
+               {
+                   if (score_off + SCORE_EDIT_MIN >= minscore)
+                   {
+                       // Near the limit, rest of the words must match.  We
+                       // can check that right now, no need to push an item
+                       // onto the stack.
+                       bi2 = bi + 1 - round;
+                       gi2 = gi + round;
+                       while (wgoodword[gi2] == wbadword[bi2])
+                       {
+                           if (wgoodword[gi2] == NUL)
+                           {
+                               minscore = score_off;
+                               break;
+                           }
+                           ++bi2;
+                           ++gi2;
+                       }
+                   }
+                   else
+                   {
+                       // try deleting a character from badword later
+                       stack[stackidx].badi = bi + 1 - round;
+                       stack[stackidx].goodi = gi + round;
+                       stack[stackidx].score = score_off;
+                       ++stackidx;
+                   }
+               }
+           }
+
+           if (score + SCORE_SWAP < minscore)
+           {
+               // If swapping two characters makes a match then the
+               // substitution is more expensive, thus there is no need to
+               // try both.
+               if (gc == wbadword[bi + 1] && bc == wgoodword[gi + 1])
+               {
+                   // Swap two characters, that is: skip them.
+                   gi += 2;
+                   bi += 2;
+                   score += SCORE_SWAP;
+                   continue;
+               }
+           }
+
+           // Substitute one character for another which is the same
+           // thing as deleting a character from both goodword and badword.
+           // Use a better score when there is only a case difference.
+           if (SPELL_TOFOLD(bc) == SPELL_TOFOLD(gc))
+               score += SCORE_ICASE;
+           else
+           {
+               // For a similar character use SCORE_SIMILAR.
+               if (slang != NULL
+                       && slang->sl_has_map
+                       && similar_chars(slang, gc, bc))
+                   score += SCORE_SIMILAR;
+               else
+                   score += SCORE_SUBST;
+           }
+
+           if (score < minscore)
+           {
+               // Do the substitution.
+               ++gi;
+               ++bi;
+               continue;
+           }
+       }
+pop:
+       // Get here to try the next alternative, pop it from the stack.
+       if (stackidx == 0)              // stack is empty, finished
+           break;
+
+       // pop an item from the stack
+       --stackidx;
+       gi = stack[stackidx].goodi;
+       bi = stack[stackidx].badi;
+       score = stack[stackidx].score;
+    }
+
+    // When the score goes over "limit" it may actually be much higher.
+    // Return a very large number to avoid going below the limit when giving a
+    // bonus.
+    if (minscore > limit)
+       return SCORE_MAXMAX;
+    return minscore;
+}
+#endif  // FEAT_SPELL
index 44d4d04148e4f18b59c517bb267115ebc89e4dd0..fa78a08e792cd64642564d82c7ab134b7a1d589e 100644 (file)
@@ -757,6 +757,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    2081,
 /**/
     2080,
 /**/